summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context/layout-scroll.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/context/layout-scroll.ts')
-rw-r--r--packages/app/src/context/layout-scroll.ts118
1 files changed, 118 insertions, 0 deletions
diff --git a/packages/app/src/context/layout-scroll.ts b/packages/app/src/context/layout-scroll.ts
new file mode 100644
index 000000000..30b0f6904
--- /dev/null
+++ b/packages/app/src/context/layout-scroll.ts
@@ -0,0 +1,118 @@
+import { createStore, produce } from "solid-js/store"
+
+export type SessionScroll = {
+ x: number
+ y: number
+}
+
+type ScrollMap = Record<string, SessionScroll>
+
+type Options = {
+ debounceMs?: number
+ getSnapshot: (sessionKey: string) => ScrollMap | undefined
+ onFlush: (sessionKey: string, scroll: ScrollMap) => void
+}
+
+export function createScrollPersistence(opts: Options) {
+ const wait = opts.debounceMs ?? 200
+ const [cache, setCache] = createStore<Record<string, ScrollMap>>({})
+ const dirty = new Set<string>()
+ const timers = new Map<string, ReturnType<typeof setTimeout>>()
+
+ function clone(input?: ScrollMap) {
+ const out: ScrollMap = {}
+ if (!input) return out
+
+ for (const key of Object.keys(input)) {
+ const pos = input[key]
+ if (!pos) continue
+ out[key] = { x: pos.x, y: pos.y }
+ }
+
+ return out
+ }
+
+ function seed(sessionKey: string) {
+ if (cache[sessionKey]) return
+ setCache(sessionKey, clone(opts.getSnapshot(sessionKey)))
+ }
+
+ function scroll(sessionKey: string, tab: string) {
+ seed(sessionKey)
+ return cache[sessionKey]?.[tab] ?? opts.getSnapshot(sessionKey)?.[tab]
+ }
+
+ function schedule(sessionKey: string) {
+ const prev = timers.get(sessionKey)
+ if (prev) clearTimeout(prev)
+ timers.set(
+ sessionKey,
+ setTimeout(() => flush(sessionKey), wait),
+ )
+ }
+
+ function setScroll(sessionKey: string, tab: string, pos: SessionScroll) {
+ seed(sessionKey)
+
+ const prev = cache[sessionKey]?.[tab]
+ if (prev?.x === pos.x && prev?.y === pos.y) return
+
+ setCache(sessionKey, tab, { x: pos.x, y: pos.y })
+ dirty.add(sessionKey)
+ schedule(sessionKey)
+ }
+
+ function flush(sessionKey: string) {
+ const timer = timers.get(sessionKey)
+ if (timer) clearTimeout(timer)
+ timers.delete(sessionKey)
+
+ if (!dirty.has(sessionKey)) return
+ dirty.delete(sessionKey)
+
+ opts.onFlush(sessionKey, clone(cache[sessionKey]))
+ }
+
+ function flushAll() {
+ const keys = Array.from(dirty)
+ if (keys.length === 0) return
+
+ for (const key of keys) {
+ flush(key)
+ }
+ }
+
+ function drop(keys: string[]) {
+ if (keys.length === 0) return
+
+ for (const key of keys) {
+ const timer = timers.get(key)
+ if (timer) clearTimeout(timer)
+ timers.delete(key)
+ dirty.delete(key)
+ }
+
+ setCache(
+ produce((draft) => {
+ for (const key of keys) {
+ delete draft[key]
+ }
+ }),
+ )
+ }
+
+ function dispose() {
+ drop(Array.from(timers.keys()))
+ }
+
+ return {
+ cache,
+ drop,
+ flush,
+ flushAll,
+ scroll,
+ seed,
+ setScroll,
+ dispose,
+ }
+}