@weasel-js/core 1.11.0 → 1.11.1
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/CHANGELOG.md +427 -0
- package/README.md +1 -1
- package/dist/animation/index.js +3 -3
- package/dist/animation/rig/bindRig.js +1 -1
- package/dist/animation/rig/rigNode.js.map +1 -1
- package/dist/barrel/canvas.js +7 -3
- package/dist/barrel/interactions.js +5 -4
- package/dist/barrel/paths.js +3 -3
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +6 -11
- package/dist/barrel/scene.js +8 -5
- package/dist/barrel/viewport.js +4 -3
- package/dist/canvas/Canvas.js +3 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
- package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
- package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +45 -5
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js +1 -11
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
- package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
- package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
- package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +93 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
- package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
- package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +8 -8
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
- package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +13 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
- package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +5 -3
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -4
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +16 -14
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SurfaceCanvas.js +5 -22
- package/dist/canvas/SurfaceCanvas.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +9 -5
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/contextAttributes.js +14 -0
- package/dist/canvas/contextAttributes.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +9 -113
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/insert.js +4 -2
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +193 -0
- package/dist/canvas/deps/pathEditCommits.js.map +1 -0
- package/dist/canvas/deps/snap.js +12 -9
- package/dist/canvas/deps/snap.js.map +1 -1
- package/dist/canvas/deps/transformFollowers.js +20 -0
- package/dist/canvas/deps/transformFollowers.js.map +1 -0
- package/dist/canvas/derivedPath.js +20 -1
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/drawnBounds.js +29 -3
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/nodeShapes/derivedPainter.js +9 -1
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +3 -2
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +1 -1
- package/dist/canvas/overlayRoleStrokes.js +12 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/pathOverlay.js +27 -7
- package/dist/canvas/pathOverlay.js.map +1 -1
- package/dist/canvas/renderDebugSnapshot.js +1 -0
- package/dist/canvas/renderDebugSnapshot.js.map +1 -1
- package/dist/canvas/renderSceneToPixels.js +6 -5
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/useCanvasPaint.js +2 -1
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- package/dist/canvas/useInFlightBounds.js +52 -0
- package/dist/canvas/useInFlightBounds.js.map +1 -0
- package/dist/canvas/useViewHelpers.js +7 -9
- package/dist/canvas/useViewHelpers.js.map +1 -1
- package/dist/clipboard.d.ts +3 -2
- package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
- package/dist/clone.d.ts +4 -3
- package/dist/core/geometry/autoPoseDescriptor.js +1 -1
- package/dist/core/ops/setDependsOn.js +37 -0
- package/dist/core/ops/setDependsOn.js.map +1 -0
- package/dist/core/ops/writtenBy.js +53 -0
- package/dist/core/ops/writtenBy.js.map +1 -0
- package/dist/core/pages/fitViewToPage.js +12 -1
- package/dist/core/pages/fitViewToPage.js.map +1 -1
- package/dist/core/pages/pageEdits.js +164 -0
- package/dist/core/pages/pageEdits.js.map +1 -0
- package/dist/core/pages/pages.js +30 -1
- package/dist/core/pages/pages.js.map +1 -1
- package/dist/core/scene/NodeRouting.js +8 -3
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/arrivalWindow.js.map +1 -1
- package/dist/core/scene/carryDescendants.js +51 -0
- package/dist/core/scene/carryDescendants.js.map +1 -0
- package/dist/core/scene/docValues.js +57 -0
- package/dist/core/scene/docValues.js.map +1 -0
- package/dist/core/scene/editPass.js +111 -0
- package/dist/core/scene/editPass.js.map +1 -0
- package/dist/core/scene/effectivePose.js +7 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fieldEdits.js +4 -1
- package/dist/core/scene/fieldEdits.js.map +1 -1
- package/dist/core/scene/historyFacade.js +1 -0
- package/dist/core/scene/historyFacade.js.map +1 -1
- package/dist/core/scene/layoutPass.js +28 -3
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/recorder.js +28 -19
- package/dist/core/scene/recorder.js.map +1 -1
- package/dist/core/scene/scene.js +15 -9
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneChanges.js +13 -0
- package/dist/core/scene/sceneChanges.js.map +1 -0
- package/dist/core/scene/sceneEdits.js +8 -1
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/sceneMutators.js +1 -0
- package/dist/core/scene/sceneMutators.js.map +1 -1
- package/dist/core/scene/sceneRecording.js +20 -3
- package/dist/core/scene/sceneRecording.js.map +1 -1
- package/dist/core/scene/sceneSelection.js +4 -1
- package/dist/core/scene/sceneSelection.js.map +1 -1
- package/dist/core/scene/sceneState.js +11 -5
- package/dist/core/scene/sceneState.js.map +1 -1
- package/dist/core/scene/serialization.js +5 -3
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/unitScopes.js +19 -1
- package/dist/core/scene/unitScopes.js.map +1 -1
- package/dist/core/selection/usePublishSceneSelection.js +6 -12
- package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
- package/dist/core/shapeKinds.js +25 -11
- package/dist/core/shapeKinds.js.map +1 -1
- package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-BHuoMs1o.d.ts} +1 -1
- package/dist/debug/createDebugOverlayLayer.js +16 -111
- package/dist/debug/createDebugOverlayLayer.js.map +1 -1
- package/dist/debug/createDebugSink.js +2 -1
- package/dist/debug/createDebugSink.js.map +1 -1
- package/dist/debug/defaultTheme.js +4 -2
- package/dist/debug/defaultTheme.js.map +1 -1
- package/dist/debug/features.js +17 -0
- package/dist/debug/features.js.map +1 -0
- package/dist/debug/overlayPanels.js +117 -0
- package/dist/debug/overlayPanels.js.map +1 -0
- package/dist/debug/parseDebugFlags.js +4 -13
- package/dist/debug/parseDebugFlags.js.map +1 -1
- package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-BpZNOv7q.d.ts} +114 -892
- package/dist/devtools/handlers.js +112 -0
- package/dist/devtools/handlers.js.map +1 -0
- package/dist/devtools/history.js +71 -0
- package/dist/devtools/history.js.map +1 -0
- package/dist/devtools/hook.js +74 -0
- package/dist/devtools/hook.js.map +1 -0
- package/dist/devtools/label.js +25 -0
- package/dist/devtools/label.js.map +1 -0
- package/dist/devtools/notices.js +58 -0
- package/dist/devtools/notices.js.map +1 -0
- package/dist/devtools/protocol.js +7 -0
- package/dist/devtools/protocol.js.map +1 -0
- package/dist/devtools/registry.js +90 -0
- package/dist/devtools/registry.js.map +1 -0
- package/dist/devtools/root.js +80 -0
- package/dist/devtools/root.js.map +1 -0
- package/dist/devtools/serialize.js +86 -0
- package/dist/devtools/serialize.js.map +1 -0
- package/dist/devtools/session.js +111 -0
- package/dist/devtools/session.js.map +1 -0
- package/dist/devtools/transport.js +67 -0
- package/dist/devtools/transport.js.map +1 -0
- package/dist/devtools/useDevtoolsRoot.js +81 -0
- package/dist/devtools/useDevtoolsRoot.js.map +1 -0
- package/dist/devtools-protocol.d.ts +435 -0
- package/dist/devtools-protocol.js +7 -0
- package/dist/devtools.d.ts +8 -0
- package/dist/devtools.js +12 -0
- package/dist/devtools.js.map +1 -0
- package/dist/features/chrome-caps/useHoverTracking.js +16 -10
- package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
- package/dist/features/ingestion/contentHandlers.js +25 -2
- package/dist/features/ingestion/contentHandlers.js.map +1 -1
- package/dist/features/measure/measureCommands.js +7 -5
- package/dist/features/measure/measureCommands.js.map +1 -1
- package/dist/features/paths/index.js +2 -2
- package/dist/features/paths/pathEditExtension.js.map +1 -1
- package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
- package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
- package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
- package/dist/features/selection/selectionPose.js +4 -1
- package/dist/features/selection/selectionPose.js.map +1 -1
- package/dist/features/snap/gather.js +9 -1
- package/dist/features/snap/gather.js.map +1 -1
- package/dist/features/snap/lazySession.js +17 -0
- package/dist/features/snap/lazySession.js.map +1 -0
- package/dist/features/snap/marks.js.map +1 -1
- package/dist/features/snap/probes2d.js +27 -19
- package/dist/features/snap/probes2d.js.map +1 -1
- package/dist/features/snap/readoutLayer.js.map +1 -1
- package/dist/features/snap/roles.js +2 -1
- package/dist/features/snap/roles.js.map +1 -1
- package/dist/features/snap/rotationSource.js +25 -8
- package/dist/features/snap/rotationSource.js.map +1 -1
- package/dist/features/snap/sceneSession2d.js +6 -4
- package/dist/features/snap/sceneSession2d.js.map +1 -1
- package/dist/features/snap/session2d.js +37 -23
- package/dist/features/snap/session2d.js.map +1 -1
- package/dist/features/snap/spanSnap.js +5 -2
- package/dist/features/snap/spanSnap.js.map +1 -1
- package/dist/icons.table.js +1 -1
- package/dist/icons.table.js.map +1 -1
- package/dist/icons.table.json +0 -10
- package/dist/index.d.ts +701 -696
- package/dist/index.js +35 -29
- package/dist/interactions/actions/align/action.js +6 -4
- package/dist/interactions/actions/align/action.js.map +1 -1
- package/dist/interactions/actions/align/align.js.map +1 -1
- package/dist/interactions/actions/booleans/action.js +7 -1
- package/dist/interactions/actions/booleans/action.js.map +1 -1
- package/dist/interactions/actions/clone/action.js +1 -1
- package/dist/interactions/actions/clone/behaviors.js +4 -4
- package/dist/interactions/actions/clone/behaviors.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +1 -1
- package/dist/interactions/actions/defaults/delete.js +48 -39
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +1 -1
- package/dist/interactions/actions/defaults/group.js +2 -2
- package/dist/interactions/actions/defaults/ingest.js +7 -5
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +13 -38
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/distribute/action.js +6 -4
- package/dist/interactions/actions/distribute/action.js.map +1 -1
- package/dist/interactions/actions/drawnBoxes.js +2 -1
- package/dist/interactions/actions/drawnBoxes.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +3 -6
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js +41 -8
- package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +6 -4
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/gestureLifecycle.js +5 -2
- package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +1 -2
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/commit.js +1 -1
- package/dist/interactions/actions/insert/parent.js +11 -0
- package/dist/interactions/actions/insert/parent.js.map +1 -0
- package/dist/interactions/actions/insert/placeAction.js +7 -2
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +5 -1
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/action.js +8 -4
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
- package/dist/interactions/actions/move/capture.js +1 -1
- package/dist/interactions/actions/move/commit/index.js +0 -2
- package/dist/interactions/actions/move/commit/index.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
- package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
- package/dist/interactions/actions/move/commit/reparent.js +11 -5
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/frame.js +4 -10
- package/dist/interactions/actions/move/frame.js.map +1 -1
- package/dist/interactions/actions/move/landedDescendants.js +20 -17
- package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
- package/dist/interactions/actions/move/layout/container.js +2 -1
- package/dist/interactions/actions/move/layout/container.js.map +1 -1
- package/dist/interactions/actions/move/layout/dragged.js +1 -1
- package/dist/interactions/actions/move/layout/dropPass.js +18 -1
- package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +5 -5
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/session.js +14 -3
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/move/translate.js +8 -37
- package/dist/interactions/actions/move/translate.js.map +1 -1
- package/dist/interactions/actions/moveNodes.js +87 -0
- package/dist/interactions/actions/moveNodes.js.map +1 -0
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/actions/resize/action.js +83 -72
- package/dist/interactions/actions/resize/action.js.map +1 -1
- package/dist/interactions/actions/resize/commit.js +2 -1
- package/dist/interactions/actions/resize/commit.js.map +1 -1
- package/dist/interactions/actions/resize/session.js +2 -0
- package/dist/interactions/actions/resize/session.js.map +1 -1
- package/dist/interactions/actions/resolvePreviews.js +3 -3
- package/dist/interactions/actions/resolvePreviews.js.map +1 -1
- package/dist/interactions/actions/rotate/action.js +7 -4
- package/dist/interactions/actions/rotate/action.js.map +1 -1
- package/dist/interactions/actions/rotate/commit.js +2 -1
- package/dist/interactions/actions/rotate/commit.js.map +1 -1
- package/dist/interactions/actions/rotate/session.js +2 -0
- package/dist/interactions/actions/rotate/session.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +13 -2
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/testUtils.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +5 -6
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/interactions/actions/transformFinish.js +106 -0
- package/dist/interactions/actions/transformFinish.js.map +1 -0
- package/dist/kitSettingsSchema.js +0 -4
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/move.d.ts +5 -4
- package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
- package/dist/resize.d.ts +4 -3
- package/dist/routing.d.ts +1 -1
- package/dist/routing.js +2 -2
- package/dist/test-hook/createTestHook.js +1 -1
- package/dist/test-hook/createTestHook.js.map +1 -1
- package/dist/test-hook/useTestHook.js +2 -1
- package/dist/test-hook/useTestHook.js.map +1 -1
- package/dist/test-seams.d.ts +9 -5
- package/dist/tools/builtin/pen/penHandle.js +3 -5
- package/dist/tools/builtin/pen/penHandle.js.map +1 -1
- package/dist/tools/index.js +26 -0
- package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
- package/dist/types-BN9hbdMI.d.ts +184 -0
- package/dist/types-CniVctmk.d.ts +949 -0
- package/dist/types-oFeQ5VjV.d.ts +264 -0
- package/dist/version.js +1 -1
- package/package.json +35 -25
- package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
- package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
- package/dist/tools/builtin/bone/boneActions.js +0 -156
- package/dist/tools/builtin/bone/boneActions.js.map +0 -1
- package/dist/tools/builtin/bone/boneOverlay.js +0 -71
- package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
- package/dist/tools/builtin/bone/placedRig.js +0 -28
- package/dist/tools/builtin/bone/placedRig.js.map +0 -1
- package/dist/tools/builtin/bone/rigEditState.js +0 -30
- package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
- package/dist/tools/builtin/bone/useBoneTool.js +0 -125
- package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
- package/dist/tools/builtin/weight/useWeightTool.js +0 -101
- package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
- package/dist/tools/builtin/weight/weightActions.js +0 -140
- package/dist/tools/builtin/weight/weightActions.js.map +0 -1
- package/dist/tools/builtin/weight/weightOverlay.js +0 -78
- package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fitViewToBounds } from "../viewport/fitViewToBounds.js";
|
|
2
|
+
import { pageOfBounds } from "./pages.js";
|
|
2
3
|
|
|
3
4
|
//#region src/core/pages/fitViewToPage.ts
|
|
4
5
|
/** The view that shows one placed page whole: `fitViewToBounds` on its rect. */
|
|
@@ -11,7 +12,17 @@ function fitViewToPage(page, viewportDims, currentView, options) {
|
|
|
11
12
|
height
|
|
12
13
|
}, viewportDims, currentView, options);
|
|
13
14
|
}
|
|
15
|
+
/** The page the view shows most of: the one overlapping the visible world rect most, or `null` when none shows. */
|
|
16
|
+
function pageInView(pages, view, viewportDims) {
|
|
17
|
+
const visible = {
|
|
18
|
+
x: view.x,
|
|
19
|
+
y: view.y,
|
|
20
|
+
width: viewportDims.width / view.scale.x,
|
|
21
|
+
height: viewportDims.height / view.scale.y
|
|
22
|
+
};
|
|
23
|
+
return pageOfBounds(pages, visible);
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
//#endregion
|
|
16
|
-
export { fitViewToPage };
|
|
27
|
+
export { fitViewToPage, pageInView };
|
|
17
28
|
//# sourceMappingURL=fitViewToPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fitViewToPage.js","names":[],"sources":["../../../src/core/pages/fitViewToPage.ts"],"sourcesContent":["import {\n fitViewToBounds,\n type FitViewToBoundsOptions,\n type ViewportDims,\n} from '../viewport/fitViewToBounds';\nimport type { View } from '../viewport/view';\nimport type
|
|
1
|
+
{"version":3,"file":"fitViewToPage.js","names":[],"sources":["../../../src/core/pages/fitViewToPage.ts"],"sourcesContent":["import {\n fitViewToBounds,\n type FitViewToBoundsOptions,\n type ViewportDims,\n} from '../viewport/fitViewToBounds';\nimport type { View } from '../viewport/view';\nimport { pageOfBounds, type PlacedPage } from './pages';\n\n/** The view that shows one placed page whole: `fitViewToBounds` on its rect. */\nexport function fitViewToPage(\n page: PlacedPage,\n viewportDims: ViewportDims,\n currentView: View,\n options?: FitViewToBoundsOptions,\n): View {\n const { x, y, width, height } = page;\n return fitViewToBounds({ x, y, width, height }, viewportDims, currentView, options);\n}\n\n/** The page the view shows most of: the one overlapping the visible world rect most, or `null` when none shows. */\nexport function pageInView(\n pages: readonly PlacedPage[],\n view: View,\n viewportDims: ViewportDims,\n): PlacedPage | null {\n const visible = { x: view.x, y: view.y, width: viewportDims.width / view.scale.x, height: viewportDims.height / view.scale.y };\n return pageOfBounds(pages, visible);\n}\n"],"mappings":";;;;;AASA,SAAgB,cACd,MACA,cACA,aACA,SACM;CACN,MAAM,EAAE,GAAG,GAAG,OAAO,WAAW;CAChC,OAAO,gBAAgB;EAAE;EAAG;EAAG;EAAO;CAAO,GAAG,cAAc,aAAa,OAAO;AACpF;;AAGA,SAAgB,WACd,OACA,MACA,cACmB;CACnB,MAAM,UAAU;EAAE,GAAG,KAAK;EAAG,GAAG,KAAK;EAAG,OAAO,aAAa,QAAQ,KAAK,MAAM;EAAG,QAAQ,aAAa,SAAS,KAAK,MAAM;CAAE;CAC7H,OAAO,aAAa,OAAO,OAAO;AACpC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { effectivePose } from "../scene/effectivePose.js";
|
|
2
|
+
import { layoutPages, pageOfBounds } from "./pages.js";
|
|
3
|
+
import { RECT_POSE_DESCRIPTOR, poseDescriptorForNode, translatePoseViaDescriptor } from "../geometry/poseDescriptor.js";
|
|
4
|
+
import { carriedDescendants, sceneCarrySource } from "../scene/carryDescendants.js";
|
|
5
|
+
|
|
6
|
+
//#region src/core/pages/pageEdits.ts
|
|
7
|
+
/**
|
|
8
|
+
* A document's pages as a scene document value, and the edits that change
|
|
9
|
+
* them. Each edit moves the nodes on a page by however far the page itself
|
|
10
|
+
* moved, so what is on a page stays where it is on that page.
|
|
11
|
+
*/
|
|
12
|
+
/** The scene document value key a page list lives under (`scene.doc`). */
|
|
13
|
+
var PAGES_DOC_KEY = "core:pages";
|
|
14
|
+
var asAny = (scene) => scene;
|
|
15
|
+
/** The scene's pages doc, or `null` when it has none. */
|
|
16
|
+
function pagesOf(scene) {
|
|
17
|
+
return scene.doc.get(PAGES_DOC_KEY) ?? null;
|
|
18
|
+
}
|
|
19
|
+
/** The scene's pages, placed in world space; empty when it has no pages doc. */
|
|
20
|
+
function placedPagesOf(scene) {
|
|
21
|
+
const doc = pagesOf(scene);
|
|
22
|
+
return doc ? layoutPages(doc.pages, doc.layout) : [];
|
|
23
|
+
}
|
|
24
|
+
/** An id none of `pages` has: `page-1`, `page-2`, and so on. */
|
|
25
|
+
function nextPageId(pages) {
|
|
26
|
+
const taken = new Set(pages.map((p) => p.id));
|
|
27
|
+
let n = 1;
|
|
28
|
+
while (taken.has(`page-${n}`)) n++;
|
|
29
|
+
return `page-${n}`;
|
|
30
|
+
}
|
|
31
|
+
/** Whether `id` draws from other nodes and goes wherever they go. */
|
|
32
|
+
function followsOthers(scene, id) {
|
|
33
|
+
const deps = scene.get(id)?.dependsOn;
|
|
34
|
+
return Array.isArray(deps) && deps.length > 0;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The root nodes on each placed page, by page id: each root belongs to the
|
|
38
|
+
* page its bounds overlap most (`pageOfBounds`). A root in the gaps belongs to
|
|
39
|
+
* none, and one that derives from other nodes is left out, since it follows
|
|
40
|
+
* them.
|
|
41
|
+
*/
|
|
42
|
+
function pageMembers(scene, placed, descriptor = RECT_POSE_DESCRIPTOR) {
|
|
43
|
+
const s = asAny(scene);
|
|
44
|
+
const out = new Map(placed.map((p) => [p.id, []]));
|
|
45
|
+
for (const id of s.roots) {
|
|
46
|
+
const node = s.get(id);
|
|
47
|
+
if (!node || followsOthers(s, id)) continue;
|
|
48
|
+
const bounds = poseDescriptorForNode(descriptor, node).getBounds(effectivePose(s, node));
|
|
49
|
+
const page = pageOfBounds(placed, bounds);
|
|
50
|
+
if (page) out.get(page.id).push(id);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** Whether a node's pose derives from its children, so moving them moves it. */
|
|
55
|
+
function derivesPose(scene, id) {
|
|
56
|
+
const node = scene.get(id);
|
|
57
|
+
return node?.dependsOn === "children" && node.derivePose !== void 0;
|
|
58
|
+
}
|
|
59
|
+
/** The stored poses that move root `id` and what it carries by `dx`, `dy`: the
|
|
60
|
+
* root, unless its pose derives from its children (then each child moves in
|
|
61
|
+
* its place), and the descendants it carries, less whatever derives its pose. */
|
|
62
|
+
function pageMoveWrites(scene, id, dx, dy, descriptor, out) {
|
|
63
|
+
const node = scene.get(id);
|
|
64
|
+
if (!node || followsOthers(scene, id)) return;
|
|
65
|
+
if (derivesPose(scene, id)) {
|
|
66
|
+
for (const child of scene.childrenOf(id)) pageMoveWrites(scene, child, dx, dy, descriptor, out);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const move = (pose, at) => translatePoseViaDescriptor(pose, dx, dy, poseDescriptorForNode(descriptor, scene.get(at)));
|
|
70
|
+
out.push({
|
|
71
|
+
id,
|
|
72
|
+
pose: move(node.pose, id)
|
|
73
|
+
});
|
|
74
|
+
const carried = carriedDescendants(sceneCarrySource(scene), id, (world, at) => followsOthers(scene, at) || derivesPose(scene, at) ? null : move(world, at));
|
|
75
|
+
for (const [at, pose] of carried) out.push({
|
|
76
|
+
id: at,
|
|
77
|
+
pose
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Replace the scene's pages doc with `next`, moving each page's members by
|
|
82
|
+
* the change in their page's place, all as one undo entry labeled `label`.
|
|
83
|
+
* Members of a page `next` drops are left where they are; `removePage` deletes
|
|
84
|
+
* them first.
|
|
85
|
+
*/
|
|
86
|
+
function setPages(scene, next, label, options = {}) {
|
|
87
|
+
const s = asAny(scene);
|
|
88
|
+
const descriptor = options.descriptor ?? RECT_POSE_DESCRIPTOR;
|
|
89
|
+
const before = placedPagesOf(s);
|
|
90
|
+
const after = new Map(layoutPages(next.pages, next.layout).map((p) => [p.id, p]));
|
|
91
|
+
const members = pageMembers(s, before, descriptor);
|
|
92
|
+
const writes = [];
|
|
93
|
+
for (const page of before) {
|
|
94
|
+
const to = after.get(page.id);
|
|
95
|
+
if (!to) continue;
|
|
96
|
+
const dx = to.x - page.x;
|
|
97
|
+
const dy = to.y - page.y;
|
|
98
|
+
if (dx === 0 && dy === 0) continue;
|
|
99
|
+
for (const root of members.get(page.id) ?? []) pageMoveWrites(s, root, dx, dy, descriptor, writes);
|
|
100
|
+
}
|
|
101
|
+
s.batch(label, () => {
|
|
102
|
+
for (const { id, pose } of writes) s.setPose(id, pose);
|
|
103
|
+
s.doc.set(PAGES_DOC_KEY, next);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function edit(scene, fallback, options, change) {
|
|
107
|
+
const doc = pagesOf(scene) ?? { pages: [] };
|
|
108
|
+
setPages(scene, {
|
|
109
|
+
...doc,
|
|
110
|
+
pages: change([...doc.pages])
|
|
111
|
+
}, options.label ?? fallback, options);
|
|
112
|
+
}
|
|
113
|
+
/** Insert `page` at `index`, by default after the last page. */
|
|
114
|
+
function addPage(scene, page, options = {}) {
|
|
115
|
+
edit(scene, "Add page", options, (pages) => {
|
|
116
|
+
pages.splice(options.index ?? pages.length, 0, page);
|
|
117
|
+
return pages;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Delete page `id`, what is on it, and every node that derives from that, as one entry. */
|
|
121
|
+
function removePage(scene, id, options = {}) {
|
|
122
|
+
const label = options.label ?? "Delete page";
|
|
123
|
+
const members = pageMembers(scene, placedPagesOf(scene), options.descriptor).get(id) ?? [];
|
|
124
|
+
scene.batch(label, () => {
|
|
125
|
+
if (members.length > 0) scene.removeMany(members);
|
|
126
|
+
edit(scene, label, {
|
|
127
|
+
...options,
|
|
128
|
+
label
|
|
129
|
+
}, (pages) => pages.filter((p) => p.id !== id));
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Move page `id` to `index` in the page order. */
|
|
133
|
+
function movePage(scene, id, index, options = {}) {
|
|
134
|
+
edit(scene, "Move page", options, (pages) => {
|
|
135
|
+
const at = pages.findIndex((p) => p.id === id);
|
|
136
|
+
if (at < 0) return pages;
|
|
137
|
+
const [page] = pages.splice(at, 1);
|
|
138
|
+
pages.splice(Math.max(0, Math.min(index, pages.length)), 0, page);
|
|
139
|
+
return pages;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/** Give page `id` a new size. Its members keep their places on it; the pages after it shift. */
|
|
143
|
+
function resizePage(scene, id, size, options = {}) {
|
|
144
|
+
edit(scene, "Resize page", options, (pages) => pages.map((p) => p.id === id ? {
|
|
145
|
+
...p,
|
|
146
|
+
width: size.width,
|
|
147
|
+
height: size.height
|
|
148
|
+
} : p));
|
|
149
|
+
}
|
|
150
|
+
/** Rename page `id`; an empty name removes it. */
|
|
151
|
+
function renamePage(scene, id, name, options = {}) {
|
|
152
|
+
edit(scene, "Rename page", options, (pages) => pages.map((p) => {
|
|
153
|
+
if (p.id !== id) return p;
|
|
154
|
+
const { name: _old, ...rest } = p;
|
|
155
|
+
return name === "" ? rest : {
|
|
156
|
+
...rest,
|
|
157
|
+
name
|
|
158
|
+
};
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
export { PAGES_DOC_KEY, addPage, movePage, nextPageId, pageMembers, pagesOf, placedPagesOf, removePage, renamePage, resizePage, setPages };
|
|
164
|
+
//# sourceMappingURL=pageEdits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageEdits.js","names":[],"sources":["../../../src/core/pages/pageEdits.ts"],"sourcesContent":["/**\n * A document's pages as a scene document value, and the edits that change\n * them. Each edit moves the nodes on a page by however far the page itself\n * moved, so what is on a page stays where it is on that page.\n */\nimport {\n RECT_POSE_DESCRIPTOR, poseDescriptorForNode, translatePoseViaDescriptor, type PoseDescriptor,\n} from '../geometry/poseDescriptor';\nimport { carriedDescendants, sceneCarrySource } from '../scene/carryDescendants';\nimport { effectivePose } from '../scene/effectivePose';\nimport type { NodeId, Scene } from '../scene/types';\nimport { layoutPages, pageOfBounds, type Page, type PageLayoutOptions, type PlacedPage } from './pages';\n\n/** The document value under {@link PAGES_DOC_KEY}: the pages in order, and how they are laid out. */\nexport interface PagesDoc {\n readonly pages: readonly Page[];\n readonly layout?: PageLayoutOptions;\n}\n\n/** The scene document value key a page list lives under (`scene.doc`). */\nexport const PAGES_DOC_KEY = 'core:pages';\n\n/** Options every page edit takes. */\nexport interface PageEditOptions {\n /** The undo entry's label. */\n label?: string;\n /** How node poses are measured and moved. Default: poses are rects. */\n descriptor?: PoseDescriptor<unknown>;\n}\n\ntype AnyScene = Scene<unknown, string, unknown>;\nconst asAny = <TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>) =>\n scene as unknown as AnyScene;\n\n/** The scene's pages doc, or `null` when it has none. */\nexport function pagesOf<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>): PagesDoc | null {\n return scene.doc.get<PagesDoc>(PAGES_DOC_KEY) ?? null;\n}\n\n/** The scene's pages, placed in world space; empty when it has no pages doc. */\nexport function placedPagesOf<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>): PlacedPage[] {\n const doc = pagesOf(scene);\n return doc ? layoutPages(doc.pages, doc.layout) : [];\n}\n\n/** An id none of `pages` has: `page-1`, `page-2`, and so on. */\nexport function nextPageId(pages: readonly Page[]): string {\n const taken = new Set(pages.map((p) => p.id));\n let n = 1;\n while (taken.has(`page-${n}`)) n++;\n return `page-${n}`;\n}\n\n/** Whether `id` draws from other nodes and goes wherever they go. */\nfunction followsOthers(scene: AnyScene, id: NodeId): boolean {\n const deps = scene.get(id)?.dependsOn;\n return Array.isArray(deps) && deps.length > 0;\n}\n\n/**\n * The root nodes on each placed page, by page id: each root belongs to the\n * page its bounds overlap most (`pageOfBounds`). A root in the gaps belongs to\n * none, and one that derives from other nodes is left out, since it follows\n * them.\n */\nexport function pageMembers<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n placed: readonly PlacedPage[],\n descriptor: PoseDescriptor<unknown> = RECT_POSE_DESCRIPTOR as PoseDescriptor<unknown>,\n): Map<string, NodeId[]> {\n const s = asAny(scene);\n const out = new Map<string, NodeId[]>(placed.map((p) => [p.id, []]));\n for (const id of s.roots) {\n const node = s.get(id);\n if (!node || followsOthers(s, id)) continue;\n const bounds = poseDescriptorForNode(descriptor, node).getBounds(effectivePose(s, node));\n const page = pageOfBounds(placed, bounds);\n if (page) out.get(page.id)!.push(id);\n }\n return out;\n}\n\n/** Whether a node's pose derives from its children, so moving them moves it. */\nfunction derivesPose(scene: AnyScene, id: NodeId): boolean {\n const node = scene.get(id);\n return node?.dependsOn === 'children' && node.derivePose !== undefined;\n}\n\n/** The stored poses that move root `id` and what it carries by `dx`, `dy`: the\n * root, unless its pose derives from its children (then each child moves in\n * its place), and the descendants it carries, less whatever derives its pose. */\nfunction pageMoveWrites(\n scene: AnyScene,\n id: NodeId,\n dx: number,\n dy: number,\n descriptor: PoseDescriptor<unknown>,\n out: { id: NodeId; pose: unknown }[],\n): void {\n const node = scene.get(id);\n if (!node || followsOthers(scene, id)) return;\n if (derivesPose(scene, id)) {\n for (const child of scene.childrenOf(id)) pageMoveWrites(scene, child, dx, dy, descriptor, out);\n return;\n }\n const move = (pose: unknown, at: NodeId) => translatePoseViaDescriptor(pose, dx, dy, poseDescriptorForNode(descriptor, scene.get(at)!));\n out.push({ id, pose: move(node.pose, id) });\n const carried = carriedDescendants(sceneCarrySource(scene), id, (world, at) => (\n followsOthers(scene, at) || derivesPose(scene, at) ? null : move(world, at)\n ));\n for (const [at, pose] of carried) out.push({ id: at, pose });\n}\n\n/**\n * Replace the scene's pages doc with `next`, moving each page's members by\n * the change in their page's place, all as one undo entry labeled `label`.\n * Members of a page `next` drops are left where they are; `removePage` deletes\n * them first.\n */\nexport function setPages<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n next: PagesDoc,\n label: string,\n options: Pick<PageEditOptions, 'descriptor'> = {},\n): void {\n const s = asAny(scene);\n const descriptor = options.descriptor ?? (RECT_POSE_DESCRIPTOR as PoseDescriptor<unknown>);\n const before = placedPagesOf(s);\n const after = new Map(layoutPages(next.pages, next.layout).map((p) => [p.id, p]));\n const members = pageMembers(s, before, descriptor);\n const writes: { id: NodeId; pose: unknown }[] = [];\n for (const page of before) {\n const to = after.get(page.id);\n if (!to) continue;\n const dx = to.x - page.x;\n const dy = to.y - page.y;\n if (dx === 0 && dy === 0) continue;\n for (const root of members.get(page.id) ?? []) pageMoveWrites(s, root, dx, dy, descriptor, writes);\n }\n s.batch(label, () => {\n for (const { id, pose } of writes) s.setPose(id, pose);\n s.doc.set(PAGES_DOC_KEY, next);\n });\n}\n\nfunction edit<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n fallback: string,\n options: PageEditOptions,\n change: (pages: Page[]) => Page[],\n): void {\n const doc = pagesOf(scene) ?? { pages: [] };\n setPages(scene, { ...doc, pages: change([...doc.pages]) }, options.label ?? fallback, options);\n}\n\n/** Insert `page` at `index`, by default after the last page. */\nexport function addPage<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n page: Page,\n options: PageEditOptions & { index?: number } = {},\n): void {\n edit(scene, 'Add page', options, (pages) => {\n pages.splice(options.index ?? pages.length, 0, page);\n return pages;\n });\n}\n\n/** Delete page `id`, what is on it, and every node that derives from that, as one entry. */\nexport function removePage<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: string,\n options: PageEditOptions = {},\n): void {\n const label = options.label ?? 'Delete page';\n const members = pageMembers(scene, placedPagesOf(scene), options.descriptor).get(id) ?? [];\n scene.batch(label, () => {\n if (members.length > 0) scene.removeMany(members);\n edit(scene, label, { ...options, label }, (pages) => pages.filter((p) => p.id !== id));\n });\n}\n\n/** Move page `id` to `index` in the page order. */\nexport function movePage<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: string,\n index: number,\n options: PageEditOptions = {},\n): void {\n edit(scene, 'Move page', options, (pages) => {\n const at = pages.findIndex((p) => p.id === id);\n if (at < 0) return pages;\n const [page] = pages.splice(at, 1);\n pages.splice(Math.max(0, Math.min(index, pages.length)), 0, page!);\n return pages;\n });\n}\n\n/** Give page `id` a new size. Its members keep their places on it; the pages after it shift. */\nexport function resizePage<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: string,\n size: { readonly width: number; readonly height: number },\n options: PageEditOptions = {},\n): void {\n edit(scene, 'Resize page', options, (pages) =>\n pages.map((p) => (p.id === id ? { ...p, width: size.width, height: size.height } : p)));\n}\n\n/** Rename page `id`; an empty name removes it. */\nexport function renamePage<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: string,\n name: string,\n options: PageEditOptions = {},\n): void {\n edit(scene, 'Rename page', options, (pages) => pages.map((p) => {\n if (p.id !== id) return p;\n const { name: _old, ...rest } = p;\n return name === '' ? rest : { ...rest, name };\n }));\n}\n"],"mappings":"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"}
|
package/dist/core/pages/pages.js
CHANGED
|
@@ -33,6 +33,10 @@ function layoutPages(pages, options = {}) {
|
|
|
33
33
|
return placed;
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
+
/** The name page `index` of the page order shows: its own, else "Page" and its place. */
|
|
37
|
+
function pageName(page, index) {
|
|
38
|
+
return page.name ?? `Page ${index + 1}`;
|
|
39
|
+
}
|
|
36
40
|
/** The page under a world point, or `null` between and outside them. A point on an edge is on the page. */
|
|
37
41
|
function pageAt(pages, point) {
|
|
38
42
|
return pageOfBounds(pages, {
|
|
@@ -64,6 +68,31 @@ function pageOfBounds(pages, bounds) {
|
|
|
64
68
|
}
|
|
65
69
|
return best;
|
|
66
70
|
}
|
|
71
|
+
function gap(page, b) {
|
|
72
|
+
const dx = Math.max(page.x - (b.x + b.width), b.x - (page.x + page.width), 0);
|
|
73
|
+
const dy = Math.max(page.y - (b.y + b.height), b.y - (page.y + page.height), 0);
|
|
74
|
+
return Math.hypot(dx, dy);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The page a world-space box is held by: the one it is on (`pageOfBounds`),
|
|
78
|
+
* else the one nearest it by the box-to-box gap, the earlier of two equally
|
|
79
|
+
* near. `null` only with no pages. Used where every object must land on some
|
|
80
|
+
* page, such as an export; `pageOfBounds` stays the rule for what is on one.
|
|
81
|
+
*/
|
|
82
|
+
function pageHolding(pages, bounds) {
|
|
83
|
+
const on = pageOfBounds(pages, bounds);
|
|
84
|
+
if (on !== null) return on;
|
|
85
|
+
let best = null;
|
|
86
|
+
let least = Infinity;
|
|
87
|
+
for (const page of pages) {
|
|
88
|
+
const d = gap(page, bounds);
|
|
89
|
+
if (d < least) {
|
|
90
|
+
best = page;
|
|
91
|
+
least = d;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return best;
|
|
95
|
+
}
|
|
67
96
|
/**
|
|
68
97
|
* The page `step` places along the page order from the one with id `current`:
|
|
69
98
|
* the next slide is `1`, the one before is `-1`. `null` past either end, unless
|
|
@@ -92,5 +121,5 @@ function pagesBounds(pages) {
|
|
|
92
121
|
}
|
|
93
122
|
|
|
94
123
|
//#endregion
|
|
95
|
-
export { adjacentPage, layoutPages, pageAt, pageOfBounds, pagesBounds };
|
|
124
|
+
export { adjacentPage, layoutPages, pageAt, pageHolding, pageName, pageOfBounds, pagesBounds };
|
|
96
125
|
//# sourceMappingURL=pages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages.js","names":[],"sources":["../../../src/core/pages/pages.ts"],"sourcesContent":["import type { Bounds } from '@weasel-js/routing';\n\n/** One page of a document: a sheet of a fixed size, in world units. */\nexport interface Page {\n readonly id: string;\n readonly name?: string;\n readonly width: number;\n readonly height: number;\n}\n\n/** A page and where it sits in world space. */\nexport interface PlacedPage extends Page {\n readonly x: number;\n readonly y: number;\n /** Its place in the document's page order. */\n readonly index: number;\n}\n\n/** Options for {@link layoutPages}. */\nexport interface PageLayoutOptions {\n /** Which way the pages run. Default `'row'`. */\n direction?: 'row' | 'column';\n /** World units between neighbors. Default 40. */\n gap?: number;\n /** Where a page sits across the run, against the largest page. Default `'start'`. */\n align?: 'start' | 'center' | 'end';\n /** Where the first page's run starts. Default the world origin. */\n origin?: { readonly x: number; readonly y: number };\n}\n\nconst ALIGN = { start: 0, center: 0.5, end: 1 } as const;\n\n/**\n * Place a document's pages in one world space, in order, so a single scene,\n * view, and history serve all of them. With the defaults the first page's\n * top-left corner is the world origin, where a one-page document's page is.\n * Throws on two pages with one id.\n */\nexport function layoutPages(pages: readonly Page[], options: PageLayoutOptions = {}): PlacedPage[] {\n const { direction = 'row', gap = 40, align = 'start', origin = { x: 0, y: 0 } } = options;\n const row = direction === 'row';\n const across = Math.max(0, ...pages.map((p) => (row ? p.height : p.width)));\n const seen = new Set<string>();\n let along = 0;\n return pages.map((page, index) => {\n if (seen.has(page.id)) throw new Error(`layoutPages: two pages have the id \"${page.id}\"`);\n seen.add(page.id);\n const offset = (across - (row ? page.height : page.width)) * ALIGN[align];\n const placed = {\n ...page,\n x: origin.x + (row ? along : offset),\n y: origin.y + (row ? offset : along),\n index,\n };\n along += (row ? page.width : page.height) + gap;\n return placed;\n });\n}\n\n/** The page under a world point, or `null` between and outside them. A point on an edge is on the page. */\nexport function pageAt(\n pages: readonly PlacedPage[],\n point: { readonly x: number; readonly y: number },\n): PlacedPage | null {\n return pageOfBounds(pages, { x: point.x, y: point.y, width: 0, height: 0 });\n}\n\nfunction overlap(page: PlacedPage, b: Bounds): number | null {\n const w = Math.min(page.x + page.width, b.x + b.width) - Math.max(page.x, b.x);\n const h = Math.min(page.y + page.height, b.y + b.height) - Math.max(page.y, b.y);\n return w < 0 || h < 0 ? null : w * h;\n}\n\n/**\n * The page a world-space box belongs to: the one it overlaps most, the earlier\n * of two it overlaps equally, and `null` when it touches none. This is the\n * rule that says which page an object is on.\n */\nexport function pageOfBounds(pages: readonly PlacedPage[], bounds: Bounds): PlacedPage | null {\n let best: PlacedPage | null = null;\n let most = -1;\n for (const page of pages) {\n const area = overlap(page, bounds);\n if (area !== null && area > most) {\n best = page;\n most = area;\n }\n }\n return best;\n}\n\n/**\n * The page `step` places along the page order from the one with id `current`:\n * the next slide is `1`, the one before is `-1`. `null` past either end, unless\n * `wrap`. With no current page in the list, a step forward starts at the first\n * page and a step back at the last.\n */\nexport function adjacentPage(\n pages: readonly PlacedPage[],\n current: string | null,\n step: number,\n options: { wrap?: boolean } = {},\n): PlacedPage | null {\n const n = pages.length;\n if (n === 0) return null;\n const at = pages.findIndex((p) => p.id === current);\n if (at < 0) return pages[step < 0 ? n - 1 : 0] ?? null;\n const to = at + step;\n return pages[options.wrap ? ((to % n) + n) % n : to] ?? null;\n}\n\n/** The box around every page, or `null` for a document with none. */\nexport function pagesBounds(pages: readonly PlacedPage[]): Bounds | null {\n if (pages.length === 0) return null;\n const x = Math.min(...pages.map((p) => p.x));\n const y = Math.min(...pages.map((p) => p.y));\n return {\n x,\n y,\n width: Math.max(...pages.map((p) => p.x + p.width)) - x,\n height: Math.max(...pages.map((p) => p.y + p.height)) - y,\n };\n}\n"],"mappings":";AA8BA,IAAM,QAAQ;CAAE,OAAO;CAAG,QAAQ;CAAK,KAAK;AAAE;;;;;;;AAQ9C,SAAgB,YAAY,OAAwB,UAA6B,CAAC,GAAiB;CACjG,MAAM,EAAE,YAAY,OAAO,MAAM,IAAI,QAAQ,SAAS,SAAS;EAAE,GAAG;EAAG,GAAG;CAAE,MAAM;CAClF,MAAM,MAAM,cAAc;CAC1B,MAAM,SAAS,KAAK,IAAI,GAAG,GAAG,MAAM,KAAK,MAAO,MAAM,EAAE,SAAS,EAAE,KAAM,CAAC;CAC1E,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,QAAQ;CACZ,OAAO,MAAM,KAAK,MAAM,UAAU;EAChC,IAAI,KAAK,IAAI,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,uCAAuC,KAAK,GAAG,EAAE;EACxF,KAAK,IAAI,KAAK,EAAE;EAChB,MAAM,UAAU,UAAU,MAAM,KAAK,SAAS,KAAK,UAAU,MAAM;EACnE,MAAM,SAAS;GACb,GAAG;GACH,GAAG,OAAO,KAAK,MAAM,QAAQ;GAC7B,GAAG,OAAO,KAAK,MAAM,SAAS;GAC9B;EACF;EACA,UAAU,MAAM,KAAK,QAAQ,KAAK,UAAU;EAC5C,OAAO;CACT,CAAC;AACH;;AAGA,SAAgB,OACd,OACA,OACmB;CACnB,OAAO,aAAa,OAAO;EAAE,GAAG,MAAM;EAAG,GAAG,MAAM;EAAG,OAAO;EAAG,QAAQ;CAAE,CAAC;AAC5E;AAEA,SAAS,QAAQ,MAAkB,GAA0B;CAC3D,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,EAAE,CAAC;CAC7E,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,QAAQ,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,KAAK,GAAG,EAAE,CAAC;CAC/E,OAAO,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI;AACrC;;;;;;AAOA,SAAgB,aAAa,OAA8B,QAAmC;CAC5F,IAAI,OAA0B;CAC9B,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,QAAQ,MAAM,MAAM;EACjC,IAAI,SAAS,QAAQ,OAAO,MAAM;GAChC,OAAO;GACP,OAAO;EACT;CACF;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,aACd,OACA,SACA,MACA,UAA8B,CAAC,GACZ;CACnB,MAAM,IAAI,MAAM;CAChB,IAAI,MAAM,GAAG,OAAO;CACpB,MAAM,KAAK,MAAM,WAAW,MAAM,EAAE,OAAO,OAAO;CAClD,IAAI,KAAK,GAAG,OAAO,MAAM,OAAO,IAAI,IAAI,IAAI,MAAM;CAClD,MAAM,KAAK,KAAK;CAChB,OAAO,MAAM,QAAQ,QAAS,KAAK,IAAK,KAAK,IAAI,OAAO;AAC1D;;AAGA,SAAgB,YAAY,OAA6C;CACvE,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;CAC3C,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;CAC3C,OAAO;EACL;EACA;EACA,OAAO,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI;EACtD,QAAQ,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI;CAC1D;AACF"}
|
|
1
|
+
{"version":3,"file":"pages.js","names":[],"sources":["../../../src/core/pages/pages.ts"],"sourcesContent":["import type { Bounds } from '@weasel-js/routing';\n\n/** One page of a document: a sheet of a fixed size, in world units. */\nexport interface Page {\n readonly id: string;\n readonly name?: string;\n readonly width: number;\n readonly height: number;\n}\n\n/** A page and where it sits in world space. */\nexport interface PlacedPage extends Page {\n readonly x: number;\n readonly y: number;\n /** Its place in the document's page order. */\n readonly index: number;\n}\n\n/** Options for {@link layoutPages}. */\nexport interface PageLayoutOptions {\n /** Which way the pages run. Default `'row'`. */\n direction?: 'row' | 'column';\n /** World units between neighbors. Default 40. */\n gap?: number;\n /** Where a page sits across the run, against the largest page. Default `'start'`. */\n align?: 'start' | 'center' | 'end';\n /** Where the first page's run starts. Default the world origin. */\n origin?: { readonly x: number; readonly y: number };\n}\n\nconst ALIGN = { start: 0, center: 0.5, end: 1 } as const;\n\n/**\n * Place a document's pages in one world space, in order, so a single scene,\n * view, and history serve all of them. With the defaults the first page's\n * top-left corner is the world origin, where a one-page document's page is.\n * Throws on two pages with one id.\n */\nexport function layoutPages(pages: readonly Page[], options: PageLayoutOptions = {}): PlacedPage[] {\n const { direction = 'row', gap = 40, align = 'start', origin = { x: 0, y: 0 } } = options;\n const row = direction === 'row';\n const across = Math.max(0, ...pages.map((p) => (row ? p.height : p.width)));\n const seen = new Set<string>();\n let along = 0;\n return pages.map((page, index) => {\n if (seen.has(page.id)) throw new Error(`layoutPages: two pages have the id \"${page.id}\"`);\n seen.add(page.id);\n const offset = (across - (row ? page.height : page.width)) * ALIGN[align];\n const placed = {\n ...page,\n x: origin.x + (row ? along : offset),\n y: origin.y + (row ? offset : along),\n index,\n };\n along += (row ? page.width : page.height) + gap;\n return placed;\n });\n}\n\n/** The name page `index` of the page order shows: its own, else \"Page\" and its place. */\nexport function pageName(page: { readonly name?: string | undefined }, index: number): string {\n return page.name ?? `Page ${index + 1}`;\n}\n\n/** The page under a world point, or `null` between and outside them. A point on an edge is on the page. */\nexport function pageAt(\n pages: readonly PlacedPage[],\n point: { readonly x: number; readonly y: number },\n): PlacedPage | null {\n return pageOfBounds(pages, { x: point.x, y: point.y, width: 0, height: 0 });\n}\n\nfunction overlap(page: PlacedPage, b: Bounds): number | null {\n const w = Math.min(page.x + page.width, b.x + b.width) - Math.max(page.x, b.x);\n const h = Math.min(page.y + page.height, b.y + b.height) - Math.max(page.y, b.y);\n return w < 0 || h < 0 ? null : w * h;\n}\n\n/**\n * The page a world-space box belongs to: the one it overlaps most, the earlier\n * of two it overlaps equally, and `null` when it touches none. This is the\n * rule that says which page an object is on.\n */\nexport function pageOfBounds(pages: readonly PlacedPage[], bounds: Bounds): PlacedPage | null {\n let best: PlacedPage | null = null;\n let most = -1;\n for (const page of pages) {\n const area = overlap(page, bounds);\n if (area !== null && area > most) {\n best = page;\n most = area;\n }\n }\n return best;\n}\n\nfunction gap(page: PlacedPage, b: Bounds): number {\n const dx = Math.max(page.x - (b.x + b.width), b.x - (page.x + page.width), 0);\n const dy = Math.max(page.y - (b.y + b.height), b.y - (page.y + page.height), 0);\n return Math.hypot(dx, dy);\n}\n\n/**\n * The page a world-space box is held by: the one it is on (`pageOfBounds`),\n * else the one nearest it by the box-to-box gap, the earlier of two equally\n * near. `null` only with no pages. Used where every object must land on some\n * page, such as an export; `pageOfBounds` stays the rule for what is on one.\n */\nexport function pageHolding(pages: readonly PlacedPage[], bounds: Bounds): PlacedPage | null {\n const on = pageOfBounds(pages, bounds);\n if (on !== null) return on;\n let best: PlacedPage | null = null;\n let least = Infinity;\n for (const page of pages) {\n const d = gap(page, bounds);\n if (d < least) {\n best = page;\n least = d;\n }\n }\n return best;\n}\n\n/**\n * The page `step` places along the page order from the one with id `current`:\n * the next slide is `1`, the one before is `-1`. `null` past either end, unless\n * `wrap`. With no current page in the list, a step forward starts at the first\n * page and a step back at the last.\n */\nexport function adjacentPage(\n pages: readonly PlacedPage[],\n current: string | null,\n step: number,\n options: { wrap?: boolean } = {},\n): PlacedPage | null {\n const n = pages.length;\n if (n === 0) return null;\n const at = pages.findIndex((p) => p.id === current);\n if (at < 0) return pages[step < 0 ? n - 1 : 0] ?? null;\n const to = at + step;\n return pages[options.wrap ? ((to % n) + n) % n : to] ?? null;\n}\n\n/** The box around every page, or `null` for a document with none. */\nexport function pagesBounds(pages: readonly PlacedPage[]): Bounds | null {\n if (pages.length === 0) return null;\n const x = Math.min(...pages.map((p) => p.x));\n const y = Math.min(...pages.map((p) => p.y));\n return {\n x,\n y,\n width: Math.max(...pages.map((p) => p.x + p.width)) - x,\n height: Math.max(...pages.map((p) => p.y + p.height)) - y,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/core/scene/NodeRouting.ts
|
|
2
|
+
/** The routing kind of a node: the name of the first entry whose `matches(data, node)` returns
|
|
3
|
+
* true, or `'unknown'`. What `NodeRouting.classify` answers, for a caller holding a plain list. */
|
|
4
|
+
function routeKind(entries, data, node) {
|
|
5
|
+
for (const kind of entries) if (kind.matches(data, node)) return kind.name;
|
|
6
|
+
return "unknown";
|
|
7
|
+
}
|
|
2
8
|
/** Build a registry of per-node-kind routing entries. Registering a duplicate
|
|
3
9
|
* kind name throws. */
|
|
4
10
|
function createNodeRouting() {
|
|
@@ -11,8 +17,7 @@ function createNodeRouting() {
|
|
|
11
17
|
entries.push(kind);
|
|
12
18
|
},
|
|
13
19
|
classify(data, node) {
|
|
14
|
-
|
|
15
|
-
return "unknown";
|
|
20
|
+
return routeKind(entries, data, node);
|
|
16
21
|
},
|
|
17
22
|
get(name) {
|
|
18
23
|
return byName.get(name);
|
|
@@ -24,5 +29,5 @@ function createNodeRouting() {
|
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
//#endregion
|
|
27
|
-
export { createNodeRouting };
|
|
32
|
+
export { createNodeRouting, routeKind };
|
|
28
33
|
//# sourceMappingURL=NodeRouting.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeRouting.js","names":[],"sources":["../../../src/core/scene/NodeRouting.ts"],"sourcesContent":["/**\n * A routing-trait classifier entry. Other traits (shape, label, icon, …)\n * are independent registries — fields like `label` or `icon` do NOT belong\n * on this interface.\n */\nexport interface NodeRoutingEntry {\n /** Unique kind name. Routing tables key on this string. Consumer-defined;\n * the kit places no constraint beyond uniqueness within a registry. */\n name: string;\n /** Predicate over a node's `data` payload, and the node itself where the caller has one, for\n * a kind its data cannot show (`'container'`). First registered kind whose `matches` returns\n * true claims the node. */\n matches: (data: unknown, node?: RoutedNode) => boolean;\n}\n\n/** What a routing entry may read of a node beside its data. */\nexport interface RoutedNode {\n /** The node's structural kind: `'leaf'` or `'container'`. */\n readonly kind?: string;\n}\n\n/**\n * The **routing trait's** registry: answers \"what routing-kind string does\n * this node's data map to?\" The result flows into declarative tool-routing\n * tables (`{ target: 'rect', actionId: 'move' }`).\n */\nexport interface NodeRouting {\n /** Register a kind. Order matters: first match wins during `classify`.\n * Throws if a kind with this name is already registered. */\n register(kind: NodeRoutingEntry): void;\n /** Walk registered kinds in registration order; return the first kind\n * whose `matches(data, node)` returns true, or `'unknown'` if none match. */\n classify(data: unknown, node?: RoutedNode): string;\n /** Lookup a kind entry by name. */\n get(name: string): NodeRoutingEntry | undefined;\n /** Enumerate registered kinds in registration order. */\n list(): readonly NodeRoutingEntry[];\n}\n\n/** Build a registry of per-node-kind routing entries. Registering a duplicate\n * kind name throws. */\nexport function createNodeRouting(): NodeRouting {\n const entries: NodeRoutingEntry[] = [];\n const byName = new Map<string, NodeRoutingEntry>();\n return {\n register(kind) {\n if (byName.has(kind.name)) {\n throw new Error(\n `createNodeRouting: duplicate kind name \"${kind.name}\"`,\n );\n }\n byName.set(kind.name, kind);\n entries.push(kind);\n },\n classify(data, node) {\n
|
|
1
|
+
{"version":3,"file":"NodeRouting.js","names":[],"sources":["../../../src/core/scene/NodeRouting.ts"],"sourcesContent":["/**\n * A routing-trait classifier entry. Other traits (shape, label, icon, …)\n * are independent registries — fields like `label` or `icon` do NOT belong\n * on this interface.\n */\nexport interface NodeRoutingEntry {\n /** Unique kind name. Routing tables key on this string. Consumer-defined;\n * the kit places no constraint beyond uniqueness within a registry. */\n name: string;\n /** Predicate over a node's `data` payload, and the node itself where the caller has one, for\n * a kind its data cannot show (`'container'`). First registered kind whose `matches` returns\n * true claims the node. */\n matches: (data: unknown, node?: RoutedNode) => boolean;\n}\n\n/** What a routing entry may read of a node beside its data. */\nexport interface RoutedNode {\n /** The node's structural kind: `'leaf'` or `'container'`. */\n readonly kind?: string;\n}\n\n/**\n * The **routing trait's** registry: answers \"what routing-kind string does\n * this node's data map to?\" The result flows into declarative tool-routing\n * tables (`{ target: 'rect', actionId: 'move' }`).\n */\nexport interface NodeRouting {\n /** Register a kind. Order matters: first match wins during `classify`.\n * Throws if a kind with this name is already registered. */\n register(kind: NodeRoutingEntry): void;\n /** Walk registered kinds in registration order; return the first kind\n * whose `matches(data, node)` returns true, or `'unknown'` if none match. */\n classify(data: unknown, node?: RoutedNode): string;\n /** Lookup a kind entry by name. */\n get(name: string): NodeRoutingEntry | undefined;\n /** Enumerate registered kinds in registration order. */\n list(): readonly NodeRoutingEntry[];\n}\n\n/** The routing kind of a node: the name of the first entry whose `matches(data, node)` returns\n * true, or `'unknown'`. What `NodeRouting.classify` answers, for a caller holding a plain list. */\nexport function routeKind(\n entries: readonly NodeRoutingEntry[],\n data: unknown,\n node?: RoutedNode,\n): string {\n for (const kind of entries) {\n if (kind.matches(data, node)) return kind.name;\n }\n return 'unknown';\n}\n\n/** Build a registry of per-node-kind routing entries. Registering a duplicate\n * kind name throws. */\nexport function createNodeRouting(): NodeRouting {\n const entries: NodeRoutingEntry[] = [];\n const byName = new Map<string, NodeRoutingEntry>();\n return {\n register(kind) {\n if (byName.has(kind.name)) {\n throw new Error(\n `createNodeRouting: duplicate kind name \"${kind.name}\"`,\n );\n }\n byName.set(kind.name, kind);\n entries.push(kind);\n },\n classify(data, node) {\n return routeKind(entries, data, node);\n },\n get(name) {\n return byName.get(name);\n },\n list() {\n return entries;\n },\n };\n}\n"],"mappings":";;;AAyCA,SAAgB,UACd,SACA,MACA,MACQ;CACR,KAAK,MAAM,QAAQ,SACjB,IAAI,KAAK,QAAQ,MAAM,IAAI,GAAG,OAAO,KAAK;CAE5C,OAAO;AACT;;;AAIA,SAAgB,oBAAiC;CAC/C,MAAM,UAA8B,CAAC;CACrC,MAAM,yBAAS,IAAI,IAA8B;CACjD,OAAO;EACL,SAAS,MAAM;GACb,IAAI,OAAO,IAAI,KAAK,IAAI,GACtB,MAAM,IAAI,MACR,2CAA2C,KAAK,KAAK,EACvD;GAEF,OAAO,IAAI,KAAK,MAAM,IAAI;GAC1B,QAAQ,KAAK,IAAI;EACnB;EACA,SAAS,MAAM,MAAM;GACnB,OAAO,UAAU,SAAS,MAAM,IAAI;EACtC;EACA,IAAI,MAAM;GACR,OAAO,OAAO,IAAI,IAAI;EACxB;EACA,OAAO;GACL,OAAO;EACT;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrivalWindow.js","names":[],"sources":["../../../src/core/scene/arrivalWindow.ts"],"sourcesContent":["/**\n * The scene's arrival window: what a live edit did to containers, collected\n * while it runs and handed to a layout handler before it closes.\n *\n * The window is open while a live edit is collecting the nodes that joined a\n * container, the ones that left one, and every container whose children or\n * size it changed; whoever opened the window settles it before the edit\n * closes. Undo, redo and journal replays never open it: they restore an\n * arrangement that was already decided.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { Op } from 'core/ops/types';\nimport { createArrangeOp, type ArrangedPose } from 'core/ops/arrange';\nimport { SceneArrivalRefused } from './arrivals';\nimport type { LayoutMove, Node, NodeId, SceneArrivalHandler } from './types';\n\n/** What the window needs from the scene it serves. */\nexport interface ArrivalWindowEnv<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n /** The pass over the scene's declared layouts, which settles arrivals once\n * a container has declared one. An installed handler takes its place. */\n layoutPass: SceneArrivalHandler<TPose>;\n /** Whether a bare scene call would record an edit of its own right now:\n * no batch is open and no history engine is driving. */\n recordsBareEdits(): boolean;\n /** Write one arranged pose. Bound into the arrange op, since not every\n * adapter an edit is applied through can write poses. */\n writePose(id: string, pose: unknown): void;\n}\n\nexport interface ArrivalWindow<TPose> {\n /** A container was given a layout: from here on the scene has arrivals to\n * settle even with no handler installed. */\n layoutDeclared(): void;\n /** Install the handler that settles arrivals. Returns its remover. */\n setHandler(handler: SceneArrivalHandler<TPose>): () => void;\n /** Hear the poses each live edit's settle wrote, after the edit lands. */\n onReflow(listener: (moves: readonly LayoutMove<TPose>[]) => void): () => void;\n noteArrival(id: NodeId, parent: NodeId | null, from: NodeId | null): void;\n noteDeparture(id: NodeId, from: NodeId | null, to: NodeId | null): void;\n /** A container's child set or order changed, or it changed size. */\n noteChange(id: NodeId | null): void;\n /** Whether a bare scene call has to run as a batch of its own, so the\n * layout pass it triggers lands in its undo step. */\n bareLayoutEdit(): boolean;\n /** Open the window when a handler wants one — or `always`, for an edit that\n * may give the scene its first layout partway through — and no enclosing\n * edit holds it already; true when this caller now owns it. */\n open(always?: boolean): boolean;\n /** Close the window and ask the handler what the arrivals get. Throws\n * `SceneArrivalRefused` when it refuses, which reverts the edit. */\n settle(): ArrangedPose[];\n /** Report what the settled edit wrote, now that it has landed. */\n emitReflow(): void;\n /** Close the window with nothing settled or reported: its owner's edit is\n * over, however it ended. */\n abandon(): void;\n /** Run an op-driven edit that may carry arrivals: a refusal reverts it and\n * is swallowed, the way a drop onto a full container is — nothing lands.\n * Returns whether the edit landed. */\n runEdit(ops: Op[], run: (ops: Op[]) => void): boolean;\n}\n\n/** Of the ids noted per container, those the landed edit bears out: still\n * under it when `joined`, gone from it otherwise. A container that is itself\n * gone is left out. */\nfunction heldMoves(\n nodes: ReadonlyMap<NodeId, { parent: NodeId | null }>,\n noted: ReadonlyMap<NodeId, NodeId[]>,\n joined: boolean,\n): Map<NodeId, NodeId[]> {\n const out = new Map<NodeId, NodeId[]>();\n for (const [parent, ids] of noted) {\n const held = ids.filter((id) => (nodes.get(id)?.parent === parent) === joined);\n if (held.length > 0 && nodes.has(parent)) out.set(parent, held);\n }\n return out;\n}\n\nexport function createArrivalWindow<TData, TLayer extends string, TPose>(\n env: ArrivalWindowEnv<TData, TLayer, TPose>,\n): ArrivalWindow<TPose> {\n const { nodes } = env;\n let installed: SceneArrivalHandler<TPose> | null = null;\n let hasLayouts = false;\n let arrivals: Map<NodeId, NodeId[]> | null = null;\n let departures = new Map<NodeId, NodeId[]>();\n let changed = new Set<NodeId>();\n /** What the last settle wrote, reported to `onReflow` once its edit lands. */\n let reflowDue: LayoutMove<TPose>[] | null = null;\n const reflowListeners = createEmitter<[moves: readonly LayoutMove<TPose>[]]>();\n\n const activeHandler = (): SceneArrivalHandler<TPose> | null => installed ?? (hasLayouts ? env.layoutPass : null);\n\n function open(always = false): boolean {\n if (arrivals !== null || (!always && activeHandler() === null)) return false;\n arrivals = new Map();\n departures = new Map();\n changed = new Set();\n reflowDue = null;\n return true;\n }\n\n function emitReflow(): void {\n const moves = reflowDue;\n reflowDue = null;\n if (moves === null || moves.length === 0) return;\n reflowListeners.emit(moves);\n }\n\n function settle(): ArrangedPose[] {\n const pending = arrivals;\n arrivals = null;\n const handler = activeHandler();\n if (pending === null || handler === null) return [];\n // Only what is still there once the edit's other mutations have landed.\n const landed = heldMoves(nodes, pending, true);\n // Only what is gone from the container once the edit has landed: a child\n // that left and came back did not depart.\n const left = heldMoves(nodes, departures, false);\n departures = new Map();\n const touched = new Set([...changed].filter((id) => nodes.get(id)?.kind === 'container'));\n if (landed.size === 0 && touched.size === 0) return [];\n const poses = handler(landed, touched, left);\n if (poses === null) throw new SceneArrivalRefused(landed);\n const out: ArrangedPose[] = [];\n for (const [id, to] of poses) {\n const node = nodes.get(id);\n if (node) out.push({ id, from: node.pose, to });\n }\n reflowDue = out as LayoutMove<TPose>[];\n return out;\n }\n\n function abandon(): void {\n arrivals = null;\n reflowDue = null;\n }\n\n /** `ops` plus the op that settles their arrivals, when this call owns the\n * window; `ops` unchanged otherwise. Opened even on a scene with no layout\n * yet: an edit's op list is fixed before it runs, and any op in it may give\n * the scene its first. The arrange op that settles nothing elides itself. */\n function withArrivalOps(ops: Op[]): { ops: Op[]; owned: boolean } {\n if (!open(true)) return { ops, owned: false };\n return { ops: [...ops, createArrangeOp({}, settle, env.writePose)], owned: true };\n }\n\n return {\n layoutDeclared: () => { hasLayouts = true; },\n setHandler(handler) {\n installed = handler;\n return () => {\n if (installed === handler) installed = null;\n };\n },\n onReflow: reflowListeners.subscribe,\n noteArrival(id, parent, from) {\n if (arrivals === null || parent === null || parent === from) return;\n changed.add(parent);\n const list = arrivals.get(parent);\n if (list === undefined) arrivals.set(parent, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteDeparture(id, from, to) {\n if (arrivals === null || from === null || from === to) return;\n changed.add(from);\n // One that joined earlier in this edit was never there to leave.\n if (arrivals.get(from)?.includes(id)) return;\n const list = departures.get(from);\n if (list === undefined) departures.set(from, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteChange(id) {\n if (arrivals !== null && id !== null) changed.add(id);\n },\n bareLayoutEdit: () => activeHandler() !== null && arrivals === null && env.recordsBareEdits(),\n open,\n settle,\n emitReflow,\n abandon,\n runEdit(ops, run) {\n const { ops: all, owned } = withArrivalOps(ops);\n // An edit inside another's window leaves a refusal to the one that owns\n // it, which reverts the whole edit and answers for it.\n if (!owned) return run(all), true;\n try {\n run(all);\n emitReflow();\n return true;\n } catch (err) {\n if (!(err instanceof SceneArrivalRefused)) throw err;\n return false;\n } finally {\n abandon();\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"arrivalWindow.js","names":[],"sources":["../../../src/core/scene/arrivalWindow.ts"],"sourcesContent":["/**\n * The scene's arrival window: what a live edit did to containers, collected\n * while it runs and handed to a layout handler before it closes.\n *\n * The window is open while a live edit is collecting the nodes that joined a\n * container, the ones that left one, and every container whose children or\n * size it changed; whoever opened the window settles it before the edit\n * closes. Undo, redo and journal replays never open it: they restore an\n * arrangement that was already decided.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { Op } from 'core/ops/types';\nimport { createArrangeOp, type ArrangedPose } from 'core/ops/arrange';\nimport { SceneArrivalRefused } from './arrivals';\nimport type { LayoutMove, Node, NodeId, SceneArrivalHandler } from './types';\n\n/** What the window needs from the scene it serves. */\nexport interface ArrivalWindowEnv<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n /** The pass over the scene's declared layouts, which settles arrivals once\n * a container has declared one. An installed handler takes its place. */\n layoutPass: SceneArrivalHandler<TPose>;\n /** Whether a bare scene call would record an edit of its own right now:\n * no batch is open and no history engine is driving. */\n recordsBareEdits(): boolean;\n /** Write one arranged pose. Bound into the arrange op, since not every\n * adapter an edit is applied through can write poses. */\n writePose(id: string, pose: unknown): void;\n}\n\nexport interface ArrivalWindow<TPose> {\n /** A container was given a layout: from here on the scene has arrivals to\n * settle even with no handler installed. */\n layoutDeclared(): void;\n /** Install the handler that settles arrivals. Returns its remover. */\n setHandler(handler: SceneArrivalHandler<TPose>): () => void;\n /** Hear the poses each live edit's settle wrote, after the edit lands. */\n onReflow(listener: (moves: readonly LayoutMove<TPose>[]) => void): () => void;\n noteArrival(id: NodeId, parent: NodeId | null, from: NodeId | null): void;\n noteDeparture(id: NodeId, from: NodeId | null, to: NodeId | null): void;\n /** A container's child set or order changed, it changed size, or a child was posed. */\n noteChange(id: NodeId | null): void;\n /** Whether a bare scene call has to run as a batch of its own, so the\n * layout pass it triggers lands in its undo step. */\n bareLayoutEdit(): boolean;\n /** Open the window when a handler wants one — or `always`, for an edit that\n * may give the scene its first layout partway through — and no enclosing\n * edit holds it already; true when this caller now owns it. */\n open(always?: boolean): boolean;\n /** Close the window and ask the handler what the arrivals get. Throws\n * `SceneArrivalRefused` when it refuses, which reverts the edit. */\n settle(): ArrangedPose[];\n /** Report what the settled edit wrote, now that it has landed. */\n emitReflow(): void;\n /** Close the window with nothing settled or reported: its owner's edit is\n * over, however it ended. */\n abandon(): void;\n /** Run an op-driven edit that may carry arrivals: a refusal reverts it and\n * is swallowed, the way a drop onto a full container is — nothing lands.\n * Returns whether the edit landed. */\n runEdit(ops: Op[], run: (ops: Op[]) => void): boolean;\n}\n\n/** Of the ids noted per container, those the landed edit bears out: still\n * under it when `joined`, gone from it otherwise. A container that is itself\n * gone is left out. */\nfunction heldMoves(\n nodes: ReadonlyMap<NodeId, { parent: NodeId | null }>,\n noted: ReadonlyMap<NodeId, NodeId[]>,\n joined: boolean,\n): Map<NodeId, NodeId[]> {\n const out = new Map<NodeId, NodeId[]>();\n for (const [parent, ids] of noted) {\n const held = ids.filter((id) => (nodes.get(id)?.parent === parent) === joined);\n if (held.length > 0 && nodes.has(parent)) out.set(parent, held);\n }\n return out;\n}\n\nexport function createArrivalWindow<TData, TLayer extends string, TPose>(\n env: ArrivalWindowEnv<TData, TLayer, TPose>,\n): ArrivalWindow<TPose> {\n const { nodes } = env;\n let installed: SceneArrivalHandler<TPose> | null = null;\n let hasLayouts = false;\n let arrivals: Map<NodeId, NodeId[]> | null = null;\n let departures = new Map<NodeId, NodeId[]>();\n let changed = new Set<NodeId>();\n /** What the last settle wrote, reported to `onReflow` once its edit lands. */\n let reflowDue: LayoutMove<TPose>[] | null = null;\n const reflowListeners = createEmitter<[moves: readonly LayoutMove<TPose>[]]>();\n\n const activeHandler = (): SceneArrivalHandler<TPose> | null => installed ?? (hasLayouts ? env.layoutPass : null);\n\n function open(always = false): boolean {\n if (arrivals !== null || (!always && activeHandler() === null)) return false;\n arrivals = new Map();\n departures = new Map();\n changed = new Set();\n reflowDue = null;\n return true;\n }\n\n function emitReflow(): void {\n const moves = reflowDue;\n reflowDue = null;\n if (moves === null || moves.length === 0) return;\n reflowListeners.emit(moves);\n }\n\n function settle(): ArrangedPose[] {\n const pending = arrivals;\n arrivals = null;\n const handler = activeHandler();\n if (pending === null || handler === null) return [];\n // Only what is still there once the edit's other mutations have landed.\n const landed = heldMoves(nodes, pending, true);\n // Only what is gone from the container once the edit has landed: a child\n // that left and came back did not depart.\n const left = heldMoves(nodes, departures, false);\n departures = new Map();\n const touched = new Set([...changed].filter((id) => nodes.get(id)?.kind === 'container'));\n if (landed.size === 0 && touched.size === 0) return [];\n const poses = handler(landed, touched, left);\n if (poses === null) throw new SceneArrivalRefused(landed);\n const out: ArrangedPose[] = [];\n for (const [id, to] of poses) {\n const node = nodes.get(id);\n if (node) out.push({ id, from: node.pose, to });\n }\n reflowDue = out as LayoutMove<TPose>[];\n return out;\n }\n\n function abandon(): void {\n arrivals = null;\n reflowDue = null;\n }\n\n /** `ops` plus the op that settles their arrivals, when this call owns the\n * window; `ops` unchanged otherwise. Opened even on a scene with no layout\n * yet: an edit's op list is fixed before it runs, and any op in it may give\n * the scene its first. The arrange op that settles nothing elides itself. */\n function withArrivalOps(ops: Op[]): { ops: Op[]; owned: boolean } {\n if (!open(true)) return { ops, owned: false };\n return { ops: [...ops, createArrangeOp({}, settle, env.writePose)], owned: true };\n }\n\n return {\n layoutDeclared: () => { hasLayouts = true; },\n setHandler(handler) {\n installed = handler;\n return () => {\n if (installed === handler) installed = null;\n };\n },\n onReflow: reflowListeners.subscribe,\n noteArrival(id, parent, from) {\n if (arrivals === null || parent === null || parent === from) return;\n changed.add(parent);\n const list = arrivals.get(parent);\n if (list === undefined) arrivals.set(parent, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteDeparture(id, from, to) {\n if (arrivals === null || from === null || from === to) return;\n changed.add(from);\n // One that joined earlier in this edit was never there to leave.\n if (arrivals.get(from)?.includes(id)) return;\n const list = departures.get(from);\n if (list === undefined) departures.set(from, [id]);\n else if (!list.includes(id)) list.push(id);\n },\n noteChange(id) {\n if (arrivals !== null && id !== null) changed.add(id);\n },\n bareLayoutEdit: () => activeHandler() !== null && arrivals === null && env.recordsBareEdits(),\n open,\n settle,\n emitReflow,\n abandon,\n runEdit(ops, run) {\n const { ops: all, owned } = withArrivalOps(ops);\n // An edit inside another's window leaves a refusal to the one that owns\n // it, which reverts the whole edit and answers for it.\n if (!owned) return run(all), true;\n try {\n run(all);\n emitReflow();\n return true;\n } catch (err) {\n if (!(err instanceof SceneArrivalRefused)) throw err;\n return false;\n } finally {\n abandon();\n }\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { composeWorldPose, composesFrames, frameAtOrAbove, rebaseLocalPose } from "./composeFrame.js";
|
|
2
|
+
import { scenePoseAdapter } from "./scenePoseAdapter.js";
|
|
3
|
+
|
|
4
|
+
//#region src/core/scene/carryDescendants.ts
|
|
5
|
+
/**
|
|
6
|
+
* Bringing a node's descendants along when something writes the node's pose
|
|
7
|
+
* and nothing writes theirs: a layout restacking a container, a drop landing
|
|
8
|
+
* one, a page move. Under absolute poses a descendant stores its own world
|
|
9
|
+
* pose, so it stays behind unless it is written too.
|
|
10
|
+
*/
|
|
11
|
+
/** A {@link CarrySource} over a scene's document poses. */
|
|
12
|
+
function sceneCarrySource(scene) {
|
|
13
|
+
return {
|
|
14
|
+
childrenOf: (id) => scene.childrenOf(id),
|
|
15
|
+
poses: scenePoseAdapter(scene),
|
|
16
|
+
composition: scene.composition
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The stored poses that bring `root`'s descendants along with it: each one's
|
|
21
|
+
* world pose put through `translate`, rebased into the frame it is stored in.
|
|
22
|
+
* A descendant stored in the frame of `root` or of a node under it is left
|
|
23
|
+
* out, since that frame carries it, and so is one `translate` answers null
|
|
24
|
+
* for. A descendant `moving` names is moving on its own: it and everything
|
|
25
|
+
* under it are left to whoever moves it.
|
|
26
|
+
*/
|
|
27
|
+
function carriedDescendants(source, root, translate, moving = () => false) {
|
|
28
|
+
const { poses, composition } = source;
|
|
29
|
+
const framed = composesFrames(composition);
|
|
30
|
+
const under = /* @__PURE__ */ new Set([root]);
|
|
31
|
+
const out = /* @__PURE__ */ new Map();
|
|
32
|
+
const walk = (at) => {
|
|
33
|
+
for (const id of source.childrenOf(at)) {
|
|
34
|
+
if (moving(id)) continue;
|
|
35
|
+
under.add(id);
|
|
36
|
+
const parent = poses.getParent(id);
|
|
37
|
+
const frame = framed ? frameAtOrAbove(poses, parent) : null;
|
|
38
|
+
if (frame === null || !under.has(frame)) {
|
|
39
|
+
const world = translate(composeWorldPose(poses, id, composition.compose), id);
|
|
40
|
+
if (world !== null) out.set(id, rebaseLocalPose(poses, world, parent, composition.compose, composition.decompose));
|
|
41
|
+
}
|
|
42
|
+
walk(id);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(root);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { carriedDescendants, sceneCarrySource };
|
|
51
|
+
//# sourceMappingURL=carryDescendants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"carryDescendants.js","names":[],"sources":["../../../src/core/scene/carryDescendants.ts"],"sourcesContent":["/**\n * Bringing a node's descendants along when something writes the node's pose\n * and nothing writes theirs: a layout restacking a container, a drop landing\n * one, a page move. Under absolute poses a descendant stores its own world\n * pose, so it stays behind unless it is written too.\n */\nimport { composeWorldPose, composesFrames, frameAtOrAbove, rebaseLocalPose, type PoseAdapter, type PoseComposition } from './composeFrame';\nimport { scenePoseAdapter } from './scenePoseAdapter';\nimport type { NodeId, Scene } from './types';\n\n/** What carrying reads: the tree, every stored pose, and how poses compose. */\nexport interface CarrySource<TPose> {\n childrenOf(id: NodeId): readonly NodeId[];\n poses: PoseAdapter<TPose>;\n composition: PoseComposition<TPose>;\n}\n\n/** A {@link CarrySource} over a scene's document poses. */\nexport function sceneCarrySource(scene: Scene<unknown, string, unknown>): CarrySource<unknown> {\n return {\n childrenOf: (id) => scene.childrenOf(id),\n poses: scenePoseAdapter(scene),\n composition: scene.composition,\n };\n}\n\n/**\n * The stored poses that bring `root`'s descendants along with it: each one's\n * world pose put through `translate`, rebased into the frame it is stored in.\n * A descendant stored in the frame of `root` or of a node under it is left\n * out, since that frame carries it, and so is one `translate` answers null\n * for. A descendant `moving` names is moving on its own: it and everything\n * under it are left to whoever moves it.\n */\nexport function carriedDescendants<TPose>(\n source: CarrySource<TPose>,\n root: NodeId,\n translate: (world: TPose, id: NodeId) => TPose | null,\n moving: (id: NodeId) => boolean = () => false,\n): Map<NodeId, TPose> {\n const { poses, composition } = source;\n const framed = composesFrames(composition);\n const under = new Set<NodeId>([root]);\n const out = new Map<NodeId, TPose>();\n const walk = (at: NodeId): void => {\n for (const id of source.childrenOf(at)) {\n if (moving(id)) continue;\n under.add(id);\n const parent = poses.getParent(id);\n const frame = framed ? frameAtOrAbove(poses, parent) : null;\n if (frame === null || !under.has(frame as NodeId)) {\n const world = translate(composeWorldPose(poses, id, composition.compose), id);\n if (world !== null) out.set(id, rebaseLocalPose(poses, world, parent, composition.compose, composition.decompose));\n }\n walk(id);\n }\n };\n walk(root);\n return out;\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAgB,iBAAiB,OAA8D;CAC7F,OAAO;EACL,aAAa,OAAO,MAAM,WAAW,EAAE;EACvC,OAAO,iBAAiB,KAAK;EAC7B,aAAa,MAAM;CACrB;AACF;;;;;;;;;AAUA,SAAgB,mBACd,QACA,MACA,WACA,eAAwC,OACpB;CACpB,MAAM,EAAE,OAAO,gBAAgB;CAC/B,MAAM,SAAS,eAAe,WAAW;CACzC,MAAM,wBAAQ,IAAI,IAAY,CAAC,IAAI,CAAC;CACpC,MAAM,sBAAM,IAAI,IAAmB;CACnC,MAAM,QAAQ,OAAqB;EACjC,KAAK,MAAM,MAAM,OAAO,WAAW,EAAE,GAAG;GACtC,IAAI,OAAO,EAAE,GAAG;GAChB,MAAM,IAAI,EAAE;GACZ,MAAM,SAAS,MAAM,UAAU,EAAE;GACjC,MAAM,QAAQ,SAAS,eAAe,OAAO,MAAM,IAAI;GACvD,IAAI,UAAU,QAAQ,CAAC,MAAM,IAAI,KAAe,GAAG;IACjD,MAAM,QAAQ,UAAU,iBAAiB,OAAO,IAAI,YAAY,OAAO,GAAG,EAAE;IAC5E,IAAI,UAAU,MAAM,IAAI,IAAI,IAAI,gBAAgB,OAAO,OAAO,QAAQ,YAAY,SAAS,YAAY,SAAS,CAAC;GACnH;GACA,KAAK,EAAE;EACT;CACF;CACA,KAAK,IAAI;CACT,OAAO;AACT"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { deepEqual } from "@weasel-js/registry";
|
|
2
|
+
|
|
3
|
+
//#region src/core/scene/docValues.ts
|
|
4
|
+
/**
|
|
5
|
+
* Document state that is not a node — a document's pages, its theme — held as
|
|
6
|
+
* named JSON values beside the tree, saved in its snapshot and undone with it.
|
|
7
|
+
*/
|
|
8
|
+
var SET_DOC = "kit:setDoc";
|
|
9
|
+
function write(values, key, to) {
|
|
10
|
+
if (to === void 0) values.delete(key);
|
|
11
|
+
else values.set(key, to);
|
|
12
|
+
}
|
|
13
|
+
/** The snapshot side of a scene's document values, kept in `values`. */
|
|
14
|
+
function createDocStore(values) {
|
|
15
|
+
return {
|
|
16
|
+
/** The values as a snapshot holds them, or `undefined` when there are none. */
|
|
17
|
+
toJSON() {
|
|
18
|
+
return values.size === 0 ? void 0 : Object.fromEntries(values);
|
|
19
|
+
},
|
|
20
|
+
/** Replace every value, outside history. */
|
|
21
|
+
reset(next) {
|
|
22
|
+
values.clear();
|
|
23
|
+
for (const [key, value] of Object.entries(next ?? {})) write(values, key, value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Register `kit:setDoc` and answer with the `SceneDoc` that records it. */
|
|
28
|
+
function createSceneDoc(env) {
|
|
29
|
+
const { values } = env;
|
|
30
|
+
env.register(SET_DOC, {
|
|
31
|
+
apply: (p) => {
|
|
32
|
+
const { key, to } = p;
|
|
33
|
+
write(values, key, to);
|
|
34
|
+
},
|
|
35
|
+
revert: (p) => {
|
|
36
|
+
const { key, from } = p;
|
|
37
|
+
write(values, key, from);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
get: (key) => values.get(key),
|
|
42
|
+
set(key, to) {
|
|
43
|
+
const from = values.get(key);
|
|
44
|
+
if (deepEqual(from, to)) return;
|
|
45
|
+
env.record(SET_DOC, {
|
|
46
|
+
key,
|
|
47
|
+
from,
|
|
48
|
+
to
|
|
49
|
+
}, "setDoc");
|
|
50
|
+
},
|
|
51
|
+
keys: () => [...values.keys()]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { createDocStore, createSceneDoc };
|
|
57
|
+
//# sourceMappingURL=docValues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docValues.js","names":[],"sources":["../../../src/core/scene/docValues.ts"],"sourcesContent":["/**\n * Document state that is not a node — a document's pages, its theme — held as\n * named JSON values beside the tree, saved in its snapshot and undone with it.\n */\nimport { deepEqual } from '@weasel-js/registry';\nimport type { RegisteredOp } from './types';\n\n/**\n * A scene's document values. Keys are namespaced by their owner\n * (`core:pages`, `diagram:theme`); values are JSON, and are not to be mutated\n * once set. A `set` is a scene edit: undoable, part of an enclosing `batch`,\n * skipped when the value is deep-equal to the current one, and heard by\n * `subscribe`. Setting `undefined` removes the key.\n */\nexport interface SceneDoc {\n get<T = unknown>(key: string): T | undefined;\n set(key: string, value: unknown): void;\n keys(): readonly string[];\n}\n\n/** The part of `Scene` that carries its document values. */\nexport interface SceneDocValues {\n readonly doc: SceneDoc;\n}\n\n/** A snapshot's document values. */\nexport type SerializedDoc = Readonly<Record<string, unknown>>;\n\n/** `undefined` on either side means the key is absent there. */\ninterface SetDocPayload { key: string; from?: unknown; to?: unknown }\n\nconst SET_DOC = 'kit:setDoc';\n\nfunction write(values: Map<string, unknown>, key: string, to: unknown): void {\n if (to === undefined) values.delete(key);\n else values.set(key, to);\n}\n\n/** The snapshot side of a scene's document values, kept in `values`. */\nexport function createDocStore(values: Map<string, unknown>) {\n return {\n /** The values as a snapshot holds them, or `undefined` when there are none. */\n toJSON(): SerializedDoc | undefined {\n return values.size === 0 ? undefined : Object.fromEntries(values);\n },\n /** Replace every value, outside history. */\n reset(next: SerializedDoc | undefined): void {\n values.clear();\n for (const [key, value] of Object.entries(next ?? {})) write(values, key, value);\n },\n };\n}\n\nexport interface SceneDocEnv {\n values: Map<string, unknown>;\n register(kind: string, handler: RegisteredOp<unknown>): void;\n record(kind: string, payload: unknown, label: string): void;\n}\n\n/** Register `kit:setDoc` and answer with the `SceneDoc` that records it. */\nexport function createSceneDoc(env: SceneDocEnv): SceneDoc {\n const { values } = env;\n env.register(SET_DOC, {\n apply: (p) => { const { key, to } = p as SetDocPayload; write(values, key, to); },\n revert: (p) => { const { key, from } = p as SetDocPayload; write(values, key, from); },\n });\n return {\n get: <T>(key: string) => values.get(key) as T | undefined,\n set(key, to) {\n const from = values.get(key);\n if (deepEqual(from, to)) return;\n env.record(SET_DOC, { key, from, to } satisfies SetDocPayload, 'setDoc');\n },\n keys: () => [...values.keys()],\n };\n}\n"],"mappings":";;;;;;;AA+BA,IAAM,UAAU;AAEhB,SAAS,MAAM,QAA8B,KAAa,IAAmB;CAC3E,IAAI,OAAO,QAAW,OAAO,OAAO,GAAG;MAClC,OAAO,IAAI,KAAK,EAAE;AACzB;;AAGA,SAAgB,eAAe,QAA8B;CAC3D,OAAO;;EAEL,SAAoC;GAClC,OAAO,OAAO,SAAS,IAAI,SAAY,OAAO,YAAY,MAAM;EAClE;;EAEA,MAAM,MAAuC;GAC3C,OAAO,MAAM;GACb,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAAC,CAAC,GAAG,MAAM,QAAQ,KAAK,KAAK;EACjF;CACF;AACF;;AASA,SAAgB,eAAe,KAA4B;CACzD,MAAM,EAAE,WAAW;CACnB,IAAI,SAAS,SAAS;EACpB,QAAQ,MAAM;GAAE,MAAM,EAAE,KAAK,OAAO;GAAoB,MAAM,QAAQ,KAAK,EAAE;EAAG;EAChF,SAAS,MAAM;GAAE,MAAM,EAAE,KAAK,SAAS;GAAoB,MAAM,QAAQ,KAAK,IAAI;EAAG;CACvF,CAAC;CACD,OAAO;EACL,MAAS,QAAgB,OAAO,IAAI,GAAG;EACvC,IAAI,KAAK,IAAI;GACX,MAAM,OAAO,OAAO,IAAI,GAAG;GAC3B,IAAI,UAAU,MAAM,EAAE,GAAG;GACzB,IAAI,OAAO,SAAS;IAAE;IAAK;IAAM;GAAG,GAA2B,QAAQ;EACzE;EACA,YAAY,CAAC,GAAG,OAAO,KAAK,CAAC;CAC/B;AACF"}
|