Created
June 11, 2020 03:47
-
-
Save IanSSenne/8392bffea20396087c5dfb34c2cb5cb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useEffect, useState } from "react"; | |
| export const NO_VALUE = Symbol("NO_VALUE"); | |
| export const Middleware = { | |
| JSON: [{ from: JSON.parse, to: JSON.stringify }], | |
| base64: [{ from: (v) => atob(v), to: (v) => btoa(v) }] | |
| } | |
| export class LocalStorageSettingsInterface { | |
| static get defaultMiddleware() { | |
| return Middleware.JSON | |
| } | |
| constructor(setting_name, defaults, middleware) { | |
| this.middleware = (middleware || LocalStorageSettingsInterface.defaultMiddleware).flat(1); | |
| this.defaults = defaults; | |
| this.setting_name = setting_name; | |
| try { | |
| this.settings = this.middleware.reduce((a, m) => m.from(a), localStorage.getItem(this.setting_name)); | |
| } catch (e) { | |
| this.settings = {}; | |
| } | |
| if (typeof this.settings != "object" || this.settings === null) { | |
| this.settings = {}; | |
| } | |
| } | |
| get(name) { | |
| const parts = name.split("/"); | |
| let settings = this.settings; | |
| let default_settings = this.defaults; | |
| let IS_DEFAULT = false; | |
| while (parts[0]) { | |
| const part = parts.shift(); | |
| if (settings === undefined) { | |
| return Promise.resolve({ IS_DEFAULT: false, value: NO_VALUE }); | |
| } | |
| if (settings[part]) { | |
| settings = settings[part] | |
| } else { | |
| IS_DEFAULT = true; | |
| settings = default_settings[part]; | |
| } | |
| default_settings = default_settings[part]; | |
| } | |
| return Promise.resolve({ IS_DEFAULT, value: settings }); | |
| } | |
| set(name, value) { | |
| const parts = name.split("/"); | |
| const end = parts.pop(); | |
| let settings = this.settings; | |
| while (parts[0]) { | |
| const part = parts.shift(); | |
| if (typeof settings[part] === "object") { | |
| settings = settings[part]; | |
| } else if (typeof settings[part] === "undefined") { | |
| settings[part] = {}; | |
| settings = settings[part]; | |
| } else { | |
| throw new Error(`unable to set ${name} as one of its parents is not an object`); | |
| } | |
| } | |
| settings[end] = value; | |
| localStorage.setItem(this.setting_name, this.middleware.reduce((a, m) => m.to(a), this.settings)); | |
| return Promise.resolve(name.split("/").map((_, i, a) => a.slice(0, 1 + i).join("/"))); | |
| } | |
| } | |
| export function createSettingsHandler(io_handlers, options) { | |
| options = Object.assign({ future_delay: 1000 }, options) | |
| const future_tasks = []; | |
| let future_cache = { last_id: null }; | |
| async function processFutures() { | |
| const updatedPaths = new Set(); | |
| const local_future_tasks = future_tasks.splice(0); | |
| while (local_future_tasks[0]) { | |
| const task = local_future_tasks.pop(); | |
| if (!updatedPaths.has(task.name)) {//since we are pushing to future_tasks we only want to head the latest update to a path in case its expensive to write to it. | |
| updatedPaths.add(task.name); | |
| await task.handler.set(task.name, task.value); | |
| } | |
| } | |
| } | |
| function createFuture(o) { | |
| future_tasks.push(o); | |
| clearTimeout(future_cache.last_id); | |
| future_cache.last_id = setTimeout(() => { | |
| processFutures(); | |
| }, options.future_delay); | |
| } | |
| function createFutureUpdateTask(name, value) { | |
| for (let i = 1; i < io_handlers.length; i++) { | |
| createFuture({ handler: io_handlers[i], name, value }); | |
| } | |
| } | |
| function should_propagate_down(name, value, end) { | |
| for (let i = io_handlers.indexOf(end); i > 0; i--) { | |
| createFuture({ handler: io_handlers[i], name, value }); | |
| } | |
| } | |
| async function get(name) { | |
| let potential_return_value = NO_VALUE; | |
| let return_value = NO_VALUE; | |
| for (let handler of io_handlers) { | |
| const value = await handler.get(name); | |
| if (undefined !== value.value) { | |
| if (io_handlers.indexOf(handler) !== 0 && !value.IS_DEFAULT && value.value !== NO_VALUE) { | |
| return_value = { name, value, handler }; | |
| break; | |
| } else { | |
| potential_return_value = { name, value, handler }; | |
| } | |
| } | |
| } | |
| if (return_value !== NO_VALUE) { | |
| should_propagate_down(return_value.name, return_value.value.value, return_value.handler); | |
| return return_value.value.value; | |
| } else if (potential_return_value !== NO_VALUE) { | |
| createFuture({ name: potential_return_value.name, handler: potential_return_value.handler, value: potential_return_value.value.value }); | |
| return potential_return_value.value.value; | |
| } else { | |
| return NO_VALUE; | |
| } | |
| } | |
| function set(name, value) { | |
| createFutureUpdateTask(name, value); | |
| return io_handlers[0].set(name, value); | |
| } | |
| const updates = {}; | |
| const Setting = ({ name, option: Option, default: defaultValue }) => { | |
| const [value, setValue] = useState(defaultValue || NO_VALUE); | |
| const onChange = (next_value) => { | |
| setValue(next_value); | |
| set(name, next_value).then((updatedPaths) => { | |
| updatedPaths.forEach((path) => { | |
| if (updates[path]) { | |
| updates[path].forEach(cb => cb(next_value)); | |
| } | |
| }); | |
| }); | |
| } | |
| useEffect(() => { | |
| (async () => { | |
| const value = await get(name); | |
| if (value === NO_VALUE) { | |
| onChange(defaultValue); | |
| } else { | |
| setValue(value); | |
| } | |
| })() | |
| }, [defaultValue, name]); | |
| return (value !== NO_VALUE && React.createElement(Option, { onChange, value })) | |
| } | |
| const useSetting = (name) => { | |
| const [value, setValue] = useState(NO_VALUE); | |
| useEffect(() => { | |
| (async () => { | |
| setValue(await get(name)); | |
| })(); | |
| updates[name] = updates[name] || []; | |
| updates[name].push(setValue); | |
| return () => updates[name].splice(updates[name].indexOf(setValue), 1); | |
| }, [name]); | |
| return value; | |
| } | |
| return { Setting, useSetting }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment