|
| 1 | +import { useRefValue } from "@follow/hooks" |
| 2 | +import { createAtomHooks } from "@follow/utils" |
| 3 | +import type { SetStateAction, WritableAtom } from "jotai" |
| 4 | +import { atom as jotaiAtom, useAtomValue } from "jotai" |
| 5 | +import { atomWithStorage, selectAtom } from "jotai/utils" |
| 6 | +import { useMemo } from "react" |
| 7 | +import { shallow } from "zustand/shallow" |
| 8 | + |
| 9 | +import { JotaiPersistSyncStorage } from "@/src/lib/jotai" |
| 10 | + |
| 11 | +const getStorageNS = (settingKey: string) => `follow-rn-${settingKey}` |
| 12 | +type Nullable<T> = T | null | undefined |
| 13 | + |
| 14 | +export const createSettingAtom = <T extends object>( |
| 15 | + settingKey: string, |
| 16 | + createDefaultSettings: () => T, |
| 17 | +) => { |
| 18 | + const atom = atomWithStorage( |
| 19 | + getStorageNS(settingKey), |
| 20 | + createDefaultSettings(), |
| 21 | + JotaiPersistSyncStorage, |
| 22 | + { |
| 23 | + getOnInit: true, |
| 24 | + }, |
| 25 | + ) as WritableAtom<T, [SetStateAction<T>], void> |
| 26 | + |
| 27 | + const [, , useSettingValue, , getSettings, setSettings] = createAtomHooks(atom) |
| 28 | + |
| 29 | + const initializeDefaultSettings = () => { |
| 30 | + const currentSettings = getSettings() |
| 31 | + const defaultSettings = createDefaultSettings() |
| 32 | + if (typeof currentSettings !== "object") setSettings(defaultSettings) |
| 33 | + const newSettings = { ...defaultSettings, ...currentSettings } |
| 34 | + setSettings(newSettings) |
| 35 | + } |
| 36 | + |
| 37 | + const selectAtomCacheMap = {} as Record<keyof ReturnType<typeof getSettings>, any> |
| 38 | + |
| 39 | + const noopAtom = jotaiAtom(null) |
| 40 | + |
| 41 | + const useMaybeSettingKey = <T extends keyof ReturnType<typeof getSettings>>(key: Nullable<T>) => { |
| 42 | + // @ts-expect-error |
| 43 | + let selectedAtom: Record<keyof T, any>[T] | null = null |
| 44 | + if (key) { |
| 45 | + selectedAtom = selectAtomCacheMap[key] |
| 46 | + if (!selectedAtom) { |
| 47 | + selectedAtom = selectAtom(atom, (s) => s[key]) |
| 48 | + selectAtomCacheMap[key] = selectedAtom |
| 49 | + } |
| 50 | + } else { |
| 51 | + selectedAtom = noopAtom |
| 52 | + } |
| 53 | + |
| 54 | + return useAtomValue(selectedAtom) as ReturnType<typeof getSettings>[T] |
| 55 | + } |
| 56 | + |
| 57 | + const useSettingKey = <T extends keyof ReturnType<typeof getSettings>>(key: T) => { |
| 58 | + return useMaybeSettingKey(key) as ReturnType<typeof getSettings>[T] |
| 59 | + } |
| 60 | + |
| 61 | + function useSettingKeys< |
| 62 | + T extends keyof ReturnType<typeof getSettings>, |
| 63 | + K1 extends T, |
| 64 | + K2 extends T, |
| 65 | + K3 extends T, |
| 66 | + K4 extends T, |
| 67 | + K5 extends T, |
| 68 | + K6 extends T, |
| 69 | + K7 extends T, |
| 70 | + K8 extends T, |
| 71 | + K9 extends T, |
| 72 | + K10 extends T, |
| 73 | + >(keys: [K1, K2?, K3?, K4?, K5?, K6?, K7?, K8?, K9?, K10?]) { |
| 74 | + return [ |
| 75 | + useMaybeSettingKey(keys[0]), |
| 76 | + useMaybeSettingKey(keys[1]), |
| 77 | + useMaybeSettingKey(keys[2]), |
| 78 | + useMaybeSettingKey(keys[3]), |
| 79 | + useMaybeSettingKey(keys[4]), |
| 80 | + useMaybeSettingKey(keys[5]), |
| 81 | + useMaybeSettingKey(keys[6]), |
| 82 | + useMaybeSettingKey(keys[7]), |
| 83 | + useMaybeSettingKey(keys[8]), |
| 84 | + useMaybeSettingKey(keys[9]), |
| 85 | + ] as [ |
| 86 | + ReturnType<typeof getSettings>[K1], |
| 87 | + ReturnType<typeof getSettings>[K2], |
| 88 | + ReturnType<typeof getSettings>[K3], |
| 89 | + ReturnType<typeof getSettings>[K4], |
| 90 | + ReturnType<typeof getSettings>[K5], |
| 91 | + ReturnType<typeof getSettings>[K6], |
| 92 | + ReturnType<typeof getSettings>[K7], |
| 93 | + ReturnType<typeof getSettings>[K8], |
| 94 | + ReturnType<typeof getSettings>[K9], |
| 95 | + ReturnType<typeof getSettings>[K10], |
| 96 | + ] |
| 97 | + } |
| 98 | + |
| 99 | + const useSettingSelector = < |
| 100 | + T extends keyof ReturnType<typeof getSettings>, |
| 101 | + S extends ReturnType<typeof getSettings>, |
| 102 | + R = S[T], |
| 103 | + >( |
| 104 | + selector: (s: S) => R, |
| 105 | + ): R => { |
| 106 | + const stableSelector = useRefValue(selector) |
| 107 | + |
| 108 | + return useAtomValue( |
| 109 | + // @ts-expect-error |
| 110 | + useMemo(() => selectAtom(atom, stableSelector.current, shallow), [stableSelector]), |
| 111 | + ) |
| 112 | + } |
| 113 | + |
| 114 | + const setSetting = <K extends keyof ReturnType<typeof getSettings>>( |
| 115 | + key: K, |
| 116 | + value: ReturnType<typeof getSettings>[K], |
| 117 | + ) => { |
| 118 | + const updated = Date.now() |
| 119 | + setSettings({ |
| 120 | + ...getSettings(), |
| 121 | + [key]: value, |
| 122 | + |
| 123 | + updated, |
| 124 | + }) |
| 125 | + } |
| 126 | + |
| 127 | + const clearSettings = () => { |
| 128 | + setSettings(createDefaultSettings()) |
| 129 | + } |
| 130 | + |
| 131 | + Object.defineProperty(useSettingValue, "select", { |
| 132 | + value: useSettingSelector, |
| 133 | + }) |
| 134 | + |
| 135 | + return { |
| 136 | + useSettingKey, |
| 137 | + useSettingSelector, |
| 138 | + setSetting, |
| 139 | + clearSettings, |
| 140 | + initializeDefaultSettings, |
| 141 | + |
| 142 | + useSettingValue, |
| 143 | + useSettingKeys, |
| 144 | + getSettings, |
| 145 | + |
| 146 | + settingAtom: atom, |
| 147 | + } as { |
| 148 | + useSettingKey: typeof useSettingKey |
| 149 | + useSettingSelector: typeof useSettingSelector |
| 150 | + setSetting: typeof setSetting |
| 151 | + clearSettings: typeof clearSettings |
| 152 | + initializeDefaultSettings: typeof initializeDefaultSettings |
| 153 | + useSettingValue: typeof useSettingValue & { |
| 154 | + select: <T extends keyof ReturnType<() => T>>(key: T) => Awaited<T[T]> |
| 155 | + } |
| 156 | + useSettingKeys: typeof useSettingKeys |
| 157 | + getSettings: typeof getSettings |
| 158 | + settingAtom: typeof atom |
| 159 | + } |
| 160 | +} |
0 commit comments