mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {useEffect, useMemo, useState} from 'react'
2import {QueryClient} from '@tanstack/react-query'
3import EventEmitter from 'eventemitter3'
4
5import {batchedUpdates} from '#/lib/batchedUpdates'
6import * as bsky from '#/types/bsky'
7import {findAllProfilesInQueryData as findAllProfilesInActorSearchQueryData} from '../queries/actor-search'
8import {findAllProfilesInQueryData as findAllProfilesInKnownFollowersQueryData} from '../queries/known-followers'
9import {findAllProfilesInQueryData as findAllProfilesInListMembersQueryData} from '../queries/list-members'
10import {findAllProfilesInQueryData as findAllProfilesInListConvosQueryData} from '../queries/messages/list-conversations'
11import {findAllProfilesInQueryData as findAllProfilesInMyBlockedAccountsQueryData} from '../queries/my-blocked-accounts'
12import {findAllProfilesInQueryData as findAllProfilesInMyMutedAccountsQueryData} from '../queries/my-muted-accounts'
13import {findAllProfilesInQueryData as findAllProfilesInFeedsQueryData} from '../queries/post-feed'
14import {findAllProfilesInQueryData as findAllProfilesInPostLikedByQueryData} from '../queries/post-liked-by'
15import {findAllProfilesInQueryData as findAllProfilesInPostQuotesQueryData} from '../queries/post-quotes'
16import {findAllProfilesInQueryData as findAllProfilesInPostRepostedByQueryData} from '../queries/post-reposted-by'
17import {findAllProfilesInQueryData as findAllProfilesInPostThreadQueryData} from '../queries/post-thread'
18import {findAllProfilesInQueryData as findAllProfilesInProfileQueryData} from '../queries/profile'
19import {findAllProfilesInQueryData as findAllProfilesInProfileFollowersQueryData} from '../queries/profile-followers'
20import {findAllProfilesInQueryData as findAllProfilesInProfileFollowsQueryData} from '../queries/profile-follows'
21import {findAllProfilesInQueryData as findAllProfilesInSuggestedFollowsQueryData} from '../queries/suggested-follows'
22import {castAsShadow, Shadow} from './types'
23
24export type {Shadow} from './types'
25
26export interface ProfileShadow {
27 followingUri: string | undefined
28 muted: boolean | undefined
29 blockingUri: string | undefined
30}
31
32const shadows: WeakMap<
33 bsky.profile.AnyProfileView,
34 Partial<ProfileShadow>
35> = new WeakMap()
36const emitter = new EventEmitter()
37
38export function useProfileShadow<
39 TProfileView extends bsky.profile.AnyProfileView,
40>(profile: TProfileView): Shadow<TProfileView> {
41 const [shadow, setShadow] = useState(() => shadows.get(profile))
42 const [prevPost, setPrevPost] = useState(profile)
43 if (profile !== prevPost) {
44 setPrevPost(profile)
45 setShadow(shadows.get(profile))
46 }
47
48 useEffect(() => {
49 function onUpdate() {
50 setShadow(shadows.get(profile))
51 }
52 emitter.addListener(profile.did, onUpdate)
53 return () => {
54 emitter.removeListener(profile.did, onUpdate)
55 }
56 }, [profile])
57
58 return useMemo(() => {
59 if (shadow) {
60 return mergeShadow(profile, shadow)
61 } else {
62 return castAsShadow(profile)
63 }
64 }, [profile, shadow])
65}
66
67/**
68 * Same as useProfileShadow, but allows for the profile to be undefined.
69 * This is useful for when the profile is not guaranteed to be loaded yet.
70 */
71export function useMaybeProfileShadow<
72 TProfileView extends bsky.profile.AnyProfileView,
73>(profile?: TProfileView): Shadow<TProfileView> | undefined {
74 const [shadow, setShadow] = useState(() =>
75 profile ? shadows.get(profile) : undefined,
76 )
77 const [prevPost, setPrevPost] = useState(profile)
78 if (profile !== prevPost) {
79 setPrevPost(profile)
80 setShadow(profile ? shadows.get(profile) : undefined)
81 }
82
83 useEffect(() => {
84 if (!profile) return
85 function onUpdate() {
86 if (!profile) return
87 setShadow(shadows.get(profile))
88 }
89 emitter.addListener(profile.did, onUpdate)
90 return () => {
91 emitter.removeListener(profile.did, onUpdate)
92 }
93 }, [profile])
94
95 return useMemo(() => {
96 if (!profile) return undefined
97 if (shadow) {
98 return mergeShadow(profile, shadow)
99 } else {
100 return castAsShadow(profile)
101 }
102 }, [profile, shadow])
103}
104
105export function updateProfileShadow(
106 queryClient: QueryClient,
107 did: string,
108 value: Partial<ProfileShadow>,
109) {
110 const cachedProfiles = findProfilesInCache(queryClient, did)
111 for (let post of cachedProfiles) {
112 shadows.set(post, {...shadows.get(post), ...value})
113 }
114 batchedUpdates(() => {
115 emitter.emit(did, value)
116 })
117}
118
119function mergeShadow<TProfileView extends bsky.profile.AnyProfileView>(
120 profile: TProfileView,
121 shadow: Partial<ProfileShadow>,
122): Shadow<TProfileView> {
123 return castAsShadow({
124 ...profile,
125 viewer: {
126 ...(profile.viewer || {}),
127 following:
128 'followingUri' in shadow
129 ? shadow.followingUri
130 : profile.viewer?.following,
131 muted: 'muted' in shadow ? shadow.muted : profile.viewer?.muted,
132 blocking:
133 'blockingUri' in shadow ? shadow.blockingUri : profile.viewer?.blocking,
134 },
135 })
136}
137
138function* findProfilesInCache(
139 queryClient: QueryClient,
140 did: string,
141): Generator<bsky.profile.AnyProfileView, void> {
142 yield* findAllProfilesInListMembersQueryData(queryClient, did)
143 yield* findAllProfilesInMyBlockedAccountsQueryData(queryClient, did)
144 yield* findAllProfilesInMyMutedAccountsQueryData(queryClient, did)
145 yield* findAllProfilesInPostLikedByQueryData(queryClient, did)
146 yield* findAllProfilesInPostRepostedByQueryData(queryClient, did)
147 yield* findAllProfilesInPostQuotesQueryData(queryClient, did)
148 yield* findAllProfilesInProfileQueryData(queryClient, did)
149 yield* findAllProfilesInProfileFollowersQueryData(queryClient, did)
150 yield* findAllProfilesInProfileFollowsQueryData(queryClient, did)
151 yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did)
152 yield* findAllProfilesInActorSearchQueryData(queryClient, did)
153 yield* findAllProfilesInListConvosQueryData(queryClient, did)
154 yield* findAllProfilesInFeedsQueryData(queryClient, did)
155 yield* findAllProfilesInPostThreadQueryData(queryClient, did)
156 yield* findAllProfilesInKnownFollowersQueryData(queryClient, did)
157}