From 4ecaa951eecc66fed2d4cdee9b93dbb4141e0b72 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 30 May 2025 13:51:54 -0500 Subject: [PATCH] Refactor client-side hidden --- src/screens/PostThread/index.tsx | 46 +++++--------- src/state/queries/usePostThread/index.ts | 67 ++++++++++++++------ src/state/queries/usePostThread/traversal.ts | 47 ++++---------- src/state/queries/usePostThread/types.ts | 10 +-- 4 files changed, 73 insertions(+), 97 deletions(-) diff --git a/src/screens/PostThread/index.tsx b/src/screens/PostThread/index.tsx index 62b76fe567..aa22532ede 100644 --- a/src/screens/PostThread/index.tsx +++ b/src/screens/PostThread/index.tsx @@ -9,11 +9,7 @@ import {ScrollProvider} from '#/lib/ScrollContext' import {cleanError} from '#/lib/strings/errors' import {isNative} from '#/platform/detection' import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences' -import { - HiddenReplyKind, - type ThreadItem, - usePostThread, -} from '#/state/queries/usePostThread' +import {type ThreadItem, usePostThread} from '#/state/queries/usePostThread' import {type OnPostSuccessData} from '#/state/shell/composer' import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposePrompt' import {PostThreadShowHiddenReplies} from '#/view/com/post-thread/PostThreadShowHiddenReplies' @@ -53,22 +49,16 @@ export function Inner({uri}: {uri: string | undefined}) { setTreeViewEnabled, } = useThreadPreferences() - const [shownHiddenReplyKinds, setShownHiddenReplyKinds] = useState< - Set - >(new Set()) - - const {isFetching, error, data, refetch, insertReplies} = usePostThread({ - enabled: isThreadPreferencesLoaded, - params: { - anchor: uri, - sort: sortReplies, - view: treeViewEnabled ? 'tree' : 'linear', - prioritizeFollowedUsers, - }, - state: { - shownHiddenReplyKinds, - }, - }) + const {isFetching, error, data, refetch, insertReplies, showHiddenReplies} = + usePostThread({ + enabled: isThreadPreferencesLoaded, + params: { + anchor: uri, + sort: sortReplies, + view: treeViewEnabled ? 'tree' : 'linear', + prioritizeFollowedUsers, + }, + }) const optimisticOnPostReply = (data: OnPostSuccessData) => { if (data) { @@ -242,9 +232,7 @@ export function Inner({uri}: {uri: string | undefined}) { item={item} threadgateRecord={data?.threadgate?.record ?? undefined} overrides={{ - moderation: - shownHiddenReplyKinds.has(HiddenReplyKind.Hidden) && - item.depth > 0, + moderation: showHiddenReplies && item.depth > 0, }} onPostSuccess={optimisticOnPostReply} /> @@ -255,9 +243,7 @@ export function Inner({uri}: {uri: string | undefined}) { item={item} threadgateRecord={data?.threadgate?.record ?? undefined} overrides={{ - moderation: - shownHiddenReplyKinds.has(HiddenReplyKind.Muted) && - item.depth > 0, + moderation: showHiddenReplies && item.depth > 0, }} onPostSuccess={optimisticOnPostReply} /> @@ -305,10 +291,8 @@ export function Inner({uri}: {uri: string | undefined}) { } else if (item.type === 'showHiddenReplies') { return ( - setShownHiddenReplyKinds(kinds => new Set([...kinds, item.kind])) - } + type="hidden" + onPress={() => item.onLoad()} /> ) } else if (item.type === 'skeleton') { diff --git a/src/state/queries/usePostThread/index.ts b/src/state/queries/usePostThread/index.ts index 7c72e91b4e..c414614f8f 100644 --- a/src/state/queries/usePostThread/index.ts +++ b/src/state/queries/usePostThread/index.ts @@ -1,4 +1,4 @@ -import {useMemo} from 'react' +import {useCallback, useMemo, useRef, useState} from 'react' import {useQuery, useQueryClient} from '@tanstack/react-query' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -9,7 +9,6 @@ import { import {traverse} from '#/state/queries/usePostThread/traversal' import { createPostThreadQueryKey, - HiddenReplyKind, type UsePostThreadProps, } from '#/state/queries/usePostThread/types' import {getThreadgateRecord} from '#/state/queries/usePostThread/utils' @@ -21,7 +20,6 @@ export * from '#/state/queries/usePostThread/types' export function usePostThread({ enabled: isEnabled, params, - state, }: UsePostThreadProps) { const qc = useQueryClient() const agent = useAgent() @@ -34,6 +32,9 @@ export function usePostThread({ params, }) + const hasHiddenReplies = useRef(false) + const [showHiddenReplies, setShowHiddenReplies] = useState(false) + const query = useQuery({ enabled, queryKey, @@ -50,10 +51,17 @@ export function usePostThread({ }, placeholderData() { if (!params.anchor) return + const placeholder = getThreadPlaceholder(qc, params.anchor) + if (placeholder) { return {thread: [placeholder], hasHiddenReplies: false} } + + /* + * Return empty data here so that `isPlaceholderData` is always true, + * which we'll use to insert skeletons. + */ return {thread: [], hasHiddenReplies: false} }, select(data) { @@ -68,7 +76,13 @@ export function usePostThread({ }, }) - // TODO map over pages, just like feeds + if (query?.data?.hasHiddenReplies) { + hasHiddenReplies.current = true + } + + const loadHiddenReplies = useCallback(async () => { + setShowHiddenReplies(true) + }, [setShowHiddenReplies]) const items = useMemo(() => { return traverse(query.data?.thread || [], { @@ -77,25 +91,21 @@ export function usePostThread({ ), moderationOpts: moderationOpts!, hasSession, - showMuted: state.shownHiddenReplyKinds.has(HiddenReplyKind.Muted), - showHidden: state.shownHiddenReplyKinds.has(HiddenReplyKind.Hidden), view: params.view, + hasHiddenReplies: hasHiddenReplies.current, + showHiddenReplies, + loadHiddenReplies, }) }, [ query.data, mergeThreadgateHiddenReplies, moderationOpts, hasSession, - state.shownHiddenReplyKinds, params.view, + showHiddenReplies, + loadHiddenReplies, ]) - const mutator = createCacheMutator({ - params, - queryKey, - queryClient: qc, - }) - if (query.isPlaceholderData) { const anchor = items.at(0) const skeletonReplies = @@ -128,12 +138,27 @@ export function usePostThread({ } } - return { - ...query, - data: { - items, - threadgate: query.data?.threadgate, - }, - insertReplies: mutator.insertReplies, - } + const mutator = useMemo( + () => + createCacheMutator({ + params, + queryKey, + queryClient: qc, + }), + [qc, params, queryKey], + ) + + return useMemo( + () => ({ + ...query, + data: { + items, + threadgate: query.data?.threadgate, + }, + hasHiddenReplies: hasHiddenReplies.current, + showHiddenReplies, + insertReplies: mutator.insertReplies, + }), + [query, items, mutator.insertReplies, showHiddenReplies], + ) } diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 65895e0ba1..6e1e61b78a 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -5,7 +5,6 @@ import { } from '@atproto/api' import { - HiddenReplyKind, type PostThreadParams, type ThreadItem, type TraversalMetadata, @@ -24,21 +23,22 @@ export function traverse( threadgateHiddenReplies, moderationOpts, hasSession, - showMuted, - showHidden, view, + hasHiddenReplies, + showHiddenReplies, + loadHiddenReplies, }: { threadgateHiddenReplies: Set moderationOpts: ModerationOpts hasSession: boolean - showMuted: boolean - showHidden: boolean view: PostThreadParams['view'] + hasHiddenReplies: boolean + showHiddenReplies: boolean + loadHiddenReplies: () => Promise }, ) { const items: ThreadItem[] = [] const hidden: ThreadItem[] = [] - const muted: ThreadItem[] = [] const metadatas = new Map() traversal: for (let i = 0; i < thread.length; i++) { @@ -178,16 +178,13 @@ export function traverse( * Moderated in some way, we're going to walk children */ const parent = post - const parentMod = postMod const parentIsTopLevelReply = parent.depth === 1 - const sortArray = parentMod.muted ? muted : hidden - // get sub tree const branch = getBranch(thread, i, item.depth) if (parentIsTopLevelReply) { // push branch anchor into sorted array - sortArray.push(parent) + hidden.push(parent) // skip branch anchor in branch traversal const startIndex = branch.start + 1 @@ -238,7 +235,7 @@ export function traverse( ) { ci = getBranch(thread, ci, child.depth).end } else { - sortArray.push(childPost) + hidden.push(childPost) } } else { /* @@ -259,36 +256,14 @@ export function traverse( } } - if (hidden.length) { - if (showHidden) { + if (hidden.length || hasHiddenReplies) { + if (showHiddenReplies) { items.push(...hidden) - - if (muted.length) { - if (showMuted) { - items.push(...muted) - } else { - items.push({ - type: 'showHiddenReplies', - key: 'showMutedReplies', - kind: HiddenReplyKind.Muted, - }) - } - } } else { items.push({ type: 'showHiddenReplies', key: 'showHiddenReplies', - kind: HiddenReplyKind.Hidden, - }) - } - } else if (muted.length) { - if (showMuted) { - items.push(...muted) - } else { - items.push({ - type: 'showHiddenReplies', - key: 'showMutedReplies', - kind: HiddenReplyKind.Muted, + onLoad: loadHiddenReplies, }) } } diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index f849543175..2035729c95 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -22,14 +22,6 @@ export type PostThreadParams = Pick< export type UsePostThreadProps = { enabled?: boolean params: PostThreadParams - state: { - shownHiddenReplyKinds: Set - } -} - -export enum HiddenReplyKind { - Hidden = 'hidden', - Muted = 'muted', } export type ThreadItem = @@ -82,7 +74,7 @@ export type ThreadItem = | { type: 'showHiddenReplies' key: string - kind: HiddenReplyKind + onLoad: () => Promise } | { type: 'readMore'