diff options
Diffstat (limited to 'src/state/models')
-rw-r--r-- | src/state/models/feed-view.ts | 28 | ||||
-rw-r--r-- | src/state/models/me.ts | 1 | ||||
-rw-r--r-- | src/state/models/my-follows.ts | 7 | ||||
-rw-r--r-- | src/state/models/onboard.ts | 65 | ||||
-rw-r--r-- | src/state/models/root-store.ts | 6 | ||||
-rw-r--r-- | src/state/models/session.ts | 1 | ||||
-rw-r--r-- | src/state/models/suggested-actors-view.ts | 22 | ||||
-rw-r--r-- | src/state/models/suggested-posts-view.ts | 92 |
8 files changed, 54 insertions, 168 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index f80c5f2c0..645b1f2eb 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -15,6 +15,12 @@ import {RootStoreModel} from './root-store' import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' import {RichText} from 'lib/strings/rich-text' +import {SUGGESTED_FOLLOWS} from 'lib/constants' +import { + getCombinedCursors, + getMultipleAuthorsPosts, + mergePosts, +} from 'lib/api/build-suggested-posts' const PAGE_SIZE = 30 @@ -535,11 +541,31 @@ export class FeedModel { } } - protected _getFeed( + protected async _getFeed( params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams = {}, ): Promise<GetTimeline.Response | GetAuthorFeed.Response> { params = Object.assign({}, this.params, params) if (this.feedType === 'home') { + await this.rootStore.me.follows.fetchIfNeeded() + if (this.rootStore.me.follows.isEmpty) { + const responses = await getMultipleAuthorsPosts( + this.rootStore, + SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), + params.before, + 20, + ) + const combinedCursor = getCombinedCursors(responses) + const finalData = mergePosts(responses, {bestOfOnly: true}) + const lastHeaders = responses[responses.length - 1].headers + return { + success: true, + data: { + feed: finalData, + cursor: combinedCursor, + }, + headers: lastHeaders, + } + } return this.rootStore.api.app.bsky.feed.getTimeline( params as GetTimeline.QueryParams, ) diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 0cb84c9fc..451d562a4 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -96,6 +96,7 @@ export class MeModel { this.avatar = '' } }) + this.mainFeed.clear() await Promise.all([ this.mainFeed.setup().catch(e => { this.rootStore.log.error('Failed to setup main feed model', e) diff --git a/src/state/models/my-follows.ts b/src/state/models/my-follows.ts index 252e8a3d3..c1fba1352 100644 --- a/src/state/models/my-follows.ts +++ b/src/state/models/my-follows.ts @@ -20,6 +20,7 @@ export class MyFollowsModel { // data followDidToRecordMap: Record<string, string> = {} lastSync = 0 + myDid?: string constructor(public rootStore: RootStoreModel) { makeAutoObservable( @@ -36,6 +37,7 @@ export class MyFollowsModel { fetchIfNeeded = bundleAsync(async () => { if ( + this.myDid !== this.rootStore.me.did || Object.keys(this.followDidToRecordMap).length === 0 || Date.now() - this.lastSync > CACHE_TTL ) { @@ -62,6 +64,7 @@ export class MyFollowsModel { this.followDidToRecordMap[record.value.subject.did] = record.uri } this.lastSync = Date.now() + this.myDid = this.rootStore.me.did }) }) @@ -69,6 +72,10 @@ export class MyFollowsModel { return !!this.followDidToRecordMap[did] } + get isEmpty() { + return Object.keys(this.followDidToRecordMap).length === 0 + } + getFollowUri(did: string): string { const v = this.followDidToRecordMap[did] if (!v) { diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts deleted file mode 100644 index aa275c6b7..000000000 --- a/src/state/models/onboard.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import {isObj, hasProp} from 'lib/type-guards' - -export const OnboardStage = { - Explainers: 'explainers', - Follows: 'follows', -} - -export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows] - -export class OnboardModel { - isOnboarding: boolean = false - stage: string = OnboardStageOrder[0] - - constructor() { - makeAutoObservable(this, { - serialize: false, - hydrate: false, - }) - } - - serialize(): unknown { - return { - isOnboarding: this.isOnboarding, - stage: this.stage, - } - } - - hydrate(v: unknown) { - if (isObj(v)) { - if (hasProp(v, 'isOnboarding') && typeof v.isOnboarding === 'boolean') { - this.isOnboarding = v.isOnboarding - } - if ( - hasProp(v, 'stage') && - typeof v.stage === 'string' && - OnboardStageOrder.includes(v.stage) - ) { - this.stage = v.stage - } - } - } - - start() { - this.isOnboarding = true - } - - stop() { - this.isOnboarding = false - } - - next() { - if (!this.isOnboarding) { - return - } - let i = OnboardStageOrder.indexOf(this.stage) - i++ - if (i >= OnboardStageOrder.length) { - this.isOnboarding = false - this.stage = OnboardStageOrder[0] // in case they make a new account - } else { - this.stage = OnboardStageOrder[i] - } - } -} diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 43523b759..4b62f501e 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -17,7 +17,6 @@ import {ProfilesViewModel} from './profiles-view' import {LinkMetasViewModel} from './link-metas-view' import {NotificationsViewItemModel} from './notifications-view' import {MeModel} from './me' -import {OnboardModel} from './onboard' export const appInfo = z.object({ build: z.string(), @@ -35,7 +34,6 @@ export class RootStoreModel { nav = new NavigationModel(this) shell = new ShellUiModel(this) me = new MeModel(this) - onboard = new OnboardModel() profiles = new ProfilesViewModel(this) linkMetas = new LinkMetasViewModel(this) @@ -85,7 +83,6 @@ export class RootStoreModel { session: this.session.serialize(), me: this.me.serialize(), nav: this.nav.serialize(), - onboard: this.onboard.serialize(), shell: this.shell.serialize(), } } @@ -107,9 +104,6 @@ export class RootStoreModel { if (hasProp(v, 'nav')) { this.nav.hydrate(v.nav) } - if (hasProp(v, 'onboard')) { - this.onboard.hydrate(v.onboard) - } if (hasProp(v, 'session')) { this.session.hydrate(v.session) } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index 6e816120d..75a60f353 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -345,7 +345,6 @@ export class SessionModel { ) this.setActiveSession(agent, did) - this.rootStore.onboard.start() this.rootStore.log.debug('SessionModel:createAccount succeeded') } diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts index 4764f581e..33c73b4e1 100644 --- a/src/state/models/suggested-actors-view.ts +++ b/src/state/models/suggested-actors-view.ts @@ -4,26 +4,12 @@ import shuffle from 'lodash.shuffle' import {RootStoreModel} from './root-store' import {cleanError} from 'lib/strings/errors' import {bundleAsync} from 'lib/async/bundle' -import { - DEV_SUGGESTED_FOLLOWS, - PROD_SUGGESTED_FOLLOWS, - STAGING_SUGGESTED_FOLLOWS, -} from 'lib/constants' +import {SUGGESTED_FOLLOWS} from 'lib/constants' const PAGE_SIZE = 30 export type SuggestedActor = Profile.ViewBasic | Profile.View -const getSuggestionList = ({serviceUrl}: {serviceUrl: string}) => { - if (serviceUrl.includes('localhost')) { - return DEV_SUGGESTED_FOLLOWS - } else if (serviceUrl.includes('staging')) { - return STAGING_SUGGESTED_FOLLOWS - } else { - return PROD_SUGGESTED_FOLLOWS - } -} - export class SuggestedActorsViewModel { // state pageSize = PAGE_SIZE @@ -126,9 +112,9 @@ export class SuggestedActorsViewModel { try { // clone the array so we can mutate it const actors = [ - ...getSuggestionList({ - serviceUrl: this.rootStore.session.currentSession?.service || '', - }), + ...SUGGESTED_FOLLOWS( + this.rootStore.session.currentSession?.service || '', + ), ] // fetch the profiles in chunks of 25 (the limit allowed by `getProfiles`) diff --git a/src/state/models/suggested-posts-view.ts b/src/state/models/suggested-posts-view.ts index 7b44370de..c6710c44e 100644 --- a/src/state/models/suggested-posts-view.ts +++ b/src/state/models/suggested-posts-view.ts @@ -1,21 +1,12 @@ import {makeAutoObservable, runInAction} from 'mobx' -import { - AppBskyFeedFeedViewPost, - AppBskyFeedGetAuthorFeed as GetAuthorFeed, -} from '@atproto/api' -type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost import {RootStoreModel} from './root-store' import {FeedItemModel} from './feed-view' import {cleanError} from 'lib/strings/errors' - -const TEAM_HANDLES = [ - 'jay.bsky.social', - 'paul.bsky.social', - 'dan.bsky.social', - 'divy.bsky.social', - 'why.bsky.social', - 'iamrosewang.bsky.social', -] +import {TEAM_HANDLES} from 'lib/constants' +import { + getMultipleAuthorsPosts, + mergePosts, +} from 'lib/api/build-suggested-posts' export class SuggestedPostsView { // state @@ -54,15 +45,18 @@ export class SuggestedPostsView { async setup() { this._xLoading() try { - const responses = await Promise.all( - TEAM_HANDLES.map(handle => - this.rootStore.api.app.bsky.feed - .getAuthorFeed({author: handle, limit: 10}) - .catch(_err => ({success: false, headers: {}, data: {feed: []}})), - ), + const responses = await getMultipleAuthorsPosts( + this.rootStore, + TEAM_HANDLES(String(this.rootStore.agent.service)), ) runInAction(() => { - this.posts = mergeAndFilterResponses(this.rootStore, responses) + 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 FeedItemModel(this.rootStore, `post-${i}`, post) + }) }) this._xIdle() } catch (e: any) { @@ -90,59 +84,3 @@ export class SuggestedPostsView { } } } - -function mergeAndFilterResponses( - store: RootStoreModel, - responses: GetAuthorFeed.Response[], -): FeedItemModel[] { - let posts: AppBskyFeedFeedViewPost.Main[] = [] - - // merge into one array - for (const res of responses) { - if (res.success) { - posts = posts.concat(res.data.feed) - } - } - - // filter down to reposts of other users - const now = Date.now() - const uris = new Set() - posts = posts.filter(p => { - if (isARepostOfSomeoneElse(p) && isRecentEnough(now, p)) { - if (uris.has(p.post.uri)) { - return false - } - uris.add(p.post.uri) - return true - } - return false - }) - - // sort by index time - posts.sort((a, b) => { - return ( - Number(new Date(b.post.indexedAt)) - Number(new Date(a.post.indexedAt)) - ) - }) - - // hydrate into models and strip the reasons to hide that these are reposts - return posts.map((post, i) => { - delete post.reason - return new FeedItemModel(store, `post-${i}`, post) - }) -} - -function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean { - return ( - post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' && - post.post.author.did !== (post.reason as ReasonRepost).by.did - ) -} - -const THREE_DAYS = 3 * 24 * 60 * 60 * 1000 -function isRecentEnough( - now: number, - post: AppBskyFeedFeedViewPost.Main, -): boolean { - return now - Number(new Date(post.post.indexedAt)) < THREE_DAYS -} |