dialog-ide 0.5.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 +69 -0
- package/LICENSE +201 -0
- package/README.md +211 -0
- package/THIRD_PARTY_LICENSES.md +157 -0
- package/bin/darwin-arm64/aambundle +0 -0
- package/bin/darwin-arm64/dgdebug +0 -0
- package/bin/darwin-arm64/dialogc +0 -0
- package/bin/dialog-lib/stddebug.dg +112 -0
- package/bin/dialog-lib/stdlib.dg +6730 -0
- package/bin/dialog-lib/unit.dg +187 -0
- package/dist/cli/commands/run-skein.d.ts +17 -0
- package/dist/cli/commands/run-skein.d.ts.map +1 -0
- package/dist/cli/commands/run-skein.js +105 -0
- package/dist/cli/commands/run-skein.js.map +1 -0
- package/dist/cli/commands/sources.d.ts +12 -0
- package/dist/cli/commands/sources.d.ts.map +1 -0
- package/dist/cli/commands/sources.js +42 -0
- package/dist/cli/commands/sources.js.map +1 -0
- package/dist/cli/commands/test.d.ts +9 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/test.js +41 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/context.d.ts +9 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +78 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +32 -0
- package/dist/cli.js.map +1 -0
- package/dist/dialog-cover-resize.d.ts +2 -0
- package/dist/dialog-cover-resize.d.ts.map +1 -0
- package/dist/dialog-cover-resize.js +46 -0
- package/dist/dialog-cover-resize.js.map +1 -0
- package/dist/dialog-export.d.ts +23 -0
- package/dist/dialog-export.d.ts.map +1 -0
- package/dist/dialog-export.js +133 -0
- package/dist/dialog-export.js.map +1 -0
- package/dist/dialog-feelies.d.ts +4 -0
- package/dist/dialog-feelies.d.ts.map +1 -0
- package/dist/dialog-feelies.js +62 -0
- package/dist/dialog-feelies.js.map +1 -0
- package/dist/dialog-outline.d.ts +19 -0
- package/dist/dialog-outline.d.ts.map +1 -0
- package/dist/dialog-outline.js +122 -0
- package/dist/dialog-outline.js.map +1 -0
- package/dist/dialog-project-init.d.ts +5 -0
- package/dist/dialog-project-init.d.ts.map +1 -0
- package/dist/dialog-project-init.js +107 -0
- package/dist/dialog-project-init.js.map +1 -0
- package/dist/dialog-source-coverage.d.ts +9 -0
- package/dist/dialog-source-coverage.d.ts.map +1 -0
- package/dist/dialog-source-coverage.js +61 -0
- package/dist/dialog-source-coverage.js.map +1 -0
- package/dist/dialog-source-decoration-provider.d.ts +11 -0
- package/dist/dialog-source-decoration-provider.d.ts.map +1 -0
- package/dist/dialog-source-decoration-provider.js +79 -0
- package/dist/dialog-source-decoration-provider.js.map +1 -0
- package/dist/dialog-symbol-index.d.ts +19 -0
- package/dist/dialog-symbol-index.d.ts.map +1 -0
- package/dist/dialog-symbol-index.js +107 -0
- package/dist/dialog-symbol-index.js.map +1 -0
- package/dist/dialog-symbol-provider.d.ts +5 -0
- package/dist/dialog-symbol-provider.d.ts.map +1 -0
- package/dist/dialog-symbol-provider.js +56 -0
- package/dist/dialog-symbol-provider.js.map +1 -0
- package/dist/dialog-web-export.d.ts +25 -0
- package/dist/dialog-web-export.d.ts.map +1 -0
- package/dist/dialog-web-export.js +283 -0
- package/dist/dialog-web-export.js.map +1 -0
- package/dist/dialog-workspace-symbol-provider.d.ts +16 -0
- package/dist/dialog-workspace-symbol-provider.d.ts.map +1 -0
- package/dist/dialog-workspace-symbol-provider.js +107 -0
- package/dist/dialog-workspace-symbol-provider.js.map +1 -0
- package/dist/dialog-workspace-watcher.d.ts +22 -0
- package/dist/dialog-workspace-watcher.d.ts.map +1 -0
- package/dist/dialog-workspace-watcher.js +102 -0
- package/dist/dialog-workspace-watcher.js.map +1 -0
- package/dist/dialoged/skein/compile-error.d.ts +10 -0
- package/dist/dialoged/skein/compile-error.d.ts.map +1 -0
- package/dist/dialoged/skein/compile-error.js +28 -0
- package/dist/dialoged/skein/compile-error.js.map +1 -0
- package/dist/dialoged/skein/dynamic.d.ts +14 -0
- package/dist/dialoged/skein/dynamic.d.ts.map +1 -0
- package/dist/dialoged/skein/dynamic.js +176 -0
- package/dist/dialoged/skein/dynamic.js.map +1 -0
- package/dist/dialoged/skein/frotz-build.d.ts +10 -0
- package/dist/dialoged/skein/frotz-build.d.ts.map +1 -0
- package/dist/dialoged/skein/frotz-build.js +70 -0
- package/dist/dialoged/skein/frotz-build.js.map +1 -0
- package/dist/dialoged/skein/index.d.ts +15 -0
- package/dist/dialoged/skein/index.d.ts.map +1 -0
- package/dist/dialoged/skein/index.js +31 -0
- package/dist/dialoged/skein/index.js.map +1 -0
- package/dist/dialoged/skein/io.d.ts +15 -0
- package/dist/dialoged/skein/io.d.ts.map +1 -0
- package/dist/dialoged/skein/io.js +53 -0
- package/dist/dialoged/skein/io.js.map +1 -0
- package/dist/dialoged/skein/persistence.d.ts +13 -0
- package/dist/dialoged/skein/persistence.d.ts.map +1 -0
- package/dist/dialoged/skein/persistence.js +228 -0
- package/dist/dialoged/skein/persistence.js.map +1 -0
- package/dist/dialoged/skein/process.d.ts +38 -0
- package/dist/dialoged/skein/process.d.ts.map +1 -0
- package/dist/dialoged/skein/process.js +189 -0
- package/dist/dialoged/skein/process.js.map +1 -0
- package/dist/dialoged/skein/progress.d.ts +17 -0
- package/dist/dialoged/skein/progress.d.ts.map +1 -0
- package/dist/dialoged/skein/progress.js +9 -0
- package/dist/dialoged/skein/progress.js.map +1 -0
- package/dist/dialoged/skein/project.d.ts +44 -0
- package/dist/dialoged/skein/project.d.ts.map +1 -0
- package/dist/dialoged/skein/project.js +223 -0
- package/dist/dialoged/skein/project.js.map +1 -0
- package/dist/dialoged/skein/search.d.ts +15 -0
- package/dist/dialoged/skein/search.d.ts.map +1 -0
- package/dist/dialoged/skein/search.js +99 -0
- package/dist/dialoged/skein/search.js.map +1 -0
- package/dist/dialoged/skein/service.d.ts +91 -0
- package/dist/dialoged/skein/service.d.ts.map +1 -0
- package/dist/dialoged/skein/service.js +1097 -0
- package/dist/dialoged/skein/service.js.map +1 -0
- package/dist/dialoged/skein/session.d.ts +96 -0
- package/dist/dialoged/skein/session.d.ts.map +1 -0
- package/dist/dialoged/skein/session.js +690 -0
- package/dist/dialoged/skein/session.js.map +1 -0
- package/dist/dialoged/skein/syntax.d.ts +5 -0
- package/dist/dialoged/skein/syntax.d.ts.map +1 -0
- package/dist/dialoged/skein/syntax.js +150 -0
- package/dist/dialoged/skein/syntax.js.map +1 -0
- package/dist/dialoged/skein/trace.d.ts +31 -0
- package/dist/dialoged/skein/trace.d.ts.map +1 -0
- package/dist/dialoged/skein/trace.js +172 -0
- package/dist/dialoged/skein/trace.js.map +1 -0
- package/dist/dialoged/skein/tree.d.ts +114 -0
- package/dist/dialoged/skein/tree.d.ts.map +1 -0
- package/dist/dialoged/skein/tree.js +701 -0
- package/dist/dialoged/skein/tree.js.map +1 -0
- package/dist/dialoged/skein/ui/ansi.d.ts +4 -0
- package/dist/dialoged/skein/ui/ansi.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/ansi.js +215 -0
- package/dist/dialoged/skein/ui/ansi.js.map +1 -0
- package/dist/dialoged/skein/ui/diff.d.ts +7 -0
- package/dist/dialoged/skein/ui/diff.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/diff.js +17 -0
- package/dist/dialoged/skein/ui/diff.js.map +1 -0
- package/dist/dialoged/skein/ui/knot-menu.d.ts +5 -0
- package/dist/dialoged/skein/ui/knot-menu.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/knot-menu.js +89 -0
- package/dist/dialoged/skein/ui/knot-menu.js.map +1 -0
- package/dist/dialoged/skein/ui/marker-colors.d.ts +5 -0
- package/dist/dialoged/skein/ui/marker-colors.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/marker-colors.js +18 -0
- package/dist/dialoged/skein/ui/marker-colors.js.map +1 -0
- package/dist/dialoged/skein/ui/render.d.ts +13 -0
- package/dist/dialoged/skein/ui/render.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/render.js +332 -0
- package/dist/dialoged/skein/ui/render.js.map +1 -0
- package/dist/dialoged/skein/ui/traceRender.d.ts +11 -0
- package/dist/dialoged/skein/ui/traceRender.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/traceRender.js +139 -0
- package/dist/dialoged/skein/ui/traceRender.js.map +1 -0
- package/dist/dialoged/skein/ui/tree-pane.d.ts +3 -0
- package/dist/dialoged/skein/ui/tree-pane.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/tree-pane.js +135 -0
- package/dist/dialoged/skein/ui/tree-pane.js.map +1 -0
- package/dist/dialoged/skein/ui/webExportPage.d.ts +29 -0
- package/dist/dialoged/skein/ui/webExportPage.d.ts.map +1 -0
- package/dist/dialoged/skein/ui/webExportPage.js +72 -0
- package/dist/dialoged/skein/ui/webExportPage.js.map +1 -0
- package/dist/extension.d.ts +4 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +885 -0
- package/dist/extension.js.map +1 -0
- package/dist/session-runner.d.ts +23 -0
- package/dist/session-runner.d.ts.map +1 -0
- package/dist/session-runner.js +92 -0
- package/dist/session-runner.js.map +1 -0
- package/language-configuration.json +43 -0
- package/media/icon.png +0 -0
- package/media/icons/arrow-down-circle-solid.svg +3 -0
- package/media/icons/arrow-down-solid.svg +3 -0
- package/media/icons/arrow-path-solid.svg +3 -0
- package/media/icons/arrow-right-solid.svg +3 -0
- package/media/icons/arrow-up-on-square-stack-solid.svg +5 -0
- package/media/icons/arrow-up-solid.svg +3 -0
- package/media/icons/arrow-uturn-left.svg +3 -0
- package/media/icons/arrow-uturn-right.svg +3 -0
- package/media/icons/bookmark-solid.svg +3 -0
- package/media/icons/bug-ant-solid.svg +3 -0
- package/media/icons/check-badge-solid.svg +3 -0
- package/media/icons/check-circle-solid.svg +3 -0
- package/media/icons/chevron-double-down-solid.svg +4 -0
- package/media/icons/chevron-double-up-solid.svg +4 -0
- package/media/icons/command-line-solid.svg +3 -0
- package/media/icons/cpu-chip-solid.svg +4 -0
- package/media/icons/dg-file-dark.svg +6 -0
- package/media/icons/dg-file-light.svg +6 -0
- package/media/icons/ellipsis-vertical-solid.svg +3 -0
- package/media/icons/exclamation-triangle.svg +3 -0
- package/media/icons/folder-arrow-down-solid.svg +3 -0
- package/media/icons/globe-alt.svg +3 -0
- package/media/icons/lock-closed-solid.svg +3 -0
- package/media/icons/magnifying-glass-solid.svg +3 -0
- package/media/icons/pencil-solid.svg +4 -0
- package/media/icons/play-solid.svg +3 -0
- package/media/icons/plus-circle-solid.svg +3 -0
- package/media/icons/scissors-solid.svg +5 -0
- package/media/icons/tag-solid.svg +3 -0
- package/media/icons/trash-solid.svg +3 -0
- package/media/icons/x-circle-solid.svg +3 -0
- package/media/js/datastar.js +9 -0
- package/media/js/main.js +1109 -0
- package/media/js/skein-loader.js +10 -0
- package/media/js/trace-loader.js +4 -0
- package/media/js/trace.js +147 -0
- package/media/style.css +5783 -0
- package/package.json +231 -0
- package/resources/bundle/default-cover.png +0 -0
- package/resources/bundle/introduction-to-if.pdf +0 -0
- package/resources/bundle/play-if-card.pdf +1990 -1
- package/resources/bundle/play.css +432 -0
- package/resources/bundle/style.css +450 -0
- package/resources/dfrotz-skein-patch.dg +4 -0
- package/syntaxes/dialog.tmLanguage.json +490 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SkeinTree = exports.ALL_MARKERS = exports.KnotLockedError = exports.LabelConflictError = void 0;
|
|
4
|
+
exports.isEffectivelyLocked = isEffectivelyLocked;
|
|
5
|
+
exports.isValidMarker = isValidMarker;
|
|
6
|
+
const immutable_1 = require("immutable");
|
|
7
|
+
class LabelConflictError extends Error {
|
|
8
|
+
constructor(label) {
|
|
9
|
+
super(`Label "${label}" is already used by another knot.`);
|
|
10
|
+
this.name = 'LabelConflictError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.LabelConflictError = LabelConflictError;
|
|
14
|
+
class KnotLockedError extends Error {
|
|
15
|
+
constructor() {
|
|
16
|
+
super('Cannot delete: this knot or one of its descendants is locked or labeled.');
|
|
17
|
+
this.name = 'KnotLockedError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.KnotLockedError = KnotLockedError;
|
|
21
|
+
function isEffectivelyLocked(knot) {
|
|
22
|
+
return knot.locked || knot.label !== null;
|
|
23
|
+
}
|
|
24
|
+
function responsesMatch(blessed, fresh) {
|
|
25
|
+
return blessed !== null && blessed.text === fresh.text && blessed.inputType === fresh.inputType;
|
|
26
|
+
}
|
|
27
|
+
function responseAfterUpdate(existing, fresh) {
|
|
28
|
+
return responsesMatch(existing.response, fresh) ? null : fresh;
|
|
29
|
+
}
|
|
30
|
+
exports.ALL_MARKERS = [1, 2, 3, 4];
|
|
31
|
+
function isValidMarker(value) {
|
|
32
|
+
return value === 1 || value === 2 || value === 3 || value === 4;
|
|
33
|
+
}
|
|
34
|
+
class SkeinTree {
|
|
35
|
+
constructor(engine, seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates) {
|
|
36
|
+
this.engine = engine;
|
|
37
|
+
this.seed = seed;
|
|
38
|
+
this.knots = knots;
|
|
39
|
+
this.knotStates = knotStates;
|
|
40
|
+
this.activeKnotId = activeKnotId;
|
|
41
|
+
this.collapsedKnotIds = collapsedKnotIds;
|
|
42
|
+
this.dynamicStates = dynamicStates;
|
|
43
|
+
}
|
|
44
|
+
static newTree(engine, seed) {
|
|
45
|
+
const initialKnot = {
|
|
46
|
+
id: 0,
|
|
47
|
+
command: 'START',
|
|
48
|
+
response: {
|
|
49
|
+
text: 'Welcome to the game. > ',
|
|
50
|
+
inputType: 'line'
|
|
51
|
+
},
|
|
52
|
+
unblessedResponse: null,
|
|
53
|
+
parentId: null,
|
|
54
|
+
label: 'START',
|
|
55
|
+
locked: false,
|
|
56
|
+
marker: null
|
|
57
|
+
};
|
|
58
|
+
const initialState = {
|
|
59
|
+
state: 'valid',
|
|
60
|
+
treeState: 'valid',
|
|
61
|
+
selectedChild: null,
|
|
62
|
+
children: []
|
|
63
|
+
};
|
|
64
|
+
return new SkeinTree(engine, seed, (0, immutable_1.Map)().set(0, initialKnot), (0, immutable_1.Map)().set(0, initialState), 0, (0, immutable_1.Set)(), (0, immutable_1.Map)());
|
|
65
|
+
}
|
|
66
|
+
addChild(parentId, command, response) {
|
|
67
|
+
const newId = this.generateNextId();
|
|
68
|
+
const parentKnot = this.knots.get(parentId);
|
|
69
|
+
if (!parentKnot) {
|
|
70
|
+
throw new Error(`Parent knot ${parentId} not found`);
|
|
71
|
+
}
|
|
72
|
+
const newKnot = {
|
|
73
|
+
id: newId,
|
|
74
|
+
command,
|
|
75
|
+
response: null,
|
|
76
|
+
unblessedResponse: response,
|
|
77
|
+
parentId,
|
|
78
|
+
label: null,
|
|
79
|
+
locked: false,
|
|
80
|
+
marker: null
|
|
81
|
+
};
|
|
82
|
+
const newState = {
|
|
83
|
+
state: 'new',
|
|
84
|
+
treeState: 'new',
|
|
85
|
+
selectedChild: null,
|
|
86
|
+
children: []
|
|
87
|
+
};
|
|
88
|
+
const parentState = this.knotStates.get(parentId);
|
|
89
|
+
const updatedParentState = {
|
|
90
|
+
...parentState,
|
|
91
|
+
children: [...parentState.children, newId],
|
|
92
|
+
selectedChild: newId
|
|
93
|
+
};
|
|
94
|
+
const knots = this.knots.set(newId, newKnot);
|
|
95
|
+
let knotStates = this.knotStates
|
|
96
|
+
.set(newId, newState)
|
|
97
|
+
.set(parentId, updatedParentState);
|
|
98
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, parentId);
|
|
99
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
100
|
+
}
|
|
101
|
+
updateKnotCommandAndResponse(id, command, response) {
|
|
102
|
+
const knot = this.knots.get(id);
|
|
103
|
+
if (!knot) {
|
|
104
|
+
throw new Error(`Knot ${id} not found`);
|
|
105
|
+
}
|
|
106
|
+
const updatedKnot = {
|
|
107
|
+
...knot,
|
|
108
|
+
command,
|
|
109
|
+
unblessedResponse: responseAfterUpdate(knot, response)
|
|
110
|
+
};
|
|
111
|
+
const knots = this.knots.set(id, updatedKnot);
|
|
112
|
+
const knotStates = SkeinTree.propagateOwnStateChange(knots, this.knotStates, id, updatedKnot);
|
|
113
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
114
|
+
}
|
|
115
|
+
updateKnotResponse(id, response) {
|
|
116
|
+
const knot = this.knots.get(id);
|
|
117
|
+
if (!knot) {
|
|
118
|
+
throw new Error(`Knot ${id} not found`);
|
|
119
|
+
}
|
|
120
|
+
const updatedKnot = {
|
|
121
|
+
...knot,
|
|
122
|
+
unblessedResponse: responseAfterUpdate(knot, response)
|
|
123
|
+
};
|
|
124
|
+
const knots = this.knots.set(id, updatedKnot);
|
|
125
|
+
const knotStates = SkeinTree.propagateOwnStateChange(knots, this.knotStates, id, updatedKnot);
|
|
126
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
127
|
+
}
|
|
128
|
+
blessKnot(id) {
|
|
129
|
+
const knot = this.knots.get(id);
|
|
130
|
+
if (!knot) {
|
|
131
|
+
throw new Error(`Knot ${id} not found`);
|
|
132
|
+
}
|
|
133
|
+
if (!knot.unblessedResponse) {
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
const updatedKnot = {
|
|
137
|
+
...knot,
|
|
138
|
+
response: knot.unblessedResponse,
|
|
139
|
+
unblessedResponse: null
|
|
140
|
+
};
|
|
141
|
+
const knots = this.knots.set(id, updatedKnot);
|
|
142
|
+
const knotStates = SkeinTree.propagateOwnStateChange(knots, this.knotStates, id, updatedKnot);
|
|
143
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
144
|
+
}
|
|
145
|
+
blessTranscript(id) {
|
|
146
|
+
if (!this.knots.get(id)) {
|
|
147
|
+
throw new Error(`Knot ${id} not found`);
|
|
148
|
+
}
|
|
149
|
+
let tree = this;
|
|
150
|
+
for (const pathId of this.pathFromRoot(id)) {
|
|
151
|
+
const knot = tree.knots.get(pathId);
|
|
152
|
+
if (SkeinTree.computeKnotState(knot) !== 'valid') {
|
|
153
|
+
tree = tree.blessKnot(pathId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return tree;
|
|
157
|
+
}
|
|
158
|
+
deleteKnot(id) {
|
|
159
|
+
const knot = this.knots.get(id);
|
|
160
|
+
if (!knot) {
|
|
161
|
+
throw new Error(`Knot ${id} not found`);
|
|
162
|
+
}
|
|
163
|
+
const subtreeIds = this.collectSubtreeIds(id);
|
|
164
|
+
if (subtreeIds.some((subtreeId) => isEffectivelyLocked(this.knots.get(subtreeId)))) {
|
|
165
|
+
throw new KnotLockedError();
|
|
166
|
+
}
|
|
167
|
+
let knots = this.knots;
|
|
168
|
+
let knotStates = this.knotStates;
|
|
169
|
+
let collapsedKnotIds = this.collapsedKnotIds;
|
|
170
|
+
let dynamicStates = this.dynamicStates;
|
|
171
|
+
for (const descendantId of subtreeIds) {
|
|
172
|
+
knots = knots.delete(descendantId);
|
|
173
|
+
knotStates = knotStates.delete(descendantId);
|
|
174
|
+
collapsedKnotIds = collapsedKnotIds.remove(descendantId);
|
|
175
|
+
dynamicStates = dynamicStates.delete(descendantId);
|
|
176
|
+
}
|
|
177
|
+
if (knot.parentId !== null) {
|
|
178
|
+
knotStates = SkeinTree.removeChildFromParent(knotStates, knot.parentId, id);
|
|
179
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, knot.parentId);
|
|
180
|
+
}
|
|
181
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, collapsedKnotIds, dynamicStates);
|
|
182
|
+
}
|
|
183
|
+
spliceKnot(id) {
|
|
184
|
+
const knot = this.knots.get(id);
|
|
185
|
+
if (!knot) {
|
|
186
|
+
throw new Error(`Knot ${id} not found`);
|
|
187
|
+
}
|
|
188
|
+
const knotState = this.knotStates.get(id);
|
|
189
|
+
const childIds = knotState.children;
|
|
190
|
+
let knots = this.knots.delete(id);
|
|
191
|
+
let knotStates = this.knotStates.delete(id);
|
|
192
|
+
for (const childId of childIds) {
|
|
193
|
+
const child = knots.get(childId);
|
|
194
|
+
knots = knots.set(childId, { ...child, parentId: knot.parentId });
|
|
195
|
+
}
|
|
196
|
+
if (knot.parentId !== null) {
|
|
197
|
+
const parentState = knotStates.get(knot.parentId);
|
|
198
|
+
const index = parentState.children.indexOf(id);
|
|
199
|
+
const children = index === -1
|
|
200
|
+
? [...parentState.children, ...childIds]
|
|
201
|
+
: [...parentState.children.slice(0, index), ...childIds, ...parentState.children.slice(index + 1)];
|
|
202
|
+
knotStates = knotStates.set(knot.parentId, {
|
|
203
|
+
...parentState,
|
|
204
|
+
children,
|
|
205
|
+
selectedChild: parentState.selectedChild === id
|
|
206
|
+
? (childIds.length > 0 ? childIds[0] : null)
|
|
207
|
+
: parentState.selectedChild
|
|
208
|
+
});
|
|
209
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, knot.parentId);
|
|
210
|
+
}
|
|
211
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, this.activeKnotId, this.collapsedKnotIds.remove(id), this.dynamicStates.delete(id));
|
|
212
|
+
}
|
|
213
|
+
nextId() {
|
|
214
|
+
return this.generateNextId();
|
|
215
|
+
}
|
|
216
|
+
insertParent(id, command, response) {
|
|
217
|
+
const knot = this.knots.get(id);
|
|
218
|
+
if (!knot) {
|
|
219
|
+
throw new Error(`Knot ${id} not found`);
|
|
220
|
+
}
|
|
221
|
+
const newId = this.generateNextId();
|
|
222
|
+
const oldParentId = knot.parentId;
|
|
223
|
+
const newParentKnot = {
|
|
224
|
+
id: newId,
|
|
225
|
+
command,
|
|
226
|
+
response: null,
|
|
227
|
+
unblessedResponse: response,
|
|
228
|
+
parentId: oldParentId,
|
|
229
|
+
label: null,
|
|
230
|
+
locked: false,
|
|
231
|
+
marker: null
|
|
232
|
+
};
|
|
233
|
+
const newParentState = {
|
|
234
|
+
state: 'new',
|
|
235
|
+
treeState: 'new',
|
|
236
|
+
selectedChild: id,
|
|
237
|
+
children: [id]
|
|
238
|
+
};
|
|
239
|
+
const updatedKnot = { ...knot, parentId: newId };
|
|
240
|
+
let knots = this.knots.set(newId, newParentKnot).set(id, updatedKnot);
|
|
241
|
+
let knotStates = this.knotStates.set(newId, newParentState);
|
|
242
|
+
let collapsedKnotIds = this.collapsedKnotIds;
|
|
243
|
+
let dynamicStates = this.dynamicStates;
|
|
244
|
+
let activeKnotId = this.activeKnotId;
|
|
245
|
+
if (oldParentId !== null) {
|
|
246
|
+
const grandparentState = knotStates.get(oldParentId);
|
|
247
|
+
const index = grandparentState.children.indexOf(id);
|
|
248
|
+
const children = index === -1
|
|
249
|
+
? grandparentState.children
|
|
250
|
+
: [...grandparentState.children.slice(0, index), newId, ...grandparentState.children.slice(index + 1)];
|
|
251
|
+
knotStates = knotStates.set(oldParentId, {
|
|
252
|
+
...grandparentState,
|
|
253
|
+
children,
|
|
254
|
+
selectedChild: grandparentState.selectedChild === id ? newId : grandparentState.selectedChild
|
|
255
|
+
});
|
|
256
|
+
const otherId = children.find((cid) => cid !== newId && knots.get(cid).command === command) ?? null;
|
|
257
|
+
if (otherId !== null) {
|
|
258
|
+
const merged = new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates)
|
|
259
|
+
.mergeSiblingInto(newId, otherId);
|
|
260
|
+
knots = merged.knots;
|
|
261
|
+
knotStates = merged.knotStates;
|
|
262
|
+
collapsedKnotIds = merged.collapsedKnotIds;
|
|
263
|
+
dynamicStates = merged.dynamicStates;
|
|
264
|
+
activeKnotId = merged.activeKnotId;
|
|
265
|
+
knotStates = SkeinTree.absorbSiblingInParent(knotStates, oldParentId, newId, otherId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, newId);
|
|
269
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates);
|
|
270
|
+
}
|
|
271
|
+
setLabel(id, label) {
|
|
272
|
+
const knot = this.knots.get(id);
|
|
273
|
+
if (!knot) {
|
|
274
|
+
throw new Error(`Knot ${id} not found`);
|
|
275
|
+
}
|
|
276
|
+
if (label !== null) {
|
|
277
|
+
const existing = this.findByLabel(label);
|
|
278
|
+
if (existing && existing.id !== id) {
|
|
279
|
+
throw new LabelConflictError(label);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const updatedKnot = {
|
|
283
|
+
...knot,
|
|
284
|
+
label
|
|
285
|
+
};
|
|
286
|
+
return new SkeinTree(this.engine, this.seed, this.knots.set(id, updatedKnot), this.knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
287
|
+
}
|
|
288
|
+
findByLabel(label) {
|
|
289
|
+
for (const knot of this.knots.valueSeq()) {
|
|
290
|
+
if (knot.label === label) {
|
|
291
|
+
return knot;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
setMarker(id, marker) {
|
|
297
|
+
const knot = this.knots.get(id);
|
|
298
|
+
if (!knot) {
|
|
299
|
+
throw new Error(`Knot ${id} not found`);
|
|
300
|
+
}
|
|
301
|
+
return new SkeinTree(this.engine, this.seed, this.knots.set(id, { ...knot, marker }), this.knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
302
|
+
}
|
|
303
|
+
renameCommand(id, command) {
|
|
304
|
+
const knot = this.knots.get(id);
|
|
305
|
+
if (!knot) {
|
|
306
|
+
throw new Error(`Knot ${id} not found`);
|
|
307
|
+
}
|
|
308
|
+
if (command === knot.command) {
|
|
309
|
+
return this;
|
|
310
|
+
}
|
|
311
|
+
let knots = this.knots.set(id, { ...knot, command });
|
|
312
|
+
let knotStates = this.knotStates;
|
|
313
|
+
let collapsedKnotIds = this.collapsedKnotIds;
|
|
314
|
+
let dynamicStates = this.dynamicStates;
|
|
315
|
+
let activeKnotId = this.activeKnotId;
|
|
316
|
+
if (knot.parentId !== null) {
|
|
317
|
+
const parentState = knotStates.get(knot.parentId);
|
|
318
|
+
const otherId = parentState.children.find((cid) => cid !== id && knots.get(cid).command === command) ?? null;
|
|
319
|
+
if (otherId !== null) {
|
|
320
|
+
const merged = new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates)
|
|
321
|
+
.mergeSiblingInto(id, otherId);
|
|
322
|
+
knots = merged.knots;
|
|
323
|
+
knotStates = merged.knotStates;
|
|
324
|
+
collapsedKnotIds = merged.collapsedKnotIds;
|
|
325
|
+
dynamicStates = merged.dynamicStates;
|
|
326
|
+
activeKnotId = merged.activeKnotId;
|
|
327
|
+
knotStates = SkeinTree.absorbSiblingInParent(knotStates, knot.parentId, id, otherId);
|
|
328
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, knot.parentId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates);
|
|
332
|
+
}
|
|
333
|
+
mergeSiblingInto(keepId, otherId) {
|
|
334
|
+
const keepKnot = this.knots.get(keepId);
|
|
335
|
+
const otherKnot = this.knots.get(otherId);
|
|
336
|
+
const otherState = this.knotStates.get(otherId);
|
|
337
|
+
let knots = this.knots.set(keepId, {
|
|
338
|
+
...keepKnot,
|
|
339
|
+
label: keepKnot.label ?? otherKnot.label,
|
|
340
|
+
locked: keepKnot.locked || otherKnot.locked,
|
|
341
|
+
marker: keepKnot.marker ?? otherKnot.marker
|
|
342
|
+
});
|
|
343
|
+
let knotStates = this.knotStates;
|
|
344
|
+
let collapsedKnotIds = this.collapsedKnotIds;
|
|
345
|
+
let dynamicStates = this.dynamicStates;
|
|
346
|
+
let activeKnotId = this.activeKnotId;
|
|
347
|
+
for (const otherChildId of otherState.children) {
|
|
348
|
+
const otherChildCommand = knots.get(otherChildId).command;
|
|
349
|
+
const keepState = knotStates.get(keepId);
|
|
350
|
+
const matchedKeepChildId = keepState.children.find((cid) => knots.get(cid).command === otherChildCommand) ?? null;
|
|
351
|
+
if (matchedKeepChildId !== null) {
|
|
352
|
+
const merged = new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates)
|
|
353
|
+
.mergeSiblingInto(matchedKeepChildId, otherChildId);
|
|
354
|
+
knots = merged.knots;
|
|
355
|
+
knotStates = merged.knotStates;
|
|
356
|
+
collapsedKnotIds = merged.collapsedKnotIds;
|
|
357
|
+
dynamicStates = merged.dynamicStates;
|
|
358
|
+
activeKnotId = merged.activeKnotId;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
knots = knots.set(otherChildId, { ...knots.get(otherChildId), parentId: keepId });
|
|
362
|
+
const ks = knotStates.get(keepId);
|
|
363
|
+
knotStates = knotStates.set(keepId, {
|
|
364
|
+
...ks,
|
|
365
|
+
children: [...ks.children, otherChildId],
|
|
366
|
+
selectedChild: ks.selectedChild ?? otherChildId
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
knots = knots.delete(otherId);
|
|
371
|
+
knotStates = knotStates.delete(otherId);
|
|
372
|
+
collapsedKnotIds = collapsedKnotIds.remove(otherId);
|
|
373
|
+
dynamicStates = dynamicStates.delete(otherId);
|
|
374
|
+
if (activeKnotId === otherId) {
|
|
375
|
+
activeKnotId = keepId;
|
|
376
|
+
}
|
|
377
|
+
const keepState = knotStates.get(keepId);
|
|
378
|
+
const treeState = keepState.children.reduce((acc, cid) => SkeinTree.maxStatus(acc, knotStates.get(cid).treeState), keepState.state);
|
|
379
|
+
knotStates = knotStates.set(keepId, { ...keepState, treeState });
|
|
380
|
+
return new SkeinTree(this.engine, this.seed, knots, knotStates, activeKnotId, collapsedKnotIds, dynamicStates);
|
|
381
|
+
}
|
|
382
|
+
static absorbSiblingInParent(knotStates, parentId, keepId, otherId) {
|
|
383
|
+
const parentState = knotStates.get(parentId);
|
|
384
|
+
const children = parentState.children.filter((cid) => cid !== otherId);
|
|
385
|
+
return knotStates.set(parentId, {
|
|
386
|
+
...parentState,
|
|
387
|
+
children,
|
|
388
|
+
selectedChild: parentState.selectedChild === otherId ? keepId : parentState.selectedChild
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
setLockStatus(id, locked) {
|
|
392
|
+
const knot = this.knots.get(id);
|
|
393
|
+
if (!knot) {
|
|
394
|
+
throw new Error(`Knot ${id} not found`);
|
|
395
|
+
}
|
|
396
|
+
const updatedKnot = {
|
|
397
|
+
...knot,
|
|
398
|
+
locked
|
|
399
|
+
};
|
|
400
|
+
return new SkeinTree(this.engine, this.seed, this.knots.set(id, updatedKnot), this.knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
401
|
+
}
|
|
402
|
+
getKnot(id) {
|
|
403
|
+
return this.knots.get(id) || null;
|
|
404
|
+
}
|
|
405
|
+
promptTypeAt(id) {
|
|
406
|
+
if (id === null) {
|
|
407
|
+
return 'line';
|
|
408
|
+
}
|
|
409
|
+
const knot = this.knots.get(id);
|
|
410
|
+
const response = knot?.response ?? knot?.unblessedResponse;
|
|
411
|
+
return response?.inputType ?? 'line';
|
|
412
|
+
}
|
|
413
|
+
updateDynamicState(id, state) {
|
|
414
|
+
if (!this.knots.get(id)) {
|
|
415
|
+
throw new Error(`Knot ${id} not found`);
|
|
416
|
+
}
|
|
417
|
+
return new SkeinTree(this.engine, this.seed, this.knots, this.knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates.set(id, state));
|
|
418
|
+
}
|
|
419
|
+
getDynamicState(id) {
|
|
420
|
+
return this.dynamicStates.get(id) ?? null;
|
|
421
|
+
}
|
|
422
|
+
findChildId(parentId, command) {
|
|
423
|
+
const parentState = this.knotStates.get(parentId);
|
|
424
|
+
if (!parentState) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
for (const childId of parentState.children) {
|
|
428
|
+
if (this.knots.get(childId)?.command === command) {
|
|
429
|
+
return childId;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
sortedChildren(parentId) {
|
|
435
|
+
const state = this.knotStates.get(parentId);
|
|
436
|
+
if (!state) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
return state.children.map((id) => this.getDerivedKnot(id)).sort((a, b) => a.command.localeCompare(b.command));
|
|
440
|
+
}
|
|
441
|
+
commandPath(id) {
|
|
442
|
+
if (!this.knots.get(id)) {
|
|
443
|
+
throw new Error(`Knot ${id} not found`);
|
|
444
|
+
}
|
|
445
|
+
return this.pathFromRoot(id)
|
|
446
|
+
.filter((pathId) => pathId !== 0)
|
|
447
|
+
.map((pathId) => ({ id: pathId, command: this.knots.get(pathId).command }));
|
|
448
|
+
}
|
|
449
|
+
getAllKnots() {
|
|
450
|
+
return this.knots.valueSeq().toArray();
|
|
451
|
+
}
|
|
452
|
+
getDerivedKnot(id) {
|
|
453
|
+
const knot = this.knots.get(id);
|
|
454
|
+
if (!knot) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const state = this.knotStates.get(id);
|
|
458
|
+
return {
|
|
459
|
+
id: knot.id,
|
|
460
|
+
command: knot.command,
|
|
461
|
+
response: knot.response ? knot.response.text : '',
|
|
462
|
+
unblessedResponse: knot.unblessedResponse ? knot.unblessedResponse.text : null,
|
|
463
|
+
state: state ? state.state : SkeinTree.computeKnotState(knot),
|
|
464
|
+
treeState: state ? state.treeState : SkeinTree.computeKnotState(knot),
|
|
465
|
+
parentId: knot.parentId,
|
|
466
|
+
children: state ? state.children : [],
|
|
467
|
+
selectedChild: state ? state.selectedChild : null,
|
|
468
|
+
inputType: knot.response ? knot.response.inputType : 'line',
|
|
469
|
+
label: knot.label,
|
|
470
|
+
locked: knot.locked,
|
|
471
|
+
marker: knot.marker,
|
|
472
|
+
collapsed: this.collapsedKnotIds.has(id)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
visibleKnotIdsForMarkerFilter(marker) {
|
|
476
|
+
const visible = new globalThis.Set();
|
|
477
|
+
const visit = (id) => {
|
|
478
|
+
const knot = this.knots.get(id);
|
|
479
|
+
const state = this.knotStates.get(id);
|
|
480
|
+
const anyChildMatches = state.children.map(visit).some(Boolean);
|
|
481
|
+
const selfMatches = knot.marker === marker;
|
|
482
|
+
if (anyChildMatches || selfMatches) {
|
|
483
|
+
visible.add(id);
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
};
|
|
488
|
+
visit(0);
|
|
489
|
+
return visible;
|
|
490
|
+
}
|
|
491
|
+
static computeKnotState(knot) {
|
|
492
|
+
if (knot.response) {
|
|
493
|
+
if (knot.unblessedResponse && knot.response.text !== knot.unblessedResponse.text) {
|
|
494
|
+
return 'error';
|
|
495
|
+
}
|
|
496
|
+
return 'valid';
|
|
497
|
+
}
|
|
498
|
+
return 'new';
|
|
499
|
+
}
|
|
500
|
+
static maxStatus(a, b) {
|
|
501
|
+
if (a === 'error' || b === 'error') {
|
|
502
|
+
return 'error';
|
|
503
|
+
}
|
|
504
|
+
if (a === 'new' || b === 'new') {
|
|
505
|
+
return 'new';
|
|
506
|
+
}
|
|
507
|
+
return 'valid';
|
|
508
|
+
}
|
|
509
|
+
static propagateOwnStateChange(knots, knotStates, id, updatedKnot) {
|
|
510
|
+
const state = knotStates.get(id);
|
|
511
|
+
const withUpdatedState = knotStates.set(id, { ...state, state: SkeinTree.computeKnotState(updatedKnot) });
|
|
512
|
+
return SkeinTree.propagateTreeState(knots, withUpdatedState, id);
|
|
513
|
+
}
|
|
514
|
+
static propagateTreeState(knots, knotStates, startId) {
|
|
515
|
+
let states = knotStates;
|
|
516
|
+
let currentId = startId;
|
|
517
|
+
while (currentId !== null) {
|
|
518
|
+
const current = states.get(currentId);
|
|
519
|
+
const computed = current.children.reduce((acc, childId) => SkeinTree.maxStatus(acc, states.get(childId).treeState), current.state);
|
|
520
|
+
states = states.set(currentId, { ...current, treeState: computed });
|
|
521
|
+
currentId = knots.get(currentId).parentId;
|
|
522
|
+
}
|
|
523
|
+
return states;
|
|
524
|
+
}
|
|
525
|
+
collectSubtreeIds(id) {
|
|
526
|
+
const ids = [id];
|
|
527
|
+
const state = this.knotStates.get(id);
|
|
528
|
+
if (state) {
|
|
529
|
+
for (const childId of state.children) {
|
|
530
|
+
ids.push(...this.collectSubtreeIds(childId));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return ids;
|
|
534
|
+
}
|
|
535
|
+
pathFromRoot(id) {
|
|
536
|
+
const path = [];
|
|
537
|
+
let current = this.knots.get(id);
|
|
538
|
+
while (current) {
|
|
539
|
+
path.unshift(current.id);
|
|
540
|
+
current = current.parentId !== null ? this.knots.get(current.parentId) : undefined;
|
|
541
|
+
}
|
|
542
|
+
return path;
|
|
543
|
+
}
|
|
544
|
+
static removeChildFromParent(knotStates, parentId, childId) {
|
|
545
|
+
const parentState = knotStates.get(parentId);
|
|
546
|
+
if (!parentState) {
|
|
547
|
+
return knotStates;
|
|
548
|
+
}
|
|
549
|
+
const children = parentState.children.filter((existingId) => existingId !== childId);
|
|
550
|
+
return knotStates.set(parentId, {
|
|
551
|
+
...parentState,
|
|
552
|
+
children,
|
|
553
|
+
selectedChild: parentState.selectedChild === childId
|
|
554
|
+
? (children.length > 0 ? children[0] : null)
|
|
555
|
+
: parentState.selectedChild
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
static fromKnots(engine, seed, wireKnots) {
|
|
559
|
+
const sorted = [...wireKnots].sort((a, b) => a.id - b.id);
|
|
560
|
+
const childrenByParent = new globalThis.Map();
|
|
561
|
+
for (const knot of sorted) {
|
|
562
|
+
if (knot.parentId !== null) {
|
|
563
|
+
const children = childrenByParent.get(knot.parentId) ?? [];
|
|
564
|
+
children.push(knot.id);
|
|
565
|
+
childrenByParent.set(knot.parentId, children);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
let knots = (0, immutable_1.Map)();
|
|
569
|
+
let knotStates = (0, immutable_1.Map)();
|
|
570
|
+
for (const knot of sorted) {
|
|
571
|
+
const children = childrenByParent.get(knot.id) ?? [];
|
|
572
|
+
const knotState = SkeinTree.computeKnotState(knot);
|
|
573
|
+
knots = knots.set(knot.id, knot);
|
|
574
|
+
knotStates = knotStates.set(knot.id, {
|
|
575
|
+
state: knotState,
|
|
576
|
+
treeState: knotState,
|
|
577
|
+
selectedChild: children.length > 0 ? children[0] : null,
|
|
578
|
+
children
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
const leafIds = sorted.filter((knot) => !childrenByParent.has(knot.id)).map((knot) => knot.id);
|
|
582
|
+
for (const leafId of leafIds) {
|
|
583
|
+
knotStates = SkeinTree.propagateTreeState(knots, knotStates, leafId);
|
|
584
|
+
}
|
|
585
|
+
let activeKnotId = 0;
|
|
586
|
+
let cursor = 0;
|
|
587
|
+
while (cursor !== null) {
|
|
588
|
+
activeKnotId = cursor;
|
|
589
|
+
cursor = knotStates.get(cursor)?.selectedChild ?? null;
|
|
590
|
+
}
|
|
591
|
+
return new SkeinTree(engine, seed, knots, knotStates, activeKnotId, (0, immutable_1.Set)(), (0, immutable_1.Map)());
|
|
592
|
+
}
|
|
593
|
+
generateNextId() {
|
|
594
|
+
let maxId = -1;
|
|
595
|
+
this.knots.forEach((knot) => {
|
|
596
|
+
if (knot.id > maxId) {
|
|
597
|
+
maxId = knot.id;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
return maxId + 1;
|
|
601
|
+
}
|
|
602
|
+
getEngine() {
|
|
603
|
+
return this.engine;
|
|
604
|
+
}
|
|
605
|
+
getSeed() {
|
|
606
|
+
return this.seed;
|
|
607
|
+
}
|
|
608
|
+
getActiveKnotId() {
|
|
609
|
+
return this.activeKnotId;
|
|
610
|
+
}
|
|
611
|
+
getSelectedLeafId() {
|
|
612
|
+
let currentId = 0;
|
|
613
|
+
for (;;) {
|
|
614
|
+
const state = this.knotStates.get(currentId);
|
|
615
|
+
if (!state || state.selectedChild === null) {
|
|
616
|
+
return currentId;
|
|
617
|
+
}
|
|
618
|
+
currentId = state.selectedChild;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
knotIdsWithStatus(status) {
|
|
622
|
+
return this.knotStates
|
|
623
|
+
.filter((state) => state.state === status)
|
|
624
|
+
.keySeq()
|
|
625
|
+
.toArray()
|
|
626
|
+
.sort((a, b) => a - b);
|
|
627
|
+
}
|
|
628
|
+
getLeafIds() {
|
|
629
|
+
return this.knotStates
|
|
630
|
+
.filter((state) => state.children.length === 0)
|
|
631
|
+
.keySeq()
|
|
632
|
+
.toArray()
|
|
633
|
+
.sort((a, b) => a - b);
|
|
634
|
+
}
|
|
635
|
+
setActiveKnotId(id) {
|
|
636
|
+
return new SkeinTree(this.engine, this.seed, this.knots, this.knotStates, id, this.collapsedKnotIds, this.dynamicStates);
|
|
637
|
+
}
|
|
638
|
+
selectKnot(id) {
|
|
639
|
+
if (!this.knots.get(id)) {
|
|
640
|
+
throw new Error(`Knot ${id} not found`);
|
|
641
|
+
}
|
|
642
|
+
return this.selectPath(id).extendSelection(id).setActiveKnotId(id);
|
|
643
|
+
}
|
|
644
|
+
selectPath(id) {
|
|
645
|
+
const path = this.pathFromRoot(id);
|
|
646
|
+
let knotStates = this.knotStates;
|
|
647
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
648
|
+
const parentId = path[i];
|
|
649
|
+
const childId = path[i + 1];
|
|
650
|
+
const parentState = knotStates.get(parentId);
|
|
651
|
+
if (parentState.selectedChild !== childId) {
|
|
652
|
+
knotStates = knotStates.set(parentId, { ...parentState, selectedChild: childId });
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return new SkeinTree(this.engine, this.seed, this.knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
656
|
+
}
|
|
657
|
+
extendSelection(id) {
|
|
658
|
+
let knotStates = this.knotStates;
|
|
659
|
+
let currentId = id;
|
|
660
|
+
for (;;) {
|
|
661
|
+
const state = knotStates.get(currentId);
|
|
662
|
+
if (!state || state.children.length !== 1) {
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
const childId = state.children[0];
|
|
666
|
+
knotStates = knotStates.set(currentId, { ...state, selectedChild: childId });
|
|
667
|
+
currentId = childId;
|
|
668
|
+
}
|
|
669
|
+
return new SkeinTree(this.engine, this.seed, this.knots, knotStates, this.activeKnotId, this.collapsedKnotIds, this.dynamicStates);
|
|
670
|
+
}
|
|
671
|
+
selectForNewChild(id) {
|
|
672
|
+
if (!this.knots.get(id)) {
|
|
673
|
+
throw new Error(`Knot ${id} not found`);
|
|
674
|
+
}
|
|
675
|
+
const afterPath = this.selectPath(id);
|
|
676
|
+
const state = afterPath.knotStates.get(id);
|
|
677
|
+
const knotStates = state.selectedChild === null ? afterPath.knotStates : afterPath.knotStates.set(id, { ...state, selectedChild: null });
|
|
678
|
+
return new SkeinTree(this.engine, this.seed, this.knots, knotStates, id, this.collapsedKnotIds, this.dynamicStates);
|
|
679
|
+
}
|
|
680
|
+
toggleCollapsed(id) {
|
|
681
|
+
if (!this.knots.get(id)) {
|
|
682
|
+
throw new Error(`Knot ${id} not found`);
|
|
683
|
+
}
|
|
684
|
+
const collapsing = !this.collapsedKnotIds.has(id);
|
|
685
|
+
const collapsedKnotIds = collapsing ? this.collapsedKnotIds.add(id) : this.collapsedKnotIds.remove(id);
|
|
686
|
+
const activeKnotId = collapsing && this.activeKnotId !== null && this.isDescendantOf(this.activeKnotId, id) ? id : this.activeKnotId;
|
|
687
|
+
return new SkeinTree(this.engine, this.seed, this.knots, this.knotStates, activeKnotId, collapsedKnotIds, this.dynamicStates);
|
|
688
|
+
}
|
|
689
|
+
isDescendantOf(id, ancestorId) {
|
|
690
|
+
let current = this.knots.get(id)?.parentId ?? null;
|
|
691
|
+
while (current !== null) {
|
|
692
|
+
if (current === ancestorId) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
current = this.knots.get(current)?.parentId ?? null;
|
|
696
|
+
}
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
exports.SkeinTree = SkeinTree;
|
|
701
|
+
//# sourceMappingURL=tree.js.map
|