@neta-art/cohub 10.0.0 → 11.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 +11 -2
- package/dist/board/editor/animation.d.ts +3 -0
- package/dist/board/editor/animation.js +16 -1
- package/dist/board/editor/clipboard.js +7 -0
- package/dist/board/editor/copy.js +23 -18
- package/dist/board/editor/creation.d.ts +5 -1
- package/dist/board/editor/creation.js +2 -1
- package/dist/board/editor/deletion.js +3 -3
- package/dist/board/editor/document.d.ts +8 -2
- package/dist/board/editor/document.js +54 -7
- package/dist/board/editor/editor.d.ts +18 -3
- package/dist/board/editor/editor.js +11 -1
- package/dist/board/editor/query.d.ts +5 -5
- package/dist/board/editor/query.js +14 -12
- package/dist/board/editor/selection.js +2 -1
- package/dist/board/export/index.d.ts +25 -4
- package/dist/board/export/index.js +45 -12
- package/dist/board/export/node/index.d.ts +5 -4
- package/dist/board/export/node/index.js +16 -11
- package/dist/board/export/node/scripts.d.ts +16 -0
- package/dist/board/export/node/scripts.js +258 -0
- package/dist/board/export/scene.d.ts +2 -1
- package/dist/board/export/scene.js +20 -6
- package/dist/board/model/animation.d.ts +4 -1
- package/dist/board/model/animation.js +34 -7
- package/dist/board/model/authoring.d.ts +20 -0
- package/dist/board/model/authoring.js +185 -0
- package/dist/board/model/export-plan.js +1 -0
- package/dist/board/model/file-preview.js +1 -1
- package/dist/board/model/file-snapshot.js +3 -2
- package/dist/board/model/image-key.js +6 -2
- package/dist/board/model/index.d.ts +13 -6
- package/dist/board/model/index.js +14 -8
- package/dist/board/model/items/builtin.js +2 -2
- package/dist/board/model/items/create.d.ts +4 -2
- package/dist/board/model/items/create.js +6 -5
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/index.js +2 -2
- package/dist/board/model/media-playback.d.ts +2 -1
- package/dist/board/model/media-playback.js +14 -4
- package/dist/board/model/media.js +7 -1
- package/dist/board/model/sampling.d.ts +19 -0
- package/dist/board/model/sampling.js +96 -0
- package/dist/board/model/script-example.d.ts +4 -0
- package/dist/board/model/script-example.js +21 -0
- package/dist/board/model/script-hit.d.ts +10 -0
- package/dist/board/model/script-hit.js +52 -0
- package/dist/board/model/script-index.d.ts +7 -0
- package/dist/board/model/script-index.js +15 -0
- package/dist/board/model/script-scene.d.ts +24 -0
- package/dist/board/model/script-scene.js +267 -0
- package/dist/board/model/script.d.ts +105 -0
- package/dist/board/model/script.js +1 -0
- package/dist/board/render/css-color.d.ts +6 -1
- package/dist/board/render/css-color.js +34 -20
- package/dist/board/render/diagnostics.d.ts +20 -0
- package/dist/board/render/diagnostics.js +117 -0
- package/dist/board/render/index.d.ts +9 -6
- package/dist/board/render/index.js +9 -6
- package/dist/board/render/palette.d.ts +2 -1
- package/dist/board/render/palette.js +6 -2
- package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -5
- package/dist/board/render/renderers/board-renderer-registry.js +3 -3
- package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
- package/dist/board/render/renderers/draw-card-renderer.js +3 -2
- package/dist/board/render/renderers/effect-card-renderer.js +22 -5
- package/dist/board/render/renderers/frame-card-renderer.js +30 -14
- package/dist/board/render/renderers/image-card-renderer.js +43 -4
- package/dist/board/render/renderers/shape-card-renderer.js +10 -27
- package/dist/board/render/renderers/text-card-renderer.js +5 -3
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sampling.d.ts +22 -0
- package/dist/board/render/sampling.js +21 -0
- package/dist/board/render/script-export.d.ts +11 -0
- package/dist/board/render/script-export.js +86 -0
- package/dist/board/render/script-host.d.ts +31 -0
- package/dist/board/render/script-host.js +421 -0
- package/dist/board/render/script-modules.d.ts +2 -0
- package/dist/board/render/script-modules.js +2 -0
- package/dist/board/render/script-runtime.d.ts +29 -0
- package/dist/board/render/script-runtime.js +213 -0
- package/dist/board/render/script-sandbox.d.ts +4 -0
- package/dist/board/render/script-sandbox.js +173 -0
- package/dist/board/render/stroke.d.ts +2 -1
- package/dist/board/render/stroke.js +22 -1
- package/dist/board/render/text-measurement.js +1 -1
- package/dist/board/stage/assets.js +6 -1
- package/dist/board/stage/maintenance.d.ts +5 -0
- package/dist/board/stage/maintenance.js +11 -0
- package/dist/board/stage/scene.js +5 -2
- package/dist/board/stage/script-pointer.d.ts +19 -0
- package/dist/board/stage/script-pointer.js +35 -0
- package/dist/board/stage/stage.d.ts +10 -0
- package/dist/board/stage/stage.js +249 -43
- package/dist/board/stage/texture-pool.js +2 -3
- package/dist/chunks/environment.d.ts +266 -23
- package/dist/chunks/host-bridge.d.ts +2 -2
- package/dist/chunks/http.d.ts +88 -7
- package/dist/chunks/http.js +155 -63
- package/dist/chunks/transport.js +3 -1
- package/dist/chunks/websocket.d.ts +9 -1
- package/dist/chunks/websocket.js +16 -2
- package/dist/host-bridge.js +2 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +182 -4
- package/dist/index.js +1374 -126
- package/dist/protocol/dist/board-assets.d.ts +1 -0
- package/dist/protocol/dist/board-constants.js +2 -2
- package/dist/protocol/dist/board-model.d.ts +201 -11
- package/dist/protocol/dist/board-model.js +33 -14
- package/dist/protocol/dist/board-patch.js +2 -1
- package/dist/protocol/dist/board-schema.d.ts +4 -2
- package/dist/protocol/dist/board-schema.js +18 -4
- package/dist/protocol/dist/board-script-modules.d.ts +25 -0
- package/dist/protocol/dist/board-script-modules.js +127 -0
- package/dist/protocol/dist/display/index.d.ts +1 -0
- package/dist/protocol/dist/index.d.ts +4 -2
- package/dist/protocol/dist/realtime/types.d.ts +1 -0
- package/dist/protocol/dist/rtc/index.d.ts +1 -0
- package/package.json +2 -1
- package/dist/board/export/node/sketch.d.ts +0 -19
- package/dist/board/export/node/sketch.js +0 -146
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
- package/dist/board/render/sketch-host.d.ts +0 -14
- package/dist/board/render/sketch-host.js +0 -216
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ The editor has no framework. Read state through its getters and listen with
|
|
|
131
131
|
change, so they work directly with `useSyncExternalStore`:
|
|
132
132
|
|
|
133
133
|
```ts
|
|
134
|
-
import { createBoardRegistry, defineBoardItem } from "@neta-art/cohub/board";
|
|
134
|
+
import { createBoardRegistry, defineBoardItem, worldPoint } from "@neta-art/cohub/board";
|
|
135
135
|
import { createBoardEditor } from "@neta-art/cohub/board/editor";
|
|
136
136
|
import { createBoardReplica } from "@neta-art/cohub/board/replica";
|
|
137
137
|
import { createBoardAssetManager, mountBoardStage } from "@neta-art/cohub/board/stage";
|
|
@@ -157,7 +157,16 @@ const editor = createBoardEditor({
|
|
|
157
157
|
});
|
|
158
158
|
replica.subscribe((state) => state.document && editor.loadDocument(state.document, boardId));
|
|
159
159
|
|
|
160
|
-
//
|
|
160
|
+
// Prefer stable media URLs. Existing Space paths remain supported by addFile.
|
|
161
|
+
editor.addMedia("https://cdn.example.com/cover.webp", worldPoint(0, 0));
|
|
162
|
+
// Supply a kind for sources without a file extension.
|
|
163
|
+
editor.addMedia("https://cdn.example.com/media?id=42", worldPoint(400, 0), {
|
|
164
|
+
kind: "video",
|
|
165
|
+
snapshot: { title: "Launch", naturalWidth: 1920, naturalHeight: 1080 },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Media URLs load directly; these callbacks only receive Space paths.
|
|
169
|
+
// A published copy can resolve paths against captured artifacts instead.
|
|
161
170
|
const source = {
|
|
162
171
|
resolveFileUrl: (path: string) => space.files.resolveUrl(path, { purpose: "preview" }),
|
|
163
172
|
resolvePlaybackUrl: (path: string) => space.files.resolveUrl(path, { purpose: "playback" }),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BoardChapter } from "../model/authoring.js";
|
|
2
|
+
import "../model/index.js";
|
|
1
3
|
import { BoardPlayhead, EditorContext } from "./context.js";
|
|
2
4
|
//#region src/board/editor/animation.d.ts
|
|
3
5
|
declare function createAnimationModule(ctx: EditorContext): {
|
|
@@ -8,6 +10,7 @@ declare function createAnimationModule(ctx: EditorContext): {
|
|
|
8
10
|
removeKeyframe: (trackId: string, at: number) => void;
|
|
9
11
|
setKeyframeEase: (trackId: string, at: number, ease: string | null) => void;
|
|
10
12
|
createAnimation: (name: string, duration?: number) => string;
|
|
13
|
+
createSequence: (name: string, chapters: BoardChapter[]) => string | null;
|
|
11
14
|
};
|
|
12
15
|
type AnimationModule = ReturnType<typeof createAnimationModule>;
|
|
13
16
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { boardSequence } from "../model/authoring.js";
|
|
1
2
|
import { createBoardItemId } from "../model/items/id.js";
|
|
2
3
|
import { readPath, sameJson } from "./document-edits.js";
|
|
3
4
|
//#region src/board/editor/animation.ts
|
|
@@ -158,6 +159,19 @@ function createAnimationModule(ctx) {
|
|
|
158
159
|
});
|
|
159
160
|
return id;
|
|
160
161
|
}
|
|
162
|
+
function createSequence(name, chapters) {
|
|
163
|
+
if (options.readonly) return null;
|
|
164
|
+
const id = createBoardItemId();
|
|
165
|
+
commitAnimation(id, {
|
|
166
|
+
...boardSequence(state.base, chapters),
|
|
167
|
+
name
|
|
168
|
+
});
|
|
169
|
+
setPlayhead({
|
|
170
|
+
animationId: id,
|
|
171
|
+
time: 0
|
|
172
|
+
});
|
|
173
|
+
return id;
|
|
174
|
+
}
|
|
161
175
|
return {
|
|
162
176
|
setPlayhead,
|
|
163
177
|
setRecording,
|
|
@@ -165,7 +179,8 @@ function createAnimationModule(ctx) {
|
|
|
165
179
|
toggleKeyframe,
|
|
166
180
|
removeKeyframe,
|
|
167
181
|
setKeyframeEase,
|
|
168
|
-
createAnimation
|
|
182
|
+
createAnimation,
|
|
183
|
+
createSequence
|
|
169
184
|
};
|
|
170
185
|
}
|
|
171
186
|
//#endregion
|
|
@@ -65,6 +65,13 @@ function remapItems(items, idMap, place = (item) => item, resolveEnd = () => nul
|
|
|
65
65
|
let item = place(source);
|
|
66
66
|
const { locked: _locked, ...unlocked } = item;
|
|
67
67
|
item = unlocked;
|
|
68
|
+
if (item.script) item = {
|
|
69
|
+
...item,
|
|
70
|
+
script: {
|
|
71
|
+
...item.script,
|
|
72
|
+
targets: Object.fromEntries(Object.entries(item.script.targets).map(([name, target]) => [name, idMap.get(target) ?? target]))
|
|
73
|
+
}
|
|
74
|
+
};
|
|
68
75
|
if (item.parent) {
|
|
69
76
|
const parent = idMap.get(item.parent);
|
|
70
77
|
if (parent) item = {
|
|
@@ -7,35 +7,39 @@ function createCopyModule(ctx) {
|
|
|
7
7
|
const { state, options } = ctx;
|
|
8
8
|
let internalClipboard = null;
|
|
9
9
|
let pasteCount = 0;
|
|
10
|
-
function detachedSubtrees(ids) {
|
|
11
|
-
const { scene } = state;
|
|
10
|
+
function detachedSubtrees(ids, scene) {
|
|
12
11
|
const items = {};
|
|
13
12
|
for (const root of ctx.query.rootsOf(ids)) {
|
|
14
13
|
const item = scene.get(root);
|
|
15
|
-
if (!item) continue;
|
|
14
|
+
if (!item || !state.base.items[root]) continue;
|
|
16
15
|
const { id: _id, frame, ...stored } = item;
|
|
17
16
|
items[root] = ctx.document.reparent(stored, frame, void 0, scene.layout.box(root));
|
|
18
17
|
for (const child of scene.descendants(root)) {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!state.base.items[child]) continue;
|
|
19
|
+
const shown = scene.get(child);
|
|
20
|
+
if (shown) {
|
|
21
|
+
const { id: _id, frame: _frame, ...value } = shown;
|
|
22
|
+
items[child] = value;
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
return items;
|
|
24
27
|
}
|
|
25
|
-
function arrowEndPoint(id, which) {
|
|
26
|
-
const arrow =
|
|
28
|
+
function arrowEndPoint(scene, id, which) {
|
|
29
|
+
const arrow = scene.get(id);
|
|
27
30
|
if (arrow?.type !== "arrow") return null;
|
|
28
|
-
const resolved = resolveSceneArrow(arrow,
|
|
29
|
-
return applyMatrix(invertMatrix(
|
|
31
|
+
const resolved = resolveSceneArrow(arrow, scene);
|
|
32
|
+
return applyMatrix(invertMatrix(scene.layout.matrix(id)), (which === "start" ? resolved.start : resolved.end).point);
|
|
30
33
|
}
|
|
31
34
|
function duplicates(ids, offset) {
|
|
32
|
-
const
|
|
33
|
-
const copied = detachedSubtrees(ids);
|
|
35
|
+
const scene = ctx.document.copyScene();
|
|
36
|
+
const copied = detachedSubtrees(ids, scene);
|
|
34
37
|
for (const id of Object.keys(copied)) for (const arrowId of scene.binders(id)) {
|
|
35
|
-
|
|
38
|
+
if (!state.base.items[arrowId]) continue;
|
|
36
39
|
const shown = scene.get(arrowId);
|
|
37
|
-
if (copied[arrowId] || !
|
|
38
|
-
|
|
40
|
+
if (copied[arrowId] || !shown || !arrowBindings(shown).every((bound) => copied[bound])) continue;
|
|
41
|
+
const { id: _id, frame, ...arrow } = shown;
|
|
42
|
+
copied[arrowId] = ctx.document.reparent(arrow, frame, void 0, scene.layout.box(arrowId));
|
|
39
43
|
}
|
|
40
44
|
const idMap = new Map(Object.keys(copied).map((id) => [id, createBoardItemId()]));
|
|
41
45
|
const moved = remapItems(copied, idMap, (item) => item.parent && copied[item.parent] ? item : {
|
|
@@ -44,8 +48,8 @@ function createCopyModule(ctx) {
|
|
|
44
48
|
x: item.position.x + offset,
|
|
45
49
|
y: item.position.y + offset
|
|
46
50
|
}
|
|
47
|
-
}, arrowEndPoint);
|
|
48
|
-
let z = ctx.document.topZ(void 0);
|
|
51
|
+
}, (id, which) => arrowEndPoint(scene, id, which));
|
|
52
|
+
let z = ctx.document.topZ(void 0, scene);
|
|
49
53
|
return new Map(Object.entries(moved).map(([id, item]) => [id, item.parent ? item : {
|
|
50
54
|
...item,
|
|
51
55
|
z: z++
|
|
@@ -66,8 +70,9 @@ function createCopyModule(ctx) {
|
|
|
66
70
|
function copySelection() {
|
|
67
71
|
const { selection } = state;
|
|
68
72
|
if (selection.length === 0) return null;
|
|
69
|
-
const
|
|
70
|
-
const
|
|
73
|
+
const scene = ctx.document.copyScene();
|
|
74
|
+
const copied = detachedSubtrees(selection, scene);
|
|
75
|
+
const origin = ctx.query.contentBounds(ctx.query.rootsOf(selection), scene);
|
|
71
76
|
const payload = origin ? encodeClipboard(copied, {
|
|
72
77
|
x: origin.x,
|
|
73
78
|
y: origin.y
|
|
@@ -3,7 +3,7 @@ import { BoardMatrix } from "../../protocol/dist/board-layout.js";
|
|
|
3
3
|
import { WorldPoint } from "../model/geometry.js";
|
|
4
4
|
import { BoardFileSnapshotFacts } from "../model/file-snapshot.js";
|
|
5
5
|
import { ShapeKind } from "../model/shape-types.js";
|
|
6
|
-
import { BoardItemEntry, createAppBoardItem } from "../model/items/create.js";
|
|
6
|
+
import { BoardItemEntry, BoardMediaItemKind, createAppBoardItem } from "../model/items/create.js";
|
|
7
7
|
import { BoardItemInput } from "../model/items/registry.js";
|
|
8
8
|
import "../model/index.js";
|
|
9
9
|
import { BoardInteraction } from "./interaction.js";
|
|
@@ -23,6 +23,10 @@ declare function createCreationModule(ctx: EditorContext): {
|
|
|
23
23
|
}) => void;
|
|
24
24
|
addItem: (type: string, input: BoardItemInput) => string | null;
|
|
25
25
|
addApp: (app: BoardAppMetadata, at: WorldPoint) => string;
|
|
26
|
+
addMedia: (src: string, at: WorldPoint, options?: {
|
|
27
|
+
kind?: BoardMediaItemKind;
|
|
28
|
+
snapshot?: BoardMediaSnapshot;
|
|
29
|
+
}) => string;
|
|
26
30
|
addFile: (path: string, at: WorldPoint, snapshot?: BoardMediaSnapshot & BoardFileSnapshotFacts) => string;
|
|
27
31
|
addTask: (taskRunId: string, snapshot: BoardTaskSnapshot, at: WorldPoint, metadata?: Record<string, unknown>) => string;
|
|
28
32
|
addTaskWithSources: (taskRunId: string, snapshot: BoardTaskSnapshot, at: WorldPoint, sources: BoardTaskSource[], metadata?: Record<string, unknown>) => string | null;
|
|
@@ -3,7 +3,7 @@ import { rectCenter } from "../model/geometry.js";
|
|
|
3
3
|
import { buildBoardScene } from "../model/scene.js";
|
|
4
4
|
import { isShapeKind } from "../model/shape-types.js";
|
|
5
5
|
import { createBoardItemId } from "../model/items/id.js";
|
|
6
|
-
import { createAppBoardItem, createArrowBoardItem, createDrawBoardItem, createFileNodeForPath, createFrameBoardItem, createShapeBoardItem, createTaskBoardItem, createTextBoardItem } from "../model/items/create.js";
|
|
6
|
+
import { createAppBoardItem, createArrowBoardItem, createDrawBoardItem, createFileNodeForPath, createFrameBoardItem, createMediaBoardItem, createShapeBoardItem, createTaskBoardItem, createTextBoardItem } from "../model/items/create.js";
|
|
7
7
|
import { sameJson } from "./document-edits.js";
|
|
8
8
|
import { isContinuousBoardTool } from "./tool.js";
|
|
9
9
|
//#region src/board/editor/creation.ts
|
|
@@ -197,6 +197,7 @@ function createCreationModule(ctx) {
|
|
|
197
197
|
add,
|
|
198
198
|
addItem,
|
|
199
199
|
addApp: (app, at) => addOne(createAppBoardItem(app, at.x, at.y)),
|
|
200
|
+
addMedia: (src, at, options = {}) => addOne(createMediaBoardItem(src, at.x, at.y, options.snapshot, void 0, options.kind)),
|
|
200
201
|
addFile: (path, at, snapshot) => addOne(createFileNodeForPath(path, at.x, at.y, snapshot)),
|
|
201
202
|
addTask: (taskRunId, snapshot, at, metadata) => addOne(createTaskBoardItem(taskRunId, snapshot, at.x, at.y, metadata)),
|
|
202
203
|
addTaskWithSources,
|
|
@@ -8,14 +8,14 @@ function createDeletionModule(ctx) {
|
|
|
8
8
|
const { scene, base } = state;
|
|
9
9
|
const doomed = /* @__PURE__ */ new Set();
|
|
10
10
|
for (const id of ids) {
|
|
11
|
-
if (!
|
|
11
|
+
if (!base.items[id]) continue;
|
|
12
12
|
doomed.add(id);
|
|
13
|
-
for (const child of scene.descendants(id)) doomed.add(child);
|
|
13
|
+
for (const child of scene.descendants(id)) if (base.items[child]) doomed.add(child);
|
|
14
14
|
}
|
|
15
15
|
const changes = /* @__PURE__ */ new Map();
|
|
16
16
|
for (const id of doomed) changes.set(id, null);
|
|
17
17
|
for (const id of doomed) for (const arrowId of scene.binders(id)) {
|
|
18
|
-
if (doomed.has(arrowId) || changes.has(arrowId)) continue;
|
|
18
|
+
if (!base.items[arrowId] || doomed.has(arrowId) || changes.has(arrowId)) continue;
|
|
19
19
|
const arrow = scene.get(arrowId);
|
|
20
20
|
if (!arrow) continue;
|
|
21
21
|
const resolved = resolveSceneArrow(arrow, scene);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BoardAnimation, BoardDocument, BoardItem, BoardSettings } from "../../protocol/dist/board-model.js";
|
|
1
|
+
import { BoardAnimation, BoardDocument, BoardItem, BoardPatch, BoardSettings } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
3
|
import { Rect } from "../model/geometry.js";
|
|
4
|
+
import { BoardScene } from "../model/scene.js";
|
|
4
5
|
import "../model/index.js";
|
|
5
6
|
import { EditorContext } from "./context.js";
|
|
6
7
|
//#region src/board/editor/document.d.ts
|
|
@@ -8,8 +9,13 @@ declare function createDocumentModule(ctx: EditorContext): {
|
|
|
8
9
|
markAdded: (ids: readonly string[]) => void;
|
|
9
10
|
consumeRecentlyAdded: () => string[];
|
|
10
11
|
displayItem: (id: string) => BoardItem | undefined;
|
|
12
|
+
copyScene: () => BoardScene;
|
|
11
13
|
reevaluate: () => void;
|
|
12
14
|
setPlayedItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
15
|
+
readonly hasScriptItems: boolean;
|
|
16
|
+
setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
17
|
+
animationItems: () => Map<string, BoardItem>;
|
|
18
|
+
commitPatch: (patch: BoardPatch) => Promise<void> | undefined;
|
|
13
19
|
rescene: (ids?: Iterable<string>) => void;
|
|
14
20
|
reset: (next: BoardDocument) => void;
|
|
15
21
|
setDraftItem: (id: string, item: BoardItem | null) => void;
|
|
@@ -28,7 +34,7 @@ declare function createDocumentModule(ctx: EditorContext): {
|
|
|
28
34
|
previewSettings: (settings: BoardSettings) => void;
|
|
29
35
|
setSettings: (settings: BoardSettings) => void;
|
|
30
36
|
reparent: (item: BoardItem, frame: BoardFrame, parent: string | undefined, box: Rect) => BoardItem;
|
|
31
|
-
topZ: (parent: string | undefined) => number;
|
|
37
|
+
topZ: (parent: string | undefined, scene?: BoardScene) => number;
|
|
32
38
|
};
|
|
33
39
|
type DocumentModule = ReturnType<typeof createDocumentModule>;
|
|
34
40
|
//#endregion
|
|
@@ -11,6 +11,7 @@ function createDocumentModule(ctx) {
|
|
|
11
11
|
const recentlyAdded = /* @__PURE__ */ new Set();
|
|
12
12
|
let evaluated = /* @__PURE__ */ new Map();
|
|
13
13
|
let played = /* @__PURE__ */ new Map();
|
|
14
|
+
let scripted = /* @__PURE__ */ new Map();
|
|
14
15
|
let compiled = null;
|
|
15
16
|
function markAdded(ids) {
|
|
16
17
|
if (ids.length === 0) return;
|
|
@@ -24,14 +25,15 @@ function createDocumentModule(ctx) {
|
|
|
24
25
|
}
|
|
25
26
|
function displayItem(id) {
|
|
26
27
|
if (draft.has(id)) return draft.get(id) ?? void 0;
|
|
27
|
-
return evaluated.get(id) ?? played.get(id) ?? state.base.items[id];
|
|
28
|
+
return scripted.get(id) ?? evaluated.get(id) ?? played.get(id) ?? state.base.items[id];
|
|
28
29
|
}
|
|
29
|
-
function displayDocument() {
|
|
30
|
+
function displayDocument(includeScripts = true) {
|
|
30
31
|
const base = state.base;
|
|
31
|
-
if (draft.size === 0 && evaluated.size === 0 && played.size === 0) return base;
|
|
32
|
+
if (draft.size === 0 && evaluated.size === 0 && played.size === 0 && (!includeScripts || scripted.size === 0)) return base;
|
|
32
33
|
const items = { ...base.items };
|
|
33
34
|
for (const [id, item] of played) items[id] = item;
|
|
34
35
|
for (const [id, item] of evaluated) items[id] = item;
|
|
36
|
+
if (includeScripts) for (const [id, item] of scripted) items[id] = item;
|
|
35
37
|
for (const [id, item] of draft) if (item) items[id] = item;
|
|
36
38
|
else delete items[id];
|
|
37
39
|
return {
|
|
@@ -59,6 +61,17 @@ function createDocumentModule(ctx) {
|
|
|
59
61
|
for (const id of changed) if (draft.has(id) || evaluated.has(id) || sameJson(previous.get(id), played.get(id))) changed.delete(id);
|
|
60
62
|
if (changed.size) rescene(changed);
|
|
61
63
|
}
|
|
64
|
+
function setScriptItems(items) {
|
|
65
|
+
const previous = scripted;
|
|
66
|
+
scripted = items;
|
|
67
|
+
const changed = [.../* @__PURE__ */ new Set([...previous.keys(), ...items.keys()])].filter((id) => !sameJson(previous.get(id), items.get(id)));
|
|
68
|
+
if (!changed.length) return;
|
|
69
|
+
rescene(changed.some((id) => {
|
|
70
|
+
const before = previous.get(id) ?? state.base.items[id];
|
|
71
|
+
const after = items.get(id) ?? state.base.items[id];
|
|
72
|
+
return !before || !after || before.parent !== after.parent || before.z !== after.z || !sameJson(arrowBindings(before), arrowBindings(after));
|
|
73
|
+
}) ? void 0 : changed);
|
|
74
|
+
}
|
|
62
75
|
function rescene(ids) {
|
|
63
76
|
if (ids) {
|
|
64
77
|
const changed = [...ids];
|
|
@@ -76,6 +89,7 @@ function createDocumentModule(ctx) {
|
|
|
76
89
|
draft.clear();
|
|
77
90
|
draftOrigins.clear();
|
|
78
91
|
played = /* @__PURE__ */ new Map();
|
|
92
|
+
scripted = /* @__PURE__ */ new Map();
|
|
79
93
|
reevaluate();
|
|
80
94
|
state.scene = buildBoardScene(next);
|
|
81
95
|
ctx.query.invalidate();
|
|
@@ -114,12 +128,20 @@ function createDocumentModule(ctx) {
|
|
|
114
128
|
rescene();
|
|
115
129
|
}
|
|
116
130
|
function send(patch) {
|
|
117
|
-
if (options.readonly || isEmptyBoardPatch(patch)) return;
|
|
131
|
+
if (options.readonly || isEmptyBoardPatch(patch)) return Promise.resolve(null);
|
|
118
132
|
state.pendingCommits += 1;
|
|
119
|
-
|
|
133
|
+
let completion;
|
|
134
|
+
try {
|
|
135
|
+
completion = Promise.resolve(options.onCommit(patch));
|
|
136
|
+
} catch (error) {
|
|
137
|
+
completion = Promise.reject(error);
|
|
138
|
+
}
|
|
139
|
+
return completion.then(() => {
|
|
120
140
|
state.saveError = null;
|
|
141
|
+
return null;
|
|
121
142
|
}).catch((error) => {
|
|
122
143
|
state.saveError = error instanceof Error ? error.message : "Failed to sync board";
|
|
144
|
+
return error instanceof Error ? error : new Error(state.saveError);
|
|
123
145
|
}).finally(() => {
|
|
124
146
|
state.pendingCommits -= 1;
|
|
125
147
|
});
|
|
@@ -183,6 +205,24 @@ function createDocumentModule(ctx) {
|
|
|
183
205
|
for (const [id, item] of changes) setDraftItem(id, item);
|
|
184
206
|
commit({}, record);
|
|
185
207
|
}
|
|
208
|
+
function commitPatch(patch) {
|
|
209
|
+
if (options.readonly) throw new Error("Board is read-only.");
|
|
210
|
+
const before = state.base;
|
|
211
|
+
const result = applyBoardPatchToDocument(before, patch);
|
|
212
|
+
if (!result.ok) throw new Error(result.diagnostics[0]?.message ?? "Invalid board patch.");
|
|
213
|
+
const ids = Object.keys(patch.items ?? {});
|
|
214
|
+
const redo = diffBoardEdits(before, result.document, ids);
|
|
215
|
+
if (isEmptyBoardPatch(redo)) return;
|
|
216
|
+
state.undoStack = [...state.undoStack.slice(-199), {
|
|
217
|
+
undo: diffBoardEdits(result.document, before, ids),
|
|
218
|
+
redo
|
|
219
|
+
}];
|
|
220
|
+
state.redoStack = [];
|
|
221
|
+
adopt(result.document, ids);
|
|
222
|
+
return send(redo).then((error) => {
|
|
223
|
+
if (error) throw error;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
186
226
|
function applyPatch(patch) {
|
|
187
227
|
const result = applyBoardPatchToDocument(state.base, patch);
|
|
188
228
|
if (!result.ok) {
|
|
@@ -222,17 +262,24 @@ function createDocumentModule(ctx) {
|
|
|
222
262
|
const matrix = parent ? state.scene.layout.matrix(parent) : IDENTITY_MATRIX;
|
|
223
263
|
return placeItem(next, frame, matrix, box);
|
|
224
264
|
}
|
|
225
|
-
function topZ(parent) {
|
|
265
|
+
function topZ(parent, scene = state.scene) {
|
|
226
266
|
let z = 0;
|
|
227
|
-
for (const id of
|
|
267
|
+
for (const id of scene.children(parent)) z = Math.max(z, scene.get(id)?.z ?? 0);
|
|
228
268
|
return z + 1;
|
|
229
269
|
}
|
|
230
270
|
return {
|
|
231
271
|
markAdded,
|
|
232
272
|
consumeRecentlyAdded,
|
|
233
273
|
displayItem,
|
|
274
|
+
copyScene: () => scripted.size ? buildBoardScene(displayDocument(false)) : state.scene,
|
|
234
275
|
reevaluate,
|
|
235
276
|
setPlayedItems,
|
|
277
|
+
get hasScriptItems() {
|
|
278
|
+
return scripted.size > 0;
|
|
279
|
+
},
|
|
280
|
+
setScriptItems,
|
|
281
|
+
animationItems: () => new Map([...played, ...evaluated]),
|
|
282
|
+
commitPatch,
|
|
236
283
|
rescene,
|
|
237
284
|
reset,
|
|
238
285
|
setDraftItem,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { BoardDocument, BoardItem, BoardMediaSnapshot, BoardSettings, BoardTaskSnapshot } from "../../protocol/dist/board-model.js";
|
|
1
|
+
import { BoardDocument, BoardItem, BoardMediaSnapshot, BoardPatch, BoardSettings, BoardTaskSnapshot } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
3
|
import { BoardViewport, Rect, ScreenPoint, WorldPoint } from "../model/geometry.js";
|
|
4
4
|
import { BoardScene, BoardSceneItem } from "../model/scene.js";
|
|
5
|
+
import { BoardChapter } from "../model/authoring.js";
|
|
5
6
|
import { BoardFileSnapshotFacts } from "../model/file-snapshot.js";
|
|
6
7
|
import { ShapeKind } from "../model/shape-types.js";
|
|
8
|
+
import { BoardItemEntry, BoardMediaItemKind } from "../model/items/create.js";
|
|
7
9
|
import { BoardItemInput, BoardRegistry } from "../model/items/registry.js";
|
|
8
10
|
import "../model/index.js";
|
|
9
11
|
import { AlignMode, DistributeAxis } from "./align.js";
|
|
@@ -22,6 +24,7 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
22
24
|
registry: BoardRegistry;
|
|
23
25
|
readonly: boolean;
|
|
24
26
|
subscribe: (channels: BoardEditorChannel | readonly BoardEditorChannel[], listener: BoardEditorListener) => () => void;
|
|
27
|
+
readonly hasScripts: boolean;
|
|
25
28
|
readonly document: BoardDocument;
|
|
26
29
|
readonly settings: {
|
|
27
30
|
title?: string | undefined;
|
|
@@ -130,11 +133,14 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
130
133
|
setSettings: (settings: BoardSettings) => void;
|
|
131
134
|
previewSettings: (settings: BoardSettings) => void;
|
|
132
135
|
setPlayedItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
136
|
+
setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
|
|
137
|
+
animationItems: () => Map<string, BoardItem>;
|
|
138
|
+
commitPatch: (patch: BoardPatch) => Promise<void> | undefined;
|
|
133
139
|
consumeRecentlyAdded: () => string[];
|
|
134
|
-
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string> | undefined) => BoardSceneItem | null;
|
|
140
|
+
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string> | undefined, includeTemporary?: boolean | undefined) => BoardSceneItem | null;
|
|
135
141
|
itemById: (id: string) => BoardSceneItem | null;
|
|
136
142
|
labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
|
|
137
|
-
idsInRect: (rect: Rect) => string[];
|
|
143
|
+
idsInRect: (rect: Rect, includeTemporary?: boolean | undefined) => string[];
|
|
138
144
|
setCamera: (viewport: BoardViewport) => void;
|
|
139
145
|
setCameraPolicy: (policy: BoardCameraPolicy) => void;
|
|
140
146
|
takeCameraControl: () => void;
|
|
@@ -162,8 +168,16 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
162
168
|
bringToFront: () => void;
|
|
163
169
|
sendToBack: () => void;
|
|
164
170
|
addItem: (type: string, input: BoardItemInput) => string | null;
|
|
171
|
+
addItems: (entries: BoardItemEntry[], { select, enter }?: {
|
|
172
|
+
select?: boolean;
|
|
173
|
+
enter?: boolean;
|
|
174
|
+
}) => void;
|
|
165
175
|
addApp: (app: BoardAppMetadata, at: WorldPoint) => string;
|
|
166
176
|
addFile: (path: string, at: WorldPoint, snapshot?: BoardMediaSnapshot & BoardFileSnapshotFacts) => string;
|
|
177
|
+
addMedia: (src: string, at: WorldPoint, options?: {
|
|
178
|
+
kind?: BoardMediaItemKind;
|
|
179
|
+
snapshot?: BoardMediaSnapshot;
|
|
180
|
+
}) => string;
|
|
167
181
|
addTask: (taskRunId: string, snapshot: BoardTaskSnapshot, at: WorldPoint, metadata?: Record<string, unknown>) => string;
|
|
168
182
|
addTaskWithSources: (taskRunId: string, snapshot: BoardTaskSnapshot, at: WorldPoint, sources: BoardTaskSource[], metadata?: Record<string, unknown>) => string | null;
|
|
169
183
|
addText: (text: string, at: WorldPoint) => void;
|
|
@@ -198,6 +212,7 @@ declare function createBoardEditor(options: BoardEditorOptions): {
|
|
|
198
212
|
removeKeyframe: (trackId: string, at: number) => void;
|
|
199
213
|
setKeyframeEase: (trackId: string, at: number, ease: string | null) => void;
|
|
200
214
|
createAnimation: (name: string, duration?: number) => string;
|
|
215
|
+
createSequence: (name: string, chapters: BoardChapter[]) => string | null;
|
|
201
216
|
pointerDown: (event: BoardPointerEvent) => void;
|
|
202
217
|
pointerMove: (event: BoardPointerEvent) => void;
|
|
203
218
|
pointerUp: (event: BoardPointerEvent) => void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { clampBoardStrokeSize } from "../../protocol/dist/board-constants.js";
|
|
2
|
+
import { hasBoardScripts } from "../model/script-index.js";
|
|
2
3
|
import { normalizeViewport } from "../model/geometry.js";
|
|
3
|
-
import { isBoardColorId } from "../model/palette.js";
|
|
4
4
|
import { buildBoardScene } from "../model/scene.js";
|
|
5
|
+
import { isBoardColorId } from "../model/palette.js";
|
|
5
6
|
import { isShapeKind } from "../model/shape-types.js";
|
|
6
7
|
import { createBoardToolStyles } from "../model/tool-styles.js";
|
|
7
8
|
import { titleForBoardItem } from "../model/items/create.js";
|
|
@@ -145,6 +146,9 @@ function createBoardEditor(options) {
|
|
|
145
146
|
registry,
|
|
146
147
|
readonly: options.readonly === true,
|
|
147
148
|
subscribe: channels.subscribe,
|
|
149
|
+
get hasScripts() {
|
|
150
|
+
return read("document", () => hasBoardScripts(state.base));
|
|
151
|
+
},
|
|
148
152
|
get document() {
|
|
149
153
|
return read("document", () => state.base);
|
|
150
154
|
},
|
|
@@ -344,6 +348,9 @@ function createBoardEditor(options) {
|
|
|
344
348
|
setSettings: document.setSettings,
|
|
345
349
|
previewSettings: document.previewSettings,
|
|
346
350
|
setPlayedItems: document.setPlayedItems,
|
|
351
|
+
setScriptItems: document.setScriptItems,
|
|
352
|
+
animationItems: document.animationItems,
|
|
353
|
+
commitPatch: document.commitPatch,
|
|
347
354
|
consumeRecentlyAdded: document.consumeRecentlyAdded,
|
|
348
355
|
itemAt: tracked(["scene", "camera"], query.itemAt),
|
|
349
356
|
itemById: tracked(["scene"], query.itemById),
|
|
@@ -380,8 +387,10 @@ function createBoardEditor(options) {
|
|
|
380
387
|
bringToFront: selection.bringToFront,
|
|
381
388
|
sendToBack: selection.sendToBack,
|
|
382
389
|
addItem: creation.addItem,
|
|
390
|
+
addItems: creation.add,
|
|
383
391
|
addApp: creation.addApp,
|
|
384
392
|
addFile: creation.addFile,
|
|
393
|
+
addMedia: creation.addMedia,
|
|
385
394
|
addTask: creation.addTask,
|
|
386
395
|
addTaskWithSources: creation.addTaskWithSources,
|
|
387
396
|
addText: creation.addText,
|
|
@@ -413,6 +422,7 @@ function createBoardEditor(options) {
|
|
|
413
422
|
removeKeyframe: animation.removeKeyframe,
|
|
414
423
|
setKeyframeEase: animation.setKeyframeEase,
|
|
415
424
|
createAnimation: animation.createAnimation,
|
|
425
|
+
createSequence: animation.createSequence,
|
|
416
426
|
pointerDown: interaction.pointerDown,
|
|
417
427
|
pointerMove: interaction.pointerMove,
|
|
418
428
|
pointerUp: interaction.pointerUp,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Rect, WorldPoint } from "../model/geometry.js";
|
|
2
|
-
import { BoardSceneItem } from "../model/scene.js";
|
|
2
|
+
import { BoardScene, BoardSceneItem } from "../model/scene.js";
|
|
3
3
|
import "../model/index.js";
|
|
4
4
|
import { EditorContext } from "./context.js";
|
|
5
5
|
//#region src/board/editor/query.d.ts
|
|
6
6
|
declare function createQueryModule(ctx: EditorContext): {
|
|
7
7
|
invalidate: (ids?: readonly string[]) => void;
|
|
8
|
-
boundsOf: (item: BoardSceneItem) => Rect;
|
|
8
|
+
boundsOf: (item: BoardSceneItem, scene?: BoardScene) => Rect;
|
|
9
9
|
itemById: (id: string) => BoardSceneItem | null;
|
|
10
10
|
isLocked: (item: {
|
|
11
11
|
locked?: boolean;
|
|
@@ -14,10 +14,10 @@ declare function createQueryModule(ctx: EditorContext): {
|
|
|
14
14
|
rootsOf: (ids: Iterable<string>) => string[];
|
|
15
15
|
withSubtrees: (ids: Iterable<string>) => Set<string>;
|
|
16
16
|
mediaIdsForPath: (path: string) => ReadonlySet<string>;
|
|
17
|
-
contentBounds: (ids?: Iterable<string
|
|
18
|
-
idsInRect: (rect: Rect) => string[];
|
|
17
|
+
contentBounds: (ids?: Iterable<string>, scene?: BoardScene) => Rect | null;
|
|
18
|
+
idsInRect: (rect: Rect, includeTemporary?: boolean) => string[];
|
|
19
19
|
frameAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
|
|
20
|
-
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string
|
|
20
|
+
itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>, includeTemporary?: boolean) => BoardSceneItem | null;
|
|
21
21
|
labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
|
|
22
22
|
arrowHandleAt: (point: WorldPoint) => "start" | "end" | "mid" | null;
|
|
23
23
|
connectTargetAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
|
|
1
2
|
import { rectContainsPoint, unionRects } from "../model/geometry.js";
|
|
2
3
|
import { arrowHitRadius, arrowPathBounds, distanceToArrow, resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
3
4
|
import { createSpatialIndex } from "./spatial.js";
|
|
@@ -53,8 +54,8 @@ function createQueryModule(ctx) {
|
|
|
53
54
|
}
|
|
54
55
|
spatial.upsert(upserts);
|
|
55
56
|
}
|
|
56
|
-
function boundsOf(item) {
|
|
57
|
-
if (item.type === "arrow") return arrowPathBounds(resolveSceneArrow(item,
|
|
57
|
+
function boundsOf(item, scene = state.scene) {
|
|
58
|
+
if (item.type === "arrow") return arrowPathBounds(resolveSceneArrow(item, scene), item.style.strokeWidth);
|
|
58
59
|
return registry.bounds(item);
|
|
59
60
|
}
|
|
60
61
|
function itemById(id) {
|
|
@@ -87,7 +88,7 @@ function createQueryModule(ctx) {
|
|
|
87
88
|
const result = /* @__PURE__ */ new Set();
|
|
88
89
|
for (const id of ids) {
|
|
89
90
|
result.add(id);
|
|
90
|
-
for (const child of state.scene.descendants(id)) result.add(child);
|
|
91
|
+
for (const child of state.scene.descendants(id)) if (state.base.items[child]) result.add(child);
|
|
91
92
|
}
|
|
92
93
|
return result;
|
|
93
94
|
}
|
|
@@ -97,6 +98,7 @@ function createQueryModule(ctx) {
|
|
|
97
98
|
for (const item of state.scene.items) {
|
|
98
99
|
if (item.type !== "image" && item.type !== "video" && item.type !== "audio") continue;
|
|
99
100
|
const src = item.props.src;
|
|
101
|
+
if (parseBoardMediaSource(src)?.kind !== "file") continue;
|
|
100
102
|
const ids = mediaIdsByPath.get(src) ?? /* @__PURE__ */ new Set();
|
|
101
103
|
ids.add(item.id);
|
|
102
104
|
mediaIdsByPath.set(src, ids);
|
|
@@ -104,20 +106,20 @@ function createQueryModule(ctx) {
|
|
|
104
106
|
}
|
|
105
107
|
return mediaIdsByPath.get(path) ?? NO_IDS;
|
|
106
108
|
}
|
|
107
|
-
function contentBounds(ids) {
|
|
108
|
-
const scene = state.scene;
|
|
109
|
+
function contentBounds(ids, scene = state.scene) {
|
|
109
110
|
const list = ids ? [...ids].flatMap((id) => scene.get(id) ? [scene.get(id)] : []) : scene.items;
|
|
110
|
-
return unionRects(list.map(boundsOf));
|
|
111
|
+
return unionRects((ctx.document.hasScriptItems ? list.filter((item) => Boolean(state.base.items[item.id])) : list).map((item) => boundsOf(item, scene)));
|
|
111
112
|
}
|
|
112
|
-
function idsInRect(rect) {
|
|
113
|
+
function idsInRect(rect, includeTemporary = false) {
|
|
113
114
|
ensureSpatial();
|
|
114
|
-
|
|
115
|
+
const ids = spatial.idsInRect(rect);
|
|
116
|
+
return includeTemporary || !ctx.document.hasScriptItems ? ids : ids.filter((id) => Boolean(state.base.items[id]));
|
|
115
117
|
}
|
|
116
118
|
function frameAt(point, exclude) {
|
|
117
119
|
ensureSpatial();
|
|
118
120
|
for (const id of spatial.idsAtPoint(point)) {
|
|
119
121
|
const item = state.scene.get(id);
|
|
120
|
-
if (item?.type !== "frame" || exclude?.has(id)) continue;
|
|
122
|
+
if (!state.base.items[id] || item?.type !== "frame" || exclude?.has(id)) continue;
|
|
121
123
|
if (registry.hitTest(item, point)) return item;
|
|
122
124
|
}
|
|
123
125
|
return null;
|
|
@@ -129,11 +131,11 @@ function createQueryModule(ctx) {
|
|
|
129
131
|
}
|
|
130
132
|
return registry.hitTest(item, point);
|
|
131
133
|
}
|
|
132
|
-
function itemAt(point, exclude) {
|
|
134
|
+
function itemAt(point, exclude, includeTemporary = false) {
|
|
133
135
|
ensureSpatial();
|
|
134
136
|
for (const id of spatial.idsAtPoint(point)) {
|
|
135
137
|
const item = state.scene.get(id);
|
|
136
|
-
if (item && !exclude?.has(id) && hitsItem(item, point)) return item;
|
|
138
|
+
if (item && (includeTemporary || state.base.items[id]) && !exclude?.has(id) && hitsItem(item, point)) return item;
|
|
137
139
|
}
|
|
138
140
|
return null;
|
|
139
141
|
}
|
|
@@ -144,7 +146,7 @@ function createQueryModule(ctx) {
|
|
|
144
146
|
const candidates = selection.length === 1 ? [...selection, ...nearby] : nearby;
|
|
145
147
|
for (const id of new Set(candidates)) {
|
|
146
148
|
const item = scene.get(id);
|
|
147
|
-
if (!item || isLocked(item)) continue;
|
|
149
|
+
if (!item || !state.base.items[id] || isLocked(item)) continue;
|
|
148
150
|
if (item.type === "frame" && rectContainsPoint({
|
|
149
151
|
x: item.frame.x,
|
|
150
152
|
y: item.frame.y - 22,
|
|
@@ -19,9 +19,10 @@ function createSelectionModule(ctx) {
|
|
|
19
19
|
const bounds = memo(() => [items()], (selected) => selectionBounds(selected.map((item) => item.frame)));
|
|
20
20
|
const transform = memo(() => [items(), bounds()], (selected, rect) => resolveSelectionTransform(registry, selected, rect));
|
|
21
21
|
function set(ids) {
|
|
22
|
-
state.selection = ids;
|
|
22
|
+
state.selection = ids.filter((id) => Boolean(state.base.items[id]));
|
|
23
23
|
}
|
|
24
24
|
function toggle(id) {
|
|
25
|
+
if (!state.base.items[id]) return;
|
|
25
26
|
const { selection } = state;
|
|
26
27
|
state.selection = selection.includes(id) ? selection.filter((selected) => selected !== id) : [...selection, id];
|
|
27
28
|
}
|