[Threads V2] Preliminary integration of unspecced V2 APIs (#8443)
* WIP * Sorting working * Rough handling of hidden/muted * Better muted/hidden sorting and handling * Clarify some naming * Fix parents * Handle first reply under highlighted/composer * WIP RaW * WIP optimistic * Optimistic WIP * Little cleanup, inserting dupes * Re-org * Add in new optimistic insert logic * Update types * Sorta working linear view optimistic state * Simple working version, no pref for OP * Working optimistic reply insertions, preference for OP * Ensure deletes are coming through * WIP scroll handling * WIP scroll tweaks * Clean up scrolling * Clean up onPostSuccess * Add annotations * Fix highlighted post calc * WIP kill me * Update APIs * Nvm don't kill me * Fix optimistic insert * Handle read more cases in tree view * Basically working read more * Handle linear view * Reorg * More reorg * Split up thread post components * New reply tree layout * Fix up traversal metadata * Tighten some spacing * Use indent ya idiot * Some linear mode cleanup * Fix lines on read more items * Vibe coding to success * Almost there with read mores * Update APIs * Bump sdk * Update import * Checkpoint new traversal * Checkpoint cleanup * Checkpoint, need to fix blocked posts * Checkpoint: think we're good, needs more cleanup * Clean it up * Two passes only * Set to default params, update comment * Fix render bug on native * Checkpoint parent rendering, can opt for slower handling here * Clean up parent handling, reply handling * Fix read more extra space * Fix read more in linear view * Fix hidden reply handling, seen count, before/after calc * Update naming * Rename Slice to ThreadItem * Add basic post and anchor skeletons * Refactor client-side hidden * WIP hidden fetching * Update types * Clean up query a bit * Scrolling still broken * Ok maybe fix scrolling * Checkpoint move state into meta query * Don't load remote hidden items unless needed * skeleton view * Reset hidden items when params change * Split up traversal and avoid multiple passes * Clean up * Checkpoint: handling exhausted replies * Clean up traversal functions further * Clean up pagination * Limit optimistic reply depth * Handle optimistic insert in hidden replies * Share root query key for easier cache extraction * Make blurred posts not look like ass * Fix double deleted item * Make optimistic deleted state not look like crap in tree view * Fix parents traversal 4 real * Rename tree post * Make optimistic deletions of linear posts not look bad * Rename linear post components * Handle tombstone views * Rename read more component * Add moreParents handling * Align interaction states of read more * Fix read more on FF * Tree view skeleton * Reply composer skele * Remove hack for showing more replies * Checkpoint: sort change scrolling fixed * Checkpoint: learned new things, reset to base * Feature gate * Rename * Replace show more * Update settings screen * Update pkg and endpoint * Remove console * Eureka * Cleanup last commit * No tests atm * Remove scroll provider * Clean up callbacks, better error state * Remove todo * Remove todo * Remove todos * Format * Ok I think scrolling is solid * Add back mobile compose input * Ok need to compute headerHeight every time * Update comments * Ok button up web too * Threads v2 tweaks (#8467) * fix error screen collapsing * use personx icon for blocked posts * Remove height/width * Revert unused Header change * Clarify code * Relate consts to theme values * Remove debug code * Typo * Fix debounce of threads prefs * Update metadata comments, dev mode * Missed a spot * Clean up todo * Fix up no-unauthenticated posts * Truncate parents if no-unauth * Update getBranch docs * Remove debug code * Expand fetching in some cases * Clear scroll need for root post to fix jump bug * Fix reply composer skeleton state * Remove uneeded initialized value * Add profile shadow cache * Some metrics * prettier tweak * eslint ignore * Fix optimistic insertion * Typo * Rename, comment * Remove wait * Counter naming * Replies seen counter for moderated sub-trees * Remove borders on skeleton * Align tombstone with optimistic deletion state * Fix optimistic deletion for thread * Add tree view icon * Rename * Cleanup * Update settings copy * Header menu open metric * Bump package * Better reply prompt (#8474) * restyle reply prompt * hide bottom bar border for cleaner look * use new border hiding hook in DMs * create `transparentifyColor` function * adjust padding * fix padding in immersive lpayer * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Integrate post-source (cherry picked from commit fe053e9b38395a4fcb30a4367bc800f64ea84fe9) --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
import {useCallback, useMemo, useState} from 'react'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {
|
||||
LINEAR_VIEW_BELOW,
|
||||
LINEAR_VIEW_BF,
|
||||
TREE_VIEW_BELOW,
|
||||
TREE_VIEW_BELOW_DESKTOP,
|
||||
TREE_VIEW_BF,
|
||||
} from '#/state/queries/usePostThread/const'
|
||||
import {
|
||||
createCacheMutator,
|
||||
getThreadPlaceholder,
|
||||
} from '#/state/queries/usePostThread/queryCache'
|
||||
import {
|
||||
buildThread,
|
||||
sortAndAnnotateThreadItems,
|
||||
} from '#/state/queries/usePostThread/traversal'
|
||||
import {
|
||||
createPostThreadOtherQueryKey,
|
||||
createPostThreadQueryKey,
|
||||
type ThreadItem,
|
||||
type UsePostThreadQueryResult,
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
|
||||
import * as views from '#/state/queries/usePostThread/views'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {useBreakpoints} from '#/alf'
|
||||
|
||||
export * from '#/state/queries/usePostThread/types'
|
||||
|
||||
export function usePostThread({anchor}: {anchor?: string}) {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const {hasSession} = useSession()
|
||||
const {gtPhone} = useBreakpoints()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const mergeThreadgateHiddenReplies = useMergeThreadgateHiddenReplies()
|
||||
const {
|
||||
isLoaded: isThreadPreferencesLoaded,
|
||||
sort,
|
||||
setSort: baseSetSort,
|
||||
view,
|
||||
setView: baseSetView,
|
||||
prioritizeFollowedUsers,
|
||||
} = useThreadPreferences()
|
||||
const below = useMemo(() => {
|
||||
return view === 'linear'
|
||||
? LINEAR_VIEW_BELOW
|
||||
: isWeb && gtPhone
|
||||
? TREE_VIEW_BELOW_DESKTOP
|
||||
: TREE_VIEW_BELOW
|
||||
}, [view, gtPhone])
|
||||
|
||||
const postThreadQueryKey = createPostThreadQueryKey({
|
||||
anchor,
|
||||
sort,
|
||||
view,
|
||||
prioritizeFollowedUsers,
|
||||
})
|
||||
const postThreadOtherQueryKey = createPostThreadOtherQueryKey({
|
||||
anchor,
|
||||
prioritizeFollowedUsers,
|
||||
})
|
||||
|
||||
const query = useQuery<UsePostThreadQueryResult>({
|
||||
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
||||
queryKey: postThreadQueryKey,
|
||||
async queryFn(ctx) {
|
||||
const {data} = await agent.app.bsky.unspecced.getPostThreadV2({
|
||||
anchor: anchor!,
|
||||
branchingFactor: view === 'linear' ? LINEAR_VIEW_BF : TREE_VIEW_BF,
|
||||
below,
|
||||
sort: sort,
|
||||
prioritizeFollowedUsers: prioritizeFollowedUsers,
|
||||
})
|
||||
|
||||
/*
|
||||
* Initialize `ctx.meta` to track if we know we have additional replies
|
||||
* we could fetch once we hit the end.
|
||||
*/
|
||||
ctx.meta = ctx.meta || {
|
||||
hasOtherReplies: false,
|
||||
}
|
||||
|
||||
/*
|
||||
* If we know we have additional replies, we'll set this to true.
|
||||
*/
|
||||
if (data.hasOtherReplies) {
|
||||
ctx.meta.hasOtherReplies = true
|
||||
}
|
||||
|
||||
const result = {
|
||||
thread: data.thread || [],
|
||||
threadgate: data.threadgate,
|
||||
hasOtherReplies: !!ctx.meta.hasOtherReplies,
|
||||
}
|
||||
|
||||
const record = getThreadgateRecord(result.threadgate)
|
||||
if (result.threadgate && record) {
|
||||
result.threadgate.record = record
|
||||
}
|
||||
|
||||
return result as UsePostThreadQueryResult
|
||||
},
|
||||
placeholderData() {
|
||||
if (!anchor) return
|
||||
const placeholder = getThreadPlaceholder(qc, anchor)
|
||||
/*
|
||||
* Always return something here, even empty data, so that
|
||||
* `isPlaceholderData` is always true, which we'll use to insert
|
||||
* skeletons.
|
||||
*/
|
||||
const thread = placeholder ? [placeholder] : []
|
||||
return {thread, threadgate: undefined, hasOtherReplies: false}
|
||||
},
|
||||
select(data) {
|
||||
const record = getThreadgateRecord(data.threadgate)
|
||||
if (data.threadgate && record) {
|
||||
data.threadgate.record = record
|
||||
}
|
||||
return data
|
||||
},
|
||||
})
|
||||
|
||||
const thread = useMemo(() => query.data?.thread || [], [query.data?.thread])
|
||||
const threadgate = useMemo(
|
||||
() => query.data?.threadgate,
|
||||
[query.data?.threadgate],
|
||||
)
|
||||
const hasOtherThreadItems = useMemo(
|
||||
() => !!query.data?.hasOtherReplies,
|
||||
[query.data?.hasOtherReplies],
|
||||
)
|
||||
const [otherItemsVisible, setOtherItemsVisible] = useState(false)
|
||||
|
||||
/**
|
||||
* Creates a mutator for the post thread cache. This is used to insert
|
||||
* replies into the thread cache after posting.
|
||||
*/
|
||||
const mutator = useMemo(
|
||||
() =>
|
||||
createCacheMutator({
|
||||
params: {view, below},
|
||||
postThreadQueryKey,
|
||||
postThreadOtherQueryKey,
|
||||
queryClient: qc,
|
||||
}),
|
||||
[qc, view, below, postThreadQueryKey, postThreadOtherQueryKey],
|
||||
)
|
||||
|
||||
/**
|
||||
* If we have additional items available from the server and the user has
|
||||
* chosen to view them, start loading data
|
||||
*/
|
||||
const additionalQueryEnabled = hasOtherThreadItems && otherItemsVisible
|
||||
const additionalItemsQuery = useQuery({
|
||||
enabled: additionalQueryEnabled,
|
||||
queryKey: postThreadOtherQueryKey,
|
||||
async queryFn() {
|
||||
const {data} = await agent.app.bsky.unspecced.getPostThreadOtherV2({
|
||||
anchor: anchor!,
|
||||
prioritizeFollowedUsers,
|
||||
})
|
||||
return data
|
||||
},
|
||||
})
|
||||
const serverOtherThreadItems: ThreadItem[] = useMemo(() => {
|
||||
if (!additionalQueryEnabled) return []
|
||||
if (additionalItemsQuery.isLoading) {
|
||||
return Array.from({length: 2}).map((_, i) =>
|
||||
views.skeleton({
|
||||
key: `other-reply-${i}`,
|
||||
item: 'reply',
|
||||
}),
|
||||
)
|
||||
} else if (additionalItemsQuery.isError) {
|
||||
/*
|
||||
* We could insert an special error component in here, but since these
|
||||
* are optional additional replies, it's not critical that they're shown
|
||||
* atm.
|
||||
*/
|
||||
return []
|
||||
} else if (additionalItemsQuery.data?.thread) {
|
||||
const {threadItems} = sortAndAnnotateThreadItems(
|
||||
additionalItemsQuery.data.thread,
|
||||
{
|
||||
view,
|
||||
skipModerationHandling: true,
|
||||
threadgateHiddenReplies: mergeThreadgateHiddenReplies(
|
||||
threadgate?.record,
|
||||
),
|
||||
moderationOpts: moderationOpts!,
|
||||
},
|
||||
)
|
||||
return threadItems
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}, [
|
||||
view,
|
||||
additionalQueryEnabled,
|
||||
additionalItemsQuery,
|
||||
mergeThreadgateHiddenReplies,
|
||||
moderationOpts,
|
||||
threadgate?.record,
|
||||
])
|
||||
|
||||
/**
|
||||
* Sets the sort order for the thread and resets the additional thread items
|
||||
*/
|
||||
const setSort: typeof baseSetSort = useCallback(
|
||||
nextSort => {
|
||||
setOtherItemsVisible(false)
|
||||
baseSetSort(nextSort)
|
||||
},
|
||||
[baseSetSort, setOtherItemsVisible],
|
||||
)
|
||||
|
||||
/**
|
||||
* Sets the view variant for the thread and resets the additional thread items
|
||||
*/
|
||||
const setView: typeof baseSetView = useCallback(
|
||||
nextView => {
|
||||
setOtherItemsVisible(false)
|
||||
baseSetView(nextView)
|
||||
},
|
||||
[baseSetView, setOtherItemsVisible],
|
||||
)
|
||||
|
||||
/*
|
||||
* This is the main thread response, sorted into separate buckets based on
|
||||
* moderation, and annotated with all UI state needed for rendering.
|
||||
*/
|
||||
const {threadItems, otherThreadItems} = useMemo(() => {
|
||||
return sortAndAnnotateThreadItems(thread, {
|
||||
view: view,
|
||||
threadgateHiddenReplies: mergeThreadgateHiddenReplies(threadgate?.record),
|
||||
moderationOpts: moderationOpts!,
|
||||
})
|
||||
}, [
|
||||
thread,
|
||||
threadgate?.record,
|
||||
mergeThreadgateHiddenReplies,
|
||||
moderationOpts,
|
||||
view,
|
||||
])
|
||||
|
||||
/*
|
||||
* Take all three sets of thread items and combine them into a single thread,
|
||||
* along with any other thread items required for rendering e.g. "Show more
|
||||
* replies" or the reply composer.
|
||||
*/
|
||||
const items = useMemo(() => {
|
||||
return buildThread({
|
||||
threadItems,
|
||||
otherThreadItems,
|
||||
serverOtherThreadItems,
|
||||
isLoading: query.isPlaceholderData,
|
||||
hasSession,
|
||||
hasOtherThreadItems,
|
||||
otherItemsVisible,
|
||||
showOtherItems: () => setOtherItemsVisible(true),
|
||||
})
|
||||
}, [
|
||||
threadItems,
|
||||
otherThreadItems,
|
||||
serverOtherThreadItems,
|
||||
query.isPlaceholderData,
|
||||
hasSession,
|
||||
hasOtherThreadItems,
|
||||
otherItemsVisible,
|
||||
setOtherItemsVisible,
|
||||
])
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
state: {
|
||||
/*
|
||||
* Copy in any query state that is useful
|
||||
*/
|
||||
isFetching: query.isFetching,
|
||||
isPlaceholderData: query.isPlaceholderData,
|
||||
error: query.error,
|
||||
/*
|
||||
* Other state
|
||||
*/
|
||||
sort,
|
||||
view,
|
||||
otherItemsVisible,
|
||||
},
|
||||
data: {
|
||||
items,
|
||||
threadgate,
|
||||
},
|
||||
actions: {
|
||||
/*
|
||||
* Copy in any query actions that are useful
|
||||
*/
|
||||
insertReplies: mutator.insertReplies,
|
||||
refetch: query.refetch,
|
||||
/*
|
||||
* Other actions
|
||||
*/
|
||||
setSort,
|
||||
setView,
|
||||
},
|
||||
}),
|
||||
[
|
||||
query,
|
||||
mutator.insertReplies,
|
||||
otherItemsVisible,
|
||||
sort,
|
||||
view,
|
||||
setSort,
|
||||
setView,
|
||||
threadgate,
|
||||
items,
|
||||
],
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user