diff options
Diffstat (limited to 'src/state/models/discovery')
-rw-r--r-- | src/state/models/discovery/suggested-posts.ts | 88 | ||||
-rw-r--r-- | src/state/models/discovery/user-autocomplete.ts | 103 |
2 files changed, 191 insertions, 0 deletions
diff --git a/src/state/models/discovery/suggested-posts.ts b/src/state/models/discovery/suggested-posts.ts new file mode 100644 index 000000000..6c8de3023 --- /dev/null +++ b/src/state/models/discovery/suggested-posts.ts @@ -0,0 +1,88 @@ +import {makeAutoObservable, runInAction} from 'mobx' +import {RootStoreModel} from '../root-store' +import {PostsFeedItemModel} from '../feeds/posts' +import {cleanError} from 'lib/strings/errors' +import {TEAM_HANDLES} from 'lib/constants' +import { + getMultipleAuthorsPosts, + mergePosts, +} from 'lib/api/build-suggested-posts' + +export class SuggestedPostsModel { + // state + isLoading = false + hasLoaded = false + error = '' + + // data + posts: PostsFeedItemModel[] = [] + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + { + rootStore: false, + }, + {autoBind: true}, + ) + } + + get hasContent() { + return this.posts.length > 0 + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async setup() { + this._xLoading() + try { + const responses = await getMultipleAuthorsPosts( + this.rootStore, + TEAM_HANDLES(String(this.rootStore.agent.service)), + undefined, + 30, + ) + runInAction(() => { + const finalPosts = mergePosts(responses, {repostsOnly: true}) + // hydrate into models + this.posts = finalPosts.map((post, i) => { + // strip the reasons to hide that these are reposts + delete post.reason + return new PostsFeedItemModel(this.rootStore, `post-${i}`, post) + }) + }) + this._xIdle() + } catch (e: any) { + this.rootStore.log.error('SuggestedPostsView: Failed to load posts', { + e, + }) + this._xIdle() // dont bubble to the user + } + } + + // state transitions + // = + + _xLoading() { + this.isLoading = true + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch suggested posts', err) + } + } +} diff --git a/src/state/models/discovery/user-autocomplete.ts b/src/state/models/discovery/user-autocomplete.ts new file mode 100644 index 000000000..601e10ea0 --- /dev/null +++ b/src/state/models/discovery/user-autocomplete.ts @@ -0,0 +1,103 @@ +import {makeAutoObservable, runInAction} from 'mobx' +import {AppBskyActorDefs} from '@atproto/api' +import AwaitLock from 'await-lock' +import {RootStoreModel} from '../root-store' + +export class UserAutocompleteModel { + // state + isLoading = false + isActive = false + prefix = '' + lock = new AwaitLock() + + // data + follows: AppBskyActorDefs.ProfileViewBasic[] = [] + searchRes: AppBskyActorDefs.ProfileViewBasic[] = [] + knownHandles: Set<string> = new Set() + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + { + rootStore: false, + knownHandles: false, + }, + {autoBind: true}, + ) + } + + get suggestions() { + if (!this.isActive) { + return [] + } + if (this.prefix) { + return this.searchRes.map(user => ({ + handle: user.handle, + displayName: user.displayName, + avatar: user.avatar, + })) + } + return this.follows.map(follow => ({ + handle: follow.handle, + displayName: follow.displayName, + avatar: follow.avatar, + })) + } + + // public api + // = + + async setup() { + await this._getFollows() + } + + setActive(v: boolean) { + this.isActive = v + } + + async setPrefix(prefix: string) { + const origPrefix = prefix.trim() + 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 + } + await this._search() + } else { + this.searchRes = [] + } + } finally { + this.lock.release() + } + } + + // internal + // = + + async _getFollows() { + const res = await this.rootStore.agent.getFollows({ + actor: this.rootStore.me.did || '', + }) + runInAction(() => { + this.follows = res.data.follows + for (const f of this.follows) { + this.knownHandles.add(f.handle) + } + }) + } + + async _search() { + const res = await this.rootStore.agent.searchActorsTypeahead({ + term: this.prefix, + limit: 8, + }) + runInAction(() => { + this.searchRes = res.data.actors + for (const u of this.searchRes) { + this.knownHandles.add(u.handle) + } + }) + } +} |