Improve typeahead search with inclusion of followed users (temporary solution) (#1612)
* Update follows cache to maintain some user info * Prioritize follows in composer autocomplete * Clean up logic and add new autocomplete to search * Update follow hook
This commit is contained in:
parent
19f8389fc7
commit
bd7db8af26
20 changed files with 197 additions and 142 deletions
103
src/state/models/cache/my-follows.ts
vendored
103
src/state/models/cache/my-follows.ts
vendored
|
|
@ -1,7 +1,14 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphGetFollows as GetFollows,
|
||||
moderateProfile,
|
||||
} from '@atproto/api'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
|
||||
const MAX_SYNC_PAGES = 10
|
||||
const SYNC_TTL = 60e3 * 10 // 10 minutes
|
||||
|
||||
type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView
|
||||
|
||||
export enum FollowState {
|
||||
|
|
@ -10,6 +17,14 @@ export enum FollowState {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
export interface FollowInfo {
|
||||
did: string
|
||||
followRecordUri: string | undefined
|
||||
handle: string
|
||||
displayName: string | undefined
|
||||
avatar: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* This model is used to maintain a synced local cache of the user's
|
||||
* follows. It should be periodically refreshed and updated any time
|
||||
|
|
@ -17,9 +32,8 @@ export enum FollowState {
|
|||
*/
|
||||
export class MyFollowsCache {
|
||||
// data
|
||||
followDidToRecordMap: Record<string, string | boolean> = {}
|
||||
byDid: Record<string, FollowInfo> = {}
|
||||
lastSync = 0
|
||||
myDid?: string
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(
|
||||
|
|
@ -35,16 +49,45 @@ export class MyFollowsCache {
|
|||
// =
|
||||
|
||||
clear() {
|
||||
this.followDidToRecordMap = {}
|
||||
this.lastSync = 0
|
||||
this.myDid = undefined
|
||||
this.byDid = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs a subset of the user's follows
|
||||
* for performance reasons, caps out at 1000 follows
|
||||
*/
|
||||
async syncIfNeeded() {
|
||||
if (this.lastSync > Date.now() - SYNC_TTL) {
|
||||
return
|
||||
}
|
||||
|
||||
let cursor
|
||||
for (let i = 0; i < MAX_SYNC_PAGES; i++) {
|
||||
const res: GetFollows.Response = await this.rootStore.agent.getFollows({
|
||||
actor: this.rootStore.me.did,
|
||||
cursor,
|
||||
limit: 100,
|
||||
})
|
||||
res.data.follows = res.data.follows.filter(
|
||||
profile =>
|
||||
!moderateProfile(profile, this.rootStore.preferences.moderationOpts)
|
||||
.account.filter,
|
||||
)
|
||||
this.hydrateMany(res.data.follows)
|
||||
if (!res.data.cursor) {
|
||||
break
|
||||
}
|
||||
cursor = res.data.cursor
|
||||
}
|
||||
|
||||
this.lastSync = Date.now()
|
||||
}
|
||||
|
||||
getFollowState(did: string): FollowState {
|
||||
if (typeof this.followDidToRecordMap[did] === 'undefined') {
|
||||
if (typeof this.byDid[did] === 'undefined') {
|
||||
return FollowState.Unknown
|
||||
}
|
||||
if (typeof this.followDidToRecordMap[did] === 'string') {
|
||||
if (typeof this.byDid[did].followRecordUri === 'string') {
|
||||
return FollowState.Following
|
||||
}
|
||||
return FollowState.NotFollowing
|
||||
|
|
@ -53,49 +96,41 @@ export class MyFollowsCache {
|
|||
async fetchFollowState(did: string): Promise<FollowState> {
|
||||
// TODO: can we get a more efficient method for this? getProfile fetches more data than we need -prf
|
||||
const res = await this.rootStore.agent.getProfile({actor: did})
|
||||
if (res.data.viewer?.following) {
|
||||
this.addFollow(did, res.data.viewer.following)
|
||||
} else {
|
||||
this.removeFollow(did)
|
||||
}
|
||||
this.hydrate(did, res.data)
|
||||
return this.getFollowState(did)
|
||||
}
|
||||
|
||||
getFollowUri(did: string): string {
|
||||
const v = this.followDidToRecordMap[did]
|
||||
const v = this.byDid[did]
|
||||
if (typeof v === 'string') {
|
||||
return v
|
||||
}
|
||||
throw new Error('Not a followed user')
|
||||
}
|
||||
|
||||
addFollow(did: string, recordUri: string) {
|
||||
this.followDidToRecordMap[did] = recordUri
|
||||
addFollow(did: string, info: FollowInfo) {
|
||||
this.byDid[did] = info
|
||||
}
|
||||
|
||||
removeFollow(did: string) {
|
||||
this.followDidToRecordMap[did] = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to incrementally update the cache as views provide information
|
||||
*/
|
||||
hydrate(did: string, recordUri: string | undefined) {
|
||||
if (recordUri) {
|
||||
this.followDidToRecordMap[did] = recordUri
|
||||
} else {
|
||||
this.followDidToRecordMap[did] = false
|
||||
if (this.byDid[did]) {
|
||||
this.byDid[did].followRecordUri = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to incrementally update the cache as views provide information
|
||||
*/
|
||||
hydrateProfiles(profiles: Profile[]) {
|
||||
hydrate(did: string, profile: Profile) {
|
||||
this.byDid[did] = {
|
||||
did,
|
||||
followRecordUri: profile.viewer?.following,
|
||||
handle: profile.handle,
|
||||
displayName: profile.displayName,
|
||||
avatar: profile.avatar,
|
||||
}
|
||||
}
|
||||
|
||||
hydrateMany(profiles: Profile[]) {
|
||||
for (const profile of profiles) {
|
||||
if (profile.viewer) {
|
||||
this.hydrate(profile.did, profile.viewer.following)
|
||||
}
|
||||
this.hydrate(profile.did, profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue