Checkpoint move state into meta query
This commit is contained in:
@@ -2,21 +2,20 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {type ThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
import * as Menu from '#/components/Menu'
|
||||
|
||||
export function HeaderDropdown({
|
||||
sortReplies,
|
||||
treeViewEnabled,
|
||||
setSortReplies,
|
||||
setTreeViewEnabled,
|
||||
}: {
|
||||
sortReplies: string
|
||||
treeViewEnabled: boolean
|
||||
setSortReplies: (newValue: string) => void
|
||||
setTreeViewEnabled: (newValue: boolean) => void
|
||||
}): React.ReactNode {
|
||||
sort,
|
||||
view,
|
||||
setSort,
|
||||
setView,
|
||||
}: Pick<
|
||||
ThreadPreferences,
|
||||
'sort' | 'setSort' | 'view' | 'setView'
|
||||
>): React.ReactNode {
|
||||
const {_} = useLingui()
|
||||
return (
|
||||
<Menu.Root>
|
||||
@@ -42,22 +41,22 @@ export function HeaderDropdown({
|
||||
<Menu.Item
|
||||
label={_(msg`Linear`)}
|
||||
onPress={() => {
|
||||
setTreeViewEnabled(false)
|
||||
setView('linear')
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Linear</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemRadio selected={!treeViewEnabled} />
|
||||
<Menu.ItemRadio selected={view === 'linear'} />
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
label={_(msg`Threaded`)}
|
||||
onPress={() => {
|
||||
setTreeViewEnabled(true)
|
||||
setView('tree')
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Threaded</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemRadio selected={treeViewEnabled} />
|
||||
<Menu.ItemRadio selected={view === 'tree'} />
|
||||
</Menu.Item>
|
||||
</Menu.Group>
|
||||
<Menu.Divider />
|
||||
@@ -68,32 +67,32 @@ export function HeaderDropdown({
|
||||
<Menu.Item
|
||||
label={_(msg`Top replies first`)}
|
||||
onPress={() => {
|
||||
setSortReplies('top')
|
||||
setSort('top')
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Top replies first</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemRadio selected={sortReplies === 'top'} />
|
||||
<Menu.ItemRadio selected={sort === 'top'} />
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
label={_(msg`Oldest replies first`)}
|
||||
onPress={() => {
|
||||
setSortReplies('oldest')
|
||||
setSort('oldest')
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Oldest replies first</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemRadio selected={sortReplies === 'oldest'} />
|
||||
<Menu.ItemRadio selected={sort === 'oldest'} />
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
label={_(msg`Newest replies first`)}
|
||||
onPress={() => {
|
||||
setSortReplies('newest')
|
||||
setSort('newest')
|
||||
}}>
|
||||
<Menu.ItemText>
|
||||
<Trans>Newest replies first</Trans>
|
||||
</Menu.ItemText>
|
||||
<Menu.ItemRadio selected={sortReplies === 'newest'} />
|
||||
<Menu.ItemRadio selected={sort === 'newest'} />
|
||||
</Menu.Item>
|
||||
</Menu.Group>
|
||||
</Menu.Outer>
|
||||
|
||||
@@ -8,7 +8,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
|
||||
import {type ThreadItem, usePostThread} from '#/state/queries/usePostThread'
|
||||
import {type OnPostSuccessData} from '#/state/shell/composer'
|
||||
import {PostThreadComposePrompt} from '#/view/com/post-thread/PostThreadComposePrompt'
|
||||
@@ -40,45 +39,23 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {height: windowHeight} = useWindowDimensions()
|
||||
|
||||
const {
|
||||
isLoaded: isThreadPreferencesLoaded,
|
||||
sortReplies,
|
||||
setSortReplies,
|
||||
prioritizeFollowedUsers,
|
||||
treeViewEnabled,
|
||||
setTreeViewEnabled,
|
||||
} = useThreadPreferences()
|
||||
/*
|
||||
* One query to rule them all
|
||||
*/
|
||||
const thread = usePostThread({anchor: uri})
|
||||
|
||||
const {
|
||||
isFetching,
|
||||
isPlaceholderData,
|
||||
error,
|
||||
data,
|
||||
refetch,
|
||||
insertReplies,
|
||||
showHiddenReplies,
|
||||
} = usePostThread({
|
||||
enabled: isThreadPreferencesLoaded,
|
||||
params: {
|
||||
anchor: uri,
|
||||
sort: sortReplies,
|
||||
view: treeViewEnabled ? 'tree' : 'linear',
|
||||
prioritizeFollowedUsers,
|
||||
},
|
||||
})
|
||||
|
||||
const optimisticOnPostReply = (data: OnPostSuccessData) => {
|
||||
if (data) {
|
||||
const {replyToUri, posts} = data
|
||||
const optimisticOnPostReply = (payload: OnPostSuccessData) => {
|
||||
if (payload) {
|
||||
const {replyToUri, posts} = payload
|
||||
if (replyToUri && posts.length) {
|
||||
insertReplies(replyToUri, posts)
|
||||
thread.actions.insertReplies(replyToUri, posts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {openComposer} = useOpenComposer()
|
||||
const onReplyToAnchor = () => {
|
||||
const anchorPost = data?.items.find(
|
||||
const anchorPost = thread.data.items.find(
|
||||
slice => slice.type === 'threadPost' && slice.ui.isAnchor,
|
||||
)
|
||||
if (anchorPost?.type !== 'threadPost') {
|
||||
@@ -159,28 +136,28 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
const hasExhaustedReplies = useRef(false)
|
||||
|
||||
const onStartReached = () => {
|
||||
if (isFetching) return
|
||||
if (thread.state.isFetching) return
|
||||
// limit to 100
|
||||
setMaxParentCount(n => Math.min(100, n + PARENT_CHUNK_SIZE))
|
||||
}
|
||||
|
||||
const onEndReached = () => {
|
||||
if (isFetching) return
|
||||
if (thread.state.isFetching) return
|
||||
// prevent any state mutations if we know we're done
|
||||
if (hasExhaustedReplies.current) return
|
||||
setMaxRepliesCount(prev => prev + REPLIES_CHUNK_SIZE)
|
||||
}
|
||||
|
||||
const items = useMemo(() => {
|
||||
const slices = useMemo(() => {
|
||||
const results: ThreadItem[] = []
|
||||
|
||||
if (!data?.items) return results
|
||||
if (!thread.data.items.length) return results
|
||||
|
||||
let repliesCount = 0
|
||||
let totalRepliesCount = 0
|
||||
|
||||
for (let i = 0; i < data.items.length; i++) {
|
||||
const item = data.items[i]
|
||||
for (let i = 0; i < thread.data.items.length; i++) {
|
||||
const item = thread.data.items[i]
|
||||
|
||||
if ('depth' in item) {
|
||||
if (item.depth === 0) {
|
||||
@@ -190,7 +167,7 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
const start = i - 1
|
||||
const limit = Math.max(0, start - maxParentCount)
|
||||
for (let pi = start; pi >= limit; pi--) {
|
||||
results.unshift(data.items[pi])
|
||||
results.unshift(thread.data.items[pi])
|
||||
}
|
||||
}
|
||||
} else if (item.depth > 0) {
|
||||
@@ -207,12 +184,15 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
}
|
||||
|
||||
// TODO should really just count these during traversal, can remove isPlaceholder data after that
|
||||
if (maxRepliesCount > totalRepliesCount && !isPlaceholderData) {
|
||||
if (
|
||||
maxRepliesCount > totalRepliesCount &&
|
||||
!thread.state.isPlaceholderData
|
||||
) {
|
||||
hasExhaustedReplies.current = true
|
||||
}
|
||||
|
||||
return results
|
||||
}, [data, deferParents, maxParentCount, maxRepliesCount, isPlaceholderData])
|
||||
}, [thread, deferParents, maxParentCount, maxRepliesCount])
|
||||
|
||||
const renderItem = ({item, index}: {item: ThreadItem; index: number}) => {
|
||||
if (item.type === 'threadPost') {
|
||||
@@ -221,7 +201,7 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
return (
|
||||
<ThreadPost
|
||||
item={item}
|
||||
threadgateRecord={data?.threadgate?.record ?? undefined}
|
||||
threadgateRecord={thread.data.threadgate?.record ?? undefined}
|
||||
overrides={{
|
||||
topBorder: index === 0, // && !item.isParentLoading, // TODO
|
||||
}}
|
||||
@@ -235,19 +215,19 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
onLayout={deferParents ? () => setDeferParents(false) : undefined}>
|
||||
<ThreadAnchor
|
||||
item={item}
|
||||
threadgateRecord={data?.threadgate?.record ?? undefined}
|
||||
threadgateRecord={thread.data.threadgate?.record ?? undefined}
|
||||
onPostSuccess={optimisticOnPostReply}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
if (treeViewEnabled) {
|
||||
if (thread.state.view === 'tree') {
|
||||
return (
|
||||
<ThreadReply
|
||||
item={item}
|
||||
threadgateRecord={data?.threadgate?.record ?? undefined}
|
||||
threadgateRecord={thread.data.threadgate?.record ?? undefined}
|
||||
overrides={{
|
||||
moderation: showHiddenReplies && item.depth > 0,
|
||||
moderation: thread.state.hiddenRepliesVisible && item.depth > 0,
|
||||
}}
|
||||
onPostSuccess={optimisticOnPostReply}
|
||||
/>
|
||||
@@ -256,9 +236,9 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
return (
|
||||
<ThreadPost
|
||||
item={item}
|
||||
threadgateRecord={data?.threadgate?.record ?? undefined}
|
||||
threadgateRecord={thread.data.threadgate?.record ?? undefined}
|
||||
overrides={{
|
||||
moderation: showHiddenReplies && item.depth > 0,
|
||||
moderation: thread.state.hiddenRepliesVisible && item.depth > 0,
|
||||
}}
|
||||
onPostSuccess={optimisticOnPostReply}
|
||||
/>
|
||||
@@ -266,7 +246,12 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
}
|
||||
}
|
||||
} else if (item.type === 'readMore') {
|
||||
return <ReadMore item={item} view={treeViewEnabled ? 'tree' : 'linear'} />
|
||||
return (
|
||||
<ReadMore
|
||||
item={item}
|
||||
view={thread.state.view === 'tree' ? 'tree' : 'linear'}
|
||||
/>
|
||||
)
|
||||
} else if (item.type === 'threadPostBlocked') {
|
||||
return (
|
||||
<View
|
||||
@@ -340,23 +325,24 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot>
|
||||
<HeaderDropdown
|
||||
sortReplies={sortReplies}
|
||||
treeViewEnabled={treeViewEnabled}
|
||||
setSortReplies={setSortReplies}
|
||||
setTreeViewEnabled={setTreeViewEnabled}
|
||||
sort={thread.state.sort}
|
||||
setSort={thread.actions.setSort}
|
||||
view={thread.state.view}
|
||||
setView={thread.actions.setView}
|
||||
/>
|
||||
</Layout.Header.Slot>
|
||||
</Layout.Header.Outer>
|
||||
|
||||
{error ? (
|
||||
<PostThreadError error={error} />
|
||||
{thread.state.error ? (
|
||||
<PostThreadError error={thread.state.error} />
|
||||
) : (
|
||||
<ScrollProvider
|
||||
// TODO do we need?
|
||||
//onMomentumEnd={onMomentumEnd}
|
||||
>
|
||||
<List
|
||||
ref={listRef}
|
||||
data={items}
|
||||
data={slices}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
onContentSizeChange={onContentSizeChangeWebOnly}
|
||||
@@ -379,8 +365,8 @@ export function Inner({uri}: {uri: string | undefined}) {
|
||||
* purpose here so we get the loader on initial render
|
||||
*/
|
||||
// isFetchingNextPage={isFetching}
|
||||
error={cleanError(error)}
|
||||
onRetry={refetch}
|
||||
error={cleanError(thread.state.error)}
|
||||
onRetry={thread.actions.refetch}
|
||||
/*
|
||||
* 200 is based on the minimum height of a post. This is enough
|
||||
* extra height for the `maintainVisPos` to work without
|
||||
|
||||
Reference in New Issue
Block a user