From 19c600843d9e4a2368a0f370e6022103fdcde2b4 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 5 Aug 2024 11:24:11 -0500 Subject: [PATCH] Start to break apart queries for better composition --- src/state/queries/threadgate/index.ts | 124 +++++++++++++++++++- src/state/queries/threadgate/util.ts | 20 +++- src/view/com/util/forms/PostDropdownBtn.tsx | 18 +-- 3 files changed, 150 insertions(+), 12 deletions(-) diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index 71ebc24797..54cf773141 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -2,7 +2,10 @@ import {AppBskyFeedThreadgate, AtUri, BskyAgent} from '@atproto/api' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {networkRetry} from '#/lib/async/retry' -import {mergeThreadgateRecords} from '#/state/queries/threadgate/util' +import { + createThreadgateRecord, + mergeThreadgateRecords, +} from '#/state/queries/threadgate/util' import {useAgent} from '#/state/session' export * from '#/state/queries/threadgate/types' @@ -47,6 +50,36 @@ export function useThreadgateRecordQuery({ }) } +export async function getThreadgateRecord({ + agent, + postUri, +}: { + agent: BskyAgent + postUri: string +}): Promise { + const postUrip = new AtUri(postUri) + + try { + const {data} = await networkRetry(2, () => + agent.api.com.atproto.repo.getRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + }), + ) + + if (data.value && AppBskyFeedThreadgate.isRecord(data.value)) { + return data.value + } + } catch (e: any) { + if (e.message.includes(`Could not locate record:`)) { + return undefined + } else { + throw new Error(`Failed to get threadgate record`, {cause: e}) + } + } +} + export async function createThreadgate({ agent, postUri, @@ -100,6 +133,34 @@ export async function createThreadgate({ } } +export async function overwriteThreadgateRecord({ + agent, + postUri, + threadgate, +}: { + agent: BskyAgent + postUri: string + threadgate: AppBskyFeedThreadgate.Record +}) { + const postUrip = new AtUri(postUri) + const record: AppBskyFeedThreadgate.Record = { + $type: 'app.bsky.feed.threadgate', + post: postUri, + allow: threadgate.allow || [], + hiddenReplies: threadgate.hiddenReplies || [], + createdAt: new Date().toISOString(), + } + + await networkRetry(2, () => + agent.api.com.atproto.repo.putRecord({ + repo: agent.session!.did, + collection: 'app.bsky.feed.threadgate', + rkey: postUrip.rkey, + record, + }), + ) +} + export function useCreateThreadgateMutation() { const agent = useAgent() const queryClient = useQueryClient() @@ -125,3 +186,64 @@ export function useCreateThreadgateMutation() { }, }) } + +export function useToggleReplyVisibilityMutation() { + const agent = useAgent() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + postUri, + replyUri, + action, + }: { + postUri: string + replyUri: string + action: 'hide' | 'show' + }) => { + const prev = await getThreadgateRecord({ + agent, + postUri, + }) + + if (prev) { + let threadgate = prev + + if (action === 'hide') { + threadgate = mergeThreadgateRecords(prev, { + hiddenReplies: [replyUri], + }) + } else if (action === 'show') { + threadgate = { + ...prev, + hiddenReplies: + prev.hiddenReplies?.filter(uri => uri !== replyUri) || [], + } + } + + await overwriteThreadgateRecord({ + agent, + postUri, + threadgate, + }) + } else { + if (action === 'hide') { + const threadgate = createThreadgateRecord({ + post: postUri, + hiddenReplies: [replyUri], + }) + await overwriteThreadgateRecord({ + agent, + postUri, + threadgate, + }) + } + } + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: [threadgateRecordQueryKeyRoot], + }) + }, + }) +} diff --git a/src/state/queries/threadgate/util.ts b/src/state/queries/threadgate/util.ts index 8f83c06e99..367e45377a 100644 --- a/src/state/queries/threadgate/util.ts +++ b/src/state/queries/threadgate/util.ts @@ -83,11 +83,25 @@ export function mergeThreadgateRecords( new Set([...(prev.hiddenReplies || []), ...(next.hiddenReplies || [])]), ) - return { - $type: 'app.bsky.feed.threadgate', + return createThreadgateRecord({ post: prev.post, allow, - createdAt: new Date().toISOString(), hiddenReplies, + }) +} + +export function createThreadgateRecord( + threadgate: Partial, +): AppBskyFeedThreadgate.Record { + if (!threadgate.post) { + throw new Error('Cannot create a threadgate record without a post URI') + } + + return { + $type: 'app.bsky.feed.threadgate', + post: threadgate.post, + createdAt: new Date().toISOString(), + allow: threadgate.allow || [], + hiddenReplies: threadgate.hiddenReplies || [], } } diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 028907d98a..76fdaec658 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -31,7 +31,7 @@ import { usePostDeleteMutation, useThreadMuteMutationQueue, } from '#/state/queries/post' -import {useCreateThreadgateMutation} from '#/state/queries/threadgate' +import {useToggleReplyVisibilityMutation} from '#/state/queries/threadgate' import {useSession} from '#/state/session' import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' @@ -107,7 +107,8 @@ let PostDropdownBtn = ({ const loggedOutWarningPromptControl = useDialogControl() const embedPostControl = useDialogControl() const sendViaChatControl = useDialogControl() - const {mutateAsync: createThreadgate} = useCreateThreadgateMutation() + const {mutateAsync: toggleReplyVisibility} = + useToggleReplyVisibilityMutation() const postUri = post.uri const postCid = post.cid @@ -402,16 +403,17 @@ let PostDropdownBtn = ({ {!isAuthor && !isPostHidden && rootPostUri && ( { - createThreadgate({ + toggleReplyVisibility({ postUri: rootPostUri, - threadgate: { - hiddenReplies: [postUri], - }, + replyUri: postUri, + action: 'hide', }) }}> - {_(msg`Hide reply`)} + + {_(msg`Hide reply for everyone`)} + )}