mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at rn-stack-repro 295 lines 8.3 kB view raw
1import { 2 AtUri, 3 AppBskyGraphGetList, 4 AppBskyGraphList, 5 AppBskyGraphDefs, 6 Facet, 7} from '@atproto/api' 8import {Image as RNImage} from 'react-native-image-crop-picker' 9import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query' 10import chunk from 'lodash.chunk' 11import {useSession, getAgent} from '../session' 12import {invalidate as invalidateMyLists} from './my-lists' 13import {RQKEY as PROFILE_LISTS_RQKEY} from './profile-lists' 14import {uploadBlob} from '#/lib/api' 15import {until} from '#/lib/async/until' 16import {STALE} from '#/state/queries' 17 18export const RQKEY = (uri: string) => ['list', uri] 19 20export function useListQuery(uri?: string) { 21 return useQuery<AppBskyGraphDefs.ListView, Error>({ 22 staleTime: STALE.MINUTES.ONE, 23 queryKey: RQKEY(uri || ''), 24 async queryFn() { 25 if (!uri) { 26 throw new Error('URI not provided') 27 } 28 const res = await getAgent().app.bsky.graph.getList({ 29 list: uri, 30 limit: 1, 31 }) 32 return res.data.list 33 }, 34 enabled: !!uri, 35 }) 36} 37 38export interface ListCreateMutateParams { 39 purpose: string 40 name: string 41 description: string 42 descriptionFacets: Facet[] | undefined 43 avatar: RNImage | null | undefined 44} 45export function useListCreateMutation() { 46 const {currentAccount} = useSession() 47 const queryClient = useQueryClient() 48 return useMutation<{uri: string; cid: string}, Error, ListCreateMutateParams>( 49 { 50 async mutationFn({ 51 purpose, 52 name, 53 description, 54 descriptionFacets, 55 avatar, 56 }) { 57 if (!currentAccount) { 58 throw new Error('Not logged in') 59 } 60 if ( 61 purpose !== 'app.bsky.graph.defs#curatelist' && 62 purpose !== 'app.bsky.graph.defs#modlist' 63 ) { 64 throw new Error('Invalid list purpose: must be curatelist or modlist') 65 } 66 const record: AppBskyGraphList.Record = { 67 purpose, 68 name, 69 description, 70 descriptionFacets, 71 avatar: undefined, 72 createdAt: new Date().toISOString(), 73 } 74 if (avatar) { 75 const blobRes = await uploadBlob(getAgent(), avatar.path, avatar.mime) 76 record.avatar = blobRes.data.blob 77 } 78 const res = await getAgent().app.bsky.graph.list.create( 79 { 80 repo: currentAccount.did, 81 }, 82 record, 83 ) 84 85 // wait for the appview to update 86 await whenAppViewReady(res.uri, (v: AppBskyGraphGetList.Response) => { 87 return typeof v?.data?.list.uri === 'string' 88 }) 89 return res 90 }, 91 onSuccess() { 92 invalidateMyLists(queryClient) 93 queryClient.invalidateQueries({ 94 queryKey: PROFILE_LISTS_RQKEY(currentAccount!.did), 95 }) 96 }, 97 }, 98 ) 99} 100 101export interface ListMetadataMutateParams { 102 uri: string 103 name: string 104 description: string 105 descriptionFacets: Facet[] | undefined 106 avatar: RNImage | null | undefined 107} 108export function useListMetadataMutation() { 109 const {currentAccount} = useSession() 110 const queryClient = useQueryClient() 111 return useMutation< 112 {uri: string; cid: string}, 113 Error, 114 ListMetadataMutateParams 115 >({ 116 async mutationFn({uri, name, description, descriptionFacets, avatar}) { 117 const {hostname, rkey} = new AtUri(uri) 118 if (!currentAccount) { 119 throw new Error('Not logged in') 120 } 121 if (currentAccount.did !== hostname) { 122 throw new Error('You do not own this list') 123 } 124 125 // get the current record 126 const {value: record} = await getAgent().app.bsky.graph.list.get({ 127 repo: currentAccount.did, 128 rkey, 129 }) 130 131 // update the fields 132 record.name = name 133 record.description = description 134 record.descriptionFacets = descriptionFacets 135 if (avatar) { 136 const blobRes = await uploadBlob(getAgent(), avatar.path, avatar.mime) 137 record.avatar = blobRes.data.blob 138 } else if (avatar === null) { 139 record.avatar = undefined 140 } 141 const res = ( 142 await getAgent().com.atproto.repo.putRecord({ 143 repo: currentAccount.did, 144 collection: 'app.bsky.graph.list', 145 rkey, 146 record, 147 }) 148 ).data 149 150 // wait for the appview to update 151 await whenAppViewReady(res.uri, (v: AppBskyGraphGetList.Response) => { 152 const list = v.data.list 153 return ( 154 list.name === record.name && list.description === record.description 155 ) 156 }) 157 return res 158 }, 159 onSuccess(data, variables) { 160 invalidateMyLists(queryClient) 161 queryClient.invalidateQueries({ 162 queryKey: PROFILE_LISTS_RQKEY(currentAccount!.did), 163 }) 164 queryClient.invalidateQueries({ 165 queryKey: RQKEY(variables.uri), 166 }) 167 }, 168 }) 169} 170 171export function useListDeleteMutation() { 172 const {currentAccount} = useSession() 173 const queryClient = useQueryClient() 174 return useMutation<void, Error, {uri: string}>({ 175 mutationFn: async ({uri}) => { 176 if (!currentAccount) { 177 return 178 } 179 // fetch all the listitem records that belong to this list 180 let cursor 181 let listitemRecordUris: string[] = [] 182 for (let i = 0; i < 100; i++) { 183 const res = await getAgent().app.bsky.graph.listitem.list({ 184 repo: currentAccount.did, 185 cursor, 186 limit: 100, 187 }) 188 listitemRecordUris = listitemRecordUris.concat( 189 res.records 190 .filter(record => record.value.list === uri) 191 .map(record => record.uri), 192 ) 193 cursor = res.cursor 194 if (!cursor) { 195 break 196 } 197 } 198 199 // batch delete the list and listitem records 200 const createDel = (uri: string) => { 201 const urip = new AtUri(uri) 202 return { 203 $type: 'com.atproto.repo.applyWrites#delete', 204 collection: urip.collection, 205 rkey: urip.rkey, 206 } 207 } 208 const writes = listitemRecordUris 209 .map(uri => createDel(uri)) 210 .concat([createDel(uri)]) 211 212 // apply in chunks 213 for (const writesChunk of chunk(writes, 10)) { 214 await getAgent().com.atproto.repo.applyWrites({ 215 repo: currentAccount.did, 216 writes: writesChunk, 217 }) 218 } 219 220 // wait for the appview to update 221 await whenAppViewReady(uri, (v: AppBskyGraphGetList.Response) => { 222 return !v?.success 223 }) 224 }, 225 onSuccess() { 226 invalidateMyLists(queryClient) 227 queryClient.invalidateQueries({ 228 queryKey: PROFILE_LISTS_RQKEY(currentAccount!.did), 229 }) 230 // TODO!! /* dont await */ this.rootStore.preferences.removeSavedFeed(this.uri) 231 }, 232 }) 233} 234 235export function useListMuteMutation() { 236 const queryClient = useQueryClient() 237 return useMutation<void, Error, {uri: string; mute: boolean}>({ 238 mutationFn: async ({uri, mute}) => { 239 if (mute) { 240 await getAgent().muteModList(uri) 241 } else { 242 await getAgent().unmuteModList(uri) 243 } 244 245 await whenAppViewReady(uri, (v: AppBskyGraphGetList.Response) => { 246 return Boolean(v?.data.list.viewer?.muted) === mute 247 }) 248 }, 249 onSuccess(data, variables) { 250 queryClient.invalidateQueries({ 251 queryKey: RQKEY(variables.uri), 252 }) 253 }, 254 }) 255} 256 257export function useListBlockMutation() { 258 const queryClient = useQueryClient() 259 return useMutation<void, Error, {uri: string; block: boolean}>({ 260 mutationFn: async ({uri, block}) => { 261 if (block) { 262 await getAgent().blockModList(uri) 263 } else { 264 await getAgent().unblockModList(uri) 265 } 266 267 await whenAppViewReady(uri, (v: AppBskyGraphGetList.Response) => { 268 return block 269 ? typeof v?.data.list.viewer?.blocked === 'string' 270 : !v?.data.list.viewer?.blocked 271 }) 272 }, 273 onSuccess(data, variables) { 274 queryClient.invalidateQueries({ 275 queryKey: RQKEY(variables.uri), 276 }) 277 }, 278 }) 279} 280 281async function whenAppViewReady( 282 uri: string, 283 fn: (res: AppBskyGraphGetList.Response) => boolean, 284) { 285 await until( 286 5, // 5 tries 287 1e3, // 1s delay between tries 288 fn, 289 () => 290 getAgent().app.bsky.graph.getList({ 291 list: uri, 292 limit: 1, 293 }), 294 ) 295}