@dxos/react-ui-canvas 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/lib/index.mjs +9044 -1135
- package/dist/lib/index.mjs.map +1 -1
- package/dist/types/src/components/About/About.d.ts +13 -0
- package/dist/types/src/components/About/About.d.ts.map +1 -0
- package/dist/types/src/components/About/About.stories.d.ts +7 -0
- package/dist/types/src/components/About/About.stories.d.ts.map +1 -0
- package/dist/types/src/components/About/index.d.ts +2 -0
- package/dist/types/src/components/About/index.d.ts.map +1 -0
- package/dist/types/src/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/types/src/components/ControlFrame/ControlFrame.d.ts +8 -2
- package/dist/types/src/components/ControlFrame/ControlFrame.d.ts.map +1 -1
- package/dist/types/src/components/Layers/Layers.d.ts +30 -0
- package/dist/types/src/components/Layers/Layers.d.ts.map +1 -0
- package/dist/types/src/components/Layers/Layers.stories.d.ts +11 -0
- package/dist/types/src/components/Layers/Layers.stories.d.ts.map +1 -0
- package/dist/types/src/components/Layers/index.d.ts +2 -0
- package/dist/types/src/components/Layers/index.d.ts.map +1 -0
- package/dist/types/src/components/Palette/Palette.d.ts +20 -4
- package/dist/types/src/components/Palette/Palette.d.ts.map +1 -1
- package/dist/types/src/components/Palette/Palette.stories.d.ts +22 -0
- package/dist/types/src/components/Palette/Palette.stories.d.ts.map +1 -0
- package/dist/types/src/components/PartEditor/PartEditor.d.ts.map +1 -1
- package/dist/types/src/components/Properties/AlignField.d.ts +23 -0
- package/dist/types/src/components/Properties/AlignField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/ClassField.d.ts +23 -0
- package/dist/types/src/components/Properties/ClassField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/FontField.d.ts +7 -0
- package/dist/types/src/components/Properties/FontField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/LayerField.d.ts +17 -0
- package/dist/types/src/components/Properties/LayerField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/Properties.d.ts +27 -3
- package/dist/types/src/components/Properties/Properties.d.ts.map +1 -1
- package/dist/types/src/components/Properties/Properties.stories.d.ts +20 -0
- package/dist/types/src/components/Properties/Properties.stories.d.ts.map +1 -0
- package/dist/types/src/components/Properties/SceneField.d.ts +8 -0
- package/dist/types/src/components/Properties/SceneField.d.ts.map +1 -0
- package/dist/types/src/components/Properties/StyleGrid.d.ts +15 -5
- package/dist/types/src/components/Properties/StyleGrid.d.ts.map +1 -1
- package/dist/types/src/components/Properties/StyleGrid.stories.d.ts +7 -0
- package/dist/types/src/components/Properties/StyleGrid.stories.d.ts.map +1 -0
- package/dist/types/src/components/SceneLayer/SceneLayer.d.ts +31 -7
- package/dist/types/src/components/SceneLayer/SceneLayer.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/Dock.d.ts +22 -0
- package/dist/types/src/components/SceneView/Dock.d.ts.map +1 -0
- package/dist/types/src/components/SceneView/Dock.stories.d.ts +15 -0
- package/dist/types/src/components/SceneView/Dock.stories.d.ts.map +1 -0
- package/dist/types/src/components/SceneView/SceneView.d.ts +33 -7
- package/dist/types/src/components/SceneView/SceneView.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/SceneView.stories.d.ts +16 -6
- package/dist/types/src/components/SceneView/SceneView.stories.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/SceneViewContext.d.ts +36 -4
- package/dist/types/src/components/SceneView/SceneViewContext.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/Scored.stories.d.ts +4 -4
- package/dist/types/src/components/SceneView/Scored.stories.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/usePointerMachine.d.ts +3 -1
- package/dist/types/src/components/SceneView/usePointerMachine.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneCamera.d.ts +2 -2
- package/dist/types/src/components/SceneView/useSceneCamera.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneClipboard.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneKeys.d.ts.map +1 -1
- package/dist/types/src/components/SceneView/useSceneNavigation.d.ts +7 -4
- package/dist/types/src/components/SceneView/useSceneNavigation.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +2 -0
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useWheel.d.ts +5 -0
- package/dist/types/src/hooks/useWheel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +4 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/model/atoms.d.ts +21 -3
- package/dist/types/src/model/atoms.d.ts.map +1 -1
- package/dist/types/src/model/projection.d.ts.map +1 -1
- package/dist/types/src/model/projections/constrained.d.ts +2 -1
- package/dist/types/src/model/projections/constrained.d.ts.map +1 -1
- package/dist/types/src/model/projections/dynamic.d.ts.map +1 -1
- package/dist/types/src/model/projections/lattice.d.ts.map +1 -1
- package/dist/types/src/model/registry.d.ts +21 -3
- package/dist/types/src/model/registry.d.ts.map +1 -1
- package/dist/types/src/model/store.d.ts +10 -2
- package/dist/types/src/model/store.d.ts.map +1 -1
- package/dist/types/src/model/types.d.ts +699 -338
- package/dist/types/src/model/types.d.ts.map +1 -1
- package/dist/types/src/playwright/SceneManager.d.ts +1 -1
- package/dist/types/src/playwright/SceneManager.d.ts.map +1 -1
- package/dist/types/src/utils/builder.d.ts +22 -41
- package/dist/types/src/utils/builder.d.ts.map +1 -1
- package/dist/types/src/utils/camera.d.ts +19 -10
- package/dist/types/src/utils/camera.d.ts.map +1 -1
- package/dist/types/src/utils/clipboard.d.ts +4 -2
- package/dist/types/src/utils/clipboard.d.ts.map +1 -1
- package/dist/types/src/utils/diagram.d.ts.map +1 -1
- package/dist/types/src/utils/group.d.ts +14 -0
- package/dist/types/src/utils/group.d.ts.map +1 -0
- package/dist/types/src/utils/group.test.d.ts +2 -0
- package/dist/types/src/utils/group.test.d.ts.map +1 -0
- package/dist/types/src/utils/gutter-route.d.ts +7 -0
- package/dist/types/src/utils/gutter-route.d.ts.map +1 -1
- package/dist/types/src/utils/hit.d.ts +3 -20
- package/dist/types/src/utils/hit.d.ts.map +1 -1
- package/dist/types/src/utils/index.d.ts +3 -0
- package/dist/types/src/utils/index.d.ts.map +1 -1
- package/dist/types/src/utils/lattice.d.ts +5 -0
- package/dist/types/src/utils/lattice.d.ts.map +1 -1
- package/dist/types/src/utils/layers.d.ts +35 -0
- package/dist/types/src/utils/layers.d.ts.map +1 -0
- package/dist/types/src/utils/layers.test.d.ts +2 -0
- package/dist/types/src/utils/layers.test.d.ts.map +1 -0
- package/dist/types/src/utils/parts.d.ts +25 -9
- package/dist/types/src/utils/parts.d.ts.map +1 -1
- package/dist/types/src/utils/ports.d.ts +2 -0
- package/dist/types/src/utils/ports.d.ts.map +1 -1
- package/dist/types/src/utils/properties.d.ts +4 -3
- package/dist/types/src/utils/properties.d.ts.map +1 -1
- package/dist/types/src/utils/route.d.ts +5 -0
- package/dist/types/src/utils/route.d.ts.map +1 -1
- package/dist/types/src/utils/scenes.d.ts +14 -0
- package/dist/types/src/utils/scenes.d.ts.map +1 -0
- package/dist/types/src/utils/scenes.test.d.ts +2 -0
- package/dist/types/src/utils/scenes.test.d.ts.map +1 -0
- package/dist/types/src/utils/shapes.d.ts +23 -9
- package/dist/types/src/utils/shapes.d.ts.map +1 -1
- package/dist/types/src/utils/style.d.ts +29 -6
- package/dist/types/src/utils/style.d.ts.map +1 -1
- package/dist/types/src/utils/style.test.d.ts +2 -0
- package/dist/types/src/utils/style.test.d.ts.map +1 -0
- package/dist/types/src/utils/testing.d.ts +2 -2
- package/dist/types/src/utils/testing.d.ts.map +1 -1
- package/dist/types/src/utils/undo.d.ts +15 -4
- package/dist/types/src/utils/undo.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -26
- package/src/components/About/About.stories.tsx +32 -0
- package/src/components/About/About.tsx +38 -0
- package/src/components/About/index.ts +5 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +17 -3
- package/src/components/ControlFrame/ControlFrame.tsx +138 -82
- package/src/components/Grid/Grid.stories.tsx +1 -1
- package/src/components/Layers/Layers.stories.tsx +131 -0
- package/src/components/Layers/Layers.tsx +216 -0
- package/src/components/Layers/index.ts +5 -0
- package/src/components/Palette/Palette.stories.tsx +135 -0
- package/src/components/Palette/Palette.tsx +230 -39
- package/src/components/PartEditor/PartEditor.tsx +4 -3
- package/src/components/Properties/AlignField.tsx +117 -0
- package/src/components/Properties/ClassField.tsx +44 -0
- package/src/components/Properties/FontField.tsx +73 -0
- package/src/components/Properties/LayerField.tsx +61 -0
- package/src/components/Properties/Properties.stories.tsx +113 -0
- package/src/components/Properties/Properties.tsx +240 -60
- package/src/components/Properties/SceneField.tsx +22 -0
- package/src/components/Properties/StyleGrid.stories.tsx +50 -0
- package/src/components/Properties/StyleGrid.tsx +54 -26
- package/src/components/SceneLayer/SceneLayer.tsx +497 -188
- package/src/components/SceneView/Constrained.stories.tsx +1 -1
- package/src/components/SceneView/Dock.stories.tsx +95 -0
- package/src/components/SceneView/Dock.tsx +146 -0
- package/src/components/SceneView/Dynamic.stories.tsx +1 -1
- package/src/components/SceneView/SceneView.stories.tsx +170 -18
- package/src/components/SceneView/SceneView.tsx +565 -151
- package/src/components/SceneView/SceneViewContext.ts +23 -2
- package/src/components/SceneView/Scored.stories.tsx +11 -11
- package/src/components/SceneView/usePointerMachine.ts +80 -20
- package/src/components/SceneView/useSceneCamera.ts +15 -8
- package/src/components/SceneView/useSceneClipboard.ts +4 -0
- package/src/components/SceneView/useSceneKeys.ts +6 -4
- package/src/components/SceneView/useSceneNavigation.ts +44 -33
- package/src/components/Toolbar/Toolbar.stories.tsx +14 -15
- package/src/components/Toolbar/Toolbar.tsx +15 -4
- package/src/components/index.ts +2 -0
- package/src/hooks/useWheel.ts +35 -0
- package/src/index.ts +4 -3
- package/src/model/atoms.ts +27 -3
- package/src/model/projection.ts +5 -0
- package/src/model/projections/constrained.test.ts +18 -10
- package/src/model/projections/constrained.ts +19 -8
- package/src/model/projections/dynamic.ts +5 -2
- package/src/model/projections/lattice.ts +15 -3
- package/src/model/registry.test.ts +14 -9
- package/src/model/registry.ts +39 -34
- package/src/model/store.ts +12 -2
- package/src/model/types.ts +124 -36
- package/src/playwright/SceneManager.ts +2 -2
- package/src/playwright/create.spec.ts +5 -3
- package/src/playwright/demo.spec.ts +2 -2
- package/src/playwright/lattice.spec.ts +4 -5
- package/src/playwright/scene.spec.ts +27 -25
- package/src/utils/builder.test.ts +46 -9
- package/src/utils/builder.ts +42 -18
- package/src/utils/camera.test.ts +22 -51
- package/src/utils/camera.ts +39 -32
- package/src/utils/clipboard.test.ts +23 -2
- package/src/utils/clipboard.ts +8 -4
- package/src/utils/diagram.test.ts +3 -1
- package/src/utils/diagram.ts +3 -25
- package/src/utils/group.test.ts +69 -0
- package/src/utils/group.ts +94 -0
- package/src/utils/gutter-route.test.ts +49 -0
- package/src/utils/gutter-route.ts +142 -16
- package/src/utils/hit.test.ts +4 -38
- package/src/utils/hit.ts +4 -26
- package/src/utils/index.ts +3 -0
- package/src/utils/lattice.ts +7 -1
- package/src/utils/layers.test.ts +139 -0
- package/src/utils/layers.ts +175 -0
- package/src/utils/parts.test.ts +66 -32
- package/src/utils/parts.ts +63 -41
- package/src/utils/ports.ts +7 -4
- package/src/utils/properties.test.ts +23 -6
- package/src/utils/properties.ts +66 -16
- package/src/utils/route.test.ts +51 -2
- package/src/utils/route.ts +133 -23
- package/src/utils/scenes.test.ts +38 -0
- package/src/utils/scenes.ts +54 -0
- package/src/utils/shapes.test.ts +33 -8
- package/src/utils/shapes.ts +51 -35
- package/src/utils/style.test.ts +54 -0
- package/src/utils/style.ts +153 -40
- package/src/utils/testing.test.ts +3 -3
- package/src/utils/testing.ts +21 -30
- package/src/utils/undo.test.ts +25 -2
- package/src/utils/undo.ts +50 -13
- package/dist/lib/chunk-Grid.mjs +0 -99
- package/dist/lib/chunk-Grid.mjs.map +0 -1
- package/dist/lib/scene.mjs +0 -6835
- package/dist/lib/scene.mjs.map +0 -1
- package/dist/lib/types.mjs +0 -16
- package/dist/lib/types.mjs.map +0 -1
- package/dist/types/src/archive/components/Canvas/Canvas.d.ts +0 -15
- package/dist/types/src/archive/components/Canvas/Canvas.d.ts.map +0 -1
- package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts +0 -18
- package/dist/types/src/archive/components/Canvas/Canvas.stories.d.ts.map +0 -1
- package/dist/types/src/archive/components/Canvas/index.d.ts +0 -2
- package/dist/types/src/archive/components/Canvas/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/CellGrid.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/CellGrid.stories.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts +0 -15
- package/dist/types/src/archive/components/CellGrid/headers/Ruler.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts +0 -19
- package/dist/types/src/archive/components/CellGrid/headers/TrackHeader.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/headers/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/headers/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/index.d.ts +0 -6
- package/dist/types/src/archive/components/CellGrid/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/input/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts +0 -33
- package/dist/types/src/archive/components/CellGrid/input/pointer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts +0 -14
- package/dist/types/src/archive/components/CellGrid/input/wheel.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/index.d.ts +0 -3
- package/dist/types/src/archive/components/CellGrid/render/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts +0 -21
- package/dist/types/src/archive/components/CellGrid/render/overlay-layer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts +0 -36
- package/dist/types/src/archive/components/CellGrid/render/static-layer.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts +0 -23
- package/dist/types/src/archive/components/CellGrid/state/atoms.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/index.d.ts +0 -4
- package/dist/types/src/archive/components/CellGrid/state/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/types.d.ts +0 -39
- package/dist/types/src/archive/components/CellGrid/state/types.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts +0 -52
- package/dist/types/src/archive/components/CellGrid/state/viewport.d.ts.map +0 -1
- package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts +0 -2
- package/dist/types/src/archive/components/CellGrid/state/viewport.test.d.ts.map +0 -1
- package/dist/types/src/archive/components/FPS.d.ts +0 -9
- package/dist/types/src/archive/components/FPS.d.ts.map +0 -1
- package/dist/types/src/archive/components/Grid/Grid.d.ts +0 -6
- package/dist/types/src/archive/components/Grid/Grid.d.ts.map +0 -1
- package/dist/types/src/archive/components/Grid/index.d.ts +0 -2
- package/dist/types/src/archive/components/Grid/index.d.ts.map +0 -1
- package/dist/types/src/archive/components/index.d.ts +0 -5
- package/dist/types/src/archive/components/index.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/index.d.ts +0 -5
- package/dist/types/src/archive/hooks/index.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/projection.d.ts +0 -60
- package/dist/types/src/archive/hooks/projection.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useCanvasContext.d.ts +0 -13
- package/dist/types/src/archive/hooks/useCanvasContext.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useDrag.d.ts +0 -6
- package/dist/types/src/archive/hooks/useDrag.d.ts.map +0 -1
- package/dist/types/src/archive/hooks/useWheel.d.ts +0 -8
- package/dist/types/src/archive/hooks/useWheel.d.ts.map +0 -1
- package/dist/types/src/archive/index.d.ts +0 -5
- package/dist/types/src/archive/index.d.ts.map +0 -1
- package/dist/types/src/archive/types.d.ts +0 -19
- package/dist/types/src/archive/types.d.ts.map +0 -1
- package/dist/types/src/archive/util/index.d.ts +0 -4
- package/dist/types/src/archive/util/index.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg-path.d.ts +0 -4
- package/dist/types/src/archive/util/svg-path.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg.d.ts +0 -32
- package/dist/types/src/archive/util/svg.d.ts.map +0 -1
- package/dist/types/src/archive/util/svg.stories.d.ts +0 -15
- package/dist/types/src/archive/util/svg.stories.d.ts.map +0 -1
- package/dist/types/src/archive/util/util.d.ts +0 -17
- package/dist/types/src/archive/util/util.d.ts.map +0 -1
- package/dist/types/src/scene.d.ts +0 -5
- package/dist/types/src/scene.d.ts.map +0 -1
- package/src/archive/components/Canvas/Canvas.stories.tsx +0 -111
- package/src/archive/components/Canvas/Canvas.tsx +0 -87
- package/src/archive/components/Canvas/index.ts +0 -5
- package/src/archive/components/CellGrid/CellGrid.stories.tsx +0 -239
- package/src/archive/components/CellGrid/CellGrid.tsx +0 -270
- package/src/archive/components/CellGrid/headers/Ruler.tsx +0 -71
- package/src/archive/components/CellGrid/headers/TrackHeader.tsx +0 -58
- package/src/archive/components/CellGrid/headers/index.ts +0 -6
- package/src/archive/components/CellGrid/index.ts +0 -9
- package/src/archive/components/CellGrid/input/index.ts +0 -6
- package/src/archive/components/CellGrid/input/pointer.ts +0 -236
- package/src/archive/components/CellGrid/input/wheel.ts +0 -68
- package/src/archive/components/CellGrid/render/index.ts +0 -6
- package/src/archive/components/CellGrid/render/overlay-layer.ts +0 -66
- package/src/archive/components/CellGrid/render/static-layer.ts +0 -112
- package/src/archive/components/CellGrid/state/atoms.ts +0 -43
- package/src/archive/components/CellGrid/state/index.ts +0 -7
- package/src/archive/components/CellGrid/state/types.ts +0 -40
- package/src/archive/components/CellGrid/state/viewport.test.ts +0 -50
- package/src/archive/components/CellGrid/state/viewport.ts +0 -94
- package/src/archive/components/FPS.tsx +0 -98
- package/src/archive/components/Grid/Grid.tsx +0 -17
- package/src/archive/components/Grid/index.ts +0 -5
- package/src/archive/components/index.ts +0 -8
- package/src/archive/hooks/index.ts +0 -8
- package/src/archive/hooks/projection.tsx +0 -156
- package/src/archive/hooks/useCanvasContext.ts +0 -32
- package/src/archive/hooks/useDrag.tsx +0 -96
- package/src/archive/hooks/useWheel.tsx +0 -80
- package/src/archive/index.ts +0 -8
- package/src/archive/types.ts +0 -14
- package/src/archive/util/index.ts +0 -7
- package/src/archive/util/svg-path.ts +0 -13
- package/src/archive/util/svg.stories.tsx +0 -46
- package/src/archive/util/svg.tsx +0 -128
- package/src/archive/util/util.ts +0 -50
- package/src/scene.ts +0 -9
|
@@ -7,7 +7,13 @@ import { type KeyboardEvent, type MouseEvent, type RefObject } from 'react';
|
|
|
7
7
|
import { createContext } from '@dxos/react-hooks';
|
|
8
8
|
|
|
9
9
|
import { type useRegistry } from '../../hooks/index.ts';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type ControlPointRef,
|
|
12
|
+
type Drag,
|
|
13
|
+
type EditingPart,
|
|
14
|
+
type PanelMode,
|
|
15
|
+
type SceneViewAtoms,
|
|
16
|
+
} from '../../model/atoms.ts';
|
|
11
17
|
import { type Projection } from '../../model/projection.ts';
|
|
12
18
|
import { type LinkRegistry, type NodeRegistry } from '../../model/registry.ts';
|
|
13
19
|
import { type SceneStore } from '../../model/store.ts';
|
|
@@ -16,6 +22,8 @@ import {
|
|
|
16
22
|
type Camera,
|
|
17
23
|
type Capabilities,
|
|
18
24
|
type ElementId,
|
|
25
|
+
type LinkId,
|
|
26
|
+
type NodeId,
|
|
19
27
|
type Scene,
|
|
20
28
|
type SceneId,
|
|
21
29
|
type Tool,
|
|
@@ -39,6 +47,8 @@ export type SceneViewContextValue = {
|
|
|
39
47
|
store: SceneStore;
|
|
40
48
|
projection: Projection;
|
|
41
49
|
capabilities: Capabilities;
|
|
50
|
+
/** The scene is only looked at: the parts that edit it (selection frame, ports, actions, properties) hide. */
|
|
51
|
+
readonly: boolean;
|
|
42
52
|
nodeRegistry: NodeRegistry;
|
|
43
53
|
linkRegistry: LinkRegistry;
|
|
44
54
|
|
|
@@ -49,16 +59,19 @@ export type SceneViewContextValue = {
|
|
|
49
59
|
blocked: boolean;
|
|
50
60
|
/** The current scene's frame. */
|
|
51
61
|
bounds: Bounds;
|
|
62
|
+
/** The scene's frame and the visible viewport together, for the lattice cells. */
|
|
63
|
+
latticeBounds: Bounds;
|
|
52
64
|
path: SceneId[];
|
|
53
65
|
camera: Camera;
|
|
54
66
|
/** The zoom against this level's own 1:1 rather than the root's; display only. */
|
|
55
|
-
nominalZoom: number;
|
|
56
67
|
/** The centre of the view in scene coordinates. */
|
|
57
68
|
pointer: { x: number; y: number };
|
|
58
69
|
/** Whether the viewport has been measured; the scene stays hidden until it has. */
|
|
59
70
|
measured: boolean;
|
|
60
71
|
/** One screen pixel in scene units, for chrome that should not grow with the camera. */
|
|
61
72
|
frameUnit: number;
|
|
73
|
+
/** The portal filling most of the view and the opacity of the rest of the layer around it (`layerOpacity`). */
|
|
74
|
+
focus: { id: ElementId; opacity: number } | undefined;
|
|
62
75
|
/** Minor grid spacing in scene px. */
|
|
63
76
|
grid: number;
|
|
64
77
|
snapEnabled: boolean;
|
|
@@ -70,14 +83,22 @@ export type SceneViewContextValue = {
|
|
|
70
83
|
hover: ElementId | undefined;
|
|
71
84
|
selectedPoint: ControlPointRef | undefined;
|
|
72
85
|
editing: EditingPart | undefined;
|
|
86
|
+
/** The node whose embedded content takes input. */
|
|
87
|
+
active: NodeId | undefined;
|
|
73
88
|
clipboard: Clipboard | undefined;
|
|
74
89
|
drag: Drag | undefined;
|
|
75
90
|
tool: Tool;
|
|
76
91
|
debug: boolean;
|
|
92
|
+
/** Where the properties and layers panels sit. */
|
|
93
|
+
panels: PanelMode;
|
|
77
94
|
|
|
78
95
|
/** The bounds a create gesture would land, drawn whether or not the node itself previews. */
|
|
79
96
|
createFrame: Bounds | undefined;
|
|
97
|
+
/** Where the nodes of a move in flight will land, while they follow the pointer. */
|
|
98
|
+
landing: readonly Bounds[] | undefined;
|
|
80
99
|
handlers: ElementHandlers;
|
|
100
|
+
/** The link under the pointer. */
|
|
101
|
+
linkHover: LinkId | undefined;
|
|
81
102
|
select: (ids: Iterable<ElementId>) => void;
|
|
82
103
|
toolbarActions: ToolbarActions;
|
|
83
104
|
} & Pick<SceneCamera, 'navigating' | 'opening'> &
|
|
@@ -21,7 +21,7 @@ import { type SceneStore, createMemoryStore } from '../../model/store.ts';
|
|
|
21
21
|
import { type Scene, type SceneId } from '../../model/types.ts';
|
|
22
22
|
import { SceneBuilder } from '../../utils/builder.ts';
|
|
23
23
|
import { diagnosticElements, toDiagramObjects } from '../../utils/diagram.ts';
|
|
24
|
-
import {
|
|
24
|
+
import { createModelSceneTree } from '../../utils/testing.ts';
|
|
25
25
|
import { SceneView } from './SceneView.tsx';
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -95,7 +95,7 @@ const fromMermaid = async (source: string): Promise<Seed> => {
|
|
|
95
95
|
...graph.edges.map(({ from, to }, index) =>
|
|
96
96
|
SceneBuilder.link('smart', nodeId(from), nodeId(to))
|
|
97
97
|
.id(nodeId(`${from}-${to}-${index}`))
|
|
98
|
-
.properties({
|
|
98
|
+
.properties({ ends: { end: 'arrow' } }),
|
|
99
99
|
),
|
|
100
100
|
]).build();
|
|
101
101
|
return { scenes, root: ROOT, engine: result.chosen.evaluation };
|
|
@@ -319,9 +319,9 @@ const Editor = ({ store, root, engine }: EditorProps) => {
|
|
|
319
319
|
};
|
|
320
320
|
|
|
321
321
|
type StoryArgs = {
|
|
322
|
-
/** Mermaid flowchart laid out by the engine; ignored when `fixture` is `
|
|
322
|
+
/** Mermaid flowchart laid out by the engine; ignored when `fixture` is `model`. */
|
|
323
323
|
source?: string;
|
|
324
|
-
fixture?: 'mermaid' | '
|
|
324
|
+
fixture?: 'mermaid' | 'model';
|
|
325
325
|
};
|
|
326
326
|
|
|
327
327
|
const DefaultStory = ({ source = BASIC, fixture = 'mermaid' }: StoryArgs) => {
|
|
@@ -330,8 +330,8 @@ const DefaultStory = ({ source = BASIC, fixture = 'mermaid' }: StoryArgs) => {
|
|
|
330
330
|
const [failure, setFailure] = useState<string>();
|
|
331
331
|
useEffect(() => {
|
|
332
332
|
const key = `${fixture}:${source}`;
|
|
333
|
-
if (fixture === '
|
|
334
|
-
setSeed({ ...
|
|
333
|
+
if (fixture === 'model') {
|
|
334
|
+
setSeed({ ...createModelSceneTree(), key });
|
|
335
335
|
setFailure(undefined);
|
|
336
336
|
return;
|
|
337
337
|
}
|
|
@@ -364,12 +364,12 @@ const DefaultStory = ({ source = BASIC, fixture = 'mermaid' }: StoryArgs) => {
|
|
|
364
364
|
};
|
|
365
365
|
|
|
366
366
|
const meta: Meta<StoryArgs> = {
|
|
367
|
-
title: 'ui/react-ui-canvas/
|
|
367
|
+
title: 'ui/react-ui-canvas/Scored',
|
|
368
368
|
render: DefaultStory,
|
|
369
369
|
decorators: [withRegistry, withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
370
370
|
argTypes: {
|
|
371
371
|
source: { control: 'text', description: 'Mermaid flowchart laid out by the engine' },
|
|
372
|
-
fixture: { control: 'radio', options: ['mermaid', '
|
|
372
|
+
fixture: { control: 'radio', options: ['mermaid', 'model'] },
|
|
373
373
|
},
|
|
374
374
|
};
|
|
375
375
|
|
|
@@ -387,7 +387,7 @@ export const Platform: Story = {
|
|
|
387
387
|
args: { source: PLATFORM.trim(), fixture: 'mermaid' },
|
|
388
388
|
};
|
|
389
389
|
|
|
390
|
-
/** The scene engine's own
|
|
391
|
-
export const
|
|
392
|
-
args: { fixture: '
|
|
390
|
+
/** The scene engine's own model fixture, graded as drawn. */
|
|
391
|
+
export const Model: Story = {
|
|
392
|
+
args: { fixture: 'model' },
|
|
393
393
|
};
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
useState,
|
|
13
13
|
} from 'react';
|
|
14
14
|
|
|
15
|
-
import { type useRegistry } from '../../hooks/index.ts';
|
|
15
|
+
import { SCENE_OVERLAY_ATTRIBUTE, type useRegistry } from '../../hooks/index.ts';
|
|
16
16
|
import { type ControlPointRef, type Drag, type Handle, type SceneViewAtoms } from '../../model/atoms.ts';
|
|
17
17
|
import { nodeDef } from '../../model/node-def.ts';
|
|
18
18
|
import { type Projection } from '../../model/projection.ts';
|
|
@@ -33,17 +33,18 @@ import {
|
|
|
33
33
|
type SplineLink,
|
|
34
34
|
type Tool,
|
|
35
35
|
endpointNode,
|
|
36
|
+
isFrameNode,
|
|
36
37
|
isPointEndpoint,
|
|
37
|
-
isPortalNode,
|
|
38
38
|
} from '../../model/types.ts';
|
|
39
39
|
import { boundsCenter, panBy, screenToScene } from '../../utils/camera.ts';
|
|
40
40
|
import { duplicateSelection } from '../../utils/clipboard.ts';
|
|
41
41
|
import { boundsFromPoints, hitTest, nodesIntersecting } from '../../utils/hit.ts';
|
|
42
|
+
import { DEFAULT_LAYER, activeLayer } from '../../utils/layers.ts';
|
|
42
43
|
import { topZ } from '../../utils/order.ts';
|
|
43
44
|
import { nodePorts, portAccepts, portPoint } from '../../utils/ports.ts';
|
|
44
45
|
import { resizeBounds } from '../../utils/resize.ts';
|
|
45
46
|
import { insertIndex, linkGeometry, sideToward } from '../../utils/route.ts';
|
|
46
|
-
import {
|
|
47
|
+
import { NOMINAL_SIZES, cloneShape, createLink, createNode, nodeBounds, nominalSize } from '../../utils/shapes.ts';
|
|
47
48
|
import { type LinkEnd, handlePoint } from '../ControlFrame/ControlFrame.tsx';
|
|
48
49
|
import { type SceneCamera } from './useSceneCamera.ts';
|
|
49
50
|
import { type SceneSnap } from './useSceneSnap.ts';
|
|
@@ -91,6 +92,8 @@ export type UsePointerMachineOptions = {
|
|
|
91
92
|
/** Set once the user takes the camera over, which stops the view re-fitting itself. */
|
|
92
93
|
interactedRef: MutableRefObject<boolean>;
|
|
93
94
|
select: (ids: Iterable<ElementId>) => void;
|
|
95
|
+
/** Scene px of one nominal unit (the model's major grid cell), which a new node's default size is counted in. */
|
|
96
|
+
cell: number;
|
|
94
97
|
} & Pick<SceneCamera, 'setCamera' | 'cancelAnimation' | 'isNavigating'> &
|
|
95
98
|
Pick<SceneSnap, 'minor' | 'major' | 'snap' | 'snapMinor'>;
|
|
96
99
|
|
|
@@ -153,6 +156,7 @@ export const usePointerMachine = ({
|
|
|
153
156
|
setCamera,
|
|
154
157
|
cancelAnimation,
|
|
155
158
|
isNavigating,
|
|
159
|
+
cell,
|
|
156
160
|
minor,
|
|
157
161
|
major,
|
|
158
162
|
snap,
|
|
@@ -248,6 +252,17 @@ export const usePointerMachine = ({
|
|
|
248
252
|
[registry, atoms.selection, select],
|
|
249
253
|
);
|
|
250
254
|
|
|
255
|
+
/** Makes a node's embedded content live (`SceneViewAtoms.active`), when it has any. */
|
|
256
|
+
const activate = useCallback(
|
|
257
|
+
(id: NodeId) => {
|
|
258
|
+
// Escaped: a host's node id may hold characters a selector would misread.
|
|
259
|
+
if (rootRef.current?.querySelector(`[data-node-id="${CSS.escape(id)}"] [${SCENE_OVERLAY_ATTRIBUTE}]`)) {
|
|
260
|
+
registry.set(atoms.active, id);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
[rootRef, registry, atoms.active],
|
|
264
|
+
);
|
|
265
|
+
|
|
251
266
|
const onNodePointerDown = useCallback(
|
|
252
267
|
(node: Node, event: PointerEvent) => {
|
|
253
268
|
const currentTool = registry.get(atoms.tool);
|
|
@@ -265,6 +280,12 @@ export const usePointerMachine = ({
|
|
|
265
280
|
return;
|
|
266
281
|
}
|
|
267
282
|
const next = clickSelect(node.id, event);
|
|
283
|
+
// A press on the content of the active node (its own toolbar or editor) starts no move: the drag's pointer
|
|
284
|
+
// capture would take the click from that content's controls. Inactive, the content is inert and the node moves.
|
|
285
|
+
const embedded = event.target instanceof Element && event.target.closest(`[${SCENE_OVERLAY_ATTRIBUTE}]`);
|
|
286
|
+
if (embedded && registry.get(atoms.active) === node.id) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
268
289
|
if (capabilities.move && !node.locked) {
|
|
269
290
|
const { x, y } = nodeBounds(node);
|
|
270
291
|
const ids = [...next].filter((id) => scene.nodes[id] !== undefined);
|
|
@@ -272,9 +293,23 @@ export const usePointerMachine = ({
|
|
|
272
293
|
{ kind: 'move', ids, origin: toScene(event), anchor: { x, y }, delta: { x: 0, y: 0 }, copy: event.metaKey },
|
|
273
294
|
event,
|
|
274
295
|
);
|
|
296
|
+
} else if (next.size === 1) {
|
|
297
|
+
// A node that cannot move has no drag to end in a click, so the press itself activates it.
|
|
298
|
+
activate(node.id);
|
|
275
299
|
}
|
|
276
300
|
},
|
|
277
|
-
[
|
|
301
|
+
[
|
|
302
|
+
registry,
|
|
303
|
+
atoms.tool,
|
|
304
|
+
atoms.active,
|
|
305
|
+
activate,
|
|
306
|
+
clickSelect,
|
|
307
|
+
capabilities.move,
|
|
308
|
+
capabilities.link,
|
|
309
|
+
scene.nodes,
|
|
310
|
+
toScene,
|
|
311
|
+
startDrag,
|
|
312
|
+
],
|
|
278
313
|
);
|
|
279
314
|
|
|
280
315
|
const onLinkPointerDown = useCallback(
|
|
@@ -529,6 +564,7 @@ export const usePointerMachine = ({
|
|
|
529
564
|
const raw = { x: point.x - current.origin.x, y: point.y - current.origin.y };
|
|
530
565
|
setDrag({
|
|
531
566
|
...current,
|
|
567
|
+
raw,
|
|
532
568
|
delta: {
|
|
533
569
|
x: snapMinor(current.anchor.x + raw.x) - current.anchor.x,
|
|
534
570
|
y: snapMinor(current.anchor.y + raw.y) - current.anchor.y,
|
|
@@ -652,7 +688,7 @@ export const usePointerMachine = ({
|
|
|
652
688
|
if (!drag.dropped && (drawn.width === 0 || drawn.height === 0)) {
|
|
653
689
|
return undefined;
|
|
654
690
|
}
|
|
655
|
-
const size = drag.dropped ?
|
|
691
|
+
const size = drag.dropped ? nominalSize(def.defaultSize, cell) : { ...drawn };
|
|
656
692
|
const center = drag.dropped
|
|
657
693
|
? { x: drag.from.x + size.width / 2, y: drag.from.y + size.height / 2 }
|
|
658
694
|
: { x: drawn.x + drawn.width / 2, y: drawn.y + drawn.height / 2 };
|
|
@@ -663,21 +699,27 @@ export const usePointerMachine = ({
|
|
|
663
699
|
pendingRef.current = { type: drag.type, node };
|
|
664
700
|
return node;
|
|
665
701
|
},
|
|
666
|
-
[nodeRegistry, scene.nodes],
|
|
702
|
+
[nodeRegistry, scene.nodes, cell],
|
|
667
703
|
);
|
|
668
704
|
|
|
669
705
|
/** Adds a new node; a new portal opens onto a fresh scene of its own, whichever path created it. */
|
|
670
706
|
const addNode = useCallback(
|
|
671
707
|
(node: Node) => {
|
|
672
|
-
if (
|
|
708
|
+
if (isFrameNode(node)) {
|
|
673
709
|
registry.set(store.scenes, {
|
|
674
710
|
...registry.get(store.scenes),
|
|
675
|
-
[node.scene]: {
|
|
711
|
+
[node.scene]: {
|
|
712
|
+
id: node.scene,
|
|
713
|
+
name: 'Untitled',
|
|
714
|
+
nodes: {},
|
|
715
|
+
links: {},
|
|
716
|
+
layers: { [DEFAULT_LAYER.id]: DEFAULT_LAYER },
|
|
717
|
+
},
|
|
676
718
|
});
|
|
677
719
|
}
|
|
678
|
-
projection.apply({ kind: 'create', node });
|
|
720
|
+
projection.apply({ kind: 'create', node: { ...node, layer: activeLayer(scene, registry.get(atoms.layer)) } });
|
|
679
721
|
},
|
|
680
|
-
[projection, registry, store],
|
|
722
|
+
[projection, registry, store, scene, atoms.layer],
|
|
681
723
|
);
|
|
682
724
|
|
|
683
725
|
/** The node the gesture made, committed: the next gesture starts from a fresh `create`. */
|
|
@@ -722,6 +764,10 @@ export const usePointerMachine = ({
|
|
|
722
764
|
}
|
|
723
765
|
case 'move': {
|
|
724
766
|
if (current.delta.x === 0 && current.delta.y === 0) {
|
|
767
|
+
// A click (a press that never moved) on a node with embedded content makes that content live.
|
|
768
|
+
if (current.ids.length === 1) {
|
|
769
|
+
activate(current.ids[0]);
|
|
770
|
+
}
|
|
725
771
|
break;
|
|
726
772
|
}
|
|
727
773
|
// ⌘-drag leaves the selection where it was and drops a copy, which becomes the selection.
|
|
@@ -750,27 +796,34 @@ export const usePointerMachine = ({
|
|
|
750
796
|
break;
|
|
751
797
|
}
|
|
752
798
|
let target = current.target;
|
|
799
|
+
let created: Node | undefined;
|
|
753
800
|
if (!target && isPointEndpoint(current.source)) {
|
|
754
801
|
// A free-ended link that never reached a node ends free too.
|
|
755
802
|
target = { point: current.to };
|
|
756
803
|
} else if (!target && capabilities.create) {
|
|
757
804
|
// Dropping a link drag on empty canvas creates a node there and links to it (canvas-editor
|
|
758
|
-
// behaviour): a copy of the shape it left, without its text, else a rectangle.
|
|
759
|
-
//
|
|
805
|
+
// behaviour): a copy of the shape it left, without its text, else a rectangle. It is centred on
|
|
806
|
+
// the release point (not the settled end, already snapped to the coarse grid) with its top-left
|
|
807
|
+
// on the minor grid, as a palette drop lands.
|
|
808
|
+
const drop = raw.kind === 'link' ? raw.to : current.to;
|
|
760
809
|
const source = scene.nodes[endpointNode(current.source) ?? ''];
|
|
761
810
|
const def = source ? nodeRegistry[source.type] : undefined;
|
|
762
|
-
const size = source?.size ??
|
|
811
|
+
const size = source?.size ?? nominalSize(NOMINAL_SIZES.rect, cell);
|
|
763
812
|
const props = {
|
|
764
813
|
id: createId(source?.type ?? 'rect'),
|
|
765
814
|
z: topZ(Object.values(scene.nodes)),
|
|
766
815
|
center: {
|
|
767
|
-
x:
|
|
768
|
-
y:
|
|
816
|
+
x: snapMinor(drop.x - size.width / 2) + size.width / 2,
|
|
817
|
+
y: snapMinor(drop.y - size.height / 2) + size.height / 2,
|
|
769
818
|
},
|
|
770
819
|
size,
|
|
771
820
|
};
|
|
772
|
-
const node =
|
|
821
|
+
const node =
|
|
822
|
+
source && def
|
|
823
|
+
? cloneShape(source, def.create(props), nodeDef(nodeRegistry, source)?.parts)
|
|
824
|
+
: createNode({ type: 'rect', ...props });
|
|
773
825
|
addNode(node);
|
|
826
|
+
created = node;
|
|
774
827
|
target = { node: node.id };
|
|
775
828
|
}
|
|
776
829
|
if (target) {
|
|
@@ -782,11 +835,15 @@ export const usePointerMachine = ({
|
|
|
782
835
|
target,
|
|
783
836
|
midpoint: { x: snap((current.from.x + current.to.x) / 2), y: snap((current.from.y + current.to.y) / 2) },
|
|
784
837
|
// A link between ports that declare a direction is drawn with one.
|
|
785
|
-
|
|
838
|
+
ends: isDirected(scene, nodeRegistry, current.source, target) ? { end: 'arrow' } : undefined,
|
|
839
|
+
});
|
|
840
|
+
projection.apply({
|
|
841
|
+
kind: 'link',
|
|
842
|
+
link: { ...link, layer: activeLayer(scene, registry.get(atoms.layer)) },
|
|
786
843
|
});
|
|
787
|
-
|
|
788
|
-
//
|
|
789
|
-
select([link.id]);
|
|
844
|
+
// What the user just made is what they act on next (style, delete): the link, and the node
|
|
845
|
+
// the drop created with it.
|
|
846
|
+
select(created ? [link.id, created.id] : [link.id]);
|
|
790
847
|
}
|
|
791
848
|
break;
|
|
792
849
|
}
|
|
@@ -832,6 +889,9 @@ export const usePointerMachine = ({
|
|
|
832
889
|
commitCreated,
|
|
833
890
|
addNode,
|
|
834
891
|
minor,
|
|
892
|
+
cell,
|
|
893
|
+
snapMinor,
|
|
894
|
+
activate,
|
|
835
895
|
],
|
|
836
896
|
);
|
|
837
897
|
|
|
@@ -15,8 +15,8 @@ const NAVIGATION_SETTLE_MS = 150;
|
|
|
15
15
|
export type SceneCamera = {
|
|
16
16
|
/** Write the camera, from a value or from its predecessor. */
|
|
17
17
|
setCamera: (next: Camera | ((camera: Camera) => Camera)) => void;
|
|
18
|
-
/** Animate to `target`, calling `done` when the last frame lands. */
|
|
19
|
-
animateTo: (target: Camera, done?: () => void) => void;
|
|
18
|
+
/** Animate to `target`, calling `done` when the last frame lands; `duration` fixes its length in ms. */
|
|
19
|
+
animateTo: (target: Camera, done?: () => void, duration?: number) => void;
|
|
20
20
|
/** Abandon an animation in flight, leaving the camera where it reached. */
|
|
21
21
|
cancelAnimation: () => void;
|
|
22
22
|
/** The camera is moving on its own, so the canvas ignores the pointer. */
|
|
@@ -88,14 +88,21 @@ export const useSceneCamera = (
|
|
|
88
88
|
}, [setNavigation]);
|
|
89
89
|
|
|
90
90
|
const animateTo = useCallback(
|
|
91
|
-
(target: Camera, done?: () => void) => {
|
|
91
|
+
(target: Camera, done?: () => void, duration?: number) => {
|
|
92
92
|
cancelAnimation();
|
|
93
93
|
setNavigation(true);
|
|
94
|
-
cancelRef.current = animateCamera(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
cancelRef.current = animateCamera(
|
|
95
|
+
registry.get(atoms.camera),
|
|
96
|
+
target,
|
|
97
|
+
viewport,
|
|
98
|
+
setCamera,
|
|
99
|
+
() => {
|
|
100
|
+
cancelRef.current = undefined;
|
|
101
|
+
setNavigation(false);
|
|
102
|
+
done?.();
|
|
103
|
+
},
|
|
104
|
+
duration,
|
|
105
|
+
);
|
|
99
106
|
},
|
|
100
107
|
[registry, atoms.camera, viewport, setCamera, cancelAnimation, setNavigation],
|
|
101
108
|
);
|
|
@@ -9,6 +9,7 @@ import { type SceneViewAtoms } from '../../model/atoms.ts';
|
|
|
9
9
|
import { type Projection } from '../../model/projection.ts';
|
|
10
10
|
import { type Capabilities, type ElementId, type Point, type Scene } from '../../model/types.ts';
|
|
11
11
|
import { clipboardBounds, copySelection, pasteFragment } from '../../utils/clipboard.ts';
|
|
12
|
+
import { activeLayer } from '../../utils/layers.ts';
|
|
12
13
|
import { between, topZ } from '../../utils/order.ts';
|
|
13
14
|
import { type SceneSnap } from './useSceneSnap.ts';
|
|
14
15
|
|
|
@@ -78,6 +79,7 @@ export const useSceneClipboard = ({
|
|
|
78
79
|
createId,
|
|
79
80
|
nodeZ: () => (nodeZ = between(nodeZ, undefined)),
|
|
80
81
|
linkZ: () => (linkZ = between(linkZ, undefined)),
|
|
82
|
+
layer: activeLayer(scene, registry.get(atoms.layer)),
|
|
81
83
|
});
|
|
82
84
|
projection.apply(intent);
|
|
83
85
|
registry.set(atoms.clipboard, { ...fragment, pasted: at ? fragment.pasted : fragment.pasted + 1 });
|
|
@@ -91,6 +93,8 @@ export const useSceneClipboard = ({
|
|
|
91
93
|
snap,
|
|
92
94
|
scene.nodes,
|
|
93
95
|
scene.links,
|
|
96
|
+
scene,
|
|
97
|
+
atoms.layer,
|
|
94
98
|
projection,
|
|
95
99
|
select,
|
|
96
100
|
createId,
|
|
@@ -11,7 +11,7 @@ import { nodeDef } from '../../model/node-def.ts';
|
|
|
11
11
|
import { type Projection } from '../../model/projection.ts';
|
|
12
12
|
import { type LinkRegistry, type NodeRegistry } from '../../model/registry.ts';
|
|
13
13
|
import { type Bounds, type Capabilities, type ElementId, type Scene, type Size } from '../../model/types.ts';
|
|
14
|
-
import { fitBounds, zoomAt } from '../../utils/camera.ts';
|
|
14
|
+
import { NOMINAL_ZOOM, fitBounds, zoomAt } from '../../utils/camera.ts';
|
|
15
15
|
import { unionBounds } from '../../utils/hit.ts';
|
|
16
16
|
import { nodeBounds } from '../../utils/shapes.ts';
|
|
17
17
|
import { toolForKey } from '../Palette/Palette.tsx';
|
|
@@ -119,18 +119,20 @@ export const useSceneKeys = ({
|
|
|
119
119
|
break;
|
|
120
120
|
}
|
|
121
121
|
case 'fit':
|
|
122
|
-
animateTo(fitBounds(bounds, viewport, inset));
|
|
122
|
+
animateTo(fitBounds(bounds, viewport, inset, NOMINAL_ZOOM));
|
|
123
123
|
event.preventDefault();
|
|
124
124
|
break;
|
|
125
125
|
case 'fitSelection': {
|
|
126
126
|
const union = unionBounds(selectedNodes.map((id) => nodeBounds(scene.nodes[id])));
|
|
127
127
|
if (union) {
|
|
128
|
-
animateTo(fitBounds(union, viewport, inset));
|
|
128
|
+
animateTo(fitBounds(union, viewport, inset, NOMINAL_ZOOM));
|
|
129
129
|
}
|
|
130
130
|
break;
|
|
131
131
|
}
|
|
132
132
|
case 'zoomReset':
|
|
133
|
-
animateTo(
|
|
133
|
+
animateTo(
|
|
134
|
+
zoomAt(registry.get(atoms.camera), { x: viewport.width / 2, y: viewport.height / 2 }, NOMINAL_ZOOM),
|
|
135
|
+
);
|
|
134
136
|
break;
|
|
135
137
|
case 'back':
|
|
136
138
|
goHistory(-1);
|
|
@@ -6,6 +6,8 @@ import { type MutableRefObject, useCallback, useEffect, useMemo, useRef } from '
|
|
|
6
6
|
|
|
7
7
|
import { type useRegistry } from '../../hooks/index.ts';
|
|
8
8
|
import { type Drag, type HistoryEntry, type SceneViewAtoms } from '../../model/atoms.ts';
|
|
9
|
+
import { nodeDef } from '../../model/node-def.ts';
|
|
10
|
+
import { type NodeRegistry } from '../../model/registry.ts';
|
|
9
11
|
import { type SceneStore } from '../../model/store.ts';
|
|
10
12
|
import {
|
|
11
13
|
type Bounds,
|
|
@@ -15,13 +17,27 @@ import {
|
|
|
15
17
|
type Scene,
|
|
16
18
|
type SceneId,
|
|
17
19
|
type Size,
|
|
18
|
-
|
|
20
|
+
isFrameNode,
|
|
19
21
|
} from '../../model/types.ts';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
+
import {
|
|
23
|
+
DRILL_ANIMATION_MS,
|
|
24
|
+
NOMINAL_ZOOM,
|
|
25
|
+
coverage,
|
|
26
|
+
enterPortal,
|
|
27
|
+
exitPortal,
|
|
28
|
+
fitBounds,
|
|
29
|
+
portalFrame,
|
|
30
|
+
} from '../../utils/camera.ts';
|
|
31
|
+
import { contentBounds } from '../../utils/hit.ts';
|
|
22
32
|
import { nodeBounds } from '../../utils/shapes.ts';
|
|
23
33
|
import { type SceneCamera } from './useSceneCamera.ts';
|
|
24
34
|
|
|
35
|
+
/** An empty root has nothing to frame: fitting it centres the origin at true size. */
|
|
36
|
+
const EMPTY_FRAME: Bounds = { x: 0, y: 0, width: 0, height: 0 };
|
|
37
|
+
|
|
38
|
+
/** The shapes of a scene, unpadded: what Fit frames, with its inset as the only margin. */
|
|
39
|
+
const fitFrame = (scene: Scene): Bounds => contentBounds(scene, 0, 1) ?? EMPTY_FRAME;
|
|
40
|
+
|
|
25
41
|
/** A portal covering this much of the viewport becomes the root; a root below this yields to its parent. */
|
|
26
42
|
const AUTO_ENTER = 0.85;
|
|
27
43
|
const AUTO_EXIT = 0.3;
|
|
@@ -30,6 +46,7 @@ const AUTO_DRILL_MS = 150;
|
|
|
30
46
|
export type UseSceneNavigationOptions = {
|
|
31
47
|
registry: ReturnType<typeof useRegistry>;
|
|
32
48
|
atoms: SceneViewAtoms;
|
|
49
|
+
nodeRegistry: NodeRegistry;
|
|
33
50
|
store: SceneStore;
|
|
34
51
|
scenes: Record<SceneId, Scene>;
|
|
35
52
|
scene: Scene;
|
|
@@ -48,13 +65,14 @@ export type SceneNavigation = {
|
|
|
48
65
|
nameOf: (id: SceneId) => string;
|
|
49
66
|
/** The portal in `parent` that shows `childId`, if any. */
|
|
50
67
|
portalTo: (parentId: SceneId | undefined, childId: SceneId) => Node | undefined;
|
|
51
|
-
/**
|
|
68
|
+
/** What fitting the scene at the head of `path` shows: its parent portal's frame, or its content. */
|
|
52
69
|
frameOf: (path: SceneId[], scene: Scene) => Bounds;
|
|
53
70
|
/** The current scene's frame, held for the visit rather than re-derived as its content changes. */
|
|
54
71
|
bounds: Bounds;
|
|
55
|
-
/**
|
|
56
|
-
|
|
72
|
+
/** What Fit shows: the current scene's shapes themselves (the margin is the fit's own inset). */
|
|
73
|
+
fitTarget: Bounds;
|
|
57
74
|
pushHistory: (entry: HistoryEntry) => void;
|
|
75
|
+
/** Drills into a frame's scene, or opens the node in the host when its type has `hostOpen` for it. */
|
|
58
76
|
drillIn: (portal: Node, animate?: boolean) => void;
|
|
59
77
|
drillOut: (levels?: number, animate?: boolean) => void;
|
|
60
78
|
goHistory: (offset: number) => void;
|
|
@@ -68,6 +86,7 @@ export type SceneNavigation = {
|
|
|
68
86
|
export const useSceneNavigation = ({
|
|
69
87
|
registry,
|
|
70
88
|
atoms,
|
|
89
|
+
nodeRegistry,
|
|
71
90
|
scenes,
|
|
72
91
|
scene,
|
|
73
92
|
path,
|
|
@@ -88,7 +107,7 @@ export const useSceneNavigation = ({
|
|
|
88
107
|
(parentId: SceneId | undefined, childId: SceneId): Node | undefined => {
|
|
89
108
|
const parent = parentId ? scenes[parentId] : undefined;
|
|
90
109
|
return parent
|
|
91
|
-
? Object.values(parent.nodes).find((node) =>
|
|
110
|
+
? Object.values(parent.nodes).find((node) => isFrameNode(node) && node.scene === childId)
|
|
92
111
|
: undefined;
|
|
93
112
|
},
|
|
94
113
|
[scenes],
|
|
@@ -97,7 +116,7 @@ export const useSceneNavigation = ({
|
|
|
97
116
|
const frameOf = useCallback(
|
|
98
117
|
(scenePath: SceneId[], current: Scene): Bounds => {
|
|
99
118
|
const portal = portalTo(scenePath[scenePath.length - 2], current.id);
|
|
100
|
-
return portal ? portalFrame(portal, contentBounds(current)) :
|
|
119
|
+
return portal ? portalFrame(portal, contentBounds(current)) : (contentBounds(current) ?? EMPTY_FRAME);
|
|
101
120
|
},
|
|
102
121
|
[portalTo],
|
|
103
122
|
);
|
|
@@ -118,23 +137,7 @@ export const useSceneNavigation = ({
|
|
|
118
137
|
return frameRef.current.frame;
|
|
119
138
|
}, [frameOf, path, scene]);
|
|
120
139
|
|
|
121
|
-
|
|
122
|
-
* A portal frame is the portal's box times a power of the grid ratio, so entering one divides the
|
|
123
|
-
* camera by that factor; reported raw, the number would drop fourfold on a drill-in that changed
|
|
124
|
-
* nothing the user can see. Display only — nothing derives geometry from it, so the frame growing
|
|
125
|
-
* with its content cannot feed back.
|
|
126
|
-
*/
|
|
127
|
-
const nominalZoom = useMemo(() => {
|
|
128
|
-
const scale = path.slice(1).reduce((accumulated, sceneId, index) => {
|
|
129
|
-
const parent = scenes[path[index]];
|
|
130
|
-
const child = scenes[sceneId];
|
|
131
|
-
const portal = parent && child ? portalTo(parent.id, sceneId) : undefined;
|
|
132
|
-
return portal && child
|
|
133
|
-
? accumulated * portalScale(portal, portalFrame(portal, contentBounds(child)))
|
|
134
|
-
: accumulated;
|
|
135
|
-
}, 1);
|
|
136
|
-
return camera.zoom / scale;
|
|
137
|
-
}, [path, scenes, portalTo, camera.zoom]);
|
|
140
|
+
const fitTarget = useMemo(() => fitFrame(scene), [scene]);
|
|
138
141
|
|
|
139
142
|
const pushHistory = useCallback(
|
|
140
143
|
(entry: HistoryEntry) => {
|
|
@@ -145,9 +148,16 @@ export const useSceneNavigation = ({
|
|
|
145
148
|
[registry, atoms.history],
|
|
146
149
|
);
|
|
147
150
|
|
|
151
|
+
const hostOpen = useCallback((node: Node) => nodeDef(nodeRegistry, node)?.hostOpen?.(node), [nodeRegistry]);
|
|
152
|
+
|
|
148
153
|
const drillIn = useCallback(
|
|
149
154
|
(portal: Node, animate = true) => {
|
|
150
|
-
const
|
|
155
|
+
const open = hostOpen(portal);
|
|
156
|
+
if (open) {
|
|
157
|
+
open();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const child = isFrameNode(portal) ? scenes[portal.scene] : undefined;
|
|
151
161
|
if (!child) {
|
|
152
162
|
return;
|
|
153
163
|
}
|
|
@@ -168,14 +178,15 @@ export const useSceneNavigation = ({
|
|
|
168
178
|
// Land the child where fitting it would, margin and all, but never past 1:1, so its text lands at
|
|
169
179
|
// its natural size rather than magnified to fill the view. Expressed in the child's own space and
|
|
170
180
|
// mapped back out, so the zoom ends exactly where the swap puts the camera.
|
|
171
|
-
const target = exitPortal(fitBounds(
|
|
172
|
-
animateTo(target, () => swap(target));
|
|
181
|
+
const target = exitPortal(fitBounds(fitFrame(child), viewport, inset, NOMINAL_ZOOM), portal, childBounds);
|
|
182
|
+
animateTo(target, () => swap(target), DRILL_ANIMATION_MS);
|
|
173
183
|
setOpening(portal.id);
|
|
174
184
|
} else {
|
|
175
185
|
swap(registry.get(atoms.camera));
|
|
176
186
|
}
|
|
177
187
|
},
|
|
178
188
|
[
|
|
189
|
+
hostOpen,
|
|
179
190
|
scenes,
|
|
180
191
|
registry,
|
|
181
192
|
atoms.path,
|
|
@@ -205,7 +216,7 @@ export const useSceneNavigation = ({
|
|
|
205
216
|
const child = scenes[next[next.length - 1]];
|
|
206
217
|
const parent = scenes[next[next.length - 2]];
|
|
207
218
|
const portal = parent
|
|
208
|
-
? Object.values(parent.nodes).find((node) =>
|
|
219
|
+
? Object.values(parent.nodes).find((node) => isFrameNode(node) && node.scene === child?.id)
|
|
209
220
|
: undefined;
|
|
210
221
|
if (!child || !portal) {
|
|
211
222
|
break;
|
|
@@ -220,7 +231,7 @@ export const useSceneNavigation = ({
|
|
|
220
231
|
setCamera(exited);
|
|
221
232
|
const parent = scenes[next[next.length - 1]];
|
|
222
233
|
if (animate && parent) {
|
|
223
|
-
animateTo(fitBounds(
|
|
234
|
+
animateTo(fitBounds(fitFrame(parent), viewport, inset, NOMINAL_ZOOM), undefined, DRILL_ANIMATION_MS);
|
|
224
235
|
}
|
|
225
236
|
pushHistory({ path: next, camera: exited });
|
|
226
237
|
},
|
|
@@ -266,7 +277,7 @@ export const useSceneNavigation = ({
|
|
|
266
277
|
}
|
|
267
278
|
const timer = setTimeout(() => {
|
|
268
279
|
const portal = Object.values(scene.nodes).find(
|
|
269
|
-
(node) =>
|
|
280
|
+
(node) => isFrameNode(node) && !hostOpen(node) && coverage(camera, nodeBounds(node), viewport) >= AUTO_ENTER,
|
|
270
281
|
);
|
|
271
282
|
if (portal) {
|
|
272
283
|
drillIn(portal, false);
|
|
@@ -282,7 +293,7 @@ export const useSceneNavigation = ({
|
|
|
282
293
|
}
|
|
283
294
|
}, AUTO_DRILL_MS);
|
|
284
295
|
return () => clearTimeout(timer);
|
|
285
|
-
}, [camera, scene, bounds, path, viewport, drag, drillIn, drillOut, registry, atoms.history, isAnimating]);
|
|
296
|
+
}, [camera, scene, bounds, path, viewport, drag, hostOpen, drillIn, drillOut, registry, atoms.history, isAnimating]);
|
|
286
297
|
|
|
287
|
-
return { nameOf, portalTo, frameOf, bounds,
|
|
298
|
+
return { nameOf, portalTo, frameOf, bounds, fitTarget, pushHistory, drillIn, drillOut, goHistory };
|
|
288
299
|
};
|