diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/badges-view.ts | 49 | ||||
-rw-r--r-- | src/state/models/feed-view.ts | 10 | ||||
-rw-r--r-- | src/state/models/profile-ui.ts | 98 | ||||
-rw-r--r-- | src/state/models/profile-view.ts | 8 |
4 files changed, 160 insertions, 5 deletions
diff --git a/src/state/models/badges-view.ts b/src/state/models/badges-view.ts new file mode 100644 index 000000000..644ec7d9e --- /dev/null +++ b/src/state/models/badges-view.ts @@ -0,0 +1,49 @@ +import {makeAutoObservable} from 'mobx' +import {RootStoreModel} from './root-store' + +// TODO / DEBUG +// this is a temporary fake for the model until the view actually gets implemented in the bsky api +// -prf + +export class BadgesViewModel { + // state + isLoading = false + isRefreshing = false + hasLoaded = false + error = '' + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + { + rootStore: false, + }, + {autoBind: true}, + ) + } + + get hasContent() { + return false + } + + get hasError() { + return this.error !== '' + } + + get isEmpty() { + return this.hasLoaded && !this.hasContent + } + + // public api + // = + + async setup() { + this.hasLoaded = true + } + + async refresh() {} + + async loadMore() {} + + async update() {} +} diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index e9405773c..9ba96764b 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -95,6 +95,7 @@ export class FeedViewModel implements bsky.FeedView.Response { isLoading = false isRefreshing = false hasLoaded = false + hasReachedEnd = false error = '' params: bsky.FeedView.Params _loadPromise: Promise<void> | undefined @@ -244,7 +245,13 @@ export class FeedViewModel implements bsky.FeedView.Response { 'blueskyweb.xyz:FeedView', params, )) as bsky.FeedView.Response - this._appendAll(res) + if (res.feed.length === 0) { + runInAction(() => { + this.hasReachedEnd = true + }) + } else { + this._appendAll(res) + } this._xIdle() } catch (e: any) { this._xIdle(`Failed to load feed: ${e.toString()}`) @@ -281,6 +288,7 @@ export class FeedViewModel implements bsky.FeedView.Response { private _replaceAll(res: bsky.FeedView.Response) { this.feed.length = 0 + this.hasReachedEnd = false this._appendAll(res) } diff --git a/src/state/models/profile-ui.ts b/src/state/models/profile-ui.ts new file mode 100644 index 000000000..98a087aeb --- /dev/null +++ b/src/state/models/profile-ui.ts @@ -0,0 +1,98 @@ +import {makeAutoObservable} from 'mobx' +import {RootStoreModel} from './root-store' +import {ProfileViewModel} from './profile-view' +import {FeedViewModel} from './feed-view' +import {BadgesViewModel} from './badges-view' + +export const SECTION_IDS = { + POSTS: 0, + BADGES: 1, +} + +export interface ProfileUiParams { + user: string +} + +export class ProfileUiModel { + // constants + static SELECTOR_ITEMS = ['Posts', 'Badges'] + + // data + profile: ProfileViewModel + feed: FeedViewModel + badges: BadgesViewModel + + // ui state + selectedViewIndex = 0 + + constructor( + public rootStore: RootStoreModel, + public params: ProfileUiParams, + ) { + makeAutoObservable( + this, + { + rootStore: false, + params: false, + }, + {autoBind: true}, + ) + this.profile = new ProfileViewModel(rootStore, {user: params.user}) + this.feed = new FeedViewModel(rootStore, {author: params.user, limit: 10}) + this.badges = new BadgesViewModel(rootStore) + } + + get currentView(): FeedViewModel | BadgesViewModel { + if (this.selectedViewIndex === SECTION_IDS.POSTS) { + return this.feed + } + if (this.selectedViewIndex === SECTION_IDS.BADGES) { + return this.badges + } + throw new Error(`Invalid selector value: ${this.selectedViewIndex}`) + } + + get isInitialLoading() { + const view = this.currentView + return view.isLoading && !view.isRefreshing && !view.hasContent + } + + get isRefreshing() { + return this.profile.isRefreshing || this.currentView.isRefreshing + } + + // public api + // = + + setSelectedViewIndex(index: number) { + this.selectedViewIndex = index + } + + async setup() { + await Promise.all([ + this.profile + .setup() + .catch(err => console.error('Failed to fetch profile', err)), + this.feed + .setup() + .catch(err => console.error('Failed to fetch feed', err)), + this.badges + .setup() + .catch(err => console.error('Failed to fetch badges', err)), + ]) + } + + async update() { + await this.currentView.update() + } + + async refresh() { + await Promise.all([this.profile.refresh(), this.currentView.refresh()]) + } + + async loadMore() { + if (!this.currentView.isLoading && !this.currentView.hasError) { + await this.currentView.loadMore() + } + } +} diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index b245335f1..89c8a75d0 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -65,7 +65,7 @@ export class ProfileViewModel implements bsky.ProfileView.Response { } async refresh() { - await this._load() + await this._load(true) } async toggleFollowing() { @@ -108,8 +108,8 @@ export class ProfileViewModel implements bsky.ProfileView.Response { // loader functions // = - private async _load() { - this._xLoading() + private async _load(isRefreshing = false) { + this._xLoading(isRefreshing) await new Promise(r => setTimeout(r, 250)) // DEBUG try { const res = (await this.rootStore.api.mainPds.view( @@ -119,7 +119,7 @@ export class ProfileViewModel implements bsky.ProfileView.Response { this._replaceAll(res) this._xIdle() } catch (e: any) { - this._xIdle(`Failed to load feed: ${e.toString()}`) + this._xIdle(e.toString()) } } |