forked from UNC-Project-Open-AAC/OS-DPI
-
Notifications
You must be signed in to change notification settings - Fork 0
/
state.js
129 lines (120 loc) · 3.18 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
122
123
124
125
126
127
128
129
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) {
// handle dotted names
const patch = {};
let p = patch;
let dots = name.split(".");
let i = 0;
for (; i < dots.length - 1; i++) {
p = p[dots[i]] = {};
}
p[dots[i]] = (/** @type {any} */ currentValue) =>
currentValue || defaultValue;
this.update(patch);
}
/** 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;
}
}