diff options
Diffstat (limited to 'src/stores/stateBin.ts')
| -rw-r--r-- | src/stores/stateBin.ts | 135 |
1 files changed, 93 insertions, 42 deletions
diff --git a/src/stores/stateBin.ts b/src/stores/stateBin.ts index f724860a..3fd38a99 100644 --- a/src/stores/stateBin.ts +++ b/src/stores/stateBin.ts @@ -1,61 +1,112 @@ -import { browser } from '$app/environment'; -import { writable, get, type Writable } from 'svelte/store'; -import localforage from 'localforage'; +import { browser } from "$app/environment"; +import { writable, get, type Writable } from "svelte/store"; +import localforage from "localforage"; interface StateBin { - dueAnimeListOpen?: boolean; - upcomingAnimeListOpen?: boolean; - dueMangaListOpen?: boolean; - completedAnimeListOpen?: boolean; - completedMangaListOpen?: boolean; - [key: string]: boolean | string | undefined; + dueAnimeListOpen?: boolean; + upcomingAnimeListOpen?: boolean; + dueMangaListOpen?: boolean; + completedAnimeListOpen?: boolean; + completedMangaListOpen?: boolean; + [key: string]: boolean | string | undefined; } -const STORAGE_KEY = 'stateBin'; +const STORAGE_KEY = "stateBin"; const baseStore = writable<StateBin>({}); +let hydrated = !browser; +let state: StateBin = {}; +let changedBeforeHydration = false; +let initialEmission = true; +let applyingStoredValue = false; +let hydrationPromise = Promise.resolve(); if (browser) { - localforage.getItem<StateBin>(STORAGE_KEY).then((value) => { - if (value && typeof value === 'object') baseStore.set(value); - }); + hydrationPromise = localforage + .getItem<StateBin>(STORAGE_KEY) + .then(async (value): Promise<void> => { + if (value && typeof value === "object") { + const nextState = changedBeforeHydration + ? { ...value, ...state } + : value; - baseStore.subscribe((value) => { - localforage.setItem(STORAGE_KEY, value); - }); -} - -const createProxyStore = (store: Writable<StateBin>) => { - return new Proxy(store, { - get(target, prop: string) { - if (prop in target) return (target as unknown as Record<string, unknown>)[prop]; + applyingStoredValue = true; + baseStore.set(nextState); + applyingStoredValue = false; + } - const derivedKey = writable(get(store)[prop]); + hydrated = true; - derivedKey.subscribe((value) => { - const state = get(store); - const updatedState = { ...state }; + await localforage.setItem(STORAGE_KEY, state); + }); - if (value === null || value === undefined) delete updatedState[prop]; - else updatedState[prop] = value; + baseStore.subscribe((value) => { + state = value; - store.set(updatedState); - }); + if (browser && !hydrated && !initialEmission && !applyingStoredValue) + changedBeforeHydration = true; - return derivedKey; - }, + if (hydrated) localforage.setItem(STORAGE_KEY, value); - set(_, prop: string, value) { - const state = get(store); - const updatedState = { ...state }; - - if (value === null || value === undefined) delete updatedState[prop]; - else updatedState[prop] = value; + initialEmission = false; + }); +} - store.set(updatedState); +export const hydrateStateBin = () => hydrationPromise; - return true; - } - }); +const createProxyStore = (store: Writable<StateBin>) => { + const keyStores = new Map< + string, + { + subscribe: (run: (value: StateBin[string]) => void) => () => void; + set: (value: StateBin[string]) => void; + update: (updater: (value: StateBin[string]) => StateBin[string]) => void; + } + >(); + + const setKeyValue = (prop: string, value: StateBin[string]) => { + const state = get(store); + const updatedState = { ...state }; + + if (value === null || value === undefined) delete updatedState[prop]; + else updatedState[prop] = value; + + store.set(updatedState); + }; + + return new Proxy(store, { + get(target, prop: string | symbol) { + if (prop in target) return Reflect.get(target, prop); + + if (typeof prop !== "string") return undefined; + + const existingStore = keyStores.get(prop); + + if (existingStore) return existingStore; + + const keyStore = { + subscribe(run: (value: StateBin[string]) => void) { + return store.subscribe((value) => run(value[prop])); + }, + set(value: StateBin[string]) { + setKeyValue(prop, value); + }, + update(updater: (value: StateBin[string]) => StateBin[string]) { + setKeyValue(prop, updater(get(store)[prop])); + }, + }; + + keyStores.set(prop, keyStore); + + return keyStore; + }, + + set(_, prop: string | symbol, value) { + if (typeof prop !== "string") return false; + + setKeyValue(prop, value); + return true; + }, + }); }; const stateBin = createProxyStore(baseStore); |