diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/queries/actor-autocomplete.ts | 169 |
1 files changed, 47 insertions, 122 deletions
diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 1bfa13f81..57f30f9c5 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -1,8 +1,6 @@ import React from 'react' -import {AppBskyActorDefs, BskyAgent} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import {useQuery, useQueryClient} from '@tanstack/react-query' -import AwaitLock from 'await-lock' -import Fuse from 'fuse.js' import {logger} from '#/logger' import {useSession} from '#/state/session' @@ -13,151 +11,78 @@ export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix] export function useActorAutocompleteQuery(prefix: string) { const {agent} = useSession() const {data: follows, isFetching} = useMyFollowsQuery() + return useQuery<AppBskyActorDefs.ProfileViewBasic[]>({ + // cached for 1 min + staleTime: 60 * 1000, queryKey: RQKEY(prefix || ''), async queryFn() { - const res = await agent.searchActorsTypeahead({ - term: prefix, - limit: 8, - }) - return computeSuggestions(prefix, follows, res.data.actors) + const res = prefix + ? await agent.searchActorsTypeahead({ + term: prefix, + limit: 8, + }) + : undefined + return computeSuggestions(prefix, follows, res?.data.actors) }, - enabled: !isFetching && !!prefix, + enabled: !isFetching, }) } -export function useActorSearch() { +export type ActorAutocompleteFn = ReturnType<typeof useActorAutocompleteFn> +export function useActorAutocompleteFn() { const queryClient = useQueryClient() const {agent} = useSession() const {data: follows} = useMyFollowsQuery() - const followsSearch = React.useMemo(() => { - if (!follows) return undefined - - return new Fuse(follows, { - includeScore: true, - keys: ['displayName', 'handle'], - }) - }, [follows]) - return React.useCallback( async ({query}: {query: string}) => { - let searchResults: AppBskyActorDefs.ProfileViewBasic[] = [] - - if (followsSearch) { - const results = followsSearch.search(query) - searchResults = results.map(({item}) => item) - } - - try { - const res = await queryClient.fetchQuery({ - // cached for 1 min - staleTime: 60 * 1000, - queryKey: ['search', query], - queryFn: () => - agent.searchActorsTypeahead({ - term: query, - limit: 8, - }), - }) - - if (res.data.actors) { - for (const actor of res.data.actors) { - if (!searchResults.find(item => item.handle === actor.handle)) { - searchResults.push(actor) - } - } + let res + if (query) { + try { + res = await queryClient.fetchQuery({ + // cached for 1 min + staleTime: 60 * 1000, + queryKey: RQKEY(query || ''), + queryFn: () => + agent.searchActorsTypeahead({ + term: query, + limit: 8, + }), + }) + } catch (e) { + logger.error('useActorSearch: searchActorsTypeahead failed', { + error: e, + }) } - } catch (e) { - logger.error('useActorSearch: searchActorsTypeahead failed', {error: e}) } - return searchResults + return computeSuggestions(query, follows, res?.data.actors) }, - [agent, followsSearch, queryClient], + [agent, follows, queryClient], ) } -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[] = [], + follows: AppBskyActorDefs.ProfileViewBasic[] | undefined, searched: AppBskyActorDefs.ProfileViewBasic[] = [], ) { - if (prefix) { - const items: AppBskyActorDefs.ProfileViewBasic[] = [] - for (const item of follows) { - if (prefixMatch(prefix, item)) { - items.push(item) - } - if (items.length >= 8) { - break - } - } - for (const item of searched) { - if (!items.find(item2 => item2.handle === item.handle)) { - items.push({ - did: item.did, - handle: item.handle, - displayName: item.displayName, - avatar: item.avatar, - }) - } + let items: AppBskyActorDefs.ProfileViewBasic[] = [] + if (follows) { + items = follows.filter(follow => prefixMatch(prefix, follow)).slice(0, 8) + } + for (const item of searched) { + if (!items.find(item2 => item2.handle === item.handle)) { + items.push({ + did: item.did, + handle: item.handle, + displayName: item.displayName, + avatar: item.avatar, + }) } - return items - } else { - return follows } + return items } function prefixMatch( |