@pascal-app/editor 0.8.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -13
- package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
- package/src/components/editor/bake-exporter.tsx +47 -0
- package/src/components/editor/custom-camera-controls.tsx +795 -28
- package/src/components/editor/editor-layout-mobile.tsx +8 -3
- package/src/components/editor/editor-layout-v2.tsx +76 -51
- package/src/components/editor/export-manager.tsx +69 -32
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
- package/src/components/editor/first-person/build-collider-world.ts +228 -83
- package/src/components/editor/first-person-controls.tsx +943 -27
- package/src/components/editor/floating-action-menu.tsx +676 -208
- package/src/components/editor/floating-building-action-menu.tsx +2 -2
- package/src/components/editor/floorplan-background-selection.test.ts +58 -0
- package/src/components/editor/floorplan-background-selection.ts +3 -2
- package/src/components/editor/floorplan-panel.tsx +6916 -12137
- package/src/components/editor/grid.tsx +197 -36
- package/src/components/editor/group-actions.ts +457 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +420 -0
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +396 -0
- package/src/components/editor/group-transform-shared.ts +489 -0
- package/src/components/editor/handles/handle-arrow.tsx +581 -0
- package/src/components/editor/handles/use-handle-drag.ts +246 -0
- package/src/components/editor/index.tsx +412 -162
- package/src/components/editor/measurement-pill.tsx +115 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +1646 -0
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +70 -0
- package/src/components/editor/quick-measurement-hud.tsx +28 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +1106 -483
- package/src/components/editor/site-edge-labels.tsx +45 -21
- package/src/components/editor/slab-hole-highlights.tsx +474 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +140 -41
- package/src/components/editor/use-floorplan-background-placement.ts +190 -35
- package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
- package/src/components/editor/use-floorplan-scene-data.ts +19 -7
- package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
- package/src/components/editor/wall-measurement-label.tsx +14 -19
- package/src/components/editor/wall-move-side-handles.tsx +870 -0
- package/src/components/editor/wall-opening-highlights.tsx +181 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
- package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
- package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/editor-2d/svg-paths.ts +10 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
- package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
- package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/stair/stair-edit-system.tsx +1 -1
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-defaults.ts +8 -0
- package/src/components/tools/elevator/elevator-tool.tsx +375 -0
- package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
- package/src/components/tools/fence/fence-drafting.ts +141 -27
- package/src/components/tools/item/move-tool.tsx +45 -105
- package/src/components/tools/item/placement-math.test.ts +22 -0
- package/src/components/tools/item/placement-math.ts +59 -7
- package/src/components/tools/item/placement-strategies.ts +432 -16
- package/src/components/tools/item/placement-types.ts +22 -1
- package/src/components/tools/item/use-draft-node.ts +51 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
- package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
- package/src/components/tools/roof/roof-tool.tsx +350 -28
- package/src/components/tools/select/box-select-state.ts +88 -0
- package/src/components/tools/select/box-select-tool.tsx +442 -520
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
- package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
- package/src/components/tools/select/select-candidates.test.ts +109 -0
- package/src/components/tools/select/select-candidates.ts +85 -0
- package/src/components/tools/shared/affordance-dispatch.ts +30 -0
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +25 -0
- package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
- package/src/components/tools/shared/placement-box-geometry.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +305 -0
- package/src/components/tools/shared/polygon-editor.tsx +866 -175
- package/src/components/tools/shared/segment-angle.ts +56 -0
- package/src/components/tools/site/site-boundary-editor.tsx +520 -16
- package/src/components/tools/site/site-flag-model.ts +18 -0
- package/src/components/tools/stair/stair-defaults.ts +2 -1
- package/src/components/tools/stair/stair-tool.tsx +342 -37
- package/src/components/tools/tool-manager.tsx +239 -62
- package/src/components/tools/wall/wall-drafting.test.ts +330 -0
- package/src/components/tools/wall/wall-drafting.ts +248 -171
- package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +86 -82
- package/src/components/ui/action-menu/action-button.tsx +21 -1
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +40 -170
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +3 -117
- package/src/components/ui/action-menu/measurement-control.tsx +188 -0
- package/src/components/ui/action-menu/structure-tools.tsx +27 -93
- package/src/components/ui/action-menu/view-toggles.tsx +279 -90
- package/src/components/ui/command-palette/editor-commands.tsx +20 -15
- package/src/components/ui/command-palette/index.tsx +5 -3
- package/src/components/ui/controls/material-paint-panel.tsx +152 -0
- package/src/components/ui/controls/material-picker.tsx +87 -177
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +142 -46
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/floating-level-selector.tsx +47 -10
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +235 -23
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
- package/src/components/ui/level-duplicate-dialog.tsx +2 -1
- package/src/components/ui/panels/node-display.ts +17 -16
- package/src/components/ui/panels/panel-manager.tsx +55 -58
- package/src/components/ui/panels/panel-wrapper.tsx +238 -12
- package/src/components/ui/panels/parametric-inspector.tsx +482 -0
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/scene-loader.tsx +1 -3
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
- package/src/components/ui/sidebar/tab-bar.tsx +75 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer-overlay.tsx +253 -61
- package/src/components/viewer-zone-system.tsx +7 -2
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +64 -10
- package/src/hooks/use-ceiling-events.ts +176 -0
- package/src/hooks/use-drag-action.ts +127 -0
- package/src/hooks/use-keyboard.ts +493 -95
- package/src/hooks/use-selection.ts +64 -0
- package/src/index.tsx +451 -6
- package/src/lib/active-placement-surface.ts +35 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/constants.ts +6 -2
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +192 -0
- package/src/lib/direct-manipulation.ts +109 -0
- package/src/lib/editor-api.ts +50 -0
- package/src/lib/elevator-support.ts +96 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +117 -0
- package/src/lib/floorplan/floorplan-export.tsx +364 -0
- package/src/lib/floorplan/geometry.ts +53 -0
- package/src/lib/floorplan/index.ts +10 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/floorplan/selection-tool.ts +40 -0
- package/src/lib/floorplan/stairs.ts +12 -1
- package/src/lib/fresh-planar-placement.test.ts +339 -0
- package/src/lib/fresh-planar-placement.ts +130 -0
- package/src/lib/glb-export.test.ts +441 -0
- package/src/lib/glb-export.ts +874 -0
- package/src/lib/history.ts +11 -1
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +51 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +211 -0
- package/src/lib/level-duplication.ts +54 -42
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +227 -48
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +183 -0
- package/src/lib/measurements.ts +199 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/placement-metadata.ts +29 -0
- package/src/lib/planar-cursor-placement.test.ts +100 -0
- package/src/lib/planar-cursor-placement.ts +42 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +7 -7
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +196 -0
- package/src/lib/scene-clipboard.ts +332 -0
- package/src/lib/scene.ts +40 -8
- package/src/lib/selection-routing.test.ts +298 -0
- package/src/lib/selection-routing.ts +175 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +69 -10
- package/src/lib/sfx-player.test.ts +124 -0
- package/src/lib/sfx-player.ts +148 -55
- package/src/lib/site-boundary.ts +1 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +138 -0
- package/src/lib/snapping-mode.ts +179 -0
- package/src/lib/stair-duplication.ts +4 -4
- package/src/lib/stair-levels.test.ts +146 -0
- package/src/lib/stair-levels.ts +177 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/world-grid-snap.ts +231 -0
- package/src/lib/zone-content.ts +130 -0
- package/src/store/use-alignment-guides.ts +21 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-editor.tsx +767 -114
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +21 -0
- package/src/store/use-floorplan-draft-preview.ts +101 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-interaction-scope.test.ts +186 -0
- package/src/store/use-interaction-scope.ts +132 -0
- package/src/store/use-measurement-draft.test.ts +530 -0
- package/src/store/use-measurement-draft.ts +543 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-placement-preview.ts +39 -0
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +43 -0
- package/src/store/use-wall-move-ghosts.ts +36 -0
- package/src/store/use-wall-snap-indicator.ts +34 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
- package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
- package/src/components/tools/building/move-building-tool.tsx +0 -157
- package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
- package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
- package/src/components/tools/column/column-tool.tsx +0 -97
- package/src/components/tools/column/move-column-tool.tsx +0 -105
- package/src/components/tools/door/door-math.ts +0 -110
- package/src/components/tools/door/door-tool.tsx +0 -324
- package/src/components/tools/door/move-door-tool.tsx +0 -412
- package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
- package/src/components/tools/fence/fence-tool.tsx +0 -346
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
- package/src/components/tools/fence/move-fence-tool.tsx +0 -302
- package/src/components/tools/item/item-tool.tsx +0 -26
- package/src/components/tools/roof/move-roof-tool.tsx +0 -364
- package/src/components/tools/slab/move-slab-tool.tsx +0 -182
- package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
- package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
- package/src/components/tools/slab/slab-tool.tsx +0 -322
- package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
- package/src/components/tools/spawn/spawn-tool.tsx +0 -130
- package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
- package/src/components/tools/wall/move-wall-tool.tsx +0 -358
- package/src/components/tools/wall/wall-tool.tsx +0 -332
- package/src/components/tools/window/move-window-tool.tsx +0 -447
- package/src/components/tools/window/window-math.ts +0 -117
- package/src/components/tools/window/window-tool.tsx +0 -332
- package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
- package/src/components/ui/helpers/slab-helper.tsx +0 -20
- package/src/components/ui/helpers/wall-helper.tsx +0 -20
- package/src/components/ui/panels/ceiling-panel.tsx +0 -249
- package/src/components/ui/panels/column-panel.tsx +0 -759
- package/src/components/ui/panels/door-panel.tsx +0 -1299
- package/src/components/ui/panels/fence-panel.tsx +0 -222
- package/src/components/ui/panels/item-panel.tsx +0 -306
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
- package/src/components/ui/panels/roof-panel.tsx +0 -280
- package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
- package/src/components/ui/panels/slab-panel.tsx +0 -250
- package/src/components/ui/panels/spawn-panel.tsx +0 -161
- package/src/components/ui/panels/stair-panel.tsx +0 -577
- package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
- package/src/components/ui/panels/wall-panel.tsx +0 -273
- package/src/components/ui/panels/window-panel.tsx +0 -968
- package/src/contexts/presets-context.tsx +0 -121
- package/src/hooks/use-contextual-tools.ts +0 -61
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@iconify/react'
|
|
4
|
+
import { type IconRef, useScene } from '@pascal-app/core'
|
|
5
|
+
import { ChevronLeft, ChevronRight, ExternalLink, Puzzle } from 'lucide-react'
|
|
6
|
+
import { lazy, type ReactNode, Suspense, useState, useSyncExternalStore } from 'react'
|
|
7
|
+
import { editorHostPanelRegistry } from '../../../../lib/plugin-panels'
|
|
8
|
+
import { Button } from '../../primitives/button'
|
|
9
|
+
|
|
10
|
+
const PLUGIN_AUTHORING_URL =
|
|
11
|
+
'https://editor.pascal.app/docs/developers/plugins'
|
|
12
|
+
|
|
13
|
+
function renderPluginIcon(ref: IconRef): ReactNode {
|
|
14
|
+
if (ref.kind === 'url') {
|
|
15
|
+
return <img alt="" className="h-8 w-8 object-contain" src={ref.src} />
|
|
16
|
+
}
|
|
17
|
+
if (ref.kind === 'iconify') {
|
|
18
|
+
return <Icon height={28} icon={ref.name} width={28} />
|
|
19
|
+
}
|
|
20
|
+
if (ref.kind === 'svg') {
|
|
21
|
+
return (
|
|
22
|
+
<svg height={28} viewBox={ref.viewBox} width={28}>
|
|
23
|
+
<path d={ref.path} fill="currentColor" />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
const LazyIcon = lazy(ref.module)
|
|
28
|
+
return (
|
|
29
|
+
<Suspense fallback={<Puzzle className="h-7 w-7" />}>
|
|
30
|
+
<LazyIcon />
|
|
31
|
+
</Suspense>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function PluginsPanel() {
|
|
36
|
+
const [selectedPluginId, setSelectedPluginId] = useState<string | null>(null)
|
|
37
|
+
const panels = useSyncExternalStore(
|
|
38
|
+
editorHostPanelRegistry.subscribe,
|
|
39
|
+
editorHostPanelRegistry.getSnapshot,
|
|
40
|
+
editorHostPanelRegistry.getSnapshot,
|
|
41
|
+
)
|
|
42
|
+
const installedPlugins = useScene((state) => state.installedPlugins)
|
|
43
|
+
const setInstalledPlugins = useScene((state) => state.setInstalledPlugins)
|
|
44
|
+
const readOnly = useScene((state) => state.readOnly)
|
|
45
|
+
const plugins = Array.from(
|
|
46
|
+
new Map(
|
|
47
|
+
panels
|
|
48
|
+
.filter((panel) => panel.pluginId)
|
|
49
|
+
.map((panel) => [panel.pluginId as string, panel]),
|
|
50
|
+
).entries(),
|
|
51
|
+
)
|
|
52
|
+
const selectedPlugin = selectedPluginId
|
|
53
|
+
? plugins.find(([pluginId]) => pluginId === selectedPluginId)
|
|
54
|
+
: undefined
|
|
55
|
+
|
|
56
|
+
if (selectedPlugin) {
|
|
57
|
+
const [pluginId, panel] = selectedPlugin
|
|
58
|
+
const installed = installedPlugins.includes(pluginId)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="flex h-full flex-col overflow-y-auto p-4">
|
|
62
|
+
<div>
|
|
63
|
+
<Button
|
|
64
|
+
className="rounded-full"
|
|
65
|
+
onClick={() => setSelectedPluginId(null)}
|
|
66
|
+
size="sm"
|
|
67
|
+
variant="ghost"
|
|
68
|
+
>
|
|
69
|
+
<ChevronLeft className="h-4 w-4" />
|
|
70
|
+
All plugins
|
|
71
|
+
</Button>
|
|
72
|
+
|
|
73
|
+
<div className="mt-5 flex items-start gap-4">
|
|
74
|
+
<div className="flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-background/60">
|
|
75
|
+
{renderPluginIcon(panel.icon)}
|
|
76
|
+
</div>
|
|
77
|
+
<div className="min-w-0 pt-1">
|
|
78
|
+
<h2 className="font-semibold text-lg text-sidebar-foreground">{panel.label}</h2>
|
|
79
|
+
<p className="text-sidebar-foreground/50 text-sm">
|
|
80
|
+
{installed ? 'Installed' : 'Not installed'}
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<p className="mt-5 text-sidebar-foreground/70 text-sm">
|
|
86
|
+
{panel.description ?? 'Adds a new tool panel to the editor.'}
|
|
87
|
+
</p>
|
|
88
|
+
|
|
89
|
+
<dl className="mt-6 divide-y divide-border/50 rounded-xl border border-border/60">
|
|
90
|
+
<div className="p-3">
|
|
91
|
+
<dt className="text-sidebar-foreground/50 text-xs">Plugin ID</dt>
|
|
92
|
+
<dd className="mt-1 break-all text-sidebar-foreground text-sm">{pluginId}</dd>
|
|
93
|
+
</div>
|
|
94
|
+
{panel.creator && (
|
|
95
|
+
<div className="p-3">
|
|
96
|
+
<dt className="text-sidebar-foreground/50 text-xs">Creator</dt>
|
|
97
|
+
<dd className="mt-1 text-sm">
|
|
98
|
+
{panel.creator.url ? (
|
|
99
|
+
<a
|
|
100
|
+
className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
|
|
101
|
+
href={panel.creator.url}
|
|
102
|
+
rel="noreferrer"
|
|
103
|
+
target="_blank"
|
|
104
|
+
>
|
|
105
|
+
{panel.creator.name}
|
|
106
|
+
<ExternalLink className="h-3 w-3" />
|
|
107
|
+
</a>
|
|
108
|
+
) : (
|
|
109
|
+
panel.creator.name
|
|
110
|
+
)}
|
|
111
|
+
</dd>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
{panel.pluginUrl && (
|
|
115
|
+
<div className="p-3">
|
|
116
|
+
<dt className="text-sidebar-foreground/50 text-xs">Plugin</dt>
|
|
117
|
+
<dd className="mt-1 text-sm">
|
|
118
|
+
<a
|
|
119
|
+
className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
|
|
120
|
+
href={panel.pluginUrl}
|
|
121
|
+
rel="noreferrer"
|
|
122
|
+
target="_blank"
|
|
123
|
+
>
|
|
124
|
+
View plugin
|
|
125
|
+
<ExternalLink className="h-3 w-3" />
|
|
126
|
+
</a>
|
|
127
|
+
</dd>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</dl>
|
|
131
|
+
|
|
132
|
+
<Button
|
|
133
|
+
className="mt-5 rounded-full"
|
|
134
|
+
disabled={readOnly}
|
|
135
|
+
onClick={() => {
|
|
136
|
+
const next = installed
|
|
137
|
+
? installedPlugins.filter((id) => id !== pluginId)
|
|
138
|
+
: [...installedPlugins, pluginId]
|
|
139
|
+
setInstalledPlugins(next, { explicit: true })
|
|
140
|
+
}}
|
|
141
|
+
variant={installed ? 'outline' : 'default'}
|
|
142
|
+
>
|
|
143
|
+
{installed ? 'Uninstall' : 'Install'}
|
|
144
|
+
</Button>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div className="mt-auto pt-6">
|
|
148
|
+
<a
|
|
149
|
+
className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
|
|
150
|
+
href={PLUGIN_AUTHORING_URL}
|
|
151
|
+
rel="noreferrer"
|
|
152
|
+
target="_blank"
|
|
153
|
+
>
|
|
154
|
+
Create a Pascal plugin
|
|
155
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
156
|
+
</a>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="flex h-full flex-col overflow-y-auto p-4">
|
|
164
|
+
<div className="mb-5">
|
|
165
|
+
<h2 className="font-semibold text-lg text-sidebar-foreground">Plugins</h2>
|
|
166
|
+
<p className="mt-1 text-sidebar-foreground/60 text-sm">
|
|
167
|
+
Add focused tools and content to this project.
|
|
168
|
+
</p>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div className="flex flex-col gap-3">
|
|
172
|
+
{plugins.map(([pluginId, panel]) => {
|
|
173
|
+
const installed = installedPlugins.includes(pluginId)
|
|
174
|
+
return (
|
|
175
|
+
<button
|
|
176
|
+
className="w-full rounded-xl border border-border/60 bg-accent/20 p-3 text-left transition-colors hover:bg-accent/40"
|
|
177
|
+
key={pluginId}
|
|
178
|
+
onClick={() => setSelectedPluginId(pluginId)}
|
|
179
|
+
type="button"
|
|
180
|
+
>
|
|
181
|
+
<div className="flex items-start gap-3">
|
|
182
|
+
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-background/60">
|
|
183
|
+
{renderPluginIcon(panel.icon)}
|
|
184
|
+
</div>
|
|
185
|
+
<div className="min-w-0 flex-1">
|
|
186
|
+
<div className="flex items-center justify-between gap-3">
|
|
187
|
+
<div>
|
|
188
|
+
<h3 className="font-medium text-sidebar-foreground">{panel.label}</h3>
|
|
189
|
+
<p className="text-sidebar-foreground/50 text-xs">
|
|
190
|
+
{installed ? 'Installed' : 'Not installed'}
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
<ChevronRight className="h-4 w-4 shrink-0 text-sidebar-foreground/50" />
|
|
194
|
+
</div>
|
|
195
|
+
<p className="mt-2 text-sidebar-foreground/60 text-sm">
|
|
196
|
+
{panel.description ?? 'Adds a new tool panel to the editor.'}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</button>
|
|
201
|
+
)
|
|
202
|
+
})}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="mt-auto pt-6">
|
|
206
|
+
<a
|
|
207
|
+
className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
|
|
208
|
+
href={PLUGIN_AUTHORING_URL}
|
|
209
|
+
rel="noreferrer"
|
|
210
|
+
target="_blank"
|
|
211
|
+
>
|
|
212
|
+
Create a Pascal plugin
|
|
213
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
214
|
+
</a>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { emitter, useScene } from '@pascal-app/core'
|
|
1
|
+
import { clearSceneHistory, emitter, useScene, validateBuildJson } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { TreeView, VisualJson } from '@visual-json/react'
|
|
4
|
-
import { Camera, Download, Save, Trash2, Upload } from 'lucide-react'
|
|
4
|
+
import { Camera, Download, Map as MapIcon, Save, Trash2, Upload } from 'lucide-react'
|
|
5
5
|
import {
|
|
6
6
|
type KeyboardEvent,
|
|
7
7
|
type SyntheticEvent,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
useState,
|
|
12
12
|
} from 'react'
|
|
13
|
+
import { exportFloorplanPdf } from '../../../../../lib/floorplan/floorplan-export'
|
|
13
14
|
import { Button } from './../../../../../components/ui/primitives/button'
|
|
14
15
|
import {
|
|
15
16
|
Dialog,
|
|
@@ -21,6 +22,7 @@ import { Switch } from './../../../../../components/ui/primitives/switch'
|
|
|
21
22
|
import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
|
|
22
23
|
import { AudioSettingsDialog } from './audio-settings-dialog'
|
|
23
24
|
import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
|
|
25
|
+
import { LoadBuildDialog, type PendingImport } from './load-build-dialog'
|
|
24
26
|
|
|
25
27
|
type SceneNode = Record<string, unknown> & {
|
|
26
28
|
id?: unknown
|
|
@@ -178,13 +180,15 @@ export function SettingsPanel({
|
|
|
178
180
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
179
181
|
const nodes = useScene((state) => state.nodes)
|
|
180
182
|
const rootNodeIds = useScene((state) => state.rootNodeIds)
|
|
183
|
+
const installedPlugins = useScene((state) => state.installedPlugins)
|
|
181
184
|
const setScene = useScene((state) => state.setScene)
|
|
182
185
|
const clearScene = useScene((state) => state.clearScene)
|
|
183
186
|
const resetSelection = useViewer((state) => state.resetSelection)
|
|
184
187
|
const exportScene = useViewer((state) => state.exportScene)
|
|
185
|
-
const
|
|
188
|
+
const shadows = useViewer((state) => state.shadows)
|
|
186
189
|
const setPhase = useEditor((state) => state.setPhase)
|
|
187
190
|
const [isGeneratingThumbnail, setIsGeneratingThumbnail] = useState(false)
|
|
191
|
+
const [pendingImport, setPendingImport] = useState<PendingImport | null>(null)
|
|
188
192
|
const sceneGraphValue = useMemo(
|
|
189
193
|
() => buildSceneGraphValue(nodes as Record<string, SceneNode>, rootNodeIds),
|
|
190
194
|
[nodes, rootNodeIds],
|
|
@@ -203,7 +207,7 @@ export function SettingsPanel({
|
|
|
203
207
|
const isLocalProject = false // Props-based; only show cloud sections when projectId provided
|
|
204
208
|
|
|
205
209
|
const handleSaveBuild = () => {
|
|
206
|
-
const sceneData = { nodes, rootNodeIds }
|
|
210
|
+
const sceneData = { nodes, rootNodeIds, installedPlugins }
|
|
207
211
|
const json = JSON.stringify(sceneData, null, 2)
|
|
208
212
|
const blob = new Blob([json], { type: 'application/json' })
|
|
209
213
|
const url = URL.createObjectURL(blob)
|
|
@@ -221,16 +225,37 @@ export function SettingsPanel({
|
|
|
221
225
|
|
|
222
226
|
const reader = new FileReader()
|
|
223
227
|
reader.onload = (event) => {
|
|
228
|
+
const text = event.target?.result as string
|
|
229
|
+
let parsed: unknown
|
|
224
230
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
parsed = JSON.parse(text)
|
|
232
|
+
} catch {
|
|
233
|
+
setPendingImport({
|
|
234
|
+
fileName: file.name,
|
|
235
|
+
fileSizeBytes: file.size,
|
|
236
|
+
result: {
|
|
237
|
+
ok: false,
|
|
238
|
+
parsed: null,
|
|
239
|
+
stats: { total: 0, byType: {}, unknownTypes: {}, floorAreaM2: 0 },
|
|
240
|
+
errors: [
|
|
241
|
+
{
|
|
242
|
+
severity: 'error',
|
|
243
|
+
code: 'invalid_json',
|
|
244
|
+
message: 'File could not be parsed as JSON.',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
warnings: [],
|
|
248
|
+
schemaIssues: [],
|
|
249
|
+
schemaIssueCount: 0,
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
return
|
|
233
253
|
}
|
|
254
|
+
setPendingImport({
|
|
255
|
+
fileName: file.name,
|
|
256
|
+
fileSizeBytes: file.size,
|
|
257
|
+
result: validateBuildJson(parsed),
|
|
258
|
+
})
|
|
234
259
|
}
|
|
235
260
|
reader.readAsText(file)
|
|
236
261
|
|
|
@@ -238,8 +263,34 @@ export function SettingsPanel({
|
|
|
238
263
|
e.target.value = ''
|
|
239
264
|
}
|
|
240
265
|
|
|
266
|
+
const handleConfirmImport = (parsed: {
|
|
267
|
+
nodes: Record<string, unknown>
|
|
268
|
+
rootNodeIds: string[]
|
|
269
|
+
installedPlugins?: string[]
|
|
270
|
+
}) => {
|
|
271
|
+
const currentScene = useScene.getState()
|
|
272
|
+
setScene(
|
|
273
|
+
parsed.nodes as Parameters<typeof setScene>[0],
|
|
274
|
+
parsed.rootNodeIds as Parameters<typeof setScene>[1],
|
|
275
|
+
{
|
|
276
|
+
installedPlugins: parsed.installedPlugins ?? currentScene.installedPlugins,
|
|
277
|
+
hasExplicitPluginInstallState:
|
|
278
|
+
parsed.installedPlugins !== undefined || currentScene.hasExplicitPluginInstallState,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
// An import is a scene load: it becomes the undo floor. Without this,
|
|
282
|
+
// undo could step back into the pre-import scene state.
|
|
283
|
+
clearSceneHistory()
|
|
284
|
+
resetSelection()
|
|
285
|
+
setPhase('site')
|
|
286
|
+
setPendingImport(null)
|
|
287
|
+
}
|
|
288
|
+
|
|
241
289
|
const handleResetToDefault = () => {
|
|
242
290
|
clearScene()
|
|
291
|
+
// Same floor rule as import — undo after a reset must not resurrect the
|
|
292
|
+
// old scene (or land on the empty intermediate `unloadScene` state).
|
|
293
|
+
clearSceneHistory()
|
|
243
294
|
resetSelection()
|
|
244
295
|
setPhase('structure')
|
|
245
296
|
selectDefaultBuildingAndLevel()
|
|
@@ -299,44 +350,68 @@ export function SettingsPanel({
|
|
|
299
350
|
</div>
|
|
300
351
|
<div className="flex items-center justify-between">
|
|
301
352
|
<div>
|
|
302
|
-
<div className="font-medium text-sm">
|
|
303
|
-
<div className="text-muted-foreground text-xs">
|
|
353
|
+
<div className="font-medium text-sm">Shadows</div>
|
|
354
|
+
<div className="text-muted-foreground text-xs">Cast shadows from lights</div>
|
|
304
355
|
</div>
|
|
305
356
|
<Switch
|
|
306
|
-
checked={
|
|
307
|
-
onCheckedChange={(checked) => useViewer.getState().
|
|
357
|
+
checked={shadows}
|
|
358
|
+
onCheckedChange={(checked) => useViewer.getState().setShadows(checked)}
|
|
308
359
|
/>
|
|
309
360
|
</div>
|
|
310
361
|
</div>
|
|
311
362
|
)}
|
|
312
363
|
|
|
313
364
|
{/* Export Section */}
|
|
314
|
-
<div className="space-y-
|
|
365
|
+
<div className="space-y-4">
|
|
315
366
|
<label className="font-medium text-muted-foreground text-xs uppercase">Export</label>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
367
|
+
|
|
368
|
+
<div className="space-y-2">
|
|
369
|
+
<div className="font-medium text-muted-foreground text-xs">3D model</div>
|
|
370
|
+
<Button
|
|
371
|
+
className="w-full justify-start gap-2"
|
|
372
|
+
onClick={() => exportScene?.('glb')}
|
|
373
|
+
variant="outline"
|
|
374
|
+
>
|
|
375
|
+
<Download className="size-4" />
|
|
376
|
+
Export GLB
|
|
377
|
+
</Button>
|
|
378
|
+
<Button
|
|
379
|
+
className="w-full justify-start gap-2"
|
|
380
|
+
onClick={() => exportScene?.('stl')}
|
|
381
|
+
variant="outline"
|
|
382
|
+
>
|
|
383
|
+
<Download className="size-4" />
|
|
384
|
+
Export STL
|
|
385
|
+
</Button>
|
|
386
|
+
<Button
|
|
387
|
+
className="w-full justify-start gap-2"
|
|
388
|
+
onClick={() => exportScene?.('obj')}
|
|
389
|
+
variant="outline"
|
|
390
|
+
>
|
|
391
|
+
<Download className="size-4" />
|
|
392
|
+
Export OBJ
|
|
393
|
+
</Button>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div className="space-y-2">
|
|
397
|
+
<div className="font-medium text-muted-foreground text-xs">Floorplan</div>
|
|
398
|
+
<Button
|
|
399
|
+
className="w-full justify-start gap-2"
|
|
400
|
+
onClick={() => exportFloorplanPdf('full')}
|
|
401
|
+
variant="outline"
|
|
402
|
+
>
|
|
403
|
+
<MapIcon className="size-4" />
|
|
404
|
+
Full floorplan
|
|
405
|
+
</Button>
|
|
406
|
+
<Button
|
|
407
|
+
className="w-full justify-start gap-2"
|
|
408
|
+
onClick={() => exportFloorplanPdf('structure')}
|
|
409
|
+
variant="outline"
|
|
410
|
+
>
|
|
411
|
+
<MapIcon className="size-4" />
|
|
412
|
+
Structure only
|
|
413
|
+
</Button>
|
|
414
|
+
</div>
|
|
340
415
|
</div>
|
|
341
416
|
|
|
342
417
|
{/* Thumbnail Section (only for cloud projects) */}
|
|
@@ -380,6 +455,12 @@ export function SettingsPanel({
|
|
|
380
455
|
ref={fileInputRef}
|
|
381
456
|
type="file"
|
|
382
457
|
/>
|
|
458
|
+
|
|
459
|
+
<LoadBuildDialog
|
|
460
|
+
onCancel={() => setPendingImport(null)}
|
|
461
|
+
onConfirm={handleConfirmImport}
|
|
462
|
+
pending={pendingImport}
|
|
463
|
+
/>
|
|
383
464
|
</div>
|
|
384
465
|
|
|
385
466
|
{/* Audio Section */}
|
|
@@ -37,7 +37,6 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
37
37
|
{ keys: ['1'], action: 'Switch to Site phase' },
|
|
38
38
|
{ keys: ['2'], action: 'Switch to Structure phase' },
|
|
39
39
|
{ keys: ['3'], action: 'Switch to Furnish phase' },
|
|
40
|
-
{ keys: ['S'], action: 'Switch to Structure layer' },
|
|
41
40
|
{ keys: ['F'], action: 'Switch to Furnish layer' },
|
|
42
41
|
{ keys: ['Z'], action: 'Switch to Zones layer' },
|
|
43
42
|
{
|
|
@@ -56,6 +55,8 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
56
55
|
shortcuts: [
|
|
57
56
|
{ keys: ['V'], action: 'Switch to Select mode' },
|
|
58
57
|
{ keys: ['B'], action: 'Switch to Build mode' },
|
|
58
|
+
{ keys: ['M'], action: 'Activate the last measurement tool' },
|
|
59
|
+
{ keys: ['X'], action: 'Switch to Delete mode' },
|
|
59
60
|
{
|
|
60
61
|
keys: ['Esc'],
|
|
61
62
|
action: 'Cancel the active tool and return to Select mode',
|
|
@@ -71,7 +72,48 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
71
72
|
{
|
|
72
73
|
keys: ['Cmd/Ctrl', 'Left click'],
|
|
73
74
|
action: 'Add or remove an object from multi-selection',
|
|
74
|
-
note: 'Works
|
|
75
|
+
note: 'Works in Select mode on the 3D canvas, the 2D floor plan, and the scene graph.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
keys: ['Shift', 'Left click'],
|
|
79
|
+
action: 'Add or remove an object from canvas multi-selection',
|
|
80
|
+
note: 'In the scene graph, Shift-click selects the visible range like a file browser.',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
keys: ['Left click'],
|
|
84
|
+
action: 'Move the whole multi-selection',
|
|
85
|
+
note:
|
|
86
|
+
'With 2+ objects selected, in 2D and 3D alike: drag the selection (or its dashed box) to slide it; click it to pick it up and place with the next click.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
keys: ['R', 'T'],
|
|
90
|
+
action: 'Rotate a multi-selection ±45° around its center',
|
|
91
|
+
note: 'Also works mid-move while carrying the selection.',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
keys: ['Esc'],
|
|
95
|
+
action: 'Clear the selection',
|
|
96
|
+
note: 'Clicking empty space does the same.',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: 'Direct Manipulation',
|
|
102
|
+
shortcuts: [
|
|
103
|
+
{
|
|
104
|
+
keys: ['Cmd/Ctrl', 'Left click'],
|
|
105
|
+
action: 'Move the selected movable object under the cursor',
|
|
106
|
+
note: 'Drag in Select mode with a single object selected. Guided snapping and guides are enabled by default.',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
keys: ['Cmd/Ctrl', 'Right click'],
|
|
110
|
+
action: 'Rotate the selected object under the cursor',
|
|
111
|
+
note: 'Drag left or right in Select mode with a single object selected. Rotation snaps to 15° increments by default.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
keys: ['Cmd/Ctrl', 'Shift', 'Right click'],
|
|
115
|
+
action: 'Rotate freely',
|
|
116
|
+
note: 'Hold Shift during the drag to bypass the 15° rotation increment.',
|
|
75
117
|
},
|
|
76
118
|
],
|
|
77
119
|
},
|
|
@@ -80,16 +122,27 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
80
122
|
shortcuts: [
|
|
81
123
|
{
|
|
82
124
|
keys: ['Shift'],
|
|
83
|
-
action: '
|
|
84
|
-
note: 'Hold
|
|
125
|
+
action: 'Bypass guided snapping and angle constraints',
|
|
126
|
+
note: 'Hold during the active gesture. Passive guide or measurement feedback may stay visible.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
keys: ['Shift'],
|
|
130
|
+
action: 'Rotate freely, bypassing the default 15° rotation snap',
|
|
131
|
+
note: 'Hold while dragging a rotate handle or direct-rotation gesture.',
|
|
85
132
|
},
|
|
86
133
|
],
|
|
87
134
|
},
|
|
88
135
|
{
|
|
89
136
|
title: 'Item Placement',
|
|
90
137
|
shortcuts: [
|
|
91
|
-
{
|
|
92
|
-
|
|
138
|
+
{
|
|
139
|
+
keys: ['R', 'T'],
|
|
140
|
+
action: 'Rotate item; with a door selected, R toggles open/closed and T closes',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
keys: ['E'],
|
|
144
|
+
action: 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed',
|
|
145
|
+
},
|
|
93
146
|
{
|
|
94
147
|
keys: ['Shift'],
|
|
95
148
|
action: 'Temporarily bypass placement validation constraints',
|
|
@@ -100,6 +153,11 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
|
|
|
100
153
|
{
|
|
101
154
|
title: 'Camera',
|
|
102
155
|
shortcuts: [
|
|
156
|
+
{
|
|
157
|
+
keys: ['W', 'A', 'S', 'D'],
|
|
158
|
+
action: 'Pan camera',
|
|
159
|
+
note: 'Moves in screen space, similar to dragging the camera view.',
|
|
160
|
+
},
|
|
103
161
|
{
|
|
104
162
|
keys: ['Middle click'],
|
|
105
163
|
action: 'Pan camera',
|
|
@@ -152,7 +210,8 @@ export function KeyboardShortcutsDialog() {
|
|
|
152
210
|
<DialogHeader className="shrink-0 border-b px-6 py-4">
|
|
153
211
|
<DialogTitle>Keyboard Shortcuts</DialogTitle>
|
|
154
212
|
<DialogDescription>
|
|
155
|
-
Shortcuts are context-aware
|
|
213
|
+
Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift
|
|
214
|
+
during an active gesture to build freely.
|
|
156
215
|
</DialogDescription>
|
|
157
216
|
</DialogHeader>
|
|
158
217
|
|