layout-scroll.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { createStore, produce } from "solid-js/store"
  2. export type SessionScroll = {
  3. x: number
  4. y: number
  5. }
  6. type ScrollMap = Record<string, SessionScroll>
  7. type Options = {
  8. debounceMs?: number
  9. getSnapshot: (sessionKey: string) => ScrollMap | undefined
  10. onFlush: (sessionKey: string, scroll: ScrollMap) => void
  11. }
  12. export function createScrollPersistence(opts: Options) {
  13. const wait = opts.debounceMs ?? 200
  14. const [cache, setCache] = createStore<Record<string, ScrollMap>>({})
  15. const dirty = new Set<string>()
  16. const timers = new Map<string, ReturnType<typeof setTimeout>>()
  17. function clone(input?: ScrollMap) {
  18. const out: ScrollMap = {}
  19. if (!input) return out
  20. for (const key of Object.keys(input)) {
  21. const pos = input[key]
  22. if (!pos) continue
  23. out[key] = { x: pos.x, y: pos.y }
  24. }
  25. return out
  26. }
  27. function seed(sessionKey: string) {
  28. if (cache[sessionKey]) return
  29. setCache(sessionKey, clone(opts.getSnapshot(sessionKey)))
  30. }
  31. function scroll(sessionKey: string, tab: string) {
  32. seed(sessionKey)
  33. return cache[sessionKey]?.[tab] ?? opts.getSnapshot(sessionKey)?.[tab]
  34. }
  35. function schedule(sessionKey: string) {
  36. const prev = timers.get(sessionKey)
  37. if (prev) clearTimeout(prev)
  38. timers.set(
  39. sessionKey,
  40. setTimeout(() => flush(sessionKey), wait),
  41. )
  42. }
  43. function setScroll(sessionKey: string, tab: string, pos: SessionScroll) {
  44. seed(sessionKey)
  45. const prev = cache[sessionKey]?.[tab]
  46. if (prev?.x === pos.x && prev?.y === pos.y) return
  47. setCache(sessionKey, tab, { x: pos.x, y: pos.y })
  48. dirty.add(sessionKey)
  49. schedule(sessionKey)
  50. }
  51. function flush(sessionKey: string) {
  52. const timer = timers.get(sessionKey)
  53. if (timer) clearTimeout(timer)
  54. timers.delete(sessionKey)
  55. if (!dirty.has(sessionKey)) return
  56. dirty.delete(sessionKey)
  57. opts.onFlush(sessionKey, clone(cache[sessionKey]))
  58. }
  59. function flushAll() {
  60. const keys = Array.from(dirty)
  61. if (keys.length === 0) return
  62. for (const key of keys) {
  63. flush(key)
  64. }
  65. }
  66. function drop(keys: string[]) {
  67. if (keys.length === 0) return
  68. for (const key of keys) {
  69. const timer = timers.get(key)
  70. if (timer) clearTimeout(timer)
  71. timers.delete(key)
  72. dirty.delete(key)
  73. }
  74. setCache(
  75. produce((draft) => {
  76. for (const key of keys) {
  77. delete draft[key]
  78. }
  79. }),
  80. )
  81. }
  82. function dispose() {
  83. drop(Array.from(timers.keys()))
  84. }
  85. return {
  86. cache,
  87. drop,
  88. flush,
  89. flushAll,
  90. scroll,
  91. seed,
  92. setScroll,
  93. dispose,
  94. }
  95. }