1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

#6372: Implement basic state management with localStorage integration

This commit is contained in:
checktheroads
2021-07-06 17:55:13 -07:00
parent 00c4ac8d51
commit d9a6f11c35
2 changed files with 165 additions and 0 deletions

View File

@ -6,6 +6,11 @@ type Dict<T extends unknown = unknown> = Record<string, T>;
type Nullable<T> = T | null;
/**
* Enforce string index type (not `number` or `symbol`).
*/
type Index<O extends Dict, K extends keyof O> = K extends string ? K : never;
type APIAnswer<T> = {
count: number;
next: Nullable<string>;

View File

@ -0,0 +1,160 @@
/**
* `StateManger` configuration options.
*/
interface StateOptions {
/**
* If true, all values will be written to localStorage when calling `set()`. Additionally, when
* a new state instance is initialized, if the same localStorage state key (see `key` property)
* exists in localStorage, the value will be read and used as the initial value.
*/
persist?: boolean;
}
/**
* Typed implementation of native `ProxyHandler`.
*/
class ProxyStateHandler<T extends Dict, K extends keyof T = keyof T> implements ProxyHandler<T> {
public set<S extends Index<T, K>>(target: T, key: S, value: T[S]): boolean {
target[key] = value;
return true;
}
public get<G extends Index<T, K>>(target: T, key: G): T[G] {
return target[key];
}
public has(target: T, key: string): boolean {
return key in target;
}
}
/**
* Manage runtime and/or locally stored (via localStorage) state.
*/
export class StateManager<T extends Dict, K extends keyof T = keyof T> {
/**
* implemented `ProxyHandler` for the underlying `Proxy` object.
*/
private handlers: ProxyStateHandler<T>;
/**
* Underlying `Proxy` object for this instance.
*/
private proxy: T;
/**
* Options for this instance.
*/
private options: StateOptions;
/**
* localStorage key for this instance.
*/
private key: string = '';
constructor(raw: T, options: StateOptions) {
this.key = this.generateStateKey(raw);
this.options = options;
if (this.options.persist) {
const saved = this.retrieve();
if (saved !== null) {
raw = { ...raw, ...saved };
}
}
this.handlers = new ProxyStateHandler<T>();
this.proxy = new Proxy(raw, this.handlers);
if (this.options.persist) {
this.save();
}
}
/**
* Generate a semi-unique localStorage key for this instance.
*/
private generateStateKey(obj: T): string {
const encoded = window.btoa(Object.keys(obj).join('---'));
return `netbox-${encoded}`;
}
/**
* Get the current value of `key`.
*
* @param key Object key name.
* @returns Object value.
*/
public get<G extends Index<T, K>>(key: G): T[G] {
return this.handlers.get(this.proxy, key);
}
/**
* Set a new value for `key`.
*
* @param key Object key name.
* @param value New value.
*/
public set<G extends Index<T, K>>(key: G, value: T[G]): void {
this.handlers.set(this.proxy, key, value);
if (this.options.persist) {
this.save();
}
}
/**
* Access the full instance.
*
* @returns StateManager instance.
*/
public all(): T {
return this.proxy;
}
/**
* Access all state keys.
*/
public keys(): K[] {
return Object.keys(this.proxy) as K[];
}
/**
* Access all state values.
*/
public values(): T[K][] {
return Object.values(this.proxy) as T[K][];
}
/**
* Serialize and save the current state to localStorage.
*/
private save(): void {
const value = JSON.stringify(this.proxy);
localStorage.setItem(this.key, value);
}
/**
* Retrieve the serialized state object from localStorage.
*
* @returns Parsed state object.
*/
private retrieve(): T | null {
const raw = localStorage.getItem(this.key);
if (raw !== null) {
const data = JSON.parse(raw) as T;
return data;
}
return null;
}
}
/**
* Create a new state object. Only one instance should exist at runtime for a given state.
*
* @param initial State's initial value.
* @param options State management instance options.
* @returns State management instance.
*/
export function createState<T extends Dict>(
initial: T,
options: StateOptions = {},
): StateManager<T> {
return new StateManager<T>(initial, options);
}