diff options
Diffstat (limited to 'src/state/models/lists')
-rw-r--r-- | src/state/models/lists/likes.ts | 131 | ||||
-rw-r--r-- | src/state/models/lists/reposted-by.ts | 135 | ||||
-rw-r--r-- | src/state/models/lists/user-followers.ts | 120 | ||||
-rw-r--r-- | src/state/models/lists/user-follows.ts | 120 |
4 files changed, 506 insertions, 0 deletions
diff --git a/src/state/models/lists/likes.ts b/src/state/models/lists/likes.ts new file mode 100644 index 000000000..e88389c56 --- /dev/null +++ b/src/state/models/lists/likes.ts @@ -0,0 +1,131 @@ +import {makeAutoObservable, runInAction} from 'mobx' +import {AtUri} from '../../../third-party/uri' +import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {cleanError} from 'lib/strings/errors' +import {bundleAsync} from 'lib/async/bundle' +import * as apilib from 'lib/api/index' + +const PAGE_SIZE = 30 + +export type LikeItem = GetLikes.Like + +export class LikesModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + resolvedUri = '' + params: GetLikes.QueryParams + hasMore = true + loadMoreCursor?: string + + // data + uri: string = '' + likes: LikeItem[] = [] + + constructor(public rootStore: RootStoreModel, params: GetLikes.QueryParams) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.uri !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async refresh() { + return this.loadMore(true) + } + + loadMore = bundleAsync(async (replace: boolean = false) => { + if (!replace && !this.hasMore) { + return + } + this._xLoading(replace) + try { + if (!this.resolvedUri) { + await this._resolveUri() + } + const params = Object.assign({}, this.params, { + uri: this.resolvedUri, + limit: PAGE_SIZE, + cursor: replace ? undefined : this.loadMoreCursor, + }) + const res = await this.rootStore.agent.getLikes(params) + if (replace) { + this._replaceAll(res) + } else { + this._appendAll(res) + } + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) + + // state transitions + // = + + _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch likes', err) + } + } + + // helper functions + // = + + async _resolveUri() { + const urip = new AtUri(this.params.uri) + if (!urip.host.startsWith('did:')) { + try { + urip.host = await apilib.resolveName(this.rootStore, urip.host) + } catch (e: any) { + this.error = e.toString() + } + } + runInAction(() => { + this.resolvedUri = urip.toString() + }) + } + + _replaceAll(res: GetLikes.Response) { + this.likes = [] + this._appendAll(res) + } + + _appendAll(res: GetLikes.Response) { + this.loadMoreCursor = res.data.cursor + this.hasMore = !!this.loadMoreCursor + this.likes = this.likes.concat(res.data.likes) + } +} diff --git a/src/state/models/lists/reposted-by.ts b/src/state/models/lists/reposted-by.ts new file mode 100644 index 000000000..08cdc9ef5 --- /dev/null +++ b/src/state/models/lists/reposted-by.ts @@ -0,0 +1,135 @@ +import {makeAutoObservable, runInAction} from 'mobx' +import {AtUri} from '../../../third-party/uri' +import { + AppBskyFeedGetRepostedBy as GetRepostedBy, + AppBskyActorDefs, +} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {bundleAsync} from 'lib/async/bundle' +import {cleanError} from 'lib/strings/errors' +import * as apilib from 'lib/api/index' + +const PAGE_SIZE = 30 + +export type RepostedByItem = AppBskyActorDefs.ProfileViewBasic + +export class RepostedByModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + resolvedUri = '' + params: GetRepostedBy.QueryParams + hasMore = true + loadMoreCursor?: string + + // data + uri: string = '' + repostedBy: RepostedByItem[] = [] + + constructor( + public rootStore: RootStoreModel, + params: GetRepostedBy.QueryParams, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.uri !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async refresh() { + return this.loadMore(true) + } + + loadMore = bundleAsync(async (replace: boolean = false) => { + this._xLoading(replace) + try { + if (!this.resolvedUri) { + await this._resolveUri() + } + const params = Object.assign({}, this.params, { + uri: this.resolvedUri, + limit: PAGE_SIZE, + cursor: replace ? undefined : this.loadMoreCursor, + }) + const res = await this.rootStore.agent.getRepostedBy(params) + if (replace) { + this._replaceAll(res) + } else { + this._appendAll(res) + } + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) + + // state transitions + // = + + _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch reposted by view', err) + } + } + + // helper functions + // = + + async _resolveUri() { + const urip = new AtUri(this.params.uri) + if (!urip.host.startsWith('did:')) { + try { + urip.host = await apilib.resolveName(this.rootStore, urip.host) + } catch (e: any) { + this.error = e.toString() + } + } + runInAction(() => { + this.resolvedUri = urip.toString() + }) + } + + _replaceAll(res: GetRepostedBy.Response) { + this.repostedBy = [] + this._appendAll(res) + } + + _appendAll(res: GetRepostedBy.Response) { + this.loadMoreCursor = res.data.cursor + this.hasMore = !!this.loadMoreCursor + this.repostedBy = this.repostedBy.concat(res.data.repostedBy) + this.rootStore.me.follows.hydrateProfiles(res.data.repostedBy) + } +} diff --git a/src/state/models/lists/user-followers.ts b/src/state/models/lists/user-followers.ts new file mode 100644 index 000000000..2962d6242 --- /dev/null +++ b/src/state/models/lists/user-followers.ts @@ -0,0 +1,120 @@ +import {makeAutoObservable} from 'mobx' +import { + AppBskyGraphGetFollowers as GetFollowers, + AppBskyActorDefs as ActorDefs, +} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {cleanError} from 'lib/strings/errors' +import {bundleAsync} from 'lib/async/bundle' + +const PAGE_SIZE = 30 + +export type FollowerItem = ActorDefs.ProfileViewBasic + +export class UserFollowersModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + params: GetFollowers.QueryParams + hasMore = true + loadMoreCursor?: string + + // data + subject: ActorDefs.ProfileViewBasic = { + did: '', + handle: '', + } + followers: FollowerItem[] = [] + + constructor( + public rootStore: RootStoreModel, + params: GetFollowers.QueryParams, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.subject.did !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async refresh() { + return this.loadMore(true) + } + + loadMore = bundleAsync(async (replace: boolean = false) => { + if (!replace && !this.hasMore) { + return + } + this._xLoading(replace) + try { + const params = Object.assign({}, this.params, { + limit: PAGE_SIZE, + cursor: replace ? undefined : this.loadMoreCursor, + }) + const res = await this.rootStore.agent.getFollowers(params) + if (replace) { + this._replaceAll(res) + } else { + this._appendAll(res) + } + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) + + // state transitions + // = + + _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch user followers', err) + } + } + + // helper functions + // = + + _replaceAll(res: GetFollowers.Response) { + this.followers = [] + this._appendAll(res) + } + + _appendAll(res: GetFollowers.Response) { + this.loadMoreCursor = res.data.cursor + this.hasMore = !!this.loadMoreCursor + this.followers = this.followers.concat(res.data.followers) + this.rootStore.me.follows.hydrateProfiles(res.data.followers) + } +} diff --git a/src/state/models/lists/user-follows.ts b/src/state/models/lists/user-follows.ts new file mode 100644 index 000000000..56432a796 --- /dev/null +++ b/src/state/models/lists/user-follows.ts @@ -0,0 +1,120 @@ +import {makeAutoObservable} from 'mobx' +import { + AppBskyGraphGetFollows as GetFollows, + AppBskyActorDefs as ActorDefs, +} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {cleanError} from 'lib/strings/errors' +import {bundleAsync} from 'lib/async/bundle' + +const PAGE_SIZE = 30 + +export type FollowItem = ActorDefs.ProfileViewBasic + +export class UserFollowsModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + params: GetFollows.QueryParams + hasMore = true + loadMoreCursor?: string + + // data + subject: ActorDefs.ProfileViewBasic = { + did: '', + handle: '', + } + follows: FollowItem[] = [] + + constructor( + public rootStore: RootStoreModel, + params: GetFollows.QueryParams, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.params = params + } + + get hasContent() { + return this.subject.did !== '' + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async refresh() { + return this.loadMore(true) + } + + loadMore = bundleAsync(async (replace: boolean = false) => { + if (!replace && !this.hasMore) { + return + } + this._xLoading(replace) + try { + const params = Object.assign({}, this.params, { + limit: PAGE_SIZE, + cursor: replace ? undefined : this.loadMoreCursor, + }) + const res = await this.rootStore.agent.getFollows(params) + if (replace) { + this._replaceAll(res) + } else { + this._appendAll(res) + } + this._xIdle() + } catch (e: any) { + this._xIdle(e) + } + }) + + // state transitions + // = + + _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing + this.error = '' + } + + _xIdle(err?: any) { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = cleanError(err) + if (err) { + this.rootStore.log.error('Failed to fetch user follows', err) + } + } + + // helper functions + // = + + _replaceAll(res: GetFollows.Response) { + this.follows = [] + this._appendAll(res) + } + + _appendAll(res: GetFollows.Response) { + this.loadMoreCursor = res.data.cursor + this.hasMore = !!this.loadMoreCursor + this.follows = this.follows.concat(res.data.follows) + this.rootStore.me.follows.hydrateProfiles(res.data.follows) + } +} |