POC cache mutator
This commit is contained in:
@@ -162,7 +162,7 @@ function ThreadItemAnchorParentReplyLine({isRoot}: {isRoot: boolean}) {
|
|||||||
const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
||||||
item,
|
item,
|
||||||
isRoot,
|
isRoot,
|
||||||
postShadow,
|
// postShadow,
|
||||||
onPostSuccess,
|
onPostSuccess,
|
||||||
threadgateRecord,
|
threadgateRecord,
|
||||||
postSource,
|
postSource,
|
||||||
@@ -488,7 +488,8 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
|
|||||||
<FeedFeedbackProvider value={feedFeedback}>
|
<FeedFeedbackProvider value={feedFeedback}>
|
||||||
<PostControls
|
<PostControls
|
||||||
big
|
big
|
||||||
post={postShadow}
|
// @ts-expect-error TODO no more shadow here
|
||||||
|
post={post}
|
||||||
record={record}
|
record={record}
|
||||||
richText={richText}
|
richText={richText}
|
||||||
onPressReply={onPressReply}
|
onPressReply={onPressReply}
|
||||||
|
|||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {mutatePost} from '#/state/queries/cache/mutations'
|
||||||
|
import {type PostMutations} from '#/state/queries/cache/types'
|
||||||
|
import {applyPostCacheMutator as applyPostCacheMutator_usePostThread} from '#/state/queries/usePostThread/queryCache'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies mutations to a post (identified by URI) in all active query caches
|
||||||
|
*/
|
||||||
|
export function applyPostCacheMutations(
|
||||||
|
qc: QueryClient,
|
||||||
|
uri: string,
|
||||||
|
mutations: Partial<PostMutations>,
|
||||||
|
) {
|
||||||
|
applyPostCacheMutator_usePostThread({
|
||||||
|
qc,
|
||||||
|
uri,
|
||||||
|
mutator: post => mutatePost(post, mutations),
|
||||||
|
})
|
||||||
|
|
||||||
|
// ... add all other queries below
|
||||||
|
}
|
||||||
Vendored
+72
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
AppBskyEmbedRecord,
|
||||||
|
AppBskyEmbedRecordWithMedia,
|
||||||
|
type AppBskyFeedDefs,
|
||||||
|
} from '@atproto/api'
|
||||||
|
|
||||||
|
import {type PostMutations} from '#/state/queries/cache/types'
|
||||||
|
import {DELETED_POST} from '#/state/queries/cache/util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies mutations to a post in the cache. If the post is deleted, returns a
|
||||||
|
* symbol to force individual cache mutators to handle the unknown value
|
||||||
|
* appropriately.
|
||||||
|
*/
|
||||||
|
export function mutatePost(
|
||||||
|
post: AppBskyFeedDefs.PostView,
|
||||||
|
mutations: Partial<PostMutations>,
|
||||||
|
): AppBskyFeedDefs.PostView | typeof DELETED_POST {
|
||||||
|
if (mutations.isDeleted) {
|
||||||
|
return DELETED_POST
|
||||||
|
}
|
||||||
|
|
||||||
|
let likeCount = post.likeCount ?? 0
|
||||||
|
if ('likeUri' in mutations) {
|
||||||
|
const wasLiked = !!post.viewer?.like
|
||||||
|
const isLiked = !!mutations.likeUri
|
||||||
|
if (wasLiked && !isLiked) {
|
||||||
|
likeCount--
|
||||||
|
} else if (!wasLiked && isLiked) {
|
||||||
|
likeCount++
|
||||||
|
}
|
||||||
|
likeCount = Math.max(0, likeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
let repostCount = post.repostCount ?? 0
|
||||||
|
if ('repostUri' in mutations) {
|
||||||
|
const wasReposted = !!post.viewer?.repost
|
||||||
|
const isReposted = !!mutations.repostUri
|
||||||
|
if (wasReposted && !isReposted) {
|
||||||
|
repostCount--
|
||||||
|
} else if (!wasReposted && isReposted) {
|
||||||
|
repostCount++
|
||||||
|
}
|
||||||
|
repostCount = Math.max(0, repostCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
let embed: typeof post.embed
|
||||||
|
if ('embed' in mutations) {
|
||||||
|
if (
|
||||||
|
(AppBskyEmbedRecord.isView(post.embed) &&
|
||||||
|
AppBskyEmbedRecord.isView(mutations.embed)) ||
|
||||||
|
(AppBskyEmbedRecordWithMedia.isView(post.embed) &&
|
||||||
|
AppBskyEmbedRecordWithMedia.isView(mutations.embed))
|
||||||
|
) {
|
||||||
|
embed = mutations.embed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...post,
|
||||||
|
embed: embed || post.embed,
|
||||||
|
likeCount: likeCount,
|
||||||
|
repostCount: repostCount,
|
||||||
|
viewer: {
|
||||||
|
...(post.viewer || {}),
|
||||||
|
like: 'likeUri' in mutations ? mutations.likeUri : post.viewer?.like,
|
||||||
|
repost:
|
||||||
|
'repostUri' in mutations ? mutations.repostUri : post.viewer?.repost,
|
||||||
|
pinned: 'pinned' in mutations ? mutations.pinned : post.viewer?.pinned,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+39
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
type AppBskyEmbedRecord,
|
||||||
|
type AppBskyEmbedRecordWithMedia,
|
||||||
|
type AppBskyFeedDefs,
|
||||||
|
} from '@atproto/api'
|
||||||
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {type DELETED_POST} from '#/state/queries/cache/util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available mutations for a post.
|
||||||
|
*/
|
||||||
|
export type PostMutations = {
|
||||||
|
likeUri: string | undefined
|
||||||
|
repostUri: string | undefined
|
||||||
|
isDeleted: boolean
|
||||||
|
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
||||||
|
pinned: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies mutations to a post.
|
||||||
|
*/
|
||||||
|
export type ApplyPostCacheMutator = (params: {
|
||||||
|
/**
|
||||||
|
* The query client to use for cache updates.
|
||||||
|
*/
|
||||||
|
qc: QueryClient
|
||||||
|
/**
|
||||||
|
* The URI of the post to mutate.
|
||||||
|
*/
|
||||||
|
uri: string
|
||||||
|
/**
|
||||||
|
* The mutation function that applies changes to the post.
|
||||||
|
*/
|
||||||
|
mutator: (
|
||||||
|
post: AppBskyFeedDefs.PostView,
|
||||||
|
) => AppBskyFeedDefs.PostView | typeof DELETED_POST
|
||||||
|
}) => void
|
||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
import {AtUri} from '@atproto/api'
|
||||||
|
|
||||||
|
import {type ApplyPostCacheMutator} from '#/state/queries/cache/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symbol used to indicate that a value has been deleted.
|
||||||
|
*/
|
||||||
|
export const DELETED_POST = Symbol('DELETED_POST')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sugar for creating a post cache mutator, for use alongside the definitions
|
||||||
|
* for all post queries.
|
||||||
|
*/
|
||||||
|
export function createApplyPostCacheMutator(
|
||||||
|
applyPostCacheMutator: ApplyPostCacheMutator,
|
||||||
|
) {
|
||||||
|
return applyPostCacheMutator
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a source URI matches a given URI, or the same URI with handle in
|
||||||
|
* place of a DID.
|
||||||
|
*/
|
||||||
|
export function uriMatches(
|
||||||
|
sourceUri: string,
|
||||||
|
matchUri: string,
|
||||||
|
matchHandle?: string,
|
||||||
|
) {
|
||||||
|
const source = new AtUri(sourceUri)
|
||||||
|
if (source.host.startsWith('did:')) return source.toString() === matchUri
|
||||||
|
if (!matchHandle) return false
|
||||||
|
source.host = matchHandle
|
||||||
|
return source.toString() === matchUri
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {type LogEvents, toClout} from '#/lib/statsig/statsig'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||||
import {type Shadow} from '#/state/cache/types'
|
import {type Shadow} from '#/state/cache/types'
|
||||||
|
import {applyPostCacheMutations} from '#/state/queries/cache'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import * as userActionHistory from '#/state/userActionHistory'
|
import * as userActionHistory from '#/state/userActionHistory'
|
||||||
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
||||||
@@ -135,7 +136,7 @@ export function usePostLikeMutationQueue(
|
|||||||
},
|
},
|
||||||
onSuccess(finalLikeUri) {
|
onSuccess(finalLikeUri) {
|
||||||
// finalize
|
// finalize
|
||||||
updatePostShadow(queryClient, postUri, {
|
applyPostCacheMutations(queryClient, postUri, {
|
||||||
likeUri: finalLikeUri,
|
likeUri: finalLikeUri,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -143,7 +144,7 @@ export function usePostLikeMutationQueue(
|
|||||||
|
|
||||||
const queueLike = useCallback(() => {
|
const queueLike = useCallback(() => {
|
||||||
// optimistically update
|
// optimistically update
|
||||||
updatePostShadow(queryClient, postUri, {
|
applyPostCacheMutations(queryClient, postUri, {
|
||||||
likeUri: 'pending',
|
likeUri: 'pending',
|
||||||
})
|
})
|
||||||
return queueToggle(true)
|
return queueToggle(true)
|
||||||
@@ -151,7 +152,7 @@ export function usePostLikeMutationQueue(
|
|||||||
|
|
||||||
const queueUnlike = useCallback(() => {
|
const queueUnlike = useCallback(() => {
|
||||||
// optimistically update
|
// optimistically update
|
||||||
updatePostShadow(queryClient, postUri, {
|
applyPostCacheMutations(queryClient, postUri, {
|
||||||
likeUri: undefined,
|
likeUri: undefined,
|
||||||
})
|
})
|
||||||
return queueToggle(false)
|
return queueToggle(false)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {type QueryClient} from '@tanstack/react-query'
|
import {type QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {
|
||||||
|
createApplyPostCacheMutator,
|
||||||
|
DELETED_POST,
|
||||||
|
uriMatches,
|
||||||
|
} from '#/state/queries/cache/util'
|
||||||
import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} from '#/state/queries/explore-feed-previews'
|
import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} from '#/state/queries/explore-feed-previews'
|
||||||
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/queries/notifications/feed'
|
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/queries/notifications/feed'
|
||||||
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed'
|
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed'
|
||||||
@@ -298,3 +303,42 @@ export function* findAllProfilesInQueryData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const applyPostCacheMutator = createApplyPostCacheMutator(
|
||||||
|
({qc, uri, mutator}) => {
|
||||||
|
qc.setQueriesData<AppBskyUnspeccedGetPostThreadV2.OutputSchema>(
|
||||||
|
{queryKey: [postThreadQueryKeyRoot]},
|
||||||
|
data => {
|
||||||
|
if (!data) return
|
||||||
|
|
||||||
|
const thread = []
|
||||||
|
|
||||||
|
for (const item of data.thread) {
|
||||||
|
if (
|
||||||
|
AppBskyUnspeccedDefs.isThreadItemPost(item.value) &&
|
||||||
|
uriMatches(uri, item.value.post.uri, item.value.post.author.handle)
|
||||||
|
) {
|
||||||
|
const post = mutator(item.value.post)
|
||||||
|
|
||||||
|
if (post !== DELETED_POST) {
|
||||||
|
thread.push({
|
||||||
|
...item,
|
||||||
|
value: {
|
||||||
|
...item.value,
|
||||||
|
post,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thread.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
thread,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user