@lukekaalim/act-backstage 3.0.0-alpha.0 → 3.0.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/CHANGELOG.md +20 -0
- package/README.md +19 -0
- package/builder.ts +24 -0
- package/mod.ts +2 -2
- package/package.json +3 -3
- package/props.ts +2 -2
- package/space2.ts +264 -0
- package/render.ts +0 -37
- package/space.ts +0 -222
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @lukekaalim/act-backstage
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 6658c01: Internal Refactor!
|
|
8
|
+
- afd247e: Another major refactor! So everything is broken. Good luck!
|
|
9
|
+
- b3f6c49: Added debug capabilities and protocol
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [6658c01]
|
|
14
|
+
- Updated dependencies [fdf1557]
|
|
15
|
+
- Updated dependencies [ccb3900]
|
|
16
|
+
- Updated dependencies [afd247e]
|
|
17
|
+
- Updated dependencies [2984273]
|
|
18
|
+
- Updated dependencies [b3f6c49]
|
|
19
|
+
- Updated dependencies [c5e8775]
|
|
20
|
+
- @lukekaalim/act-recon@3.0.0
|
|
21
|
+
- @lukekaalim/act@4.0.0
|
|
22
|
+
|
|
3
23
|
## 3.0.0-alpha.0
|
|
4
24
|
|
|
5
25
|
### Major Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @lukekaalim/act-backstage
|
|
2
|
+
|
|
3
|
+
Backstage is a collection of utilities for creating Act renderers
|
|
4
|
+
fairly simply. Most renderers in Act are just composed
|
|
5
|
+
of one or more "RenderSpace"'s, which are the systems
|
|
6
|
+
that keep track of which HTML/ThreeJs/SVG nodes correspond
|
|
7
|
+
to which commit.
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
<TypeDoc project="@lukekaalim/act-backstage" name="NodeBuilder" />
|
|
12
|
+
|
|
13
|
+
<TypeDoc project="@lukekaalim/act-backstage" name="RenderSpace2" />
|
|
14
|
+
|
|
15
|
+
<TypeDoc project="@lukekaalim/act-backstage" name="setPropObject" />
|
|
16
|
+
|
|
17
|
+
## Guide
|
|
18
|
+
|
|
19
|
+
We're going to build a classic render function
|
package/builder.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Element } from "@lukekaalim/act";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This type describes an implementation
|
|
5
|
+
* that can be provided to a RenderSpace
|
|
6
|
+
* in order to produce a fully-featured renderer.
|
|
7
|
+
*/
|
|
8
|
+
export type NodeBuilder<TNode, TRoot = string | symbol> = {
|
|
9
|
+
roots: Set<TRoot>,
|
|
10
|
+
|
|
11
|
+
create: (element: Element, root: TRoot) => null | TNode,
|
|
12
|
+
destroy?: (el: TNode) => unknown,
|
|
13
|
+
|
|
14
|
+
linkRoot?: (child: TNode) => unknown,
|
|
15
|
+
unlinkRoot?: (child: TNode) => unknown,
|
|
16
|
+
link?: (child: TNode, parent: TNode) => unknown,
|
|
17
|
+
unlink?: (child: TNode, parent: TNode) => unknown,
|
|
18
|
+
|
|
19
|
+
sort?: (el: TNode, children: readonly TNode[]) => unknown,
|
|
20
|
+
update?: (el: TNode, next: Element, prev: null | Element) => unknown,
|
|
21
|
+
|
|
22
|
+
suspend?: (el: TNode, parent: TNode) => void,
|
|
23
|
+
unsuspend?: (el: TNode, parent: TNode) => void,
|
|
24
|
+
};
|
package/mod.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukekaalim/act-backstage",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "mod.ts",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@lukekaalim/act": "^
|
|
7
|
-
"@lukekaalim/act-recon": "^3.0.0
|
|
6
|
+
"@lukekaalim/act": "^4.0.0",
|
|
7
|
+
"@lukekaalim/act-recon": "^3.0.0"
|
|
8
8
|
}
|
|
9
9
|
}
|
package/props.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generically attempt to apply props to a target either by assigning them,
|
|
3
|
-
* or letting a
|
|
3
|
+
* or letting a specialized function handle it.
|
|
4
4
|
*
|
|
5
5
|
* Iterate across props from as to present, setting removed props to "undefined"
|
|
6
6
|
*/
|
|
@@ -24,4 +24,4 @@ export const setPropObject = (
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
}
|
package/space2.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { primitiveNodeTypes, specialNodeTypes, SuspendProps } from "@lukekaalim/act";
|
|
2
|
+
import { Commit2, CommitID, CommitRef2, CommitTree2, Delta, ReconcilerEventBus } from "@lukekaalim/act-recon"
|
|
3
|
+
import { NodeBuilder } from "./builder";
|
|
4
|
+
|
|
5
|
+
type ParentSearchResult<TNode> = {
|
|
6
|
+
/**
|
|
7
|
+
* You might not have a parent - no
|
|
8
|
+
* commit means there are no Nodes above you - just Root.
|
|
9
|
+
*/
|
|
10
|
+
commit: Commit2 | null,
|
|
11
|
+
/**
|
|
12
|
+
* Your parent might be a valid node,
|
|
13
|
+
* or it might be "null"
|
|
14
|
+
*/
|
|
15
|
+
node: TNode | null,
|
|
16
|
+
|
|
17
|
+
attachable: boolean,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The RenderSpace class
|
|
22
|
+
*/
|
|
23
|
+
export class RenderSpace2<TNode, TRoot extends string | symbol> {
|
|
24
|
+
/** The CommitTree this render space is connected to */
|
|
25
|
+
tree: CommitTree2;
|
|
26
|
+
|
|
27
|
+
/** A reverse map to look up Commits given just the node they represent */
|
|
28
|
+
nodeByCommit: Map<CommitID, TNode> = new Map();
|
|
29
|
+
/** A reverse map to look up Commits given just the node they represent */
|
|
30
|
+
commitByNode: Map<TNode, Commit2> = new Map();
|
|
31
|
+
|
|
32
|
+
/** For a given CommitID, find it's closest "real" parent (a ancestor with a non-null TNode) */
|
|
33
|
+
parentByNode: Map<CommitID, Commit2> = new Map();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A set of all special Root element IDs in the tree.
|
|
37
|
+
* A Root element helps tell a renderer what kind of element
|
|
38
|
+
* to render, based on it's closest Root.
|
|
39
|
+
*/
|
|
40
|
+
roots: Map<CommitID, Commit2> = new Map();
|
|
41
|
+
bus: ReconcilerEventBus;
|
|
42
|
+
builder: NodeBuilder<TNode, TRoot>;
|
|
43
|
+
|
|
44
|
+
constructor(tree: CommitTree2, builder: NodeBuilder<TNode, TRoot>) {
|
|
45
|
+
this.tree = tree;
|
|
46
|
+
this.bus = {
|
|
47
|
+
render: (delta) => {
|
|
48
|
+
this.create(delta);
|
|
49
|
+
this.update(delta);
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
this.builder = builder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
findChildren(id: CommitID, ignoreFirst = false, ignoreSuspended = true): TNode[] {
|
|
56
|
+
const node = this.nodeByCommit.get(id);
|
|
57
|
+
if (node && !ignoreFirst)
|
|
58
|
+
return [node];
|
|
59
|
+
|
|
60
|
+
const commit = this.tree.commits.get(id);
|
|
61
|
+
if (!commit)
|
|
62
|
+
return [];
|
|
63
|
+
if (commit.element.type === primitiveNodeTypes.null)
|
|
64
|
+
return [];
|
|
65
|
+
// suspended nodes don't count as children
|
|
66
|
+
if (ignoreSuspended && commit.isSuspended())
|
|
67
|
+
return [];
|
|
68
|
+
|
|
69
|
+
return commit.children.map(c => this.findChildren(c.id)).flat(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* "find parent" can return 3 types of results:
|
|
74
|
+
* - An ancestor has a node! we return that.
|
|
75
|
+
* - An ancestor is a "null" node - we return the special form of "commit but no node"
|
|
76
|
+
* - No ancestor has a node - you might be near the root
|
|
77
|
+
* @param ref
|
|
78
|
+
* @returns
|
|
79
|
+
*/
|
|
80
|
+
findParent(ref: CommitRef2): ParentSearchResult<TNode> {
|
|
81
|
+
let ancestor: CommitRef2 | null = ref;
|
|
82
|
+
let attachable = true;
|
|
83
|
+
|
|
84
|
+
while (ancestor) {
|
|
85
|
+
if (ancestor.id !== ref.id) {
|
|
86
|
+
const commit = this.tree.commits.get(ancestor.id) || null;
|
|
87
|
+
|
|
88
|
+
// Early exit out of parent lookup if someone on the path is null;
|
|
89
|
+
if (commit && commit.element.type === primitiveNodeTypes.null)
|
|
90
|
+
return { commit, node: null, attachable: false };
|
|
91
|
+
|
|
92
|
+
// maybe a bad idea... we'll see
|
|
93
|
+
if (commit && commit.isSuspended())
|
|
94
|
+
attachable = false;
|
|
95
|
+
|
|
96
|
+
const node = this.nodeByCommit.get(ancestor.id);
|
|
97
|
+
// If you find an element with a node
|
|
98
|
+
if (node)
|
|
99
|
+
return { commit, node, attachable }
|
|
100
|
+
}
|
|
101
|
+
ancestor = ancestor.parent;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// this element has no "node" parents - it is probably a "root" commit
|
|
105
|
+
return { commit: null, node: null, attachable };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
findRoot(ref: CommitRef2) {
|
|
109
|
+
let ancestor: CommitRef2 | null = ref;
|
|
110
|
+
|
|
111
|
+
while (ancestor) {
|
|
112
|
+
const root = this.roots.get(ancestor.id);
|
|
113
|
+
if (root)
|
|
114
|
+
return root;
|
|
115
|
+
ancestor = ancestor.parent;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
newNodes: Set<[Commit2, TNode]> = new Set();
|
|
122
|
+
needsReorder: Set<CommitID> = new Set();
|
|
123
|
+
|
|
124
|
+
create(deltas: Delta) {
|
|
125
|
+
this.newNodes.clear();
|
|
126
|
+
this.needsReorder.clear();
|
|
127
|
+
|
|
128
|
+
for (const next of deltas.fresh.values()) {
|
|
129
|
+
if (next.element.type === specialNodeTypes.render) {
|
|
130
|
+
// add render boundary
|
|
131
|
+
this.roots.set(next.ref.id, next);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const root = this.findRoot(next.ref);
|
|
135
|
+
if (!root)
|
|
136
|
+
continue;
|
|
137
|
+
|
|
138
|
+
const rootType = root.element.props['type'] as TRoot;
|
|
139
|
+
|
|
140
|
+
// test to see if this element
|
|
141
|
+
// belongs to this
|
|
142
|
+
if (this.builder.roots.has(rootType) ) {
|
|
143
|
+
// Try to create a <T> for every new commit
|
|
144
|
+
const node = this.builder.create(next.element, rootType);
|
|
145
|
+
// Not all commits have a corresponding node
|
|
146
|
+
if (node) {
|
|
147
|
+
this.newNodes.add([next, node]);
|
|
148
|
+
this.nodeByCommit.set(next.ref.id, node);
|
|
149
|
+
this.commitByNode.set(node, next);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
update(deltas: Delta) {
|
|
156
|
+
const {
|
|
157
|
+
link,
|
|
158
|
+
unlink,
|
|
159
|
+
linkRoot,
|
|
160
|
+
sort,
|
|
161
|
+
update,
|
|
162
|
+
destroy,
|
|
163
|
+
unlinkRoot,
|
|
164
|
+
suspend = unlink,
|
|
165
|
+
unsuspend = link
|
|
166
|
+
} = this.builder;
|
|
167
|
+
|
|
168
|
+
if (link || sort) {
|
|
169
|
+
// Loop through newly created nodes
|
|
170
|
+
for (const [next, node] of this.newNodes) {
|
|
171
|
+
const result = this.findParent(next.ref);
|
|
172
|
+
|
|
173
|
+
if (next.element.type === specialNodeTypes.suspend)
|
|
174
|
+
console.log(`Creating suspense node`, result)
|
|
175
|
+
|
|
176
|
+
if (result.commit && result.node && result.attachable) {
|
|
177
|
+
this.needsReorder.add(result.commit.ref.id)
|
|
178
|
+
|
|
179
|
+
if (link)
|
|
180
|
+
link(node, result.node);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (linkRoot && !result.commit && result.attachable)
|
|
184
|
+
linkRoot(node);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (update) {
|
|
189
|
+
for (const { prev, next, moved } of deltas.changed.values()) {
|
|
190
|
+
|
|
191
|
+
// suspense code
|
|
192
|
+
if (next.element.type === specialNodeTypes.suspend) {
|
|
193
|
+
const result = this.findParent(next.ref);
|
|
194
|
+
|
|
195
|
+
const wasSuspended = !!prev.props.suspended;
|
|
196
|
+
const isSuspended = !!next.element.props.suspended;
|
|
197
|
+
|
|
198
|
+
const suspenseChanged = wasSuspended !== isSuspended;
|
|
199
|
+
if (suspenseChanged && result.commit && result.node) {
|
|
200
|
+
this.needsReorder.add(result.commit.ref.id);
|
|
201
|
+
|
|
202
|
+
const children = this.findChildren(next.ref.id, true, false);
|
|
203
|
+
for (const child of children) {
|
|
204
|
+
if (isSuspended && suspend) {
|
|
205
|
+
suspend(child, result.node);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!isSuspended && unsuspend) {
|
|
209
|
+
unsuspend(child, result.node);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const node = this.nodeByCommit.get(next.ref.id);
|
|
217
|
+
if (!node)
|
|
218
|
+
continue;
|
|
219
|
+
|
|
220
|
+
update(node, next.element, prev);
|
|
221
|
+
|
|
222
|
+
if (moved) {
|
|
223
|
+
const result = this.findParent(next.ref);
|
|
224
|
+
|
|
225
|
+
if (result.commit) {
|
|
226
|
+
this.needsReorder.add(result.commit.ref.id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
for (const next of deltas.fresh.values()) {
|
|
231
|
+
const node = this.nodeByCommit.get(next.ref.id);
|
|
232
|
+
if (node)
|
|
233
|
+
update(node, next.element, null);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const prev of deltas.removed.values()) {
|
|
237
|
+
const node = this.nodeByCommit.get(prev.ref.id);
|
|
238
|
+
if (node) {
|
|
239
|
+
this.nodeByCommit.delete(prev.ref.id);
|
|
240
|
+
const parent = this.findParent(prev.ref);
|
|
241
|
+
if (parent.commit && parent.node)
|
|
242
|
+
this.needsReorder.add(parent.commit.ref.id)
|
|
243
|
+
|
|
244
|
+
this.commitByNode.delete(node);
|
|
245
|
+
if (unlink && parent.node)
|
|
246
|
+
unlink(node, parent.node);
|
|
247
|
+
if (unlinkRoot && !parent.commit)
|
|
248
|
+
unlinkRoot(node)
|
|
249
|
+
if (destroy)
|
|
250
|
+
destroy(node);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (sort) {
|
|
255
|
+
for (const id of this.needsReorder) {
|
|
256
|
+
const node = this.nodeByCommit.get(id);
|
|
257
|
+
if (node) {
|
|
258
|
+
const children = this.findChildren(id, true, true);
|
|
259
|
+
sort(node, children);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
package/render.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { CommitTree, createReconciler, Scheduler, WorkThread } from "@lukekaalim/act-recon";
|
|
2
|
-
import { RenderSpace } from "./space";
|
|
3
|
-
import * as act from '@lukekaalim/act';
|
|
4
|
-
|
|
5
|
-
export type RenderFunction<T> = (node: act.Node, root: T) => { stop: () => void }
|
|
6
|
-
|
|
7
|
-
export const createRenderFunction = <T>(
|
|
8
|
-
scheduler: Scheduler,
|
|
9
|
-
createSpace: (tree: CommitTree, root: T) => RenderSpace
|
|
10
|
-
): RenderFunction<T> => {
|
|
11
|
-
|
|
12
|
-
const render: RenderFunction<T> = (node: act.Node, root: T) => {
|
|
13
|
-
const onThreadComplete = (thread: WorkThread) => {
|
|
14
|
-
space.create(thread.deltas).configure();
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const reconciler = createReconciler(scheduler);
|
|
18
|
-
const threadCompleteSub = reconciler.subscribe(event => {
|
|
19
|
-
switch (event.type) {
|
|
20
|
-
case 'thread:complete':
|
|
21
|
-
return onThreadComplete(event.thread);
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
const space = createSpace(reconciler.tree, root);
|
|
26
|
-
|
|
27
|
-
reconciler.mount(node);
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
stop() {
|
|
31
|
-
threadCompleteSub.cancel();
|
|
32
|
-
},
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return render;
|
|
37
|
-
}
|
package/space.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import * as act from '@lukekaalim/act';
|
|
2
|
-
import * as recon from '@lukekaalim/act-recon';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A "RenderSpace" is a service that manages
|
|
6
|
-
* a subtree of a particular type (T). Its broken
|
|
7
|
-
* down into two phases, "create" & "configure".
|
|
8
|
-
*
|
|
9
|
-
* This needs to be done in two phases, because
|
|
10
|
-
* often configuring elements requires references to
|
|
11
|
-
* children and parents, and if they haven't been
|
|
12
|
-
* created yet (or if the order of their creation is
|
|
13
|
-
* uncertain), then these functions cant act properly.
|
|
14
|
-
*
|
|
15
|
-
* #### Create
|
|
16
|
-
* The create phase is responsible for the assignment
|
|
17
|
-
* of CommitIds to T.
|
|
18
|
-
*
|
|
19
|
-
* #### Configure
|
|
20
|
-
* This is where props are assigned, and children +
|
|
21
|
-
* heirarchial elements can be setup.
|
|
22
|
-
*/
|
|
23
|
-
export type RenderSpace = {
|
|
24
|
-
create(deltas: recon.DeltaSet): { configure: () => unknown },
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export type SimpleRenderSpaceArgs<T, R extends string | Symbol> = {
|
|
28
|
-
rootTypes: Set<R>,
|
|
29
|
-
|
|
30
|
-
create: (element: act.Element, rootType: R) => null | T,
|
|
31
|
-
link?: (el: T, parent: null | T) => unknown,
|
|
32
|
-
unlink?: (el: T, parent: null | T) => unknown,
|
|
33
|
-
|
|
34
|
-
sort?: (el: T, children: readonly T[]) => unknown,
|
|
35
|
-
update?: (el: T, next: act.Element, prev: null | act.Element) => unknown,
|
|
36
|
-
destroy?: (el: T) => unknown,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type NodeRef<T> = {
|
|
40
|
-
id: recon.CommitID,
|
|
41
|
-
node: T,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* A "simple" render space is just a conventional
|
|
46
|
-
* render space where we keep track of each commit
|
|
47
|
-
* in a map - and take a "SimpleRenderSpaceArgs"
|
|
48
|
-
* args object that tells us how we make, link,
|
|
49
|
-
* sort, and update our nodes.
|
|
50
|
-
*
|
|
51
|
-
* @param tree
|
|
52
|
-
* @param args
|
|
53
|
-
* @param nodeByCommit
|
|
54
|
-
* @returns
|
|
55
|
-
*/
|
|
56
|
-
export const createSimpleRenderSpace = <T, R extends string | Symbol>(
|
|
57
|
-
tree: recon.CommitTree,
|
|
58
|
-
args: SimpleRenderSpaceArgs<T, R>,
|
|
59
|
-
nodeByCommit: Map<recon.CommitID, T | null> = new Map(),
|
|
60
|
-
): RenderSpace => {
|
|
61
|
-
const commitByNode = new Map<T, recon.CommitID>();
|
|
62
|
-
|
|
63
|
-
const rootIds = new Set<recon.CommitID>();
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Find all the nodes that belong children (in commit order!)
|
|
67
|
-
* for a particular commit.
|
|
68
|
-
*/
|
|
69
|
-
const findChildren = (id: recon.CommitID, ignoreFirst = false): readonly T[] => {
|
|
70
|
-
const node = nodeByCommit.get(id);
|
|
71
|
-
if (node && !ignoreFirst)
|
|
72
|
-
return [node];
|
|
73
|
-
const commit = tree.commits.get(id);
|
|
74
|
-
if (!commit)
|
|
75
|
-
return [];
|
|
76
|
-
if (commit.element.type === act.primitiveNodeTypes.null)
|
|
77
|
-
return [];
|
|
78
|
-
return commit.children.map(c => findChildren(c.id)).flat(1);
|
|
79
|
-
};
|
|
80
|
-
const findParent = (ref: recon.CommitRef): null | NodeRef<T | null> => {
|
|
81
|
-
for (let i = 1; i < ref.path.length; i++) {
|
|
82
|
-
const id = ref.path[ref.path.length - i - 1];
|
|
83
|
-
|
|
84
|
-
const commit = tree.commits.get(id) as recon.Commit;
|
|
85
|
-
// Early exit out of parent lookup if someone on the path is null;
|
|
86
|
-
if (commit.element.type === act.primitiveNodeTypes.null)
|
|
87
|
-
return { id, node: null };
|
|
88
|
-
|
|
89
|
-
const node = nodeByCommit.get(id);
|
|
90
|
-
// If you find an element with a node
|
|
91
|
-
if (node)
|
|
92
|
-
return { id, node }
|
|
93
|
-
}
|
|
94
|
-
// this element has no "node" parents - it is probably a "root" commit
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
const findRootId = (ref: recon.CommitRef) => {
|
|
98
|
-
for (let i = ref.path.length - 1; i >= 0; i--) {
|
|
99
|
-
const id = ref.path[i];
|
|
100
|
-
if (rootIds.has(id))
|
|
101
|
-
return id;
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const space: RenderSpace = {
|
|
107
|
-
create(deltas) {
|
|
108
|
-
const newNodes: Set<{ delta: recon.CreateDelta, node: T }> = new Set();
|
|
109
|
-
const needsReorder = new Set<recon.CommitID>();
|
|
110
|
-
|
|
111
|
-
for (const delta of deltas.created) {
|
|
112
|
-
if (delta.next.element.type === act.renderNodeType) {
|
|
113
|
-
// add render boundary
|
|
114
|
-
rootIds.add(delta.ref.id);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
const rootId = findRootId(delta.ref);
|
|
118
|
-
const root = rootId && tree.commits.get(rootId) || null;
|
|
119
|
-
if (!root)
|
|
120
|
-
continue;
|
|
121
|
-
|
|
122
|
-
const rootType = root.element.props['type'] as R;
|
|
123
|
-
|
|
124
|
-
// test to see if this element
|
|
125
|
-
// belongs to this
|
|
126
|
-
if (args.rootTypes.has(rootType) ) {
|
|
127
|
-
// Try to create a <T> for every new commit
|
|
128
|
-
const node = args.create(delta.next.element, rootType);
|
|
129
|
-
// Not all commits have a corresponding node
|
|
130
|
-
if (node) {
|
|
131
|
-
newNodes.add({ node, delta });
|
|
132
|
-
nodeByCommit.set(delta.ref.id, node);
|
|
133
|
-
commitByNode.set(node, delta.ref.id)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
configure() {
|
|
140
|
-
if (args.link || args.sort) {
|
|
141
|
-
// Loop through newly created nodes
|
|
142
|
-
for (const { delta, node } of newNodes) {
|
|
143
|
-
const parent = findParent(delta.ref);
|
|
144
|
-
const parentNode = parent && parent.node;
|
|
145
|
-
if (parentNode) {
|
|
146
|
-
needsReorder.add(parent.id)
|
|
147
|
-
}
|
|
148
|
-
if (args.link && (!parent || parentNode)) {
|
|
149
|
-
args.link(node, parentNode);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (args.update) {
|
|
155
|
-
for (const delta of deltas.updated) {
|
|
156
|
-
const node = nodeByCommit.get(delta.ref.id);
|
|
157
|
-
if (!node)
|
|
158
|
-
continue;
|
|
159
|
-
args.update(node, delta.next.element, delta.prev.element);
|
|
160
|
-
if (delta.moved) {
|
|
161
|
-
const parent = findParent(delta.ref);
|
|
162
|
-
const parentNode = parent && parent.node;
|
|
163
|
-
if (parentNode) {
|
|
164
|
-
needsReorder.add(parent.id)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
for (const delta of deltas.created) {
|
|
169
|
-
const node = nodeByCommit.get(delta.ref.id);
|
|
170
|
-
if (node)
|
|
171
|
-
args.update(node, delta.next.element, null);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
for (const delta of deltas.removed) {
|
|
175
|
-
const prevResult = nodeByCommit.get(delta.ref.id);
|
|
176
|
-
if (prevResult) {
|
|
177
|
-
nodeByCommit.delete(delta.ref.id);
|
|
178
|
-
const parentId = delta.ref.path.find(id => nodeByCommit.get(id));
|
|
179
|
-
if (parentId)
|
|
180
|
-
needsReorder.add(parentId)
|
|
181
|
-
|
|
182
|
-
commitByNode.delete(prevResult);
|
|
183
|
-
if (args.destroy)
|
|
184
|
-
args.destroy(prevResult);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (args.sort) {
|
|
189
|
-
for (const id of needsReorder) {
|
|
190
|
-
const node = nodeByCommit.get(id);
|
|
191
|
-
if (node) {
|
|
192
|
-
const children = findChildren(id, true);
|
|
193
|
-
args.sort(node, children);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return space;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
export const RenderSpace = {
|
|
209
|
-
combine(subspaces: RenderSpace[]) {
|
|
210
|
-
const create = (deltas: recon.DeltaSet) => {
|
|
211
|
-
const results = subspaces.map(ss => ss.create(deltas));
|
|
212
|
-
|
|
213
|
-
const configure = () => {
|
|
214
|
-
results.map(result => result.configure());
|
|
215
|
-
}
|
|
216
|
-
return { configure }
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
return { create };
|
|
220
|
-
},
|
|
221
|
-
simple: createSimpleRenderSpace
|
|
222
|
-
}
|