an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
at main 184 lines 4.7 kB view raw
1import { type Agent, AtUri } from "@atproto/api"; 2import { TID } from "@atproto/common-web"; 3import type { QueryClient, UseQueryResult } from "@tanstack/react-query"; 4 5import { type linksRecordsResponse, useQueryConstellation } from "./useQuery"; 6 7export function useGetFollowState({ 8 target, 9 user, 10}: { 11 target: string; 12 user?: string; 13}): string[] | undefined { 14 const { data: followData } = useQueryConstellation( 15 user 16 ? { 17 method: "/links", 18 target: target, 19 // @ts-expect-error overloading sucks so much 20 collection: "app.bsky.graph.follow", 21 path: ".subject", 22 dids: [user], 23 } 24 : { method: "undefined", target: "whatever" }, 25 // overloading sucks so much 26 ) as { data: linksRecordsResponse | undefined }; 27 const follows = followData?.linking_records.slice(0, 50) ?? []; 28 29 if (follows.length > 0) { 30 return follows.map((linksRecord) => { 31 return `at://${linksRecord.did}/${linksRecord.collection}/${linksRecord.rkey}`; 32 }); 33 } 34 35 return undefined; 36} 37 38export function toggleFollow({ 39 agent, 40 targetDid, 41 followRecords, 42 queryClient, 43}: { 44 agent?: Agent; 45 targetDid?: string; 46 followRecords: undefined | string[]; 47 queryClient: QueryClient; 48}) { 49 if (!agent?.did || !targetDid) return; 50 51 const queryKey = [ 52 "constellation", 53 "/links", 54 targetDid, 55 "app.bsky.graph.follow", 56 ".subject", 57 undefined, 58 [agent.did], 59 ] as const; 60 61 const updateCache = ( 62 updater: ( 63 oldData: linksRecordsResponse | undefined, 64 ) => linksRecordsResponse | undefined, 65 ) => { 66 queryClient.setQueryData( 67 queryKey, 68 (oldData: linksRecordsResponse | undefined) => updater(oldData), 69 ); 70 }; 71 72 if (typeof followRecords === "undefined") { 73 const newRecord = { 74 repo: agent.did, 75 collection: "app.bsky.graph.follow", 76 rkey: TID.next().toString(), 77 record: { 78 $type: "app.bsky.graph.follow", 79 subject: targetDid, 80 createdAt: new Date().toISOString(), 81 }, 82 }; 83 84 updateCache((old) => { 85 const newLinkingRecords = [newRecord, ...(old?.linking_records ?? [])]; 86 return { 87 ...old, 88 linking_records: newLinkingRecords, 89 } as linksRecordsResponse; 90 }); 91 92 agent.com.atproto.repo.createRecord(newRecord).catch((err) => { 93 console.error("Follow failed, reverting cache:", err); 94 // rollback cache 95 updateCache((old) => { 96 return { 97 ...old, 98 linking_records: 99 old?.linking_records.filter((r) => r.rkey !== newRecord.rkey) ?? [], 100 } as linksRecordsResponse; 101 }); 102 }); 103 104 return; 105 } 106 107 followRecords.forEach((followRecord) => { 108 const aturi = new AtUri(followRecord); 109 agent.com.atproto.repo 110 .deleteRecord({ 111 repo: agent.did!, 112 collection: "app.bsky.graph.follow", 113 rkey: aturi.rkey, 114 }) 115 .catch(console.error); 116 }); 117 118 updateCache((old) => { 119 if (!old?.linking_records) return old; 120 return { 121 ...old, 122 linking_records: old.linking_records.filter( 123 (rec) => 124 !followRecords.includes( 125 `at://${rec.did}/${rec.collection}/${rec.rkey}`, 126 ), 127 ), 128 }; 129 }); 130} 131 132export function useGetOneToOneState(params?: { 133 target: string; 134 user: string; 135 collection: string; 136 path: string; 137 enabled?: boolean; 138}): { 139 uris: string[], 140 isLoading: boolean; 141 isError: boolean; 142} { 143 const whatever = useQueryConstellation( 144 params && params.user 145 ? { 146 method: "/links", 147 target: params.target, 148 // @ts-expect-error overloading sucks so much 149 collection: params.collection, 150 path: params.path, 151 dids: [params.user], 152 // todo disgusting hack please never code again 153 customkey: params.collection.includes("reddwarf.poll.vote") 154 ? "constellation-polls" 155 : undefined, 156 enabled: params.enabled || false, 157 } 158 : { method: "undefined", target: "whatever", enabled: false }, 159 // overloading sucks so much 160 ) as UseQueryResult<linksRecordsResponse | undefined, Error>; 161 if (!params || !params.user) return { 162 uris: [], 163 isError: true, 164 isLoading: false, 165 }; 166 const arbitrarydata = whatever.data; 167 const data = arbitrarydata?.linking_records.slice(0, 50) ?? []; 168 169 if (data.length > 0) { 170 return { 171 uris: data.map((linksRecord) => { 172 return `at://${linksRecord.did}/${linksRecord.collection}/${linksRecord.rkey}`; 173 }), 174 isError: false, 175 isLoading: false, 176 }; 177 } 178 179 return { 180 uris: [], 181 isError: whatever.isLoading, 182 isLoading: whatever.isError, 183 }; 184}