@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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
|
+
import { log } from '@dxos/log';
|
|
8
9
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
9
10
|
|
|
10
11
|
import { freehandCapabilities } from '../../model/projection.ts';
|
|
@@ -14,17 +15,16 @@ import { ActionToolbar, CameraToolbar, NavigationToolbar, type ToolbarActions }
|
|
|
14
15
|
|
|
15
16
|
/** The bar over a fake view: every action appends to a log so each button is seen to fire. */
|
|
16
17
|
const DefaultStory = () => {
|
|
17
|
-
const [log, setLog] = useState<string[]>([]);
|
|
18
18
|
const [snap, setSnap] = useState(true);
|
|
19
19
|
const [guides, setGuides] = useState(true);
|
|
20
20
|
const [debug, setDebug] = useState(false);
|
|
21
21
|
const [zoom, setZoom] = useState(1);
|
|
22
|
-
const note = (entry: string) => setLog((entries) => [entry, ...entries].slice(0, 12));
|
|
23
22
|
const actions: ToolbarActions = {
|
|
24
23
|
path: ['scene:root', 'scene:root/L'],
|
|
25
24
|
nameOf: (id) => id.split(':')[1] ?? id,
|
|
26
|
-
onPath: (index) =>
|
|
27
|
-
fit: () =>
|
|
25
|
+
onPath: (index) => log(`path → ${index}`),
|
|
26
|
+
fit: () => log('fit'),
|
|
27
|
+
zoomReset: () => setZoom(1),
|
|
28
28
|
zoomIn: () => setZoom((value) => value * 1.25),
|
|
29
29
|
zoomOut: () => setZoom((value) => value / 1.25),
|
|
30
30
|
snap,
|
|
@@ -35,34 +35,33 @@ const DefaultStory = () => {
|
|
|
35
35
|
toggleDebug: () => setDebug((value) => !value),
|
|
36
36
|
canUndo: true,
|
|
37
37
|
canRedo: false,
|
|
38
|
-
undo: () =>
|
|
39
|
-
redo: () =>
|
|
38
|
+
undo: () => log('undo'),
|
|
39
|
+
redo: () => log('redo'),
|
|
40
40
|
hasSelection: true,
|
|
41
41
|
hasClipboard: true,
|
|
42
|
-
cut: () =>
|
|
43
|
-
copy: () =>
|
|
44
|
-
paste: () =>
|
|
45
|
-
delete: () =>
|
|
46
|
-
create: (type: NodeType) =>
|
|
42
|
+
cut: () => log('cut'),
|
|
43
|
+
copy: () => log('copy'),
|
|
44
|
+
paste: () => log('paste'),
|
|
45
|
+
delete: () => log('delete'),
|
|
46
|
+
create: (type: NodeType) => log(`create ${type}`),
|
|
47
47
|
};
|
|
48
48
|
return (
|
|
49
49
|
// `items-start`: each bar fills its box, as SceneView floats it, so the column must not stretch them.
|
|
50
50
|
<div className='flex flex-col items-start gap-2 p-2'>
|
|
51
|
-
<NavigationToolbar actions={actions}
|
|
51
|
+
<NavigationToolbar actions={actions} />
|
|
52
52
|
<ActionToolbar actions={actions} nodes={defaultNodeRegistry} capabilities={freehandCapabilities} />
|
|
53
53
|
<CameraToolbar actions={actions}>
|
|
54
54
|
{Math.round(zoom * 100)}% · snap {snap ? 'on' : 'off'} · guides {guides ? 'on' : 'off'} · debug{' '}
|
|
55
55
|
{debug ? 'on' : 'off'}
|
|
56
56
|
</CameraToolbar>
|
|
57
|
-
<pre className='text-xs text-fg-muted'>{log.join('\n')}</pre>
|
|
58
57
|
</div>
|
|
59
58
|
);
|
|
60
59
|
};
|
|
61
60
|
|
|
62
61
|
const meta: Meta = {
|
|
63
|
-
title: 'ui/react-ui-canvas/
|
|
62
|
+
title: 'ui/react-ui-canvas/Toolbar',
|
|
64
63
|
render: DefaultStory,
|
|
65
|
-
decorators: [withTheme(), withLayout({ layout: '
|
|
64
|
+
decorators: [withTheme(), withLayout({ layout: 'centered', classNames: 'w-[30rem]' })],
|
|
66
65
|
};
|
|
67
66
|
|
|
68
67
|
export default meta;
|
|
@@ -29,6 +29,8 @@ export type ToolbarActions = {
|
|
|
29
29
|
nameOf: (id: SceneId) => string;
|
|
30
30
|
onPath: (index: number) => void;
|
|
31
31
|
fit: () => void;
|
|
32
|
+
/** Back to true size (100%), about the view's centre. */
|
|
33
|
+
zoomReset: () => void;
|
|
32
34
|
zoomIn: () => void;
|
|
33
35
|
zoomOut: () => void;
|
|
34
36
|
snap: boolean;
|
|
@@ -75,12 +77,13 @@ export const NavigationToolbar = ({ classNames, actions, children }: NavigationT
|
|
|
75
77
|
<Toolbar.Root size='sm' classNames={mx(barClasses, classNames)} data-testid='canvas-toolbar'>
|
|
76
78
|
<Button.Root
|
|
77
79
|
variant='ghost'
|
|
80
|
+
iconOnly
|
|
81
|
+
icon='ph--arrow-up--regular'
|
|
82
|
+
label='Up'
|
|
78
83
|
disabled={path.length < 2}
|
|
79
84
|
data-testid='toolbar-up'
|
|
80
85
|
onClick={() => actions.onPath(path.length - 2)}
|
|
81
|
-
|
|
82
|
-
Up
|
|
83
|
-
</Button.Root>
|
|
86
|
+
/>
|
|
84
87
|
<Breadcrumbs path={path} nameOf={actions.nameOf} onSelect={actions.onPath} />
|
|
85
88
|
{children && (
|
|
86
89
|
<>
|
|
@@ -106,6 +109,14 @@ export const CameraToolbar = ({ classNames, actions, children }: CameraToolbarPr
|
|
|
106
109
|
data-testid='toolbar-fit'
|
|
107
110
|
onClick={actions.fit}
|
|
108
111
|
/>
|
|
112
|
+
<Button.Root
|
|
113
|
+
variant='ghost'
|
|
114
|
+
iconOnly
|
|
115
|
+
icon='ph--number-square-one--regular'
|
|
116
|
+
label={`Actual size (${shortcutFor('zoomReset')})`}
|
|
117
|
+
data-testid='toolbar-zoom-reset'
|
|
118
|
+
onClick={actions.zoomReset}
|
|
119
|
+
/>
|
|
109
120
|
<Button.Root
|
|
110
121
|
variant='ghost'
|
|
111
122
|
iconOnly
|
|
@@ -151,7 +162,7 @@ export const ActionToolbar = ({ classNames, actions, nodes, capabilities }: Acti
|
|
|
151
162
|
variant='ghost'
|
|
152
163
|
iconOnly
|
|
153
164
|
icon='ph--frame-corners--regular'
|
|
154
|
-
label={`Guides (${shortcutFor('guides')}): show the
|
|
165
|
+
label={`Guides (${shortcutFor('guides')}): show the lattice cells`}
|
|
155
166
|
classNames={mx(actions.guides && 'bg-primary-500/20')}
|
|
156
167
|
data-testid='toolbar-guides'
|
|
157
168
|
onClick={actions.toggleGuides}
|
package/src/components/index.ts
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
export * from './About/index.ts';
|
|
5
6
|
export * from './Breadcrumbs/index.ts';
|
|
6
7
|
export * from './ControlFrame/index.ts';
|
|
7
8
|
export * from './Grid/index.ts';
|
|
8
9
|
export * from './LatticeGrid/index.ts';
|
|
10
|
+
export * from './Layers/index.ts';
|
|
9
11
|
export * from './Palette/index.ts';
|
|
10
12
|
export * from './PartEditor/index.ts';
|
|
11
13
|
export * from './Properties/index.ts';
|
package/src/hooks/useWheel.ts
CHANGED
|
@@ -6,6 +6,31 @@ import { type RefObject, useEffect } from 'react';
|
|
|
6
6
|
|
|
7
7
|
export type WheelHandler = (event: WheelEvent, pointer: { x: number; y: number }) => void;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Marks content whose own scrolling takes the wheel rather than the canvas panning: a floating panel over the canvas
|
|
11
|
+
* (the properties form), or a host's content embedded in a node (an object's surface in a frame).
|
|
12
|
+
*/
|
|
13
|
+
export const SCENE_OVERLAY_ATTRIBUTE = 'data-scene-overlay';
|
|
14
|
+
|
|
15
|
+
const SCROLLS = /(auto|scroll)/;
|
|
16
|
+
|
|
17
|
+
/** Whether an element from `target` up to `overlay` scrolls, so the wheel is its own rather than the canvas's. */
|
|
18
|
+
const scrollsWithin = (target: Element, overlay: Element): boolean => {
|
|
19
|
+
for (let element: Element | null = target; element; element = element.parentElement) {
|
|
20
|
+
const { overflowX, overflowY } = getComputedStyle(element);
|
|
21
|
+
if (
|
|
22
|
+
(SCROLLS.test(overflowY) && element.scrollHeight > element.clientHeight) ||
|
|
23
|
+
(SCROLLS.test(overflowX) && element.scrollWidth > element.clientWidth)
|
|
24
|
+
) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (element === overlay) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
|
|
9
34
|
/** Non-passive wheel listener with the pointer in element coordinates, so the page never scrolls. */
|
|
10
35
|
export const useWheel = (ref: RefObject<HTMLElement | null>, handler: WheelHandler) => {
|
|
11
36
|
useEffect(() => {
|
|
@@ -14,6 +39,16 @@ export const useWheel = (ref: RefObject<HTMLElement | null>, handler: WheelHandl
|
|
|
14
39
|
return;
|
|
15
40
|
}
|
|
16
41
|
const onWheel = (event: WheelEvent) => {
|
|
42
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
43
|
+
const overlay = target?.closest(`[${SCENE_OVERLAY_ATTRIBUTE}]`);
|
|
44
|
+
// Content embedded in a node with nothing to scroll (a card that fits its frame) still lets the canvas pan.
|
|
45
|
+
if (target && overlay && (!overlay.closest('[data-node-id]') || scrollsWithin(target, overlay))) {
|
|
46
|
+
// A pinch over a panel still must not zoom the page; a scroll is the panel's own.
|
|
47
|
+
if (event.ctrlKey || event.metaKey) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
17
52
|
event.preventDefault();
|
|
18
53
|
const rect = element.getBoundingClientRect();
|
|
19
54
|
handler(event, { x: event.clientX - rect.left, y: event.clientY - rect.top });
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export * from './
|
|
5
|
+
export * from './components/index.ts';
|
|
6
|
+
export * from './hooks/index.ts';
|
|
7
|
+
export * from './model/index.ts';
|
|
8
|
+
export * from './utils/index.ts';
|
package/src/model/atoms.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type Camera,
|
|
18
18
|
type ElementId,
|
|
19
19
|
type Endpoint,
|
|
20
|
+
type LayerId,
|
|
20
21
|
type LinkId,
|
|
21
22
|
type LinkType,
|
|
22
23
|
type NodeId,
|
|
@@ -41,9 +42,10 @@ export type Drag =
|
|
|
41
42
|
/**
|
|
42
43
|
* Moving the selection; `anchor` is the pressed node's top-left, which is what snaps to the grid,
|
|
43
44
|
* and `delta` the resulting scene-space offset applied transiently to every selected node. With
|
|
44
|
-
* `copy` (⌘ held) the originals stay and copies land at `delta` instead.
|
|
45
|
+
* `copy` (⌘ held) the originals stay and copies land at `delta` instead. `raw` is the pointer's own offset, which
|
|
46
|
+
* the nodes follow while the drag is in flight; `delta` is where they land.
|
|
45
47
|
*/
|
|
46
|
-
| { kind: 'move'; ids: NodeId[]; origin: Point; anchor: Point; delta: Point; copy?: boolean }
|
|
48
|
+
| { kind: 'move'; ids: NodeId[]; origin: Point; anchor: Point; delta: Point; raw?: Point; copy?: boolean }
|
|
47
49
|
/** Resizing one node by a handle; `bounds` is the transient result. */
|
|
48
50
|
| { kind: 'resize'; id: NodeId; handle: Handle; start: Bounds; bounds: Bounds }
|
|
49
51
|
/**
|
|
@@ -61,6 +63,13 @@ export type Drag =
|
|
|
61
63
|
/** Re-attaching one end of a link; `fixed` is the other end's resolved port for the rubber band. */
|
|
62
64
|
| { kind: 'end'; id: LinkId; end: 'source' | 'target'; fixed: Point; fixedSide: Side; to: Point; target?: Endpoint };
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Whether a move has left where it was pressed: until it does, a press on a selected node is a click, and the
|
|
68
|
+
* selection keeps its outline and handles rather than flickering off and on.
|
|
69
|
+
*/
|
|
70
|
+
export const isMoving = (drag: Drag | undefined): boolean =>
|
|
71
|
+
drag?.kind === 'move' && drag.raw !== undefined && (drag.raw.x !== 0 || drag.raw.y !== 0);
|
|
72
|
+
|
|
64
73
|
export type HistoryEntry = { path: SceneId[]; camera: Camera };
|
|
65
74
|
|
|
66
75
|
export type ControlPointRef = { link: LinkId; index: number };
|
|
@@ -68,11 +77,18 @@ export type ControlPointRef = { link: LinkId; index: number };
|
|
|
68
77
|
/** The text part being edited in place (`parts.ts`). */
|
|
69
78
|
export type EditingPart = { id: NodeId; part: PartKey };
|
|
70
79
|
|
|
80
|
+
/** Where the properties and layers panels sit: floating over the canvas, or docked in a column beside it. */
|
|
81
|
+
export type PanelMode = 'floating' | 'docked';
|
|
82
|
+
|
|
71
83
|
export type SceneViewAtoms = {
|
|
72
84
|
camera: Atom.Writable<Camera>;
|
|
73
85
|
path: Atom.Writable<SceneId[]>;
|
|
74
86
|
selection: Atom.Writable<ReadonlySet<ElementId>>;
|
|
75
87
|
hover: Atom.Writable<NodeId | undefined>;
|
|
88
|
+
/** The link under the pointer, which shows its end handles as a selected one does. */
|
|
89
|
+
linkHover: Atom.Writable<LinkId | undefined>;
|
|
90
|
+
/** The layer new shapes and links go on; unset (or a layer the scene does not have), the top one. */
|
|
91
|
+
layer: Atom.Writable<LayerId | undefined>;
|
|
76
92
|
/** The selected control point of a selected spline, if any. */
|
|
77
93
|
point: Atom.Writable<ControlPointRef | undefined>;
|
|
78
94
|
tool: Atom.Writable<Tool>;
|
|
@@ -80,7 +96,7 @@ export type SceneViewAtoms = {
|
|
|
80
96
|
linkType: Atom.Writable<LinkType>;
|
|
81
97
|
/** Grid shown and moves/resizes snapped to it. */
|
|
82
98
|
snap: Atom.Writable<boolean>;
|
|
83
|
-
/** Guides shown:
|
|
99
|
+
/** Guides shown: on a lattice, its cells. */
|
|
84
100
|
guides: Atom.Writable<boolean>;
|
|
85
101
|
/** On a lattice scene, snap lands on the lattice's cells rather than the basic grid. */
|
|
86
102
|
lattice: Atom.Writable<boolean>;
|
|
@@ -91,6 +107,11 @@ export type SceneViewAtoms = {
|
|
|
91
107
|
/** The last cut or copied fragment (`clipboard.ts`). */
|
|
92
108
|
clipboard: Atom.Writable<Clipboard | undefined>;
|
|
93
109
|
editing: Atom.Writable<EditingPart | undefined>;
|
|
110
|
+
/**
|
|
111
|
+
* The node whose embedded content takes input (an editor in a frame), entered by clicking the node; any other
|
|
112
|
+
* node's embedded content is inert, so a press anywhere on it selects and moves it.
|
|
113
|
+
*/
|
|
114
|
+
active: Atom.Writable<NodeId | undefined>;
|
|
94
115
|
/** Frames show their id, type and geometry. */
|
|
95
116
|
debug: Atom.Writable<boolean>;
|
|
96
117
|
};
|
|
@@ -104,6 +125,8 @@ export const createSceneViewAtoms = (root: SceneId): SceneViewAtoms => ({
|
|
|
104
125
|
path: Atom.keepAlive(Atom.make<SceneId[]>([root])),
|
|
105
126
|
selection: Atom.keepAlive(Atom.make<ReadonlySet<ElementId>>(new Set<ElementId>())),
|
|
106
127
|
hover: Atom.keepAlive(Atom.make<NodeId | undefined>(undefined)),
|
|
128
|
+
linkHover: Atom.keepAlive(Atom.make<LinkId | undefined>(undefined)),
|
|
129
|
+
layer: Atom.keepAlive(Atom.make<LayerId | undefined>(undefined)),
|
|
107
130
|
point: Atom.keepAlive(Atom.make<ControlPointRef | undefined>(undefined)),
|
|
108
131
|
tool: Atom.keepAlive(Atom.make<Tool>({ kind: 'select' })),
|
|
109
132
|
linkType: Atom.keepAlive(Atom.make<LinkType>('curve')),
|
|
@@ -115,5 +138,6 @@ export const createSceneViewAtoms = (root: SceneId): SceneViewAtoms => ({
|
|
|
115
138
|
undo: Atom.keepAlive(Atom.make<UndoState>(emptyUndo())),
|
|
116
139
|
clipboard: Atom.keepAlive(Atom.make<Clipboard | undefined>(undefined)),
|
|
117
140
|
editing: Atom.keepAlive(Atom.make<EditingPart | undefined>(undefined)),
|
|
141
|
+
active: Atom.keepAlive(Atom.make<NodeId | undefined>(undefined)),
|
|
118
142
|
debug: Atom.keepAlive(Atom.make<boolean>(false)),
|
|
119
143
|
});
|
package/src/model/projection.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type * as Registry from 'effect/reactivity/AtomRegistry';
|
|
|
13
13
|
import * as Schema from 'effect/Schema';
|
|
14
14
|
|
|
15
15
|
import { type LatticeSpec } from '../utils/lattice.ts';
|
|
16
|
+
import { reduceLayerIntent } from '../utils/layers.ts';
|
|
16
17
|
import { layoutScene } from '../utils/layout.ts';
|
|
17
18
|
import { resizeNode } from '../utils/shapes.ts';
|
|
18
19
|
import { type SceneStore, putScene, updateScene } from './store.ts';
|
|
@@ -136,6 +137,10 @@ export const reduceIntent = (scene: Scene, intent: Intent): Scene => {
|
|
|
136
137
|
case 'batch':
|
|
137
138
|
return intent.intents.reduce(reduceIntent, scene);
|
|
138
139
|
|
|
140
|
+
case 'layer':
|
|
141
|
+
case 'removeLayer':
|
|
142
|
+
return reduceLayerIntent(scene, intent);
|
|
143
|
+
|
|
139
144
|
case 'update': {
|
|
140
145
|
const node = scene.nodes[intent.id];
|
|
141
146
|
if (node) {
|
|
@@ -6,7 +6,7 @@ import * as Atom from 'effect/reactivity/Atom';
|
|
|
6
6
|
import * as Registry from 'effect/reactivity/AtomRegistry';
|
|
7
7
|
import { describe, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import { type
|
|
9
|
+
import { type EllipseNode, type NoteNode, type Scene, isEllipseNode, isNoteNode } from '../types.ts';
|
|
10
10
|
import { type ConstrainedModel, createConstrainedProjection, rewriteForDrop, solve } from './constrained.ts';
|
|
11
11
|
|
|
12
12
|
const model: ConstrainedModel = {
|
|
@@ -115,24 +115,32 @@ describe('constrained projection', () => {
|
|
|
115
115
|
size: { width: 128, height: 64 },
|
|
116
116
|
label: 'Round',
|
|
117
117
|
};
|
|
118
|
-
const
|
|
119
|
-
type: '
|
|
118
|
+
const note: NoteNode = {
|
|
119
|
+
type: 'note',
|
|
120
120
|
id: 'K',
|
|
121
121
|
z: 'z',
|
|
122
122
|
center: { x: 0, y: 400 },
|
|
123
123
|
size: { width: 1, height: 1 },
|
|
124
|
-
|
|
125
|
-
attributes: [],
|
|
126
|
-
methods: [],
|
|
124
|
+
text: 'Klass',
|
|
127
125
|
};
|
|
128
126
|
projection.apply({ kind: 'create', node: ellipse });
|
|
129
|
-
projection.apply({ kind: 'create', node:
|
|
127
|
+
projection.apply({ kind: 'create', node: note });
|
|
130
128
|
const scene = registry.get(projection.scene);
|
|
131
129
|
expect(scene.nodes.E.type).toBe('ellipse');
|
|
132
130
|
expect(isEllipseNode(scene.nodes.E) && scene.nodes.E.label).toBe('Round');
|
|
133
|
-
expect(
|
|
134
|
-
projection.apply({ kind: 'update', id: 'K', values: {
|
|
131
|
+
expect(isNoteNode(scene.nodes.K) && scene.nodes.K.text).toBe('Klass');
|
|
132
|
+
projection.apply({ kind: 'update', id: 'K', values: { text: 'Renamed' } });
|
|
135
133
|
const after = registry.get(projection.scene).nodes.K;
|
|
136
|
-
expect(
|
|
134
|
+
expect(isNoteNode(after) && after.text).toBe('Renamed');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('update records style alongside the label', ({ expect }) => {
|
|
138
|
+
const registry = Registry.make();
|
|
139
|
+
const atom = Atom.keepAlive(Atom.make<ConstrainedModel>(model));
|
|
140
|
+
const projection = createConstrainedProjection({ registry, model: atom });
|
|
141
|
+
projection.apply({ kind: 'update', id: 'A', values: { style: { hue: 'teal', rounded: true } } });
|
|
142
|
+
expect(registry.get(projection.scene).nodes.A.style).toEqual({ hue: 'teal', rounded: true });
|
|
143
|
+
projection.apply({ kind: 'update', id: 'A', values: { label: 'Alpha' } });
|
|
144
|
+
expect(registry.get(projection.scene).nodes.A.style).toEqual({ hue: 'teal', rounded: true });
|
|
137
145
|
});
|
|
138
146
|
});
|
|
@@ -15,18 +15,21 @@ import { Layout } from '@dxos/diagram';
|
|
|
15
15
|
|
|
16
16
|
import { centeredOrigin } from '../../utils/layout.ts';
|
|
17
17
|
import { initialKeys } from '../../utils/order.ts';
|
|
18
|
-
import {
|
|
18
|
+
import { withTitle } from '../../utils/parts.ts';
|
|
19
|
+
import { createNode } from '../../utils/shapes.ts';
|
|
19
20
|
import { type Projection } from '../projection.ts';
|
|
21
|
+
import { defaultNodeRegistry } from '../registry.ts';
|
|
20
22
|
import {
|
|
21
23
|
type BuiltinNodeType,
|
|
22
24
|
type Capabilities,
|
|
23
25
|
type Intent,
|
|
24
26
|
type Node,
|
|
27
|
+
type NodeStyle,
|
|
25
28
|
type Point,
|
|
26
29
|
type Scene,
|
|
27
30
|
type Size,
|
|
28
31
|
isBuiltinNode,
|
|
29
|
-
|
|
32
|
+
isFrameNode,
|
|
30
33
|
} from '../types.ts';
|
|
31
34
|
|
|
32
35
|
/** `subject <relation> object`: "A east of B", "A aligned with B" (same row). */
|
|
@@ -35,7 +38,7 @@ export type Relation = 'east' | 'west' | 'north' | 'south' | 'aligned';
|
|
|
35
38
|
export type Constraint = { subject: string; relation: Relation; object: string };
|
|
36
39
|
|
|
37
40
|
/** `type` is the node's shape; the solver places every type on the same grid. */
|
|
38
|
-
export type ConstrainedNode = { id: string; label?: string; type?: BuiltinNodeType };
|
|
41
|
+
export type ConstrainedNode = { id: string; label?: string; type?: BuiltinNodeType; style?: NodeStyle };
|
|
39
42
|
|
|
40
43
|
export type ConstrainedModel = {
|
|
41
44
|
nodes: ConstrainedNode[];
|
|
@@ -192,10 +195,12 @@ export const solve = (model: ConstrainedModel, options: ConstrainedOptions = {})
|
|
|
192
195
|
x: origin.x + (columns.get(node.id) ?? 0) * pitch.width,
|
|
193
196
|
y: origin.y + (rows.get(node.id) ?? 0) * pitch.height,
|
|
194
197
|
};
|
|
195
|
-
|
|
198
|
+
const solved = withTitle(
|
|
199
|
+
defaultNodeRegistry,
|
|
196
200
|
createNode({ type: node.type ?? 'rect', id: node.id, z: keys[index], center, size }),
|
|
197
201
|
node.label ?? node.id,
|
|
198
202
|
);
|
|
203
|
+
nodes[node.id] = node.style ? { ...solved, style: node.style } : solved;
|
|
199
204
|
});
|
|
200
205
|
return { id: CONSTRAINED_SCENE_ID, name: 'Constrained', nodes, links: {} };
|
|
201
206
|
};
|
|
@@ -281,13 +286,14 @@ export const createConstrainedProjection = ({ registry, model, options }: Constr
|
|
|
281
286
|
}
|
|
282
287
|
case 'create': {
|
|
283
288
|
// The model records built-in types only; a portal or a host type has no place in it and is refused.
|
|
284
|
-
if (!isBuiltinNode(intent.node) ||
|
|
289
|
+
if (!isBuiltinNode(intent.node) || isFrameNode(intent.node)) {
|
|
285
290
|
return;
|
|
286
291
|
}
|
|
287
292
|
const node: ConstrainedNode = {
|
|
288
293
|
id: intent.node.id,
|
|
289
294
|
type: intent.node.type,
|
|
290
295
|
label: labelOf(intent.node),
|
|
296
|
+
style: intent.node.style,
|
|
291
297
|
};
|
|
292
298
|
const added = { ...current, nodes: [...current.nodes, node] };
|
|
293
299
|
// Constrain the new node as if it had been dropped where it was drawn.
|
|
@@ -308,12 +314,17 @@ export const createConstrainedProjection = ({ registry, model, options }: Constr
|
|
|
308
314
|
intent.intents.forEach(apply);
|
|
309
315
|
break;
|
|
310
316
|
case 'update': {
|
|
311
|
-
// Only the label
|
|
317
|
+
// Only the label and style live in the model; geometry is solved, so those edits are dropped.
|
|
312
318
|
const label = labelOf(intent.values);
|
|
313
|
-
|
|
319
|
+
const style = 'style' in intent.values ? intent.values.style : undefined;
|
|
320
|
+
if (label !== undefined || style !== undefined) {
|
|
314
321
|
registry.set(model, {
|
|
315
322
|
...current,
|
|
316
|
-
nodes: current.nodes.map((node) =>
|
|
323
|
+
nodes: current.nodes.map((node) =>
|
|
324
|
+
node.id === intent.id
|
|
325
|
+
? { ...node, ...(label !== undefined && { label }), ...(style !== undefined && { style }) }
|
|
326
|
+
: node,
|
|
327
|
+
),
|
|
317
328
|
});
|
|
318
329
|
}
|
|
319
330
|
break;
|
|
@@ -16,8 +16,10 @@ import { Layout } from '@dxos/diagram';
|
|
|
16
16
|
|
|
17
17
|
import { centeredOrigin } from '../../utils/layout.ts';
|
|
18
18
|
import { initialKeys } from '../../utils/order.ts';
|
|
19
|
-
import {
|
|
19
|
+
import { withTitle } from '../../utils/parts.ts';
|
|
20
|
+
import { createNode } from '../../utils/shapes.ts';
|
|
20
21
|
import { type Projection } from '../projection.ts';
|
|
22
|
+
import { defaultNodeRegistry } from '../registry.ts';
|
|
21
23
|
import {
|
|
22
24
|
type BuiltinNodeType,
|
|
23
25
|
type Capabilities,
|
|
@@ -94,7 +96,8 @@ export const layoutGraph = (graph: GraphModel, overlay: Overlay, options: Dynami
|
|
|
94
96
|
x: origin.x + (columns.get(node.id) ?? 0) * pitch.width,
|
|
95
97
|
y: origin.y + (ranks.get(node.id) ?? 0) * pitch.height,
|
|
96
98
|
};
|
|
97
|
-
nodes[node.id] =
|
|
99
|
+
nodes[node.id] = withTitle(
|
|
100
|
+
defaultNodeRegistry,
|
|
98
101
|
createNode({ type: node.type ?? 'rect', id: node.id, z: keys[graph.edges.length + index], center, size }),
|
|
99
102
|
node.label ?? node.id,
|
|
100
103
|
);
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
collides,
|
|
16
16
|
coveredCells,
|
|
17
17
|
occupancy,
|
|
18
|
+
onLattice,
|
|
18
19
|
quantize,
|
|
19
20
|
resizeCell,
|
|
20
21
|
toCell,
|
|
@@ -60,6 +61,9 @@ const blocked = (scene: Scene, spec: LatticeSpec, bounds: Bounds, except: Readon
|
|
|
60
61
|
export const constrainIntent = (scene: Scene, intent: Intent, spec: LatticeSpec): Intent | undefined => {
|
|
61
62
|
switch (intent.kind) {
|
|
62
63
|
case 'create': {
|
|
64
|
+
if (!onLattice(intent.node)) {
|
|
65
|
+
return intent;
|
|
66
|
+
}
|
|
63
67
|
const bounds = quantize(nodeBounds(intent.node), spec);
|
|
64
68
|
return blocked(scene, spec, bounds, new Set())
|
|
65
69
|
? undefined
|
|
@@ -69,6 +73,9 @@ export const constrainIntent = (scene: Scene, intent: Intent, spec: LatticeSpec)
|
|
|
69
73
|
case 'resize': {
|
|
70
74
|
// The dragged edge snaps to a cell edge and the opposite edge stays, so a face steps one cell at a time.
|
|
71
75
|
const node = scene.nodes[intent.id];
|
|
76
|
+
if (node && !onLattice(node)) {
|
|
77
|
+
return intent;
|
|
78
|
+
}
|
|
72
79
|
const from = node && nodeBounds(node);
|
|
73
80
|
const bounds = from
|
|
74
81
|
? cellBounds(resizeCell(toCell(from, spec), from, intent.bounds, spec), spec)
|
|
@@ -81,10 +88,15 @@ export const constrainIntent = (scene: Scene, intent: Intent, spec: LatticeSpec)
|
|
|
81
88
|
const moving = new Set(intent.ids);
|
|
82
89
|
const others = occupancy(Object.values(scene.nodes), spec, moving);
|
|
83
90
|
const taken = new Map<string, ElementId>();
|
|
84
|
-
|
|
91
|
+
// A node off the lattice (a group's backdrop) moves by the drag as is, beside the snapped ones.
|
|
92
|
+
const free = intent.ids.filter((id) => {
|
|
93
|
+
const node = scene.nodes[id];
|
|
94
|
+
return node !== undefined && !node.locked && !onLattice(node);
|
|
95
|
+
});
|
|
96
|
+
const intents: Intent[] = free.length > 0 ? [{ kind: 'move', ids: free, delta: intent.delta }] : [];
|
|
85
97
|
for (const id of intent.ids) {
|
|
86
98
|
const node = scene.nodes[id];
|
|
87
|
-
if (!node || node.locked) {
|
|
99
|
+
if (!node || node.locked || !onLattice(node)) {
|
|
88
100
|
continue;
|
|
89
101
|
}
|
|
90
102
|
const from = nodeBounds(node);
|
|
@@ -103,7 +115,7 @@ export const constrainIntent = (scene: Scene, intent: Intent, spec: LatticeSpec)
|
|
|
103
115
|
|
|
104
116
|
case 'update': {
|
|
105
117
|
const node = scene.nodes[intent.id];
|
|
106
|
-
const bounds = node && updatedBounds(node, intent.values);
|
|
118
|
+
const bounds = node && onLattice(node) ? updatedBounds(node, intent.values) : undefined;
|
|
107
119
|
if (!node || !bounds) {
|
|
108
120
|
return intent;
|
|
109
121
|
}
|
|
@@ -7,7 +7,7 @@ import { describe, test } from 'vitest';
|
|
|
7
7
|
|
|
8
8
|
import { UnknownNodeView } from '../components/SceneLayer/SceneLayer.tsx';
|
|
9
9
|
import { nodePorts } from '../utils/ports.ts';
|
|
10
|
-
import { createNode, nodeBounds } from '../utils/shapes.ts';
|
|
10
|
+
import { createNode, nodeBounds, nominalSize } from '../utils/shapes.ts';
|
|
11
11
|
import { nodeDef } from './node-def.ts';
|
|
12
12
|
import {
|
|
13
13
|
type CreateProps,
|
|
@@ -26,12 +26,12 @@ const functionDef: NodeDef = {
|
|
|
26
26
|
type: 'function',
|
|
27
27
|
name: 'Function',
|
|
28
28
|
icon: 'ph--function--regular',
|
|
29
|
-
key: '
|
|
29
|
+
key: 'U',
|
|
30
30
|
group: 'compute',
|
|
31
31
|
schema: FunctionNode,
|
|
32
32
|
component: UnknownNodeView,
|
|
33
33
|
create: ({ id, z, center, size }) => ({ type: 'function', id, z, center, size, fn: 'identity' }),
|
|
34
|
-
defaultSize: { width:
|
|
34
|
+
defaultSize: { width: 2, height: 1 },
|
|
35
35
|
ports: () => [
|
|
36
36
|
{ id: 'in', side: 'w', offset: 0.5, accepts: 'in' },
|
|
37
37
|
{ id: 'out', side: 'e', offset: 0.5, accepts: 'out' },
|
|
@@ -43,7 +43,12 @@ const registry: NodeRegistry = { ...defaultNodeRegistry, function: functionDef }
|
|
|
43
43
|
describe('registry', () => {
|
|
44
44
|
test('a host composes the scene schema from its registry', ({ expect }) => {
|
|
45
45
|
const HostScene = createSceneSchema(Object.values(registry).map((def) => def.schema));
|
|
46
|
-
const fn = functionDef.create({
|
|
46
|
+
const fn = functionDef.create({
|
|
47
|
+
id: 'f',
|
|
48
|
+
z: 'a',
|
|
49
|
+
center: { x: 64, y: 64 },
|
|
50
|
+
size: nominalSize(functionDef.defaultSize),
|
|
51
|
+
});
|
|
47
52
|
const scene = { id: 's', nodes: { f: fn }, links: {} };
|
|
48
53
|
expect(Schema.is(HostScene)(scene)).toBe(true);
|
|
49
54
|
// The built-in schema rejects the host type; the engine still handles the node as a `NodeBase`.
|
|
@@ -73,13 +78,13 @@ describe('registry', () => {
|
|
|
73
78
|
});
|
|
74
79
|
|
|
75
80
|
describe('node registry', () => {
|
|
76
|
-
test('rectangle and
|
|
77
|
-
const { rect,
|
|
81
|
+
test('rectangle and frame share the box prototype and differ only where they say so', ({ expect }) => {
|
|
82
|
+
const { rect, frame } = defaultNodeRegistry;
|
|
78
83
|
expect(rect.component).toBe(boxPrototype.component);
|
|
79
84
|
expect(rect.resizable).toBe(true);
|
|
80
|
-
expect(
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
85
|
+
expect(frame.resizable).toBe(rect.resizable);
|
|
86
|
+
expect(frame.portsPerSide).toBe(rect.portsPerSide);
|
|
87
|
+
expect(frame.openable).toBe(true);
|
|
83
88
|
expect(rect.openable).toBeUndefined();
|
|
84
89
|
// A prototype is not a type: the palette and the scene schema never see it.
|
|
85
90
|
expect(Object.keys(defaultNodeRegistry)).not.toContain('box');
|