layout-scroll.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. const next = clone(opts.getSnapshot(sessionKey))
  29. const current = cache[sessionKey]
  30. if (!current) {
  31. setCache(sessionKey, next)
  32. return
  33. }
  34. if (Object.keys(current).length > 0) return
  35. if (Object.keys(next).length === 0) return
  36. setCache(sessionKey, next)
  37. }
  38. function scroll(sessionKey: string, tab: string) {
  39. seed(sessionKey)
  40. return cache[sessionKey]?.[tab] ?? opts.getSnapshot(sessionKey)?.[tab]
  41. }
  42. function schedule(sessionKey: string) {
  43. const prev = timers.get(sessionKey)
  44. if (prev) clearTimeout(prev)
  45. timers.set(
  46. sessionKey,
  47. setTimeout(() => flush(sessionKey), wait),
  48. )
  49. }
  50. function setScroll(sessionKey: string, tab: string, pos: SessionScroll) {
  51. seed(sessionKey)
  52. const prev = cache[sessionKey]?.[tab]
  53. if (prev?.x === pos.x && prev?.y === pos.y) return
  54. setCache(sessionKey, tab, { x: pos.x, y: pos.y })
  55. dirty.add(sessionKey)
  56. schedule(sessionKey)
  57. }
  58. function flush(sessionKey: string) {
  59. const timer = timers.get(sessionKey)
  60. if (timer) clearTimeout(timer)
  61. timers.delete(sessionKey)
  62. if (!dirty.has(sessionKey)) return
  63. dirty.delete(sessionKey)
  64. opts.onFlush(sessionKey, clone(cache[sessionKey]))
  65. }
  66. function flushAll() {
  67. const keys = Array.from(dirty)
  68. if (keys.length === 0) return
  69. for (const key of keys) {
  70. flush(key)
  71. }
  72. }
  73. function drop(keys: string[]) {
  74. if (keys.length === 0) return
  75. for (const key of keys) {
  76. const timer = timers.get(key)
  77. if (timer) clearTimeout(timer)
  78. timers.delete(key)
  79. dirty.delete(key)
  80. }
  81. setCache(
  82. produce((draft) => {
  83. for (const key of keys) {
  84. delete draft[key]
  85. }
  86. }),
  87. )
  88. }
  89. function dispose() {
  90. drop(Array.from(timers.keys()))
  91. }
  92. return {
  93. cache,
  94. drop,
  95. flush,
  96. flushAll,
  97. scroll,
  98. seed,
  99. setScroll,
  100. dispose,
  101. }
  102. }