@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
package/dist/core/scene/scene.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { IDENTITY_POSE_COMPOSITION } from "./composeFrame.js";
|
|
2
2
|
import { effectivePose } from "./effectivePose.js";
|
|
3
3
|
import { asNodeId } from "./types.js";
|
|
4
|
+
import { documentReads, sceneMutators } from "./sceneMutators.js";
|
|
4
5
|
import { withKitRegistry } from "./kitRegistry.js";
|
|
5
6
|
import { createPoseOverrides } from "./poseOverrides.js";
|
|
6
7
|
import { createArrivalWindow } from "./arrivalWindow.js";
|
|
7
8
|
import { createNotifier } from "./notifier.js";
|
|
9
|
+
import { sceneChanges } from "./sceneChanges.js";
|
|
8
10
|
import { createPoseWriteLog } from "./poseWrites.js";
|
|
9
11
|
import { createEditMode } from "./editMode.js";
|
|
10
12
|
import { resolveLayoutFrame, runLayoutPass } from "./layoutPass.js";
|
|
@@ -30,14 +32,15 @@ var defaultGenerateId = () => asNodeId(`n${(defaultIdCounter++).toString(36)}-${
|
|
|
30
32
|
* this in tests, in headless rendering, or wherever a scene must outlive a
|
|
31
33
|
* component. Throws if no system layers are declared. */
|
|
32
34
|
function createScene(options) {
|
|
33
|
-
return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial);
|
|
35
|
+
return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);
|
|
34
36
|
}
|
|
35
|
-
/** A scene holding `layers`,
|
|
36
|
-
* it is handed the registry their functions
|
|
37
|
-
|
|
37
|
+
/** A scene holding `layers`, the document values `initialDoc`, and the nodes
|
|
38
|
+
* `initialNodes` answers with once it is handed the registry their functions
|
|
39
|
+
* resolve against. */
|
|
40
|
+
function buildScene(options, layers, initialNodes, initialDoc) {
|
|
38
41
|
if (layers.length === 0) throw new Error("Scene: systemLayers must declare at least one layer");
|
|
39
42
|
const registry = withKitRegistry(options.registry ?? {});
|
|
40
|
-
const doc = createSceneState(registry, layers, () => arrivals.layoutDeclared());
|
|
43
|
+
const doc = createSceneState(registry, layers, () => arrivals.layoutDeclared(), initialDoc);
|
|
41
44
|
const { state, locks, reads } = doc;
|
|
42
45
|
const notifier = createNotifier();
|
|
43
46
|
const mode = createEditMode();
|
|
@@ -63,6 +66,10 @@ function buildScene(options, layers, initialNodes) {
|
|
|
63
66
|
const recording = createSceneRecording({
|
|
64
67
|
notifier,
|
|
65
68
|
mode,
|
|
69
|
+
passAdapter: () => ({
|
|
70
|
+
...sceneMutators(scene),
|
|
71
|
+
...documentReads(scene)
|
|
72
|
+
}),
|
|
66
73
|
stale: doc.stale,
|
|
67
74
|
lifted: locks.lifted,
|
|
68
75
|
selection,
|
|
@@ -117,14 +124,13 @@ function buildScene(options, layers, initialNodes) {
|
|
|
117
124
|
const specs = specsFromSerialized(json, registry);
|
|
118
125
|
if (mode.open()) throw new Error("Scene: cannot loadState inside another edit — it replaces the state that edit would revert to");
|
|
119
126
|
overrides.clearAll();
|
|
120
|
-
doc.reset(json.systemLayers.map(layerFromSerialized));
|
|
127
|
+
doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);
|
|
121
128
|
recording.clearHistory();
|
|
122
129
|
poseWrites.quiet(() => edits.construct(specs));
|
|
123
130
|
selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));
|
|
124
131
|
notifier.notify();
|
|
125
132
|
},
|
|
126
|
-
|
|
127
|
-
getVersion: notifier.version
|
|
133
|
+
...sceneChanges(notifier, selection)
|
|
128
134
|
};
|
|
129
135
|
const initial = initialNodes(registry);
|
|
130
136
|
if (initial) {
|
|
@@ -139,7 +145,7 @@ function buildScene(options, layers, initialNodes) {
|
|
|
139
145
|
* keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).
|
|
140
146
|
* The scene starts with empty history. */
|
|
141
147
|
function sceneFromJSON(json, options = {}) {
|
|
142
|
-
return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry));
|
|
148
|
+
return buildScene(options, json.systemLayers.map(layerFromSerialized), (registry) => specsFromSerialized(json, registry), json.doc);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { createNotifier } from './notifier';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial'>;\n\n/** A scene holding `layers`, and the nodes `initialNodes` answers with once\n * it is handed the registry their functions resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared());\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void =>\n poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized));\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n subscribe: notifier.subscribe,\n getVersion: notifier.version,\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scene.js","names":[],"sources":["../../../src/core/scene/scene.ts"],"sourcesContent":["import { withKitRegistry } from './kitRegistry';\nimport { createPoseOverrides } from './poseOverrides';\nimport { createArrivalWindow } from './arrivalWindow';\nimport { documentReads, sceneMutators } from './sceneMutators';\nimport { createNotifier } from './notifier';\nimport type { SerializedDoc } from './docValues';\nimport { sceneChanges } from './sceneChanges';\nimport { createPoseWriteLog } from './poseWrites';\nimport { effectivePose } from './effectivePose';\nimport { createEditMode } from './editMode';\nimport { createSceneEdits } from './sceneEdits';\nimport { createSceneRecording } from './sceneRecording';\nimport { createSceneSelection } from './sceneSelection';\nimport { createSceneState } from './sceneState';\nimport { systemLayerRecord } from './layerStack';\nimport { layerFromSerialized, specsFromSerialized } from './serialization';\nimport { resolveLayoutFrame, runLayoutPass } from './layoutPass';\nimport { IDENTITY_POSE_COMPOSITION, type PoseComposition } from './composeFrame';\nimport {\n asNodeId,\n type AddNodeSpec,\n type LayerRecord,\n type NodeId,\n type RegisteredOp,\n type Scene,\n type SceneRegistry,\n type SerializedScene,\n type UseSceneOptions,\n} from './types';\n\n/** A node still in `scene`, with the pose it is shown at. */\nfunction shownNode<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>, id: NodeId) {\n const node = scene.get(id);\n return node && { node, pose: effectivePose(scene, node) };\n}\n\nlet defaultIdCounter = 0;\nconst defaultGenerateId = (): NodeId =>\n asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);\n\n/** Build a scene outside React. `useScene` is the usual entry point; reach for\n * this in tests, in headless rendering, or wherever a scene must outlive a\n * component. Throws if no system layers are declared. */\nexport function createScene<TData, TLayer extends string, TPose = import('./composePose').RectPose>(\n options: UseSceneOptions<TData, TLayer, TPose>,\n): Scene<TData, TLayer, TPose> {\n return buildScene(options, (options.systemLayers ?? []).map(systemLayerRecord), () => options.initial, options.doc);\n}\n\ntype SceneConfig<TData, TLayer extends string, TPose> =\n Omit<UseSceneOptions<TData, TLayer, TPose>, 'systemLayers' | 'initial' | 'doc'>;\n\n/** A scene holding `layers`, the document values `initialDoc`, and the nodes\n * `initialNodes` answers with once it is handed the registry their functions\n * resolve against. */\nfunction buildScene<TData, TLayer extends string, TPose>(\n options: SceneConfig<TData, TLayer, TPose>,\n layers: readonly LayerRecord<TLayer>[],\n initialNodes: (registry: SceneRegistry<TPose, TData, TLayer>) => readonly AddNodeSpec<TData, TLayer, TPose>[] | undefined,\n initialDoc: SerializedDoc | undefined,\n): Scene<TData, TLayer, TPose> {\n if (layers.length === 0) throw new Error('Scene: systemLayers must declare at least one layer');\n\n // The kit's own registry entries sit under the consumer's, so a snapshot\n // carrying a kit-attached function (a grouped container's derived pose)\n // reloads in any scene, not only one whose consumer registered the key.\n const registry = withKitRegistry(options.registry ?? {});\n\n const doc = createSceneState<TData, TLayer, TPose>(registry, layers, () => arrivals.layoutDeclared(), initialDoc);\n const { state, locks, reads } = doc;\n const notifier = createNotifier();\n const mode = createEditMode();\n const poseWrites = createPoseWriteLog({ untracked: mode.untracked, changes: notifier, shown: (id) => shownNode(scene, id) });\n const writePose = (id: string, pose: unknown): void => poseWrites.layout(() => scene.setPose(asNodeId(id), pose as TPose));\n\n const layoutFrame = resolveLayoutFrame(options.layoutFrame);\n const arrivals = createArrivalWindow<TData, TLayer, TPose>({\n nodes: state.nodes,\n layoutPass: (arrived, touched, departed) =>\n runLayoutPass(scene, reads.layoutOf, arrived, touched, layoutFrame, departed),\n recordsBareEdits: () => !mode.open(),\n writePose,\n });\n\n const generateId = options.generateId ?? defaultGenerateId;\n const selection = createSceneSelection(locks.reachable);\n // A selection change is a scene change to every subscriber.\n selection.subscribe(notifier.notify);\n\n /** Ephemeral per-node overrides. Deliberately not part of `state`: not\n * document content, never reaches `toJSON`, and never touches history. */\n const overrides = createPoseOverrides<TPose>((id) => state.nodes.get(id), doc.store.invalidateDependents);\n\n const recording = createSceneRecording({\n notifier,\n mode,\n passAdapter: () => ({ ...sceneMutators(scene), ...documentReads(scene) }),\n stale: doc.stale,\n lifted: locks.lifted,\n selection,\n arrivals,\n writePose,\n poseWrites,\n config: options,\n });\n\n const edits = createSceneEdits<TData, TLayer, TPose>({\n state,\n store: doc.store,\n locks,\n layers: doc.layers,\n invalidateOrder: doc.order.invalidate,\n fnFields: doc.fnFields,\n ops: { ...recording.ops, register: poseWrites.reporting(recording.ops.register) },\n recorder: recording.recorder,\n arrivals,\n layoutFrame,\n generateId,\n narrowSelection: selection.narrow,\n clearOverride: overrides.clear,\n });\n for (const [kind, handler] of Object.entries(options.ops ?? {})) recording.ops.register(kind, handler);\n\n const scene: Scene<TData, TLayer, TPose> = {\n get nodes() { return state.nodes; },\n get roots() { return state.roots; },\n get layers() { return state.layers; },\n registry,\n\n ...reads,\n\n mintId: generateId,\n composition: options.composition ?? (IDENTITY_POSE_COMPOSITION as PoseComposition<TPose>),\n\n ...edits.methods,\n ...recording.methods,\n\n unlocked: (fn) => locks.lifted(fn),\n\n setArrivalHandler: arrivals.setHandler,\n onReflow: arrivals.onReflow,\n onPoseWrite: poseWrites.subscribe,\n\n overrides,\n\n getSelection: selection.get,\n setSelection: (ids) => selection.set([...ids]),\n\n loadState(json) {\n // Validate + map up front (throws before we touch live state on a bad\n // version or unknown registry key).\n const specs = specsFromSerialized(json, registry);\n if (mode.open()) {\n throw new Error('Scene: cannot loadState inside another edit — it replaces the state that edit would revert to');\n }\n // Ephemeral, and every id is a new node after this.\n overrides.clearAll();\n doc.reset(json.systemLayers.map(layerFromSerialized), json.doc);\n recording.clearHistory();\n poseWrites.quiet(() => edits.construct(specs));\n selection.replace(locks.reachable(selection.get().filter((id) => state.nodes.has(id))));\n notifier.notify();\n },\n\n ...sceneChanges(notifier, selection),\n };\n\n const initial = initialNodes(registry);\n if (initial) {\n poseWrites.quiet(() => edits.construct(initial));\n notifier.notify();\n }\n\n return scene;\n}\n\n/** Reconstruct a Scene from a JSON snapshot produced by `scene.toJSON()`.\n * Function fields (e.g., `clipFromPose`) are resolved by string key via the\n * registry passed in `options`. Throws on unknown version, unknown registry\n * keys, or invalid scene shape (cross-layer subtrees, unknown layer ids).\n * The scene starts with empty history. */\nexport function sceneFromJSON<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n options: {\n registry?: SceneRegistry<TPose, TData, TLayer>;\n historyLimit?: number;\n coalesceWindowMs?: number;\n generateId?: () => NodeId;\n ops?: Readonly<Record<string, RegisteredOp<unknown>>>;\n composition?: UseSceneOptions<TData, TLayer, TPose>['composition'];\n layoutFrame?: UseSceneOptions<TData, TLayer, TPose>['layoutFrame'];\n } = {},\n): Scene<TData, TLayer, TPose> {\n return buildScene(\n options,\n json.systemLayers.map(layerFromSerialized),\n (registry) => specsFromSerialized(json, registry),\n json.doc,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/core/scene/sceneChanges.ts
|
|
2
|
+
/** A scene's change members, over its notifier and its selection's own count of changes. */
|
|
3
|
+
function sceneChanges(notifier, selection) {
|
|
4
|
+
return {
|
|
5
|
+
subscribe: notifier.subscribe,
|
|
6
|
+
getVersion: notifier.version,
|
|
7
|
+
getContentVersion: () => notifier.version() - selection.changes()
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { sceneChanges };
|
|
13
|
+
//# sourceMappingURL=sceneChanges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneChanges.js","names":[],"sources":["../../../src/core/scene/sceneChanges.ts"],"sourcesContent":["/** How a scene tells its readers it changed. */\nimport type { Notifier } from './notifier';\n\nexport interface SceneChanges {\n /** Hear every change, a change of selection included. Returns the unsubscribe. */\n subscribe(listener: () => void): () => void;\n /** Monotonically increasing version. Snapshot for `useSyncExternalStore`. */\n getVersion(): number;\n /** {@link SceneChanges.getVersion} less the changes that only moved the selection: unmoved\n * across a notice that changed nothing else. */\n getContentVersion(): number;\n}\n\n/** A scene's change members, over its notifier and its selection's own count of changes. */\nexport function sceneChanges(notifier: Notifier, selection: { changes(): number }): SceneChanges {\n return {\n subscribe: notifier.subscribe,\n getVersion: notifier.version,\n getContentVersion: () => notifier.version() - selection.changes(),\n };\n}\n"],"mappings":";;AAcA,SAAgB,aAAa,UAAoB,WAAgD;CAC/F,OAAO;EACL,WAAW,SAAS;EACpB,YAAY,SAAS;EACrB,yBAAyB,SAAS,QAAQ,IAAI,UAAU,QAAQ;CAClE;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createSceneDoc } from "./docValues.js";
|
|
1
2
|
import { createFieldEdits } from "./fieldEdits.js";
|
|
2
3
|
import { registerKitOps } from "./kitOps.js";
|
|
3
4
|
import { createNodeFlags } from "./nodeFlags.js";
|
|
@@ -55,13 +56,19 @@ function createSceneEdits(env) {
|
|
|
55
56
|
record: recorder.record,
|
|
56
57
|
narrowSelection: () => env.narrowSelection()
|
|
57
58
|
});
|
|
59
|
+
const doc = createSceneDoc({
|
|
60
|
+
values: state.docValues,
|
|
61
|
+
register: env.ops.register,
|
|
62
|
+
record: recorder.record
|
|
63
|
+
});
|
|
58
64
|
return {
|
|
59
65
|
construct,
|
|
60
66
|
methods: {
|
|
61
67
|
...treeEdits,
|
|
62
68
|
...fieldEdits,
|
|
63
69
|
...layerEdits,
|
|
64
|
-
...flagEdits
|
|
70
|
+
...flagEdits,
|
|
71
|
+
doc
|
|
65
72
|
}
|
|
66
73
|
};
|
|
67
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, and
|
|
1
|
+
{"version":3,"file":"sceneEdits.js","names":[],"sources":["../../../src/core/scene/sceneEdits.ts"],"sourcesContent":["/**\n * The scene's edits to its document — tree, node fields, layers, and document values — with\n * the kit ops they record.\n */\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { createSceneDoc, type SceneDocValues } from './docValues';\nimport { createFieldEdits, type FieldEdits } from './fieldEdits';\nimport { registerKitOps } from './kitOps';\nimport { createNodeFlags } from './nodeFlags';\nimport { createLayerEdits, type LayerEdits, type createLayerStack } from './layerStack';\nimport type { ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { OpTable } from './opTable';\nimport type { Recorder } from './recorder';\nimport { createTreeEdits, type TreeEdits } from './treeEdits';\nimport type { LayerRecord, Node, NodeId } from './types';\n\nexport interface SceneEditsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n layerIndex: Map<TLayer, number>;\n /** The scene's document values, which `doc` reads and records. */\n docValues: Map<string, unknown>;\n };\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer' | 'lifted'>;\n layers: Pick<\n ReturnType<typeof createLayerStack<TLayer>>,\n 'requireLayerIndex' | 'rebuildLayerIndex' | 'assertSubtreeLayer'\n >;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteArrival' | 'noteDeparture' | 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n generateId(): NodeId;\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n clearOverride(id: NodeId): void;\n}\n\nexport interface SceneEdits<TData, TLayer extends string, TPose> {\n construct: TreeEdits<TData, TLayer, TPose>['construct'];\n methods: Omit<TreeEdits<TData, TLayer, TPose>, 'construct'>\n & FieldEdits<TData, TLayer, TPose>\n & LayerEdits<TData, TLayer, TPose>\n & ReturnType<typeof createNodeFlags<TData, TLayer, TPose>>\n & SceneDocValues;\n}\n\nexport function createSceneEdits<TData, TLayer extends string, TPose>(\n env: SceneEditsEnv<TData, TLayer, TPose>,\n): SceneEdits<TData, TLayer, TPose> {\n const { state, store, locks, layers, fnFields, recorder, arrivals } = env;\n\n const { layoutRefs } = registerKitOps<TData, TLayer, TPose>({\n state,\n register: env.ops.register,\n store,\n requireLayerIndex: layers.requireLayerIndex,\n rebuildLayerIndex: layers.rebuildLayerIndex,\n invalidateOrder: env.invalidateOrder,\n fnFields,\n narrowSelection: env.narrowSelection,\n clearOverride: env.clearOverride,\n });\n\n const { construct, ...treeEdits } = createTreeEdits<TData, TLayer, TPose>({\n nodes: state.nodes,\n generateId: env.generateId,\n store,\n locks,\n layers,\n fnFields,\n runOp: env.ops.run,\n recorder,\n arrivals,\n });\n\n const fieldEdits = createFieldEdits<TData, TLayer, TPose>({\n store,\n locks,\n layers,\n recorder,\n arrivals,\n layoutFrame: env.layoutFrame,\n layoutField: fnFields.find((f) => f.spec.field === 'layout')!,\n layoutRefs,\n });\n\n const layerEdits = createLayerEdits<TData, TLayer, TPose>({\n layers: state.layers,\n layerIndex: state.layerIndex,\n nodes: state.nodes,\n requireLayerIndex: layers.requireLayerIndex,\n record: recorder.record,\n lifted: locks.lifted,\n batch: recorder.batch,\n removeMany: treeEdits.removeMany,\n });\n\n const flagEdits = createNodeFlags<TData, TLayer, TPose>({\n register: env.ops.register,\n requireNode: store.requireNode,\n record: recorder.record,\n narrowSelection: () => env.narrowSelection(),\n });\n\n const doc = createSceneDoc({ values: state.docValues, register: env.ops.register, record: recorder.record });\n\n return { construct, methods: { ...treeEdits, ...fieldEdits, ...layerEdits, ...flagEdits, doc } };\n}\n"],"mappings":";;;;;;;;AAqDA,SAAgB,iBACd,KACkC;CAClC,MAAM,EAAE,OAAO,OAAO,OAAO,QAAQ,UAAU,UAAU,aAAa;CAEtE,MAAM,EAAE,eAAe,eAAqC;EAC1D;EACA,UAAU,IAAI,IAAI;EAClB;EACA,mBAAmB,OAAO;EAC1B,mBAAmB,OAAO;EAC1B,iBAAiB,IAAI;EACrB;EACA,iBAAiB,IAAI;EACrB,eAAe,IAAI;CACrB,CAAC;CAED,MAAM,EAAE,WAAW,GAAG,cAAc,gBAAsC;EACxE,OAAO,MAAM;EACb,YAAY,IAAI;EAChB;EACA;EACA;EACA;EACA,OAAO,IAAI,IAAI;EACf;EACA;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD;EACA;EACA;EACA;EACA;EACA,aAAa,IAAI;EACjB,aAAa,SAAS,MAAM,MAAM,EAAE,KAAK,UAAU,QAAQ;EAC3D;CACF,CAAC;CAED,MAAM,aAAa,iBAAuC;EACxD,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,mBAAmB,OAAO;EAC1B,QAAQ,SAAS;EACjB,QAAQ,MAAM;EACd,OAAO,SAAS;EAChB,YAAY,UAAU;CACxB,CAAC;CAED,MAAM,YAAY,gBAAsC;EACtD,UAAU,IAAI,IAAI;EAClB,aAAa,MAAM;EACnB,QAAQ,SAAS;EACjB,uBAAuB,IAAI,gBAAgB;CAC7C,CAAC;CAED,MAAM,MAAM,eAAe;EAAE,QAAQ,MAAM;EAAW,UAAU,IAAI,IAAI;EAAU,QAAQ,SAAS;CAAO,CAAC;CAE3G,OAAO;EAAE;EAAW,SAAS;GAAE,GAAG;GAAW,GAAG;GAAY,GAAG;GAAY,GAAG;GAAW;EAAI;CAAE;AACjG"}
|
|
@@ -23,6 +23,7 @@ function sceneMutators(scene) {
|
|
|
23
23
|
},
|
|
24
24
|
setData: (id, data) => scene.update(asNodeId(id), { data }),
|
|
25
25
|
setLayer: (id, layer) => scene.setLayer(asNodeId(id), layer),
|
|
26
|
+
setDependsOn: (id, dependsOn) => scene.setDependsOn(asNodeId(id), Array.isArray(dependsOn) ? dependsOn.map(asNodeId) : dependsOn),
|
|
26
27
|
setLayout: (id, layout) => scene.setLayout(asNodeId(id), layout),
|
|
27
28
|
setHidden: (id, hidden) => scene.setHidden(asNodeId(id), hidden),
|
|
28
29
|
setLocked: (id, locked) => scene.setLocked(asNodeId(id), locked),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneMutators.js","names":[],"sources":["../../../src/core/scene/sceneMutators.ts"],"sourcesContent":["/**\n * What every scene-backed adapter shares: the writes an op applies through,\n * and the structural reads those ops need. `sceneToAdapter`,\n * `defaultCommitAdapter`, and `moveGestureAdapter` all build on it.\n *\n * It leaves out the pose and node-list reads, where the adapters differ on\n * purpose. The canvas adapter answers with what is shown: the effective pose,\n * visible layers only. An adapter ops commit through answers with what the\n * document holds, which is {@link documentReads}.\n */\nimport type { LayoutStrategy } from '../../layout/types';\nimport { documentPose } from './effectivePose';\nimport { fnFieldsOfNode } from './nodeFnFields';\nimport { statedFields } from './statedFields';\nimport { asNodeId, type Node, type Scene } from './types';\n\nexport interface SceneMutators<TData, TLayer extends string, TPose> {\n getNode(id: string): Node<TData, TLayer, TPose> | undefined;\n getParent(id: string): string | null;\n /** Ordered children of `parentId`; `null` names the root siblings. */\n getChildren(parentId: string | null): string[];\n /** Every id removing `ids` takes: their subtrees and their dependents. */\n getRemovalClosure(ids: readonly string[]): string[];\n setPose(id: string, pose: TPose): void;\n setParent(id: string, parentId: string | null): void;\n /** One undo step when called bare; inside a batch it joins that batch. */\n setChildOrder(parentId: string | null, ids: string[]): void;\n setData(id: string, data: TData): void;\n setLayer(id: string, layer: TLayer): void;\n setLayout(id: string, layout: LayoutStrategy<TPose> | string | null): void;\n /** What a `setHidden` op writes through. */\n setHidden(id: string, hidden: boolean): void;\n /** What a `setLocked` op writes through. */\n setLocked(id: string, locked: boolean): void;\n removeNode(id: string): void;\n /** Re-add `node` as it was, at sibling `index` when one is given. */\n insertNode(node: Node<TData, TLayer, TPose>, index?: number): void;\n}\n\nexport function sceneMutators<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): SceneMutators<TData, TLayer, TPose> {\n const getChildren = (parentId: string | null): string[] =>\n (parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))]);\n return {\n getNode: (id) => scene.get(asNodeId(id)),\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n getChildren,\n getRemovalClosure: (ids) => [...scene.removalClosure(ids.map(asNodeId))],\n setPose: (id, pose) => scene.setPose(asNodeId(id), pose),\n setParent: (id, parentId) => scene.move(asNodeId(id), parentId == null ? null : asNodeId(parentId)),\n setChildOrder: (parentId, ids) => {\n scene.batch('Reorder', () => {\n for (let i = 0; i < ids.length; i++) {\n // Re-read each time: a reorder shifts later ids, sometimes into place.\n if (getChildren(parentId)[i] === ids[i]) continue;\n scene.reorder(asNodeId(ids[i]), i);\n }\n });\n },\n setData: (id, data) => scene.update(asNodeId(id), { data }),\n setLayer: (id, layer) => scene.setLayer(asNodeId(id), layer),\n setLayout: (id, layout) => scene.setLayout(asNodeId(id), layout),\n setHidden: (id, hidden) => scene.setHidden(asNodeId(id), hidden),\n setLocked: (id, locked) => scene.setLocked(asNodeId(id), locked),\n removeNode: (id) => scene.remove(asNodeId(id)),\n insertNode: (node, index) => {\n scene.add({\n kind: node.kind,\n layer: node.layer,\n pose: node.pose,\n data: node.data,\n id: node.id,\n ...(index !== undefined ? { index } : {}),\n ...(node.parent != null ? { parent: node.parent } : {}),\n // Derivation travels with the node, or undo of a delete brings an\n // edge back as a static path that never follows its endpoints again.\n ...(node.dependsOn !== undefined ? { dependsOn: node.dependsOn } : {}),\n ...statedFields(node),\n ...fnFieldsOfNode(node),\n });\n },\n };\n}\n\n/** The pose and node-list reads of an adapter ops commit through: the document,\n * whatever a gesture is showing and whichever layers are hidden. */\nexport function documentReads<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): { getPose(id: string): TPose; getNodes(): Node<TData, TLayer, TPose>[] } {\n return {\n getPose: (id) => documentPose(scene, scene.get(asNodeId(id))!),\n getNodes: () => [...scene.renderOrderNodes()],\n };\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"sceneMutators.js","names":[],"sources":["../../../src/core/scene/sceneMutators.ts"],"sourcesContent":["/**\n * What every scene-backed adapter shares: the writes an op applies through,\n * and the structural reads those ops need. `sceneToAdapter`,\n * `defaultCommitAdapter`, and `moveGestureAdapter` all build on it.\n *\n * It leaves out the pose and node-list reads, where the adapters differ on\n * purpose. The canvas adapter answers with what is shown: the effective pose,\n * visible layers only. An adapter ops commit through answers with what the\n * document holds, which is {@link documentReads}.\n */\nimport type { LayoutStrategy } from '../../layout/types';\nimport { documentPose } from './effectivePose';\nimport { fnFieldsOfNode } from './nodeFnFields';\nimport { statedFields } from './statedFields';\nimport { asNodeId, type Node, type Scene } from './types';\n\nexport interface SceneMutators<TData, TLayer extends string, TPose> {\n getNode(id: string): Node<TData, TLayer, TPose> | undefined;\n getParent(id: string): string | null;\n /** Ordered children of `parentId`; `null` names the root siblings. */\n getChildren(parentId: string | null): string[];\n /** Every id removing `ids` takes: their subtrees and their dependents. */\n getRemovalClosure(ids: readonly string[]): string[];\n setPose(id: string, pose: TPose): void;\n setParent(id: string, parentId: string | null): void;\n /** One undo step when called bare; inside a batch it joins that batch. */\n setChildOrder(parentId: string | null, ids: string[]): void;\n setData(id: string, data: TData): void;\n setLayer(id: string, layer: TLayer): void;\n /** What a `setDependsOn` op writes through. */\n setDependsOn(id: string, dependsOn: readonly string[] | 'children' | undefined): void;\n setLayout(id: string, layout: LayoutStrategy<TPose> | string | null): void;\n /** What a `setHidden` op writes through. */\n setHidden(id: string, hidden: boolean): void;\n /** What a `setLocked` op writes through. */\n setLocked(id: string, locked: boolean): void;\n removeNode(id: string): void;\n /** Re-add `node` as it was, at sibling `index` when one is given. */\n insertNode(node: Node<TData, TLayer, TPose>, index?: number): void;\n}\n\nexport function sceneMutators<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): SceneMutators<TData, TLayer, TPose> {\n const getChildren = (parentId: string | null): string[] =>\n (parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))]);\n return {\n getNode: (id) => scene.get(asNodeId(id)),\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n getChildren,\n getRemovalClosure: (ids) => [...scene.removalClosure(ids.map(asNodeId))],\n setPose: (id, pose) => scene.setPose(asNodeId(id), pose),\n setParent: (id, parentId) => scene.move(asNodeId(id), parentId == null ? null : asNodeId(parentId)),\n setChildOrder: (parentId, ids) => {\n scene.batch('Reorder', () => {\n for (let i = 0; i < ids.length; i++) {\n // Re-read each time: a reorder shifts later ids, sometimes into place.\n if (getChildren(parentId)[i] === ids[i]) continue;\n scene.reorder(asNodeId(ids[i]), i);\n }\n });\n },\n setData: (id, data) => scene.update(asNodeId(id), { data }),\n setLayer: (id, layer) => scene.setLayer(asNodeId(id), layer),\n setDependsOn: (id, dependsOn) =>\n scene.setDependsOn(asNodeId(id), Array.isArray(dependsOn) ? dependsOn.map(asNodeId) : dependsOn as 'children' | undefined),\n setLayout: (id, layout) => scene.setLayout(asNodeId(id), layout),\n setHidden: (id, hidden) => scene.setHidden(asNodeId(id), hidden),\n setLocked: (id, locked) => scene.setLocked(asNodeId(id), locked),\n removeNode: (id) => scene.remove(asNodeId(id)),\n insertNode: (node, index) => {\n scene.add({\n kind: node.kind,\n layer: node.layer,\n pose: node.pose,\n data: node.data,\n id: node.id,\n ...(index !== undefined ? { index } : {}),\n ...(node.parent != null ? { parent: node.parent } : {}),\n // Derivation travels with the node, or undo of a delete brings an\n // edge back as a static path that never follows its endpoints again.\n ...(node.dependsOn !== undefined ? { dependsOn: node.dependsOn } : {}),\n ...statedFields(node),\n ...fnFieldsOfNode(node),\n });\n },\n };\n}\n\n/** The pose and node-list reads of an adapter ops commit through: the document,\n * whatever a gesture is showing and whichever layers are hidden. */\nexport function documentReads<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n): { getPose(id: string): TPose; getNodes(): Node<TData, TLayer, TPose>[] } {\n return {\n getPose: (id) => documentPose(scene, scene.get(asNodeId(id))!),\n getNodes: () => [...scene.renderOrderNodes()],\n };\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,cACd,OACqC;CACrC,MAAM,eAAe,aAClB,aAAa,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,WAAW,SAAS,QAAQ,CAAC,CAAC;CAClF,OAAO;EACL,UAAU,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC;EACvC,YAAY,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE,UAAU;EACtD;EACA,oBAAoB,QAAQ,CAAC,GAAG,MAAM,eAAe,IAAI,IAAI,QAAQ,CAAC,CAAC;EACvE,UAAU,IAAI,SAAS,MAAM,QAAQ,SAAS,EAAE,GAAG,IAAI;EACvD,YAAY,IAAI,aAAa,MAAM,KAAK,SAAS,EAAE,GAAG,YAAY,OAAO,OAAO,SAAS,QAAQ,CAAC;EAClG,gBAAgB,UAAU,QAAQ;GAChC,MAAM,MAAM,iBAAiB;IAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;KAEnC,IAAI,YAAY,QAAQ,CAAC,CAAC,OAAO,IAAI,IAAI;KACzC,MAAM,QAAQ,SAAS,IAAI,EAAE,GAAG,CAAC;IACnC;GACF,CAAC;EACH;EACA,UAAU,IAAI,SAAS,MAAM,OAAO,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC;EAC1D,WAAW,IAAI,UAAU,MAAM,SAAS,SAAS,EAAE,GAAG,KAAK;EAC3D,eAAe,IAAI,cACjB,MAAM,aAAa,SAAS,EAAE,GAAG,MAAM,QAAQ,SAAS,IAAI,UAAU,IAAI,QAAQ,IAAI,SAAmC;EAC3H,YAAY,IAAI,WAAW,MAAM,UAAU,SAAS,EAAE,GAAG,MAAM;EAC/D,YAAY,IAAI,WAAW,MAAM,UAAU,SAAS,EAAE,GAAG,MAAM;EAC/D,YAAY,IAAI,WAAW,MAAM,UAAU,SAAS,EAAE,GAAG,MAAM;EAC/D,aAAa,OAAO,MAAM,OAAO,SAAS,EAAE,CAAC;EAC7C,aAAa,MAAM,UAAU;GAC3B,MAAM,IAAI;IACR,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX,IAAI,KAAK;IACT,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;IACvC,GAAI,KAAK,UAAU,OAAO,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;IAGrD,GAAI,KAAK,cAAc,SAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;IACpE,GAAG,aAAa,IAAI;IACpB,GAAG,eAAe,IAAI;GACxB,CAAC;EACH;CACF;AACF;;;AAIA,SAAgB,cACd,OAC0E;CAC1E,OAAO;EACL,UAAU,OAAO,aAAa,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC,CAAE;EAC7D,gBAAgB,CAAC,GAAG,MAAM,iBAAiB,CAAC;CAC9C;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHistory } from "../ops/createHistory.js";
|
|
2
2
|
import { createAppliedLog } from "./appliedLog.js";
|
|
3
|
+
import { createEditPasses } from "./editPass.js";
|
|
3
4
|
import { createSceneHistory } from "./historyFacade.js";
|
|
4
5
|
import { createOpTable } from "./opTable.js";
|
|
5
6
|
import { createOuterOps } from "./outerOps.js";
|
|
@@ -9,6 +10,20 @@ import { createRecorder } from "./recorder.js";
|
|
|
9
10
|
function createSceneRecording(env) {
|
|
10
11
|
const { notifier, mode, stale, lifted, selection, arrivals, config, poseWrites } = env;
|
|
11
12
|
const { notify } = notifier;
|
|
13
|
+
const passes = createEditPasses({
|
|
14
|
+
adapter: env.passAdapter,
|
|
15
|
+
untracked: mode.untracked
|
|
16
|
+
});
|
|
17
|
+
/** Run an op-driven edit with its arrivals settled and its passes run at
|
|
18
|
+
* its end, in the same entry, when this call owns them. */
|
|
19
|
+
const runEdit = (ops, run) => arrivals.runEdit(ops, (all) => {
|
|
20
|
+
if (!passes.open()) return run(all);
|
|
21
|
+
try {
|
|
22
|
+
run([...all, passes.op()]);
|
|
23
|
+
} finally {
|
|
24
|
+
passes.abandon();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
12
27
|
const ops = createOpTable({
|
|
13
28
|
driven: mode.open,
|
|
14
29
|
replayAlone: (step) => {
|
|
@@ -51,6 +66,7 @@ function createSceneRecording(env) {
|
|
|
51
66
|
makeOp: ops.op,
|
|
52
67
|
log,
|
|
53
68
|
arrivals,
|
|
69
|
+
passes,
|
|
54
70
|
selection: selection.get,
|
|
55
71
|
writePose: env.writePose,
|
|
56
72
|
labeled: poseWrites.labeled
|
|
@@ -72,7 +88,7 @@ function createSceneRecording(env) {
|
|
|
72
88
|
replay,
|
|
73
89
|
notify,
|
|
74
90
|
refuseUntracked,
|
|
75
|
-
runArrivalEdit:
|
|
91
|
+
runArrivalEdit: runEdit,
|
|
76
92
|
labeled: (label, fn) => poseWrites.labeled(label, "ops", fn),
|
|
77
93
|
activeJournal: outer.activeJournal
|
|
78
94
|
});
|
|
@@ -90,14 +106,15 @@ function createSceneRecording(env) {
|
|
|
90
106
|
},
|
|
91
107
|
applyBatch(batch, label, adapter) {
|
|
92
108
|
refuseUntracked("applyBatch");
|
|
93
|
-
return poseWrites.labeled(label, "ops", () =>
|
|
109
|
+
return poseWrites.labeled(label, "ops", () => runEdit(batch, (all) => outer.apply(engine, all, label, adapter)));
|
|
94
110
|
},
|
|
95
111
|
history,
|
|
96
112
|
...methods,
|
|
97
113
|
batch: recorder.batch,
|
|
98
114
|
untracked: recorder.untracked,
|
|
99
115
|
setActiveJournalAccessor: outer.setActiveJournalAccessor,
|
|
100
|
-
setHistoryAdapter: outer.setHistoryAdapter
|
|
116
|
+
setHistoryAdapter: outer.setHistoryAdapter,
|
|
117
|
+
addEditPass: passes.add
|
|
101
118
|
}
|
|
102
119
|
};
|
|
103
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneRecording.js","names":[],"sources":["../../../src/core/scene/sceneRecording.ts"],"sourcesContent":["/**\n * How a scene's edits reach its undo stack: the op table, the log that takes\n * back an edit refused partway, the engine, and the recorder between them.\n */\nimport type { Journal } from '@weasel-js/history';\nimport { createHistory } from 'core/ops/createHistory';\nimport { createAppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { EditMode } from './editMode';\nimport { createSceneHistory, type SceneHistoryMethods } from './historyFacade';\nimport type { Notifier } from './notifier';\nimport type { PoseWriteLog } from './poseWrites';\nimport { createOpTable, type OpTable } from './opTable';\nimport { createOuterOps } from './outerOps';\nimport { createRecorder, type Recorder, type RecorderEnv } from './recorder';\nimport type { SceneSelection } from './sceneSelection';\nimport type { StaleReplay } from './staleReplay';\nimport type { NodeId, RegisteredOp, Scene } from './types';\n\nexport interface SceneRecordingEnv {\n notifier: Notifier;\n mode: EditMode;\n stale: StaleReplay;\n /** Run with the lock guard lifted. */\n lifted<T>(fn: () => T): T;\n selection: Pick<SceneSelection, 'get' | 'set' | 'replace'>;\n arrivals: RecorderEnv['arrivals'] & Pick<ArrivalWindow<unknown>, 'runEdit'>;\n writePose(id: string, pose: unknown): void;\n poseWrites: Pick<PoseWriteLog<unknown>, 'labeled' | 'replaying'>;\n config: {\n getActiveJournal?: (() => Journal | null) | undefined;\n coalesceWindowMs?: number | undefined;\n historyLimit?: number | undefined;\n };\n}\n\nexport interface SceneRecording {\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Recorder;\n clearHistory(): void;\n methods: SceneHistoryMethods & Pick<\n Scene<unknown, string, unknown>,\n | 'registerOp' | 'recordOp' | 'applyBatch' | 'history' | 'batch' | 'untracked'\n | 'setActiveJournalAccessor' | 'setHistoryAdapter'\n >;\n}\n\nexport function createSceneRecording(env: SceneRecordingEnv): SceneRecording {\n const { notifier, mode, stale, lifted, selection, arrivals, config, poseWrites } = env;\n const { notify } = notifier;\n\n const ops = createOpTable({\n driven: mode.open,\n replayAlone: (step) => { replay(step); notify(); },\n skips: stale.skips,\n // Only a replay is logged: a first apply is recorded by the entry it\n // creates, and reverting it would leave that entry describing nothing.\n applied: (kind, payload, reverted) => { if (!mode.records()) logApplied(kind, payload, reverted); },\n });\n\n const outer = createOuterOps({\n own: (name, args) => (ops.handlers.has(name) ? ops.op(name, args) : null),\n recorder: () => recorder,\n // Deferred rather than muted, so a throwing op cannot strand subscribers\n // after earlier ops in the batch already mutated state.\n driving: (step) => notifier.deferred(() => mode.during('engine', () => atomically(step, () => true))),\n getActiveJournal: config.getActiveJournal ?? null,\n });\n\n // The scene's undo/redo engine. Its recorded ops close over their payloads,\n // so the engine's adapter argument is unused.\n const engine = createHistory(undefined, {\n selection: { get: selection.get, set: (ids) => selection.set(ids as readonly NodeId[]) },\n ...(config.coalesceWindowMs !== undefined ? { coalesceWindowMs: config.coalesceWindowMs } : {}),\n ...(config.historyLimit !== undefined ? { historyLimit: config.historyLimit } : {}),\n rebuildOp: outer.rebuild,\n });\n\n const log = createAppliedLog({\n registered: ops.handlers,\n lifted,\n getSelection: selection.get,\n setSelection: selection.replace,\n notify,\n });\n const { atomically, logApplied } = log;\n\n const recorder = createRecorder({\n notifier,\n mode,\n engine,\n runOp: ops.run,\n makeOp: ops.op,\n log,\n arrivals,\n selection: selection.get,\n writePose: env.writePose,\n labeled: poseWrites.labeled,\n });\n const { record, engineDriven } = recorder;\n\n /** Run an engine step that replays recorded ops — undo, redo, goto, a\n * journal stepping. The engine inverts whole ops when one throws; this\n * also reverts the scene writes of the op that threw partway. */\n function replay(step: () => void): void {\n poseWrites.replaying(() => atomically(() => lifted(() => stale.during(() => engineDriven(step))), () => true));\n }\n\n function refuseUntracked(verb: string): void {\n if (mode.untracked()) throw new Error(`Scene: cannot ${verb} inside untracked() — it records`);\n }\n\n const { history, methods } = createSceneHistory({\n engine,\n applying: (step) => atomically(() => engineDriven(step), () => true),\n causing: outer.causing,\n replay,\n notify,\n refuseUntracked,\n runArrivalEdit:
|
|
1
|
+
{"version":3,"file":"sceneRecording.js","names":[],"sources":["../../../src/core/scene/sceneRecording.ts"],"sourcesContent":["/**\n * How a scene's edits reach its undo stack: the op table, the log that takes\n * back an edit refused partway, the engine, and the recorder between them.\n */\nimport type { Journal } from '@weasel-js/history';\nimport type { Op } from 'core/ops/types';\nimport { createHistory } from 'core/ops/createHistory';\nimport { createAppliedLog } from './appliedLog';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport type { EditMode } from './editMode';\nimport { createEditPasses } from './editPass';\nimport { createSceneHistory, type SceneHistoryMethods } from './historyFacade';\nimport type { Notifier } from './notifier';\nimport type { PoseWriteLog } from './poseWrites';\nimport { createOpTable, type OpTable } from './opTable';\nimport { createOuterOps } from './outerOps';\nimport { createRecorder, type Recorder, type RecorderEnv } from './recorder';\nimport type { SceneSelection } from './sceneSelection';\nimport type { StaleReplay } from './staleReplay';\nimport type { NodeId, RegisteredOp, Scene } from './types';\n\nexport interface SceneRecordingEnv {\n notifier: Notifier;\n mode: EditMode;\n stale: StaleReplay;\n /** Run with the lock guard lifted. */\n lifted<T>(fn: () => T): T;\n selection: Pick<SceneSelection, 'get' | 'set' | 'replace'>;\n arrivals: RecorderEnv['arrivals'] & Pick<ArrivalWindow<unknown>, 'runEdit'>;\n /** The adapter an edit pass's ops apply through. */\n passAdapter(): unknown;\n writePose(id: string, pose: unknown): void;\n poseWrites: Pick<PoseWriteLog<unknown>, 'labeled' | 'replaying'>;\n config: {\n getActiveJournal?: (() => Journal | null) | undefined;\n coalesceWindowMs?: number | undefined;\n historyLimit?: number | undefined;\n };\n}\n\nexport interface SceneRecording {\n ops: Pick<OpTable, 'register' | 'run'>;\n recorder: Recorder;\n clearHistory(): void;\n methods: SceneHistoryMethods & Pick<\n Scene<unknown, string, unknown>,\n | 'registerOp' | 'recordOp' | 'applyBatch' | 'history' | 'batch' | 'untracked'\n | 'setActiveJournalAccessor' | 'setHistoryAdapter' | 'addEditPass'\n >;\n}\n\nexport function createSceneRecording(env: SceneRecordingEnv): SceneRecording {\n const { notifier, mode, stale, lifted, selection, arrivals, config, poseWrites } = env;\n const { notify } = notifier;\n const passes = createEditPasses({ adapter: env.passAdapter, untracked: mode.untracked });\n\n /** Run an op-driven edit with its arrivals settled and its passes run at\n * its end, in the same entry, when this call owns them. */\n const runEdit = (ops: Op[], run: (ops: Op[]) => void): boolean => arrivals.runEdit(ops, (all) => {\n if (!passes.open()) return run(all);\n try {\n run([...all, passes.op()]);\n } finally {\n passes.abandon();\n }\n });\n\n const ops = createOpTable({\n driven: mode.open,\n replayAlone: (step) => { replay(step); notify(); },\n skips: stale.skips,\n // Only a replay is logged: a first apply is recorded by the entry it\n // creates, and reverting it would leave that entry describing nothing.\n applied: (kind, payload, reverted) => { if (!mode.records()) logApplied(kind, payload, reverted); },\n });\n\n const outer = createOuterOps({\n own: (name, args) => (ops.handlers.has(name) ? ops.op(name, args) : null),\n recorder: () => recorder,\n // Deferred rather than muted, so a throwing op cannot strand subscribers\n // after earlier ops in the batch already mutated state.\n driving: (step) => notifier.deferred(() => mode.during('engine', () => atomically(step, () => true))),\n getActiveJournal: config.getActiveJournal ?? null,\n });\n\n // The scene's undo/redo engine. Its recorded ops close over their payloads,\n // so the engine's adapter argument is unused.\n const engine = createHistory(undefined, {\n selection: { get: selection.get, set: (ids) => selection.set(ids as readonly NodeId[]) },\n ...(config.coalesceWindowMs !== undefined ? { coalesceWindowMs: config.coalesceWindowMs } : {}),\n ...(config.historyLimit !== undefined ? { historyLimit: config.historyLimit } : {}),\n rebuildOp: outer.rebuild,\n });\n\n const log = createAppliedLog({\n registered: ops.handlers,\n lifted,\n getSelection: selection.get,\n setSelection: selection.replace,\n notify,\n });\n const { atomically, logApplied } = log;\n\n const recorder = createRecorder({\n notifier,\n mode,\n engine,\n runOp: ops.run,\n makeOp: ops.op,\n log,\n arrivals,\n passes,\n selection: selection.get,\n writePose: env.writePose,\n labeled: poseWrites.labeled,\n });\n const { record, engineDriven } = recorder;\n\n /** Run an engine step that replays recorded ops — undo, redo, goto, a\n * journal stepping. The engine inverts whole ops when one throws; this\n * also reverts the scene writes of the op that threw partway. */\n function replay(step: () => void): void {\n poseWrites.replaying(() => atomically(() => lifted(() => stale.during(() => engineDriven(step))), () => true));\n }\n\n function refuseUntracked(verb: string): void {\n if (mode.untracked()) throw new Error(`Scene: cannot ${verb} inside untracked() — it records`);\n }\n\n const { history, methods } = createSceneHistory({\n engine,\n applying: (step) => atomically(() => engineDriven(step), () => true),\n causing: outer.causing,\n replay,\n notify,\n refuseUntracked,\n runArrivalEdit: runEdit,\n labeled: (label, fn) => poseWrites.labeled(label, 'ops', fn),\n activeJournal: outer.activeJournal,\n });\n\n return {\n ops,\n recorder,\n clearHistory: () => engine.clear(),\n methods: {\n registerOp(kind, handler) {\n if (kind.startsWith('kit:')) {\n throw new Error(`Scene: op kind \"${kind}\" is reserved (kit:* prefix)`);\n }\n ops.register(kind, handler as RegisteredOp<unknown>);\n },\n recordOp(op) {\n record(op.kind, op.payload, op.kind);\n },\n applyBatch(batch, label, adapter) {\n refuseUntracked('applyBatch');\n return poseWrites.labeled(label, 'ops', () =>\n runEdit(batch, (all) => outer.apply(engine, all, label, adapter)));\n },\n history,\n ...methods,\n batch: recorder.batch,\n untracked: recorder.untracked,\n setActiveJournalAccessor: outer.setActiveJournalAccessor,\n setHistoryAdapter: outer.setHistoryAdapter,\n addEditPass: passes.add,\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -3,11 +3,13 @@ import { createEmitter } from "@weasel-js/registry";
|
|
|
3
3
|
//#region src/core/scene/sceneSelection.ts
|
|
4
4
|
function createSceneSelection(reachable) {
|
|
5
5
|
let ids = [];
|
|
6
|
+
let changes = 0;
|
|
6
7
|
const changed = createEmitter();
|
|
7
8
|
return {
|
|
8
9
|
get: () => ids,
|
|
9
10
|
set(next) {
|
|
10
11
|
ids = reachable(next);
|
|
12
|
+
changes++;
|
|
11
13
|
changed.emit();
|
|
12
14
|
},
|
|
13
15
|
narrow(gone) {
|
|
@@ -16,7 +18,8 @@ function createSceneSelection(reachable) {
|
|
|
16
18
|
replace(next) {
|
|
17
19
|
ids = next;
|
|
18
20
|
},
|
|
19
|
-
subscribe: changed.subscribe
|
|
21
|
+
subscribe: changed.subscribe,
|
|
22
|
+
changes: () => changes
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
/** The scene's selection in the shape `createHistory` wants, for a consumer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneSelection.js","names":[],"sources":["../../../src/core/scene/sceneSelection.ts"],"sourcesContent":["/**\n * The scene's selection: the transient set of active ids. Not document\n * content, so it never reaches `toJSON`; it does ride on history entries.\n */\nimport type { HistorySelection } from '@weasel-js/history';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { NodeId } from './types';\n\nexport interface SceneSelection {\n get(): readonly NodeId[];\n /** Select the ids of `ids` that are in reach, and tell subscribers. */\n set(ids: readonly NodeId[]): void;\n /** Drop the ids in `gone`, and any a lock has put out of reach. Tells no\n * one: the edit that took them is what subscribers hear about. */\n narrow(gone?: ReadonlySet<NodeId>): void;\n /** Put a selection in place exactly as given, telling no one: for a caller\n * returning what it read earlier, or one that has already filtered. */\n replace(ids: readonly NodeId[]): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createSceneSelection(\n /** `ids` less those out of reach; the same array when none is. */\n reachable: (ids: readonly NodeId[]) => readonly NodeId[],\n): SceneSelection {\n let ids: readonly NodeId[] = [];\n const changed = createEmitter();\n\n return {\n get: () => ids,\n set(next) {\n ids = reachable(next);\n changed.emit();\n },\n narrow(gone) {\n // A new array only when one is dropped: readers compare the selection by identity.\n const kept = gone !== undefined && ids.some((id) => gone.has(id)) ? ids.filter((id) => !gone.has(id)) : ids;\n ids = reachable(kept);\n },\n replace(next) {\n ids = next;\n },\n subscribe: changed.subscribe,\n };\n}\n\n/** The scene's selection in the shape `createHistory` wants, for a consumer\n * building a second History over the same scene (a modality machine, a\n * journal owner) that should restore selection the way the scene's own\n * history does. */\nexport function sceneSelectionStore(scene: {\n getSelection(): readonly NodeId[];\n setSelection(ids: readonly NodeId[]): void;\n}): HistorySelection {\n return {\n get: () => scene.getSelection(),\n set: (ids) => scene.setSelection(ids as readonly NodeId[]),\n };\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"sceneSelection.js","names":[],"sources":["../../../src/core/scene/sceneSelection.ts"],"sourcesContent":["/**\n * The scene's selection: the transient set of active ids. Not document\n * content, so it never reaches `toJSON`; it does ride on history entries.\n */\nimport type { HistorySelection } from '@weasel-js/history';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { NodeId } from './types';\n\nexport interface SceneSelection {\n get(): readonly NodeId[];\n /** Select the ids of `ids` that are in reach, and tell subscribers. */\n set(ids: readonly NodeId[]): void;\n /** Drop the ids in `gone`, and any a lock has put out of reach. Tells no\n * one: the edit that took them is what subscribers hear about. */\n narrow(gone?: ReadonlySet<NodeId>): void;\n /** Put a selection in place exactly as given, telling no one: for a caller\n * returning what it read earlier, or one that has already filtered. */\n replace(ids: readonly NodeId[]): void;\n subscribe(listener: () => void): () => void;\n /** How many times subscribers have been told the selection changed. */\n changes(): number;\n}\n\nexport function createSceneSelection(\n /** `ids` less those out of reach; the same array when none is. */\n reachable: (ids: readonly NodeId[]) => readonly NodeId[],\n): SceneSelection {\n let ids: readonly NodeId[] = [];\n let changes = 0;\n const changed = createEmitter();\n\n return {\n get: () => ids,\n set(next) {\n ids = reachable(next);\n changes++;\n changed.emit();\n },\n narrow(gone) {\n // A new array only when one is dropped: readers compare the selection by identity.\n const kept = gone !== undefined && ids.some((id) => gone.has(id)) ? ids.filter((id) => !gone.has(id)) : ids;\n ids = reachable(kept);\n },\n replace(next) {\n ids = next;\n },\n subscribe: changed.subscribe,\n changes: () => changes,\n };\n}\n\n/** The scene's selection in the shape `createHistory` wants, for a consumer\n * building a second History over the same scene (a modality machine, a\n * journal owner) that should restore selection the way the scene's own\n * history does. */\nexport function sceneSelectionStore(scene: {\n getSelection(): readonly NodeId[];\n setSelection(ids: readonly NodeId[]): void;\n}): HistorySelection {\n return {\n get: () => scene.getSelection(),\n set: (ids) => scene.setSelection(ids as readonly NodeId[]),\n };\n}\n"],"mappings":";;;AAuBA,SAAgB,qBAEd,WACgB;CAChB,IAAI,MAAyB,CAAC;CAC9B,IAAI,UAAU;CACd,MAAM,UAAU,cAAc;CAE9B,OAAO;EACL,WAAW;EACX,IAAI,MAAM;GACR,MAAM,UAAU,IAAI;GACpB;GACA,QAAQ,KAAK;EACf;EACA,OAAO,MAAM;GAGX,MAAM,UADO,SAAS,UAAa,IAAI,MAAM,OAAO,KAAK,IAAI,EAAE,CAAC,IAAI,IAAI,QAAQ,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,GACpF;EACtB;EACA,QAAQ,MAAM;GACZ,MAAM;EACR;EACA,WAAW,QAAQ;EACnB,eAAe;CACjB;AACF;;;;;AAMA,SAAgB,oBAAoB,OAGf;CACnB,OAAO;EACL,WAAW,MAAM,aAAa;EAC9B,MAAM,QAAQ,MAAM,aAAa,GAAwB;CAC3D;AACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createNodeFnFields } from "./nodeFnFields.js";
|
|
2
|
+
import { createDocStore } from "./docValues.js";
|
|
2
3
|
import { createLayerStack } from "./layerStack.js";
|
|
3
4
|
import { createStaleReplay } from "./staleReplay.js";
|
|
4
5
|
import { createLockGuard } from "./lockGuard.js";
|
|
@@ -12,12 +13,13 @@ import { createTreeReads } from "./treeReads.js";
|
|
|
12
13
|
* A scene's document state — its nodes, roots, and layers — with the modules
|
|
13
14
|
* that read and write it directly.
|
|
14
15
|
*/
|
|
15
|
-
function createSceneState(registry, initialLayers, layoutDeclared) {
|
|
16
|
+
function createSceneState(registry, initialLayers, layoutDeclared, initialDoc) {
|
|
16
17
|
const state = {
|
|
17
18
|
nodes: /* @__PURE__ */ new Map(),
|
|
18
19
|
roots: [],
|
|
19
20
|
layers: [],
|
|
20
|
-
layerIndex: /* @__PURE__ */ new Map()
|
|
21
|
+
layerIndex: /* @__PURE__ */ new Map(),
|
|
22
|
+
docValues: /* @__PURE__ */ new Map()
|
|
21
23
|
};
|
|
22
24
|
const order = createRenderOrder(state);
|
|
23
25
|
/** The registry-keyed function fields, each with its key maps. A function
|
|
@@ -29,6 +31,8 @@ function createSceneState(registry, initialLayers, layoutDeclared) {
|
|
|
29
31
|
const layers = createLayerStack(state, order.invalidate);
|
|
30
32
|
layers.reset(initialLayers);
|
|
31
33
|
const locks = createLockGuard(state);
|
|
34
|
+
const docValues = createDocStore(state.docValues);
|
|
35
|
+
docValues.reset(initialDoc);
|
|
32
36
|
const reads = {
|
|
33
37
|
...createTreeReads(state.nodes),
|
|
34
38
|
renderOrder: order.ids,
|
|
@@ -40,7 +44,7 @@ function createSceneState(registry, initialLayers, layoutDeclared) {
|
|
|
40
44
|
const node = state.nodes.get(id);
|
|
41
45
|
return node?.kind === "container" ? node.layout ?? null : null;
|
|
42
46
|
},
|
|
43
|
-
toJSON: () => serializeScene(state, fnFields)
|
|
47
|
+
toJSON: () => serializeScene(state, fnFields, docValues.toJSON())
|
|
44
48
|
};
|
|
45
49
|
return {
|
|
46
50
|
state,
|
|
@@ -51,8 +55,10 @@ function createSceneState(registry, initialLayers, layoutDeclared) {
|
|
|
51
55
|
store,
|
|
52
56
|
layers,
|
|
53
57
|
reads,
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
docValues,
|
|
59
|
+
/** Empty the document and give it `records` as its layers and `values` as its document values. */
|
|
60
|
+
reset(records, values) {
|
|
61
|
+
docValues.reset(values);
|
|
56
62
|
store.dependents.clear();
|
|
57
63
|
store.childDerived.clear();
|
|
58
64
|
state.nodes.clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneState.js","names":[],"sources":["../../../src/core/scene/sceneState.ts"],"sourcesContent":["/**\n * A scene's document state — its nodes, roots, and layers — with the modules\n * that read and write it directly.\n */\nimport { createLayerStack } from './layerStack';\nimport { createLockGuard } from './lockGuard';\nimport { createNodeFnFields } from './nodeFnFields';\nimport { createNodeStore } from './nodeStore';\nimport { createRenderOrder } from './renderOrder';\nimport { serializeScene } from './serialization';\nimport { createStaleReplay } from './staleReplay';\nimport { createTreeReads } from './treeReads';\nimport type { LayerRecord, Node, NodeId, Scene, SceneRegistry } from './types';\n\nexport function createSceneState<TData, TLayer extends string, TPose>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n initialLayers: readonly LayerRecord<TLayer>[],\n /** Called when a node is given a layout. */\n layoutDeclared: () => void,\n) {\n const state = {\n nodes: new Map<NodeId, Node<TData, TLayer, TPose>>(),\n roots: [] as NodeId[],\n layers: [] as LayerRecord<TLayer>[],\n layerIndex: new Map<TLayer, number>(),\n };\n const order = createRenderOrder(state);\n /** The registry-keyed function fields, each with its key maps. A function\n * cannot travel through a serializable op payload, so an op carries its key. */\n const fnFields = createNodeFnFields<TPose>(registry, (field) => {\n if (field === 'layout') layoutDeclared();\n });\n const store = createNodeStore(state, order.invalidate, fnFields);\n const layers = createLayerStack(state, order.invalidate);\n layers.reset(initialLayers);\n const locks = createLockGuard(state);\n\n const reads: Pick<\n Scene<TData, TLayer, TPose>,\n | 'get' | 'incarnation' | 'childrenOf' | 'ancestorsOf' | 'renderOrder' | 'renderOrderNodes'\n | 'nodesOnLayer' | 'isLocked' | 'isHidden' | 'layoutOf' | 'toJSON'\n > = {\n ...createTreeReads(state.nodes),\n renderOrder: order.ids,\n renderOrderNodes: order.nodes,\n nodesOnLayer: order.nodesOnLayer,\n isLocked: (id) => locks.isLocked(id),\n isHidden: (id) => locks.isHidden(id),\n layoutOf(id) {\n const node = state.nodes.get(id);\n return node?.kind === 'container' ? (node.layout ?? null) : null;\n },\n toJSON: () => serializeScene(state, fnFields),\n };\n\n return {\n state,\n order,\n locks,\n stale: createStaleReplay(state.nodes),\n fnFields,\n store,\n layers,\n reads,\n\n /** Empty the document and give it `records` as its layers. */\n reset(records: readonly LayerRecord<TLayer>[]): void {\n store.dependents.clear();\n store.childDerived.clear();\n state.nodes.clear();\n state.roots.length = 0;\n layers.reset(records);\n },\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"sceneState.js","names":[],"sources":["../../../src/core/scene/sceneState.ts"],"sourcesContent":["/**\n * A scene's document state — its nodes, roots, and layers — with the modules\n * that read and write it directly.\n */\nimport { createDocStore, type SerializedDoc } from './docValues';\nimport { createLayerStack } from './layerStack';\nimport { createLockGuard } from './lockGuard';\nimport { createNodeFnFields } from './nodeFnFields';\nimport { createNodeStore } from './nodeStore';\nimport { createRenderOrder } from './renderOrder';\nimport { serializeScene } from './serialization';\nimport { createStaleReplay } from './staleReplay';\nimport { createTreeReads } from './treeReads';\nimport type { LayerRecord, Node, NodeId, Scene, SceneRegistry } from './types';\n\nexport function createSceneState<TData, TLayer extends string, TPose>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n initialLayers: readonly LayerRecord<TLayer>[],\n /** Called when a node is given a layout. */\n layoutDeclared: () => void,\n initialDoc?: SerializedDoc,\n) {\n const state = {\n nodes: new Map<NodeId, Node<TData, TLayer, TPose>>(),\n roots: [] as NodeId[],\n layers: [] as LayerRecord<TLayer>[],\n layerIndex: new Map<TLayer, number>(),\n docValues: new Map<string, unknown>(),\n };\n const order = createRenderOrder(state);\n /** The registry-keyed function fields, each with its key maps. A function\n * cannot travel through a serializable op payload, so an op carries its key. */\n const fnFields = createNodeFnFields<TPose>(registry, (field) => {\n if (field === 'layout') layoutDeclared();\n });\n const store = createNodeStore(state, order.invalidate, fnFields);\n const layers = createLayerStack(state, order.invalidate);\n layers.reset(initialLayers);\n const locks = createLockGuard(state);\n const docValues = createDocStore(state.docValues);\n docValues.reset(initialDoc);\n\n const reads: Pick<\n Scene<TData, TLayer, TPose>,\n | 'get' | 'incarnation' | 'childrenOf' | 'ancestorsOf' | 'renderOrder' | 'renderOrderNodes'\n | 'nodesOnLayer' | 'isLocked' | 'isHidden' | 'layoutOf' | 'toJSON'\n > = {\n ...createTreeReads(state.nodes),\n renderOrder: order.ids,\n renderOrderNodes: order.nodes,\n nodesOnLayer: order.nodesOnLayer,\n isLocked: (id) => locks.isLocked(id),\n isHidden: (id) => locks.isHidden(id),\n layoutOf(id) {\n const node = state.nodes.get(id);\n return node?.kind === 'container' ? (node.layout ?? null) : null;\n },\n toJSON: () => serializeScene(state, fnFields, docValues.toJSON()),\n };\n\n return {\n state,\n order,\n locks,\n stale: createStaleReplay(state.nodes),\n fnFields,\n store,\n layers,\n reads,\n docValues,\n\n /** Empty the document and give it `records` as its layers and `values` as its document values. */\n reset(records: readonly LayerRecord<TLayer>[], values?: SerializedDoc): void {\n docValues.reset(values);\n store.dependents.clear();\n store.childDerived.clear();\n state.nodes.clear();\n state.roots.length = 0;\n layers.reset(records);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,iBACd,UACA,eAEA,gBACA,YACA;CACA,MAAM,QAAQ;EACZ,uBAAO,IAAI,IAAwC;EACnD,OAAO,CAAC;EACR,QAAQ,CAAC;EACT,4BAAY,IAAI,IAAoB;EACpC,2BAAW,IAAI,IAAqB;CACtC;CACA,MAAM,QAAQ,kBAAkB,KAAK;;;CAGrC,MAAM,WAAW,mBAA0B,WAAW,UAAU;EAC9D,IAAI,UAAU,UAAU,eAAe;CACzC,CAAC;CACD,MAAM,QAAQ,gBAAgB,OAAO,MAAM,YAAY,QAAQ;CAC/D,MAAM,SAAS,iBAAiB,OAAO,MAAM,UAAU;CACvD,OAAO,MAAM,aAAa;CAC1B,MAAM,QAAQ,gBAAgB,KAAK;CACnC,MAAM,YAAY,eAAe,MAAM,SAAS;CAChD,UAAU,MAAM,UAAU;CAE1B,MAAM,QAIF;EACF,GAAG,gBAAgB,MAAM,KAAK;EAC9B,aAAa,MAAM;EACnB,kBAAkB,MAAM;EACxB,cAAc,MAAM;EACpB,WAAW,OAAO,MAAM,SAAS,EAAE;EACnC,WAAW,OAAO,MAAM,SAAS,EAAE;EACnC,SAAS,IAAI;GACX,MAAM,OAAO,MAAM,MAAM,IAAI,EAAE;GAC/B,OAAO,MAAM,SAAS,cAAe,KAAK,UAAU,OAAQ;EAC9D;EACA,cAAc,eAAe,OAAO,UAAU,UAAU,OAAO,CAAC;CAClE;CAEA,OAAO;EACL;EACA;EACA;EACA,OAAO,kBAAkB,MAAM,KAAK;EACpC;EACA;EACA;EACA;EACA;;EAGA,MAAM,SAAyC,QAA8B;GAC3E,UAAU,MAAM,MAAM;GACtB,MAAM,WAAW,MAAM;GACvB,MAAM,aAAa,MAAM;GACzB,MAAM,MAAM,MAAM;GAClB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,OAAO;EACtB;CACF;AACF"}
|
|
@@ -29,9 +29,10 @@ function layerFromSerialized(spec) {
|
|
|
29
29
|
kind: "system"
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
/** The snapshot of `state`: layers in order, nodes in tree preorder
|
|
32
|
+
/** The snapshot of `state`: layers in order, nodes in tree preorder, and the
|
|
33
|
+
* document values when there are any. Throws
|
|
33
34
|
* when a node carries a function the registry has no key for. */
|
|
34
|
-
function serializeScene(state, fnFields) {
|
|
35
|
+
function serializeScene(state, fnFields, doc) {
|
|
35
36
|
const nodes = [];
|
|
36
37
|
for (const n of treePreorder(state.roots, state.nodes)) {
|
|
37
38
|
const id = n.id;
|
|
@@ -68,7 +69,8 @@ function serializeScene(state, fnFields) {
|
|
|
68
69
|
}
|
|
69
70
|
return layer;
|
|
70
71
|
}),
|
|
71
|
-
nodes
|
|
72
|
+
nodes,
|
|
73
|
+
...doc ? { doc } : {}
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.js","names":[],"sources":["../../../src/core/scene/serialization.ts"],"sourcesContent":["/**\n * A scene to and from its JSON snapshot: the walk that writes one, and the\n * two readers that turn one back into layer records and node specs.\n */\nimport {\n NODE_FN_FIELDS, fnKeysOf, type FnBearingNode, type NodeFnField,\n} from './nodeFnFields';\nimport { treePreorder } from './treeOrder';\nimport type {\n AddNodeSpec, LayerRecord, Node, NodeId, SceneRegistry, SerializedLayer, SerializedNode, SerializedScene,\n} from './types';\n\n/** Whether `value` is a snapshot `specsFromSerialized` reads, as against a live scene. */\nexport function isSerializedScene<TData, TLayer extends string, TPose>(\n value: unknown,\n): value is SerializedScene<TData, TLayer, TPose> {\n return typeof value === 'object' && value != null\n && (value as { version?: unknown }).version === 1\n && Array.isArray((value as { nodes?: unknown }).nodes);\n}\n\n/** What a snapshot is written from. */\nexport interface SerializableState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: LayerRecord<TLayer>[];\n}\n\n/** One layer record from its snapshot form. A snapshot with no `kind` predates\n * user layers, so everything in it was a system layer. */\nexport function layerFromSerialized<TLayer extends string>(\n spec: SerializedLayer<TLayer>,\n): LayerRecord<TLayer> {\n const base = {\n id: spec.id,\n visible: spec.visible ?? true,\n locked: spec.locked ?? false,\n ...(spec.parallax ? { parallax: spec.parallax } : {}),\n ...(spec.annotation ? { annotation: true } : {}),\n };\n return spec.kind === 'user'\n ? { ...base, kind: 'user', name: spec.name ?? spec.id }\n : { ...base, kind: 'system' };\n}\n\n/** The snapshot of `state`: layers in order, nodes in tree preorder. Throws\n * when a node carries a function the registry has no key for. */\nexport function serializeScene<TData, TLayer extends string, TPose>(\n state: SerializableState<TData, TLayer, TPose>,\n fnFields: readonly NodeFnField[],\n): SerializedScene<TData, TLayer, TPose> {\n const nodes: SerializedNode<TData, TLayer, TPose>[] = [];\n for (const n of treePreorder(state.roots, state.nodes)) {\n const id = n.id;\n const out: SerializedNode<TData, TLayer, TPose> = {\n id,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent != null) out.parent = n.parent;\n if (n.dependsOn === 'children') out.dependsOn = 'children';\n else if (n.dependsOn && n.dependsOn.length > 0) out.dependsOn = n.dependsOn;\n if (n.pickable === false) out.pickable = false;\n if (n.hidden === true) out.hidden = true;\n if (n.locked === true) out.locked = true;\n if (n.kind === 'container' && n.units !== undefined) out.units = n.units;\n Object.assign(\n out,\n fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey', (field) => {\n throw new Error(\n `Scene.toJSON: node '${id}' has ${field.spec.field} but no matching registry key. ` +\n `The function must be registered via createScene's registry option to round-trip.`\n );\n }),\n );\n nodes.push(out);\n }\n const systemLayers = state.layers.map((l) => {\n const layer: SerializedLayer<TLayer> = { id: l.id };\n if (l.visible === false) layer.visible = false;\n if (l.locked === true) layer.locked = true;\n if (l.parallax) layer.parallax = l.parallax;\n if (l.annotation) layer.annotation = true;\n if (l.kind === 'user') {\n layer.kind = 'user';\n layer.name = l.name;\n }\n return layer;\n });\n return { version: 1, systemLayers, nodes };\n}\n\n/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`\n * (new instance) and `Scene.loadState` (in-place). Validates version and\n * resolves every `NODE_FN_FIELDS` key → function via the registry;\n * throws on an unsupported version or an unknown registry key. */\nexport function specsFromSerialized<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer>,\n): AddNodeSpec<TData, TLayer, TPose>[] {\n if (json.version !== 1) {\n throw new Error(`Scene: unsupported version ${json.version}; only v1 supported`);\n }\n return json.nodes.map((n) => {\n const spec: AddNodeSpec<TData, TLayer, TPose> = {\n id: n.id as NodeId,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent !== undefined) spec.parent = n.parent as NodeId;\n if (n.pickable !== undefined) spec.pickable = n.pickable;\n if (n.hidden === true) spec.hidden = true;\n if (n.locked === true) spec.locked = true;\n if (n.units !== undefined) spec.units = n.units;\n if (n.dependsOn !== undefined) {\n spec.dependsOn = n.dependsOn === 'children'\n ? 'children'\n : (n.dependsOn as readonly NodeId[]);\n }\n const serialized = n as unknown as Record<string, string | undefined>;\n for (const field of NODE_FN_FIELDS) {\n const key = serialized[field.jsonKey];\n if (key === undefined) continue;\n const fn = (registry[field.field] as Record<string, unknown> | undefined)?.[key];\n if (!fn) {\n throw new Error(\n `Scene: unknown ${field.field} key '${key}'. ` +\n `Register a function with this key in the registry option.`,\n );\n }\n (spec as unknown as Record<string, unknown>)[field.field] = fn;\n }\n return spec;\n });\n}\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"serialization.js","names":[],"sources":["../../../src/core/scene/serialization.ts"],"sourcesContent":["/**\n * A scene to and from its JSON snapshot: the walk that writes one, and the\n * two readers that turn one back into layer records and node specs.\n */\nimport {\n NODE_FN_FIELDS, fnKeysOf, type FnBearingNode, type NodeFnField,\n} from './nodeFnFields';\nimport type { SerializedDoc } from './docValues';\nimport { treePreorder } from './treeOrder';\nimport type {\n AddNodeSpec, LayerRecord, Node, NodeId, SceneRegistry, SerializedLayer, SerializedNode, SerializedScene,\n} from './types';\n\n/** Whether `value` is a snapshot `specsFromSerialized` reads, as against a live scene. */\nexport function isSerializedScene<TData, TLayer extends string, TPose>(\n value: unknown,\n): value is SerializedScene<TData, TLayer, TPose> {\n return typeof value === 'object' && value != null\n && (value as { version?: unknown }).version === 1\n && Array.isArray((value as { nodes?: unknown }).nodes);\n}\n\n/** What a snapshot is written from. */\nexport interface SerializableState<TData, TLayer extends string, TPose> {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n roots: NodeId[];\n layers: LayerRecord<TLayer>[];\n}\n\n/** One layer record from its snapshot form. A snapshot with no `kind` predates\n * user layers, so everything in it was a system layer. */\nexport function layerFromSerialized<TLayer extends string>(\n spec: SerializedLayer<TLayer>,\n): LayerRecord<TLayer> {\n const base = {\n id: spec.id,\n visible: spec.visible ?? true,\n locked: spec.locked ?? false,\n ...(spec.parallax ? { parallax: spec.parallax } : {}),\n ...(spec.annotation ? { annotation: true } : {}),\n };\n return spec.kind === 'user'\n ? { ...base, kind: 'user', name: spec.name ?? spec.id }\n : { ...base, kind: 'system' };\n}\n\n/** The snapshot of `state`: layers in order, nodes in tree preorder, and the\n * document values when there are any. Throws\n * when a node carries a function the registry has no key for. */\nexport function serializeScene<TData, TLayer extends string, TPose>(\n state: SerializableState<TData, TLayer, TPose>,\n fnFields: readonly NodeFnField[],\n doc?: SerializedDoc,\n): SerializedScene<TData, TLayer, TPose> {\n const nodes: SerializedNode<TData, TLayer, TPose>[] = [];\n for (const n of treePreorder(state.roots, state.nodes)) {\n const id = n.id;\n const out: SerializedNode<TData, TLayer, TPose> = {\n id,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent != null) out.parent = n.parent;\n if (n.dependsOn === 'children') out.dependsOn = 'children';\n else if (n.dependsOn && n.dependsOn.length > 0) out.dependsOn = n.dependsOn;\n if (n.pickable === false) out.pickable = false;\n if (n.hidden === true) out.hidden = true;\n if (n.locked === true) out.locked = true;\n if (n.kind === 'container' && n.units !== undefined) out.units = n.units;\n Object.assign(\n out,\n fnKeysOf(fnFields, n as unknown as FnBearingNode, 'jsonKey', (field) => {\n throw new Error(\n `Scene.toJSON: node '${id}' has ${field.spec.field} but no matching registry key. ` +\n `The function must be registered via createScene's registry option to round-trip.`\n );\n }),\n );\n nodes.push(out);\n }\n const systemLayers = state.layers.map((l) => {\n const layer: SerializedLayer<TLayer> = { id: l.id };\n if (l.visible === false) layer.visible = false;\n if (l.locked === true) layer.locked = true;\n if (l.parallax) layer.parallax = l.parallax;\n if (l.annotation) layer.annotation = true;\n if (l.kind === 'user') {\n layer.kind = 'user';\n layer.name = l.name;\n }\n return layer;\n });\n return { version: 1, systemLayers, nodes, ...(doc ? { doc } : {}) };\n}\n\n/** Map a `SerializedScene` to construction specs. Shared by `sceneFromJSON`\n * (new instance) and `Scene.loadState` (in-place). Validates version and\n * resolves every `NODE_FN_FIELDS` key → function via the registry;\n * throws on an unsupported version or an unknown registry key. */\nexport function specsFromSerialized<TData, TLayer extends string, TPose>(\n json: SerializedScene<TData, TLayer, TPose>,\n registry: SceneRegistry<TPose, TData, TLayer>,\n): AddNodeSpec<TData, TLayer, TPose>[] {\n if (json.version !== 1) {\n throw new Error(`Scene: unsupported version ${json.version}; only v1 supported`);\n }\n return json.nodes.map((n) => {\n const spec: AddNodeSpec<TData, TLayer, TPose> = {\n id: n.id as NodeId,\n kind: n.kind,\n layer: n.layer,\n pose: n.pose,\n data: n.data,\n };\n if (n.parent !== undefined) spec.parent = n.parent as NodeId;\n if (n.pickable !== undefined) spec.pickable = n.pickable;\n if (n.hidden === true) spec.hidden = true;\n if (n.locked === true) spec.locked = true;\n if (n.units !== undefined) spec.units = n.units;\n if (n.dependsOn !== undefined) {\n spec.dependsOn = n.dependsOn === 'children'\n ? 'children'\n : (n.dependsOn as readonly NodeId[]);\n }\n const serialized = n as unknown as Record<string, string | undefined>;\n for (const field of NODE_FN_FIELDS) {\n const key = serialized[field.jsonKey];\n if (key === undefined) continue;\n const fn = (registry[field.field] as Record<string, unknown> | undefined)?.[key];\n if (!fn) {\n throw new Error(\n `Scene: unknown ${field.field} key '${key}'. ` +\n `Register a function with this key in the registry option.`,\n );\n }\n (spec as unknown as Record<string, unknown>)[field.field] = fn;\n }\n return spec;\n });\n}\n"],"mappings":"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"}
|