@yuzhouu/canvas-kit 0.1.2
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 +145 -0
- package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
- package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
- package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
- package/_vendor/canvas-core/contextMenu.ts +85 -0
- package/_vendor/canvas-core/editor/Editor.ts +2614 -0
- package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
- package/_vendor/canvas-core/editor/StateNode.ts +162 -0
- package/_vendor/canvas-core/editor/camera.ts +6 -0
- package/_vendor/canvas-core/editor/clipboard.ts +478 -0
- package/_vendor/canvas-core/editor/commands.ts +833 -0
- package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
- package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
- package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
- package/_vendor/canvas-core/editor/events.ts +93 -0
- package/_vendor/canvas-core/editor/index.ts +12 -0
- package/_vendor/canvas-core/editor/interactions.ts +101 -0
- package/_vendor/canvas-core/editor/internal.ts +22 -0
- package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
- package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
- package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
- package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
- package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
- package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
- package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
- package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
- package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
- package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
- package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
- package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
- package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
- package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
- package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
- package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
- package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
- package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
- package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
- package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
- package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
- package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
- package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
- package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
- package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
- package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
- package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
- package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
- package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
- package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
- package/_vendor/canvas-core/editor/selection.ts +489 -0
- package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
- package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
- package/_vendor/canvas-core/exports/integrity.ts +174 -0
- package/_vendor/canvas-core/exports/svg.ts +494 -0
- package/_vendor/canvas-core/geometry/Box.ts +80 -0
- package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
- package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
- package/_vendor/canvas-core/geometry/Mat.ts +92 -0
- package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
- package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
- package/_vendor/canvas-core/geometry/Vec.ts +123 -0
- package/_vendor/canvas-core/geometry/index.ts +13 -0
- package/_vendor/canvas-core/geometry/segments.ts +65 -0
- package/_vendor/canvas-core/index.ts +391 -0
- package/_vendor/canvas-core/plugins/index.ts +5 -0
- package/_vendor/canvas-core/plugins/types.ts +53 -0
- package/_vendor/canvas-core/reactivity.ts +16 -0
- package/_vendor/canvas-core/schema/records.ts +221 -0
- package/_vendor/canvas-core/schema/snapshots.ts +527 -0
- package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
- package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
- package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
- package/_vendor/canvas-core/shapes/types.ts +20 -0
- package/_vendor/canvas-core/toolbar.ts +148 -0
- package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
- package/_vendor/canvas-core/tools/index.ts +2 -0
- package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
- package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
- package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
- package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
- package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
- package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
- package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
- package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
- package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
- package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
- package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
- package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
- package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
- package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
- package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
- package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
- package/_vendor/canvas-plugin-api/feature.ts +204 -0
- package/_vendor/canvas-plugin-api/index.ts +142 -0
- package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
- package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
- package/_vendor/canvas-product-react/api/index.ts +31 -0
- package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
- package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
- package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
- package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
- package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
- package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
- package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
- package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
- package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
- package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
- package/_vendor/canvas-product-react/index.ts +84 -0
- package/_vendor/canvas-product-react/persistence/index.ts +18 -0
- package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
- package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
- package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
- package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
- package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
- package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
- package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
- package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
- package/_vendor/canvas-react/components/index.ts +4 -0
- package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
- package/_vendor/canvas-react/hooks/useValue.ts +14 -0
- package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
- package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
- package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
- package/_vendor/canvas-react/host/index.ts +38 -0
- package/_vendor/canvas-react/host/pluginHost.ts +635 -0
- package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
- package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
- package/_vendor/canvas-react/index.ts +3 -0
- package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
- package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
- package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
- package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
- package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
- package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
- package/_vendor/plugin-arrow/canvas.tsx +92 -0
- package/_vendor/plugin-arrow/core.ts +409 -0
- package/_vendor/plugin-arrow/feature.ts +20 -0
- package/_vendor/plugin-arrow/index.ts +28 -0
- package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
- package/_vendor/plugin-arrow/types.ts +63 -0
- package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
- package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
- package/_vendor/plugin-draw/DrawTool.ts +444 -0
- package/_vendor/plugin-draw/canvas.tsx +24 -0
- package/_vendor/plugin-draw/core.ts +515 -0
- package/_vendor/plugin-draw/drawGeometry.ts +491 -0
- package/_vendor/plugin-draw/drawHitTest.ts +271 -0
- package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
- package/_vendor/plugin-draw/drawPath.ts +550 -0
- package/_vendor/plugin-draw/drawSegments.ts +106 -0
- package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
- package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
- package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
- package/_vendor/plugin-draw/feature.ts +20 -0
- package/_vendor/plugin-draw/index.ts +31 -0
- package/_vendor/plugin-draw/types.ts +36 -0
- package/_vendor/plugin-eraser/EraserTool.ts +297 -0
- package/_vendor/plugin-eraser/canvas.tsx +616 -0
- package/_vendor/plugin-eraser/core.ts +49 -0
- package/_vendor/plugin-eraser/feature.ts +20 -0
- package/_vendor/plugin-eraser/index.ts +4 -0
- package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
- package/_vendor/plugin-frame/FrameTool.ts +225 -0
- package/_vendor/plugin-frame/canvas.tsx +75 -0
- package/_vendor/plugin-frame/core.ts +534 -0
- package/_vendor/plugin-frame/feature.ts +20 -0
- package/_vendor/plugin-frame/index.ts +6 -0
- package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
- package/_vendor/plugin-geo/GeoTools.ts +247 -0
- package/_vendor/plugin-geo/canvas.tsx +90 -0
- package/_vendor/plugin-geo/core.ts +495 -0
- package/_vendor/plugin-geo/feature.ts +20 -0
- package/_vendor/plugin-geo/index.ts +7 -0
- package/_vendor/plugin-geo/types.ts +27 -0
- package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
- package/_vendor/plugin-group/canvas.tsx +59 -0
- package/_vendor/plugin-group/core.ts +40 -0
- package/_vendor/plugin-group/feature.ts +20 -0
- package/_vendor/plugin-group/index.ts +5 -0
- package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
- package/_vendor/plugin-image/canvas.tsx +212 -0
- package/_vendor/plugin-image/core.ts +542 -0
- package/_vendor/plugin-image/crop.ts +770 -0
- package/_vendor/plugin-image/cropInteractions.ts +350 -0
- package/_vendor/plugin-image/cropSession.ts +38 -0
- package/_vendor/plugin-image/feature.ts +20 -0
- package/_vendor/plugin-image/index.ts +29 -0
- package/_vendor/plugin-image/types.ts +55 -0
- package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
- package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
- package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
- package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
- package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
- package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
- package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
- package/_vendor/plugin-image-product-react/index.ts +60 -0
- package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
- package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
- package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
- package/_vendor/plugin-laser/LaserTool.ts +154 -0
- package/_vendor/plugin-laser/canvas.tsx +645 -0
- package/_vendor/plugin-laser/core.ts +49 -0
- package/_vendor/plugin-laser/feature.ts +20 -0
- package/_vendor/plugin-laser/index.ts +4 -0
- package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
- package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
- package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
- package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
- package/_vendor/plugin-text/TextTool.ts +228 -0
- package/_vendor/plugin-text/canvas.tsx +115 -0
- package/_vendor/plugin-text/core.ts +422 -0
- package/_vendor/plugin-text/feature.ts +20 -0
- package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
- package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
- package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
- package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
- package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
- package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
- package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
- package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
- package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
- package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
- package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
- package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
- package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
- package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
- package/_vendor/plugin-text/index.ts +18 -0
- package/_vendor/plugin-text/richTextUtils.ts +259 -0
- package/_vendor/plugin-text/textInteractions.ts +366 -0
- package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
- package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
- package/_vendor/plugin-text/textStyle.ts +96 -0
- package/_vendor/plugin-text/textTypes.ts +48 -0
- package/_vendor/signals/atom.ts +74 -0
- package/_vendor/signals/computed.ts +132 -0
- package/_vendor/signals/core.ts +130 -0
- package/_vendor/signals/effect.ts +117 -0
- package/_vendor/signals/index.ts +9 -0
- package/_vendor/signals/types.ts +17 -0
- package/_vendor/store/Store.ts +171 -0
- package/_vendor/store/diff.ts +229 -0
- package/_vendor/store/history.ts +177 -0
- package/_vendor/store/index.ts +32 -0
- package/_vendor/store/types.ts +37 -0
- package/api/canvasKitApi.d.ts +29 -0
- package/index.css +3 -0
- package/index.d.ts +16 -0
- package/index.mjs +1 -0
- package/internal-modules.d.ts +187 -0
- package/package.json +48 -0
- package/product/Shell.d.ts +19 -0
- package/product/chromeContext.d.ts +70 -0
- package/product/createCanvasKitEditor.d.ts +6 -0
- package/product/index.d.ts +7 -0
- package/product/persistence.d.ts +3 -0
- package/product/product.d.ts +22 -0
- package/product/useCanvasClipboardShortcuts.d.ts +6 -0
- package/product-iyBbEPIs.mjs +4 -0
- package/product.mjs +1 -0
- package/standard/extensions.d.ts +1 -0
- package/standard/index.d.ts +3 -0
- package/standard/standardApi.d.ts +91 -0
- package/standard-BmrxwqpJ.mjs +44 -0
- package/standard.mjs +1 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
filterRecordsDiff,
|
|
3
|
+
hasRecordsDiff,
|
|
4
|
+
} from "@land/store";
|
|
5
|
+
import {
|
|
6
|
+
StoreHistoryBuffer,
|
|
7
|
+
type StoreHistoryEntry,
|
|
8
|
+
type StoreHistoryState,
|
|
9
|
+
} from "@land/store";
|
|
10
|
+
import type { StoreChange } from "@land/store";
|
|
11
|
+
import {
|
|
12
|
+
type EditorRecord,
|
|
13
|
+
isDocumentRecord,
|
|
14
|
+
type DocumentRecord,
|
|
15
|
+
} from "../schema/records";
|
|
16
|
+
import type { Editor } from "./Editor";
|
|
17
|
+
import { getEditorStore } from "./internal";
|
|
18
|
+
|
|
19
|
+
export type HistoryState = StoreHistoryState;
|
|
20
|
+
|
|
21
|
+
export class HistoryManager {
|
|
22
|
+
private readonly buffer = new StoreHistoryBuffer<DocumentRecord>();
|
|
23
|
+
readonly state = this.buffer.state;
|
|
24
|
+
|
|
25
|
+
constructor(private readonly editor: Editor) {
|
|
26
|
+
getEditorStore(this.editor).listen((change) => {
|
|
27
|
+
this.handleStoreChange(change);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clear(): void {
|
|
32
|
+
this.buffer.clear();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
mark(id: string): void {
|
|
36
|
+
this.buffer.mark(id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bailToMark(id: string): void {
|
|
40
|
+
const diff = this.buffer.bailToMark(id);
|
|
41
|
+
if (diff) {
|
|
42
|
+
getEditorStore(this.editor).applyDiff(diff, "history");
|
|
43
|
+
this.editor.syncAfterDocumentRestore();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
squashToMark(id: string, label = id): void {
|
|
48
|
+
this.buffer.squashToMark(id, label);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
undo(): boolean {
|
|
52
|
+
const entry = this.buffer.undo();
|
|
53
|
+
if (!entry) return false;
|
|
54
|
+
|
|
55
|
+
getEditorStore(this.editor).applyDiff(entry.inverse, "history");
|
|
56
|
+
this.editor.syncAfterDocumentRestore();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
redo(): boolean {
|
|
61
|
+
const entry = this.buffer.redo();
|
|
62
|
+
if (!entry) return false;
|
|
63
|
+
|
|
64
|
+
getEditorStore(this.editor).applyDiff(entry.forward, "history");
|
|
65
|
+
this.editor.syncAfterDocumentRestore();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getEntries(): readonly StoreHistoryEntry<DocumentRecord>[] {
|
|
70
|
+
return this.buffer.getEntries();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private handleStoreChange(change: StoreChange<EditorRecord>): void {
|
|
74
|
+
const documentDiff = filterRecordsDiff(change.diff, isDocumentRecord);
|
|
75
|
+
if (!hasRecordsDiff(documentDiff)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (change.source === "user") {
|
|
80
|
+
this.buffer.append(documentDiff);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Editor } from "./Editor";
|
|
2
|
+
import type {
|
|
3
|
+
CancelEditorEvent,
|
|
4
|
+
ClickEditorEvent,
|
|
5
|
+
EditorEvent,
|
|
6
|
+
KeyboardEditorEvent,
|
|
7
|
+
PointerEditorEvent,
|
|
8
|
+
TickEditorEvent,
|
|
9
|
+
WheelEditorEvent,
|
|
10
|
+
} from "./events";
|
|
11
|
+
|
|
12
|
+
export abstract class StateNode {
|
|
13
|
+
protected readonly children = new Map<string, StateNode>();
|
|
14
|
+
protected currentChild: StateNode | null = null;
|
|
15
|
+
protected initial: string | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
readonly editor: Editor,
|
|
19
|
+
readonly id: string,
|
|
20
|
+
readonly parent?: StateNode,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
protected addChild(child: StateNode): void {
|
|
24
|
+
this.children.set(child.id, child);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
enter(info?: unknown): void {
|
|
28
|
+
this.onEnter(info);
|
|
29
|
+
if (!this.initial) return;
|
|
30
|
+
const child = this.children.get(this.initial);
|
|
31
|
+
if (!child) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Missing child state \"${this.initial}\" on \"${this.id}\"`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
this.currentChild = child;
|
|
37
|
+
child.enter(info);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
exit(info?: unknown): void {
|
|
41
|
+
if (this.currentChild) {
|
|
42
|
+
this.currentChild.exit(info);
|
|
43
|
+
this.currentChild = null;
|
|
44
|
+
}
|
|
45
|
+
this.onExit(info);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
transition(id: string, info?: unknown): void {
|
|
49
|
+
if (!this.parent) {
|
|
50
|
+
const next = this.children.get(id);
|
|
51
|
+
if (!next) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Missing root child state \"${id}\" on tool \"${this.id}\"`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (this.currentChild === next) return;
|
|
57
|
+
this.currentChild?.exit(info);
|
|
58
|
+
this.currentChild = next;
|
|
59
|
+
next.enter(info);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.parent.transition(id, info);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isInInitialState(): boolean {
|
|
66
|
+
if (!this.currentChild) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!this.initial) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
this.currentChild.id === this.initial &&
|
|
76
|
+
this.currentChild.isInInitialState()
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
dispatch(event: EditorEvent): void {
|
|
81
|
+
if (this.currentChild) {
|
|
82
|
+
this.currentChild.dispatch(event);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
switch (event.type) {
|
|
87
|
+
case "pointer":
|
|
88
|
+
this.handlePointerEvent(event);
|
|
89
|
+
return;
|
|
90
|
+
case "click":
|
|
91
|
+
this.handleClickEvent(event);
|
|
92
|
+
return;
|
|
93
|
+
case "keyboard":
|
|
94
|
+
this.handleKeyboardEvent(event);
|
|
95
|
+
return;
|
|
96
|
+
case "wheel":
|
|
97
|
+
this.onWheel(event);
|
|
98
|
+
return;
|
|
99
|
+
case "tick":
|
|
100
|
+
this.onTick(event);
|
|
101
|
+
return;
|
|
102
|
+
case "cancel":
|
|
103
|
+
this.onCancel(event);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected onEnter(_info?: unknown): void {}
|
|
109
|
+
|
|
110
|
+
protected onExit(_info?: unknown): void {}
|
|
111
|
+
|
|
112
|
+
protected onPointerDown(_event: PointerEditorEvent): void {}
|
|
113
|
+
|
|
114
|
+
protected onPointerMove(_event: PointerEditorEvent): void {}
|
|
115
|
+
|
|
116
|
+
protected onPointerUp(_event: PointerEditorEvent): void {}
|
|
117
|
+
|
|
118
|
+
protected onDoubleClick(_event: ClickEditorEvent): void {}
|
|
119
|
+
|
|
120
|
+
protected onCancel(_event: CancelEditorEvent): void {}
|
|
121
|
+
|
|
122
|
+
protected onKeyDown(_event: KeyboardEditorEvent): void {}
|
|
123
|
+
|
|
124
|
+
protected onKeyUp(_event: KeyboardEditorEvent): void {}
|
|
125
|
+
|
|
126
|
+
protected onWheel(_event: WheelEditorEvent): void {}
|
|
127
|
+
|
|
128
|
+
protected onTick(_event: TickEditorEvent): void {}
|
|
129
|
+
|
|
130
|
+
private handlePointerEvent(event: PointerEditorEvent): void {
|
|
131
|
+
switch (event.name) {
|
|
132
|
+
case "pointer_down":
|
|
133
|
+
this.onPointerDown(event);
|
|
134
|
+
return;
|
|
135
|
+
case "pointer_move":
|
|
136
|
+
this.onPointerMove(event);
|
|
137
|
+
return;
|
|
138
|
+
case "pointer_up":
|
|
139
|
+
this.onPointerUp(event);
|
|
140
|
+
return;
|
|
141
|
+
case "pointer_cancel":
|
|
142
|
+
this.onCancel({ type: "cancel", reason: "pointer_cancel" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private handleClickEvent(event: ClickEditorEvent): void {
|
|
148
|
+
switch (event.name) {
|
|
149
|
+
case "double_click":
|
|
150
|
+
this.onDoubleClick(event);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private handleKeyboardEvent(event: KeyboardEditorEvent): void {
|
|
156
|
+
if (event.name === "key_down") {
|
|
157
|
+
this.onKeyDown(event);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.onKeyUp(event);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import type { ChangeSource } from "@land/store";
|
|
2
|
+
import { Box } from "../geometry/Box";
|
|
3
|
+
import { Vec } from "../geometry/Vec";
|
|
4
|
+
import type {
|
|
5
|
+
AssetId,
|
|
6
|
+
AssetRecord,
|
|
7
|
+
BindingRecord,
|
|
8
|
+
PageId,
|
|
9
|
+
ShapeId,
|
|
10
|
+
ShapeParentId,
|
|
11
|
+
ShapeRecord,
|
|
12
|
+
} from "../schema/records";
|
|
13
|
+
import type { Editor } from "./Editor";
|
|
14
|
+
import { getShapeAssetId } from "./editorComputedHelpers";
|
|
15
|
+
import { getEditorStore } from "./internal";
|
|
16
|
+
|
|
17
|
+
const DEFAULT_PASTE_OFFSET = new Vec(24, 24);
|
|
18
|
+
|
|
19
|
+
export interface EditorClipboardPayload {
|
|
20
|
+
type: "land/clipboard";
|
|
21
|
+
version: 1;
|
|
22
|
+
source: {
|
|
23
|
+
app: "land";
|
|
24
|
+
documentId?: string;
|
|
25
|
+
};
|
|
26
|
+
rootShapeIds: readonly ShapeId[];
|
|
27
|
+
records: {
|
|
28
|
+
shapes: readonly ShapeRecord[];
|
|
29
|
+
bindings: readonly BindingRecord[];
|
|
30
|
+
assets: readonly AssetRecord[];
|
|
31
|
+
};
|
|
32
|
+
bounds: {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
w: number;
|
|
36
|
+
h: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ClipboardAssetSidecar {
|
|
41
|
+
assetId: AssetId;
|
|
42
|
+
kind: "local-image-blob";
|
|
43
|
+
storageKey: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PasteClipboardPayloadInput {
|
|
47
|
+
payload: EditorClipboardPayload;
|
|
48
|
+
pagePoint?: Vec;
|
|
49
|
+
offset?: Vec;
|
|
50
|
+
source?: ChangeSource;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DuplicateSelectionInput {
|
|
54
|
+
offset?: Vec;
|
|
55
|
+
source?: ChangeSource;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isEditorClipboardPayload(
|
|
59
|
+
value: unknown,
|
|
60
|
+
): value is EditorClipboardPayload {
|
|
61
|
+
if (!value || typeof value !== "object") {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const candidate = value as Partial<EditorClipboardPayload>;
|
|
66
|
+
return (
|
|
67
|
+
candidate.type === "land/clipboard" &&
|
|
68
|
+
candidate.version === 1 &&
|
|
69
|
+
!!candidate.records &&
|
|
70
|
+
Array.isArray(candidate.rootShapeIds) &&
|
|
71
|
+
Array.isArray(candidate.records.shapes) &&
|
|
72
|
+
Array.isArray(candidate.records.bindings) &&
|
|
73
|
+
Array.isArray(candidate.records.assets)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createEditorClipboardPayloadFromSelection(
|
|
78
|
+
editor: Editor,
|
|
79
|
+
): EditorClipboardPayload | null {
|
|
80
|
+
const rootShapeIds = getClipboardRootShapeIds(editor);
|
|
81
|
+
if (rootShapeIds.length === 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const shapeIds = getClipboardShapeIds(editor, rootShapeIds);
|
|
86
|
+
const shapeIdSet = new Set(shapeIds);
|
|
87
|
+
const shapes = shapeIds
|
|
88
|
+
.map((shapeId) => editor.getShape(shapeId))
|
|
89
|
+
.filter((shape): shape is ShapeRecord => !!shape)
|
|
90
|
+
.map(cloneSerializableRecord);
|
|
91
|
+
if (shapes.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const bindings = getClipboardBindings(editor, shapes, shapeIdSet);
|
|
96
|
+
const assets = getClipboardAssets(editor, shapes);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
type: "land/clipboard",
|
|
100
|
+
version: 1,
|
|
101
|
+
source: {
|
|
102
|
+
app: "land",
|
|
103
|
+
},
|
|
104
|
+
rootShapeIds,
|
|
105
|
+
records: {
|
|
106
|
+
shapes,
|
|
107
|
+
bindings,
|
|
108
|
+
assets,
|
|
109
|
+
},
|
|
110
|
+
bounds: getPayloadBounds(editor, shapeIds),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function pasteEditorClipboardPayload(
|
|
115
|
+
editor: Editor,
|
|
116
|
+
input: PasteClipboardPayloadInput,
|
|
117
|
+
): ShapeId[] {
|
|
118
|
+
const { payload } = input;
|
|
119
|
+
if (!isEditorClipboardPayload(payload) || payload.records.shapes.length === 0) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const source = input.source ?? "user";
|
|
124
|
+
const currentPageId = editor.getCurrentPageId();
|
|
125
|
+
const offset = getPasteOffset(input);
|
|
126
|
+
const assetIdMap = new Map<AssetId, AssetId>();
|
|
127
|
+
const shapeIdMap = new Map<ShapeId, ShapeId>();
|
|
128
|
+
const pastedRootIds: ShapeId[] = [];
|
|
129
|
+
|
|
130
|
+
// Batch the whole paste into a single transaction so it is one undo step.
|
|
131
|
+
// Nested command atomics participate in this outer transaction.
|
|
132
|
+
getEditorStore(editor).atomic(() => {
|
|
133
|
+
const assets = payload.records.assets.map((asset) => {
|
|
134
|
+
const nextAsset = editor.createAssetRecord({
|
|
135
|
+
type: asset.type,
|
|
136
|
+
props: cloneRecordObject(asset.props),
|
|
137
|
+
meta: cloneRecordObject(asset.meta),
|
|
138
|
+
});
|
|
139
|
+
assetIdMap.set(asset.id, nextAsset.id);
|
|
140
|
+
return nextAsset;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (assets.length > 0) {
|
|
144
|
+
editor.commands.createAssetsFromRecords(assets, { source });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const draftShapes = payload.records.shapes.map((shape) => {
|
|
148
|
+
const props = remapAssetIdsInRecord(shape.props, assetIdMap);
|
|
149
|
+
const draft = editor.createShapeRecord({
|
|
150
|
+
type: shape.type,
|
|
151
|
+
x: shape.x + offset.x,
|
|
152
|
+
y: shape.y + offset.y,
|
|
153
|
+
props,
|
|
154
|
+
parentId: currentPageId,
|
|
155
|
+
rotation: shape.rotation,
|
|
156
|
+
scaleX: shape.scaleX,
|
|
157
|
+
scaleY: shape.scaleY,
|
|
158
|
+
opacity: shape.opacity,
|
|
159
|
+
isHidden: shape.isHidden,
|
|
160
|
+
isLocked: shape.isLocked,
|
|
161
|
+
meta: cloneRecordObject(shape.meta),
|
|
162
|
+
});
|
|
163
|
+
shapeIdMap.set(shape.id, draft.id);
|
|
164
|
+
return { original: shape, draft };
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const shapeRecords = draftShapes.map(({ original, draft }) => ({
|
|
168
|
+
...draft,
|
|
169
|
+
parentId: remapPastedParentId(
|
|
170
|
+
original.parentId,
|
|
171
|
+
shapeIdMap,
|
|
172
|
+
currentPageId,
|
|
173
|
+
),
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
if (shapeRecords.length > 0) {
|
|
177
|
+
editor.commands.createShapesFromRecords(shapeRecords, { source });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const binding of payload.records.bindings) {
|
|
181
|
+
const fromId = shapeIdMap.get(binding.fromId);
|
|
182
|
+
const toId = shapeIdMap.get(binding.toId);
|
|
183
|
+
if (!fromId || !toId) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
editor.commands.createBinding(
|
|
188
|
+
{
|
|
189
|
+
type: binding.type,
|
|
190
|
+
fromId,
|
|
191
|
+
toId,
|
|
192
|
+
props: cloneRecordObject(binding.props),
|
|
193
|
+
meta: cloneRecordObject(binding.meta),
|
|
194
|
+
},
|
|
195
|
+
{ source },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}, source);
|
|
199
|
+
|
|
200
|
+
for (const rootShapeId of payload.rootShapeIds) {
|
|
201
|
+
const pastedShapeId = shapeIdMap.get(rootShapeId);
|
|
202
|
+
if (pastedShapeId) {
|
|
203
|
+
pastedRootIds.push(pastedShapeId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (pastedRootIds.length > 0) {
|
|
208
|
+
editor.commands.setSelectedShapeIds(pastedRootIds, { source });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return pastedRootIds;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function duplicateEditorSelection(
|
|
215
|
+
editor: Editor,
|
|
216
|
+
input: DuplicateSelectionInput = {},
|
|
217
|
+
): ShapeId[] {
|
|
218
|
+
const payload = createEditorClipboardPayloadFromSelection(editor);
|
|
219
|
+
if (!payload) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return pasteEditorClipboardPayload(editor, {
|
|
224
|
+
payload,
|
|
225
|
+
offset: input.offset ?? DEFAULT_PASTE_OFFSET,
|
|
226
|
+
source: input.source,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getClipboardRootShapeIds(editor: Editor): ShapeId[] {
|
|
231
|
+
const selectedShapeIds = new Set(editor.getSelectedShapeIds());
|
|
232
|
+
if (selectedShapeIds.size === 0) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return editor
|
|
237
|
+
.getCurrentPageShapeIds()
|
|
238
|
+
.filter((shapeId) => selectedShapeIds.has(shapeId))
|
|
239
|
+
.filter((shapeId) => !hasAncestorInSet(editor, shapeId, selectedShapeIds));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getClipboardShapeIds(
|
|
243
|
+
editor: Editor,
|
|
244
|
+
rootShapeIds: readonly ShapeId[],
|
|
245
|
+
): ShapeId[] {
|
|
246
|
+
const traversalIds = editor.getShapeExportTraversalIds(rootShapeIds);
|
|
247
|
+
const traversalIdSet = new Set(traversalIds);
|
|
248
|
+
return editor
|
|
249
|
+
.getCurrentPageShapeIds()
|
|
250
|
+
.filter((shapeId) => traversalIdSet.has(shapeId));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getClipboardBindings(
|
|
254
|
+
editor: Editor,
|
|
255
|
+
shapes: readonly ShapeRecord[],
|
|
256
|
+
shapeIdSet: ReadonlySet<ShapeId>,
|
|
257
|
+
): BindingRecord[] {
|
|
258
|
+
const bindings: BindingRecord[] = [];
|
|
259
|
+
const seenBindingIds = new Set<string>();
|
|
260
|
+
|
|
261
|
+
for (const shape of shapes) {
|
|
262
|
+
for (const binding of editor.getBindingsForShape(shape.id)) {
|
|
263
|
+
if (seenBindingIds.has(binding.id)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (!shapeIdSet.has(binding.fromId) || !shapeIdSet.has(binding.toId)) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
seenBindingIds.add(binding.id);
|
|
270
|
+
bindings.push(cloneSerializableRecord(binding));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return bindings;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getClipboardAssets(
|
|
278
|
+
editor: Editor,
|
|
279
|
+
shapes: readonly ShapeRecord[],
|
|
280
|
+
): AssetRecord[] {
|
|
281
|
+
const assets: AssetRecord[] = [];
|
|
282
|
+
const seenAssetIds = new Set<AssetId>();
|
|
283
|
+
|
|
284
|
+
for (const shape of shapes) {
|
|
285
|
+
for (const assetId of getShapeAssetIds(shape)) {
|
|
286
|
+
if (seenAssetIds.has(assetId)) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const asset = editor.getAsset(assetId);
|
|
290
|
+
if (!asset) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
seenAssetIds.add(assetId);
|
|
294
|
+
assets.push(cloneSerializableRecord(asset));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return assets;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function getPayloadBounds(
|
|
302
|
+
editor: Editor,
|
|
303
|
+
shapeIds: readonly ShapeId[],
|
|
304
|
+
): EditorClipboardPayload["bounds"] {
|
|
305
|
+
const boxes = shapeIds
|
|
306
|
+
.map((shapeId) => editor.getShapePageBounds(shapeId))
|
|
307
|
+
.filter((box): box is Box => !!box);
|
|
308
|
+
const bounds = unionBoxes(boxes) ?? new Box(0, 0, 0, 0);
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
x: bounds.x,
|
|
312
|
+
y: bounds.y,
|
|
313
|
+
w: bounds.w,
|
|
314
|
+
h: bounds.h,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function getPasteOffset(input: PasteClipboardPayloadInput): Vec {
|
|
319
|
+
const bounds = input.payload.bounds;
|
|
320
|
+
if (input.pagePoint) {
|
|
321
|
+
return input.pagePoint.sub(
|
|
322
|
+
new Vec(bounds.x + bounds.w / 2, bounds.y + bounds.h / 2),
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return input.offset ?? DEFAULT_PASTE_OFFSET;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function remapPastedParentId(
|
|
330
|
+
parentId: ShapeParentId,
|
|
331
|
+
shapeIdMap: ReadonlyMap<ShapeId, ShapeId>,
|
|
332
|
+
currentPageId: PageId,
|
|
333
|
+
): ShapeParentId {
|
|
334
|
+
if (!parentId.startsWith("shape:")) {
|
|
335
|
+
return currentPageId;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return shapeIdMap.get(parentId as ShapeId) ?? currentPageId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function hasAncestorInSet(
|
|
342
|
+
editor: Editor,
|
|
343
|
+
shapeId: ShapeId,
|
|
344
|
+
shapeIdSet: ReadonlySet<ShapeId>,
|
|
345
|
+
): boolean {
|
|
346
|
+
const seen = new Set<ShapeId>([shapeId]);
|
|
347
|
+
let shape = editor.getShape(shapeId);
|
|
348
|
+
|
|
349
|
+
while (shape?.parentId.startsWith("shape:")) {
|
|
350
|
+
const parentId = shape.parentId as ShapeId;
|
|
351
|
+
if (shapeIdSet.has(parentId)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (seen.has(parentId)) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
seen.add(parentId);
|
|
358
|
+
shape = editor.getShape(parentId);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getShapeAssetIds(shape: ShapeRecord): AssetId[] {
|
|
365
|
+
const assetIds: AssetId[] = [];
|
|
366
|
+
const seenAssetIds = new Set<AssetId>();
|
|
367
|
+
const topLevelAssetId = getShapeAssetId(shape);
|
|
368
|
+
if (topLevelAssetId) {
|
|
369
|
+
seenAssetIds.add(topLevelAssetId);
|
|
370
|
+
assetIds.push(topLevelAssetId);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
collectAssetIdsFromValue(shape.props, seenAssetIds, assetIds);
|
|
374
|
+
return assetIds;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function collectAssetIdsFromValue(
|
|
378
|
+
value: unknown,
|
|
379
|
+
seenAssetIds: Set<AssetId>,
|
|
380
|
+
assetIds: AssetId[],
|
|
381
|
+
): void {
|
|
382
|
+
if (isAssetId(value)) {
|
|
383
|
+
if (!seenAssetIds.has(value)) {
|
|
384
|
+
seenAssetIds.add(value);
|
|
385
|
+
assetIds.push(value);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (Array.isArray(value)) {
|
|
391
|
+
for (const item of value) {
|
|
392
|
+
collectAssetIdsFromValue(item, seenAssetIds, assetIds);
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!value || typeof value !== "object") {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const nested of Object.values(value)) {
|
|
402
|
+
collectAssetIdsFromValue(nested, seenAssetIds, assetIds);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function remapAssetIdsInRecord(
|
|
407
|
+
record: object,
|
|
408
|
+
assetIdMap: ReadonlyMap<AssetId, AssetId>,
|
|
409
|
+
): Record<string, unknown> {
|
|
410
|
+
return remapAssetIdsInValue(record, assetIdMap) as Record<string, unknown>;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function remapAssetIdsInValue(
|
|
414
|
+
value: unknown,
|
|
415
|
+
assetIdMap: ReadonlyMap<AssetId, AssetId>,
|
|
416
|
+
): unknown {
|
|
417
|
+
if (isAssetId(value)) {
|
|
418
|
+
return assetIdMap.get(value) ?? value;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (Array.isArray(value)) {
|
|
422
|
+
return value.map((item) => remapAssetIdsInValue(item, assetIdMap));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!value || typeof value !== "object") {
|
|
426
|
+
return value;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const next: Record<string, unknown> = {};
|
|
430
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
431
|
+
next[key] = remapAssetIdsInValue(nested, assetIdMap);
|
|
432
|
+
}
|
|
433
|
+
return next;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function cloneRecordObject(value: object): Record<string, unknown> {
|
|
437
|
+
return cloneSerializableRecord(value) as Record<string, unknown>;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function cloneSerializableRecord<T>(value: T): T {
|
|
441
|
+
if (Array.isArray(value)) {
|
|
442
|
+
return value.map(cloneSerializableRecord) as T;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (!value || typeof value !== "object") {
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const next: Record<string, unknown> = {};
|
|
450
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
451
|
+
next[key] = cloneSerializableRecord(nested);
|
|
452
|
+
}
|
|
453
|
+
return next as T;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function isAssetId(value: unknown): value is AssetId {
|
|
457
|
+
return typeof value === "string" && value.startsWith("asset:");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function unionBoxes(boxes: readonly Box[]): Box | null {
|
|
461
|
+
if (boxes.length === 0) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let minX = Infinity;
|
|
466
|
+
let minY = Infinity;
|
|
467
|
+
let maxX = -Infinity;
|
|
468
|
+
let maxY = -Infinity;
|
|
469
|
+
|
|
470
|
+
for (const box of boxes) {
|
|
471
|
+
minX = Math.min(minX, box.minX);
|
|
472
|
+
minY = Math.min(minY, box.minY);
|
|
473
|
+
maxX = Math.max(maxX, box.maxX);
|
|
474
|
+
maxY = Math.max(maxY, box.maxY);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return new Box(minX, minY, maxX - minX, maxY - minY);
|
|
478
|
+
}
|