Prefetch data needed for interaction settings
This commit is contained in:
+55
-25
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
AccessibilityProps,
|
AccessibilityProps,
|
||||||
|
GestureResponderEvent,
|
||||||
|
MouseEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
PressableProps,
|
PressableProps,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
@@ -65,7 +67,15 @@ type NonTextElements =
|
|||||||
|
|
||||||
export type ButtonProps = Pick<
|
export type ButtonProps = Pick<
|
||||||
PressableProps,
|
PressableProps,
|
||||||
'disabled' | 'onPress' | 'testID' | 'onLongPress' | 'hitSlop'
|
| 'disabled'
|
||||||
|
| 'onPress'
|
||||||
|
| 'testID'
|
||||||
|
| 'onLongPress'
|
||||||
|
| 'hitSlop'
|
||||||
|
| 'onHoverIn'
|
||||||
|
| 'onHoverOut'
|
||||||
|
| 'onPressIn'
|
||||||
|
| 'onPressOut'
|
||||||
> &
|
> &
|
||||||
AccessibilityProps &
|
AccessibilityProps &
|
||||||
VariantProps & {
|
VariantProps & {
|
||||||
@@ -115,30 +125,50 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
|||||||
focused: false,
|
focused: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPressIn = React.useCallback(() => {
|
const onPressInOuter = rest.onPressIn
|
||||||
setState(s => ({
|
const onPressIn = React.useCallback(
|
||||||
...s,
|
(e: GestureResponderEvent) => {
|
||||||
pressed: true,
|
setState(s => ({
|
||||||
}))
|
...s,
|
||||||
}, [setState])
|
pressed: true,
|
||||||
const onPressOut = React.useCallback(() => {
|
}))
|
||||||
setState(s => ({
|
onPressInOuter?.(e)
|
||||||
...s,
|
},
|
||||||
pressed: false,
|
[setState, onPressInOuter],
|
||||||
}))
|
)
|
||||||
}, [setState])
|
const onPressOutOuter = rest.onPressOut
|
||||||
const onHoverIn = React.useCallback(() => {
|
const onPressOut = React.useCallback(
|
||||||
setState(s => ({
|
(e: GestureResponderEvent) => {
|
||||||
...s,
|
setState(s => ({
|
||||||
hovered: true,
|
...s,
|
||||||
}))
|
pressed: false,
|
||||||
}, [setState])
|
}))
|
||||||
const onHoverOut = React.useCallback(() => {
|
onPressOutOuter?.(e)
|
||||||
setState(s => ({
|
},
|
||||||
...s,
|
[setState, onPressOutOuter],
|
||||||
hovered: false,
|
)
|
||||||
}))
|
const onHoverInOuter = rest.onHoverIn
|
||||||
}, [setState])
|
const onHoverIn = React.useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
hovered: true,
|
||||||
|
}))
|
||||||
|
onHoverInOuter?.(e)
|
||||||
|
},
|
||||||
|
[setState, onHoverInOuter],
|
||||||
|
)
|
||||||
|
const onHoverOutOuter = rest.onHoverOut
|
||||||
|
const onHoverOut = React.useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
setState(s => ({
|
||||||
|
...s,
|
||||||
|
hovered: false,
|
||||||
|
}))
|
||||||
|
onHoverOutOuter?.(e)
|
||||||
|
},
|
||||||
|
[setState, onHoverOutOuter],
|
||||||
|
)
|
||||||
const onFocus = React.useCallback(() => {
|
const onFocus = React.useCallback(() => {
|
||||||
setState(s => ({
|
setState(s => ({
|
||||||
...s,
|
...s,
|
||||||
|
|||||||
@@ -125,8 +125,14 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
|
|||||||
}}
|
}}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onPressIn={onPressIn}
|
onPressIn={e => {
|
||||||
onPressOut={onPressOut}
|
onPressIn()
|
||||||
|
rest.onPressIn?.(e)
|
||||||
|
}}
|
||||||
|
onPressOut={e => {
|
||||||
|
onPressOut()
|
||||||
|
rest.onPressOut?.(e)
|
||||||
|
}}
|
||||||
style={[
|
style={[
|
||||||
a.flex_row,
|
a.flex_row,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native'
|
import {Keyboard, Platform, StyleProp, View, ViewStyle} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyFeedDefs,
|
AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
@@ -20,12 +20,15 @@ import {atoms as a, useTheme} from '#/alf'
|
|||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {
|
||||||
|
PostInteractionSettingsDialog,
|
||||||
|
usePrefetchPostInteractionSettings,
|
||||||
|
} from '#/components/dialogs/PostInteractionSettingsDialog'
|
||||||
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
|
||||||
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
import {Earth_Stroke2_Corner0_Rounded as Earth} from '#/components/icons/Globe'
|
||||||
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {PostInteractionSettingsDialog} from './dialogs/PostInteractionSettingsDialog'
|
|
||||||
import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil'
|
import {PencilLine_Stroke2_Corner0_Rounded as PencilLine} from './icons/Pencil'
|
||||||
|
|
||||||
interface WhoCanReplyProps {
|
interface WhoCanReplyProps {
|
||||||
@@ -52,6 +55,11 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
|||||||
return threadgateViewToAllowUISetting(post.threadgate)
|
return threadgateViewToAllowUISetting(post.threadgate)
|
||||||
}, [post.threadgate])
|
}, [post.threadgate])
|
||||||
|
|
||||||
|
const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({
|
||||||
|
postUri: post.uri,
|
||||||
|
rootPostUri: rootUri,
|
||||||
|
})
|
||||||
|
|
||||||
const anyoneCanReply =
|
const anyoneCanReply =
|
||||||
settings.length === 1 && settings[0].type === 'everybody'
|
settings.length === 1 && settings[0].type === 'everybody'
|
||||||
const noOneCanReply = settings.length === 1 && settings[0].type === 'nobody'
|
const noOneCanReply = settings.length === 1 && settings[0].type === 'nobody'
|
||||||
@@ -79,6 +87,16 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
|||||||
isThreadAuthor ? _(msg`Edit who can reply`) : _(msg`Who can reply`)
|
isThreadAuthor ? _(msg`Edit who can reply`) : _(msg`Who can reply`)
|
||||||
}
|
}
|
||||||
onPress={onPressOpen}
|
onPress={onPressOpen}
|
||||||
|
{...(isThreadAuthor
|
||||||
|
? Platform.select({
|
||||||
|
web: {
|
||||||
|
onHoverIn: prefetchPostInteractionSettings,
|
||||||
|
},
|
||||||
|
native: {
|
||||||
|
onPressIn: prefetchPostInteractionSettings,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: {})}
|
||||||
hitSlop={HITSLOP_10}>
|
hitSlop={HITSLOP_10}>
|
||||||
{({hovered}) => (
|
{({hovered}) => (
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_xs, style]}>
|
<View style={[a.flex_row, a.align_center, a.gap_xs, style]}>
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {STALE} from '#/state/queries'
|
||||||
import {useMyListsQuery} from '#/state/queries/my-lists'
|
import {useMyListsQuery} from '#/state/queries/my-lists'
|
||||||
import {
|
import {
|
||||||
|
createPostgateQueryKey,
|
||||||
|
getPostgateRecord,
|
||||||
usePostgateQuery,
|
usePostgateQuery,
|
||||||
useWritePostgateMutation,
|
useWritePostgateMutation,
|
||||||
} from '#/state/queries/postgate'
|
} from '#/state/queries/postgate'
|
||||||
@@ -21,18 +25,19 @@ import {
|
|||||||
embeddingRules,
|
embeddingRules,
|
||||||
} from '#/state/queries/postgate/util'
|
} from '#/state/queries/postgate/util'
|
||||||
import {
|
import {
|
||||||
|
createThreadgateViewQueryKey,
|
||||||
|
getThreadgateView,
|
||||||
ThreadgateAllowUISetting,
|
ThreadgateAllowUISetting,
|
||||||
threadgateViewToAllowUISetting,
|
threadgateViewToAllowUISetting,
|
||||||
useSetThreadgateAllowMutation,
|
useSetThreadgateAllowMutation,
|
||||||
useThreadgateViewQuery,
|
useThreadgateViewQuery,
|
||||||
} from '#/state/queries/threadgate'
|
} from '#/state/queries/threadgate'
|
||||||
import {useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import * as Toast from '#/view/com/util/toast'
|
import * as Toast from '#/view/com/util/toast'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
|
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
@@ -116,14 +121,13 @@ export function PostInteractionSettingsDialogControlledInner(
|
|||||||
|
|
||||||
const {mutateAsync: writePostgateRecord} = useWritePostgateMutation()
|
const {mutateAsync: writePostgateRecord} = useWritePostgateMutation()
|
||||||
const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation()
|
const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation()
|
||||||
const naturalLoading = useDelayedLoading(500) // TODO
|
|
||||||
|
|
||||||
const [editedPostgate, setEditedPostgate] =
|
const [editedPostgate, setEditedPostgate] =
|
||||||
React.useState<AppBskyFeedPostgate.Record>()
|
React.useState<AppBskyFeedPostgate.Record>()
|
||||||
const [editedAllowUISettings, setEditedAllowUISettings] =
|
const [editedAllowUISettings, setEditedAllowUISettings] =
|
||||||
React.useState<ThreadgateAllowUISetting[]>()
|
React.useState<ThreadgateAllowUISetting[]>()
|
||||||
|
|
||||||
const isLoading = isLoadingThreadgate || isLoadingPostgate || naturalLoading
|
const isLoading = isLoadingThreadgate || isLoadingPostgate
|
||||||
const threadgateView = threadgateViewLoaded || props.initialThreadgateView
|
const threadgateView = threadgateViewLoaded || props.initialThreadgateView
|
||||||
const isThreadgateOwnedByViewer = React.useMemo(() => {
|
const isThreadgateOwnedByViewer = React.useMemo(() => {
|
||||||
if (AppBskyFeedThreadgate.isRecord(threadgateView?.record)) {
|
if (AppBskyFeedThreadgate.isRecord(threadgateView?.record)) {
|
||||||
@@ -504,3 +508,35 @@ function Selectable({
|
|||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePrefetchPostInteractionSettings({
|
||||||
|
postUri,
|
||||||
|
rootPostUri,
|
||||||
|
}: {
|
||||||
|
postUri: string
|
||||||
|
rootPostUri: string
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const agent = useAgent()
|
||||||
|
|
||||||
|
return React.useCallback(async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: createPostgateQueryKey(postUri),
|
||||||
|
queryFn: () => getPostgateRecord({agent, postUri}),
|
||||||
|
staleTime: STALE.SECONDS.THIRTY,
|
||||||
|
}),
|
||||||
|
queryClient.prefetchQuery({
|
||||||
|
queryKey: createThreadgateViewQueryKey(rootPostUri),
|
||||||
|
queryFn: () => getThreadgateView({agent, postUri: rootPostUri}),
|
||||||
|
staleTime: STALE.SECONDS.THIRTY,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`Failed to prefetch post interaction settings`, {
|
||||||
|
safeMessage: e.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [queryClient, agent, postUri, rootPostUri])
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
|||||||
import {networkRetry, retry} from '#/lib/async/retry'
|
import {networkRetry, retry} from '#/lib/async/retry'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||||
|
import {STALE} from '#/state/queries'
|
||||||
import {useGetPosts} from '#/state/queries/post'
|
import {useGetPosts} from '#/state/queries/post'
|
||||||
import {
|
import {
|
||||||
createMaybeDetachedQuoteEmbed,
|
createMaybeDetachedQuoteEmbed,
|
||||||
@@ -128,6 +129,7 @@ export const createPostgateQueryKey = (postUri: string) => [
|
|||||||
export function usePostgateQuery({postUri}: {postUri: string}) {
|
export function usePostgateQuery({postUri}: {postUri: string}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
staleTime: STALE.SECONDS.THIRTY,
|
||||||
queryKey: createPostgateQueryKey(postUri),
|
queryKey: createPostgateQueryKey(postUri),
|
||||||
async queryFn() {
|
async queryFn() {
|
||||||
return (await getPostgateRecord({agent, postUri})) ?? null
|
return (await getPostgateRecord({agent, postUri})) ?? null
|
||||||
@@ -154,7 +156,7 @@ export function useWritePostgateMutation() {
|
|||||||
},
|
},
|
||||||
onSuccess(_, {postUri}) {
|
onSuccess(_, {postUri}) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [createPostgateQueryKey(postUri)],
|
queryKey: createPostgateQueryKey(postUri),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -72,21 +72,33 @@ export function useThreadgateViewQuery({
|
|||||||
placeholderData: initialData,
|
placeholderData: initialData,
|
||||||
staleTime: STALE.MINUTES.ONE,
|
staleTime: STALE.MINUTES.ONE,
|
||||||
async queryFn() {
|
async queryFn() {
|
||||||
const {data} = await agent.app.bsky.feed.getPostThread({
|
return getThreadgateView({
|
||||||
uri: postUri!,
|
agent,
|
||||||
depth: 0,
|
postUri: postUri!,
|
||||||
})
|
})
|
||||||
console.log(data.thread)
|
|
||||||
|
|
||||||
if (AppBskyFeedDefs.isThreadViewPost(data.thread)) {
|
|
||||||
return data.thread.post.threadgate ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getThreadgateView({
|
||||||
|
agent,
|
||||||
|
postUri,
|
||||||
|
}: {
|
||||||
|
agent: BskyAgent
|
||||||
|
postUri: string
|
||||||
|
}) {
|
||||||
|
const {data} = await agent.app.bsky.feed.getPostThread({
|
||||||
|
uri: postUri!,
|
||||||
|
depth: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (AppBskyFeedDefs.isThreadViewPost(data.thread)) {
|
||||||
|
return data.thread.post.threadgate ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export async function getThreadgateRecord({
|
export async function getThreadgateRecord({
|
||||||
agent,
|
agent,
|
||||||
postUri,
|
postUri,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, {memo} from 'react'
|
import React, {memo} from 'react'
|
||||||
import {
|
import {
|
||||||
|
Platform,
|
||||||
Pressable,
|
Pressable,
|
||||||
type PressableProps,
|
type PressableProps,
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
@@ -44,7 +45,10 @@ import {atoms as a, useBreakpoints, useTheme as useAlf} from '#/alf'
|
|||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
|
||||||
import {EmbedDialog} from '#/components/dialogs/Embed'
|
import {EmbedDialog} from '#/components/dialogs/Embed'
|
||||||
import {PostInteractionSettingsDialog} from '#/components/dialogs/PostInteractionSettingsDialog'
|
import {
|
||||||
|
PostInteractionSettingsDialog,
|
||||||
|
usePrefetchPostInteractionSettings,
|
||||||
|
} from '#/components/dialogs/PostInteractionSettingsDialog'
|
||||||
import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
|
import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
|
||||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
||||||
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
|
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
|
||||||
@@ -144,6 +148,11 @@ let PostDropdownBtn = ({
|
|||||||
const {mutateAsync: toggleQuoteDetachment, isPending} =
|
const {mutateAsync: toggleQuoteDetachment, isPending} =
|
||||||
useToggleQuoteDetachmentMutation()
|
useToggleQuoteDetachmentMutation()
|
||||||
|
|
||||||
|
const prefetchPostInteractionSettings = usePrefetchPostInteractionSettings({
|
||||||
|
postUri: post.uri,
|
||||||
|
rootPostUri: rootUri,
|
||||||
|
})
|
||||||
|
|
||||||
const href = React.useMemo(() => {
|
const href = React.useMemo(() => {
|
||||||
const urip = new AtUri(postUri)
|
const urip = new AtUri(postUri)
|
||||||
return makeProfileLink(postAuthor, 'post', urip.rkey)
|
return makeProfileLink(postAuthor, 'post', urip.rkey)
|
||||||
@@ -556,7 +565,17 @@ let PostDropdownBtn = ({
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="postDropdownEditPostInteractions"
|
testID="postDropdownEditPostInteractions"
|
||||||
label={_(msg`Edit interaction settings`)}
|
label={_(msg`Edit interaction settings`)}
|
||||||
onPress={postInteractionSettingsDialogControl.open}>
|
onPress={postInteractionSettingsDialogControl.open}
|
||||||
|
{...(isAuthor
|
||||||
|
? Platform.select({
|
||||||
|
web: {
|
||||||
|
onHoverIn: prefetchPostInteractionSettings,
|
||||||
|
},
|
||||||
|
native: {
|
||||||
|
onPressIn: prefetchPostInteractionSettings,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: {})}>
|
||||||
<Menu.ItemText>
|
<Menu.ItemText>
|
||||||
{_(msg`Edit interaction settings`)}
|
{_(msg`Edit interaction settings`)}
|
||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
|
|||||||
Reference in New Issue
Block a user