forked from gbishop/OS-DPI
-
Notifications
You must be signed in to change notification settings - Fork 0
/
state.js
121 lines (112 loc) · 2.98 KB
/
state.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import merge from "mergerino";
export class State {
constructor(persistKey = "") {
this.persistKey = persistKey;
/** @type {Set<function>} */
this.listeners = new Set();
/** @type {Object} */
this.values = {};
/** @type {Set<string>} */
this.updated = new Set();
if (this.persistKey) {
/* persistence */
const persist = window.sessionStorage.getItem(this.persistKey);
if (persist) {
this.values = JSON.parse(persist);
}
}
}
/** unified interface to state
* @param {string} [name] - possibly dotted path to a value
* @param {any} defaultValue
* @returns {any}
*/
get(name, defaultValue = "") {
if (name && name.length) {
return name
.split(".")
.reduce((o, p) => (o ? o[p] : defaultValue), this.values);
} else {
return undefined;
}
}
/**
* update the state with a patch and invoke any listeners
*
* @param {Object} patch - the changes to make to the state
* @return {void}
*/
update(patch = {}) {
for (const key in patch) {
this.updated.add(key);
}
this.values = merge(this.values, patch);
for (const callback of this.listeners) {
callback();
}
if (this.persistKey) {
const persist = JSON.stringify(this.values);
window.sessionStorage.setItem(this.persistKey, persist);
}
}
/**
* return a new state with the patch applied
* @param {Object} patch - changes to apply
* @return {State} - new independent state
*/
clone(patch = {}) {
const result = new State();
result.values = merge(this.values, patch);
return result;
}
/** clear - reset the state
*/
clear() {
const userState = Object.keys(this.values).filter((name) =>
name.startsWith("$")
);
const patch = Object.fromEntries(
userState.map((name) => [name, undefined])
);
this.update(patch);
}
/** observe - call this function when the state updates
* @param {Function} callback
*/
observe(callback) {
this.listeners.add(callback);
}
/** return true if the given state has been upated since last you asked
* @param {string} stateName
* @returns boolean
*/
hasBeenUpdated(stateName) {
const result = this.updated.has(stateName);
if (result) {
this.updated.delete(stateName);
}
return result;
}
/** define - add a named state to the global system state
* @param {String} name - name of the state
* @param {any} defaultValue - value if not already defined
*/
define(name, defaultValue) {
if (typeof this.values[name] === "undefined") {
this.values[name] = defaultValue;
}
}
/** interpolate
* @param {string} input
* @returns {string} input with $name replaced by values from the state
*/
interpolate(input) {
let result = input.replace(/(\$[a-zA-Z0-9_.]+)/, (_, name) =>
this.get(name)
);
result = result.replace(/\$\{([a-zA-Z0-9_.]+)}/, (_, name) =>
this.get("$" + name)
);
return result;
}
}