-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "follows you" information and sync follow state between views (#215)
* Bump @atproto/api@0.1.2 and update API usage * Add 'follows you' pill to profile header (close #110) * Add 'follows you' to followers and follows (close #103) * Update reposted-by and liked-by views to use the same components as followers and following * Create a local follows cache MyFollowsModel to keep views in sync (close #205) * Add incremental hydration to the MyFollows model * Fix tests * Update deps * Fix lint * Fix to paginated fetches * Fix reference * Fix potential state-desync issue
- Loading branch information
Showing
19 changed files
with
370 additions
and
515 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import {makeAutoObservable, runInAction} from 'mobx' | ||
import {FollowRecord, AppBskyActorProfile, AppBskyActorRef} from '@atproto/api' | ||
import {RootStoreModel} from './root-store' | ||
import {bundleAsync} from '../../lib/async/bundle' | ||
|
||
const CACHE_TTL = 1000 * 60 * 60 // hourly | ||
type FollowsListResponse = Awaited<ReturnType<FollowRecord['list']>> | ||
type FollowsListResponseRecord = FollowsListResponse['records'][0] | ||
type Profile = | ||
| AppBskyActorProfile.ViewBasic | ||
| AppBskyActorProfile.View | ||
| AppBskyActorRef.WithInfo | ||
|
||
/** | ||
* This model is used to maintain a synced local cache of the user's | ||
* follows. It should be periodically refreshed and updated any time | ||
* the user makes a change to their follows. | ||
*/ | ||
export class MyFollowsModel { | ||
// data | ||
followDidToRecordMap: Record<string, string> = {} | ||
lastSync = 0 | ||
|
||
constructor(public rootStore: RootStoreModel) { | ||
makeAutoObservable( | ||
this, | ||
{ | ||
rootStore: false, | ||
}, | ||
{autoBind: true}, | ||
) | ||
} | ||
|
||
// public api | ||
// = | ||
|
||
fetchIfNeeded = bundleAsync(async () => { | ||
if ( | ||
Object.keys(this.followDidToRecordMap).length === 0 || | ||
Date.now() - this.lastSync > CACHE_TTL | ||
) { | ||
return await this.fetch() | ||
} | ||
}) | ||
|
||
fetch = bundleAsync(async () => { | ||
this.rootStore.log.debug('MyFollowsModel:fetch running full fetch') | ||
let before | ||
let records: FollowsListResponseRecord[] = [] | ||
do { | ||
const res: FollowsListResponse = | ||
await this.rootStore.api.app.bsky.graph.follow.list({ | ||
user: this.rootStore.me.did, | ||
before, | ||
}) | ||
records = records.concat(res.records) | ||
before = res.cursor | ||
} while (typeof before !== 'undefined') | ||
runInAction(() => { | ||
this.followDidToRecordMap = {} | ||
for (const record of records) { | ||
this.followDidToRecordMap[record.value.subject.did] = record.uri | ||
} | ||
this.lastSync = Date.now() | ||
}) | ||
}) | ||
|
||
isFollowing(did: string) { | ||
return !!this.followDidToRecordMap[did] | ||
} | ||
|
||
getFollowUri(did: string): string { | ||
const v = this.followDidToRecordMap[did] | ||
if (!v) { | ||
throw new Error('Not a followed user') | ||
} | ||
return v | ||
} | ||
|
||
addFollow(did: string, recordUri: string) { | ||
this.followDidToRecordMap[did] = recordUri | ||
} | ||
|
||
removeFollow(did: string) { | ||
delete this.followDidToRecordMap[did] | ||
} | ||
|
||
/** | ||
* Use this to incrementally update the cache as views provide information | ||
*/ | ||
hydrate(did: string, recordUri: string | undefined) { | ||
if (recordUri) { | ||
this.followDidToRecordMap[did] = recordUri | ||
} else { | ||
delete this.followDidToRecordMap[did] | ||
} | ||
} | ||
|
||
/** | ||
* Use this to incrementally update the cache as views provide information | ||
*/ | ||
hydrateProfiles(profiles: Profile[]) { | ||
for (const profile of profiles) { | ||
if (profile.viewer) { | ||
this.hydrate(profile.did, profile.viewer.following) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.