Checkpoint move state into meta query
This commit is contained in:
@@ -1,23 +1,42 @@
|
||||
import {useMemo, useState} from 'react'
|
||||
import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api'
|
||||
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {type Literal} from '#/types/utils'
|
||||
|
||||
export function useThreadPreferences() {
|
||||
export type ThreadSortOption = Literal<
|
||||
AppBskyUnspeccedGetPostThreadV2.QueryParams['sort'],
|
||||
string
|
||||
>
|
||||
export type ThreadViewOption = 'linear' | 'tree'
|
||||
export type ThreadPreferences = {
|
||||
isLoaded: boolean
|
||||
sort: ThreadSortOption
|
||||
setSort: (sort: ThreadSortOption) => void
|
||||
view: ThreadViewOption
|
||||
setView: (view: ThreadViewOption) => void
|
||||
prioritizeFollowedUsers: boolean
|
||||
setPrioritizeFollowedUsers: (prioritize: boolean) => void
|
||||
}
|
||||
|
||||
export function useThreadPreferences(): ThreadPreferences {
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const nextThreadPreferences = preferences?.threadViewPrefs
|
||||
|
||||
/*
|
||||
* Create local state representations of server state
|
||||
*/
|
||||
const [sortReplies, setSortReplies] = useState(
|
||||
nextThreadPreferences?.sort ?? 'hotness',
|
||||
const [sort, setSort] = useState(
|
||||
migrateFromSortV1(nextThreadPreferences?.sort || 'top'),
|
||||
)
|
||||
const [view, setView] = useState(
|
||||
computeView({
|
||||
treeViewEnabled: !!nextThreadPreferences?.lab_treeViewEnabled,
|
||||
}),
|
||||
)
|
||||
const [prioritizeFollowedUsers, setPrioritizeFollowedUsers] = useState(
|
||||
!!nextThreadPreferences?.prioritizeFollowedUsers,
|
||||
)
|
||||
const [treeViewEnabled, setTreeViewEnabled] = useState(
|
||||
!!nextThreadPreferences?.lab_treeViewEnabled,
|
||||
)
|
||||
|
||||
/**
|
||||
* Cache existing and if we get a server update, reset local state
|
||||
@@ -29,9 +48,13 @@ export function useThreadPreferences() {
|
||||
/*
|
||||
* Reset
|
||||
*/
|
||||
setSortReplies(nextThreadPreferences.sort)
|
||||
setSort(migrateFromSortV1(nextThreadPreferences.sort))
|
||||
setPrioritizeFollowedUsers(nextThreadPreferences.prioritizeFollowedUsers)
|
||||
setTreeViewEnabled(!!nextThreadPreferences.lab_treeViewEnabled)
|
||||
setView(
|
||||
computeView({
|
||||
treeViewEnabled: !!nextThreadPreferences.lab_treeViewEnabled,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const isLoaded = !!prevServerPrefs
|
||||
@@ -39,21 +62,46 @@ export function useThreadPreferences() {
|
||||
return useMemo(
|
||||
() => ({
|
||||
isLoaded,
|
||||
sortReplies,
|
||||
setSortReplies,
|
||||
sort,
|
||||
setSort,
|
||||
prioritizeFollowedUsers,
|
||||
setPrioritizeFollowedUsers,
|
||||
treeViewEnabled,
|
||||
setTreeViewEnabled,
|
||||
view,
|
||||
setView,
|
||||
}),
|
||||
[
|
||||
isLoaded,
|
||||
sortReplies,
|
||||
setSortReplies,
|
||||
sort,
|
||||
setSort,
|
||||
prioritizeFollowedUsers,
|
||||
setPrioritizeFollowedUsers,
|
||||
treeViewEnabled,
|
||||
setTreeViewEnabled,
|
||||
view,
|
||||
setView,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates user thread preferences from the old sort values to V2
|
||||
*/
|
||||
function migrateFromSortV1(sort: string): ThreadSortOption {
|
||||
switch (sort) {
|
||||
case 'oldest':
|
||||
return 'oldest'
|
||||
case 'newest':
|
||||
return 'newest'
|
||||
default:
|
||||
return 'top'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms existing treeViewEnabled preference into a ThreadViewOption
|
||||
*/
|
||||
function computeView({
|
||||
treeViewEnabled,
|
||||
}: {
|
||||
treeViewEnabled: boolean
|
||||
}): ThreadViewOption {
|
||||
return treeViewEnabled ? 'tree' : 'linear'
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {
|
||||
createCacheMutator,
|
||||
getThreadPlaceholder,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
createPostThreadHiddenQueryKey,
|
||||
createPostThreadQueryKey,
|
||||
type ThreadItem,
|
||||
type UsePostThreadProps,
|
||||
} from '#/state/queries/usePostThread/types'
|
||||
import {getThreadgateRecord} from '#/state/queries/usePostThread/utils'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
@@ -20,34 +20,40 @@ import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies
|
||||
|
||||
export * from '#/state/queries/usePostThread/types'
|
||||
|
||||
export function usePostThread({
|
||||
enabled: isEnabled,
|
||||
params,
|
||||
}: UsePostThreadProps) {
|
||||
export function usePostThread({anchor}: {anchor?: string}) {
|
||||
const qc = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const {hasSession} = useSession()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const mergeThreadgateHiddenReplies = useMergeThreadgateHiddenReplies()
|
||||
|
||||
const enabled = isEnabled !== false && !!params.anchor && !!moderationOpts
|
||||
const queryKey = createPostThreadQueryKey({
|
||||
params,
|
||||
const {
|
||||
isLoaded: isThreadPreferencesLoaded,
|
||||
sort,
|
||||
setSort,
|
||||
view,
|
||||
setView,
|
||||
prioritizeFollowedUsers,
|
||||
} = useThreadPreferences()
|
||||
const postThreadQueryKey = createPostThreadQueryKey({
|
||||
anchor,
|
||||
sort,
|
||||
view,
|
||||
prioritizeFollowedUsers,
|
||||
})
|
||||
|
||||
const query = useQuery({
|
||||
enabled,
|
||||
queryKey,
|
||||
enabled: isThreadPreferencesLoaded && !!anchor && !!moderationOpts,
|
||||
queryKey: postThreadQueryKey,
|
||||
// gcTime: 0, // TODO faster if we let it cache
|
||||
async queryFn(ctx) {
|
||||
const {data} = await wait(
|
||||
400,
|
||||
agent.app.bsky.unspecced.getPostThreadV2({
|
||||
anchor: params.anchor!,
|
||||
branchingFactor: params.view === 'linear' ? 1 : undefined,
|
||||
anchor: anchor!,
|
||||
branchingFactor: view === 'linear' ? 1 : undefined,
|
||||
below: 4,
|
||||
sort: params.sort,
|
||||
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
|
||||
sort: sort,
|
||||
prioritizeFollowedUsers: prioritizeFollowedUsers,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -72,8 +78,8 @@ export function usePostThread({
|
||||
}
|
||||
},
|
||||
placeholderData() {
|
||||
if (!params.anchor) return
|
||||
const placeholder = getThreadPlaceholder(qc, params.anchor)
|
||||
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
|
||||
@@ -95,8 +101,8 @@ export function usePostThread({
|
||||
})
|
||||
|
||||
const hasHiddenReplies = !!query.data?.hasHiddenReplies
|
||||
const [showHiddenReplies, setShowHiddenReplies] = useState(false)
|
||||
const [hiddenReplies, setHiddenReplies] = useState<ThreadItem[]>([])
|
||||
const [hiddenRepliesVisible, setHiddenRepliesVisible] = useState(false)
|
||||
const [hiddenItems, setHiddenItems] = useState<ThreadItem[]>([])
|
||||
|
||||
/**
|
||||
* Loads hidden replies for this thread. Any replies that are moderated from
|
||||
@@ -105,19 +111,19 @@ export function usePostThread({
|
||||
*/
|
||||
const loadHiddenReplies = useCallback(async () => {
|
||||
// immediately show any moderated replies already in memory
|
||||
setShowHiddenReplies(true)
|
||||
setHiddenRepliesVisible(true)
|
||||
// add skeletons for the replies that will be loaded
|
||||
setHiddenReplies(
|
||||
setHiddenItems(
|
||||
Array.from({length: 2}).map((_, i) => ({
|
||||
type: 'skeleton',
|
||||
key: `${params.anchor!}-reply-${i}`,
|
||||
key: `${anchor!}-reply-${i}`,
|
||||
item: 'reply',
|
||||
})),
|
||||
)
|
||||
|
||||
const queryParams = {
|
||||
anchor: params.anchor!,
|
||||
prioritizeFollowedUsers: params.prioritizeFollowedUsers,
|
||||
anchor: anchor!,
|
||||
prioritizeFollowedUsers: prioritizeFollowedUsers,
|
||||
}
|
||||
|
||||
const data = await wait(
|
||||
@@ -139,26 +145,28 @@ export function usePostThread({
|
||||
),
|
||||
moderationOpts: moderationOpts!,
|
||||
hasSession,
|
||||
view: params.view,
|
||||
view,
|
||||
hasHiddenReplies,
|
||||
showHiddenReplies,
|
||||
hiddenRepliesVisible,
|
||||
skipHiddenReplyHandling: true,
|
||||
loadHiddenReplies,
|
||||
})
|
||||
|
||||
// insert the hidden replies into the state
|
||||
setHiddenReplies(items)
|
||||
setHiddenItems(items)
|
||||
}, [
|
||||
agent,
|
||||
params,
|
||||
view,
|
||||
anchor,
|
||||
prioritizeFollowedUsers,
|
||||
hasSession,
|
||||
mergeThreadgateHiddenReplies,
|
||||
moderationOpts,
|
||||
qc,
|
||||
query.data?.threadgate?.record,
|
||||
hasHiddenReplies,
|
||||
showHiddenReplies,
|
||||
setShowHiddenReplies,
|
||||
hiddenRepliesVisible,
|
||||
setHiddenRepliesVisible,
|
||||
])
|
||||
|
||||
const items = useMemo(() => {
|
||||
@@ -168,36 +176,36 @@ export function usePostThread({
|
||||
),
|
||||
moderationOpts: moderationOpts!,
|
||||
hasSession,
|
||||
view: params.view,
|
||||
view: view,
|
||||
hasHiddenReplies,
|
||||
showHiddenReplies,
|
||||
hiddenRepliesVisible,
|
||||
loadHiddenReplies,
|
||||
})
|
||||
|
||||
return results.concat(hiddenReplies)
|
||||
return results.concat(hiddenItems)
|
||||
}, [
|
||||
query.data,
|
||||
mergeThreadgateHiddenReplies,
|
||||
moderationOpts,
|
||||
hasSession,
|
||||
params.view,
|
||||
view,
|
||||
hasHiddenReplies,
|
||||
showHiddenReplies,
|
||||
hiddenRepliesVisible,
|
||||
loadHiddenReplies,
|
||||
hiddenReplies,
|
||||
hiddenItems,
|
||||
])
|
||||
|
||||
if (query.isPlaceholderData) {
|
||||
const anchor = items.at(0)
|
||||
const anchorPost = items.at(0)
|
||||
const skeletonReplies =
|
||||
anchor && anchor.type === 'threadPost'
|
||||
? anchor?.value.post.replyCount ?? 4
|
||||
anchorPost && anchorPost.type === 'threadPost'
|
||||
? anchorPost?.value.post.replyCount ?? 4
|
||||
: 4
|
||||
|
||||
if (!items.length) {
|
||||
items.push({
|
||||
type: 'skeleton',
|
||||
key: params.anchor!,
|
||||
key: anchor!,
|
||||
item: 'anchor',
|
||||
})
|
||||
|
||||
@@ -213,7 +221,7 @@ export function usePostThread({
|
||||
for (let i = 0; i < skeletonReplies; i++) {
|
||||
items.push({
|
||||
type: 'skeleton',
|
||||
key: `${params.anchor!}-reply-${i}`,
|
||||
key: `${anchor!}-reply-${i}`,
|
||||
item: 'reply',
|
||||
})
|
||||
}
|
||||
@@ -222,23 +230,46 @@ export function usePostThread({
|
||||
const mutator = useMemo(
|
||||
() =>
|
||||
createCacheMutator({
|
||||
params,
|
||||
queryKey,
|
||||
params: {
|
||||
sort,
|
||||
view,
|
||||
},
|
||||
queryKey: postThreadQueryKey,
|
||||
queryClient: qc,
|
||||
}),
|
||||
[qc, params, queryKey],
|
||||
[qc, sort, view, postThreadQueryKey],
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
...query,
|
||||
state: {
|
||||
isFetching: query.isFetching,
|
||||
isPlaceholderData: query.isPlaceholderData,
|
||||
error: query.error,
|
||||
hiddenRepliesVisible,
|
||||
sort,
|
||||
view,
|
||||
},
|
||||
data: {
|
||||
items,
|
||||
items: items || [],
|
||||
threadgate: query.data?.threadgate,
|
||||
},
|
||||
showHiddenReplies,
|
||||
insertReplies: mutator.insertReplies,
|
||||
actions: {
|
||||
insertReplies: mutator.insertReplies,
|
||||
refetch: query.refetch,
|
||||
setSort,
|
||||
setView,
|
||||
},
|
||||
}),
|
||||
[query, items, mutator.insertReplies, showHiddenReplies],
|
||||
[
|
||||
query,
|
||||
items,
|
||||
mutator.insertReplies,
|
||||
hiddenRepliesVisible,
|
||||
sort,
|
||||
view,
|
||||
setSort,
|
||||
setView,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,13 +24,14 @@ import {didOrHandleUriMatches, getEmbeddedPost} from '#/state/queries/util'
|
||||
import {embedViewRecordToPostView} from '#/state/queries/util'
|
||||
|
||||
export function createCacheMutator({
|
||||
params,
|
||||
queryKey,
|
||||
queryClient,
|
||||
queryKey,
|
||||
params,
|
||||
}: {
|
||||
params: PostThreadParams
|
||||
queryKey: ReturnType<typeof createPostThreadQueryKey>
|
||||
queryClient: QueryClient
|
||||
queryKey: ReturnType<typeof createPostThreadQueryKey>
|
||||
// TODO could clean this up?
|
||||
params: PostThreadParams
|
||||
}) {
|
||||
return {
|
||||
insertReplies(
|
||||
|
||||
@@ -26,7 +26,7 @@ export function traverse(
|
||||
hasSession,
|
||||
view,
|
||||
hasHiddenReplies,
|
||||
showHiddenReplies,
|
||||
hiddenRepliesVisible,
|
||||
skipHiddenReplyHandling,
|
||||
loadHiddenReplies,
|
||||
}: {
|
||||
@@ -35,7 +35,7 @@ export function traverse(
|
||||
hasSession: boolean
|
||||
view: PostThreadParams['view']
|
||||
hasHiddenReplies: boolean
|
||||
showHiddenReplies: boolean
|
||||
hiddenRepliesVisible: boolean
|
||||
skipHiddenReplyHandling?: boolean
|
||||
loadHiddenReplies: () => Promise<void>
|
||||
},
|
||||
@@ -241,7 +241,7 @@ export function traverse(
|
||||
|
||||
if (!skipHiddenReplyHandling) {
|
||||
if (hidden.length || hasHiddenReplies) {
|
||||
if (showHiddenReplies) {
|
||||
if (hiddenRepliesVisible) {
|
||||
items.push(...hidden)
|
||||
} else {
|
||||
items.push({
|
||||
|
||||
@@ -14,9 +14,8 @@ export type ApiThreadItem =
|
||||
export const postThreadQueryKeyRoot = 'getPostThreadV2' as const
|
||||
export const postThreadHiddenQueryKeyRoot = 'getPostThreadHiddenV2' as const
|
||||
|
||||
export const createPostThreadQueryKey = (
|
||||
props: Pick<UsePostThreadProps, 'params'>,
|
||||
) => [postThreadQueryKeyRoot, props] as const
|
||||
export const createPostThreadQueryKey = (props: PostThreadParams) =>
|
||||
[postThreadQueryKeyRoot, props] as const
|
||||
|
||||
export const createPostThreadHiddenQueryKey = (
|
||||
props: AppBskyUnspeccedGetPostThreadHiddenV2.QueryParams,
|
||||
@@ -30,11 +29,6 @@ export type PostThreadParams = Pick<
|
||||
view: 'tree' | 'linear'
|
||||
}
|
||||
|
||||
export type UsePostThreadProps = {
|
||||
enabled?: boolean
|
||||
params: PostThreadParams
|
||||
}
|
||||
|
||||
export type ThreadItem =
|
||||
| {
|
||||
type: 'threadPost'
|
||||
|
||||
Reference in New Issue
Block a user