import store, { StoreAPI } from 'store2'; import storeSetup from './lib/store-setup'; import { State } from './index'; // setting up the store, overriding set and get to use telejson // @ts-ignore storeSetup(store._); export const STORAGE_KEY = '@storybook/ui/store'; function get(storage: StoreAPI) { const data = storage.get(STORAGE_KEY); return data || {}; } function set(storage: StoreAPI, value: Patch) { return storage.set(STORAGE_KEY, value); } function update(storage: StoreAPI, patch: Patch) { const previous = get(storage); // Apply the same behaviour as react here return set(storage, { ...previous, ...patch }); } type GetState = () => State; type SetState = (a: any, b: any) => any; interface Upstream { getState: GetState; setState: SetState; } type Patch = Partial; type InputFnPatch = (s: State) => Patch; type InputPatch = Patch | InputFnPatch; export interface Options { persistence: 'none' | 'session' | string; } type CallBack = (s: State) => void; type CallbackOrOptions = CallBack | Options; // Our store piggybacks off the internal React state of the Context Provider // It has been augmented to persist state to local/sessionStorage export default class Store { upstreamGetState: GetState; upstreamSetState: SetState; constructor({ setState, getState }: Upstream) { this.upstreamSetState = setState; this.upstreamGetState = getState; } // The assumption is that this will be called once, to initialize the React state // when the module is instanciated getInitialState(base: State) { // We don't only merge at the very top level (the same way as React setState) // when you set keys, so it makes sense to do the same in combining the two storage modes // Really, you shouldn't store the same key in both places return { ...base, ...get(store.local), ...get(store.session) }; } getState() { return this.upstreamGetState(); } async setState(inputPatch: InputPatch, options?: Options): Promise; async setState(inputPatch: InputPatch, callback?: CallBack, options?: Options): Promise; async setState( inputPatch: InputPatch, cbOrOptions?: CallbackOrOptions, inputOptions?: Options ): Promise { let callback; let options; if (typeof cbOrOptions === 'function') { callback = cbOrOptions; options = inputOptions; } else { options = cbOrOptions; } const { persistence = 'none' } = options || {}; let patch: Patch = {}; // What did the patch actually return let delta: Patch = {}; if (typeof inputPatch === 'function') { // Pass the same function, but set delta on the way patch = (state: State) => { const getDelta = inputPatch as InputFnPatch; delta = getDelta(state); return delta; }; } else { patch = inputPatch; delta = patch; } const newState: State = await new Promise((resolve) => { this.upstreamSetState(patch, resolve); }); if (persistence !== 'none') { const storage = persistence === 'session' ? store.session : store.local; await update(storage, delta); } if (callback) { callback(newState); } return newState; } }