about summary refs log tree commit diff
path: root/src/state/cache
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-11-09 15:35:25 -0800
committerGitHub <noreply@github.com>2023-11-09 15:35:25 -0800
commitfb4f5709c43c070653c917e3196b9b1c120418a6 (patch)
tree74e6ff954441b6da3044853e16ebf5dd12213c87 /src/state/cache
parent625cbc435f15bc0d611661b44dbf8add990dff7d (diff)
downloadvoidsky-fb4f5709c43c070653c917e3196b9b1c120418a6.tar.zst
Refactor post threads to use react query (#1851)
* Add post and post-thread queries

* Update PostThread components to use new queries

* Move from normalized cache to shadow cache model

* Merge post shadow into the post automatically

* Remove dead code

* Remove old temporary session

* Fix: set agent on session creation

* Temporarily double-login

* Handle post-thread uri resolution errors
Diffstat (limited to 'src/state/cache')
-rw-r--r--src/state/cache/post-shadow.ts90
1 files changed, 90 insertions, 0 deletions
diff --git a/src/state/cache/post-shadow.ts b/src/state/cache/post-shadow.ts
new file mode 100644
index 000000000..c06ed60c4
--- /dev/null
+++ b/src/state/cache/post-shadow.ts
@@ -0,0 +1,90 @@
+import {useEffect, useState, useCallback, useRef} from 'react'
+import EventEmitter from 'eventemitter3'
+import {AppBskyFeedDefs} from '@atproto/api'
+
+const emitter = new EventEmitter()
+
+export interface PostShadow {
+  likeUri: string | undefined
+  likeCount: number | undefined
+  repostUri: string | undefined
+  repostCount: number | undefined
+  isDeleted: boolean
+}
+
+export const POST_TOMBSTONE = Symbol('PostTombstone')
+
+interface CacheEntry {
+  ts: number
+  value: PostShadow
+}
+
+export function usePostShadow(
+  post: AppBskyFeedDefs.PostView,
+  ifAfterTS: number,
+): AppBskyFeedDefs.PostView | typeof POST_TOMBSTONE {
+  const [state, setState] = useState<CacheEntry>({
+    ts: Date.now(),
+    value: fromPost(post),
+  })
+  const firstRun = useRef(true)
+
+  const onUpdate = useCallback(
+    (value: Partial<PostShadow>) => {
+      setState(s => ({ts: Date.now(), value: {...s.value, ...value}}))
+    },
+    [setState],
+  )
+
+  // react to shadow updates
+  useEffect(() => {
+    emitter.addListener(post.uri, onUpdate)
+    return () => {
+      emitter.removeListener(post.uri, onUpdate)
+    }
+  }, [post.uri, onUpdate])
+
+  // react to post updates
+  useEffect(() => {
+    // dont fire on first run to avoid needless re-renders
+    if (!firstRun.current) {
+      setState({ts: Date.now(), value: fromPost(post)})
+    }
+    firstRun.current = false
+  }, [post])
+
+  return state.ts > ifAfterTS ? mergeShadow(post, state.value) : post
+}
+
+export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
+  emitter.emit(uri, value)
+}
+
+function fromPost(post: AppBskyFeedDefs.PostView): PostShadow {
+  return {
+    likeUri: post.viewer?.like,
+    likeCount: post.likeCount,
+    repostUri: post.viewer?.repost,
+    repostCount: post.repostCount,
+    isDeleted: false,
+  }
+}
+
+function mergeShadow(
+  post: AppBskyFeedDefs.PostView,
+  shadow: PostShadow,
+): AppBskyFeedDefs.PostView | typeof POST_TOMBSTONE {
+  if (shadow.isDeleted) {
+    return POST_TOMBSTONE
+  }
+  return {
+    ...post,
+    likeCount: shadow.likeCount,
+    repostCount: shadow.repostCount,
+    viewer: {
+      ...(post.viewer || {}),
+      like: shadow.likeUri,
+      repost: shadow.repostUri,
+    },
+  }
+}