From c5aba7fce672d05694974e72d8ab20094b049a5b Mon Sep 17 00:00:00 2001 From: 2nthony Date: Fri, 17 Sep 2021 18:08:53 +0800 Subject: [PATCH] feat(useStorage): init (#15) --- indexes.json | 5 +- packages/core/indexes.json | 5 +- packages/core/useStorage/demo.svelte | 16 +++ packages/core/useStorage/index.md | 66 ++++++++++ packages/core/useStorage/index.ts | 181 ++++++++++++++++++++++++++- packages/functions.md | 3 + packages/shared/utils/index.ts | 2 +- packages/shared/utils/types.ts | 10 +- 8 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 packages/core/useStorage/demo.svelte create mode 100644 packages/core/useStorage/index.md diff --git a/indexes.json b/indexes.json index 1bf765a..730427e 100644 --- a/indexes.json +++ b/indexes.json @@ -24,6 +24,7 @@ "Browser", "Component", "Sensors", + "State", "Utilities" ], "functions": [ @@ -98,7 +99,9 @@ { "name": "useStorage", "package": "core", - "internal": true + "docs": "/core/useStorage/", + "category": "State", + "description": "reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)/[SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)" }, { "name": "useTitle", diff --git a/packages/core/indexes.json b/packages/core/indexes.json index 1bf765a..730427e 100644 --- a/packages/core/indexes.json +++ b/packages/core/indexes.json @@ -24,6 +24,7 @@ "Browser", "Component", "Sensors", + "State", "Utilities" ], "functions": [ @@ -98,7 +99,9 @@ { "name": "useStorage", "package": "core", - "internal": true + "docs": "/core/useStorage/", + "category": "State", + "description": "reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)/[SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)" }, { "name": "useTitle", diff --git a/packages/core/useStorage/demo.svelte b/packages/core/useStorage/demo.svelte new file mode 100644 index 0000000..c967ccb --- /dev/null +++ b/packages/core/useStorage/demo.svelte @@ -0,0 +1,16 @@ + + + + + + +
{ dump($state, { skipInvalid: true }) }
diff --git a/packages/core/useStorage/index.md b/packages/core/useStorage/index.md new file mode 100644 index 0000000..db7383f --- /dev/null +++ b/packages/core/useStorage/index.md @@ -0,0 +1,66 @@ +--- +category: State +--- + +# useStorage + +Reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)/[SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) + +## Usage + +```html + +``` + +## Custom Serialization + +By default, `useStorage` will smartly use the corresponding serializer based on the data type of provided default value. For example, `JSON.stringify` / `JSON.parse` will be used for objects, `Number.toString` / `parseFloat` for numbers, etc. + +You can also provide your own serialization function to `useStorage`: + +```html + +``` + +Please note when you provide `null` as the default value, `useStorage` can't assume the data type from it. In this case, you can provide a custom serializer or reuse the built-in ones explicitly. + +```html + +``` diff --git a/packages/core/useStorage/index.ts b/packages/core/useStorage/index.ts index 7fa342f..e09de07 100644 --- a/packages/core/useStorage/index.ts +++ b/packages/core/useStorage/index.ts @@ -1 +1,180 @@ -export function useStorage() {} +import { + MaybeWritable, + RemovableWritable, + toWritable, + unstore, +} from '@svelte-use/shared' +import { useEventListener } from '../useEventListener' +import { ConfigurableWindow, defaultWindow } from '../_configurable' + +export type Serializer = { + read(raw: string): T + write(value: T): string +} + +export const StorageSerializers: Record< + 'boolean' | 'object' | 'number' | 'string' | 'any', + Serializer +> = { + boolean: { + read: (v: any) => v === 'true', + write: (v: any) => String(v), + }, + object: { + read: (v: any) => JSON.parse(v), + write: (v: any) => JSON.stringify(v), + }, + number: { + read: (v: any) => Number.parseFloat(v), + write: (v: any) => String(v), + }, + string: { + read: (v: any) => v, + write: (v: any) => String(v), + }, + any: { + read: (v: any) => v, + write: (v: any) => String(v), + }, +} + +export type StorageLike = Pick + +export interface StorageOptions extends ConfigurableWindow { + /** + * Listen to storage changes, useful for multiple tabs application + * + * @default true + */ + listenToStorageChanges?: boolean + + /** + * Custom data serialization + */ + serializer?: Serializer + + /** + * On error callback + * + * Default lo error to `console.error` + */ + onError?: (error: unknown) => void +} + +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage?: StorageLike, + options?: StorageOptions, +): RemovableWritable +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage?: StorageLike, + options?: StorageOptions, +): RemovableWritable +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage?: StorageLike, + options?: StorageOptions, +): RemovableWritable +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage?: StorageLike, + options?: StorageOptions, +): RemovableWritable +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage?: StorageLike, + options?: StorageOptions, +): RemovableWritable + +/** + * Reactive LocalStorage/SessionStorage + * + * @see https://svelte-use.vercel.app/core/useStorage + * @param key + * @param initialValue + * @param storage + * @param options + */ +export function useStorage( + key: string, + initialValue: MaybeWritable, + storage: StorageLike | undefined = defaultWindow?.localStorage, + options: StorageOptions = {}, +): RemovableWritable { + const { + listenToStorageChanges = true, + window = defaultWindow, + onError = (e) => { + console.error(e) + }, + } = options + + const rawInit: T = unstore(initialValue) + + // https://github.com/vueuse/vueuse/blob/706a04921c6bb81a5fc3687c2b2748a81f5e9a21/packages/core/useStorage/index.ts#L98 + const type = + rawInit == null + ? 'any' + : typeof rawInit === 'boolean' + ? 'boolean' + : typeof rawInit === 'string' + ? 'string' + : typeof rawInit === 'object' + ? 'object' + : Array.isArray(rawInit) + ? 'object' + : !Number.isNaN(rawInit) + ? 'number' + : 'any' + + const data = toWritable(initialValue) as RemovableWritable + const serializer = options.serializer ?? StorageSerializers[type] + + function read(event?: StorageEvent) { + if (!storage || (event && event.key !== key)) { + return + } + + try { + const rawValue = event ? event.newValue : storage.getItem(key) + if (rawValue == null) { + data.set(rawInit) + if (rawInit !== null) { + storage.setItem(key, serializer.write(rawInit)) + } + } else { + data.set(serializer.read(rawValue)) + } + } catch (e) { + onError(e) + } + } + + read() + + if (window && listenToStorageChanges) { + useEventListener(window, 'storage', read) + } + + if (storage) { + data.subscribe((value) => { + try { + if (value == null) { + storage.removeItem(key) + } else { + storage.setItem(key, serializer.write(value)) + } + } catch (e) { + onError(e) + } + }) + } + + return data +} diff --git a/packages/functions.md b/packages/functions.md index 9f480f6..57d9d3a 100644 --- a/packages/functions.md +++ b/packages/functions.md @@ -23,6 +23,9 @@ ### Sensors - [`useMutationObserver`](/core/useMutationObserver/) — watch for changes being made to the DOM tree +### State + - [`useStorage`](/core/useStorage/) — reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)/[SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) + ### Utilities - [`whenever`](/shared/whenever/) — shorthand for watching value to be truthy diff --git a/packages/shared/utils/index.ts b/packages/shared/utils/index.ts index 5849b3e..32dcddb 100644 --- a/packages/shared/utils/index.ts +++ b/packages/shared/utils/index.ts @@ -35,7 +35,7 @@ export function promiseTimeout( }) } -export function unReadable(val: MaybeReadable): T { +export function unstore(val: MaybeReadable): T { return isReadable(val) ? get(val) : val } diff --git a/packages/shared/utils/types.ts b/packages/shared/utils/types.ts index 62c190d..9a43e2a 100644 --- a/packages/shared/utils/types.ts +++ b/packages/shared/utils/types.ts @@ -1,4 +1,4 @@ -import { Readable, Writable } from 'svelte/store' +import { Readable, Updater, Writable } from 'svelte/store' /** * Any function */ @@ -27,6 +27,14 @@ export type MaybeReadable = T | Readable */ export type MaybeWritable = T | Writable +/** + * A writable that allow to set/update `null` or `undefined` + */ +export type RemovableWritable = Writable & { + set(this: void, value: T | null | undefined): void + update(this: void, updater: Updater): void +} + export interface Stopable { /** * A writable indicate whether a stopable instance is executing