Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

finish agent logic

+126 -57
+28
src/state/session/index.tsx
··· 64 64 resumeSession: async () => {}, 65 65 removeAccount: () => {}, 66 66 partialRefreshSession: async () => {}, 67 + createEphemeralAgent: async () => { 68 + throw new Error('Not implemented') 69 + }, 67 70 }) 68 71 ApiContext.displayName = 'SessionApiContext' 69 72 ··· 314 317 }) 315 318 }, [store, state, cancelPendingTask]) 316 319 320 + const createEphemeralAgent = React.useCallback< 321 + SessionApiContext['createEphemeralAgent'] 322 + >( 323 + async storedAccount => { 324 + const {agent} = await createAgentAndResume( 325 + storedAccount, 326 + (ephemeralAgent, accountDid, sessionEvent) => { 327 + const refreshedAccount = agentToSessionAccount(ephemeralAgent) 328 + 329 + store.dispatch({ 330 + type: 'received-agent-event', 331 + agent: ephemeralAgent as any, 332 + refreshedAccount, 333 + accountDid, 334 + sessionEvent, 335 + }) 336 + }, 337 + ) 338 + return agent 339 + }, 340 + [store], 341 + ) 342 + 317 343 const removeAccount = useCallback<SessionApiContext['removeAccount']>( 318 344 account => { 319 345 addSessionDebugLog({ ··· 388 414 resumeSession, 389 415 removeAccount, 390 416 partialRefreshSession, 417 + createEphemeralAgent, 391 418 }), 392 419 [ 393 420 createAccount, ··· 397 424 resumeSession, 398 425 removeAccount, 399 426 partialRefreshSession, 427 + createEphemeralAgent, 400 428 ], 401 429 ) 402 430
+3
src/state/session/types.ts
··· 54 54 * `persistSessionHandler`. 55 55 */ 56 56 partialRefreshSession: () => Promise<void> 57 + createEphemeralAgent: ( 58 + account: SessionAccount, 59 + ) => Promise<import('@atproto/api').BskyAgent> 57 60 }
+72 -43
src/view/com/composer/Composer.tsx
··· 106 106 useOpenRouterModel, 107 107 } from '#/state/preferences/openrouter' 108 108 import {usePreferencesQuery} from '#/state/queries/preferences' 109 - import {useProfileQuery} from '#/state/queries/profile' 109 + import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' 110 110 import {type Gif} from '#/state/queries/tenor' 111 - import {useAgent, useSession} from '#/state/session' 111 + import {useAgent, useSession, useSessionApi} from '#/state/session' 112 112 import {useComposerControls} from '#/state/shell/composer' 113 113 import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' 114 114 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 134 134 import {VideoPreview} from '#/view/com/composer/videos/VideoPreview' 135 135 import {VideoTranscodeProgress} from '#/view/com/composer/videos/VideoTranscodeProgress' 136 136 import {UserAvatar} from '#/view/com/util/UserAvatar' 137 + import {SwitchMenuItems} from '#/view/shell/desktop/LeftNav' 137 138 import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf' 138 139 import {Admonition} from '#/components/Admonition' 139 140 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 142 143 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' 143 144 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 144 145 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 146 + import * as Menu from '#/components/Menu' 145 147 import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed' 146 148 import * as Prompt from '#/components/Prompt' 147 149 import * as Toast from '#/components/Toast' ··· 186 188 import {type TextInputRef} from './text-input/TextInput.types' 187 189 import {getVideoMetadata} from './videos/pickVideo' 188 190 import {clearThumbnailCache} from './videos/VideoTranscodeBackdrop' 189 - import * as Menu from '#/components/Menu' 190 - import {SwitchMenuItems} from "#/view/shell/desktop/LeftNav" 191 - import {useProfilesQuery} from '#/state/queries/profile' 192 191 193 192 type CancelRef = { 194 193 onPressCancel: () => void ··· 210 209 }: Props & { 211 210 cancelRef?: React.RefObject<CancelRef | null> 212 211 }) => { 213 - const {currentAccount} = useSession() 212 + const {currentAccount, accounts} = useSession() 214 213 const t = useTheme() 215 214 const ax = useAnalytics() 216 215 const agent = useAgent() 216 + const sessionApi = useSessionApi() 217 217 const queryClient = useQueryClient() 218 218 const currentDid = currentAccount!.did 219 + 220 + const [activeAccountDid, setActiveAccountDid] = useState<string>(currentDid) 221 + 219 222 const {closeComposer} = useComposerControls() 220 223 const {t: l, i18n} = useLingui() 221 224 const requireAltTextEnabled = useRequireAltTextEnabled() ··· 905 908 setError('') 906 909 setIsPublishing(true) 907 910 911 + let currentAgent = agent 912 + let ephemeralAgent: BskyAgent | undefined 913 + if (activeAccountDid && activeAccountDid !== currentAccount?.did) { 914 + const activeAccount = accounts.find(a => a.did === activeAccountDid) 915 + if (activeAccount) { 916 + ephemeralAgent = await sessionApi.createEphemeralAgent(activeAccount) 917 + currentAgent = ephemeralAgent 918 + } 919 + } 920 + 908 921 let postUri: string | undefined 909 922 let postSuccessData: OnPostSuccessData 910 923 try { 911 924 logger.info(`composer: posting...`) 912 925 postUri = ( 913 926 await apilib.post( 914 - agent, 927 + currentAgent, 915 928 queryClient, 916 929 { 917 930 thread: filteredThread, ··· 943 956 5, 944 957 _e => true, 945 958 async () => { 946 - const res = await agent.app.bsky.unspecced.getPostThreadV2({ 959 + const res = await currentAgent.app.bsky.unspecced.getPostThreadV2({ 947 960 anchor: postUri!, 948 961 above: false, 949 962 below: filteredThread.posts.length - 1, ··· 991 1004 setIsPublishing(false) 992 1005 return 993 1006 } finally { 1007 + if (ephemeralAgent && 'dispose' in ephemeralAgent) { 1008 + // @ts-ignore 1009 + ephemeralAgent.dispose() 1010 + } 1011 + 994 1012 if (postUri) { 995 1013 let index = 0 996 1014 for (let post of filteredThread.posts) { ··· 1099 1117 onPostSuccess, 1100 1118 initQuote, 1101 1119 replyTo, 1102 - setLangPrefs, 1120 + setPublishOnUpload, 1103 1121 queryClient, 1104 1122 navigation, 1105 1123 composerState.draftId, ··· 1108 1126 cleanupPublishedDraft, 1109 1127 loadedDraftCreatedAt, 1110 1128 emptyPostsPromptControl, 1129 + setLangPrefs, 1130 + accounts, 1131 + activeAccountDid, 1132 + currentAccount?.did, 1133 + sessionApi, 1111 1134 ]) 1112 1135 1113 1136 const handleConfirmSkipEmpty = () => { ··· 1308 1331 canRemoveQuote={index > 0 || !initQuote} 1309 1332 onSelectVideo={selectVideo} 1310 1333 onClearVideo={clearVideo} 1334 + onError={setError} 1311 1335 onPublish={onComposerPostPublish} 1312 - onError={setError} 1336 + activeAccountDid={activeAccountDid} 1337 + setActiveAccountDid={setActiveAccountDid} 1313 1338 /> 1314 1339 {IS_WEBFooterSticky && post.id === activePost.id && ( 1315 1340 <View style={styles.stickyFooterWeb}>{footer}</View> ··· 1406 1431 onSelectVideo, 1407 1432 onError, 1408 1433 onPublish, 1434 + activeAccountDid, 1435 + setActiveAccountDid, 1409 1436 }: { 1410 1437 post: PostDraft 1411 1438 dispatch: (action: ComposerAction) => void ··· 1421 1448 onSelectVideo: (postId: string, asset: ImagePickerAsset) => void 1422 1449 onError: (error: string) => void 1423 1450 onPublish: (richtext: RichText) => void 1451 + activeAccountDid: string 1452 + setActiveAccountDid: (did: string) => void 1424 1453 }) { 1425 1454 const {currentAccount, accounts} = useSession() 1426 - const currentDid = currentAccount!.did 1427 1455 const {t: l} = useLingui() 1428 - const {data: currentProfile} = useProfileQuery({did: currentDid}) 1456 + const {data: currentProfile} = useProfileQuery({did: activeAccountDid}) 1429 1457 const richtext = post.richtext 1430 1458 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media 1431 1459 const forceMinHeight = IS_WEB && isTextOnly && isActive ··· 1494 1522 [post.id, onSelectVideo, onImageAdd, l], 1495 1523 ) 1496 1524 1497 - 1498 - const {isLoading, data} = useProfilesQuery({ 1525 + const {data} = useProfilesQuery({ 1499 1526 handles: accounts.map(acc => acc.did), 1500 1527 }) 1501 1528 const profiles = data?.profiles ··· 1519 1546 isTextOnly && isLastPost && IS_NATIVE && a.flex_grow, 1520 1547 ]}> 1521 1548 <View style={[a.flex_row, IS_NATIVE && a.flex_1]}> 1522 - <Menu.Root> 1523 - <Menu.Trigger label={_(msg`Switch accounts`)}> 1524 - {({props}) => ( 1525 - <Button 1526 - label={props.accessibilityLabel} 1527 - {...props} 1528 - style={[ 1529 - a.transition_color, 1530 - enableSquareButtons ? a.rounded_sm : a.rounded_full, 1531 - a.self_start 1532 - ]}> 1533 - <UserAvatar 1534 - avatar={currentProfile?.avatar} 1535 - size={42} 1536 - type={currentProfile?.associated?.labeler ? 'labeler' : 'user'} 1537 - style={[a.mt_xs]} 1538 - /> 1539 - </Button> 1540 - )} 1541 - </Menu.Trigger> 1542 - { 1543 - <SwitchMenuItems 1544 - accounts={otherAccounts} 1545 - signOutPromptControl={signOutPromptControl} 1546 - showExtraButtons={false} 1547 - />} 1548 - </Menu.Root> 1549 + <Menu.Root> 1550 + <Menu.Trigger label={_(msg`Switch accounts`)}> 1551 + {({props}) => ( 1552 + <Button 1553 + label={props.accessibilityLabel} 1554 + {...props} 1555 + style={[ 1556 + a.transition_color, 1557 + enableSquareButtons ? a.rounded_sm : a.rounded_full, 1558 + a.self_start, 1559 + ]}> 1560 + <UserAvatar 1561 + avatar={currentProfile?.avatar} 1562 + size={42} 1563 + type={ 1564 + currentProfile?.associated?.labeler ? 'labeler' : 'user' 1565 + } 1566 + style={[a.mt_xs]} 1567 + /> 1568 + </Button> 1569 + )} 1570 + </Menu.Trigger> 1571 + { 1572 + <SwitchMenuItems 1573 + accounts={otherAccounts} 1574 + signOutPromptControl={signOutPromptControl} 1575 + showExtraButtons={false} 1576 + onSelectAccount={account => setActiveAccountDid(account.did)} 1577 + /> 1578 + } 1579 + </Menu.Root> 1549 1580 <TextInput 1550 1581 ref={textInputRef} 1551 1582 style={[a.pt_xs]} ··· 2874 2905 </ToolbarWrapper> 2875 2906 ) 2876 2907 } 2877 - 2878 -
+23 -14
src/view/shell/desktop/LeftNav.tsx
··· 225 225 export function SwitchMenuItems({ 226 226 accounts, 227 227 signOutPromptControl, 228 - showExtraButtons 228 + showExtraButtons, 229 + onSelectAccount, 229 230 }: { 230 231 accounts: 231 232 | { ··· 235 236 | undefined 236 237 signOutPromptControl: DialogControlProps 237 238 showExtraButtons?: boolean 239 + onSelectAccount?: (account: SessionAccount) => void 238 240 }) { 239 241 const {_} = useLingui() 240 242 const {setShowLoggedOut} = useLoggedOutViewControls() 241 243 const closeEverything = useCloseAllActiveElements() 242 244 243 - showExtraButtons = showExtraButtons ?? true; 245 + showExtraButtons = showExtraButtons ?? true 244 246 245 247 const onAddAnotherAccount = () => { 246 248 setShowLoggedOut(true) ··· 260 262 key={other.account.did} 261 263 account={other.account} 262 264 profile={other.profile} 265 + onSelectAccount={onSelectAccount} 263 266 /> 264 267 ))} 265 268 </Menu.Group> 266 269 <Menu.Divider /> 267 270 </> 268 271 )} 269 - {showExtraButtons ? <SwitcherMenuProfileLink /> : undefined } 272 + {showExtraButtons ? <SwitcherMenuProfileLink /> : undefined} 270 273 <Menu.Item 271 274 label={_(msg`Add another account`)} 272 275 onPress={onAddAnotherAccount}> ··· 275 278 <Trans>Add another account</Trans> 276 279 </Menu.ItemText> 277 280 </Menu.Item> 278 - { 279 - showExtraButtons ? 280 - <Menu.Item label={_(msg`Sign out`)} onPress={signOutPromptControl.open}> 281 - <Menu.ItemIcon icon={LeaveIcon} /> 282 - <Menu.ItemText> 283 - <Trans>Sign out</Trans> 284 - </Menu.ItemText> 285 - </Menu.Item> 286 - : undefined 287 - } 281 + {showExtraButtons ? ( 282 + <Menu.Item label={_(msg`Sign out`)} onPress={signOutPromptControl.open}> 283 + <Menu.ItemIcon icon={LeaveIcon} /> 284 + <Menu.ItemText> 285 + <Trans>Sign out</Trans> 286 + </Menu.ItemText> 287 + </Menu.Item> 288 + ) : undefined} 288 289 </Menu.Outer> 289 290 ) 290 291 } ··· 342 343 function SwitchMenuItem({ 343 344 account, 344 345 profile, 346 + onSelectAccount, 345 347 }: { 346 348 account: SessionAccount 347 349 profile: AppBskyActorDefs.ProfileViewDetailed | undefined 350 + onSelectAccount?: (account: SessionAccount) => void 348 351 }) { 349 352 const {_} = useLingui() 350 353 const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() ··· 361 364 '@', 362 365 )}`, 363 366 )} 364 - onPress={() => void onPressSwitchAccount(account, 'SwitchAccount')}> 367 + onPress={() => { 368 + if (onSelectAccount) { 369 + onSelectAccount(account) 370 + } else { 371 + void onPressSwitchAccount(account, 'SwitchAccount') 372 + } 373 + }}> 365 374 <View> 366 375 <UserAvatar 367 376 avatar={profile?.avatar}