diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index 01194ef651..980ca594c6 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -31,6 +31,10 @@ import { useSetThreadgateAllowMutation, useThreadgateViewQuery, } from '#/state/queries/threadgate' +import { + PostThreadContextProvider, + usePostThreadContext, +} from '#/state/queries/usePostThread' import {useAgent, useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' @@ -133,10 +137,13 @@ export type PostInteractionSettingsDialogProps = { export function PostInteractionSettingsDialog( props: PostInteractionSettingsDialogProps, ) { + const postThreadContext = usePostThreadContext() return ( - + + + ) } diff --git a/src/screens/PostThread/index.tsx b/src/screens/PostThread/index.tsx index 1e646e1bbd..885adec230 100644 --- a/src/screens/PostThread/index.tsx +++ b/src/screens/PostThread/index.tsx @@ -7,7 +7,11 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useFeedFeedback} from '#/state/feed-feedback' import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences' -import {type ThreadItem, usePostThread} from '#/state/queries/usePostThread' +import { + PostThreadContextProvider, + type ThreadItem, + usePostThread, +} from '#/state/queries/usePostThread' import {useSession} from '#/state/session' import {type OnPostSuccessData} from '#/state/shell/composer' import {useShellLayout} from '#/state/shell/shell-layout' @@ -495,7 +499,7 @@ export function PostThread({uri}: {uri: string}) { const defaultListFooterHeight = hasParents ? windowHeight - 200 : undefined return ( - <> + @@ -578,7 +582,7 @@ export function PostThread({uri}: {uri: string}) { {!gtMobile && canReply && hasSession && ( )} - + ) } diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index 86f1e8d1fd..e4b9241cd6 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -1,6 +1,5 @@ import { AppBskyFeedDefs, - type AppBskyFeedGetPostThread, AppBskyFeedThreadgate, AtUri, type BskyAgent, @@ -8,8 +7,8 @@ import { import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {networkRetry, retry} from '#/lib/async/retry' -import {until} from '#/lib/async/until' import {STALE} from '#/state/queries' +import {useGetPost} from '#/state/queries/post' import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' import { createThreadgateRecord, @@ -17,6 +16,7 @@ import { threadgateAllowUISettingToAllowRecordValue, threadgateViewToAllowUISetting, } from '#/state/queries/threadgate/util' +import {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread' import {useAgent} from '#/state/session' import {useThreadgateHiddenReplyUrisAPI} from '#/state/threadgate-hidden-replies' import * as bsky from '#/types/bsky' @@ -247,6 +247,8 @@ export async function updateThreadgateAllow({ export function useSetThreadgateAllowMutation() { const agent = useAgent() const queryClient = useQueryClient() + const getPost = useGetPost() + const updatePostThreadThreadgate = useUpdatePostThreadThreadgateQueryCache() return useMutation({ mutationFn: async ({ @@ -271,26 +273,31 @@ export function useSetThreadgateAllowMutation() { }) }, async onSuccess(_, {postUri, allow}) { - await until( + const data = await retry( 5, // 5 tries - 1e3, // 1s delay between tries - (res: AppBskyFeedGetPostThread.Response) => { - const thread = res.data.thread - if (AppBskyFeedDefs.isThreadViewPost(thread)) { - const fetchedSettings = threadgateViewToAllowUISetting( - thread.post.threadgate, + _e => true, + async () => { + const post = await getPost({uri: postUri}) + const threadgate = post.threadgate + if (!threadgate) { + throw new Error( + `useSetThreadgateAllowMutation: could not fetch threadgate, appview may not be ready yet`, ) - return JSON.stringify(fetchedSettings) === JSON.stringify(allow) } - return false + const fetchedSettings = threadgateViewToAllowUISetting(threadgate) + const isReady = + JSON.stringify(fetchedSettings) === JSON.stringify(allow) + if (!isReady) { + throw new Error( + `useSetThreadgateAllowMutation: appview isn't ready yet`, + ) // try again + } + return threadgate }, - () => { - return agent.app.bsky.feed.getPostThread({ - uri: postUri, - depth: 0, - }) - }, - ) + 1e3, // 1s delay between tries + ).catch(() => {}) + + if (data) updatePostThreadThreadgate(data) queryClient.invalidateQueries({ queryKey: [threadgateRecordQueryKeyRoot], diff --git a/src/state/queries/usePostThread/context.tsx b/src/state/queries/usePostThread/context.tsx new file mode 100644 index 0000000000..f825965416 --- /dev/null +++ b/src/state/queries/usePostThread/context.tsx @@ -0,0 +1,43 @@ +import {createContext, useContext} from 'react' + +import { + type createPostThreadOtherQueryKey, + type createPostThreadQueryKey, +} from '#/state/queries/usePostThread/types' + +/** + * Contains static metadata about the post thread query, suitable for + * context e.g. query keys and other things that don't update frequently. + * + * Be careful adding things here, as it could cause unnecessary re-renders. + */ +export type PostThreadContextType = { + postThreadQueryKey: ReturnType + postThreadOtherQueryKey: ReturnType +} + +const PostThreadContext = createContext( + undefined, +) + +/** + * Use the current {@link PostThreadContext}, if one is available. If not, + * returns `undefined`. + */ +export function usePostThreadContext() { + return useContext(PostThreadContext) +} + +export function PostThreadContextProvider({ + children, + context, +}: { + children: React.ReactNode + context?: PostThreadContextType +}) { + return ( + + {children} + + ) +} diff --git a/src/state/queries/usePostThread/index.ts b/src/state/queries/usePostThread/index.tsx similarity index 94% rename from src/state/queries/usePostThread/index.ts rename to src/state/queries/usePostThread/index.tsx index 8495df04c8..bf3bb31581 100644 --- a/src/state/queries/usePostThread/index.ts +++ b/src/state/queries/usePostThread/index.tsx @@ -31,6 +31,8 @@ import {useAgent, useSession} from '#/state/session' import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' import {useBreakpoints} from '#/alf' +export * from '#/state/queries/usePostThread/context' +export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache' export * from '#/state/queries/usePostThread/types' export function usePostThread({anchor}: {anchor?: string}) { @@ -277,8 +279,13 @@ export function usePostThread({anchor}: {anchor?: string}) { setOtherItemsVisible, ]) - return useMemo( - () => ({ + return useMemo(() => { + const context: PostThreadContextType = { + postThreadQueryKey, + postThreadOtherQueryKey, + } + return { + context, state: { /* * Copy in any query state that is useful @@ -309,17 +316,18 @@ export function usePostThread({anchor}: {anchor?: string}) { setSort, setView, }, - }), - [ - query, - mutator.insertReplies, - otherItemsVisible, - sort, - view, - setSort, - setView, - threadgate, - items, - ], - ) + } + }, [ + query, + mutator.insertReplies, + otherItemsVisible, + sort, + view, + setSort, + setView, + threadgate, + items, + postThreadQueryKey, + postThreadOtherQueryKey, + ]) } diff --git a/src/state/queries/usePostThread/queryCache.ts b/src/state/queries/usePostThread/queryCache.ts index 5e27ebb875..5f9f263280 100644 --- a/src/state/queries/usePostThread/queryCache.ts +++ b/src/state/queries/usePostThread/queryCache.ts @@ -1,3 +1,4 @@ +import {useCallback} from 'react' import { type $Typed, type AppBskyActorDefs, @@ -7,7 +8,7 @@ import { type AppBskyUnspeccedGetPostThreadV2, AtUri, } from '@atproto/api' -import {type QueryClient} from '@tanstack/react-query' +import {type QueryClient, useQueryClient} from '@tanstack/react-query' import { dangerousGetPostShadow, @@ -18,6 +19,7 @@ import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/ import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed' import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes' import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts' +import {usePostThreadContext} from '#/state/queries/usePostThread' import {getBranch} from '#/state/queries/usePostThread/traversal' import { type ApiThreadItem, @@ -322,3 +324,51 @@ export function* findAllProfilesInQueryData( } } } + +export function useUpdatePostThreadThreadgateQueryCache() { + const qc = useQueryClient() + const context = usePostThreadContext() + + return useCallback( + (threadgate: AppBskyFeedDefs.ThreadgateView) => { + if (!context) return + + function mutator(thread: ApiThreadItem[]): T[] { + for (let i = 0; i < thread.length; i++) { + const item = thread[i] + + if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) continue + + if (item.depth === 0) { + thread.splice(i, 1, { + ...item, + value: { + ...item.value, + post: { + ...item.value.post, + threadgate, + }, + }, + }) + } + } + + return thread as T[] + } + + qc.setQueryData( + context.postThreadQueryKey, + data => { + if (!data) return + return { + ...data, + thread: mutator([ + ...data.thread, + ]), + } + }, + ) + }, + [qc, context], + ) +}