forked from gbishop/OS-DPI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate.js
More file actions
134 lines (126 loc) · 3.28 KB
/
state.js
File metadata and controls
134 lines (126 loc) · 3.28 KB
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
130
131
132
133
134
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 = undefined) {
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);
}
};
/** 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;
}
/**
* Normalize tags
*
* @param {string[]} tags - Tags that must be in each row
* @return {string[]} normalized tags as an array
*/
normalizeTags(tags) {
/** @type {string[]} tags */
// normalize
return tags
.map((t) => {
if (t.startsWith("$")) return this.get(t) || "";
else return t;
})
.flat();
}
}