From d53f253ae92abd425f8e6d57ccdefa92dca089c5 Mon Sep 17 00:00:00 2001 From: Hailey Date: Sun, 9 Jun 2024 19:11:14 -0700 Subject: [PATCH] finish editing --- src/screens/StarterPack/Wizard/State.tsx | 6 +- src/screens/StarterPack/Wizard/index.tsx | 246 ++++++++++++++++++----- src/state/queries/actor-starter-packs.ts | 22 +- src/state/queries/list-members.ts | 10 + src/state/queries/useStarterPackQuery.ts | 28 ++- 5 files changed, 247 insertions(+), 65 deletions(-) diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx index 9724ce19dc..96ced694a2 100644 --- a/src/screens/StarterPack/Wizard/State.tsx +++ b/src/screens/StarterPack/Wizard/State.tsx @@ -116,11 +116,11 @@ function reducer(state: State, action: Action): State { // TODO supply the initial state to this component export function Provider({ starterPack, - profiles, + listItems, children, }: { starterPack?: AppBskyGraphDefs.StarterPackView - profiles?: AppBskyActorDefs.ProfileViewBasic[] + listItems?: AppBskyGraphDefs.ListItemView[] children: React.ReactNode }) { const createInitialState = (): State => { @@ -130,7 +130,7 @@ export function Provider({ currentStep: 'Details', name: starterPack.record.name, description: starterPack.record.description, - profiles: profiles ?? [], + profiles: listItems?.map(i => i.subject) ?? [], feeds: starterPack.feeds ?? [], processing: false, } diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx index 9d0deb7942..7c1df78c27 100644 --- a/src/screens/StarterPack/Wizard/index.tsx +++ b/src/screens/StarterPack/Wizard/index.tsx @@ -1,22 +1,35 @@ import React from 'react' import {Keyboard, TouchableOpacity, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {AppBskyActorDefs, AtUri} from '@atproto/api' +import { + AppBskyActorDefs, + AppBskyGraphDefs, + AppBskyGraphStarterpack, + AtUri, +} from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {useQueryClient} from '@tanstack/react-query' import {HITSLOP_10} from 'lib/constants' import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' import {enforceLen} from 'lib/strings/helpers' import {isAndroid, isNative, isWeb} from 'platform/detection' -import {useListMembersQuery} from 'state/queries/list-members' +import {invalidateActorStarterPacksQuery} from 'state/queries/actor-starter-packs' +import { + invalidateListMembersQuery, + useListMembersQuery, +} from 'state/queries/list-members' import {useProfileQuery} from 'state/queries/profile' import {useResolveDidQuery} from 'state/queries/resolve-uri' -import {useStarterPackQuery} from 'state/queries/useStarterPackQuery' +import { + invalidateStarterPack, + useStarterPackQuery, +} from 'state/queries/useStarterPackQuery' import {useAgent, useSession} from 'state/session' import {useSetMinimalShellMode} from 'state/shell' import {UserAvatar} from 'view/com/util/UserAvatar' @@ -61,9 +74,13 @@ export function Wizard({ isLoading: isLoadingProfiles, isError: isErrorProfiles, } = useListMembersQuery(listUri, 51) // 51 because we also include the current user - const profiles = profilesData?.pages.flatMap(p => p.items.map(i => i.subject)) + const listItems = profilesData?.pages.flatMap(p => p.items) - if (name && rkey && (!starterPack || (starterPack && listUri && !profiles))) { + if ( + name && + rkey && + (!starterPack || (starterPack && listUri && !listItems)) + ) { return ( - + + ) } -function WizardInner() { +function WizardInner({ + did, + rkey, + createdAt: initialCreatedAt, + listUri: initialListUri, + listItems: initialListItems, +}: { + did?: string + rkey?: string + createdAt?: string + listUri?: string + listItems?: AppBskyGraphDefs.ListItemView[] +}) { const navigation = useNavigation() const {_} = useLingui() const t = useTheme() + const queryClient = useQueryClient() const [state, dispatch] = useWizardState() const agent = useAgent() const {currentAccount} = useSession() @@ -131,63 +171,163 @@ function WizardInner() { const uiStrings = wizardUiStrings[state.currentStep] + const createList = async (): Promise< + {uri: string; cid: string} | undefined + > => { + if (state.profiles.length === 0) return + + const list = await agent.app.bsky.graph.list.create( + {repo: currentAccount?.did}, + { + name: state.name ?? '', + description: state.description ?? '', + descriptionFacets: [], + avatar: undefined, + createdAt: new Date().toISOString(), + purpose: 'app.bsky.graph.defs#referencelist', + }, + ) + if (!list) throw new Error('List creation failed') + await agent.com.atproto.repo.applyWrites({ + repo: currentAccount!.did, + writes: state.profiles.map(p => ({ + $type: 'com.atproto.repo.applyWrites#create', + collection: 'app.bsky.graph.listitem', + value: { + $type: 'app.bsky.graph.listitem', + subject: p.did, + list: list?.uri, + createdAt: new Date().toISOString(), + }, + })), + }) + + return list + } + const submit = async () => { dispatch({type: 'SetProcessing', processing: true}) try { - const list = await agent.app.bsky.graph.list.create( - {repo: currentAccount?.did}, - { - name: state.name ?? '', - description: state.description ?? '', - descriptionFacets: [], - avatar: undefined, - createdAt: new Date().toISOString(), - purpose: 'app.bsky.graph.defs#referencelist', - }, - ) + if (did && rkey) { + // Editing an existing starter pack + let list: {uri: string; cid: string} | undefined = initialListUri + ? {uri: initialListUri, cid: ''} + : undefined + if (initialListUri) { + const removedItems = initialListItems?.filter( + i => !state.profiles.find(p => p.did === i.subject.did), + ) + if (removedItems && removedItems.length > 0) { + await agent.com.atproto.repo.applyWrites({ + repo: currentAccount!.did, + writes: removedItems.map(i => ({ + $type: 'com.atproto.repo.applyWrites#delete', + collection: 'app.bsky.graph.listitem', + rkey: new AtUri(i.uri).rkey, + })), + }) + } - await agent.com.atproto.repo.applyWrites({ - repo: currentAccount!.did, - writes: state.profiles.map(p => ({ - $type: 'com.atproto.repo.applyWrites#create', - collection: 'app.bsky.graph.listitem', - value: { - $type: 'app.bsky.graph.listitem', - subject: p.did, - list: list.uri, + const addedProfiles = state.profiles.filter( + p => !initialListItems?.find(i => i.subject.did === p.did), + ) + + if (addedProfiles.length > 0) { + await agent.com.atproto.repo.applyWrites({ + repo: currentAccount!.did, + writes: addedProfiles.map(p => ({ + $type: 'com.atproto.repo.applyWrites#create', + collection: 'app.bsky.graph.listitem', + value: { + $type: 'app.bsky.graph.listitem', + subject: p.did, + list: list?.uri, + createdAt: new Date().toISOString(), + }, + })), + }) + } + } else { + list = await createList() + } + + await agent.com.atproto.repo.putRecord({ + repo: currentAccount!.did, + collection: 'app.bsky.graph.starterpack', + rkey, + record: { + name: state.name ?? '', + description: state.description ?? '', + descriptionFacets: [], + list: list?.uri, + feeds: state.feeds.map(f => ({ + uri: f.uri, + })), + createdAt: initialCreatedAt, + updatedAt: new Date().toISOString(), + }, + }) + + if (initialListUri) { + await invalidateListMembersQuery({queryClient, uri: initialListUri}) + } + await invalidateActorStarterPacksQuery({ + queryClient, + did, + }) + await invalidateStarterPack({ + queryClient, + did, + rkey, + }) + + setTimeout(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.replace('StarterPack', { + name: currentAccount!.handle, + rkey, + }) + } + dispatch({type: 'SetProcessing', processing: false}) + }, 1000) + } else { + // Creating a new starter pack + const list = await createList() + const res = await agent.app.bsky.graph.starterpack.create( + { + repo: currentAccount!.did, + validate: false, + }, + { + name: state.name ?? '', + description: state.description ?? '', + descriptionFacets: [], + list: list?.uri, + feeds: state.feeds.map(f => ({ + uri: f.uri, + })), createdAt: new Date().toISOString(), }, - })), - }) + ) - const res = await agent.app.bsky.graph.starterpack.create( - { - repo: currentAccount!.did, - validate: false, - }, - { - name: state.name ?? '', - description: state.description ?? '', - descriptionFacets: [], - list: list.uri, - feeds: state.feeds.map(f => ({ - uri: f.uri, - })), - createdAt: new Date().toISOString(), - }, - ) + const newRkey = new AtUri(res.uri).rkey - const rkey = new AtUri(res.uri).rkey - - // TODO hack? - setTimeout(() => { - navigation.replace('StarterPack', {name: currentAccount!.handle, rkey}) - dispatch({type: 'SetProcessing', processing: false}) - }, 1000) + // TODO hack? + setTimeout(() => { + navigation.replace('StarterPack', { + name: currentAccount!.handle, + rkey: newRkey, + }) + dispatch({type: 'SetProcessing', processing: false}) + }, 1000) + } } catch (e) { // TODO handle the error here dispatch({type: 'SetProcessing', processing: false}) + return } } diff --git a/src/state/queries/actor-starter-packs.ts b/src/state/queries/actor-starter-packs.ts index fda876e64f..3aff90761c 100644 --- a/src/state/queries/actor-starter-packs.ts +++ b/src/state/queries/actor-starter-packs.ts @@ -1,10 +1,15 @@ import {AppBskyGraphGetActorStarterPacks} from '@atproto/api' -import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' +import { + InfiniteData, + QueryClient, + QueryKey, + useInfiniteQuery, +} from '@tanstack/react-query' -import {STALE} from 'state/queries/index' import {useAgent} from 'state/session' const RQKEY_ROOT = 'actor-starter-packs' +const RQKEY = (did?: string) => [RQKEY_ROOT, did] export function useActorStarterPacksQuery({did}: {did?: string}) { const agent = useAgent() @@ -16,7 +21,7 @@ export function useActorStarterPacksQuery({did}: {did?: string}) { QueryKey, string | undefined >({ - queryKey: [RQKEY_ROOT, did], + queryKey: RQKEY(did), queryFn: async ({pageParam}: {pageParam?: string}) => { const res = await agent.app.bsky.graph.getActorStarterPacks({ actor: did!, @@ -28,6 +33,15 @@ export function useActorStarterPacksQuery({did}: {did?: string}) { enabled: Boolean(did), initialPageParam: undefined, getNextPageParam: lastPage => lastPage.cursor, - staleTime: STALE.MINUTES.ONE, }) } + +export async function invalidateActorStarterPacksQuery({ + queryClient, + did, +}: { + queryClient: QueryClient + did: string +}) { + await queryClient.invalidateQueries({queryKey: RQKEY(did)}) +} diff --git a/src/state/queries/list-members.ts b/src/state/queries/list-members.ts index 92792325ef..816609263f 100644 --- a/src/state/queries/list-members.ts +++ b/src/state/queries/list-members.ts @@ -40,6 +40,16 @@ export function useListMembersQuery(uri?: string, limit: number = PAGE_SIZE) { }) } +export async function invalidateListMembersQuery({ + queryClient, + uri, +}: { + queryClient: QueryClient + uri: string +}) { + await queryClient.invalidateQueries({queryKey: RQKEY(uri)}) +} + export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, diff --git a/src/state/queries/useStarterPackQuery.ts b/src/state/queries/useStarterPackQuery.ts index d9db740bc8..f847371090 100644 --- a/src/state/queries/useStarterPackQuery.ts +++ b/src/state/queries/useStarterPackQuery.ts @@ -1,10 +1,10 @@ import {StarterPackView} from '@atproto/api/dist/client/types/app/bsky/graph/defs' -import {useMutation, useQuery} from '@tanstack/react-query' +import {QueryClient, useMutation, useQuery} from '@tanstack/react-query' -import {STALE} from 'state/queries/index' import {useAgent, useSession} from 'state/session' const RQKEY_ROOT = 'starter-pack' +const RQKEY = (did?: string, rkey?: string) => [RQKEY_ROOT, did, rkey] export function useStarterPackQuery({ did, @@ -17,7 +17,7 @@ export function useStarterPackQuery({ const uri = `at://${did}/app.bsky.graph.starterpack/${rkey}` return useQuery({ - queryKey: [RQKEY_ROOT, did, rkey], + queryKey: RQKEY(did, rkey), queryFn: async () => { const res = await agent.app.bsky.graph.getStarterPack({ starterPack: uri, @@ -25,7 +25,6 @@ export function useStarterPackQuery({ return res.data.starterPack }, enabled: Boolean(did) && Boolean(rkey), - staleTime: STALE.MINUTES.FIVE, }) } @@ -40,13 +39,32 @@ export function useDeleteStarterPackMutation({ const {currentAccount} = useSession() return useMutation({ - mutationFn: async (rkey: string) => { + mutationFn: async (rkey: string, listRkey?: string) => { await agent.app.bsky.graph.starterpack.delete({ repo: currentAccount!.did, rkey, }) + + if (listRkey) { + await agent.app.bsky.graph.list.delete({ + repo: currentAccount!.did, + rkey: listRkey, + }) + } }, onError, onSuccess, }) } + +export async function invalidateStarterPack({ + queryClient, + did, + rkey, +}: { + queryClient: QueryClient + did: string + rkey: string +}) { + await queryClient.invalidateQueries({queryKey: RQKEY(did, rkey)}) +}