From 5d78f69d950792d64223cc38897019518b8a6368 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 12 Aug 2024 15:33:50 -0500 Subject: [PATCH] Plumbing --- src/components/WhoCanReply.tsx | 178 +++------------- .../dialogs/PostInteractionSettingsDialog.tsx | 190 +++++++++++++++++- src/state/queries/threadgate/index.ts | 1 + .../com/composer/threadgate/ThreadgateBtn.tsx | 5 +- src/view/com/post-thread/PostThread.tsx | 1 + src/view/com/post-thread/PostThreadItem.tsx | 40 +--- src/view/com/util/forms/PostDropdownBtn.tsx | 187 ++++------------- src/view/com/util/post-ctrls/PostCtrls.tsx | 3 - 8 files changed, 260 insertions(+), 345 deletions(-) diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx index 11db92d06c..72f373bc09 100644 --- a/src/components/WhoCanReply.tsx +++ b/src/components/WhoCanReply.tsx @@ -2,32 +2,20 @@ import React from 'react' import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' import { AppBskyFeedDefs, - AppBskyFeedGetPostThread, - AppBskyFeedPostgate, + AppBskyFeedPost, AppBskyGraphDefs, AtUri, - BskyAgent, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useQueryClient} from '@tanstack/react-query' -import {until} from '#/lib/async/until' import {HITSLOP_10} from '#/lib/constants' import {makeListLink, makeProfileLink} from '#/lib/routes/links' -import {logger} from '#/logger' import {isNative} from '#/platform/detection' -import {RQKEY_ROOT as POST_THREAD_RQKEY_ROOT} from '#/state/queries/post-thread' -import {useWritePostgateMutation} from '#/state/queries/postgate' -import {embeddingRules} from '#/state/queries/postgate/util' import { ThreadgateAllowUISetting, - threadgateRecordQueryKeyRoot, threadgateViewToAllowUISetting, - updateThreadgateAllow, } from '#/state/queries/threadgate' -import {useAgent} from '#/state/session' -import * as Toast from 'view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -44,29 +32,25 @@ interface WhoCanReplyProps { post: AppBskyFeedDefs.PostView isThreadAuthor: boolean style?: StyleProp - postgate: AppBskyFeedPostgate.Record } -export function WhoCanReply({ - post, - isThreadAuthor, - style, - postgate: initialPostgate, -}: WhoCanReplyProps) { +export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { const {_} = useLingui() const t = useTheme() const infoDialogControl = useDialogControl() const editDialogControl = useDialogControl() - const agent = useAgent() - const queryClient = useQueryClient() - const [isSaving, setIsSaving] = React.useState(false) - const {mutateAsync: writePostgateRecord} = useWritePostgateMutation() - const [postgate, setPostgate] = React.useState(initialPostgate) - // TODO test if we get weird data back - const [settings, setSettings] = React.useState( - threadgateViewToAllowUISetting(post.threadgate), - ) + /* + * `WhoCanReply` is only used for root posts atm, in case this changes + * unexpectedly, we should check to make sure it's for sure the root URI. + */ + const rootUri = + AppBskyFeedPost.isRecord(post.record) && post.record.reply?.root + ? post.record.reply.root.uri + : post.uri + const settings = React.useMemo(() => { + return threadgateViewToAllowUISetting(post.threadgate) + }, [post.threadgate]) const anyoneCanReply = settings.length === 1 && settings[0].type === 'everybody' @@ -88,95 +72,6 @@ export function WhoCanReply({ } } - const onChangePostgate = React.useCallback( - (next: AppBskyFeedPostgate.Record) => { - setPostgate(next) - }, - [setPostgate], - ) - - const onChangeThreadgateAllowUISettings = React.useCallback( - (next: ThreadgateAllowUISetting[]) => { - setSettings(next) - }, - [setSettings], - ) - - const saveThreadgateAllowSettings = React.useCallback(async () => { - try { - await updateThreadgateAllow({ - agent, - postUri: post.uri, - allow: settings, - }) - - await whenAppViewReady(agent, post.uri, res => { - const thread = res.data.thread - if (AppBskyFeedDefs.isThreadViewPost(thread)) { - const fetchedSettings = threadgateViewToAllowUISetting( - thread.post.threadgate, - ) - return JSON.stringify(fetchedSettings) === JSON.stringify(settings) - } - return false - }) - - queryClient.invalidateQueries({ - queryKey: [POST_THREAD_RQKEY_ROOT], - }) - queryClient.invalidateQueries({ - queryKey: [threadgateRecordQueryKeyRoot], - }) - } catch (e: any) { - logger.error('Failed to edit threadgate', {safeMessage: e.message}) - Toast.show( - _( - msg`There was an issue. Please check your internet connection and try again.`, - ), - 'xmark', - ) - } - }, [_, agent, post, settings, queryClient]) - - const savePostgateRecord = React.useCallback(async () => { - try { - await writePostgateRecord({postUri: post.uri, postgate}) - } catch (e: any) { - logger.error('Failed to save postgate', {safeMessage: e.message}) - Toast.show( - _( - msg`There was an issue. Please check your internet connection and try again.`, - ), - 'xmark', - ) - } - }, [_, post, postgate, writePostgateRecord]) - - const onSave = React.useCallback(async () => { - setIsSaving(true) - - try { - await Promise.all([saveThreadgateAllowSettings(), savePostgateRecord()]) - editDialogControl.close() - Toast.show(_(msg`Thread settings updated`)) - } catch (e: any) { - Toast.show( - _( - msg`There was an issue. Please check your internet connection and try again.`, - ), - 'xmark', - ) - } finally { - setIsSaving(false) - } - }, [ - _, - editDialogControl, - saveThreadgateAllowSettings, - savePostgateRecord, - setIsSaving, - ]) - return ( <> - { control.close() }} diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index d21100b40d..a5801db705 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -443,6 +443,7 @@ export function PostThread({uri}: {uri: string | undefined}) { void hideTopBorder?: boolean + threadgateRecord?: AppBskyFeedThreadgate.Record }) { // TODO should load this for every post, ugh - const {data: postgate, isLoading} = usePostgateQuery({postUri: post.uri}) const postShadowed = usePostShadow(post) const richText = useMemo( () => @@ -97,7 +95,7 @@ export function PostThreadItem({ if (postShadowed === POST_TOMBSTONE) { return } - if (richText && moderation && !isLoading) { + if (richText && moderation) { return ( ) } @@ -162,7 +160,7 @@ let PostThreadItemLoaded = ({ overrideBlur, onPostReply, hideTopBorder, - postgate, + threadgateRecord, }: { post: Shadow record: AppBskyFeedPost.Record @@ -180,7 +178,7 @@ let PostThreadItemLoaded = ({ overrideBlur: boolean onPostReply: (postUri: string | undefined) => void hideTopBorder?: boolean - postgate: AppBskyFeedPostgate.Record | undefined + threadgateRecord?: AppBskyFeedThreadgate.Record }): React.ReactNode => { const pal = usePalette('default') const {_} = useLingui() @@ -209,9 +207,6 @@ let PostThreadItemLoaded = ({ return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') }, [post.uri, post.author]) const repostsTitle = _(msg`Reposts of this post`) - const {data: threadgateRecord} = useThreadgateRecordQuery({ - postUri: rootUri, - }) const isPostHiddenByThreadgate = threadgateRecord?.hiddenReplies?.includes( post.uri, ) @@ -370,7 +365,6 @@ let PostThreadItemLoaded = ({ isThreadAuthor={isThreadAuthor} translatorUrl={translatorUrl} needsTranslation={needsTranslation} - postgate={postgate} /> {post.repostCount !== 0 || post.likeCount !== 0 ? ( // Show this section unless we're *sure* it has no engagement. @@ -421,8 +415,7 @@ let PostThreadItemLoaded = ({ richText={richText} onPressReply={onPressReply} logContext="PostThreadItem" - rootPostUri={rootUri} - threadgateRecord={threadgateRecord ?? undefined} + threadgateRecord={threadgateRecord} /> @@ -575,8 +568,7 @@ let PostThreadItemLoaded = ({ richText={richText} onPressReply={onPressReply} logContext="PostThreadItem" - rootPostUri={rootUri} - threadgateRecord={threadgateRecord ?? undefined} + threadgateRecord={threadgateRecord} /> @@ -674,13 +666,11 @@ function ExpandedPostDetails({ isThreadAuthor, needsTranslation, translatorUrl, - postgate, }: { post: AppBskyFeedDefs.PostView isThreadAuthor: boolean needsTranslation: boolean translatorUrl: string - postgate: AppBskyFeedPostgate.Record | undefined }) { const pal = usePalette('default') const {_} = useLingui() @@ -703,17 +693,7 @@ function ExpandedPostDetails({ ]}> {niceDate(post.indexedAt)} {isRootPost && ( - + )} {needsTranslation && ( <> diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 1f78f81e83..bdff4ddbee 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -9,7 +9,6 @@ import * as Clipboard from 'expo-clipboard' import { AppBskyFeedDefs, AppBskyFeedPost, - AppBskyFeedPostgate, AppBskyFeedThreadgate, AtUri, RichText as RichTextAPI, @@ -33,17 +32,9 @@ import { usePostDeleteMutation, useThreadMuteMutationQueue, } from '#/state/queries/post' -import { - usePostgateQuery, - useToggleQuoteDetachmentMutation, - useWritePostgateMutation, -} from '#/state/queries/postgate' -import { - createPostgateRecord, - getMaybeDetachedQuoteEmbed, -} from '#/state/queries/postgate/util' +import {useToggleQuoteDetachmentMutation} from '#/state/queries/postgate' +import {getMaybeDetachedQuoteEmbed} from '#/state/queries/postgate/util' import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' -import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate/util' import {useSession} from '#/state/session' import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' @@ -51,10 +42,9 @@ import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf' import {useDialogControl} from '#/components/Dialog' -import * as Dialog from '#/components/Dialog' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {EmbedDialog} from '#/components/dialogs/Embed' -import {PostInteractionSettingsDialogInner} from '#/components/dialogs/PostInteractionSettingsDialog' +import {PostInteractionSettingsDialog} from '#/components/dialogs/PostInteractionSettingsDialog' import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' @@ -91,7 +81,6 @@ let PostDropdownBtn = ({ hitSlop, size, timestamp, - rootPostUri, threadgateRecord, }: { testID: string @@ -103,7 +92,6 @@ let PostDropdownBtn = ({ hitSlop?: PressableProps['hitSlop'] size?: 'lg' | 'md' | 'sm' timestamp: string - rootPostUri?: string threadgateRecord?: AppBskyFeedThreadgate.Record }): React.ReactNode => { const {hasSession, currentAccount} = useSession() @@ -282,36 +270,6 @@ let PostDropdownBtn = ({ [navigation, postUri], ) - const onToggleReplyVisibility = React.useCallback(async () => { - // TODO no threadgate? - if (!rootPostUri) return - - const action = isReplyHiddenByThreadgate ? 'show' : 'hide' - const isHide = action === 'hide' - - try { - await toggleReplyVisibility({ - postUri: rootPostUri, - replyUri: postUri, - action, - }) - Toast.show( - isHide - ? _(msg`Reply was successfully hidden`) - : _(msg`Reply visibility updated`), - ) - } catch (e: any) { - Toast.show(_(msg`Updating reply visibility failed`)) - logger.error(`Failed to ${action} reply`, {safeMessage: e.message}) - } - }, [ - _, - isReplyHiddenByThreadgate, - rootPostUri, - postUri, - toggleReplyVisibility, - ]) - const onToggleQuotePostAttachment = React.useCallback(async () => { if (!quoteEmbed) return @@ -336,10 +294,40 @@ let PostDropdownBtn = ({ }, [_, quoteEmbed, post, toggleQuoteDetachment]) const canEmbed = isWeb && gtMobile && !hideInPWI - const canHideReply = - !isAuthor && isRootPostAuthor && !isPostHidden && rootPostUri && isReply + const canHideReply = !isAuthor && isRootPostAuthor && !isPostHidden && isReply const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer + const onToggleReplyVisibility = React.useCallback(async () => { + // TODO no threadgate? + if (!canHideReply) return + + const action = isReplyHiddenByThreadgate ? 'show' : 'hide' + const isHide = action === 'hide' + + try { + await toggleReplyVisibility({ + postUri: rootUri, + replyUri: postUri, + action, + }) + Toast.show( + isHide + ? _(msg`Reply was successfully hidden`) + : _(msg`Reply visibility updated`), + ) + } catch (e: any) { + Toast.show(_(msg`Updating reply visibility failed`)) + logger.error(`Failed to ${action} reply`, {safeMessage: e.message}) + } + }, [ + _, + isReplyHiddenByThreadgate, + rootUri, + postUri, + canHideReply, + toggleReplyVisibility, + ]) + return ( @@ -643,114 +631,15 @@ let PostDropdownBtn = ({ onSelectChat={onSelectChatToShareTo} /> - ) } -export function PostInteractionSettingsDialogControlled(props: { - control: Dialog.DialogControlProps - postUri: string - threadgateRecord?: AppBskyFeedThreadgate.Record - replySettingsDisabled: boolean -}) { - return ( - - - - - ) -} - -function PostInteractionSettingsDialogControlledInner(props: { - control: Dialog.DialogControlProps - postUri: string - threadgateRecord?: AppBskyFeedThreadgate.Record - replySettingsDisabled: boolean -}) { - const {_} = useLingui() - const threadgateAllowUISettings = React.useMemo(() => { - return threadgateRecordToAllowUISetting(props.threadgateRecord) - }, [props.threadgateRecord]) - const [isSaving, setIsSaving] = React.useState(false) - const {mutateAsync: writePostgateRecord} = useWritePostgateMutation() - const {data: postgate, isLoading} = usePostgateQuery({postUri: props.postUri}) - const [editedPostgate, setEditedPostgate] = - React.useState() - - const onChangePostgate = React.useCallback( - (next: AppBskyFeedPostgate.Record) => { - setEditedPostgate(next) - }, - [setEditedPostgate], - ) - - const onSave = React.useCallback(async () => { - if (!editedPostgate) { - props.control.close() - return - } - - setIsSaving(true) - - try { - await writePostgateRecord({ - postUri: props.postUri, - postgate: editedPostgate, - }) - props.control.close() - } catch (e: any) { - logger.error(`Failed to save post interaction settings`, { - context: 'PostInteractionSettingsDialogControlledInner', - safeMessage: e.message, - }) - Toast.show( - _( - msg`There was an issue. Please check your internet connection and try again.`, - ), - 'xmark', - ) - } finally { - setIsSaving(false) - } - }, [ - _, - props.postUri, - props.control, - editedPostgate, - setIsSaving, - writePostgateRecord, - ]) - - return ( - - {isLoading ? ( - - ) : ( - {}} - /> - )} - - ) -} - PostDropdownBtn = memo(PostDropdownBtn) export {PostDropdownBtn} diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 9d2be2c4ca..16bd757c44 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -60,7 +60,6 @@ let PostCtrls = ({ style, onPressReply, logContext, - rootPostUri, threadgateRecord, }: { big?: boolean @@ -71,7 +70,6 @@ let PostCtrls = ({ style?: StyleProp onPressReply: () => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' - rootPostUri?: string threadgateRecord?: AppBskyFeedThreadgate.Record }): React.ReactNode => { const t = useTheme() @@ -344,7 +342,6 @@ let PostCtrls = ({ style={{padding: 5}} hitSlop={POST_CTRL_HITSLOP} timestamp={post.indexedAt} - rootPostUri={rootPostUri} threadgateRecord={threadgateRecord} />