solarite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/build.bat +3 -0
- package/build/build.js +139 -0
- package/build/lib/rollup.min.js +11 -0
- package/build/lib/source-map.min.js +1 -0
- package/build/lib/terser.min.js +1 -0
- package/dist/Solarite-debug.js +4143 -0
- package/dist/Solarite.js +3740 -0
- package/dist/Solarite.min.js +4 -0
- package/docs/index.md +423 -0
- package/docs/js/Playground.js +184 -0
- package/docs/js/codemirror/codemirror6.js +32036 -0
- package/docs/js/codemirror/themeSolarIce.js +312 -0
- package/docs/js/documentation.js +32 -0
- package/docs/js/ui/CodeEditor.js +840 -0
- package/docs/js/ui/DarkToggle.js +52 -0
- package/docs/js/ui/FlexResizer.js +142 -0
- package/docs/js/util/Draggable2.js +151 -0
- package/docs/js/util/Errors.js +9 -0
- package/docs/js/util/Html.js +147 -0
- package/docs/js/util/Icons.js +623 -0
- package/docs/js/util/Input.js +253 -0
- package/docs/js/util/Util.js +88 -0
- package/docs/js/util/delve.js +43 -0
- package/docs/media/FiraCode400.woff2 +0 -0
- package/docs/media/cabin-latin-700.woff2 +0 -0
- package/docs/media/documentation.css +93 -0
- package/docs/media/eternium.css +1123 -0
- package/docs/media/solarite-machine.webp +0 -0
- package/index.html +325 -0
- package/package.json +33 -0
- package/readme.md +3 -0
- package/src/solarite/ExprPath.js +554 -0
- package/src/solarite/MultiValueMap.js +65 -0
- package/src/solarite/NodeGroup.js +706 -0
- package/src/solarite/NodeGroupManager.js +582 -0
- package/src/solarite/Shell.js +307 -0
- package/src/solarite/Solarite.js +19 -0
- package/src/solarite/Template.js +85 -0
- package/src/solarite/Util.js +264 -0
- package/src/solarite/createSolarite.js +267 -0
- package/src/solarite/getArg.js +99 -0
- package/src/solarite/hash.js +101 -0
- package/src/solarite/r.js +143 -0
- package/src/solarite/udomdiff.js +233 -0
- package/src/solarite/watch.js +302 -0
- package/src/solarite/watch2.js +439 -0
- package/src/unused/FastLookupArray.js +54 -0
- package/src/unused/Hashes.js +339 -0
- package/src/unused/InUse.test.js +92 -0
- package/src/unused/InUseMap.js +98 -0
- package/src/unused/LinkedList.js +117 -0
- package/src/unused/LinkedList.test.js +115 -0
- package/src/unused/Perf.js +47 -0
- package/src/unused/Template.js +108 -0
- package/src/util/Errors.js +9 -0
- package/src/util/Util.js +88 -0
- package/src/util/delve.js +43 -0
- package/tests/Benchmark.test.js +319 -0
- package/tests/NodeGroup.test.js +115 -0
- package/tests/Shell.test.js +75 -0
- package/tests/Solarite.test.js +2896 -0
- package/tests/Testimony.js +602 -0
- package/tests/index.html +75 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISC License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2020, Andrea Giammarchi, @WebReflection
|
|
5
|
+
*
|
|
6
|
+
* Permission to use, copy, modify, and/or distribute this software for any
|
|
7
|
+
* purpose with or without fee is hereby granted, provided that the above
|
|
8
|
+
* copyright notice and this permission notice appear in all copies.
|
|
9
|
+
*
|
|
10
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
11
|
+
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
12
|
+
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
13
|
+
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
14
|
+
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
15
|
+
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
16
|
+
* PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import NodeGroup from "./NodeGroup.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Node} parentNode The container where children live
|
|
23
|
+
* @param {Node[]} a The list of current/live children
|
|
24
|
+
* @param {Node[]} b The list of future children
|
|
25
|
+
* @param {(entry: Node, action: number) => Node} get
|
|
26
|
+
* The callback invoked per each entry related DOM operation.
|
|
27
|
+
* @param {Node} [before] The optional node used as anchor to insert before.
|
|
28
|
+
* @returns {Node[]} The same list of future children.
|
|
29
|
+
*/
|
|
30
|
+
const udomdiff = (parentNode, a, b, before) => {
|
|
31
|
+
//#IFDEV
|
|
32
|
+
// if (parentNode instanceof ExprPath)
|
|
33
|
+
// parentNode.verify();
|
|
34
|
+
//#ENDIF
|
|
35
|
+
|
|
36
|
+
const bLength = b.length;
|
|
37
|
+
let aEnd = a.length;
|
|
38
|
+
let bEnd = bLength;
|
|
39
|
+
let aStart = 0;
|
|
40
|
+
let bStart = 0;
|
|
41
|
+
let map = null;
|
|
42
|
+
while (aStart < aEnd || bStart < bEnd) {
|
|
43
|
+
// append head, tail, or nodes in between: fast path
|
|
44
|
+
if (aEnd === aStart) {
|
|
45
|
+
// we could be in a situation where the rest of nodes that
|
|
46
|
+
// need to be added are not at the end, and in such case
|
|
47
|
+
// the node to `insertBefore`, if the index is more than 0
|
|
48
|
+
// must be retrieved, otherwise it's gonna be the first item.
|
|
49
|
+
const node = bEnd < bLength
|
|
50
|
+
? (bStart
|
|
51
|
+
? (b[bStart - 1].nextSibling)
|
|
52
|
+
: b[bEnd - bStart])
|
|
53
|
+
: before;
|
|
54
|
+
while (bStart < bEnd) {
|
|
55
|
+
let bNode = b[bStart++];
|
|
56
|
+
parentNode.insertBefore(bNode, node);
|
|
57
|
+
|
|
58
|
+
//#IFDEV
|
|
59
|
+
if (bNode instanceof NodeGroup)
|
|
60
|
+
bNode.verify();
|
|
61
|
+
// if (parentNode instanceof ExprPath)
|
|
62
|
+
// parentNode.verify();
|
|
63
|
+
//#ENDIF
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// remove head or tail: fast path
|
|
67
|
+
else if (bEnd === bStart) {
|
|
68
|
+
while (aStart < aEnd) {
|
|
69
|
+
// remove the node only if it's unknown or not live
|
|
70
|
+
let aNode = a[aStart];
|
|
71
|
+
if (!map || !map.has(aNode)) {
|
|
72
|
+
parentNode.removeChild(aNode);
|
|
73
|
+
|
|
74
|
+
//#IFDEV
|
|
75
|
+
if (aNode instanceof NodeGroup)
|
|
76
|
+
aNode.verify();
|
|
77
|
+
// if (parentNode instanceof ExprPath)
|
|
78
|
+
// parentNode.verify();
|
|
79
|
+
//#ENDIF
|
|
80
|
+
}
|
|
81
|
+
aStart++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// same node: fast path
|
|
85
|
+
else if (a[aStart] === b[bStart]) {
|
|
86
|
+
aStart++;
|
|
87
|
+
bStart++;
|
|
88
|
+
}
|
|
89
|
+
// same tail: fast path
|
|
90
|
+
else if (a[aEnd - 1] === b[bEnd - 1]) {
|
|
91
|
+
aEnd--;
|
|
92
|
+
bEnd--;
|
|
93
|
+
}
|
|
94
|
+
// The once here single last swap "fast path" has been removed in v1.1.0
|
|
95
|
+
// https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
|
|
96
|
+
// reverse swap: also fast path
|
|
97
|
+
else if (
|
|
98
|
+
a[aStart] === b[bEnd - 1] &&
|
|
99
|
+
b[bStart] === a[aEnd - 1]
|
|
100
|
+
) {
|
|
101
|
+
// this is a "shrink" operation that could happen in these cases:
|
|
102
|
+
// [1, 2, 3, 4, 5]
|
|
103
|
+
// [1, 4, 3, 2, 5]
|
|
104
|
+
// or asymmetric too
|
|
105
|
+
// [1, 2, 3, 4, 5]
|
|
106
|
+
// [1, 2, 3, 5, 6, 4]
|
|
107
|
+
const node = a[--aEnd].nextSibling;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
let a2 = b[bStart++];
|
|
111
|
+
let b2 = a[aStart++];
|
|
112
|
+
parentNode.insertBefore(
|
|
113
|
+
a2,
|
|
114
|
+
b2.nextSibling
|
|
115
|
+
);
|
|
116
|
+
//#IFDEV
|
|
117
|
+
if (a2 instanceof NodeGroup)
|
|
118
|
+
a2.verify();
|
|
119
|
+
// if (parentNode instanceof ExprPath)
|
|
120
|
+
// parentNode.verify();
|
|
121
|
+
//#ENDIF
|
|
122
|
+
|
|
123
|
+
let bNode = b[--bEnd];
|
|
124
|
+
parentNode.insertBefore(bNode, node);
|
|
125
|
+
|
|
126
|
+
//#IFDEV
|
|
127
|
+
if (bNode instanceof NodeGroup)
|
|
128
|
+
bNode.verify();
|
|
129
|
+
// if (parentNode instanceof ExprPath)
|
|
130
|
+
// parentNode.verify();
|
|
131
|
+
|
|
132
|
+
//#ENDIF
|
|
133
|
+
|
|
134
|
+
// mark the future index as identical (yeah, it's dirty, but cheap 👍)
|
|
135
|
+
// The main reason to do this, is that when a[aEnd] will be reached,
|
|
136
|
+
// the loop will likely be on the fast path, as identical to b[bEnd].
|
|
137
|
+
// In the best case scenario, the next loop will skip the tail,
|
|
138
|
+
// but in the worst one, this node will be considered as already
|
|
139
|
+
// processed, bailing out pretty quickly from the map index check
|
|
140
|
+
a[aEnd] = b[bEnd];
|
|
141
|
+
}
|
|
142
|
+
// map based fallback, "slow" path
|
|
143
|
+
else {
|
|
144
|
+
// the map requires an O(bEnd - bStart) operation once
|
|
145
|
+
// to store all future nodes indexes for later purposes.
|
|
146
|
+
// In the worst case scenario, this is a full O(N) cost,
|
|
147
|
+
// and such scenario happens at least when all nodes are different,
|
|
148
|
+
// but also if both first and last items of the lists are different
|
|
149
|
+
if (!map) {
|
|
150
|
+
map = new Map;
|
|
151
|
+
let i = bStart;
|
|
152
|
+
while (i < bEnd)
|
|
153
|
+
map.set(b[i], i++);
|
|
154
|
+
}
|
|
155
|
+
// if it's a future node, hence it needs some handling
|
|
156
|
+
if (map.has(a[aStart])) {
|
|
157
|
+
// grab the index of such node, 'cause it might have been processed
|
|
158
|
+
const index = map.get(a[aStart]);
|
|
159
|
+
// if it's not already processed, look on demand for the next LCS
|
|
160
|
+
if (bStart < index && index < bEnd) {
|
|
161
|
+
let i = aStart;
|
|
162
|
+
// counts the amount of nodes that are the same in the future
|
|
163
|
+
let sequence = 1;
|
|
164
|
+
while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
|
|
165
|
+
sequence++;
|
|
166
|
+
// effort decision here: if the sequence is longer than replaces
|
|
167
|
+
// needed to reach such sequence, which would brings again this loop
|
|
168
|
+
// to the fast path, prepend the difference before a sequence,
|
|
169
|
+
// and move only the future list index forward, so that aStart
|
|
170
|
+
// and bStart will be aligned again, hence on the fast path.
|
|
171
|
+
// An example considering aStart and bStart are both 0:
|
|
172
|
+
// a: [1, 2, 3, 4]
|
|
173
|
+
// b: [7, 1, 2, 3, 6]
|
|
174
|
+
// this would place 7 before 1 and, from that time on, 1, 2, and 3
|
|
175
|
+
// will be processed at zero cost
|
|
176
|
+
if (sequence > (index - bStart)) {
|
|
177
|
+
const node = a[aStart];
|
|
178
|
+
while (bStart < index) {
|
|
179
|
+
let bNode = b[bStart++];
|
|
180
|
+
parentNode.insertBefore(bNode, node);
|
|
181
|
+
|
|
182
|
+
//#IFDEV
|
|
183
|
+
if (bNode instanceof NodeGroup)
|
|
184
|
+
bNode.verify();
|
|
185
|
+
// if (parentNode instanceof ExprPath)
|
|
186
|
+
// parentNode.verify();
|
|
187
|
+
|
|
188
|
+
//#ENDIF
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// if the effort wasn't good enough, fallback to a replace,
|
|
192
|
+
// moving both source and target indexes forward, hoping that some
|
|
193
|
+
// similar node will be found later on, to go back to the fast path
|
|
194
|
+
else {
|
|
195
|
+
let aNode = a[aStart++]
|
|
196
|
+
let bNode = b[bStart++]
|
|
197
|
+
parentNode.replaceChild(
|
|
198
|
+
bNode,
|
|
199
|
+
aNode
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
//#IFDEV
|
|
203
|
+
if (aNode instanceof NodeGroup)
|
|
204
|
+
aNode.verify();
|
|
205
|
+
// if (parentNode instanceof ExprPath)
|
|
206
|
+
// parentNode.verify();
|
|
207
|
+
//#ENDIF
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// otherwise move the source forward, 'cause there's nothing to do
|
|
211
|
+
else
|
|
212
|
+
aStart++;
|
|
213
|
+
}
|
|
214
|
+
// this node has no meaning in the future list, so it's more than safe
|
|
215
|
+
// to remove it, and check the next live node out instead, meaning
|
|
216
|
+
// that only the live list index should be forwarded
|
|
217
|
+
else {
|
|
218
|
+
let aNode = a[aStart++];
|
|
219
|
+
parentNode.removeChild(aNode);
|
|
220
|
+
|
|
221
|
+
//#IFDEV
|
|
222
|
+
if (aNode instanceof NodeGroup)
|
|
223
|
+
aNode.verify();
|
|
224
|
+
// if (parentNode instanceof ExprPath)
|
|
225
|
+
// parentNode.verify();
|
|
226
|
+
//#ENDIF
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return b;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export default udomdiff;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for watch variables and performing precise renders.
|
|
3
|
+
*/
|
|
4
|
+
import {assert} from "../util/Errors.js";
|
|
5
|
+
import delve from "../util/delve.js";
|
|
6
|
+
import {getObjectHash, getObjectId} from "./hash.js";
|
|
7
|
+
import MultiValueMap from "./MultiValueMap.js";
|
|
8
|
+
import NodeGroupManager, {LoopInfo} from "./NodeGroupManager.js";
|
|
9
|
+
import Template from "./Template.js";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stores info how to transform a path to a template. */
|
|
14
|
+
export class TransformerInfo {
|
|
15
|
+
constructor(path, transformer, hash) {
|
|
16
|
+
this.path = path;
|
|
17
|
+
this.transformer = transformer;
|
|
18
|
+
this.hash = hash;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maps an object path to the function that converts it to a Template.
|
|
25
|
+
* Once it's convert to a template, we can get the hash of that Tempate.
|
|
26
|
+
* Then that hash tells us what NodeGroups are affected by the object.
|
|
27
|
+
*
|
|
28
|
+
* We store the function to get the Template, instead of the Template itself,
|
|
29
|
+
* so we can call that function again when the object has a new value.
|
|
30
|
+
* @type {MultiValueMap<Object, function(...Object):Template>} */
|
|
31
|
+
let pathToTransformer = new MultiValueMap(); // uses a Set() for each value.
|
|
32
|
+
export {pathToTransformer}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Watch the given properties on obj and call obj.render() when any of them are changed.
|
|
38
|
+
* @param obj {Object}
|
|
39
|
+
* @param props {string[]} */
|
|
40
|
+
export function renderWhenChanged(obj, ...props) {
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @param objectPaths {(*|function)[]}
|
|
47
|
+
* @returns {Template} */
|
|
48
|
+
export function watchGet(...objectPaths) {
|
|
49
|
+
|
|
50
|
+
/** @type {function} */
|
|
51
|
+
let transformer = objectPaths.at(-1);
|
|
52
|
+
let paths;
|
|
53
|
+
if (typeof transformer === 'function') {
|
|
54
|
+
paths = objectPaths.slice(0, -1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// No transformer provided, so we create our own.
|
|
58
|
+
else if (objectPaths.length === 1) {
|
|
59
|
+
paths = [objectPaths[0].slice(0, -1)];
|
|
60
|
+
let prop = objectPaths[0].at(-1);
|
|
61
|
+
transformer = (...args) => (args[0][prop])
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// Save arguments used to call the template, so we can call it again when those args have their values change.
|
|
66
|
+
let args = [];
|
|
67
|
+
for (let path of paths) {
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
let obj = delve(watchSet(path[0]), path.slice(1));
|
|
71
|
+
args.push(obj);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let template = transformer(...args);
|
|
75
|
+
|
|
76
|
+
// If the result isn't a Template, convert the function to return a Template that wraps the result.
|
|
77
|
+
// This way NodeGroupManager.findAndDelete() can find a NodeGroup that matches this Template's hash.
|
|
78
|
+
if (!(template instanceof Template)) {
|
|
79
|
+
let oldToTemplate = transformer;
|
|
80
|
+
transformer = function() {
|
|
81
|
+
return new Template(['', ''], [oldToTemplate(...arguments)]);
|
|
82
|
+
}
|
|
83
|
+
template = transformer(...args);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Map the object paths to the function that creates a template.
|
|
87
|
+
let hash = getObjectHash(template);
|
|
88
|
+
for (let path of paths) {
|
|
89
|
+
let serializedPath = serializePath(path);
|
|
90
|
+
pathToTransformer.add(serializedPath, new TransformerInfo(path, transformer, hash)); // Uses a Set() to ensure no duplicates.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return template;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//let proxyCache = new WeakMap();
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set the value of a variable in a way that's watched, so later when we call .renderWatched()
|
|
101
|
+
* We can find what NodeGroups to update.*/
|
|
102
|
+
export function watchSet(obj) {
|
|
103
|
+
if (obj?.$isProxy===true)
|
|
104
|
+
return obj; // It's already a Proxy.
|
|
105
|
+
|
|
106
|
+
// This cache doesn't make things faster.
|
|
107
|
+
// let result = proxyCache.get(obj);
|
|
108
|
+
// if (!result) {
|
|
109
|
+
// result = new Proxy(obj, new ProxyHandler(obj));
|
|
110
|
+
// proxyCache.set(obj, result);
|
|
111
|
+
// }
|
|
112
|
+
// return result;
|
|
113
|
+
return new Proxy(obj, new ProxyHandler(obj));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Loop over each item and apply watchGet() to each item.
|
|
118
|
+
* @param arrayPath {*[]}
|
|
119
|
+
* @param callback {function(obj:Object, index:int):Template}
|
|
120
|
+
* @returns {Template} */
|
|
121
|
+
export function forEach(arrayPath, callback) {
|
|
122
|
+
let array = delve(arrayPath[0], arrayPath.slice(1));
|
|
123
|
+
|
|
124
|
+
// This is retrieved on the 'insert' path inside renderWatched()
|
|
125
|
+
let ngm = NodeGroupManager.get(arrayPath[0]);
|
|
126
|
+
if (ngm.clearSubscribers) {
|
|
127
|
+
ngm.clearSubscribers = false;
|
|
128
|
+
ngm.pathToLoopInfo = new MultiValueMap();
|
|
129
|
+
} // TODO: Move tis into NodeGroupMAnager.get() without breaking things?
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
let newItems = [...array.map((item, i) => {
|
|
133
|
+
// TODO: This needs to wrap callback so we can pass it the index also.
|
|
134
|
+
return watchGet([...arrayPath, i], callback); // calls callback(array[i], i)
|
|
135
|
+
})
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// We return a template that wraps the array
|
|
139
|
+
// So that NodeGroup.applyOneExpr can set the ExprPath and nextSibling on the template.
|
|
140
|
+
// Then the 'insert' path in renderWatched() uses that data fora dding more nodes.
|
|
141
|
+
let result = new Template(['', ''], [newItems])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
// We get a unique hash for each foreach template because the [''] array is unique each time.
|
|
145
|
+
let loopInfo = new LoopInfo(result, callback);
|
|
146
|
+
ngm.pathToLoopInfo.add(serializePath(arrayPath), loopInfo);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function serializePath(path) {
|
|
151
|
+
// Convert any array indices to strings, so serialized comparisons work.
|
|
152
|
+
return JSON.stringify([getObjectId(path[0]), ...path.slice(1).map(item => item+'')])
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* When an object property is accessed, a new Proxy with a new instance of this handler class is created,
|
|
159
|
+
* but it tracks the path from the root to the property.
|
|
160
|
+
* That way when a property is set, it can report the changed path. */
|
|
161
|
+
class ProxyHandler {
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param root An element managed by a NodeGroupManager. The same as the NodeGroupManager's rootEl.
|
|
165
|
+
* @param path {string[]} Used internally. */
|
|
166
|
+
constructor(root, path=[]) {
|
|
167
|
+
/*#IFDEV*/assert(NodeGroupManager.get(root))/*#ENDIF*/
|
|
168
|
+
this.root = root;
|
|
169
|
+
this.path = path; // path from root to this Proxy.
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param obj {Object}
|
|
174
|
+
* @param prop {string} */
|
|
175
|
+
get(obj, prop) {
|
|
176
|
+
|
|
177
|
+
// Special props. Currently unused.
|
|
178
|
+
// if (prop === '$path')
|
|
179
|
+
// return this.path;
|
|
180
|
+
// if (prop === '$root')
|
|
181
|
+
// return this.root;
|
|
182
|
+
if (prop === '$isProxy')
|
|
183
|
+
return true;
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// 1. Array.splice()
|
|
187
|
+
if (prop === 'splice' && Array.isArray(obj)) {
|
|
188
|
+
return (index, deleteCount, ...items) => {
|
|
189
|
+
let ngm = NodeGroupManager.get(this.root)
|
|
190
|
+
|
|
191
|
+
if (deleteCount) {
|
|
192
|
+
|
|
193
|
+
// Get the hash of each object along the delete range. The process to get the hash is:
|
|
194
|
+
// Serialized Path -> transformer -> Template -> hash.
|
|
195
|
+
let hashes = [];
|
|
196
|
+
for (let i=index; i<index+deleteCount; i++) {
|
|
197
|
+
let serializedPath = serializePath([this.root, ...this.path, i+'']);
|
|
198
|
+
|
|
199
|
+
let obj = delve(this.root, [...this.path, i])
|
|
200
|
+
for (let transformerInfo of pathToTransformer.getAll(serializedPath)) {
|
|
201
|
+
let template = transformerInfo.transformer(obj);
|
|
202
|
+
let hash = getObjectHash(template);
|
|
203
|
+
hashes.push(hash); // Hashes may go to nodes in more than one loop.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ngm.changes.push(new Change('delete', this.root, [...this.path, index+''], hashes));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//let oldArray = [...obj];
|
|
211
|
+
let result = obj.splice(index, deleteCount);
|
|
212
|
+
|
|
213
|
+
// Inserting
|
|
214
|
+
if (items.length) {
|
|
215
|
+
|
|
216
|
+
let beforeNgs;
|
|
217
|
+
for (let loopInfo of ngm.getLoopInfo([this.root, ...this.path])) {
|
|
218
|
+
let beforeObj = delve(this.root, [...this.path, index]);
|
|
219
|
+
|
|
220
|
+
// Find where to insert before.
|
|
221
|
+
if (beforeObj) {
|
|
222
|
+
let beforeTemplate = loopInfo.itemTransformer(beforeObj);
|
|
223
|
+
let beforeHash = getObjectHash(beforeTemplate);
|
|
224
|
+
beforeNgs = ngm.nodeGroupsAvailable.data[beforeHash];
|
|
225
|
+
|
|
226
|
+
if (beforeNgs) {
|
|
227
|
+
let hash = getObjectHash(loopInfo.template)
|
|
228
|
+
let loopNgs = ngm.nodeGroupsAvailable.data[hash] || [];
|
|
229
|
+
for (let loopNg of loopNgs)
|
|
230
|
+
for (let beforeNg of beforeNgs)
|
|
231
|
+
if (beforeNg.startNode.parentNode === loopNg.startNode.parentNode)
|
|
232
|
+
ngm.changes.push(new Change('insert', this.root, [...this.path, index + ''], items, beforeTemplate));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!beforeNgs)
|
|
236
|
+
ngm.changes.push(new Change('insert', this.root, [...this.path, index + ''], items));
|
|
237
|
+
}
|
|
238
|
+
obj.splice(index, 0, ...items);
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// 2. Get property
|
|
247
|
+
// If we're getting an object or array property, apply watch() to it recursively.
|
|
248
|
+
let result = Reflect.get(obj, prop)
|
|
249
|
+
if (result && typeof result === 'object') {
|
|
250
|
+
let handler = new ProxyHandler(this.root, [...this.path, prop]); // same root, one level deeper on the path.
|
|
251
|
+
return new Proxy(result, handler);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
set(obj, prop, newValue) {
|
|
259
|
+
let ngm = NodeGroupManager.get(this.root);
|
|
260
|
+
ngm.changes.push(new Change('set', this.root, [...this.path, prop], newValue));
|
|
261
|
+
return Reflect.set(obj, prop, newValue)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
*
|
|
268
|
+
*/
|
|
269
|
+
class Change {
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @param action {string}
|
|
273
|
+
* @param root {Object|Array}
|
|
274
|
+
* @param path {string[]}
|
|
275
|
+
* @param value
|
|
276
|
+
* If setting a value, this is the new value.
|
|
277
|
+
* If deleting from an array, this is an array of all the NodeGroups to delete.
|
|
278
|
+
*
|
|
279
|
+
* @param beforeTemplate
|
|
280
|
+
* */
|
|
281
|
+
constructor(action, root, path, value, beforeTemplate=null) {
|
|
282
|
+
this.action = action;
|
|
283
|
+
|
|
284
|
+
// TODO: Store root as first item of path, to be consistent with code elsewhere.
|
|
285
|
+
this.root = root;
|
|
286
|
+
this.path = path;
|
|
287
|
+
this.value = value;
|
|
288
|
+
this.beforeTemplate = beforeTemplate;
|
|
289
|
+
|
|
290
|
+
/** @type {TransformerInfo[]} */
|
|
291
|
+
this.transformerInfo = [];
|
|
292
|
+
|
|
293
|
+
// Traverse up the path.
|
|
294
|
+
for (let i=this.path.length; i>0; i--) {
|
|
295
|
+
let path = this.path.slice(0, i);
|
|
296
|
+
let fullPath = [this.root, ...path];
|
|
297
|
+
|
|
298
|
+
let serializedPath = getObjectHash(fullPath); // TODO: Why not serializedPath() ?
|
|
299
|
+
this.transformerInfo.push(...pathToTransformer.getAll(serializedPath))
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|