@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,2614 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UNINITIALIZED,
|
|
3
|
+
atom,
|
|
4
|
+
computed,
|
|
5
|
+
type Atom,
|
|
6
|
+
type Signal,
|
|
7
|
+
} from "@land/signals";
|
|
8
|
+
import type {
|
|
9
|
+
ArrowTerminalBindingResolver,
|
|
10
|
+
ArrowTerminalBindingResolverInput,
|
|
11
|
+
ResolvedArrowTerminalBinding,
|
|
12
|
+
} from "../bindings/arrow/arrowBindingHelpers";
|
|
13
|
+
import type { BindingUtil } from "../bindings/BindingUtil";
|
|
14
|
+
import { BindingUtilRegistry } from "../bindings/BindingUtilRegistry";
|
|
15
|
+
import { Box } from "../geometry/Box";
|
|
16
|
+
import { Mat } from "../geometry/Mat";
|
|
17
|
+
import { Vec } from "../geometry/Vec";
|
|
18
|
+
import {
|
|
19
|
+
type BindingId,
|
|
20
|
+
type BindingRecord,
|
|
21
|
+
DEFAULT_CAMERA,
|
|
22
|
+
DEFAULT_SESSION_ID,
|
|
23
|
+
type AssetId,
|
|
24
|
+
type AssetRecord,
|
|
25
|
+
type Camera,
|
|
26
|
+
isAssetRecord,
|
|
27
|
+
isBindingRecord,
|
|
28
|
+
isPageRecord,
|
|
29
|
+
isSessionRecord,
|
|
30
|
+
isShapeRecord,
|
|
31
|
+
type PageId,
|
|
32
|
+
type PageRecord,
|
|
33
|
+
type EditorRecord,
|
|
34
|
+
type RecordScope,
|
|
35
|
+
type SessionRecord,
|
|
36
|
+
type ShapeId,
|
|
37
|
+
type ShapeParentId,
|
|
38
|
+
type ShapeRecord,
|
|
39
|
+
type ShapeEditingEntryRequest,
|
|
40
|
+
type ShapeEditingSession,
|
|
41
|
+
} from "../schema/records";
|
|
42
|
+
import {
|
|
43
|
+
type DocumentSnapshot,
|
|
44
|
+
type SessionSnapshot,
|
|
45
|
+
} from "../schema/snapshots";
|
|
46
|
+
import type {
|
|
47
|
+
ShapeSelectionFlipAxis,
|
|
48
|
+
ShapeUtil,
|
|
49
|
+
} from "../shapes/ShapeUtil";
|
|
50
|
+
import { ShapeUtilRegistry } from "../shapes/ShapeUtilRegistry";
|
|
51
|
+
import {
|
|
52
|
+
exportShapesToSvgString,
|
|
53
|
+
ShapeSvgExporterRegistry,
|
|
54
|
+
type ShapeSvgExporter,
|
|
55
|
+
type SvgExportOptions,
|
|
56
|
+
type SvgExportResult,
|
|
57
|
+
} from "../exports/svg";
|
|
58
|
+
import {
|
|
59
|
+
getExportIntegrityReport,
|
|
60
|
+
repairExportIntegrity,
|
|
61
|
+
type ExportIntegrityRepairResult,
|
|
62
|
+
type ExportIntegrityReport,
|
|
63
|
+
} from "../exports/integrity";
|
|
64
|
+
import {
|
|
65
|
+
getShapeCapabilityManifest,
|
|
66
|
+
type ShapeCapabilityDefinition,
|
|
67
|
+
type ShapeCapabilityManifest,
|
|
68
|
+
} from "../shapes/capabilities";
|
|
69
|
+
import { Store, type ChangeSource, type StoreChange } from "@land/store";
|
|
70
|
+
import {
|
|
71
|
+
createEditorCommands,
|
|
72
|
+
type CreateAssetInput,
|
|
73
|
+
type CreateBindingInput,
|
|
74
|
+
type CreatePageInput,
|
|
75
|
+
type CreateShapeInput,
|
|
76
|
+
type StartEditingShapeInput,
|
|
77
|
+
type EditorCommands,
|
|
78
|
+
} from "./commands";
|
|
79
|
+
import type {
|
|
80
|
+
CancelEditorEvent,
|
|
81
|
+
ClickEditorEvent,
|
|
82
|
+
EditorEvent,
|
|
83
|
+
PointerEditorEvent,
|
|
84
|
+
} from "./events";
|
|
85
|
+
import { HistoryManager, type HistoryState } from "./HistoryManager";
|
|
86
|
+
import { BindingManager } from "./managers/BindingManager";
|
|
87
|
+
import { CameraManager } from "./managers/CameraManager";
|
|
88
|
+
import { ClipboardManager } from "./managers/ClipboardManager";
|
|
89
|
+
import { DocumentSnapshotManager } from "./managers/DocumentSnapshotManager";
|
|
90
|
+
import { InputsManager } from "./managers/InputsManager";
|
|
91
|
+
import {
|
|
92
|
+
InteractionTransientManager,
|
|
93
|
+
type LaserTrailState,
|
|
94
|
+
} from "./managers/InteractionTransientManager";
|
|
95
|
+
import {
|
|
96
|
+
QueryManager,
|
|
97
|
+
type ShapeContainment,
|
|
98
|
+
} from "./managers/QueryManager";
|
|
99
|
+
import {
|
|
100
|
+
SpatialIndexManager,
|
|
101
|
+
type ShapeAtPointOptions,
|
|
102
|
+
type ShapeLineSegmentHitTestOptions,
|
|
103
|
+
type SpatialQueryOptions,
|
|
104
|
+
} from "./managers/SpatialIndexManager";
|
|
105
|
+
import { SnapManager } from "./managers/SnapManager";
|
|
106
|
+
import { SideEffectManager } from "./managers/SideEffectManager";
|
|
107
|
+
import { SelectionLayoutManager } from "./managers/SelectionLayoutManager";
|
|
108
|
+
import { SelectionManager } from "./managers/SelectionManager";
|
|
109
|
+
import { SelectionOverlayManager } from "./managers/SelectionOverlayManager";
|
|
110
|
+
import { ShapeEditingManager } from "./managers/ShapeEditingManager";
|
|
111
|
+
import { ShapeGeometryManager } from "./managers/ShapeGeometryManager";
|
|
112
|
+
import { ShapeHierarchyManager } from "./managers/ShapeHierarchyManager";
|
|
113
|
+
import { RenderWindowManager } from "./managers/RenderWindowManager";
|
|
114
|
+
import { ToolManager } from "./managers/ToolManager";
|
|
115
|
+
import type { ShapeGeometry } from "./queries/shapeTransforms";
|
|
116
|
+
import { editorStoreSymbol, getEditorStore, setEditorStore } from "./internal";
|
|
117
|
+
import {
|
|
118
|
+
getEditorSelectionFlipUpdates,
|
|
119
|
+
resetEditorSelectionRotation,
|
|
120
|
+
} from "./editorSelectionTransforms";
|
|
121
|
+
import type {
|
|
122
|
+
DuplicateSelectionInput,
|
|
123
|
+
EditorClipboardPayload,
|
|
124
|
+
PasteClipboardPayloadInput,
|
|
125
|
+
} from "./clipboard";
|
|
126
|
+
import { InteractionHandlerRegistry } from "./interactions";
|
|
127
|
+
import { SelectionHandleInteractionRegistry } from "./selectionHandleInteractions";
|
|
128
|
+
import {
|
|
129
|
+
RENDER_CULLING_OVERSCAN_SCREEN,
|
|
130
|
+
doesBoxContainBox,
|
|
131
|
+
} from "./renderWindow";
|
|
132
|
+
import {
|
|
133
|
+
areSelectedShapeOverlayItemsEqual,
|
|
134
|
+
getSelectionHandleState,
|
|
135
|
+
isSingleSelectionPathOverlay,
|
|
136
|
+
type SelectedShapeOverlayItem,
|
|
137
|
+
} from "./selectionOverlay";
|
|
138
|
+
import type {
|
|
139
|
+
ArrowBindingPreviewState,
|
|
140
|
+
SnapIndicatorState,
|
|
141
|
+
SelectionHandle,
|
|
142
|
+
SelectionFrame,
|
|
143
|
+
SelectionOverlayFrameState,
|
|
144
|
+
SelectionOverlayFrameGeometryState,
|
|
145
|
+
SelectionOverlayFrameStyleState,
|
|
146
|
+
SelectionOverlayHandleAnchorsState,
|
|
147
|
+
SelectionOverlayHandleCountsState,
|
|
148
|
+
SelectionOverlayHandlesState,
|
|
149
|
+
SelectionOverlayState,
|
|
150
|
+
} from "./selection";
|
|
151
|
+
|
|
152
|
+
const SELECTION_HANDLE_HIT_PADDING_SCREEN = 6;
|
|
153
|
+
const DOUBLE_CLICK_DURATION_MS = 250;
|
|
154
|
+
const DOUBLE_CLICK_DISTANCE = 40;
|
|
155
|
+
const DOUBLE_CLICK_DISTANCE_SQUARED =
|
|
156
|
+
DOUBLE_CLICK_DISTANCE * DOUBLE_CLICK_DISTANCE;
|
|
157
|
+
const CAMERA_WHEEL_MAX_ZOOM_STEP = 10;
|
|
158
|
+
const CAMERA_WHEEL_ZOOM_SPEED = 1;
|
|
159
|
+
|
|
160
|
+
export interface EditorOptions {
|
|
161
|
+
shapeUtils?: ShapeUtil[];
|
|
162
|
+
shapeSvgExporters?: ShapeSvgExporter[];
|
|
163
|
+
bindingUtils?: BindingUtil[];
|
|
164
|
+
shapeCapabilities?: readonly ShapeCapabilityDefinition[];
|
|
165
|
+
resolveAssetUrl?: (assetId: AssetId) => string | null;
|
|
166
|
+
documentSnapshot?: unknown;
|
|
167
|
+
sessionSnapshot?: unknown;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ContainerBoundsQueryOptions {
|
|
171
|
+
includeIntersecting?: boolean;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface RenderItem {
|
|
175
|
+
id: ShapeId;
|
|
176
|
+
shape: ShapeRecord;
|
|
177
|
+
bounds: Box;
|
|
178
|
+
isSelected: boolean;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface EditorPageState {
|
|
182
|
+
pages: PageRecord[];
|
|
183
|
+
currentPageId: PageId;
|
|
184
|
+
currentPage: PageRecord | null;
|
|
185
|
+
canDeleteCurrentPage: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface EditorToolState {
|
|
189
|
+
activeToolId: string;
|
|
190
|
+
isToolLocked: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type ShapeSiblingOrderDirection = "backward" | "forward";
|
|
194
|
+
export type ShapeSiblingOrderEdge = "back" | "front";
|
|
195
|
+
|
|
196
|
+
export interface ShapeSiblingOrderState {
|
|
197
|
+
parentId: ShapeParentId;
|
|
198
|
+
index: number;
|
|
199
|
+
count: number;
|
|
200
|
+
canMoveBackward: boolean;
|
|
201
|
+
canMoveForward: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface ShapeSelectionSiblingOrderState {
|
|
205
|
+
parentId: ShapeParentId;
|
|
206
|
+
selectedCount: number;
|
|
207
|
+
count: number;
|
|
208
|
+
canMoveBackward: boolean;
|
|
209
|
+
canMoveForward: boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export type ReparentShapePlacementFailureReason =
|
|
213
|
+
| "notFound"
|
|
214
|
+
| "locked"
|
|
215
|
+
| "hidden"
|
|
216
|
+
| "invalidParent"
|
|
217
|
+
| "cycle"
|
|
218
|
+
| "invalidBeforeId";
|
|
219
|
+
|
|
220
|
+
export type ReparentShapePlacementResult =
|
|
221
|
+
| { ok: true; movedIds: ShapeId[] }
|
|
222
|
+
| { ok: false; reason: ReparentShapePlacementFailureReason };
|
|
223
|
+
|
|
224
|
+
export interface ReparentShapePlacementInput {
|
|
225
|
+
ids: readonly ShapeId[];
|
|
226
|
+
parentId: ShapeParentId;
|
|
227
|
+
beforeId?: ShapeId;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface ShapeGroupState {
|
|
231
|
+
parentId: ShapeParentId;
|
|
232
|
+
shapeIds: ShapeId[];
|
|
233
|
+
bounds: Box;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface LayerTreeNode {
|
|
237
|
+
id: ShapeId;
|
|
238
|
+
parentId: ShapeParentId;
|
|
239
|
+
pageId: PageId;
|
|
240
|
+
type: ShapeRecord["type"];
|
|
241
|
+
childIds: ShapeId[];
|
|
242
|
+
index: number;
|
|
243
|
+
depth: number;
|
|
244
|
+
isHidden: boolean;
|
|
245
|
+
isLocked: boolean;
|
|
246
|
+
isEffectivelyHidden: boolean;
|
|
247
|
+
isEffectivelyLocked: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export type LayerOrderDirection = "backward" | "forward";
|
|
251
|
+
export type LayerOrderEdge = "back" | "front";
|
|
252
|
+
|
|
253
|
+
export type ShapePropsPatch = Record<string, unknown>;
|
|
254
|
+
|
|
255
|
+
export interface EditorShapeEditingState {
|
|
256
|
+
editingShapeId: ShapeId | null;
|
|
257
|
+
session: ShapeEditingSession | null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface EditorShapeEditingEntryRequestState {
|
|
261
|
+
request: ShapeEditingEntryRequest | null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface EditorSelectionState {
|
|
265
|
+
count: number;
|
|
266
|
+
singleShapeType: ShapeRecord["type"] | null;
|
|
267
|
+
commonShapeType: ShapeRecord["type"] | null;
|
|
268
|
+
hasPointHandleSelection: boolean;
|
|
269
|
+
hasFrameSelection: boolean;
|
|
270
|
+
isMixedFrameAndPointHandleSelection: boolean;
|
|
271
|
+
canDragPointHandles: boolean;
|
|
272
|
+
hasSelectionRotation: boolean;
|
|
273
|
+
hasMixedSelectionRotation: boolean;
|
|
274
|
+
canResetSelectionRotation: boolean;
|
|
275
|
+
frameBounds: Box | null;
|
|
276
|
+
frameRotation: number;
|
|
277
|
+
frameRotationCenter: Vec | null;
|
|
278
|
+
toolbarAnchorPagePoint: Vec | null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface EditorRenderHostDebugState {
|
|
282
|
+
culledCount: number;
|
|
283
|
+
visibleCount: number;
|
|
284
|
+
retainedCount: number;
|
|
285
|
+
retainedBaseCount: number;
|
|
286
|
+
culledRangeCount: number;
|
|
287
|
+
culledRangeSpan: number;
|
|
288
|
+
culledRangesSummary: string;
|
|
289
|
+
retainedBaseRangeCount: number;
|
|
290
|
+
retainedBaseRangeSpan: number;
|
|
291
|
+
retainedBaseRangesSummary: string;
|
|
292
|
+
retainedRangeCount: number;
|
|
293
|
+
retainedRangeSpan: number;
|
|
294
|
+
retainedRangesSummary: string;
|
|
295
|
+
retainedChangeCount: number;
|
|
296
|
+
retainedAddedCount: number;
|
|
297
|
+
retainedRemovedCount: number;
|
|
298
|
+
selectedOutsideCulledCount: number;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface EditorOverlayDebugState {
|
|
302
|
+
selectionFrameActive: boolean;
|
|
303
|
+
selectionHandlesActive: boolean;
|
|
304
|
+
bindingPreviewActive: boolean;
|
|
305
|
+
brushBoxActive: boolean;
|
|
306
|
+
selectionFrameChangeCount: number;
|
|
307
|
+
selectionHandlesChangeCount: number;
|
|
308
|
+
resizeHandleCount: number;
|
|
309
|
+
rotateHandleCount: number;
|
|
310
|
+
terminalHandleCount: number;
|
|
311
|
+
middleHandleCount: number;
|
|
312
|
+
customHandleCount: number;
|
|
313
|
+
boundsSummary: string;
|
|
314
|
+
rotation: number | null;
|
|
315
|
+
strokeWidth: number | null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
interface ReadonlyStore {
|
|
319
|
+
readonly recordsSignal: Signal<Map<string, EditorRecord>>;
|
|
320
|
+
get(id: string): EditorRecord | undefined;
|
|
321
|
+
getAll(): EditorRecord[];
|
|
322
|
+
getByScope(scope: RecordScope): EditorRecord[];
|
|
323
|
+
listen(listener: (change: StoreChange<EditorRecord>) => void): () => void;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
import {
|
|
327
|
+
areBoxesEqual,
|
|
328
|
+
areShapeIdArraysEqual,
|
|
329
|
+
areShapeIdSetsEqual,
|
|
330
|
+
areShapePropsPatchesEqual,
|
|
331
|
+
areVecsEqual,
|
|
332
|
+
getCommonShapeType,
|
|
333
|
+
getShapeAssetId,
|
|
334
|
+
} from "./editorComputedHelpers";
|
|
335
|
+
|
|
336
|
+
export class Editor {
|
|
337
|
+
readonly store: ReadonlyStore;
|
|
338
|
+
readonly [editorStoreSymbol]!: Store<EditorRecord>;
|
|
339
|
+
readonly inputs = new InputsManager();
|
|
340
|
+
readonly snapshots = new DocumentSnapshotManager(this);
|
|
341
|
+
readonly clipboard = new ClipboardManager(this);
|
|
342
|
+
readonly tools = new ToolManager(this);
|
|
343
|
+
readonly camera = new CameraManager(this);
|
|
344
|
+
readonly selection = new SelectionManager(this);
|
|
345
|
+
readonly selectionLayout = new SelectionLayoutManager(this);
|
|
346
|
+
readonly editing = new ShapeEditingManager(this);
|
|
347
|
+
readonly geometry = new ShapeGeometryManager(this);
|
|
348
|
+
readonly hierarchy = new ShapeHierarchyManager(this);
|
|
349
|
+
readonly shapeUtils = new ShapeUtilRegistry();
|
|
350
|
+
readonly shapeSvgExporters = new ShapeSvgExporterRegistry();
|
|
351
|
+
readonly bindingUtils = new BindingUtilRegistry();
|
|
352
|
+
readonly interactions = new InteractionHandlerRegistry();
|
|
353
|
+
readonly selectionHandleInteractions = new SelectionHandleInteractionRegistry(
|
|
354
|
+
this.interactions,
|
|
355
|
+
);
|
|
356
|
+
readonly queries: QueryManager;
|
|
357
|
+
readonly bindings: BindingManager;
|
|
358
|
+
readonly spatialIndex: SpatialIndexManager;
|
|
359
|
+
readonly snaps: SnapManager;
|
|
360
|
+
readonly renderWindow: RenderWindowManager;
|
|
361
|
+
readonly selectionOverlay: SelectionOverlayManager;
|
|
362
|
+
readonly transients = new InteractionTransientManager(this);
|
|
363
|
+
readonly history: HistoryManager;
|
|
364
|
+
readonly sideEffects = new SideEffectManager();
|
|
365
|
+
readonly commands: EditorCommands;
|
|
366
|
+
private readonly shapeCapabilityDefinitions: readonly ShapeCapabilityDefinition[];
|
|
367
|
+
readonly brushBoxSignal: Signal<Box | null> = this.transients.brushBoxSignal;
|
|
368
|
+
readonly brushBoxActiveSignal: Signal<boolean> =
|
|
369
|
+
this.transients.brushBoxActiveSignal;
|
|
370
|
+
readonly erasingShapeIdsSignal: Signal<ShapeId[]> =
|
|
371
|
+
this.transients.erasingShapeIdsSignal;
|
|
372
|
+
readonly erasingShapeIdsActiveSignal: Signal<boolean> =
|
|
373
|
+
this.transients.erasingShapeIdsActiveSignal;
|
|
374
|
+
readonly eraserTrailSignal: Signal<Vec[]> =
|
|
375
|
+
this.transients.eraserTrailSignal;
|
|
376
|
+
readonly eraserTrailActiveSignal: Signal<boolean> =
|
|
377
|
+
this.transients.eraserTrailActiveSignal;
|
|
378
|
+
readonly laserTrailSignal: Signal<LaserTrailState | null> =
|
|
379
|
+
this.transients.laserTrailSignal;
|
|
380
|
+
readonly laserTrailActiveSignal: Signal<boolean> =
|
|
381
|
+
this.transients.laserTrailActiveSignal;
|
|
382
|
+
readonly bindingPreviewSignal: Signal<ArrowBindingPreviewState | null> =
|
|
383
|
+
this.transients.bindingPreviewSignal;
|
|
384
|
+
readonly bindingPreviewActiveSignal: Signal<boolean> =
|
|
385
|
+
this.transients.bindingPreviewActiveSignal;
|
|
386
|
+
readonly dropTargetShapeIdSignal: Signal<ShapeId | null> =
|
|
387
|
+
this.transients.dropTargetShapeIdSignal;
|
|
388
|
+
readonly snapIndicatorsSignal: Signal<SnapIndicatorState[]>;
|
|
389
|
+
readonly snapIndicatorsActiveSignal: Signal<boolean>;
|
|
390
|
+
get viewportScreenSizeSignal(): Signal<{ w: number; h: number }> {
|
|
391
|
+
return this.camera.viewportScreenSizeSignal;
|
|
392
|
+
}
|
|
393
|
+
readonly shapeEditingEntryRequestSignal: Signal<ShapeEditingEntryRequest | null> =
|
|
394
|
+
this.editing.entryRequestSignal;
|
|
395
|
+
private readonly _shapeDefaultPropsByTypeSignal: Atom<
|
|
396
|
+
ReadonlyMap<ShapeRecord["type"], ShapePropsPatch>
|
|
397
|
+
> = atom<ReadonlyMap<ShapeRecord["type"], ShapePropsPatch>>(
|
|
398
|
+
"editor.shapeDefaultPropsByType",
|
|
399
|
+
new Map(),
|
|
400
|
+
);
|
|
401
|
+
readonly shapeDefaultPropsByTypeSignal: Signal<
|
|
402
|
+
ReadonlyMap<ShapeRecord["type"], ShapePropsPatch>
|
|
403
|
+
> = this._shapeDefaultPropsByTypeSignal;
|
|
404
|
+
private nextShapeNumber = 1;
|
|
405
|
+
private nextPageNumber = 1;
|
|
406
|
+
private nextBindingNumber = 1;
|
|
407
|
+
private nextAssetNumber = 1;
|
|
408
|
+
private assetUrlResolver: (assetId: AssetId) => string | null = () => null;
|
|
409
|
+
|
|
410
|
+
private get _internalStore(): Store<EditorRecord> {
|
|
411
|
+
return getEditorStore(this);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
readonly sessionSignal = computed<SessionRecord>("editor.session", () => {
|
|
415
|
+
const session = this._internalStore.recordsSignal
|
|
416
|
+
.get()
|
|
417
|
+
.get(DEFAULT_SESSION_ID);
|
|
418
|
+
if (!isSessionRecord(session)) {
|
|
419
|
+
throw new Error("Missing session record");
|
|
420
|
+
}
|
|
421
|
+
return session;
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
readonly currentPageIdSignal = computed<PageId>(
|
|
425
|
+
"editor.currentPageId",
|
|
426
|
+
() => {
|
|
427
|
+
return this.sessionSignal.get().currentPageId;
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
readonly currentPageShapeIdsSignal: Signal<ShapeId[]>;
|
|
432
|
+
|
|
433
|
+
readonly currentPageShapesSignal: Signal<ShapeRecord[]>;
|
|
434
|
+
|
|
435
|
+
readonly culledShapeIdsSignal: Signal<ShapeId[]>;
|
|
436
|
+
readonly renderVisibleShapeIdsSignal: Signal<ShapeId[]>;
|
|
437
|
+
readonly retainedShapeIdsSignal: Signal<ShapeId[]>;
|
|
438
|
+
readonly selectionPageBoundsSignal: Signal<Box | null>;
|
|
439
|
+
readonly selectionFrameBoundsSignal: Signal<Box | null>;
|
|
440
|
+
readonly selectionToolbarAnchorPagePointSignal: Signal<Vec | null>;
|
|
441
|
+
|
|
442
|
+
readonly pagesSignal = computed<PageRecord[]>("editor.pages", () => {
|
|
443
|
+
return Array.from(this._internalStore.recordsSignal.get().values())
|
|
444
|
+
.filter(isPageRecord)
|
|
445
|
+
.sort((left, right) => left.index.localeCompare(right.index));
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
readonly currentPageSignal = computed<PageRecord | null>(
|
|
449
|
+
"editor.currentPage",
|
|
450
|
+
() => {
|
|
451
|
+
const currentPageId = this.currentPageIdSignal.get();
|
|
452
|
+
return (
|
|
453
|
+
this.pagesSignal.get().find((page) => page.id === currentPageId) ?? null
|
|
454
|
+
);
|
|
455
|
+
},
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
readonly pageStateSignal = computed<EditorPageState>(
|
|
459
|
+
"editor.pageState",
|
|
460
|
+
() => {
|
|
461
|
+
const pages = this.pagesSignal.get();
|
|
462
|
+
const currentPageId = this.currentPageIdSignal.get();
|
|
463
|
+
const currentPage =
|
|
464
|
+
pages.find((page) => page.id === currentPageId) ?? null;
|
|
465
|
+
return {
|
|
466
|
+
pages,
|
|
467
|
+
currentPageId,
|
|
468
|
+
currentPage,
|
|
469
|
+
canDeleteCurrentPage: pages.length > 1 && currentPage !== null,
|
|
470
|
+
};
|
|
471
|
+
},
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
readonly toolStateSignal = computed<EditorToolState>(
|
|
475
|
+
"editor.toolState",
|
|
476
|
+
() => {
|
|
477
|
+
const session = this.sessionSignal.get();
|
|
478
|
+
return {
|
|
479
|
+
activeToolId: session.activeToolId,
|
|
480
|
+
isToolLocked: session.isToolLocked,
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
readonly editingShapeIdSignal = computed<ShapeId | null>(
|
|
486
|
+
"editor.editingShapeId",
|
|
487
|
+
() => this.sessionSignal.get().editingShapeId,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
readonly shapeEditingSessionSignal = computed<ShapeEditingSession | null>(
|
|
491
|
+
"editor.shapeEditingSession",
|
|
492
|
+
() => this.sessionSignal.get().shapeEditingSession,
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
readonly shapeEditingStateSignal = computed<EditorShapeEditingState>(
|
|
496
|
+
"editor.shapeEditingState",
|
|
497
|
+
() => ({
|
|
498
|
+
editingShapeId: this.editingShapeIdSignal.get(),
|
|
499
|
+
session: this.shapeEditingSessionSignal.get(),
|
|
500
|
+
}),
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
readonly currentCameraSignal = computed<Camera>(
|
|
504
|
+
"editor.currentCamera",
|
|
505
|
+
() => {
|
|
506
|
+
const session = this.sessionSignal.get();
|
|
507
|
+
return (
|
|
508
|
+
session.pageCameraById[this.currentPageIdSignal.get()] ?? DEFAULT_CAMERA
|
|
509
|
+
);
|
|
510
|
+
},
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
readonly selectedShapeIdsSignal = computed<ShapeId[]>(
|
|
514
|
+
"editor.selectedShapeIds",
|
|
515
|
+
() => {
|
|
516
|
+
const session = this.sessionSignal.get();
|
|
517
|
+
return session.pageSelectedShapeIdsById[session.currentPageId] ?? [];
|
|
518
|
+
},
|
|
519
|
+
{ isEqual: areShapeIdArraysEqual },
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
readonly selectedShapeIdSetSignal = computed<Set<ShapeId>>(
|
|
523
|
+
"editor.selectedShapeIdSet",
|
|
524
|
+
() => new Set(this.selectedShapeIdsSignal.get()),
|
|
525
|
+
{ isEqual: areShapeIdSetsEqual },
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
readonly selectedShapesSignal = computed<ShapeRecord[]>(
|
|
529
|
+
"editor.selectedShapes",
|
|
530
|
+
() =>
|
|
531
|
+
this.selectedShapeIdsSignal
|
|
532
|
+
.get()
|
|
533
|
+
.map((id) => this.getShapeSignal(id).get())
|
|
534
|
+
.filter((shape): shape is ShapeRecord => !!shape),
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
readonly selectedShapeOverlayItemsSignal = computed<
|
|
538
|
+
SelectedShapeOverlayItem[]
|
|
539
|
+
>(
|
|
540
|
+
"editor.selectedShapeOverlayItems",
|
|
541
|
+
() => {
|
|
542
|
+
if (this.toolStateSignal.get().activeToolId !== "select") {
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
return this.selectedShapeIdsSignal
|
|
546
|
+
.get()
|
|
547
|
+
.map((id) => this.getShapeSignal(id).get())
|
|
548
|
+
.filter((shape): shape is ShapeRecord => !!shape)
|
|
549
|
+
.map((shape) => {
|
|
550
|
+
const overlayInfo =
|
|
551
|
+
this.getShapeUtil(shape).getSelectionOverlayInfo(shape);
|
|
552
|
+
return {
|
|
553
|
+
id: shape.id,
|
|
554
|
+
usesSelectionPathOverlay: overlayInfo.usesSelectionPathOverlay,
|
|
555
|
+
startPagePoint: overlayInfo.startPagePoint,
|
|
556
|
+
endPagePoint: overlayInfo.endPagePoint,
|
|
557
|
+
middlePagePoint: overlayInfo.middlePagePoint,
|
|
558
|
+
path: overlayInfo.path,
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
{ isEqual: areSelectedShapeOverlayItemsEqual },
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
readonly selectionStateSignal = computed<EditorSelectionState>(
|
|
566
|
+
"editor.selectionState",
|
|
567
|
+
() => {
|
|
568
|
+
const selectedShapeIds = this.selectedShapeIdsSignal.get();
|
|
569
|
+
const singleShape =
|
|
570
|
+
selectedShapeIds.length === 1
|
|
571
|
+
? (this.getShapeSignal(selectedShapeIds[0]!).get() ?? null)
|
|
572
|
+
: null;
|
|
573
|
+
const selectedShapes = this.selectedShapesSignal.get();
|
|
574
|
+
const commonShapeType = getCommonShapeType(selectedShapes);
|
|
575
|
+
const isOnlySelected = selectedShapes.length === 1;
|
|
576
|
+
const selectedShapeHandleAnchors = selectedShapes.map((shape) =>
|
|
577
|
+
this.getShapeUtil(shape).getSelectionHandleAnchors(shape, {
|
|
578
|
+
isOnlySelected,
|
|
579
|
+
editor: this,
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
const hasPointHandleSelection = selectedShapeHandleAnchors.some(
|
|
583
|
+
(anchors) =>
|
|
584
|
+
anchors.terminalHandleAnchors.length > 0 ||
|
|
585
|
+
anchors.middleHandleAnchors.length > 0 ||
|
|
586
|
+
(anchors.customHandleAnchors?.length ?? 0) > 0,
|
|
587
|
+
);
|
|
588
|
+
const hasFrameSelection = selectedShapes.some((shape) =>
|
|
589
|
+
this.getShapeUtil(shape).contributesToSelectionFrame(shape, {
|
|
590
|
+
editor: this,
|
|
591
|
+
}),
|
|
592
|
+
);
|
|
593
|
+
const isMixedFrameAndPointHandleSelection =
|
|
594
|
+
hasPointHandleSelection && hasFrameSelection;
|
|
595
|
+
const singleShapeHandleAnchors =
|
|
596
|
+
singleShape ?
|
|
597
|
+
this.getShapeUtil(singleShape).getSelectionHandleAnchors(
|
|
598
|
+
singleShape,
|
|
599
|
+
{ isOnlySelected, editor: this },
|
|
600
|
+
)
|
|
601
|
+
: null;
|
|
602
|
+
const canDragPointHandles =
|
|
603
|
+
selectedShapeIds.length === 1 &&
|
|
604
|
+
!!singleShapeHandleAnchors &&
|
|
605
|
+
(singleShapeHandleAnchors.terminalHandleAnchors.length > 0 ||
|
|
606
|
+
(singleShapeHandleAnchors.customHandleAnchors?.length ?? 0) > 0);
|
|
607
|
+
const hasSelectionRotation = this.queries.selectionHasRotationSignal.get();
|
|
608
|
+
return {
|
|
609
|
+
count: selectedShapeIds.length,
|
|
610
|
+
singleShapeType: singleShape?.type ?? null,
|
|
611
|
+
commonShapeType,
|
|
612
|
+
hasPointHandleSelection,
|
|
613
|
+
hasFrameSelection,
|
|
614
|
+
isMixedFrameAndPointHandleSelection,
|
|
615
|
+
canDragPointHandles,
|
|
616
|
+
hasSelectionRotation,
|
|
617
|
+
hasMixedSelectionRotation:
|
|
618
|
+
!this.queries.selectionHasCompatibleRotationSignal.get(),
|
|
619
|
+
canResetSelectionRotation:
|
|
620
|
+
hasSelectionRotation && hasFrameSelection,
|
|
621
|
+
frameBounds: this.selectionFrameBoundsSignal.get(),
|
|
622
|
+
frameRotation: this.queries.selectionRotationSignal.get(),
|
|
623
|
+
frameRotationCenter: this.queries.selectionRotationCenterSignal.get(),
|
|
624
|
+
toolbarAnchorPagePoint:
|
|
625
|
+
this.selectionToolbarAnchorPagePointSignal.get(),
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
readonly pageTransformSignal = computed<Mat>("editor.pageTransform", () => {
|
|
631
|
+
const camera = this.currentCameraSignal.get();
|
|
632
|
+
return Mat.Scale(camera.z, camera.z).compose(
|
|
633
|
+
Mat.Translate(-camera.x, -camera.y),
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
readonly cameraZoomSignal = computed<number>("editor.cameraZoom", () => {
|
|
638
|
+
return this.currentCameraSignal.get().z;
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
readonly viewportPageBoundsSignal = computed<Box | null>(
|
|
642
|
+
"editor.viewportPageBounds",
|
|
643
|
+
() => {
|
|
644
|
+
const { w, h } = this.viewportScreenSizeSignal.get();
|
|
645
|
+
if (w <= 0 || h <= 0) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const camera = this.currentCameraSignal.get();
|
|
650
|
+
return new Box(camera.x, camera.y, w / camera.z, h / camera.z);
|
|
651
|
+
},
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
readonly renderViewportPageBoundsSignal = computed<Box | null>(
|
|
655
|
+
"editor.renderViewportPageBounds",
|
|
656
|
+
(previous) => {
|
|
657
|
+
const viewportBounds = this.viewportPageBoundsSignal.get();
|
|
658
|
+
if (!viewportBounds) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (
|
|
663
|
+
previous !== UNINITIALIZED &&
|
|
664
|
+
previous &&
|
|
665
|
+
doesBoxContainBox(previous, viewportBounds)
|
|
666
|
+
) {
|
|
667
|
+
return previous;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return viewportBounds.expand(
|
|
671
|
+
RENDER_CULLING_OVERSCAN_SCREEN / this.cameraZoomSignal.get(),
|
|
672
|
+
);
|
|
673
|
+
},
|
|
674
|
+
{ isEqual: areBoxesEqual },
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
readonly renderItemsSignal: Signal<RenderItem[]>;
|
|
678
|
+
readonly renderHostDebugStateSignal: Signal<EditorRenderHostDebugState>;
|
|
679
|
+
readonly historyStateSignal: Signal<HistoryState>;
|
|
680
|
+
readonly shapeRenderOrderByIdSignal: Signal<Map<ShapeId, number>>;
|
|
681
|
+
readonly culledShapeIdSetSignal: Signal<Set<ShapeId>>;
|
|
682
|
+
readonly selectedShapeIdsOutsideCulledSignal: Signal<ShapeId[]>;
|
|
683
|
+
readonly selectedShapeIdsOutsideCulledSetSignal: Signal<Set<ShapeId>>;
|
|
684
|
+
readonly renderVisibleShapeIdSetSignal: Signal<Set<ShapeId>>;
|
|
685
|
+
|
|
686
|
+
private readonly shapeSignals = new Map<
|
|
687
|
+
ShapeId,
|
|
688
|
+
Signal<ShapeRecord | undefined>
|
|
689
|
+
>();
|
|
690
|
+
private readonly shapeSelectedSignals = new Map<ShapeId, Signal<boolean>>();
|
|
691
|
+
private readonly shapeRenderOrderSignals = new Map<ShapeId, Signal<number>>();
|
|
692
|
+
private readonly shapeRenderVisibleSignals = new Map<
|
|
693
|
+
ShapeId,
|
|
694
|
+
Signal<boolean>
|
|
695
|
+
>();
|
|
696
|
+
private readonly shapeDocumentVisibleSignals = new Map<
|
|
697
|
+
ShapeId,
|
|
698
|
+
Signal<boolean>
|
|
699
|
+
>();
|
|
700
|
+
private readonly shapeEditingSessionSignals = new Map<
|
|
701
|
+
ShapeId,
|
|
702
|
+
Signal<ShapeEditingSession | null>
|
|
703
|
+
>();
|
|
704
|
+
private readonly shapeDropTargetSignals = new Map<ShapeId, Signal<boolean>>();
|
|
705
|
+
private readonly shapeErasingSignals = new Map<ShapeId, Signal<boolean>>();
|
|
706
|
+
private readonly shapeSelectedDescendantTransformAncestorSignals = new Map<
|
|
707
|
+
ShapeId,
|
|
708
|
+
Signal<boolean>
|
|
709
|
+
>();
|
|
710
|
+
private lastPointerDownForClickCount:
|
|
711
|
+
| { timestamp: number; screenPoint: Vec }
|
|
712
|
+
| null = null;
|
|
713
|
+
private lastPointerUpForClickCount:
|
|
714
|
+
| { timestamp: number; screenPoint: Vec }
|
|
715
|
+
| null = null;
|
|
716
|
+
private currentPointerClickCount = 1;
|
|
717
|
+
|
|
718
|
+
readonly selectionOverlayFrameGeometrySignal: Signal<SelectionOverlayFrameGeometryState | null>;
|
|
719
|
+
readonly selectionOverlayCompanionFrameGeometriesSignal: Signal<
|
|
720
|
+
SelectionOverlayFrameGeometryState[]
|
|
721
|
+
>;
|
|
722
|
+
readonly selectionOverlayArrowPathSignal: Signal<string | null>;
|
|
723
|
+
readonly selectionOverlayFrameStyleSignal: Signal<SelectionOverlayFrameStyleState>;
|
|
724
|
+
readonly selectionOverlayFrameSignal: Signal<SelectionOverlayFrameState | null>;
|
|
725
|
+
readonly selectionOverlayHandleAnchorsSignal: Signal<SelectionOverlayHandleAnchorsState | null>;
|
|
726
|
+
readonly selectionOverlayHandlesSignal: Signal<SelectionOverlayHandlesState | null>;
|
|
727
|
+
readonly selectionOverlayHandleCountsSignal: Signal<SelectionOverlayHandleCountsState | null>;
|
|
728
|
+
readonly selectionOverlaySignal: Signal<SelectionOverlayState | null>;
|
|
729
|
+
readonly overlayDebugStateSignal: Signal<EditorOverlayDebugState>;
|
|
730
|
+
|
|
731
|
+
constructor(options: EditorOptions = {}) {
|
|
732
|
+
this.shapeCapabilityDefinitions = options.shapeCapabilities ?? [];
|
|
733
|
+
if (options.resolveAssetUrl) {
|
|
734
|
+
this.assetUrlResolver = options.resolveAssetUrl;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
for (const util of options.shapeUtils ?? []) {
|
|
738
|
+
this.shapeUtils.register(util);
|
|
739
|
+
}
|
|
740
|
+
for (const exporter of options.shapeSvgExporters ?? []) {
|
|
741
|
+
this.shapeSvgExporters.register(exporter);
|
|
742
|
+
}
|
|
743
|
+
for (const util of options.bindingUtils ?? []) {
|
|
744
|
+
this.bindingUtils.register(util);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const store = new Store<EditorRecord>(
|
|
748
|
+
this.snapshots.buildInitialRecords(
|
|
749
|
+
options.documentSnapshot ?? null,
|
|
750
|
+
options.sessionSnapshot ?? null,
|
|
751
|
+
),
|
|
752
|
+
);
|
|
753
|
+
setEditorStore(this, store);
|
|
754
|
+
this.store = store;
|
|
755
|
+
this.snapshots.ensureSessionIntegrity();
|
|
756
|
+
this.queries = new QueryManager(this);
|
|
757
|
+
this.bindings = new BindingManager(this);
|
|
758
|
+
this.spatialIndex = new SpatialIndexManager(this, this.queries);
|
|
759
|
+
this.snaps = new SnapManager(this);
|
|
760
|
+
this.snapIndicatorsSignal = this.snaps.indicatorsSignal;
|
|
761
|
+
this.snapIndicatorsActiveSignal = this.snaps.activeSignal;
|
|
762
|
+
this.currentPageShapeIdsSignal = this.queries.currentPageShapeIdsSignal;
|
|
763
|
+
this.currentPageShapesSignal = this.queries.currentPageShapesSignal;
|
|
764
|
+
this.selectionPageBoundsSignal = this.queries.selectionPageBoundsSignal;
|
|
765
|
+
this.selectionFrameBoundsSignal = this.queries.selectionFrameBoundsSignal;
|
|
766
|
+
this.selectionToolbarAnchorPagePointSignal =
|
|
767
|
+
this.queries.selectionToolbarAnchorPagePointSignal;
|
|
768
|
+
this.selectionOverlay = new SelectionOverlayManager(this);
|
|
769
|
+
this.selectionOverlayFrameGeometrySignal =
|
|
770
|
+
this.selectionOverlay.frameGeometrySignal;
|
|
771
|
+
this.selectionOverlayCompanionFrameGeometriesSignal =
|
|
772
|
+
this.selectionOverlay.companionFrameGeometriesSignal;
|
|
773
|
+
this.selectionOverlayArrowPathSignal =
|
|
774
|
+
this.selectionOverlay.arrowPathSignal;
|
|
775
|
+
this.selectionOverlayFrameStyleSignal =
|
|
776
|
+
this.selectionOverlay.frameStyleSignal;
|
|
777
|
+
this.selectionOverlayFrameSignal = this.selectionOverlay.frameSignal;
|
|
778
|
+
this.selectionOverlayHandleAnchorsSignal =
|
|
779
|
+
this.selectionOverlay.handleAnchorsSignal;
|
|
780
|
+
this.selectionOverlayHandlesSignal = this.selectionOverlay.handlesSignal;
|
|
781
|
+
this.selectionOverlayHandleCountsSignal =
|
|
782
|
+
this.selectionOverlay.handleCountsSignal;
|
|
783
|
+
this.selectionOverlaySignal = this.selectionOverlay.overlaySignal;
|
|
784
|
+
this.overlayDebugStateSignal = this.selectionOverlay.debugStateSignal;
|
|
785
|
+
this.renderWindow = new RenderWindowManager(this);
|
|
786
|
+
this.shapeRenderOrderByIdSignal =
|
|
787
|
+
this.renderWindow.shapeRenderOrderByIdSignal;
|
|
788
|
+
this.culledShapeIdsSignal = this.renderWindow.culledShapeIdsSignal;
|
|
789
|
+
this.culledShapeIdSetSignal = this.renderWindow.culledShapeIdSetSignal;
|
|
790
|
+
this.selectedShapeIdsOutsideCulledSignal =
|
|
791
|
+
this.renderWindow.selectedShapeIdsOutsideCulledSignal;
|
|
792
|
+
this.selectedShapeIdsOutsideCulledSetSignal =
|
|
793
|
+
this.renderWindow.selectedShapeIdsOutsideCulledSetSignal;
|
|
794
|
+
this.renderVisibleShapeIdsSignal =
|
|
795
|
+
this.renderWindow.renderVisibleShapeIdsSignal;
|
|
796
|
+
this.retainedShapeIdsSignal = this.renderWindow.retainedShapeIdsSignal;
|
|
797
|
+
this.renderVisibleShapeIdSetSignal =
|
|
798
|
+
this.renderWindow.renderVisibleShapeIdSetSignal;
|
|
799
|
+
this.renderItemsSignal = this.renderWindow.renderItemsSignal;
|
|
800
|
+
this.renderHostDebugStateSignal =
|
|
801
|
+
this.renderWindow.renderHostDebugStateSignal;
|
|
802
|
+
this.syncShapeCounter();
|
|
803
|
+
this.syncPageCounter();
|
|
804
|
+
this.syncBindingCounter();
|
|
805
|
+
this.syncAssetCounter();
|
|
806
|
+
this.bindings.repairLoadedBindings("system");
|
|
807
|
+
this.history = new HistoryManager(this);
|
|
808
|
+
this.historyStateSignal = this.history.state;
|
|
809
|
+
this.commands = createEditorCommands(this);
|
|
810
|
+
this.registerBuiltInSideEffects();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private registerBuiltInSideEffects(): void {
|
|
814
|
+
// Binding cleanup must run before the shapes are removed so the bindings
|
|
815
|
+
// can still read the doomed shapes. Selection/editing-session repair runs
|
|
816
|
+
// after the records are gone.
|
|
817
|
+
this.sideEffects.registerBeforeDeleteHandler("shape", (ids, source) => {
|
|
818
|
+
this.bindings.cleanupBindingsForDeletedShapes(ids as ShapeId[], source);
|
|
819
|
+
});
|
|
820
|
+
this.sideEffects.registerAfterDeleteHandler("shape", (ids, source) => {
|
|
821
|
+
this.removeShapeIdsFromSelection(ids as ShapeId[], source);
|
|
822
|
+
this.removeShapeIdsFromEditingSession(ids as ShapeId[], source);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
dispatch(event: EditorEvent): void {
|
|
827
|
+
const normalizedEvent = this.normalizeEvent(event);
|
|
828
|
+
this.inputs.handleEvent(normalizedEvent);
|
|
829
|
+
|
|
830
|
+
if (normalizedEvent.type === "keyboard" && normalizedEvent.name === "key_down") {
|
|
831
|
+
if (
|
|
832
|
+
normalizedEvent.altKey &&
|
|
833
|
+
!normalizedEvent.accelKey &&
|
|
834
|
+
!normalizedEvent.shiftKey &&
|
|
835
|
+
(normalizedEvent.key === "ArrowDown" ||
|
|
836
|
+
normalizedEvent.key === "ArrowUp")
|
|
837
|
+
) {
|
|
838
|
+
const direction =
|
|
839
|
+
normalizedEvent.key === "ArrowDown" ? "backward" : "forward";
|
|
840
|
+
if (this.moveSelectionWithinParent(direction, "user")) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (normalizedEvent.accelKey && normalizedEvent.key.toLowerCase() === "z") {
|
|
846
|
+
if (normalizedEvent.shiftKey) {
|
|
847
|
+
this.redo();
|
|
848
|
+
} else {
|
|
849
|
+
this.undo();
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (!normalizedEvent.accelKey && !normalizedEvent.altKey) {
|
|
855
|
+
const shortcutToolId = this.tools.getToolIdForShortcut(
|
|
856
|
+
normalizedEvent.key,
|
|
857
|
+
);
|
|
858
|
+
if (shortcutToolId) {
|
|
859
|
+
this.commands.setActiveTool(shortcutToolId, { source: "system" });
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (normalizedEvent.key === "Backspace" || normalizedEvent.key === "Delete") {
|
|
864
|
+
this.deleteSelection("user");
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (
|
|
872
|
+
normalizedEvent.type === "keyboard" &&
|
|
873
|
+
normalizedEvent.name === "key_down" &&
|
|
874
|
+
normalizedEvent.key === "Escape"
|
|
875
|
+
) {
|
|
876
|
+
const wasInteracting = !this.tools.isActiveToolInInitialState();
|
|
877
|
+
if (!wasInteracting && this.getActiveToolId() !== "select") {
|
|
878
|
+
this.commands.setActiveTool("select", { source: "system" });
|
|
879
|
+
} else {
|
|
880
|
+
this.cancelInteractionAndClearTransients("escape");
|
|
881
|
+
}
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (normalizedEvent.type === "wheel") {
|
|
886
|
+
this.handleWheelEvent(normalizedEvent);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (normalizedEvent.type === "cancel") {
|
|
891
|
+
this.cancelInteractionAndClearTransients(normalizedEvent.reason);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (normalizedEvent.type === "pointer" && normalizedEvent.name === "pointer_cancel") {
|
|
896
|
+
this.cancelInteractionAndClearTransients("pointer_cancel");
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (this.interactions.handleEvent(this, normalizedEvent) === "handled") {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
this.tools.dispatch(normalizedEvent);
|
|
905
|
+
|
|
906
|
+
if (
|
|
907
|
+
normalizedEvent.type === "pointer" &&
|
|
908
|
+
normalizedEvent.name === "pointer_up"
|
|
909
|
+
) {
|
|
910
|
+
const doubleClickEvent = this.getDoubleClickEvent(normalizedEvent);
|
|
911
|
+
if (doubleClickEvent) {
|
|
912
|
+
if (this.interactions.handleEvent(this, doubleClickEvent) !== "handled") {
|
|
913
|
+
this.tools.dispatch(doubleClickEvent);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
handleInteractionEvent(event: EditorEvent): boolean {
|
|
920
|
+
const normalizedEvent = this.normalizeEvent(event);
|
|
921
|
+
this.inputs.handleEvent(normalizedEvent);
|
|
922
|
+
return this.interactions.handleEvent(this, normalizedEvent) === "handled";
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private normalizeEvent(event: EditorEvent): EditorEvent {
|
|
926
|
+
if (event.type !== "pointer") {
|
|
927
|
+
return event;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (event.name === "pointer_up") {
|
|
931
|
+
// Pointer up inherits the click count computed from the preceding down/up
|
|
932
|
+
// sequence so tools can distinguish single-click caret placement from
|
|
933
|
+
// double-click select-all without relying on host DOM timing quirks.
|
|
934
|
+
const nextEvent =
|
|
935
|
+
event.clickCount === this.currentPointerClickCount
|
|
936
|
+
? event
|
|
937
|
+
: ({
|
|
938
|
+
...event,
|
|
939
|
+
clickCount: this.currentPointerClickCount,
|
|
940
|
+
} satisfies PointerEditorEvent);
|
|
941
|
+
this.lastPointerUpForClickCount = {
|
|
942
|
+
timestamp: nextEvent.timestamp,
|
|
943
|
+
screenPoint: nextEvent.screenPoint.clone(),
|
|
944
|
+
};
|
|
945
|
+
return nextEvent;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (event.name !== "pointer_down") {
|
|
949
|
+
return event;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Keep double-click detection in core so host layers only forward pointer
|
|
953
|
+
// events; select/text tools can then share one consistent click contract.
|
|
954
|
+
const previousDown = this.lastPointerDownForClickCount;
|
|
955
|
+
const previousUp = this.lastPointerUpForClickCount;
|
|
956
|
+
const nextClickCount =
|
|
957
|
+
previousDown &&
|
|
958
|
+
previousUp &&
|
|
959
|
+
previousUp.timestamp >= previousDown.timestamp &&
|
|
960
|
+
event.timestamp - previousUp.timestamp <= DOUBLE_CLICK_DURATION_MS &&
|
|
961
|
+
previousUp.screenPoint.dist2(event.screenPoint) <=
|
|
962
|
+
DOUBLE_CLICK_DISTANCE_SQUARED
|
|
963
|
+
? 2
|
|
964
|
+
: 1;
|
|
965
|
+
|
|
966
|
+
this.lastPointerDownForClickCount = {
|
|
967
|
+
timestamp: event.timestamp,
|
|
968
|
+
screenPoint: event.screenPoint.clone(),
|
|
969
|
+
};
|
|
970
|
+
this.currentPointerClickCount = nextClickCount;
|
|
971
|
+
|
|
972
|
+
if (event.clickCount === nextClickCount) {
|
|
973
|
+
return event;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return {
|
|
977
|
+
...event,
|
|
978
|
+
clickCount: nextClickCount,
|
|
979
|
+
} satisfies PointerEditorEvent;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
private getDoubleClickEvent(
|
|
983
|
+
event: PointerEditorEvent,
|
|
984
|
+
): ClickEditorEvent | null {
|
|
985
|
+
if ((event.clickCount ?? 1) < 2 || this.currentPointerClickCount < 2) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return {
|
|
990
|
+
type: "click",
|
|
991
|
+
name: "double_click",
|
|
992
|
+
phase: "up",
|
|
993
|
+
timestamp: event.timestamp,
|
|
994
|
+
pointerId: event.pointerId,
|
|
995
|
+
button: event.button,
|
|
996
|
+
buttons: event.buttons,
|
|
997
|
+
pointerType: event.pointerType,
|
|
998
|
+
screenPoint: event.screenPoint.clone(),
|
|
999
|
+
pagePoint: event.pagePoint.clone(),
|
|
1000
|
+
pressure: event.pressure,
|
|
1001
|
+
shiftKey: event.shiftKey,
|
|
1002
|
+
altKey: event.altKey,
|
|
1003
|
+
ctrlKey: event.ctrlKey,
|
|
1004
|
+
metaKey: event.metaKey,
|
|
1005
|
+
accelKey: event.accelKey,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
listen(listener: (change: StoreChange<EditorRecord>) => void): () => void {
|
|
1010
|
+
return this.store.listen((change) => listener(change));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
getRenderItems(): RenderItem[] {
|
|
1014
|
+
return this.renderItemsSignal.get();
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
getPages(): PageRecord[] {
|
|
1018
|
+
return this.pagesSignal.get();
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
getCurrentPageId(): PageId {
|
|
1022
|
+
return this.currentPageIdSignal.get();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
getActiveToolId(): string {
|
|
1026
|
+
return this.sessionSignal.get().activeToolId;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
isToolLocked(): boolean {
|
|
1030
|
+
return this.sessionSignal.get().isToolLocked;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
isActiveToolInInitialState(): boolean {
|
|
1034
|
+
return this.tools.isActiveToolInInitialState();
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
getEditingShapeId(): ShapeId | null {
|
|
1038
|
+
return this.editingShapeIdSignal.get();
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
getShapeEditingSession(): ShapeEditingSession | null {
|
|
1042
|
+
return this.shapeEditingSessionSignal.get();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
getShapeEditingEntryRequest(): ShapeEditingEntryRequest | null {
|
|
1046
|
+
return this.shapeEditingEntryRequestSignal.get();
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
resetPointerClickTracking(): void {
|
|
1050
|
+
this.lastPointerDownForClickCount = null;
|
|
1051
|
+
this.lastPointerUpForClickCount = null;
|
|
1052
|
+
this.currentPointerClickCount = 1;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
getCurrentPageShapeIds(): ShapeId[] {
|
|
1056
|
+
return this.currentPageShapeIdsSignal.get();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
getPage(id: PageId): PageRecord | undefined {
|
|
1060
|
+
const record = this._internalStore.get(id);
|
|
1061
|
+
return isPageRecord(record) ? record : undefined;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
getCurrentCamera(): Camera {
|
|
1065
|
+
return this.camera.getCurrentCamera();
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
undo(): boolean {
|
|
1069
|
+
this.cancelInteractionForCommand();
|
|
1070
|
+
return this.history.undo();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
redo(): boolean {
|
|
1074
|
+
this.cancelInteractionForCommand();
|
|
1075
|
+
return this.history.redo();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
createPageAndSelect(
|
|
1079
|
+
input?: CreatePageInput,
|
|
1080
|
+
source: ChangeSource = "user",
|
|
1081
|
+
): PageRecord {
|
|
1082
|
+
const page = this.commands.createPage(input, { source });
|
|
1083
|
+
this.commands.setCurrentPageId(page.id, { source });
|
|
1084
|
+
return page;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
selectPage(pageId: PageId, source: ChangeSource = "user"): void {
|
|
1088
|
+
this.commands.setCurrentPageId(pageId, { source });
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
renamePage(
|
|
1092
|
+
pageId: PageId,
|
|
1093
|
+
name: string,
|
|
1094
|
+
source: ChangeSource = "user",
|
|
1095
|
+
): void {
|
|
1096
|
+
this.commands.renamePage({ id: pageId, name }, { source });
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
deletePage(pageId: PageId, source: ChangeSource = "user"): boolean {
|
|
1100
|
+
this.cancelInteractionForCommand();
|
|
1101
|
+
return this.commands.deletePage(pageId, { source });
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
setActiveTool(toolId: string, source: ChangeSource = "system"): void {
|
|
1105
|
+
this.commands.setActiveTool(toolId, { source });
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
setToolLocked(locked: boolean, source: ChangeSource = "system"): void {
|
|
1109
|
+
this.commands.setToolLocked(locked, { source });
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
toggleToolLocked(source: ChangeSource = "system"): void {
|
|
1113
|
+
this.setToolLocked(!this.isToolLocked(), source);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
getCamera(pageId: PageId = this.getCurrentPageId()): Camera {
|
|
1117
|
+
return this.camera.getCamera(pageId);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
pageToScreen(point: Vec, pageId: PageId = this.getCurrentPageId()): Vec {
|
|
1121
|
+
return this.camera.pageToScreen(point, pageId);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
screenToPage(point: Vec, pageId: PageId = this.getCurrentPageId()): Vec {
|
|
1125
|
+
return this.camera.screenToPage(point, pageId);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
getSelectedShapeIds(pageId: PageId = this.getCurrentPageId()): ShapeId[] {
|
|
1129
|
+
if (pageId === this.getCurrentPageId()) {
|
|
1130
|
+
return this.selectedShapeIdsSignal.get();
|
|
1131
|
+
}
|
|
1132
|
+
return this.sessionSignal.get().pageSelectedShapeIdsById[pageId] ?? [];
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
isShapeHidden(shapeId: ShapeId): boolean {
|
|
1136
|
+
const seen = new Set<ShapeId>();
|
|
1137
|
+
let shape = this.getShape(shapeId);
|
|
1138
|
+
while (shape) {
|
|
1139
|
+
if (shape.isHidden) {
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
if (!shape.parentId.startsWith("shape:")) {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const parentId = shape.parentId as ShapeId;
|
|
1147
|
+
if (seen.has(parentId)) {
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
seen.add(parentId);
|
|
1151
|
+
shape = this.getShape(parentId);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
getSelectedShapes(): ShapeRecord[] {
|
|
1158
|
+
return this.selectedShapesSignal.get();
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
getDefaultShapeProps<TProps extends object = object>(
|
|
1162
|
+
type: ShapeRecord["type"],
|
|
1163
|
+
): TProps {
|
|
1164
|
+
const util = this.shapeUtils.get(type);
|
|
1165
|
+
const override = this._shapeDefaultPropsByTypeSignal.get().get(type) ?? {};
|
|
1166
|
+
return util.validateProps({
|
|
1167
|
+
...util.getDefaultProps(),
|
|
1168
|
+
...override,
|
|
1169
|
+
}) as TProps;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
updateDefaultShapeProps(
|
|
1173
|
+
type: ShapeRecord["type"],
|
|
1174
|
+
props: ShapePropsPatch,
|
|
1175
|
+
): void {
|
|
1176
|
+
const util = this.shapeUtils.get(type);
|
|
1177
|
+
const current = this._shapeDefaultPropsByTypeSignal.get().get(type) ?? {};
|
|
1178
|
+
const nextProps = util.validateProps({
|
|
1179
|
+
...util.getDefaultProps(),
|
|
1180
|
+
...current,
|
|
1181
|
+
...props,
|
|
1182
|
+
});
|
|
1183
|
+
const fallback = util.validateProps(util.getDefaultProps());
|
|
1184
|
+
const nextOverride: ShapePropsPatch = {};
|
|
1185
|
+
|
|
1186
|
+
for (const [key, value] of Object.entries(nextProps)) {
|
|
1187
|
+
if ((fallback as Record<string, unknown>)[key] !== value) {
|
|
1188
|
+
nextOverride[key] = value;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (areShapePropsPatchesEqual(current, nextOverride)) {
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const nextDefaults = new Map(this._shapeDefaultPropsByTypeSignal.get());
|
|
1197
|
+
if (Object.keys(nextOverride).length === 0) {
|
|
1198
|
+
nextDefaults.delete(type);
|
|
1199
|
+
} else {
|
|
1200
|
+
nextDefaults.set(type, nextOverride);
|
|
1201
|
+
}
|
|
1202
|
+
this._shapeDefaultPropsByTypeSignal.set(nextDefaults);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
getSelectionParentOrderState(): ShapeSelectionSiblingOrderState | null {
|
|
1206
|
+
return this.hierarchy.getSelectionParentOrderState();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
getSelectionGroupState(): ShapeGroupState | null {
|
|
1210
|
+
return this.hierarchy.getSelectionGroupState();
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
canGroupSelection(): boolean {
|
|
1214
|
+
return this.hierarchy.canGroupSelection();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
getLayerTree(pageId: PageId = this.getCurrentPageId()): LayerTreeNode[] {
|
|
1218
|
+
return this.hierarchy.getLayerTree(pageId);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
getLayerNode(id: ShapeId): LayerTreeNode | null {
|
|
1222
|
+
return this.hierarchy.getLayerNode(id);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
canGroupShapes(ids: readonly ShapeId[]): boolean {
|
|
1226
|
+
return this.hierarchy.canGroupShapes(ids);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
groupShapes(
|
|
1230
|
+
ids: readonly ShapeId[],
|
|
1231
|
+
source: ChangeSource = "user",
|
|
1232
|
+
): ShapeRecord | null {
|
|
1233
|
+
return this.hierarchy.groupShapes(ids, source);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
groupSelection(source: ChangeSource = "user"): ShapeRecord | null {
|
|
1237
|
+
return this.hierarchy.groupSelection(source);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
getUngroupableSelectedShapeIds(): ShapeId[] {
|
|
1241
|
+
return this.hierarchy.getUngroupableSelectedShapeIds();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
getUngroupableShapeIds(ids: readonly ShapeId[]): ShapeId[] {
|
|
1245
|
+
return this.hierarchy.getUngroupableShapeIds(ids);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
canUngroupSelection(): boolean {
|
|
1249
|
+
return this.hierarchy.canUngroupSelection();
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
canUngroupShapes(ids: readonly ShapeId[]): boolean {
|
|
1253
|
+
return this.hierarchy.canUngroupShapes(ids);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
ungroupShapes(
|
|
1257
|
+
ids: readonly ShapeId[],
|
|
1258
|
+
source: ChangeSource = "user",
|
|
1259
|
+
): ShapeId[] {
|
|
1260
|
+
return this.hierarchy.ungroupShapes(ids, source);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
ungroupSelection(source: ChangeSource = "user"): ShapeId[] {
|
|
1264
|
+
return this.hierarchy.ungroupSelection(source);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
getSelectionSiblingOrderState(): ShapeSiblingOrderState | null {
|
|
1268
|
+
return this.hierarchy.getSelectionSiblingOrderState();
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
canMoveSelectionWithinParent(
|
|
1272
|
+
direction: ShapeSiblingOrderDirection,
|
|
1273
|
+
): boolean {
|
|
1274
|
+
return this.hierarchy.canMoveSelectionWithinParent(direction);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
canMoveSelectionInParentOrder(
|
|
1278
|
+
direction: ShapeSiblingOrderDirection,
|
|
1279
|
+
): boolean {
|
|
1280
|
+
return this.hierarchy.canMoveSelectionInParentOrder(direction);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
canMoveShapesInParentOrder(
|
|
1284
|
+
ids: readonly ShapeId[],
|
|
1285
|
+
direction: LayerOrderDirection,
|
|
1286
|
+
): boolean {
|
|
1287
|
+
return this.hierarchy.canMoveShapesInParentOrder(ids, direction);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
moveShapesInParentOrder(
|
|
1291
|
+
ids: readonly ShapeId[],
|
|
1292
|
+
direction: LayerOrderDirection,
|
|
1293
|
+
source: ChangeSource = "user",
|
|
1294
|
+
): boolean {
|
|
1295
|
+
return this.hierarchy.moveShapesInParentOrder(ids, direction, source);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
moveShapesToParentOrderEdge(
|
|
1299
|
+
ids: readonly ShapeId[],
|
|
1300
|
+
edge: LayerOrderEdge,
|
|
1301
|
+
source: ChangeSource = "user",
|
|
1302
|
+
): boolean {
|
|
1303
|
+
return this.hierarchy.moveShapesToParentOrderEdge(ids, edge, source);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
moveSelectionWithinParent(
|
|
1307
|
+
direction: ShapeSiblingOrderDirection,
|
|
1308
|
+
source: ChangeSource = "user",
|
|
1309
|
+
): boolean {
|
|
1310
|
+
return this.hierarchy.moveSelectionWithinParent(direction, source);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Align selected top-level shapes along an edge or center of their combined
|
|
1315
|
+
* page bounds. Only shapes parented directly to the current page are moved so
|
|
1316
|
+
* the page-space delta maps directly onto `x`/`y`. Needs at least two shapes.
|
|
1317
|
+
*/
|
|
1318
|
+
alignSelection(
|
|
1319
|
+
mode:
|
|
1320
|
+
| "left"
|
|
1321
|
+
| "horizontal-center"
|
|
1322
|
+
| "right"
|
|
1323
|
+
| "top"
|
|
1324
|
+
| "vertical-center"
|
|
1325
|
+
| "bottom",
|
|
1326
|
+
source: ChangeSource = "user",
|
|
1327
|
+
): boolean {
|
|
1328
|
+
this.cancelInteractionForCommand();
|
|
1329
|
+
return this.selectionLayout.align(mode, source);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Distribute selected top-level shapes so the gaps between them are equal
|
|
1334
|
+
* along the given axis. Needs at least three shapes.
|
|
1335
|
+
*/
|
|
1336
|
+
distributeSelection(
|
|
1337
|
+
axis: "horizontal" | "vertical",
|
|
1338
|
+
source: ChangeSource = "user",
|
|
1339
|
+
): boolean {
|
|
1340
|
+
this.cancelInteractionForCommand();
|
|
1341
|
+
return this.selectionLayout.distribute(axis, source);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
moveShapesToParentPlacement(
|
|
1345
|
+
input: ReparentShapePlacementInput,
|
|
1346
|
+
source: ChangeSource = "user",
|
|
1347
|
+
): boolean {
|
|
1348
|
+
return this.hierarchy.moveShapesToParentPlacement(input, source);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
moveShapesToParentPlacementResult(
|
|
1352
|
+
input: ReparentShapePlacementInput,
|
|
1353
|
+
source: ChangeSource = "user",
|
|
1354
|
+
): ReparentShapePlacementResult {
|
|
1355
|
+
return this.hierarchy.moveShapesToParentPlacementResult(input, source);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
moveSelectionInParentOrder(
|
|
1359
|
+
direction: ShapeSiblingOrderDirection,
|
|
1360
|
+
source: ChangeSource = "user",
|
|
1361
|
+
): boolean {
|
|
1362
|
+
return this.hierarchy.moveSelectionInParentOrder(direction, source);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
moveSelectionToParentOrderEdge(
|
|
1366
|
+
edge: ShapeSiblingOrderEdge,
|
|
1367
|
+
source: ChangeSource = "user",
|
|
1368
|
+
): boolean {
|
|
1369
|
+
return this.hierarchy.moveSelectionToParentOrderEdge(edge, source);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
canEditShape(shapeOrId: ShapeRecord | ShapeId | null | undefined): boolean {
|
|
1373
|
+
return this.editing.canEditShape(shapeOrId);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
startEditingShape(
|
|
1377
|
+
input: StartEditingShapeInput,
|
|
1378
|
+
source: ChangeSource = "user",
|
|
1379
|
+
): ShapeEditingSession | null {
|
|
1380
|
+
return this.editing.start(input, source);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
completeEditing(source: ChangeSource = "user"): boolean {
|
|
1384
|
+
return this.editing.complete(source);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
cancelEditing(source: ChangeSource = "user"): boolean {
|
|
1388
|
+
return this.editing.cancel(source);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
setShapeEditingComposition(
|
|
1392
|
+
isComposing: boolean,
|
|
1393
|
+
source: ChangeSource = "system",
|
|
1394
|
+
): void {
|
|
1395
|
+
this.editing.setComposition(isComposing, source);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
consumeShapeEditingEntryRequest(requestId: number): void {
|
|
1399
|
+
this.editing.consumeEntryRequest(requestId);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
deleteSelection(source: ChangeSource = "user"): void {
|
|
1403
|
+
this.deleteShapesById(this.getSelectedShapeIds(), source);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
deleteShapesById(
|
|
1407
|
+
ids: readonly ShapeId[],
|
|
1408
|
+
source: ChangeSource = "user",
|
|
1409
|
+
): void {
|
|
1410
|
+
const uniqueIds = Array.from(new Set(ids));
|
|
1411
|
+
if (uniqueIds.length === 0) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
this.cancelInteractionForCommand();
|
|
1415
|
+
this.commands.deleteShapes(uniqueIds, { source });
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
setShapesHidden(
|
|
1419
|
+
ids: readonly ShapeId[],
|
|
1420
|
+
isHidden: boolean,
|
|
1421
|
+
source: ChangeSource = "user",
|
|
1422
|
+
): ShapeId[] {
|
|
1423
|
+
const nextIds = Array.from(
|
|
1424
|
+
new Set(
|
|
1425
|
+
ids.filter((id) => {
|
|
1426
|
+
const shape = this.getShape(id);
|
|
1427
|
+
return !!shape && shape.isHidden !== isHidden;
|
|
1428
|
+
}),
|
|
1429
|
+
),
|
|
1430
|
+
);
|
|
1431
|
+
if (nextIds.length === 0) {
|
|
1432
|
+
return [];
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
this.cancelInteractionForCommand();
|
|
1436
|
+
this.commands.updateShapes(
|
|
1437
|
+
nextIds.map((id) => ({
|
|
1438
|
+
id,
|
|
1439
|
+
isHidden,
|
|
1440
|
+
})),
|
|
1441
|
+
{ source },
|
|
1442
|
+
);
|
|
1443
|
+
|
|
1444
|
+
if (isHidden) {
|
|
1445
|
+
this.removeShapeIdsFromSelection(
|
|
1446
|
+
this.getShapeIdsIncludingDescendants(nextIds),
|
|
1447
|
+
source,
|
|
1448
|
+
);
|
|
1449
|
+
this.removeShapeIdsFromEditingSession(
|
|
1450
|
+
this.getShapeIdsIncludingDescendants(nextIds),
|
|
1451
|
+
source,
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return nextIds;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
setShapesLocked(
|
|
1459
|
+
ids: readonly ShapeId[],
|
|
1460
|
+
isLocked: boolean,
|
|
1461
|
+
source: ChangeSource = "user",
|
|
1462
|
+
): ShapeId[] {
|
|
1463
|
+
const nextIds = Array.from(
|
|
1464
|
+
new Set(
|
|
1465
|
+
ids.filter((id) => {
|
|
1466
|
+
const shape = this.getShape(id);
|
|
1467
|
+
return !!shape && shape.isLocked !== isLocked;
|
|
1468
|
+
}),
|
|
1469
|
+
),
|
|
1470
|
+
);
|
|
1471
|
+
if (nextIds.length === 0) {
|
|
1472
|
+
return [];
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
this.cancelInteractionForCommand();
|
|
1476
|
+
this.commands.updateShapes(
|
|
1477
|
+
nextIds.map((id) => ({
|
|
1478
|
+
id,
|
|
1479
|
+
isLocked,
|
|
1480
|
+
})),
|
|
1481
|
+
{ source },
|
|
1482
|
+
);
|
|
1483
|
+
|
|
1484
|
+
if (isLocked) {
|
|
1485
|
+
this.removeShapeIdsFromSelection(
|
|
1486
|
+
this.getShapeIdsIncludingDescendants(nextIds),
|
|
1487
|
+
source,
|
|
1488
|
+
);
|
|
1489
|
+
this.removeShapeIdsFromEditingSession(
|
|
1490
|
+
this.getShapeIdsIncludingDescendants(nextIds),
|
|
1491
|
+
source,
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return nextIds;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
createClipboardPayloadFromSelection(): EditorClipboardPayload | null {
|
|
1499
|
+
return this.clipboard.createPayloadFromSelection();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
pasteClipboardPayload(input: PasteClipboardPayloadInput): ShapeId[] {
|
|
1503
|
+
return this.clipboard.pastePayload(input);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
duplicateSelection(input: DuplicateSelectionInput = {}): ShapeId[] {
|
|
1507
|
+
return this.clipboard.duplicateSelection(input);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
selectAllCurrentPage(source: ChangeSource = "user"): void {
|
|
1511
|
+
this.cancelInteractionForCommand();
|
|
1512
|
+
this.selection.selectAllCurrentPage(source);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
resetSelectionRotation(source: ChangeSource = "user"): void {
|
|
1516
|
+
resetEditorSelectionRotation(this, source);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
flipSelection(
|
|
1520
|
+
axis: ShapeSelectionFlipAxis,
|
|
1521
|
+
source: ChangeSource = "user",
|
|
1522
|
+
): void {
|
|
1523
|
+
const updates = getEditorSelectionFlipUpdates(this, axis);
|
|
1524
|
+
if (updates.length === 0) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
this.cancelInteractionForCommand();
|
|
1529
|
+
this.commands.updateShapes(updates, { source });
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
getShape(id: ShapeId): ShapeRecord | undefined {
|
|
1533
|
+
const record = this._internalStore.get(id);
|
|
1534
|
+
return isShapeRecord(record) ? record : undefined;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
getAsset(id: AssetId): AssetRecord | undefined {
|
|
1538
|
+
const record = this._internalStore.get(id);
|
|
1539
|
+
return isAssetRecord(record) ? record : undefined;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
getAssets(): AssetRecord[] {
|
|
1543
|
+
return this._internalStore
|
|
1544
|
+
.getAll()
|
|
1545
|
+
.filter(isAssetRecord)
|
|
1546
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
resolveAssetUrl(assetId: AssetId): string | null {
|
|
1550
|
+
return this.assetUrlResolver(assetId);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
getUnreferencedAssetIdsAfterShapeDelete(
|
|
1554
|
+
shapeIds: readonly ShapeId[],
|
|
1555
|
+
): AssetId[] {
|
|
1556
|
+
if (shapeIds.length === 0) {
|
|
1557
|
+
return [];
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const deletedShapeIds = new Set(shapeIds);
|
|
1561
|
+
const deletedAssetIds = new Set<AssetId>();
|
|
1562
|
+
const retainedAssetIds = new Set<AssetId>();
|
|
1563
|
+
|
|
1564
|
+
for (const shape of this._internalStore.getAll().filter(isShapeRecord)) {
|
|
1565
|
+
const assetId = getShapeAssetId(shape);
|
|
1566
|
+
if (!assetId) {
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
if (deletedShapeIds.has(shape.id)) {
|
|
1570
|
+
deletedAssetIds.add(assetId);
|
|
1571
|
+
} else {
|
|
1572
|
+
retainedAssetIds.add(assetId);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return this.getAssets()
|
|
1577
|
+
.map((asset) => asset.id)
|
|
1578
|
+
.filter(
|
|
1579
|
+
(assetId) =>
|
|
1580
|
+
deletedAssetIds.has(assetId) && !retainedAssetIds.has(assetId),
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
getShapeSignal(id: ShapeId): Signal<ShapeRecord | undefined> {
|
|
1585
|
+
let signal = this.shapeSignals.get(id);
|
|
1586
|
+
if (!signal) {
|
|
1587
|
+
signal = computed<ShapeRecord | undefined>(`editor.shape.${id}`, () =>
|
|
1588
|
+
this.getShape(id),
|
|
1589
|
+
);
|
|
1590
|
+
this.shapeSignals.set(id, signal);
|
|
1591
|
+
}
|
|
1592
|
+
return signal;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
getShapeSelectedSignal(id: ShapeId): Signal<boolean> {
|
|
1596
|
+
let signal = this.shapeSelectedSignals.get(id);
|
|
1597
|
+
if (!signal) {
|
|
1598
|
+
signal = computed<boolean>(`editor.shapeSelected.${id}`, () =>
|
|
1599
|
+
this.selectedShapeIdSetSignal.get().has(id),
|
|
1600
|
+
);
|
|
1601
|
+
this.shapeSelectedSignals.set(id, signal);
|
|
1602
|
+
}
|
|
1603
|
+
return signal;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
getShapeRenderOrderSignal(id: ShapeId): Signal<number> {
|
|
1607
|
+
let signal = this.shapeRenderOrderSignals.get(id);
|
|
1608
|
+
if (!signal) {
|
|
1609
|
+
signal = computed<number>(`editor.shapeRenderOrder.${id}`, () => {
|
|
1610
|
+
return this.shapeRenderOrderByIdSignal.get().get(id) ?? -1;
|
|
1611
|
+
});
|
|
1612
|
+
this.shapeRenderOrderSignals.set(id, signal);
|
|
1613
|
+
}
|
|
1614
|
+
return signal;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
getShapeRenderVisibleSignal(id: ShapeId): Signal<boolean> {
|
|
1618
|
+
let signal = this.shapeRenderVisibleSignals.get(id);
|
|
1619
|
+
if (!signal) {
|
|
1620
|
+
signal = computed<boolean>(`editor.shapeRenderVisible.${id}`, () => {
|
|
1621
|
+
return (
|
|
1622
|
+
this.renderVisibleShapeIdSetSignal.get().has(id) &&
|
|
1623
|
+
!this.isShapeHidden(id)
|
|
1624
|
+
);
|
|
1625
|
+
});
|
|
1626
|
+
this.shapeRenderVisibleSignals.set(id, signal);
|
|
1627
|
+
}
|
|
1628
|
+
return signal;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
getShapeDocumentVisibleSignal(id: ShapeId): Signal<boolean> {
|
|
1632
|
+
let signal = this.shapeDocumentVisibleSignals.get(id);
|
|
1633
|
+
if (!signal) {
|
|
1634
|
+
signal = computed<boolean>(`editor.shapeDocumentVisible.${id}`, () => {
|
|
1635
|
+
const shape = this.getShapeSignal(id).get();
|
|
1636
|
+
return !!shape && !this.isShapeHidden(shape.id);
|
|
1637
|
+
});
|
|
1638
|
+
this.shapeDocumentVisibleSignals.set(id, signal);
|
|
1639
|
+
}
|
|
1640
|
+
return signal;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
getShapeEditingSessionForShapeSignal(
|
|
1644
|
+
id: ShapeId,
|
|
1645
|
+
): Signal<ShapeEditingSession | null> {
|
|
1646
|
+
let signal = this.shapeEditingSessionSignals.get(id);
|
|
1647
|
+
if (!signal) {
|
|
1648
|
+
signal = computed<ShapeEditingSession | null>(
|
|
1649
|
+
`editor.shapeEditingSessionForShape.${id}`,
|
|
1650
|
+
() => {
|
|
1651
|
+
const session = this.shapeEditingSessionSignal.get();
|
|
1652
|
+
return session?.shapeId === id ? session : null;
|
|
1653
|
+
},
|
|
1654
|
+
);
|
|
1655
|
+
this.shapeEditingSessionSignals.set(id, signal);
|
|
1656
|
+
}
|
|
1657
|
+
return signal;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
getShapeDropTargetSignal(id: ShapeId): Signal<boolean> {
|
|
1661
|
+
let signal = this.shapeDropTargetSignals.get(id);
|
|
1662
|
+
if (!signal) {
|
|
1663
|
+
signal = computed<boolean>(`editor.shapeDropTarget.${id}`, () => {
|
|
1664
|
+
return this.dropTargetShapeIdSignal.get() === id;
|
|
1665
|
+
});
|
|
1666
|
+
this.shapeDropTargetSignals.set(id, signal);
|
|
1667
|
+
}
|
|
1668
|
+
return signal;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
getShapeErasingSignal(id: ShapeId): Signal<boolean> {
|
|
1672
|
+
let signal = this.shapeErasingSignals.get(id);
|
|
1673
|
+
if (!signal) {
|
|
1674
|
+
signal = computed<boolean>(`editor.shapeErasing.${id}`, () => {
|
|
1675
|
+
const erasingShapeIds = new Set(this.erasingShapeIdsSignal.get());
|
|
1676
|
+
return this.hierarchy.isShapeOrAncestorInSet(id, erasingShapeIds);
|
|
1677
|
+
});
|
|
1678
|
+
this.shapeErasingSignals.set(id, signal);
|
|
1679
|
+
}
|
|
1680
|
+
return signal;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
getShapeSelectedDescendantTransformAncestorSignal(
|
|
1684
|
+
id: ShapeId,
|
|
1685
|
+
): Signal<boolean> {
|
|
1686
|
+
let signal = this.shapeSelectedDescendantTransformAncestorSignals.get(id);
|
|
1687
|
+
if (!signal) {
|
|
1688
|
+
signal = computed<boolean>(
|
|
1689
|
+
`editor.shapeSelectedDescendantTransformAncestor.${id}`,
|
|
1690
|
+
() => {
|
|
1691
|
+
if (this.getShapeSelectedSignal(id).get()) {
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
const shape = this.getShapeSignal(id).get();
|
|
1695
|
+
if (
|
|
1696
|
+
!shape ||
|
|
1697
|
+
!this.getShapeUtil(shape).shouldTransformDescendants(shape)
|
|
1698
|
+
) {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
return this.selectedShapeIdsSignal
|
|
1702
|
+
.get()
|
|
1703
|
+
.some((selectedShapeId) =>
|
|
1704
|
+
this.hierarchy.isShapeDescendantOf(selectedShapeId, id),
|
|
1705
|
+
);
|
|
1706
|
+
},
|
|
1707
|
+
);
|
|
1708
|
+
this.shapeSelectedDescendantTransformAncestorSignals.set(id, signal);
|
|
1709
|
+
}
|
|
1710
|
+
return signal;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
getBinding(id: BindingId): BindingRecord | undefined {
|
|
1714
|
+
const record = this._internalStore.get(id);
|
|
1715
|
+
return isBindingRecord(record) ? record : undefined;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
getShapesForPage(pageId: PageId): ShapeRecord[] {
|
|
1719
|
+
return this.queries.getShapesForPage(pageId);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
getBindingsFromShape(shapeId: ShapeId): BindingRecord[] {
|
|
1723
|
+
return this.queries.getBindingsFromShape(shapeId);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
getBindingsToShape(shapeId: ShapeId): BindingRecord[] {
|
|
1727
|
+
return this.queries.getBindingsToShape(shapeId);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
getBindingsForShape(shapeId: ShapeId): BindingRecord[] {
|
|
1731
|
+
return this.queries.getBindingsForShape(shapeId);
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
getChildShapeIds(parentId: ShapeParentId): ShapeId[] {
|
|
1735
|
+
return this.queries.getChildShapeIds(parentId);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
getChildShapes(parentId: ShapeParentId): ShapeRecord[] {
|
|
1739
|
+
return this.queries.getChildShapes(parentId);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
getDescendantShapeIds(parentId: ShapeParentId): ShapeId[] {
|
|
1743
|
+
return this.queries.getDescendantShapeIds(parentId);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
getDescendantPageBounds(parentId: ShapeParentId): Box | null {
|
|
1747
|
+
return this.queries.getDescendantPageBounds(parentId);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
getShapeSubtreePageBounds(shapeId: ShapeId): Box | null {
|
|
1751
|
+
return this.queries.getShapeSubtreePageBounds(shapeId);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
getShapePageId(shapeId: ShapeId): PageId | null {
|
|
1755
|
+
return this.queries.getPageIdForShape(shapeId);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
isShapeOrAncestorLocked(shapeId: ShapeId): boolean {
|
|
1759
|
+
return this.hierarchy.isShapeOrAncestorLocked(shapeId);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
hasLockedDescendant(shapeId: ShapeId): boolean {
|
|
1763
|
+
return this.hierarchy.hasLockedDescendant(shapeId);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
getOutermostSelectableShapeId(shapeId: ShapeId): ShapeId {
|
|
1767
|
+
return this.hierarchy.getOutermostSelectableShapeId(shapeId);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
getShapeIdsIncludingDescendants(shapeIds: Iterable<ShapeId>): ShapeId[] {
|
|
1771
|
+
return this.hierarchy.getShapeIdsIncludingDescendants(shapeIds);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
getShapeIdsForSelectionTransform(shapeIds: Iterable<ShapeId>): ShapeId[] {
|
|
1775
|
+
return this.hierarchy.getShapeIdsForSelectionTransform(shapeIds);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
getShapeIdsForSelectionRotation(shapeIds: Iterable<ShapeId>): ShapeId[] {
|
|
1779
|
+
return this.hierarchy.getShapeIdsForSelectionRotation(shapeIds);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
getSelectionTransformTargetShapes(): ShapeRecord[] {
|
|
1783
|
+
return this.hierarchy.getSelectionTransformTargetShapes();
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
getSelectionRotationTargetShapes(): ShapeRecord[] {
|
|
1787
|
+
return this.hierarchy.getSelectionRotationTargetShapes();
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
getShapeExportTraversalIds(shapeIds: Iterable<ShapeId>): ShapeId[] {
|
|
1791
|
+
return this.hierarchy.getShapeExportTraversalIds(shapeIds);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
getShapeContainment(
|
|
1795
|
+
containerId: ShapeId,
|
|
1796
|
+
shapeId: ShapeId,
|
|
1797
|
+
): ShapeContainment | null {
|
|
1798
|
+
return this.queries.getShapeContainment(containerId, shapeId);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
getContainingShapeParentId(
|
|
1802
|
+
shapeIds: Iterable<ShapeId>,
|
|
1803
|
+
): ShapeParentId | null {
|
|
1804
|
+
return this.queries.getContainingShapeParentId(shapeIds);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
getContainingParentIdForShapeRecord(shape: ShapeRecord): ShapeParentId | null {
|
|
1808
|
+
return this.queries.getContainingParentIdForShapeRecord(shape);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
getShapeClipAncestorIds(shapeId: ShapeId): ShapeId[] {
|
|
1812
|
+
return this.queries.getShapeClipAncestorIds(shapeId);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
getShapeClipAncestorIdsSignal(shapeId: ShapeId): Signal<ShapeId[]> {
|
|
1816
|
+
return this.queries.getShapeClipAncestorIdsSignal(shapeId);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
isPagePointInShapeClip(shapeId: ShapeId, point: Vec): boolean {
|
|
1820
|
+
return this.queries.isPagePointInShapeClip(shapeId, point);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
getShapeClippedPageBounds(shapeId: ShapeId): Box | null {
|
|
1824
|
+
return this.queries.getShapeClippedPageBounds(shapeId);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
getShapeIdsInContainerBounds(
|
|
1828
|
+
containerId: ShapeId,
|
|
1829
|
+
options: ContainerBoundsQueryOptions = {},
|
|
1830
|
+
): ShapeId[] {
|
|
1831
|
+
return this.hierarchy.getShapeIdsInContainerBounds(containerId, options);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
canUseShapeParent(parentId: ShapeParentId, childId?: ShapeId): boolean {
|
|
1835
|
+
return this.hierarchy.canUseShapeParent(parentId, childId);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
getShapeUtil<S extends ShapeRecord>(shape: S): ShapeUtil<S> {
|
|
1839
|
+
return this.shapeUtils.get(shape.type);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
getBindingUtil<B extends BindingRecord>(binding: B): BindingUtil<B> {
|
|
1843
|
+
return this.bindingUtils.get(binding.type);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// Pure geometry helpers operate only on the provided shape snapshot.
|
|
1847
|
+
computeShapeBounds(shape: ShapeRecord): Box {
|
|
1848
|
+
return this.geometry.computeShapeBounds(shape);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
computeShapeLocalBounds(shape: ShapeRecord): Box {
|
|
1852
|
+
return this.geometry.computeShapeLocalBounds(shape);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
computeShapeRenderBounds(shape: ShapeRecord): Box {
|
|
1856
|
+
return this.geometry.computeShapeRenderBounds(shape);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
computeShapeExportBounds(shape: ShapeRecord): Box {
|
|
1860
|
+
return this.geometry.computeShapeExportBounds(shape);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
computeShapeLocalToPageTransform(shape: ShapeRecord): Mat {
|
|
1864
|
+
return this.geometry.computeShapeLocalToPageTransform(shape);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
computeShapeRenderToPageTransform(shape: ShapeRecord): Mat {
|
|
1868
|
+
return this.geometry.computeShapeRenderToPageTransform(shape);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
computeShapeExportToPageTransform(shape: ShapeRecord): Mat {
|
|
1872
|
+
return this.geometry.computeShapeExportToPageTransform(shape);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
computeShapePageBounds(shape: ShapeRecord): Box {
|
|
1876
|
+
return this.geometry.computeShapePageBounds(shape);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
computeShapeExportPageBounds(shape: ShapeRecord): Box {
|
|
1880
|
+
return this.geometry.computeShapeExportPageBounds(shape);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
computeShapeGeometry(shape: ShapeRecord): ShapeGeometry {
|
|
1884
|
+
return this.geometry.computeShapeGeometry(shape);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
computeShapePositionedToPageTransform(shape: ShapeRecord): Mat {
|
|
1888
|
+
return this.geometry.computeShapePositionedToPageTransform(shape);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
computeShapePageCenter(shape: ShapeRecord): Vec {
|
|
1892
|
+
return this.geometry.computeShapePageCenter(shape);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
computeShapePageCorners(shape: ShapeRecord): Vec[] {
|
|
1896
|
+
return this.geometry.computeShapePageCorners(shape);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
computeShapePageOutlineVertices(shape: ShapeRecord): Vec[] {
|
|
1900
|
+
return this.geometry.computeShapePageOutlineVertices(shape);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// Query helpers always read the current editor state for a shape id.
|
|
1904
|
+
getShapeBounds(shapeId: ShapeId): Box | null {
|
|
1905
|
+
return this.geometry.getShapeBounds(shapeId);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
getShapePageBounds(shapeId: ShapeId): Box | null {
|
|
1909
|
+
return this.geometry.getShapePageBounds(shapeId);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
getShapeGeometry(shapeId: ShapeId): ShapeGeometry | null {
|
|
1913
|
+
return this.geometry.getShapeGeometry(shapeId);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
getShapePositionedToPageTransform(shapeId: ShapeId): Mat | null {
|
|
1917
|
+
return this.geometry.getShapePositionedToPageTransform(shapeId);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
getShapePageCenter(shapeId: ShapeId): Vec | null {
|
|
1921
|
+
return this.geometry.getShapePageCenter(shapeId);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
getShapePageCorners(shapeId: ShapeId): Vec[] | null {
|
|
1925
|
+
return this.geometry.getShapePageCorners(shapeId);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
getShapePageOutlineVertices(shapeId: ShapeId): Vec[] {
|
|
1929
|
+
return this.geometry.getShapePageOutlineVertices(shapeId);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
getSelectionPageBounds(): Box | null {
|
|
1933
|
+
return this.geometry.getSelectionPageBounds();
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
getSelectionFrame():
|
|
1937
|
+
| SelectionFrame
|
|
1938
|
+
| null {
|
|
1939
|
+
return this.geometry.getSelectionFrame();
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
isPointInSelectionBounds(point: Vec): boolean {
|
|
1943
|
+
return this.geometry.isPointInSelectionBounds(point);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
getSelectionFrameLocalBoundsPoint(point: Vec): Vec | null {
|
|
1947
|
+
return this.geometry.getSelectionFrameLocalBoundsPoint(point);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
getSelectionPageOutlineVertices(): Vec[] {
|
|
1951
|
+
return this.geometry.getSelectionPageOutlineVertices();
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
getSelectionToolbarAnchorPagePoint(): Vec | null {
|
|
1955
|
+
return this.geometry.getSelectionToolbarAnchorPagePoint();
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
hitTestSelectionHandle(point: Vec): SelectionHandle | null {
|
|
1959
|
+
const selectedShapes = this.selectedShapesSignal.get();
|
|
1960
|
+
const selectionFrame = this.getSelectionFrame();
|
|
1961
|
+
if (selectedShapes.length === 0) {
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
const isOnlySelected = selectedShapes.length === 1;
|
|
1965
|
+
const {
|
|
1966
|
+
terminalHandles,
|
|
1967
|
+
middleHandles,
|
|
1968
|
+
customHandles,
|
|
1969
|
+
rotateHandles,
|
|
1970
|
+
resizeHandles,
|
|
1971
|
+
} =
|
|
1972
|
+
getSelectionHandleState(
|
|
1973
|
+
selectedShapes,
|
|
1974
|
+
selectionFrame,
|
|
1975
|
+
this.getCurrentCamera().z,
|
|
1976
|
+
(shape) =>
|
|
1977
|
+
this.getShapeUtil(shape).getSelectionHandleAnchors(shape, {
|
|
1978
|
+
isOnlySelected,
|
|
1979
|
+
editor: this,
|
|
1980
|
+
}),
|
|
1981
|
+
(shape) =>
|
|
1982
|
+
this.getShapeUtil(shape).canUseSelectionFrameHandles(shape, {
|
|
1983
|
+
editor: this,
|
|
1984
|
+
}),
|
|
1985
|
+
);
|
|
1986
|
+
|
|
1987
|
+
for (const handle of middleHandles) {
|
|
1988
|
+
if (
|
|
1989
|
+
handle.bounds
|
|
1990
|
+
.expand(
|
|
1991
|
+
SELECTION_HANDLE_HIT_PADDING_SCREEN / this.getCurrentCamera().z,
|
|
1992
|
+
)
|
|
1993
|
+
.containsPoint(point)
|
|
1994
|
+
) {
|
|
1995
|
+
return handle;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
for (const handle of terminalHandles) {
|
|
2000
|
+
if (
|
|
2001
|
+
handle.bounds
|
|
2002
|
+
.expand(
|
|
2003
|
+
SELECTION_HANDLE_HIT_PADDING_SCREEN / this.getCurrentCamera().z,
|
|
2004
|
+
)
|
|
2005
|
+
.containsPoint(point)
|
|
2006
|
+
) {
|
|
2007
|
+
return handle;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
for (const handle of customHandles) {
|
|
2012
|
+
if (
|
|
2013
|
+
handle.bounds
|
|
2014
|
+
.expand(SELECTION_HANDLE_HIT_PADDING_SCREEN / this.getCurrentCamera().z)
|
|
2015
|
+
.containsPoint(point)
|
|
2016
|
+
) {
|
|
2017
|
+
return handle;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
for (const handle of resizeHandles) {
|
|
2022
|
+
if (handle.bounds.containsPoint(point)) {
|
|
2023
|
+
return handle;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
for (const handle of rotateHandles) {
|
|
2028
|
+
if (
|
|
2029
|
+
handle.bounds
|
|
2030
|
+
.expand(SELECTION_HANDLE_HIT_PADDING_SCREEN / this.getCurrentCamera().z)
|
|
2031
|
+
.containsPoint(point)
|
|
2032
|
+
) {
|
|
2033
|
+
return handle;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
setBrushBox(brushBox: Box | null): void {
|
|
2041
|
+
this.transients.setBrushBox(brushBox);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
getErasingShapeIds(): ShapeId[] {
|
|
2045
|
+
return this.transients.getErasingShapeIds();
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
setErasingShapeIds(shapeIds: readonly ShapeId[]): void {
|
|
2049
|
+
this.transients.setErasingShapeIds(shapeIds);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
clearErasingShapeIds(): void {
|
|
2053
|
+
this.transients.clearErasingShapeIds();
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
getEraserTrail(): Vec[] {
|
|
2057
|
+
return this.transients.getEraserTrail();
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
setEraserTrail(points: readonly Vec[]): void {
|
|
2061
|
+
this.transients.setEraserTrail(points);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
clearEraserTrail(): void {
|
|
2065
|
+
this.transients.clearEraserTrail();
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
startLaserTrail(): number {
|
|
2069
|
+
return this.transients.startLaserTrail();
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
setLaserTrail(sessionId: number, points: readonly Vec[]): void {
|
|
2073
|
+
this.transients.setLaserTrail(sessionId, points);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
finishLaserTrail(sessionId: number): void {
|
|
2077
|
+
this.transients.finishLaserTrail(sessionId);
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
clearLaserTrail(sessionId: number): void {
|
|
2081
|
+
this.transients.clearLaserTrail(sessionId);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
setBindingPreview(preview: ArrowBindingPreviewState | null): void {
|
|
2085
|
+
this.transients.setBindingPreview(preview);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
setDropTargetShapeId(shapeId: ShapeId | null): void {
|
|
2089
|
+
this.transients.setDropTargetShapeId(shapeId);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
setSnapIndicators(indicators: SnapIndicatorState[] | null): void {
|
|
2093
|
+
this.transients.setSnapIndicators(indicators);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
setArrowTerminalBindingResolver(
|
|
2097
|
+
resolver: ArrowTerminalBindingResolver,
|
|
2098
|
+
): void {
|
|
2099
|
+
this.transients.setArrowTerminalBindingResolver(resolver);
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
resolveArrowTerminalBinding(
|
|
2103
|
+
input: ArrowTerminalBindingResolverInput,
|
|
2104
|
+
): ResolvedArrowTerminalBinding {
|
|
2105
|
+
return this.transients.resolveArrowTerminalBinding(input);
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
clearSelectionTransients(): void {
|
|
2109
|
+
this.transients.clearSelectionTransients();
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
hitTestShape(point: Vec): ShapeRecord | null {
|
|
2113
|
+
return this.hitTestShapesAtPoint(point)[0] ?? null;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
hitTestShapesAtPoint(
|
|
2117
|
+
point: Vec,
|
|
2118
|
+
options?: ShapeAtPointOptions,
|
|
2119
|
+
): ShapeRecord[] {
|
|
2120
|
+
return this.spatialIndex.getShapesAtPoint(point, {
|
|
2121
|
+
...options,
|
|
2122
|
+
filter: (shape) =>
|
|
2123
|
+
!this.isShapeHidden(shape.id) &&
|
|
2124
|
+
this.isPagePointInShapeClip(shape.id, point) &&
|
|
2125
|
+
(options?.filter?.(shape) ?? true),
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
hitTestShapesByLineSegment(
|
|
2130
|
+
start: Vec,
|
|
2131
|
+
end: Vec,
|
|
2132
|
+
options?: ShapeLineSegmentHitTestOptions,
|
|
2133
|
+
): ShapeRecord[] {
|
|
2134
|
+
return this.spatialIndex.getShapesHitByLineSegment(start, end, {
|
|
2135
|
+
...options,
|
|
2136
|
+
filter: (shape) =>
|
|
2137
|
+
!this.isShapeHidden(shape.id) && (options?.filter?.(shape) ?? true),
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
getShapeIdsInBounds(bounds: Box, options?: SpatialQueryOptions): ShapeId[] {
|
|
2142
|
+
return this.spatialIndex
|
|
2143
|
+
.getShapeIdsInBounds(bounds, options)
|
|
2144
|
+
.filter((shapeId) => !this.isShapeHidden(shapeId));
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
getShapesInBounds(bounds: Box, options?: SpatialQueryOptions): ShapeRecord[] {
|
|
2148
|
+
return this.spatialIndex
|
|
2149
|
+
.getShapesInBounds(bounds, options)
|
|
2150
|
+
.filter((shape) => !this.isShapeHidden(shape.id));
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
getShapesInViewport(
|
|
2154
|
+
bounds: Box,
|
|
2155
|
+
options?: SpatialQueryOptions,
|
|
2156
|
+
): ShapeRecord[] {
|
|
2157
|
+
return this.spatialIndex
|
|
2158
|
+
.getShapesInViewport(bounds, options)
|
|
2159
|
+
.filter((shape) => !this.isShapeHidden(shape.id));
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
getDocumentSnapshot(): DocumentSnapshot {
|
|
2163
|
+
return this.snapshots.getDocumentSnapshot();
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
getSessionSnapshot(): SessionSnapshot {
|
|
2167
|
+
return this.snapshots.getSessionSnapshot();
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
getSvgString(
|
|
2171
|
+
shapeIds: Iterable<ShapeId> = this.getSelectedShapeIds(),
|
|
2172
|
+
options?: SvgExportOptions,
|
|
2173
|
+
): SvgExportResult | null {
|
|
2174
|
+
return exportShapesToSvgString(
|
|
2175
|
+
this,
|
|
2176
|
+
shapeIds,
|
|
2177
|
+
this.shapeSvgExporters,
|
|
2178
|
+
options,
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
getExportIntegrityReport(
|
|
2183
|
+
shapeIds: Iterable<ShapeId> = this.getSelectedShapeIds(),
|
|
2184
|
+
): ExportIntegrityReport {
|
|
2185
|
+
return getExportIntegrityReport({
|
|
2186
|
+
editor: this,
|
|
2187
|
+
exporters: this.shapeSvgExporters,
|
|
2188
|
+
shapeIds,
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
repairExportIntegrity(
|
|
2193
|
+
shapeIds: Iterable<ShapeId> = this.getSelectedShapeIds(),
|
|
2194
|
+
): ExportIntegrityRepairResult {
|
|
2195
|
+
return repairExportIntegrity({
|
|
2196
|
+
editor: this,
|
|
2197
|
+
exporters: this.shapeSvgExporters,
|
|
2198
|
+
shapeIds,
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
getShapeCapabilityManifest(): ShapeCapabilityManifest {
|
|
2203
|
+
return getShapeCapabilityManifest({
|
|
2204
|
+
definitions: this.shapeCapabilityDefinitions,
|
|
2205
|
+
exporters: this.shapeSvgExporters,
|
|
2206
|
+
shapeUtils: this.shapeUtils,
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
createPageRecord(input?: {
|
|
2211
|
+
name?: string;
|
|
2212
|
+
index?: string;
|
|
2213
|
+
meta?: Record<string, unknown>;
|
|
2214
|
+
}): PageRecord {
|
|
2215
|
+
const pageNumber = this.nextPageNumber;
|
|
2216
|
+
return {
|
|
2217
|
+
id: this.createPageId(pageNumber),
|
|
2218
|
+
typeName: "page",
|
|
2219
|
+
scope: "document",
|
|
2220
|
+
name: input?.name ?? `Page ${pageNumber}`,
|
|
2221
|
+
index: input?.index ?? this.createPageIndex(pageNumber),
|
|
2222
|
+
meta: input?.meta ?? {},
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
loadDocumentSnapshot(
|
|
2227
|
+
snapshot: unknown,
|
|
2228
|
+
options?: { source?: ChangeSource },
|
|
2229
|
+
): void {
|
|
2230
|
+
this.snapshots.loadDocumentSnapshot(snapshot, options);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
loadSessionSnapshot(
|
|
2234
|
+
snapshot: unknown,
|
|
2235
|
+
options?: { source?: ChangeSource },
|
|
2236
|
+
): void {
|
|
2237
|
+
this.snapshots.loadSessionSnapshot(snapshot, options);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
syncAfterDocumentRestore(): void {
|
|
2241
|
+
this.snapshots.syncAfterDocumentRestore();
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
cancelInteractionForCommand(): void {
|
|
2245
|
+
this.cancelInteractionAndClearTransients("command");
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
cancelInteractionAndClearTransients(
|
|
2249
|
+
reason: CancelEditorEvent["reason"],
|
|
2250
|
+
): void {
|
|
2251
|
+
this.tools.cancel(reason);
|
|
2252
|
+
this.inputs.resetPointer();
|
|
2253
|
+
this.clearSelectionTransients();
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
syncToolStateFromSession(source: ChangeSource): void {
|
|
2257
|
+
const { activeToolId, isToolLocked } = this.sessionSignal.get();
|
|
2258
|
+
|
|
2259
|
+
if (this.tools.hasTool(activeToolId)) {
|
|
2260
|
+
this.tools.setActiveTool(activeToolId, "restore");
|
|
2261
|
+
} else {
|
|
2262
|
+
this.tools.setActiveTool("select", "restore");
|
|
2263
|
+
this.updateSession({ activeToolId: "select" }, source);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
this.tools.setToolLocked(isToolLocked);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
createShapeRecord(input: CreateShapeInput): ShapeRecord {
|
|
2270
|
+
const parentId = input.parentId ?? this.sessionSignal.get().currentPageId;
|
|
2271
|
+
if (!this.canUseShapeParent(parentId)) {
|
|
2272
|
+
throw new Error(`Invalid parent id for shape`);
|
|
2273
|
+
}
|
|
2274
|
+
const shapeNumber = this.nextShapeNumber;
|
|
2275
|
+
const id = this.createShapeId(shapeNumber);
|
|
2276
|
+
const util = this.shapeUtils.get(input.type);
|
|
2277
|
+
const props = util.validateProps({
|
|
2278
|
+
...this.getDefaultShapeProps(input.type),
|
|
2279
|
+
...input.props,
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
return {
|
|
2283
|
+
id,
|
|
2284
|
+
typeName: "shape" as const,
|
|
2285
|
+
scope: "document" as const,
|
|
2286
|
+
type: input.type,
|
|
2287
|
+
parentId,
|
|
2288
|
+
index: input.index ?? this.createShapeIndex(shapeNumber),
|
|
2289
|
+
x: input.x,
|
|
2290
|
+
y: input.y,
|
|
2291
|
+
rotation: input.rotation ?? 0,
|
|
2292
|
+
scaleX: input.scaleX ?? 1,
|
|
2293
|
+
scaleY: input.scaleY ?? 1,
|
|
2294
|
+
opacity: input.opacity ?? 1,
|
|
2295
|
+
isHidden: input.isHidden ?? false,
|
|
2296
|
+
isLocked: input.isLocked ?? false,
|
|
2297
|
+
props,
|
|
2298
|
+
meta: input.meta ?? {},
|
|
2299
|
+
} as ShapeRecord;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
createBindingRecord(input: CreateBindingInput): BindingRecord {
|
|
2303
|
+
const bindingNumber = this.nextBindingNumber;
|
|
2304
|
+
const id = input.id ?? this.createBindingId(bindingNumber);
|
|
2305
|
+
const util = this.bindingUtils.get(input.type);
|
|
2306
|
+
const props = util.validateProps({
|
|
2307
|
+
...util.getDefaultProps(),
|
|
2308
|
+
...input.props,
|
|
2309
|
+
});
|
|
2310
|
+
|
|
2311
|
+
return {
|
|
2312
|
+
id,
|
|
2313
|
+
typeName: "binding",
|
|
2314
|
+
scope: "document",
|
|
2315
|
+
type: input.type,
|
|
2316
|
+
fromId: input.fromId,
|
|
2317
|
+
toId: input.toId,
|
|
2318
|
+
props,
|
|
2319
|
+
meta: input.meta ?? {},
|
|
2320
|
+
} as BindingRecord;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
createAssetRecord(input: CreateAssetInput): AssetRecord {
|
|
2324
|
+
const assetNumber = this.nextAssetNumber;
|
|
2325
|
+
const id = input.id ?? this.createAssetId(assetNumber);
|
|
2326
|
+
|
|
2327
|
+
return {
|
|
2328
|
+
id,
|
|
2329
|
+
typeName: "asset",
|
|
2330
|
+
scope: "document",
|
|
2331
|
+
type: input.type,
|
|
2332
|
+
props: input.props ?? {},
|
|
2333
|
+
meta: input.meta ?? {},
|
|
2334
|
+
} as AssetRecord;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
updateSession(
|
|
2338
|
+
partial: Partial<SessionRecord>,
|
|
2339
|
+
source: ChangeSource = "system",
|
|
2340
|
+
): void {
|
|
2341
|
+
const current = this.sessionSignal.get();
|
|
2342
|
+
this._internalStore.put(
|
|
2343
|
+
[
|
|
2344
|
+
{
|
|
2345
|
+
...current,
|
|
2346
|
+
...partial,
|
|
2347
|
+
},
|
|
2348
|
+
],
|
|
2349
|
+
source,
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
setCurrentPage(pageId: PageId, source: ChangeSource = "system"): void {
|
|
2354
|
+
if (!this.getPage(pageId)) {
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
if (pageId !== this.getCurrentPageId()) {
|
|
2359
|
+
this.cancelInteractionAndClearTransients("page_switch");
|
|
2360
|
+
this.completeEditing(source);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
const session = this.sessionSignal.get();
|
|
2364
|
+
this.updateSession(
|
|
2365
|
+
{
|
|
2366
|
+
currentPageId: pageId,
|
|
2367
|
+
pageCameraById: {
|
|
2368
|
+
...session.pageCameraById,
|
|
2369
|
+
[pageId]: session.pageCameraById[pageId] ?? { ...DEFAULT_CAMERA },
|
|
2370
|
+
},
|
|
2371
|
+
pageSelectedShapeIdsById: {
|
|
2372
|
+
...session.pageSelectedShapeIdsById,
|
|
2373
|
+
[pageId]: session.pageSelectedShapeIdsById[pageId] ?? [],
|
|
2374
|
+
},
|
|
2375
|
+
},
|
|
2376
|
+
source,
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
setSelectedShapeIds(
|
|
2381
|
+
ids: ShapeId[],
|
|
2382
|
+
options?: { pageId?: PageId; source?: ChangeSource },
|
|
2383
|
+
): void {
|
|
2384
|
+
this.selection.setSelectedShapeIds(ids, options);
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
removeShapeIdsFromSelection(
|
|
2388
|
+
ids: ShapeId[],
|
|
2389
|
+
source: ChangeSource = "system",
|
|
2390
|
+
): void {
|
|
2391
|
+
this.selection.removeShapeIdsFromSelection(ids, source);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
removeShapeIdsFromEditingSession(
|
|
2395
|
+
ids: ShapeId[],
|
|
2396
|
+
source: ChangeSource = "system",
|
|
2397
|
+
): void {
|
|
2398
|
+
this.editing.removeShapeIds(ids, source);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
ensurePageSessionState(
|
|
2402
|
+
pageId: PageId,
|
|
2403
|
+
source: ChangeSource = "system",
|
|
2404
|
+
): void {
|
|
2405
|
+
const session = this.sessionSignal.get();
|
|
2406
|
+
const nextCamera = session.pageCameraById[pageId] ?? { ...DEFAULT_CAMERA };
|
|
2407
|
+
const nextSelection = session.pageSelectedShapeIdsById[pageId] ?? [];
|
|
2408
|
+
|
|
2409
|
+
this.updateSession(
|
|
2410
|
+
{
|
|
2411
|
+
pageCameraById: {
|
|
2412
|
+
...session.pageCameraById,
|
|
2413
|
+
[pageId]: nextCamera,
|
|
2414
|
+
},
|
|
2415
|
+
pageSelectedShapeIdsById: {
|
|
2416
|
+
...session.pageSelectedShapeIdsById,
|
|
2417
|
+
[pageId]: nextSelection,
|
|
2418
|
+
},
|
|
2419
|
+
},
|
|
2420
|
+
source,
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
removePageSessionState(
|
|
2425
|
+
pageId: PageId,
|
|
2426
|
+
nextCurrentPageId: PageId,
|
|
2427
|
+
source: ChangeSource = "system",
|
|
2428
|
+
): void {
|
|
2429
|
+
const session = this.sessionSignal.get();
|
|
2430
|
+
const nextCameraById = { ...session.pageCameraById };
|
|
2431
|
+
const nextSelectionById = { ...session.pageSelectedShapeIdsById };
|
|
2432
|
+
delete nextCameraById[pageId];
|
|
2433
|
+
delete nextSelectionById[pageId];
|
|
2434
|
+
|
|
2435
|
+
this.updateSession(
|
|
2436
|
+
{
|
|
2437
|
+
currentPageId: nextCurrentPageId,
|
|
2438
|
+
pageCameraById: nextCameraById,
|
|
2439
|
+
pageSelectedShapeIdsById: nextSelectionById,
|
|
2440
|
+
},
|
|
2441
|
+
source,
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
setCamera(
|
|
2446
|
+
partial: Partial<Camera>,
|
|
2447
|
+
options?: { pageId?: PageId; source?: ChangeSource },
|
|
2448
|
+
): void {
|
|
2449
|
+
this.camera.setCamera(partial, options);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
panCamera(delta: Vec, source: ChangeSource = "system"): void {
|
|
2453
|
+
this.camera.panCamera(delta, source);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
zoomCameraAtScreenPoint(
|
|
2457
|
+
screenPoint: Vec,
|
|
2458
|
+
deltaY: number,
|
|
2459
|
+
source: ChangeSource = "system",
|
|
2460
|
+
): void {
|
|
2461
|
+
this.camera.zoomCameraAtScreenPoint(screenPoint, deltaY, source);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
zoomCameraToScreenPoint(
|
|
2465
|
+
screenPoint: Vec,
|
|
2466
|
+
zoom: number,
|
|
2467
|
+
source: ChangeSource = "system",
|
|
2468
|
+
): void {
|
|
2469
|
+
this.camera.zoomCameraToScreenPoint(screenPoint, zoom, source);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
zoomAtViewportCenter(deltaY: number, source: ChangeSource = "system"): void {
|
|
2473
|
+
this.camera.zoomAtViewportCenter(deltaY, source);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
resetZoom(source: ChangeSource = "user"): void {
|
|
2477
|
+
this.camera.resetZoom(source);
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
setViewportScreenSize(size: { w: number; h: number }): void {
|
|
2481
|
+
this.camera.setViewportScreenSize(size);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/** Combined page bounds of every shape on a page, or null when empty. */
|
|
2485
|
+
getCurrentPageBounds(pageId: PageId = this.getCurrentPageId()): Box | null {
|
|
2486
|
+
return this.selectionLayout.getCurrentPageBounds(pageId);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
/**
|
|
2490
|
+
* Move and zoom the camera so `bounds` fits inside the viewport with optional
|
|
2491
|
+
* padding (screen px). Returns false when the viewport or bounds are empty.
|
|
2492
|
+
*/
|
|
2493
|
+
zoomToBounds(
|
|
2494
|
+
bounds: Box,
|
|
2495
|
+
options?: { padding?: number; maxZoom?: number; source?: ChangeSource },
|
|
2496
|
+
): boolean {
|
|
2497
|
+
return this.selectionLayout.zoomToBounds(bounds, options);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
/** Fit all shapes on the current page into the viewport. */
|
|
2501
|
+
zoomToFit(options?: { padding?: number; source?: ChangeSource }): boolean {
|
|
2502
|
+
return this.selectionLayout.zoomToFit(options);
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
/** Fit the current selection into the viewport. */
|
|
2506
|
+
zoomToSelection(options?: {
|
|
2507
|
+
padding?: number;
|
|
2508
|
+
source?: ChangeSource;
|
|
2509
|
+
}): boolean {
|
|
2510
|
+
return this.selectionLayout.zoomToSelection(options);
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
private createShapeId(shapeNumber: number): ShapeId {
|
|
2514
|
+
const id = `shape:${shapeNumber}` as ShapeId;
|
|
2515
|
+
this.nextShapeNumber += 1;
|
|
2516
|
+
return id;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
createShapeIndex(shapeNumber: number): string {
|
|
2520
|
+
return String(shapeNumber).padStart(6, "0");
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
private createBindingId(bindingNumber: number): BindingId {
|
|
2524
|
+
const id = `binding:${bindingNumber}` as BindingId;
|
|
2525
|
+
this.nextBindingNumber += 1;
|
|
2526
|
+
return id;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
private createAssetId(assetNumber: number): AssetId {
|
|
2530
|
+
const id = `asset:${assetNumber}` as AssetId;
|
|
2531
|
+
this.nextAssetNumber += 1;
|
|
2532
|
+
return id;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
syncShapeCounter(): void {
|
|
2536
|
+
let maxShapeNumber = 0;
|
|
2537
|
+
for (const record of this._internalStore.getAll()) {
|
|
2538
|
+
if (!isShapeRecord(record)) continue;
|
|
2539
|
+
const value = Number(record.id.split(":")[1] ?? 0);
|
|
2540
|
+
if (Number.isFinite(value)) {
|
|
2541
|
+
maxShapeNumber = Math.max(maxShapeNumber, value);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
this.nextShapeNumber = maxShapeNumber + 1;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
syncPageCounter(): void {
|
|
2548
|
+
let maxPageNumber = 0;
|
|
2549
|
+
for (const record of this._internalStore.getAll()) {
|
|
2550
|
+
if (!isPageRecord(record)) continue;
|
|
2551
|
+
const suffix = record.id.split(":")[1] ?? "";
|
|
2552
|
+
const value = Number(suffix === "default" ? 1 : suffix);
|
|
2553
|
+
if (Number.isFinite(value)) {
|
|
2554
|
+
maxPageNumber = Math.max(maxPageNumber, value);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
this.nextPageNumber = maxPageNumber + 1;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
syncBindingCounter(): void {
|
|
2561
|
+
let maxBindingNumber = 0;
|
|
2562
|
+
for (const record of this._internalStore.getAll()) {
|
|
2563
|
+
if (!isBindingRecord(record)) continue;
|
|
2564
|
+
const value = Number(record.id.split(":")[1] ?? 0);
|
|
2565
|
+
if (Number.isFinite(value)) {
|
|
2566
|
+
maxBindingNumber = Math.max(maxBindingNumber, value);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
this.nextBindingNumber = maxBindingNumber + 1;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
syncAssetCounter(): void {
|
|
2573
|
+
let maxAssetNumber = 0;
|
|
2574
|
+
for (const record of this._internalStore.getAll()) {
|
|
2575
|
+
if (!isAssetRecord(record)) continue;
|
|
2576
|
+
const value = Number(record.id.split(":")[1] ?? 0);
|
|
2577
|
+
if (Number.isFinite(value)) {
|
|
2578
|
+
maxAssetNumber = Math.max(maxAssetNumber, value);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
this.nextAssetNumber = maxAssetNumber + 1;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
private createPageId(pageNumber: number): PageId {
|
|
2585
|
+
const id = `page:${pageNumber}` as PageId;
|
|
2586
|
+
this.nextPageNumber += 1;
|
|
2587
|
+
return id;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
private createPageIndex(pageNumber: number): string {
|
|
2591
|
+
return String(pageNumber).padStart(4, "0");
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
private handleWheelEvent(
|
|
2595
|
+
event: Extract<EditorEvent, { type: "wheel" }>,
|
|
2596
|
+
): void {
|
|
2597
|
+
if (event.ctrlKey) {
|
|
2598
|
+
const current = this.getCurrentCamera();
|
|
2599
|
+
const deltaY = event.delta.y;
|
|
2600
|
+
const zoomDelta =
|
|
2601
|
+
(Math.abs(deltaY) > CAMERA_WHEEL_MAX_ZOOM_STEP
|
|
2602
|
+
? CAMERA_WHEEL_MAX_ZOOM_STEP * Math.sign(deltaY)
|
|
2603
|
+
: deltaY) / 100;
|
|
2604
|
+
this.zoomCameraToScreenPoint(
|
|
2605
|
+
event.screenPoint,
|
|
2606
|
+
current.z - zoomDelta * CAMERA_WHEEL_ZOOM_SPEED * current.z,
|
|
2607
|
+
);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
this.panCamera(event.delta);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
}
|