Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 237 lines 7.5 kB view raw
1import {View} from 'react-native' 2import { 3 type $Typed, 4 type AppBskyGraphDefs, 5 type AppBskyGraphListitem, 6 type AppBskyGraphStarterpack, 7 AtUri, 8 type ComAtprotoRepoApplyWrites, 9} from '@atproto/api' 10import {TID} from '@atproto/common-web' 11import {msg} from '@lingui/core/macro' 12import {useLingui} from '@lingui/react' 13import {Trans} from '@lingui/react/macro' 14import {useNavigation} from '@react-navigation/native' 15import {useQueryClient} from '@tanstack/react-query' 16import chunk from 'lodash.chunk' 17 18import {until} from '#/lib/async/until' 19import {wait} from '#/lib/async/wait' 20import {type NavigationProp} from '#/lib/routes/types' 21import {logger} from '#/logger' 22import {getAllListMembers} from '#/state/queries/list-members' 23import {useAgent, useSession} from '#/state/session' 24import {atoms as a, platform, useTheme, web} from '#/alf' 25import {Admonition} from '#/components/Admonition' 26import {Button, ButtonText} from '#/components/Button' 27import * as Dialog from '#/components/Dialog' 28import {Loader} from '#/components/Loader' 29import * as Toast from '#/components/Toast' 30import {Text} from '#/components/Typography' 31import {useAnalytics} from '#/analytics' 32import {CreateOrEditListDialog} from './CreateOrEditListDialog' 33 34export function CreateListFromStarterPackDialog({ 35 control, 36 starterPack, 37}: { 38 control: Dialog.DialogControlProps 39 starterPack: AppBskyGraphDefs.StarterPackView 40}) { 41 const {_} = useLingui() 42 const t = useTheme() 43 const agent = useAgent() 44 const ax = useAnalytics() 45 const {currentAccount} = useSession() 46 const navigation = useNavigation<NavigationProp>() 47 const queryClient = useQueryClient() 48 const createDialogControl = Dialog.useDialogControl() 49 const loadingDialogControl = Dialog.useDialogControl() 50 51 const record = starterPack.record as AppBskyGraphStarterpack.Record 52 53 const onPressCreate = () => { 54 control.close(() => createDialogControl.open()) 55 } 56 57 const addMembersAndNavigate = async (listUri: string) => { 58 const navigateToList = () => { 59 const urip = new AtUri(listUri) 60 navigation.navigate('ProfileList', { 61 name: urip.hostname, 62 rkey: urip.rkey, 63 }) 64 } 65 66 if (!starterPack.list || !currentAccount) { 67 loadingDialogControl.close(navigateToList) 68 return 69 } 70 71 try { 72 // Fetch all members and add them, with minimum 3s duration for UX 73 const listItems = await wait( 74 3000, 75 (async () => { 76 const items = await getAllListMembers(agent, starterPack.list!.uri) 77 78 if (items.length > 0) { 79 const listitemWrites: $Typed<ComAtprotoRepoApplyWrites.Create>[] = 80 items.map(item => { 81 const listitemRecord: $Typed<AppBskyGraphListitem.Record> = { 82 $type: 'app.bsky.graph.listitem', 83 subject: item.subject.did, 84 list: listUri, 85 createdAt: new Date().toISOString(), 86 } 87 return { 88 $type: 'com.atproto.repo.applyWrites#create', 89 collection: 'app.bsky.graph.listitem', 90 rkey: TID.nextStr(), 91 value: listitemRecord, 92 } 93 }) 94 95 const chunks = chunk(listitemWrites, 50) 96 for (const c of chunks) { 97 await agent.com.atproto.repo.applyWrites({ 98 repo: currentAccount.did, 99 writes: c, 100 }) 101 } 102 103 await until( 104 5, 105 1e3, 106 (res: {data: {items: unknown[]}}) => res.data.items.length > 0, 107 () => 108 agent.app.bsky.graph.getList({ 109 list: listUri, 110 limit: 1, 111 }), 112 ) 113 } 114 115 return items 116 })(), 117 ) 118 119 queryClient.invalidateQueries({queryKey: ['list-members', listUri]}) 120 121 ax.metric('starterPack:convertToList', { 122 starterPack: starterPack.uri, 123 memberCount: listItems.length, 124 }) 125 } catch (e) { 126 logger.error('Failed to add members to list', {safeMessage: e}) 127 Toast.show(_(msg`List created, but failed to add some members`), { 128 type: 'error', 129 }) 130 } 131 132 loadingDialogControl.close(navigateToList) 133 } 134 135 const onListCreated = (listUri: string) => { 136 loadingDialogControl.open() 137 addMembersAndNavigate(listUri) 138 } 139 140 return ( 141 <> 142 <Dialog.Outer 143 control={control} 144 testID="createListFromStarterPackDialog" 145 nativeOptions={{preventExpansion: true}}> 146 <Dialog.Handle /> 147 <Dialog.ScrollableInner 148 label={_(msg`Create list from starter pack`)} 149 style={web({maxWidth: 400})}> 150 <View style={[a.gap_lg]}> 151 <Text style={[a.text_xl, a.font_bold]}> 152 <Trans>Create list from starter pack</Trans> 153 </Text> 154 155 <Text 156 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_high]}> 157 <Trans> 158 This will create a new list with the same name, description, and 159 members as this starter pack. 160 </Trans> 161 </Text> 162 163 <Admonition type="tip"> 164 <Trans> 165 Changes to the starter pack will not be reflected in the list 166 after creation. The list will be an independent copy. 167 </Trans> 168 </Admonition> 169 170 <View 171 style={[ 172 platform({ 173 web: [a.flex_row_reverse], 174 native: [a.flex_col], 175 }), 176 a.gap_md, 177 a.pt_sm, 178 ]}> 179 <Button 180 label={_(msg`Create list`)} 181 onPress={onPressCreate} 182 size={platform({ 183 web: 'small', 184 native: 'large', 185 })} 186 color="primary"> 187 <ButtonText> 188 <Trans>Create list</Trans> 189 </ButtonText> 190 </Button> 191 <Button 192 label={_(msg`Cancel`)} 193 onPress={() => control.close()} 194 size={platform({ 195 web: 'small', 196 native: 'large', 197 })} 198 color="secondary"> 199 <ButtonText> 200 <Trans>Cancel</Trans> 201 </ButtonText> 202 </Button> 203 </View> 204 </View> 205 <Dialog.Close /> 206 </Dialog.ScrollableInner> 207 </Dialog.Outer> 208 209 <CreateOrEditListDialog 210 control={createDialogControl} 211 purpose="app.bsky.graph.defs#curatelist" 212 onSave={onListCreated} 213 initialValues={{ 214 name: record.name, 215 description: record.description, 216 avatar: starterPack.list?.avatar, 217 }} 218 /> 219 220 <Dialog.Outer 221 control={loadingDialogControl} 222 nativeOptions={{preventDismiss: true}}> 223 <Dialog.Handle /> 224 <Dialog.ScrollableInner 225 label={_(msg`Adding members to list...`)} 226 style={web({maxWidth: 400})}> 227 <View style={[a.align_center, a.gap_lg, a.py_5xl]}> 228 <Loader size="xl" /> 229 <Text style={[a.text_lg, t.atoms.text_contrast_high]}> 230 <Trans>Adding members to list...</Trans> 231 </Text> 232 </View> 233 </Dialog.ScrollableInner> 234 </Dialog.Outer> 235 </> 236 ) 237}