@neta-art/cohub 9.0.0 → 10.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/README.md +100 -103
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +14 -0
- package/dist/board/editor/animation.js +172 -0
- package/dist/board/editor/camera.d.ts +32 -0
- package/dist/board/editor/camera.js +137 -0
- package/dist/board/editor/clipboard.d.ts +41 -0
- package/dist/board/editor/clipboard.js +122 -0
- package/dist/board/editor/connection-ports.d.ts +19 -0
- package/dist/board/editor/connection-ports.js +85 -0
- package/dist/board/editor/context.d.ts +111 -0
- package/dist/board/editor/context.js +30 -0
- package/dist/board/editor/copy.d.ts +17 -0
- package/dist/board/editor/copy.js +116 -0
- package/dist/board/editor/creation.d.ts +47 -0
- package/dist/board/editor/creation.js +224 -0
- package/dist/board/editor/deletion.d.ts +9 -0
- package/dist/board/editor/deletion.js +90 -0
- package/dist/board/editor/document-edits.d.ts +22 -0
- package/dist/board/editor/document-edits.js +193 -0
- package/dist/board/editor/document.d.ts +35 -0
- package/dist/board/editor/document.js +255 -0
- package/dist/board/editor/editor.d.ts +210 -0
- package/dist/board/editor/editor.js +429 -0
- package/dist/board/editor/index.d.ts +18 -0
- package/dist/board/editor/index.js +12 -0
- package/dist/board/editor/interaction.d.ts +115 -0
- package/dist/board/editor/interaction.js +584 -0
- package/dist/board/editor/query.d.ts +27 -0
- package/dist/board/editor/query.js +201 -0
- package/dist/board/editor/remote.d.ts +11 -0
- package/dist/board/editor/remote.js +55 -0
- package/dist/board/editor/selection-transform.d.ts +24 -0
- package/dist/board/editor/selection-transform.js +81 -0
- package/dist/board/editor/selection.d.ts +33 -0
- package/dist/board/editor/selection.js +196 -0
- package/dist/board/editor/snapping.d.ts +33 -0
- package/dist/board/editor/snapping.js +75 -0
- package/dist/board/editor/snapshots.d.ts +27 -0
- package/dist/board/editor/snapshots.js +132 -0
- package/dist/board/editor/spatial.d.ts +41 -0
- package/dist/board/editor/spatial.js +191 -0
- package/dist/board/editor/state.d.ts +12 -0
- package/dist/board/editor/state.js +49 -0
- package/dist/board/editor/tool.d.ts +10 -0
- package/dist/board/editor/tool.js +19 -0
- package/dist/board/editor/wheel.d.ts +5 -0
- package/dist/board/editor/wheel.js +17 -0
- package/dist/board/export/index.d.ts +5 -4
- package/dist/board/export/index.js +4 -3
- package/dist/board/export/node/index.d.ts +55 -0
- package/dist/board/{headless → export/node}/index.js +12 -12
- package/dist/board/export/node/sketch.d.ts +19 -0
- package/dist/board/{headless → export/node}/sketch.js +6 -6
- package/dist/board/export/scene.d.ts +6 -4
- package/dist/board/export/scene.js +4 -3
- package/dist/board/{animation.d.ts → model/animation.d.ts} +4 -4
- package/dist/board/{animation.js → model/animation.js} +3 -3
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
- package/dist/board/{core → model}/draw-geometry.js +1 -1
- package/dist/board/model/draw-input.d.ts +14 -0
- package/dist/board/model/draw-input.js +28 -0
- package/dist/board/{core → model}/export-assets.d.ts +1 -1
- package/dist/board/{core → model}/export-assets.js +1 -1
- package/dist/board/{core → model}/export-plan.d.ts +2 -2
- package/dist/board/{core → model}/export-plan.js +2 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +1 -1
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +1 -1
- package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
- package/dist/board/{geometry.js → model/geometry.js} +1 -1
- package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
- package/dist/board/{image-key.js → model/image-key.js} +1 -1
- package/dist/board/model/index.d.ts +35 -0
- package/dist/board/{index.js → model/index.js} +26 -22
- package/dist/board/model/items/builtin.d.ts +5 -0
- package/dist/board/model/items/builtin.js +119 -0
- package/dist/board/model/items/create.d.ts +59 -0
- package/dist/board/model/items/create.js +271 -0
- package/dist/board/model/items/definition.d.ts +35 -0
- package/dist/board/model/items/definition.js +18 -0
- package/dist/board/model/items/id.d.ts +5 -0
- package/dist/board/model/items/id.js +11 -0
- package/dist/board/model/items/index.d.ts +6 -0
- package/dist/board/model/items/index.js +6 -0
- package/dist/board/model/items/registry.d.ts +35 -0
- package/dist/board/model/items/registry.js +84 -0
- package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +2 -2
- package/dist/board/{media-playback.js → model/media-playback.js} +1 -1
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +1 -1
- package/dist/board/{core → model}/palette.d.ts +1 -1
- package/dist/board/{core → model}/palette.js +1 -1
- package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
- package/dist/board/{presets.js → model/presets.js} +1 -1
- package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
- package/dist/board/{replay.js → model/replay.js} +2 -2
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- package/dist/board/model/shape-types.d.ts +8 -0
- package/dist/board/model/shape-types.js +8 -0
- package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
- package/dist/board/{task.js → model/task.js} +3 -3
- package/dist/board/{core → model}/text-metrics.d.ts +1 -1
- package/dist/board/{core → model}/text-metrics.js +1 -1
- package/dist/board/{core → model}/tool-styles.d.ts +1 -1
- package/dist/board/{core → model}/tool-styles.js +1 -1
- package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
- package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
- package/dist/board/render/board-background.d.ts +1 -1
- package/dist/board/render/clip.d.ts +1 -1
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +2 -2
- package/dist/board/render/media-interaction.d.ts +2 -2
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +1 -1
- package/dist/board/render/palette.js +1 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +1 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +11 -6
- package/dist/board/render/renderers/board-renderer-registry.js +15 -15
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +2 -2
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +1 -1
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- package/dist/board/render/stroke.d.ts +1 -1
- package/dist/board/render/stroke.js +1 -1
- package/dist/board/render/text-measurement.js +1 -1
- package/dist/board/replica/index.d.ts +70 -0
- package/dist/board/replica/index.js +276 -0
- package/dist/board/stage/assets.d.ts +41 -0
- package/dist/board/stage/assets.js +350 -0
- package/dist/board/stage/index.d.ts +8 -0
- package/dist/board/stage/index.js +8 -0
- package/dist/board/stage/lru.d.ts +32 -0
- package/dist/board/stage/lru.js +29 -0
- package/dist/board/stage/pointer.d.ts +8 -0
- package/dist/board/stage/pointer.js +33 -0
- package/dist/board/stage/scene.d.ts +85 -0
- package/dist/board/stage/scene.js +512 -0
- package/dist/board/stage/stage.d.ts +66 -0
- package/dist/board/stage/stage.js +568 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +154 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +25 -1
- package/dist/chunks/http.d.ts +99 -66
- package/dist/chunks/http.js +214 -207
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +20 -4
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board.d.ts +28 -2
- package/dist/protocol/dist/board.js +3 -1
- package/dist/protocol/dist/core/media.d.ts +5 -0
- package/dist/protocol/dist/core/media.js +39 -0
- package/dist/protocol/dist/index.d.ts +5 -5
- package/dist/types.d.ts +1 -1
- package/package.json +26 -14
- package/dist/board/core/shape-definition.d.ts +0 -23
- package/dist/board/core/shape-definition.js +0 -45
- package/dist/board/core/shape-types.d.ts +0 -38
- package/dist/board/core/shape-types.js +0 -22
- package/dist/board/headless/index.d.ts +0 -55
- package/dist/board/headless/sketch.d.ts +0 -19
- package/dist/board/index.d.ts +0 -30
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { IDENTITY_MATRIX, applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
|
+
import { rectCenter } from "../model/geometry.js";
|
|
3
|
+
import { buildBoardScene } from "../model/scene.js";
|
|
4
|
+
import { isShapeKind } from "../model/shape-types.js";
|
|
5
|
+
import { createBoardItemId } from "../model/items/id.js";
|
|
6
|
+
import { createAppBoardItem, createArrowBoardItem, createDrawBoardItem, createFileNodeForPath, createFrameBoardItem, createShapeBoardItem, createTaskBoardItem, createTextBoardItem } from "../model/items/create.js";
|
|
7
|
+
import { sameJson } from "./document-edits.js";
|
|
8
|
+
import { isContinuousBoardTool } from "./tool.js";
|
|
9
|
+
//#region src/board/editor/creation.ts
|
|
10
|
+
function createCreationModule(ctx) {
|
|
11
|
+
const { state, options } = ctx;
|
|
12
|
+
function placeNew(entries) {
|
|
13
|
+
const created = new Set(entries.map((entry) => entry.id));
|
|
14
|
+
const probe = buildBoardScene({
|
|
15
|
+
...state.base,
|
|
16
|
+
items: Object.fromEntries(entries.map((entry) => [entry.id, entry.item]))
|
|
17
|
+
});
|
|
18
|
+
const nextZ = /* @__PURE__ */ new Map();
|
|
19
|
+
const changes = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const { id, item } of entries) {
|
|
21
|
+
if (item.parent && created.has(item.parent)) {
|
|
22
|
+
changes.set(id, item);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const shown = probe.get(id);
|
|
26
|
+
const container = shown && item.type !== "frame" && item.type !== "arrow" ? ctx.query.frameAt(rectCenter(shown.frame), created) : null;
|
|
27
|
+
const value = container && shown ? ctx.document.reparent(item, shown.frame, container.id, probe.layout.box(id)) : item;
|
|
28
|
+
const z = nextZ.get(container?.id) ?? ctx.document.topZ(container?.id);
|
|
29
|
+
nextZ.set(container?.id, z + 1);
|
|
30
|
+
changes.set(id, {
|
|
31
|
+
...value,
|
|
32
|
+
z
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return changes;
|
|
36
|
+
}
|
|
37
|
+
function returnToSelect() {
|
|
38
|
+
if (!isContinuousBoardTool(state.tool)) state.tool = "select";
|
|
39
|
+
}
|
|
40
|
+
function add(entries, { select = !isContinuousBoardTool(state.tool), enter = true } = {}) {
|
|
41
|
+
if (options.readonly || entries.length === 0) return;
|
|
42
|
+
const changes = placeNew(entries);
|
|
43
|
+
if (enter) ctx.document.markAdded([...changes.keys()]);
|
|
44
|
+
state.selection = select ? [...changes.keys()] : [];
|
|
45
|
+
ctx.document.commitItems(changes);
|
|
46
|
+
returnToSelect();
|
|
47
|
+
}
|
|
48
|
+
function addOne(entry) {
|
|
49
|
+
add([entry]);
|
|
50
|
+
return entry.id;
|
|
51
|
+
}
|
|
52
|
+
function addItem(type, input) {
|
|
53
|
+
if (options.readonly) return null;
|
|
54
|
+
return addOne(ctx.registry.create(type, input));
|
|
55
|
+
}
|
|
56
|
+
function addTaskWithSources(taskRunId, snapshot, at, sources, metadata) {
|
|
57
|
+
if (options.readonly) return null;
|
|
58
|
+
const { arrow } = state.toolStyles;
|
|
59
|
+
const task = createTaskBoardItem(taskRunId, snapshot, at.x, at.y, metadata);
|
|
60
|
+
add([task, ...sources.filter((source) => source.itemId !== task.id && state.scene.get(source.itemId)).map((source) => {
|
|
61
|
+
const entry = createArrowBoardItem({
|
|
62
|
+
x: 0,
|
|
63
|
+
y: 0
|
|
64
|
+
}, { item: task.id }, arrow.color, createBoardItemId(), arrow.size, { item: source.itemId });
|
|
65
|
+
const props = entry.item.props;
|
|
66
|
+
return {
|
|
67
|
+
id: entry.id,
|
|
68
|
+
item: {
|
|
69
|
+
...entry.item,
|
|
70
|
+
metadata: { boardFlow: {
|
|
71
|
+
version: 1,
|
|
72
|
+
kind: "content",
|
|
73
|
+
sourcePortId: source.sourcePortId,
|
|
74
|
+
targetPortId: source.targetPortId
|
|
75
|
+
} },
|
|
76
|
+
props: {
|
|
77
|
+
...props,
|
|
78
|
+
start: {
|
|
79
|
+
item: source.itemId,
|
|
80
|
+
anchor: "auto",
|
|
81
|
+
port: source.sourcePortId
|
|
82
|
+
},
|
|
83
|
+
end: {
|
|
84
|
+
item: task.id,
|
|
85
|
+
anchor: "auto",
|
|
86
|
+
port: source.targetPortId
|
|
87
|
+
},
|
|
88
|
+
relation: "input"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
})], { select: false });
|
|
93
|
+
state.selection = [task.id];
|
|
94
|
+
return task.id;
|
|
95
|
+
}
|
|
96
|
+
function commitBox(gesture) {
|
|
97
|
+
const dx = gesture.current.x - gesture.start.x;
|
|
98
|
+
const dy = gesture.current.y - gesture.start.y;
|
|
99
|
+
const click = Math.hypot(dx, dy) <= 6 / Math.max(state.camera.zoom, 1e-4);
|
|
100
|
+
const minimum = gesture.kind === "frame" ? 48 : 24;
|
|
101
|
+
const box = click ? void 0 : {
|
|
102
|
+
x: Math.min(gesture.start.x, gesture.current.x),
|
|
103
|
+
y: Math.min(gesture.start.y, gesture.current.y),
|
|
104
|
+
width: Math.max(minimum, Math.abs(dx)),
|
|
105
|
+
height: Math.max(minimum, Math.abs(dy))
|
|
106
|
+
};
|
|
107
|
+
const geometry = isShapeKind(gesture.geometry) ? gesture.geometry : "rectangle";
|
|
108
|
+
const { x, y } = gesture.start;
|
|
109
|
+
add([gesture.kind === "shape" ? createShapeBoardItem(geometry, x, y, gesture.color, gesture.id, box) : createFrameBoardItem(x, y, gesture.color, "Frame", gesture.id, box)], { enter: false });
|
|
110
|
+
}
|
|
111
|
+
function commitDraw(id, points, color, size) {
|
|
112
|
+
if (points.length === 0) return;
|
|
113
|
+
add([createDrawBoardItem(points, color, size, id)], { enter: false });
|
|
114
|
+
}
|
|
115
|
+
function arrowEnd(point, targetItemId, arrowMatrix = IDENTITY_MATRIX) {
|
|
116
|
+
if (targetItemId) return {
|
|
117
|
+
item: targetItemId,
|
|
118
|
+
anchor: "auto"
|
|
119
|
+
};
|
|
120
|
+
const local = applyMatrix(invertMatrix(arrowMatrix), point);
|
|
121
|
+
return {
|
|
122
|
+
x: local.x,
|
|
123
|
+
y: local.y
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function commitArrow(gesture, cancelled) {
|
|
127
|
+
const { start, current, startItemId, targetItemId } = gesture;
|
|
128
|
+
const bound = !cancelled && targetItemId && targetItemId !== startItemId ? targetItemId : null;
|
|
129
|
+
if (!bound && Math.hypot(current.x - start.x, current.y - start.y) < 2 / state.camera.zoom) return;
|
|
130
|
+
add([createArrowBoardItem(start, bound ? { item: bound } : current, gesture.color, gesture.id, gesture.size, startItemId ? {
|
|
131
|
+
item: startItemId,
|
|
132
|
+
...gesture.startSide ? { anchor: {
|
|
133
|
+
side: gesture.startSide,
|
|
134
|
+
offset: .5
|
|
135
|
+
} } : {}
|
|
136
|
+
} : void 0)], { enter: false });
|
|
137
|
+
}
|
|
138
|
+
function beginTextDraft(at) {
|
|
139
|
+
const entry = createTextBoardItem("", at.x, at.y, state.toolStyles.text.color);
|
|
140
|
+
ctx.document.writeDraft(placeNew([entry]), true);
|
|
141
|
+
state.draftTextId = entry.id;
|
|
142
|
+
state.selection = [entry.id];
|
|
143
|
+
state.editingId = entry.id;
|
|
144
|
+
}
|
|
145
|
+
function previewTextLayout(id, text) {
|
|
146
|
+
const item = ctx.document.displayItem(id);
|
|
147
|
+
if (item?.type !== "text" || item.props.text === text) return;
|
|
148
|
+
ctx.document.writeDraft(/* @__PURE__ */ new Map([[id, {
|
|
149
|
+
...item,
|
|
150
|
+
props: {
|
|
151
|
+
...item.props,
|
|
152
|
+
text
|
|
153
|
+
}
|
|
154
|
+
}]]));
|
|
155
|
+
}
|
|
156
|
+
function updateText(id, text, created = false) {
|
|
157
|
+
const item = ctx.document.displayItem(id);
|
|
158
|
+
if (!item || item.type !== "text" && item.type !== "shape" && item.type !== "arrow" && item.type !== "frame") return;
|
|
159
|
+
const next = item.type === "arrow" || item.type === "frame" ? {
|
|
160
|
+
...item,
|
|
161
|
+
props: {
|
|
162
|
+
...item.props,
|
|
163
|
+
label: text
|
|
164
|
+
}
|
|
165
|
+
} : {
|
|
166
|
+
...item,
|
|
167
|
+
props: {
|
|
168
|
+
...item.props,
|
|
169
|
+
text
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
if (!created && sameJson(next, state.base.items[id])) {
|
|
173
|
+
ctx.document.dropDraftItem(id);
|
|
174
|
+
ctx.document.rescene([id]);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
ctx.document.commitItems(/* @__PURE__ */ new Map([[id, next]]));
|
|
178
|
+
}
|
|
179
|
+
function commitTextEdit(id, text) {
|
|
180
|
+
const isDraft = id === state.draftTextId;
|
|
181
|
+
const target = ctx.document.displayItem(id);
|
|
182
|
+
const empty = text.trim() === "" && (isDraft || target?.type === "text");
|
|
183
|
+
state.editingId = null;
|
|
184
|
+
state.draftTextId = null;
|
|
185
|
+
if (empty) {
|
|
186
|
+
if (isDraft) {
|
|
187
|
+
ctx.document.dropDraftItem(id);
|
|
188
|
+
ctx.document.rescene();
|
|
189
|
+
} else ctx.deletion.deleteItem(id);
|
|
190
|
+
} else updateText(id, text, isDraft);
|
|
191
|
+
if (state.tool === "text") state.tool = "select";
|
|
192
|
+
ctx.remote.flushPending();
|
|
193
|
+
}
|
|
194
|
+
const styles = () => state.toolStyles;
|
|
195
|
+
return {
|
|
196
|
+
placeNew,
|
|
197
|
+
add,
|
|
198
|
+
addItem,
|
|
199
|
+
addApp: (app, at) => addOne(createAppBoardItem(app, at.x, at.y)),
|
|
200
|
+
addFile: (path, at, snapshot) => addOne(createFileNodeForPath(path, at.x, at.y, snapshot)),
|
|
201
|
+
addTask: (taskRunId, snapshot, at, metadata) => addOne(createTaskBoardItem(taskRunId, snapshot, at.x, at.y, metadata)),
|
|
202
|
+
addTaskWithSources,
|
|
203
|
+
addText: (text, at) => {
|
|
204
|
+
add([createTextBoardItem(text, at.x, at.y, styles().text.color)]);
|
|
205
|
+
},
|
|
206
|
+
addShape: (at, geometry) => {
|
|
207
|
+
const { shape } = styles();
|
|
208
|
+
add([createShapeBoardItem(geometry ?? shape.geometry, at.x, at.y, shape.color)]);
|
|
209
|
+
},
|
|
210
|
+
addFrame: (at) => {
|
|
211
|
+
add([createFrameBoardItem(at.x, at.y, styles().frame.color)]);
|
|
212
|
+
},
|
|
213
|
+
commitBox,
|
|
214
|
+
commitDraw,
|
|
215
|
+
commitArrow,
|
|
216
|
+
arrowEnd,
|
|
217
|
+
beginTextDraft,
|
|
218
|
+
previewTextLayout,
|
|
219
|
+
updateText,
|
|
220
|
+
commitTextEdit
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { createCreationModule };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { EditorContext } from "./context.js";
|
|
2
|
+
//#region src/board/editor/deletion.d.ts
|
|
3
|
+
declare function createDeletionModule(ctx: EditorContext): {
|
|
4
|
+
deleteSelection: () => void;
|
|
5
|
+
deleteItem: (id: string) => void;
|
|
6
|
+
};
|
|
7
|
+
type DeletionModule = ReturnType<typeof createDeletionModule>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { DeletionModule, createDeletionModule };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { applyMatrix, invertMatrix, isArrowBinding } from "../../protocol/dist/board-layout.js";
|
|
2
|
+
import { pruneBoardTrack } from "../../protocol/dist/board-patch.js";
|
|
3
|
+
import { resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
4
|
+
//#region src/board/editor/deletion.ts
|
|
5
|
+
function createDeletionModule(ctx) {
|
|
6
|
+
const { state } = ctx;
|
|
7
|
+
function changesFor(ids) {
|
|
8
|
+
const { scene, base } = state;
|
|
9
|
+
const doomed = /* @__PURE__ */ new Set();
|
|
10
|
+
for (const id of ids) {
|
|
11
|
+
if (!scene.get(id)) continue;
|
|
12
|
+
doomed.add(id);
|
|
13
|
+
for (const child of scene.descendants(id)) doomed.add(child);
|
|
14
|
+
}
|
|
15
|
+
const changes = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const id of doomed) changes.set(id, null);
|
|
17
|
+
for (const id of doomed) for (const arrowId of scene.binders(id)) {
|
|
18
|
+
if (doomed.has(arrowId) || changes.has(arrowId)) continue;
|
|
19
|
+
const arrow = scene.get(arrowId);
|
|
20
|
+
if (!arrow) continue;
|
|
21
|
+
const resolved = resolveSceneArrow(arrow, scene);
|
|
22
|
+
const toLocal = invertMatrix(scene.layout.matrix(arrowId));
|
|
23
|
+
const free = (end, point) => {
|
|
24
|
+
if (!isArrowBinding(end) || !doomed.has(end.item)) return end;
|
|
25
|
+
const local = applyMatrix(toLocal, point);
|
|
26
|
+
return {
|
|
27
|
+
x: local.x,
|
|
28
|
+
y: local.y
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const { id: _id, frame: _frame, ...stored } = arrow;
|
|
32
|
+
changes.set(arrowId, {
|
|
33
|
+
...stored,
|
|
34
|
+
props: {
|
|
35
|
+
...arrow.props,
|
|
36
|
+
start: free(arrow.props.start, resolved.start.point),
|
|
37
|
+
end: free(arrow.props.end, resolved.end.point)
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
let animations;
|
|
42
|
+
for (const [animationId, animation] of Object.entries(base.animations)) {
|
|
43
|
+
const tracks = { ...animation.tracks };
|
|
44
|
+
let changed = false;
|
|
45
|
+
for (const [id, track] of Object.entries(tracks)) {
|
|
46
|
+
const next = pruneBoardTrack(track, doomed);
|
|
47
|
+
if (next === track) continue;
|
|
48
|
+
changed = true;
|
|
49
|
+
if (next) tracks[id] = next;
|
|
50
|
+
else delete tracks[id];
|
|
51
|
+
}
|
|
52
|
+
if (!changed) continue;
|
|
53
|
+
animations ??= { ...base.animations };
|
|
54
|
+
animations[animationId] = {
|
|
55
|
+
...animation,
|
|
56
|
+
tracks
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
items: changes,
|
|
61
|
+
...animations ? { animations } : {}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function deleteIds(ids) {
|
|
65
|
+
const { items: changes, animations } = changesFor(ids);
|
|
66
|
+
if (changes.size === 0) return;
|
|
67
|
+
for (const [id, item] of changes) ctx.document.setDraftItem(id, item);
|
|
68
|
+
ctx.document.commit(animations ? { animations } : {});
|
|
69
|
+
state.selection = state.selection.filter((id) => !changes.has(id) || changes.get(id) !== null);
|
|
70
|
+
if (state.editingId && changes.get(state.editingId) === null) state.editingId = null;
|
|
71
|
+
}
|
|
72
|
+
function deleteSelection() {
|
|
73
|
+
const ids = ctx.query.unlockedIds(state.selection);
|
|
74
|
+
if (ids.length === 0) return;
|
|
75
|
+
deleteIds(ids);
|
|
76
|
+
state.selection = [];
|
|
77
|
+
state.editingId = null;
|
|
78
|
+
}
|
|
79
|
+
function deleteItem(id) {
|
|
80
|
+
const target = state.scene.get(id);
|
|
81
|
+
if (!target || ctx.query.isLocked(target)) return;
|
|
82
|
+
deleteIds([id]);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
deleteSelection,
|
|
86
|
+
deleteItem
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { createDeletionModule };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BoardDocument, BoardItem, BoardPatch } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
//#region src/board/editor/document-edits.d.ts
|
|
4
|
+
declare function sameJson(a: unknown, b: unknown): boolean;
|
|
5
|
+
declare function readPath(value: unknown, path: readonly string[]): unknown;
|
|
6
|
+
declare function writePath<T>(value: T, path: readonly string[], next: unknown): T;
|
|
7
|
+
type BoardEditPlayhead = {
|
|
8
|
+
animationId: string;
|
|
9
|
+
time: number;
|
|
10
|
+
};
|
|
11
|
+
declare function routeBoardEdits(input: {
|
|
12
|
+
base: BoardDocument;
|
|
13
|
+
evaluated: ReadonlyMap<string, BoardItem>;
|
|
14
|
+
draft: ReadonlyMap<string, BoardItem | null>;
|
|
15
|
+
displayed?: ReadonlyMap<string, BoardItem>;
|
|
16
|
+
playhead: BoardEditPlayhead | null;
|
|
17
|
+
recording: boolean;
|
|
18
|
+
}): BoardDocument;
|
|
19
|
+
declare function isEmptyBoardPatch(patch: BoardPatch): boolean;
|
|
20
|
+
declare function diffBoardEdits(from: BoardDocument, to: BoardDocument, ids: Iterable<string>): BoardPatch;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { BoardEditPlayhead, diffBoardEdits, isEmptyBoardPatch, readPath, routeBoardEdits, sameJson, writePath };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { scaleVector } from "../../protocol/dist/board-layout.js";
|
|
2
|
+
import { diffMergePatch, listBoardProperties } from "../../protocol/dist/board-patch.js";
|
|
3
|
+
import { createBoardEntityId } from "../model/items/id.js";
|
|
4
|
+
//#region src/board/editor/document-edits.ts
|
|
5
|
+
function sameJson(a, b) {
|
|
6
|
+
if (a === b) return true;
|
|
7
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
8
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
9
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
10
|
+
for (let index = 0; index < a.length; index += 1) if (!sameJson(a[index], b[index])) return false;
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const left = a;
|
|
14
|
+
const right = b;
|
|
15
|
+
let members = 0;
|
|
16
|
+
for (const key of Object.keys(left)) {
|
|
17
|
+
if (left[key] === void 0) continue;
|
|
18
|
+
members += 1;
|
|
19
|
+
if (!sameJson(left[key], right[key])) return false;
|
|
20
|
+
}
|
|
21
|
+
for (const key of Object.keys(right)) if (right[key] !== void 0) members -= 1;
|
|
22
|
+
return members === 0;
|
|
23
|
+
}
|
|
24
|
+
function readPath(value, path) {
|
|
25
|
+
let current = value;
|
|
26
|
+
for (const segment of path) {
|
|
27
|
+
if (!current || typeof current !== "object") return void 0;
|
|
28
|
+
current = current[segment];
|
|
29
|
+
}
|
|
30
|
+
return current;
|
|
31
|
+
}
|
|
32
|
+
function writePath(value, path, next) {
|
|
33
|
+
const [head, ...rest] = path;
|
|
34
|
+
if (head === void 0) return next;
|
|
35
|
+
const record = value && typeof value === "object" ? value : {};
|
|
36
|
+
return {
|
|
37
|
+
...record,
|
|
38
|
+
[head]: writePath(record[head], rest, next)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const KEYFRAMABLE_KINDS = /* @__PURE__ */ new Set([
|
|
42
|
+
"number",
|
|
43
|
+
"vec2",
|
|
44
|
+
"scale",
|
|
45
|
+
"color"
|
|
46
|
+
]);
|
|
47
|
+
const STRUCTURAL_KEYS = [
|
|
48
|
+
"parent",
|
|
49
|
+
"z",
|
|
50
|
+
"locked",
|
|
51
|
+
"metadata"
|
|
52
|
+
];
|
|
53
|
+
function keyframe(animations, playhead, recording, id, property, value, storedValue) {
|
|
54
|
+
const animation = animations[playhead.animationId];
|
|
55
|
+
if (!animation) return false;
|
|
56
|
+
const time = Math.round(playhead.time);
|
|
57
|
+
const existing = Object.entries(animation.tracks).find(([, track]) => track.target === id && track.property === property && track.composite === "replace");
|
|
58
|
+
if (!existing && !recording) return false;
|
|
59
|
+
const candidateId = `${id}-${property.replaceAll(".", "-")}`;
|
|
60
|
+
const newId = candidateId.length <= 160 && !Object.hasOwn(animation.tracks, candidateId) ? candidateId : createBoardEntityId();
|
|
61
|
+
const [trackId, track] = existing ?? [newId, {
|
|
62
|
+
target: id,
|
|
63
|
+
property,
|
|
64
|
+
keyframes: time > 0 ? [{
|
|
65
|
+
at: 0,
|
|
66
|
+
value: storedValue
|
|
67
|
+
}] : [],
|
|
68
|
+
composite: "replace",
|
|
69
|
+
interpolation: "auto"
|
|
70
|
+
}];
|
|
71
|
+
const keyframes = [...track.keyframes.filter((entry) => entry.at !== time), {
|
|
72
|
+
at: time,
|
|
73
|
+
value
|
|
74
|
+
}].sort((a, b) => a.at - b.at);
|
|
75
|
+
animations[playhead.animationId] = {
|
|
76
|
+
...animation,
|
|
77
|
+
duration: Math.max(animation.duration, time),
|
|
78
|
+
tracks: {
|
|
79
|
+
...animation.tracks,
|
|
80
|
+
[trackId]: {
|
|
81
|
+
...track,
|
|
82
|
+
keyframes
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function authoredValue(stored, shown, next, property = "") {
|
|
89
|
+
if (sameJson(shown, next)) return stored;
|
|
90
|
+
if (property === "scale" && stored !== void 0 && shown !== void 0 && next !== void 0 && [
|
|
91
|
+
stored,
|
|
92
|
+
shown,
|
|
93
|
+
next
|
|
94
|
+
].some((value) => typeof value === "object")) return authoredValue(scaleVector(stored), scaleVector(shown), scaleVector(next), "scale.vector");
|
|
95
|
+
if (typeof stored === "number" && typeof shown === "number" && typeof next === "number") {
|
|
96
|
+
if (property === "opacity") return Math.max(0, Math.min(1, shown ? stored * next / shown : next));
|
|
97
|
+
if (property === "scale" || property.startsWith("scale.")) return shown ? stored * next / shown : next;
|
|
98
|
+
return stored + next - shown;
|
|
99
|
+
}
|
|
100
|
+
const record = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
101
|
+
if (record(stored) && record(shown) && record(next)) {
|
|
102
|
+
const result = { ...stored };
|
|
103
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(shown), ...Object.keys(next)])) {
|
|
104
|
+
if (sameJson(shown[key], next[key])) continue;
|
|
105
|
+
if (!(key in next)) delete result[key];
|
|
106
|
+
else result[key] = authoredValue(stored[key], shown[key], next[key], property ? `${property}.${key}` : key);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
function routeBoardEdits(input) {
|
|
113
|
+
const { base, evaluated, draft, playhead, recording } = input;
|
|
114
|
+
if (draft.size === 0) return base;
|
|
115
|
+
const items = { ...base.items };
|
|
116
|
+
const animations = { ...base.animations };
|
|
117
|
+
let keyed = false;
|
|
118
|
+
for (const [id, next] of draft) {
|
|
119
|
+
const stored = base.items[id];
|
|
120
|
+
if (!next) {
|
|
121
|
+
delete items[id];
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!stored || next.type !== stored.type) {
|
|
125
|
+
items[id] = next;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const shown = input.displayed?.get(id) ?? evaluated.get(id) ?? stored;
|
|
129
|
+
if (!playhead) {
|
|
130
|
+
items[id] = authoredValue(stored, shown, next);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const properties = listBoardProperties(next.type);
|
|
134
|
+
let item = stored;
|
|
135
|
+
for (const { property, kind } of properties) {
|
|
136
|
+
const path = property.split(".");
|
|
137
|
+
const value = readPath(next, path);
|
|
138
|
+
if (sameJson(value, readPath(shown, path))) continue;
|
|
139
|
+
if (KEYFRAMABLE_KINDS.has(kind) && keyframe(animations, playhead, recording, id, property, value, readPath(stored, path))) keyed = true;
|
|
140
|
+
else item = writePath(item, path, authoredValue(readPath(stored, path), readPath(shown, path), value, property));
|
|
141
|
+
}
|
|
142
|
+
for (const key of STRUCTURAL_KEYS) if (!sameJson(next[key], shown[key])) {
|
|
143
|
+
const copy = { ...item };
|
|
144
|
+
if (next[key] === void 0) delete copy[key];
|
|
145
|
+
else copy[key] = next[key];
|
|
146
|
+
item = copy;
|
|
147
|
+
}
|
|
148
|
+
items[id] = item;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
...base,
|
|
152
|
+
items,
|
|
153
|
+
animations: keyed ? animations : base.animations
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function isEmptyBoardPatch(patch) {
|
|
157
|
+
return !patch.board && !patch.items && !patch.animations;
|
|
158
|
+
}
|
|
159
|
+
function diffBoardEdits(from, to, ids) {
|
|
160
|
+
const patch = {};
|
|
161
|
+
if (from.board !== to.board && !sameJson(from.board, to.board)) patch.board = diffMergePatch(from.board, to.board);
|
|
162
|
+
const itemPatch = {};
|
|
163
|
+
for (const id of new Set(ids)) {
|
|
164
|
+
const before = from.items[id];
|
|
165
|
+
const after = to.items[id];
|
|
166
|
+
if (before === after) continue;
|
|
167
|
+
if (!after) {
|
|
168
|
+
if (before) itemPatch[id] = null;
|
|
169
|
+
} else if (!before || before.type !== after.type) itemPatch[id] = after;
|
|
170
|
+
else {
|
|
171
|
+
const change = diffMergePatch(before, after);
|
|
172
|
+
if (change && typeof change === "object" && Object.keys(change).length) itemPatch[id] = change;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (Object.keys(itemPatch).length) patch.items = itemPatch;
|
|
176
|
+
if (from.animations !== to.animations) {
|
|
177
|
+
const animationPatch = {};
|
|
178
|
+
for (const id of /* @__PURE__ */ new Set([...Object.keys(from.animations), ...Object.keys(to.animations)])) {
|
|
179
|
+
const before = from.animations[id];
|
|
180
|
+
const after = to.animations[id];
|
|
181
|
+
if (before === after) continue;
|
|
182
|
+
if (!after) animationPatch[id] = null;
|
|
183
|
+
else {
|
|
184
|
+
const change = diffMergePatch(before ?? {}, after);
|
|
185
|
+
if (change && typeof change === "object" && Object.keys(change).length) animationPatch[id] = change;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (Object.keys(animationPatch).length) patch.animations = animationPatch;
|
|
189
|
+
}
|
|
190
|
+
return patch;
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { diffBoardEdits, isEmptyBoardPatch, readPath, routeBoardEdits, sameJson, writePath };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BoardAnimation, BoardDocument, BoardItem, BoardSettings } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { Rect } from "../model/geometry.js";
|
|
4
|
+
import "../model/index.js";
|
|
5
|
+
import { EditorContext } from "./context.js";
|
|
6
|
+
//#region src/board/editor/document.d.ts
|
|
7
|
+
declare function createDocumentModule(ctx: EditorContext): {
|
|
8
|
+
markAdded: (ids: readonly string[]) => void;
|
|
9
|
+
consumeRecentlyAdded: () => string[];
|
|
10
|
+
displayItem: (id: string) => BoardItem | undefined;
|
|
11
|
+
reevaluate: () => void;
|
|
12
|
+
setPlayedItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
13
|
+
rescene: (ids?: Iterable<string>) => void;
|
|
14
|
+
reset: (next: BoardDocument) => void;
|
|
15
|
+
setDraftItem: (id: string, item: BoardItem | null) => void;
|
|
16
|
+
draftItem: (id: string) => BoardItem | null | undefined;
|
|
17
|
+
dropDraftItem: (id: string) => void;
|
|
18
|
+
writeDraft: (changes: ReadonlyMap<string, BoardItem | null>, structural?: boolean) => void;
|
|
19
|
+
discardDraft: () => void;
|
|
20
|
+
adopt: (next: BoardDocument, changedIds?: Iterable<string>) => void;
|
|
21
|
+
commit: (extra?: {
|
|
22
|
+
board?: BoardSettings;
|
|
23
|
+
animations?: Record<string, BoardAnimation>;
|
|
24
|
+
}, record?: boolean) => void;
|
|
25
|
+
commitItems: (changes: ReadonlyMap<string, BoardItem | null>, record?: boolean) => void;
|
|
26
|
+
undo: () => void;
|
|
27
|
+
redo: () => void;
|
|
28
|
+
previewSettings: (settings: BoardSettings) => void;
|
|
29
|
+
setSettings: (settings: BoardSettings) => void;
|
|
30
|
+
reparent: (item: BoardItem, frame: BoardFrame, parent: string | undefined, box: Rect) => BoardItem;
|
|
31
|
+
topZ: (parent: string | undefined) => number;
|
|
32
|
+
};
|
|
33
|
+
type DocumentModule = ReturnType<typeof createDocumentModule>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { DocumentModule, createDocumentModule };
|