diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-11-14 10:41:55 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-14 10:41:55 -0800 |
commit | 0a26e78dcbbf48dad5daae73b210e236d706b22c (patch) | |
tree | c06c737ed49e8294bf5cbec1a75c36b591cb6669 /src/state | |
parent | c687172de96bd6aa85d3aa025c2e0f024640f345 (diff) | |
download | voidsky-0a26e78dcbbf48dad5daae73b210e236d706b22c.tar.zst |
Composer update (react-query refactor) (#1899)
* Move composer state to a context * Rework composer to use RQ --------- Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/models/media/gallery.ts | 9 | ||||
-rw-r--r-- | src/state/models/media/image.ts | 9 | ||||
-rw-r--r-- | src/state/models/ui/shell.ts | 52 | ||||
-rw-r--r-- | src/state/queries/actor-autocomplete.ts | 54 | ||||
-rw-r--r-- | src/state/shell/composer.tsx | 74 | ||||
-rw-r--r-- | src/state/shell/index.tsx | 5 |
6 files changed, 138 insertions, 65 deletions
diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts index f9c3efcad..04023bf82 100644 --- a/src/state/models/media/gallery.ts +++ b/src/state/models/media/gallery.ts @@ -1,5 +1,4 @@ import {makeAutoObservable, runInAction} from 'mobx' -import {RootStoreModel} from 'state/index' import {ImageModel} from './image' import {Image as RNImage} from 'react-native-image-crop-picker' import {openPicker} from 'lib/media/picker' @@ -8,10 +7,8 @@ import {getImageDim} from 'lib/media/manip' export class GalleryModel { images: ImageModel[] = [] - constructor(public rootStore: RootStoreModel) { - makeAutoObservable(this, { - rootStore: false, - }) + constructor() { + makeAutoObservable(this) } get isEmpty() { @@ -33,7 +30,7 @@ export class GalleryModel { // Temporarily enforce uniqueness but can eventually also use index if (!this.images.some(i => i.path === image_.path)) { - const image = new ImageModel(this.rootStore, image_) + const image = new ImageModel(image_) // Initial resize image.manipulate({}) diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts index b3796060c..6a226484e 100644 --- a/src/state/models/media/image.ts +++ b/src/state/models/media/image.ts @@ -1,5 +1,4 @@ import {Image as RNImage} from 'react-native-image-crop-picker' -import {RootStoreModel} from 'state/index' import {makeAutoObservable, runInAction} from 'mobx' import {POST_IMG_MAX} from 'lib/constants' import * as ImageManipulator from 'expo-image-manipulator' @@ -42,10 +41,8 @@ export class ImageModel implements Omit<RNImage, 'size'> { } prevAttributes: ImageManipulationAttributes = {} - constructor(public rootStore: RootStoreModel, image: Omit<RNImage, 'size'>) { - makeAutoObservable(this, { - rootStore: false, - }) + constructor(image: Omit<RNImage, 'size'>) { + makeAutoObservable(this) this.path = image.path this.width = image.width @@ -178,7 +175,7 @@ export class ImageModel implements Omit<RNImage, 'size'> { height: this.height, }) - const cropped = await openCropper(this.rootStore, { + const cropped = await openCropper({ mediaType: 'photo', path: this.path, freeStyleCropEnabled: true, diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 9ce9b6635..310d4f0f9 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -1,4 +1,4 @@ -import {AppBskyEmbedRecord, AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' import {makeAutoObservable, runInAction} from 'mobx' import { @@ -37,41 +37,9 @@ export class ImagesLightbox implements LightboxModel { } } -export interface ComposerOptsPostRef { - uri: string - cid: string - text: string - author: { - handle: string - displayName?: string - avatar?: string - } -} -export interface ComposerOptsQuote { - uri: string - cid: string - text: string - indexedAt: string - author: { - did: string - handle: string - displayName?: string - avatar?: string - } - embeds?: AppBskyEmbedRecord.ViewRecord['embeds'] -} -export interface ComposerOpts { - replyTo?: ComposerOptsPostRef - onPost?: () => void - quote?: ComposerOptsQuote - mention?: string // handle of user to mention -} - export class ShellUiModel { isLightboxActive = false activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null - isComposerActive = false - composerOpts: ComposerOpts | undefined tickEveryMinute = Date.now() constructor(public rootStore: RootStoreModel) { @@ -92,10 +60,6 @@ export class ShellUiModel { this.closeLightbox() return true } - if (this.isComposerActive) { - this.closeComposer() - return true - } return false } @@ -106,9 +70,6 @@ export class ShellUiModel { if (this.isLightboxActive) { this.closeLightbox() } - if (this.isComposerActive) { - this.closeComposer() - } } openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) { @@ -122,17 +83,6 @@ export class ShellUiModel { this.activeLightbox = null } - openComposer(opts: ComposerOpts) { - this.rootStore.emitNavigation() - this.isComposerActive = true - this.composerOpts = opts - } - - closeComposer() { - this.isComposerActive = false - this.composerOpts = undefined - } - setupClock() { setInterval(() => { runInAction(() => { diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 18abb6314..62c4781c4 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -1,7 +1,8 @@ -import {AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs, BskyAgent} from '@atproto/api' import {useQuery} from '@tanstack/react-query' import {useSession} from '../session' import {useMyFollowsQuery} from './my-follows' +import AwaitLock from 'await-lock' export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix] @@ -21,6 +22,57 @@ export function useActorAutocompleteQuery(prefix: string) { }) } +export class ActorAutocomplete { + // state + isLoading = false + isActive = false + prefix = '' + lock = new AwaitLock() + + // data + suggestions: AppBskyActorDefs.ProfileViewBasic[] = [] + + constructor( + public agent: BskyAgent, + public follows?: AppBskyActorDefs.ProfileViewBasic[] | undefined, + ) {} + + setFollows(follows: AppBskyActorDefs.ProfileViewBasic[]) { + this.follows = follows + } + + async query(prefix: string) { + const origPrefix = prefix.trim().toLocaleLowerCase() + this.prefix = origPrefix + await this.lock.acquireAsync() + try { + if (this.prefix) { + if (this.prefix !== origPrefix) { + return // another prefix was set before we got our chance + } + + // start with follow results + this.suggestions = computeSuggestions(this.prefix, this.follows) + + // ask backend + const res = await this.agent.searchActorsTypeahead({ + term: this.prefix, + limit: 8, + }) + this.suggestions = computeSuggestions( + this.prefix, + this.follows, + res.data.actors, + ) + } else { + this.suggestions = computeSuggestions(this.prefix, this.follows) + } + } finally { + this.lock.release() + } + } +} + function computeSuggestions( prefix: string, follows: AppBskyActorDefs.ProfileViewBasic[] = [], diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx new file mode 100644 index 000000000..a350bd7f3 --- /dev/null +++ b/src/state/shell/composer.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import {AppBskyEmbedRecord} from '@atproto/api' + +export interface ComposerOptsPostRef { + uri: string + cid: string + text: string + author: { + handle: string + displayName?: string + avatar?: string + } +} +export interface ComposerOptsQuote { + uri: string + cid: string + text: string + indexedAt: string + author: { + did: string + handle: string + displayName?: string + avatar?: string + } + embeds?: AppBskyEmbedRecord.ViewRecord['embeds'] +} +export interface ComposerOpts { + replyTo?: ComposerOptsPostRef + onPost?: () => void + quote?: ComposerOptsQuote + mention?: string // handle of user to mention +} + +type StateContext = ComposerOpts | undefined +type ControlsContext = { + openComposer: (opts: ComposerOpts) => void + closeComposer: () => void +} + +const stateContext = React.createContext<StateContext>(undefined) +const controlsContext = React.createContext<ControlsContext>({ + openComposer(_opts: ComposerOpts) {}, + closeComposer() {}, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [state, setState] = React.useState<StateContext>() + const api = React.useMemo( + () => ({ + openComposer(opts: ComposerOpts) { + setState(opts) + }, + closeComposer() { + setState(undefined) + }, + }), + [setState], + ) + return ( + <stateContext.Provider value={state}> + <controlsContext.Provider value={api}> + {children} + </controlsContext.Provider> + </stateContext.Provider> + ) +} + +export function useComposerState() { + return React.useContext(stateContext) +} + +export function useComposerControls() { + return React.useContext(controlsContext) +} diff --git a/src/state/shell/index.tsx b/src/state/shell/index.tsx index eb549b9f9..63c3763d1 100644 --- a/src/state/shell/index.tsx +++ b/src/state/shell/index.tsx @@ -5,6 +5,7 @@ import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled' import {Provider as MinimalModeProvider} from './minimal-mode' import {Provider as ColorModeProvider} from './color-mode' import {Provider as OnboardingProvider} from './onboarding' +import {Provider as ComposerProvider} from './composer' export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open' export { @@ -22,7 +23,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { <DrawerSwipableProvider> <MinimalModeProvider> <ColorModeProvider> - <OnboardingProvider>{children}</OnboardingProvider> + <OnboardingProvider> + <ComposerProvider>{children}</ComposerProvider> + </OnboardingProvider> </ColorModeProvider> </MinimalModeProvider> </DrawerSwipableProvider> |