@neta-art/cohub 9.0.1 → 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 +108 -102
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +17 -0
- package/dist/board/editor/animation.js +187 -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 +129 -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 +121 -0
- package/dist/board/editor/creation.d.ts +51 -0
- package/dist/board/editor/creation.js +225 -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 +41 -0
- package/dist/board/editor/document.js +302 -0
- package/dist/board/editor/editor.d.ts +225 -0
- package/dist/board/editor/editor.js +439 -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 +203 -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 +197 -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 +28 -6
- package/dist/board/export/index.js +47 -13
- package/dist/board/export/node/index.d.ts +56 -0
- package/dist/board/{headless → export/node}/index.js +26 -21
- 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 +8 -5
- package/dist/board/export/scene.js +24 -9
- package/dist/board/{animation.d.ts → model/animation.d.ts} +8 -5
- package/dist/board/{animation.js → model/animation.js} +37 -10
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/model/authoring.d.ts +20 -0
- package/dist/board/model/authoring.js +185 -0
- 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 +3 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +2 -2
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +4 -3
- 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} +7 -3
- package/dist/board/model/index.d.ts +42 -0
- package/dist/board/model/index.js +39 -0
- 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 +61 -0
- package/dist/board/model/items/create.js +272 -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} +4 -3
- package/dist/board/{media-playback.js → model/media-playback.js} +15 -5
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +8 -2
- 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/model/sampling.d.ts +19 -0
- package/dist/board/model/sampling.js +96 -0
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- 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/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/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 +10 -7
- 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 +3 -2
- package/dist/board/render/palette.js +7 -3
- package/dist/board/render/renderers/arrow-card-renderer.js +4 -3
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -10
- package/dist/board/render/renderers/board-renderer-registry.js +17 -17
- 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 +4 -3
- package/dist/board/render/renderers/effect-card-renderer.js +22 -5
- 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/frame-card-renderer.js +30 -14
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/image-card-renderer.js +43 -4
- package/dist/board/render/renderers/shape-card-renderer.js +11 -28
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +6 -4
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- 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 +3 -2
- package/dist/board/render/stroke.js +23 -2
- package/dist/board/render/text-measurement.js +2 -2
- 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 +355 -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/maintenance.d.ts +5 -0
- package/dist/board/stage/maintenance.js +11 -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 +515 -0
- 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 +76 -0
- package/dist/board/stage/stage.js +774 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +153 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +289 -23
- package/dist/chunks/host-bridge.d.ts +2 -2
- package/dist/chunks/http.d.ts +185 -71
- package/dist/chunks/http.js +368 -269
- 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 +1393 -129
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- 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 +3 -2
- 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/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/display/index.d.ts +1 -0
- package/dist/protocol/dist/index.d.ts +7 -5
- package/dist/protocol/dist/realtime/types.d.ts +1 -0
- package/dist/protocol/dist/rtc/index.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +21 -8
- 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/headless/sketch.js +0 -146
- package/dist/board/index.d.ts +0 -30
- package/dist/board/index.js +0 -29
- 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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { cameraForRect, normalizeViewport, panBy, pointToWorld, screenPoint, zoomAround } from "../model/geometry.js";
|
|
2
|
+
import { normalizeWheelDelta, wheelZoomFactor } from "./wheel.js";
|
|
3
|
+
//#region src/board/editor/camera.ts
|
|
4
|
+
const ZOOM_STEP = 1.28;
|
|
5
|
+
const CAMERA_ANIMATION_MS = 240;
|
|
6
|
+
function easeOutCubic(t) {
|
|
7
|
+
return 1 - (1 - t) * (1 - t) * (1 - t);
|
|
8
|
+
}
|
|
9
|
+
function createCameraModule(ctx) {
|
|
10
|
+
const { state } = ctx;
|
|
11
|
+
let animation = 0;
|
|
12
|
+
function setCamera(viewport) {
|
|
13
|
+
state.camera = normalizeViewport(viewport, state.camera);
|
|
14
|
+
state.hoverPoint = null;
|
|
15
|
+
state.hoverId = null;
|
|
16
|
+
}
|
|
17
|
+
function setCameraPolicy(policy) {
|
|
18
|
+
if (state.cameraPolicy === policy) return;
|
|
19
|
+
state.cameraPolicy = policy;
|
|
20
|
+
ctx.preferencesChanged();
|
|
21
|
+
}
|
|
22
|
+
function takeCameraControl() {
|
|
23
|
+
setCameraPolicy("free");
|
|
24
|
+
}
|
|
25
|
+
function cancelAnimation() {
|
|
26
|
+
if (animation) cancelAnimationFrame(animation);
|
|
27
|
+
animation = 0;
|
|
28
|
+
}
|
|
29
|
+
function animateCamera(target) {
|
|
30
|
+
cancelAnimation();
|
|
31
|
+
const from = normalizeViewport(state.camera);
|
|
32
|
+
const to = normalizeViewport(target, from);
|
|
33
|
+
const started = performance.now();
|
|
34
|
+
const step = (now) => {
|
|
35
|
+
const t = Math.min(1, (now - started) / CAMERA_ANIMATION_MS);
|
|
36
|
+
const eased = easeOutCubic(t);
|
|
37
|
+
setCamera({
|
|
38
|
+
x: from.x + (to.x - from.x) * eased,
|
|
39
|
+
y: from.y + (to.y - from.y) * eased,
|
|
40
|
+
zoom: from.zoom + (to.zoom - from.zoom) * eased
|
|
41
|
+
});
|
|
42
|
+
animation = t < 1 ? requestAnimationFrame(step) : 0;
|
|
43
|
+
};
|
|
44
|
+
animation = requestAnimationFrame(step);
|
|
45
|
+
}
|
|
46
|
+
function surfaceCenter() {
|
|
47
|
+
return screenPoint(state.surfaceSize.width / 2, state.surfaceSize.height / 2);
|
|
48
|
+
}
|
|
49
|
+
function viewCenter() {
|
|
50
|
+
return pointToWorld(surfaceCenter(), state.camera);
|
|
51
|
+
}
|
|
52
|
+
function visibleRect() {
|
|
53
|
+
const { camera, surfaceSize } = state;
|
|
54
|
+
if (surfaceSize.width <= 0 || surfaceSize.height <= 0) return null;
|
|
55
|
+
return {
|
|
56
|
+
x: -camera.x / camera.zoom,
|
|
57
|
+
y: -camera.y / camera.zoom,
|
|
58
|
+
width: surfaceSize.width / camera.zoom,
|
|
59
|
+
height: surfaceSize.height / camera.zoom
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function zoomAt(point, factor, animate = false) {
|
|
63
|
+
const target = zoomAround(state.camera, point, state.camera.zoom * factor);
|
|
64
|
+
if (animate) animateCamera(target);
|
|
65
|
+
else setCamera(target);
|
|
66
|
+
}
|
|
67
|
+
function focusRect(rect, focus = {}) {
|
|
68
|
+
const { surfaceSize } = state;
|
|
69
|
+
if (surfaceSize.width <= 0 || surfaceSize.height <= 0) return;
|
|
70
|
+
const target = cameraForRect(rect, surfaceSize, {
|
|
71
|
+
fit: focus.fit ?? "contain",
|
|
72
|
+
padding: focus.padding ?? 32,
|
|
73
|
+
minZoom: focus.minZoom,
|
|
74
|
+
maxZoom: focus.maxZoom
|
|
75
|
+
});
|
|
76
|
+
if (focus.animate === false) setCamera(target);
|
|
77
|
+
else animateCamera(target);
|
|
78
|
+
}
|
|
79
|
+
function fitView(focus = {}) {
|
|
80
|
+
const content = ctx.query.contentBounds();
|
|
81
|
+
if (content && state.surfaceSize.width > 0) focusRect(content, {
|
|
82
|
+
padding: 64,
|
|
83
|
+
...focus
|
|
84
|
+
});
|
|
85
|
+
else if (focus.animate === false) setCamera({
|
|
86
|
+
x: 0,
|
|
87
|
+
y: 0,
|
|
88
|
+
zoom: 1
|
|
89
|
+
});
|
|
90
|
+
else animateCamera({
|
|
91
|
+
x: 0,
|
|
92
|
+
y: 0,
|
|
93
|
+
zoom: 1
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function focusItems(ids, focus) {
|
|
97
|
+
const rect = ctx.query.contentBounds(ids);
|
|
98
|
+
if (rect) focusRect(rect, focus);
|
|
99
|
+
}
|
|
100
|
+
function focusSelection(focus) {
|
|
101
|
+
const rect = ctx.query.contentBounds(state.selection);
|
|
102
|
+
if (rect) focusRect(rect, focus);
|
|
103
|
+
else fitView();
|
|
104
|
+
}
|
|
105
|
+
function wheel(point, deltaX, deltaY, zoomKey, deltaMode = 0) {
|
|
106
|
+
cancelAnimation();
|
|
107
|
+
const { camera } = state;
|
|
108
|
+
if (zoomKey) {
|
|
109
|
+
setCamera(zoomAround(camera, point, camera.zoom * wheelZoomFactor(deltaY, deltaMode)));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setCamera(panBy(camera, -normalizeWheelDelta(deltaX, deltaMode) * 1.15, -normalizeWheelDelta(deltaY, deltaMode) * 1.15));
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
setCamera,
|
|
116
|
+
setCameraPolicy,
|
|
117
|
+
takeCameraControl,
|
|
118
|
+
cancelAnimation,
|
|
119
|
+
viewCenter,
|
|
120
|
+
visibleRect,
|
|
121
|
+
zoomAt,
|
|
122
|
+
zoomIn: () => zoomAt(surfaceCenter(), ZOOM_STEP, true),
|
|
123
|
+
zoomOut: () => zoomAt(surfaceCenter(), 1 / ZOOM_STEP, true),
|
|
124
|
+
resetZoom: () => animateCamera({
|
|
125
|
+
...state.camera,
|
|
126
|
+
zoom: 1
|
|
127
|
+
}),
|
|
128
|
+
focusRect,
|
|
129
|
+
fitView,
|
|
130
|
+
focusItems,
|
|
131
|
+
focusNode: (id, focus) => focusItems([id], focus),
|
|
132
|
+
focusSelection,
|
|
133
|
+
wheel
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { createCameraModule };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BOARD_CLIPBOARD_KIND, BOARD_CLIPBOARD_MIME } from "../../protocol/dist/board.js";
|
|
3
|
+
import "../../protocol/dist/index.js";
|
|
4
|
+
import "../model/index.js";
|
|
5
|
+
//#region src/board/editor/clipboard.d.ts
|
|
6
|
+
/** How far a duplicate or repeated paste lands from its source. */
|
|
7
|
+
declare const DUPLICATE_OFFSET = 24;
|
|
8
|
+
declare const BOARD_CLIPBOARD_VERSION: 3;
|
|
9
|
+
declare const MAX_CLIPBOARD_ITEMS = 2000;
|
|
10
|
+
declare const MAX_CLIPBOARD_CHARS: number;
|
|
11
|
+
type BoardClipboardPayload = {
|
|
12
|
+
kind: typeof BOARD_CLIPBOARD_KIND;
|
|
13
|
+
version: typeof BOARD_CLIPBOARD_VERSION;
|
|
14
|
+
items: Record<string, BoardItem>;
|
|
15
|
+
origin: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
declare function encodeClipboard(items: Record<string, BoardItem>, origin: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}): BoardClipboardPayload | null;
|
|
24
|
+
declare function parseClipboard(raw: unknown): BoardClipboardPayload | null;
|
|
25
|
+
declare function materializeClipboard(payload: BoardClipboardPayload, at: {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
}, resolveEnd?: (id: string, which: "start" | "end") => {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
} | null): Record<string, BoardItem>;
|
|
32
|
+
declare function remapItems(items: Record<string, BoardItem>, idMap: ReadonlyMap<string, string>, place?: (item: BoardItem) => BoardItem, resolveEnd?: (id: string, which: "start" | "end") => {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
} | null): Record<string, BoardItem>;
|
|
36
|
+
declare function defaultPasteOffset(count?: number): {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BoardClipboardPayload, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { arrowBindings, isArrowBinding } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { BOARD_CLIPBOARD_KIND, BOARD_CLIPBOARD_MIME } from "../../protocol/dist/board.js";
|
|
4
|
+
import { createBoardItemId } from "../model/items/id.js";
|
|
5
|
+
//#region src/board/editor/clipboard.ts
|
|
6
|
+
/** How far a duplicate or repeated paste lands from its source. */
|
|
7
|
+
const DUPLICATE_OFFSET = 24;
|
|
8
|
+
const BOARD_CLIPBOARD_VERSION = 3;
|
|
9
|
+
const MAX_CLIPBOARD_ITEMS = 2e3;
|
|
10
|
+
const MAX_CLIPBOARD_CHARS = 4194304;
|
|
11
|
+
function encodeClipboard(items, origin) {
|
|
12
|
+
const ids = Object.keys(items);
|
|
13
|
+
if (ids.length === 0 || ids.length > 2e3) return null;
|
|
14
|
+
return {
|
|
15
|
+
kind: BOARD_CLIPBOARD_KIND,
|
|
16
|
+
version: 3,
|
|
17
|
+
items: Object.fromEntries(ids.map((id) => {
|
|
18
|
+
const item = items[id];
|
|
19
|
+
return [id, item.parent && items[item.parent] ? item : shiftItem(item, -origin.x, -origin.y)];
|
|
20
|
+
})),
|
|
21
|
+
origin
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function parseClipboard(raw) {
|
|
25
|
+
if (raw == null) return null;
|
|
26
|
+
if (typeof raw === "string") {
|
|
27
|
+
if (raw.length > 4194304) return null;
|
|
28
|
+
try {
|
|
29
|
+
raw = JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!raw || typeof raw !== "object") return null;
|
|
35
|
+
const record = raw;
|
|
36
|
+
if (record.kind !== "cohub.board.clipboard" || record.version !== 3) return null;
|
|
37
|
+
const origin = record.origin;
|
|
38
|
+
if (typeof origin?.x !== "number" || typeof origin.y !== "number" || !Number.isFinite(origin.x) || !Number.isFinite(origin.y)) return null;
|
|
39
|
+
if (!record.items || typeof record.items !== "object" || Array.isArray(record.items)) return null;
|
|
40
|
+
const entries = Object.entries(record.items);
|
|
41
|
+
if (entries.length === 0 || entries.length > 2e3) return null;
|
|
42
|
+
const items = {};
|
|
43
|
+
for (const [id, value] of entries) {
|
|
44
|
+
const parsed = parseBoardItem(value);
|
|
45
|
+
if (!parsed.ok) return null;
|
|
46
|
+
items[id] = parsed.item;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
kind: BOARD_CLIPBOARD_KIND,
|
|
50
|
+
version: 3,
|
|
51
|
+
items,
|
|
52
|
+
origin: {
|
|
53
|
+
x: origin.x,
|
|
54
|
+
y: origin.y
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function materializeClipboard(payload, at, resolveEnd = () => null) {
|
|
59
|
+
const idMap = new Map(Object.keys(payload.items).map((id) => [id, createBoardItemId()]));
|
|
60
|
+
return remapItems(payload.items, idMap, (item) => item.parent && idMap.has(item.parent) ? item : shiftItem(item, at.x, at.y), resolveEnd);
|
|
61
|
+
}
|
|
62
|
+
function remapItems(items, idMap, place = (item) => item, resolveEnd = () => null) {
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const [id, source] of Object.entries(items)) {
|
|
65
|
+
let item = place(source);
|
|
66
|
+
const { locked: _locked, ...unlocked } = item;
|
|
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
|
+
};
|
|
75
|
+
if (item.parent) {
|
|
76
|
+
const parent = idMap.get(item.parent);
|
|
77
|
+
if (parent) item = {
|
|
78
|
+
...item,
|
|
79
|
+
parent
|
|
80
|
+
};
|
|
81
|
+
else {
|
|
82
|
+
const { parent: _parent, ...root } = item;
|
|
83
|
+
item = root;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (item.type === "arrow" && arrowBindings(item).length) {
|
|
87
|
+
const end = (which) => {
|
|
88
|
+
const value = item.type === "arrow" ? item.props[which] : null;
|
|
89
|
+
if (!value || !isArrowBinding(value)) return value;
|
|
90
|
+
const mapped = idMap.get(value.item);
|
|
91
|
+
if (mapped) return {
|
|
92
|
+
...value,
|
|
93
|
+
item: mapped
|
|
94
|
+
};
|
|
95
|
+
return resolveEnd(id, which) ?? {
|
|
96
|
+
x: 0,
|
|
97
|
+
y: 0
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
item = {
|
|
101
|
+
...item,
|
|
102
|
+
props: {
|
|
103
|
+
...item.props,
|
|
104
|
+
start: end("start"),
|
|
105
|
+
end: end("end")
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
result[idMap.get(id) ?? createBoardItemId()] = item;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
function defaultPasteOffset(count = 1) {
|
|
114
|
+
return {
|
|
115
|
+
x: 24 * count,
|
|
116
|
+
y: 24 * count
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function shiftItem(item, dx, dy) {
|
|
120
|
+
return {
|
|
121
|
+
...item,
|
|
122
|
+
position: {
|
|
123
|
+
x: item.position.x + dx,
|
|
124
|
+
y: item.position.y + dy
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BoardAnchorSide } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { WorldPoint } from "../model/geometry.js";
|
|
4
|
+
import "../model/index.js";
|
|
5
|
+
//#region src/board/editor/connection-ports.d.ts
|
|
6
|
+
declare const CONNECTION_SIDES: readonly BoardAnchorSide[];
|
|
7
|
+
declare const CONNECTION_PORT_OFFSET = 14;
|
|
8
|
+
declare const CONNECTION_PORT_RADIUS = 4;
|
|
9
|
+
declare const CONNECTION_PORT_HIT_RADIUS = 11;
|
|
10
|
+
declare const CONNECTION_PORT_TOUCH_HIT_RADIUS = 22;
|
|
11
|
+
type ConnectionPort = {
|
|
12
|
+
side: BoardAnchorSide;
|
|
13
|
+
point: WorldPoint;
|
|
14
|
+
};
|
|
15
|
+
declare function portHitRadius(pointerType: string): number;
|
|
16
|
+
declare function connectionPorts(frame: BoardFrame, zoom: number): ConnectionPort[];
|
|
17
|
+
declare function connectionPortAt(frame: BoardFrame, point: WorldPoint, zoom: number, pointerType: string): ConnectionPort | null;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, connectionPortAt, connectionPorts, portHitRadius };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { degToRad, frameRect, rectCenter, rotatePointAround, worldPoint } from "../model/geometry.js";
|
|
2
|
+
//#region src/board/editor/connection-ports.ts
|
|
3
|
+
const CONNECTION_SIDES = [
|
|
4
|
+
"top",
|
|
5
|
+
"right",
|
|
6
|
+
"bottom",
|
|
7
|
+
"left"
|
|
8
|
+
];
|
|
9
|
+
const CONNECTION_PORT_OFFSET = 14;
|
|
10
|
+
const CONNECTION_PORT_RADIUS = 4;
|
|
11
|
+
const CONNECTION_PORT_HIT_RADIUS = 11;
|
|
12
|
+
const CONNECTION_PORT_TOUCH_HIT_RADIUS = 22;
|
|
13
|
+
function portHitRadius(pointerType) {
|
|
14
|
+
return pointerType === "touch" || pointerType === "pen" ? 22 : 11;
|
|
15
|
+
}
|
|
16
|
+
const SIDE_NORMAL = {
|
|
17
|
+
top: {
|
|
18
|
+
x: 0,
|
|
19
|
+
y: -1
|
|
20
|
+
},
|
|
21
|
+
right: {
|
|
22
|
+
x: 1,
|
|
23
|
+
y: 0
|
|
24
|
+
},
|
|
25
|
+
bottom: {
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 1
|
|
28
|
+
},
|
|
29
|
+
left: {
|
|
30
|
+
x: -1,
|
|
31
|
+
y: 0
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const SIDE_ANCHOR = {
|
|
35
|
+
top: {
|
|
36
|
+
nx: .5,
|
|
37
|
+
ny: 0
|
|
38
|
+
},
|
|
39
|
+
right: {
|
|
40
|
+
nx: 1,
|
|
41
|
+
ny: .5
|
|
42
|
+
},
|
|
43
|
+
bottom: {
|
|
44
|
+
nx: .5,
|
|
45
|
+
ny: 1
|
|
46
|
+
},
|
|
47
|
+
left: {
|
|
48
|
+
nx: 0,
|
|
49
|
+
ny: .5
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function connectionPorts(frame, zoom) {
|
|
53
|
+
const offset = 14 / Math.max(zoom, 1e-4);
|
|
54
|
+
const center = rectCenter(frameRect(frame));
|
|
55
|
+
const rotation = frame.rotation ? degToRad(frame.rotation) : 0;
|
|
56
|
+
return CONNECTION_SIDES.map((side) => {
|
|
57
|
+
const anchor = SIDE_ANCHOR[side];
|
|
58
|
+
const base = worldPoint(frame.x + anchor.nx * frame.width, frame.y + anchor.ny * frame.height);
|
|
59
|
+
const rotated = rotation ? rotatePointAround(base, center, rotation) : base;
|
|
60
|
+
const normal = SIDE_NORMAL[side];
|
|
61
|
+
const direction = rotation ? {
|
|
62
|
+
x: normal.x * Math.cos(rotation) - normal.y * Math.sin(rotation),
|
|
63
|
+
y: normal.x * Math.sin(rotation) + normal.y * Math.cos(rotation)
|
|
64
|
+
} : normal;
|
|
65
|
+
return {
|
|
66
|
+
side,
|
|
67
|
+
point: worldPoint(rotated.x + direction.x * offset, rotated.y + direction.y * offset)
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function connectionPortAt(frame, point, zoom, pointerType) {
|
|
72
|
+
const radius = portHitRadius(pointerType) / Math.max(zoom, 1e-4);
|
|
73
|
+
let closest = null;
|
|
74
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
75
|
+
for (const port of connectionPorts(frame, zoom)) {
|
|
76
|
+
const distance = Math.hypot(port.point.x - point.x, port.point.y - point.y);
|
|
77
|
+
if (distance <= radius && distance < closestDistance) {
|
|
78
|
+
closest = port;
|
|
79
|
+
closestDistance = distance;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return closest;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, connectionPortAt, connectionPorts, portHitRadius };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { BoardDocument, BoardPatch, BoardSettings } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardViewport, WorldPoint } from "../model/geometry.js";
|
|
3
|
+
import { BoardScene } from "../model/scene.js";
|
|
4
|
+
import { BoardToolStyleMap } from "../model/tool-styles.js";
|
|
5
|
+
import { BoardRegistry } from "../model/items/registry.js";
|
|
6
|
+
import "../model/index.js";
|
|
7
|
+
import { DocumentModule } from "./document.js";
|
|
8
|
+
import { QueryModule } from "./query.js";
|
|
9
|
+
import { CameraModule } from "./camera.js";
|
|
10
|
+
import { SelectionModule } from "./selection.js";
|
|
11
|
+
import { BoardInteraction, InteractionModule } from "./interaction.js";
|
|
12
|
+
import { CreationModule } from "./creation.js";
|
|
13
|
+
import { DeletionModule } from "./deletion.js";
|
|
14
|
+
import { CopyModule } from "./copy.js";
|
|
15
|
+
import { SnapshotsModule } from "./snapshots.js";
|
|
16
|
+
import { RemoteModule } from "./remote.js";
|
|
17
|
+
import { SnapGuide } from "./snapping.js";
|
|
18
|
+
import { BoardEditorChannel, BoardEditorChannels } from "./state.js";
|
|
19
|
+
import { BoardToolId } from "./tool.js";
|
|
20
|
+
import { AnimationModule } from "./animation.js";
|
|
21
|
+
//#region src/board/editor/context.d.ts
|
|
22
|
+
type BoardCameraPolicy = "follow" | "free";
|
|
23
|
+
type BoardPlayhead = {
|
|
24
|
+
animationId: string;
|
|
25
|
+
time: number;
|
|
26
|
+
};
|
|
27
|
+
type BoardUndoEntry = {
|
|
28
|
+
undo: BoardPatch;
|
|
29
|
+
redo: BoardPatch;
|
|
30
|
+
};
|
|
31
|
+
type BoardEditorState = {
|
|
32
|
+
base: BoardDocument;
|
|
33
|
+
settingsPreview: BoardSettings | null;
|
|
34
|
+
scene: BoardScene;
|
|
35
|
+
structureVersion: number;
|
|
36
|
+
geometryVersion: number;
|
|
37
|
+
selection: string[];
|
|
38
|
+
camera: BoardViewport;
|
|
39
|
+
surfaceSize: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
};
|
|
43
|
+
cameraPolicy: BoardCameraPolicy;
|
|
44
|
+
tool: BoardToolId;
|
|
45
|
+
toolStyles: BoardToolStyleMap;
|
|
46
|
+
interaction: BoardInteraction;
|
|
47
|
+
snapGuides: SnapGuide[];
|
|
48
|
+
spaceHeld: boolean;
|
|
49
|
+
hoverId: string | null;
|
|
50
|
+
hoverPoint: WorldPoint | null;
|
|
51
|
+
hoverPointerType: string;
|
|
52
|
+
editingId: string | null;
|
|
53
|
+
draftTextId: string | null;
|
|
54
|
+
playhead: BoardPlayhead | null;
|
|
55
|
+
recording: boolean;
|
|
56
|
+
undoStack: BoardUndoEntry[];
|
|
57
|
+
redoStack: BoardUndoEntry[];
|
|
58
|
+
saveError: string | null;
|
|
59
|
+
pendingCommits: number;
|
|
60
|
+
};
|
|
61
|
+
declare const STATE_CHANNELS: { readonly [K in keyof BoardEditorState]: BoardEditorChannel; };
|
|
62
|
+
type BoardEditorPreferences = {
|
|
63
|
+
toolStyles: BoardToolStyleMap;
|
|
64
|
+
cameraPolicy: BoardCameraPolicy;
|
|
65
|
+
};
|
|
66
|
+
type BoardViewState = {
|
|
67
|
+
visibleRect: {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
} | null;
|
|
73
|
+
selectedNodes: Array<{
|
|
74
|
+
id: string;
|
|
75
|
+
type: string;
|
|
76
|
+
title?: string;
|
|
77
|
+
}>;
|
|
78
|
+
};
|
|
79
|
+
type BoardEditorOptions = {
|
|
80
|
+
document: BoardDocument;
|
|
81
|
+
registry?: BoardRegistry;
|
|
82
|
+
viewport?: BoardViewport;
|
|
83
|
+
initialTool?: BoardToolId;
|
|
84
|
+
key?: string;
|
|
85
|
+
readonly?: boolean;
|
|
86
|
+
preferences?: Partial<BoardEditorPreferences>;
|
|
87
|
+
onPreferencesChange?: (preferences: BoardEditorPreferences) => void;
|
|
88
|
+
onCommit: (patch: BoardPatch) => void | Promise<void>;
|
|
89
|
+
onViewStateChange?: (state: BoardViewState) => void;
|
|
90
|
+
track?: (channel: BoardEditorChannel) => void;
|
|
91
|
+
};
|
|
92
|
+
type EditorContext = {
|
|
93
|
+
readonly options: BoardEditorOptions;
|
|
94
|
+
readonly registry: BoardRegistry;
|
|
95
|
+
readonly state: BoardEditorState;
|
|
96
|
+
readonly channels: BoardEditorChannels;
|
|
97
|
+
preferencesChanged(): void;
|
|
98
|
+
document: DocumentModule;
|
|
99
|
+
query: QueryModule;
|
|
100
|
+
camera: CameraModule;
|
|
101
|
+
selection: SelectionModule;
|
|
102
|
+
creation: CreationModule;
|
|
103
|
+
deletion: DeletionModule;
|
|
104
|
+
copy: CopyModule;
|
|
105
|
+
interaction: InteractionModule;
|
|
106
|
+
animation: AnimationModule;
|
|
107
|
+
snapshots: SnapshotsModule;
|
|
108
|
+
remote: RemoteModule;
|
|
109
|
+
};
|
|
110
|
+
//#endregion
|
|
111
|
+
export { BoardCameraPolicy, BoardEditorOptions, BoardEditorPreferences, BoardEditorState, BoardPlayhead, BoardUndoEntry, BoardViewState, EditorContext, STATE_CHANNELS };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/board/editor/context.ts
|
|
2
|
+
const STATE_CHANNELS = {
|
|
3
|
+
base: "document",
|
|
4
|
+
settingsPreview: "document",
|
|
5
|
+
scene: "scene",
|
|
6
|
+
structureVersion: "scene",
|
|
7
|
+
geometryVersion: "scene",
|
|
8
|
+
selection: "selection",
|
|
9
|
+
camera: "camera",
|
|
10
|
+
surfaceSize: "camera",
|
|
11
|
+
cameraPolicy: "camera",
|
|
12
|
+
tool: "tool",
|
|
13
|
+
toolStyles: "tool",
|
|
14
|
+
interaction: "interaction",
|
|
15
|
+
snapGuides: "interaction",
|
|
16
|
+
spaceHeld: "interaction",
|
|
17
|
+
hoverId: "hover",
|
|
18
|
+
hoverPoint: "hover",
|
|
19
|
+
hoverPointerType: "hover",
|
|
20
|
+
editingId: "editing",
|
|
21
|
+
draftTextId: "editing",
|
|
22
|
+
playhead: "playback",
|
|
23
|
+
recording: "playback",
|
|
24
|
+
undoStack: "history",
|
|
25
|
+
redoStack: "history",
|
|
26
|
+
saveError: "status",
|
|
27
|
+
pendingCommits: "status"
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { STATE_CHANNELS };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { WorldPoint } from "../model/geometry.js";
|
|
3
|
+
import "../model/index.js";
|
|
4
|
+
import { BoardClipboardPayload } from "./clipboard.js";
|
|
5
|
+
import { EditorContext } from "./context.js";
|
|
6
|
+
//#region src/board/editor/copy.d.ts
|
|
7
|
+
declare function createCopyModule(ctx: EditorContext): {
|
|
8
|
+
duplicates: (ids: string[], offset: number) => Map<string, BoardItem>;
|
|
9
|
+
rootsOfCopies: (copies: ReadonlyMap<string, BoardItem>) => string[];
|
|
10
|
+
duplicateSelection: () => void;
|
|
11
|
+
copySelection: () => BoardClipboardPayload | null;
|
|
12
|
+
cutSelection: () => BoardClipboardPayload | null;
|
|
13
|
+
pasteClipboard: (raw?: unknown, at?: WorldPoint) => void;
|
|
14
|
+
};
|
|
15
|
+
type CopyModule = ReturnType<typeof createCopyModule>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { CopyModule, createCopyModule };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { applyMatrix, arrowBindings, invertMatrix } from "../../protocol/dist/board-layout.js";
|
|
2
|
+
import { resolveSceneArrow } from "../model/arrow-geometry.js";
|
|
3
|
+
import { createBoardItemId } from "../model/items/id.js";
|
|
4
|
+
import { defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems } from "./clipboard.js";
|
|
5
|
+
//#region src/board/editor/copy.ts
|
|
6
|
+
function createCopyModule(ctx) {
|
|
7
|
+
const { state, options } = ctx;
|
|
8
|
+
let internalClipboard = null;
|
|
9
|
+
let pasteCount = 0;
|
|
10
|
+
function detachedSubtrees(ids, scene) {
|
|
11
|
+
const items = {};
|
|
12
|
+
for (const root of ctx.query.rootsOf(ids)) {
|
|
13
|
+
const item = scene.get(root);
|
|
14
|
+
if (!item || !state.base.items[root]) continue;
|
|
15
|
+
const { id: _id, frame, ...stored } = item;
|
|
16
|
+
items[root] = ctx.document.reparent(stored, frame, void 0, scene.layout.box(root));
|
|
17
|
+
for (const child of scene.descendants(root)) {
|
|
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
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return items;
|
|
27
|
+
}
|
|
28
|
+
function arrowEndPoint(scene, id, which) {
|
|
29
|
+
const arrow = scene.get(id);
|
|
30
|
+
if (arrow?.type !== "arrow") return null;
|
|
31
|
+
const resolved = resolveSceneArrow(arrow, scene);
|
|
32
|
+
return applyMatrix(invertMatrix(scene.layout.matrix(id)), (which === "start" ? resolved.start : resolved.end).point);
|
|
33
|
+
}
|
|
34
|
+
function duplicates(ids, offset) {
|
|
35
|
+
const scene = ctx.document.copyScene();
|
|
36
|
+
const copied = detachedSubtrees(ids, scene);
|
|
37
|
+
for (const id of Object.keys(copied)) for (const arrowId of scene.binders(id)) {
|
|
38
|
+
if (!state.base.items[arrowId]) continue;
|
|
39
|
+
const shown = scene.get(arrowId);
|
|
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));
|
|
43
|
+
}
|
|
44
|
+
const idMap = new Map(Object.keys(copied).map((id) => [id, createBoardItemId()]));
|
|
45
|
+
const moved = remapItems(copied, idMap, (item) => item.parent && copied[item.parent] ? item : {
|
|
46
|
+
...item,
|
|
47
|
+
position: {
|
|
48
|
+
x: item.position.x + offset,
|
|
49
|
+
y: item.position.y + offset
|
|
50
|
+
}
|
|
51
|
+
}, (id, which) => arrowEndPoint(scene, id, which));
|
|
52
|
+
let z = ctx.document.topZ(void 0, scene);
|
|
53
|
+
return new Map(Object.entries(moved).map(([id, item]) => [id, item.parent ? item : {
|
|
54
|
+
...item,
|
|
55
|
+
z: z++
|
|
56
|
+
}]));
|
|
57
|
+
}
|
|
58
|
+
function rootsOfCopies(copies) {
|
|
59
|
+
return [...copies].filter(([, item]) => !item.parent || !copies.has(item.parent)).map(([id]) => id);
|
|
60
|
+
}
|
|
61
|
+
function duplicateSelection() {
|
|
62
|
+
if (options.readonly || state.selection.length === 0) return;
|
|
63
|
+
const copies = duplicates(state.selection, 24);
|
|
64
|
+
if (copies.size === 0) return;
|
|
65
|
+
const roots = rootsOfCopies(copies);
|
|
66
|
+
ctx.document.markAdded(roots);
|
|
67
|
+
state.selection = roots;
|
|
68
|
+
ctx.document.commitItems(copies);
|
|
69
|
+
}
|
|
70
|
+
function copySelection() {
|
|
71
|
+
const { selection } = state;
|
|
72
|
+
if (selection.length === 0) return null;
|
|
73
|
+
const scene = ctx.document.copyScene();
|
|
74
|
+
const copied = detachedSubtrees(selection, scene);
|
|
75
|
+
const origin = ctx.query.contentBounds(ctx.query.rootsOf(selection), scene);
|
|
76
|
+
const payload = origin ? encodeClipboard(copied, {
|
|
77
|
+
x: origin.x,
|
|
78
|
+
y: origin.y
|
|
79
|
+
}) : null;
|
|
80
|
+
if (payload) {
|
|
81
|
+
internalClipboard = payload;
|
|
82
|
+
pasteCount = 0;
|
|
83
|
+
}
|
|
84
|
+
return payload;
|
|
85
|
+
}
|
|
86
|
+
function cutSelection() {
|
|
87
|
+
const payload = copySelection();
|
|
88
|
+
if (payload) ctx.deletion.deleteSelection();
|
|
89
|
+
return payload;
|
|
90
|
+
}
|
|
91
|
+
function pasteClipboard(raw, at) {
|
|
92
|
+
if (options.readonly) return;
|
|
93
|
+
const parsed = parseClipboard(raw) ?? (raw == null ? parseClipboard(internalClipboard) : null);
|
|
94
|
+
if (!parsed) return;
|
|
95
|
+
pasteCount += 1;
|
|
96
|
+
const offset = at ?? {
|
|
97
|
+
x: parsed.origin.x + defaultPasteOffset(pasteCount).x,
|
|
98
|
+
y: parsed.origin.y + defaultPasteOffset(pasteCount).y
|
|
99
|
+
};
|
|
100
|
+
const pasted = materializeClipboard(parsed, offset);
|
|
101
|
+
const entries = Object.entries(pasted).map(([id, item]) => ({
|
|
102
|
+
id,
|
|
103
|
+
item
|
|
104
|
+
}));
|
|
105
|
+
const roots = entries.filter(({ item }) => !item.parent).map(({ id }) => id);
|
|
106
|
+
const changes = ctx.creation.placeNew(entries);
|
|
107
|
+
ctx.document.markAdded(roots);
|
|
108
|
+
state.selection = roots;
|
|
109
|
+
ctx.document.commitItems(changes);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
duplicates,
|
|
113
|
+
rootsOfCopies,
|
|
114
|
+
duplicateSelection,
|
|
115
|
+
copySelection,
|
|
116
|
+
cutSelection,
|
|
117
|
+
pasteClipboard
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { createCopyModule };
|