@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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Icon } from '@iconify/react'
|
|
4
3
|
import {
|
|
5
4
|
type AnyNodeId,
|
|
6
5
|
type BuildingNode,
|
|
@@ -10,25 +9,22 @@ import {
|
|
|
10
9
|
useScene,
|
|
11
10
|
} from '@pascal-app/core'
|
|
12
11
|
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
-
import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2 } from 'lucide-react'
|
|
12
|
+
import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2, Waypoints } from 'lucide-react'
|
|
14
13
|
import { useCallback, useRef, useState } from 'react'
|
|
15
14
|
import { useShallow } from 'zustand/react/shallow'
|
|
15
|
+
import { getLevelDisplayName } from '@pascal-app/core'
|
|
16
16
|
import { createLocalGuideImage } from '../../../lib/local-guide-image'
|
|
17
17
|
import { cn } from '../../../lib/utils'
|
|
18
|
-
import useEditor
|
|
18
|
+
import useEditor from '../../../store/use-editor'
|
|
19
19
|
import { useUploadStore } from '../../../store/use-upload'
|
|
20
20
|
import { SliderControl } from '../controls/slider-control'
|
|
21
21
|
import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
|
|
22
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
23
22
|
import { ActionButton } from './action-button'
|
|
24
23
|
|
|
25
24
|
const MAX_FILE_SIZE = 200 * 1024 * 1024 // 200MB
|
|
26
25
|
const ACCEPTED_FILE_TYPES = '.glb,.gltf,image/jpeg,image/png,image/webp,image/gif'
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
function formatGridSnapStep(step: GridSnapStep) {
|
|
30
|
-
return step.toFixed(2)
|
|
31
|
-
}
|
|
26
|
+
const REFERENCES_EMPTY_TEXT =
|
|
27
|
+
'Upload GLB meshes as scan references or blueprint images as guide references.'
|
|
32
28
|
|
|
33
29
|
// ── Helper: get guide images for the current level ──────────────────────────
|
|
34
30
|
|
|
@@ -84,10 +80,6 @@ function useLowerReferenceLevels(): LevelNode[] {
|
|
|
84
80
|
)
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
function getLevelDisplayName(level: LevelNode) {
|
|
88
|
-
return level.name || `Level ${level.level}`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
83
|
// ── Shared upload button for dropdowns ──────────────────────────────────────
|
|
92
84
|
|
|
93
85
|
function UploadButton({ onError }: { onError: (message: string | null) => void }) {
|
|
@@ -227,7 +219,7 @@ function GuidesControl() {
|
|
|
227
219
|
<img
|
|
228
220
|
alt="Guides"
|
|
229
221
|
className="h-[28px] w-[28px] object-contain"
|
|
230
|
-
src="/icons/floorplan.
|
|
222
|
+
src="/icons/floorplan.webp"
|
|
231
223
|
/>
|
|
232
224
|
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
233
225
|
{guides.length}
|
|
@@ -266,7 +258,7 @@ function GuidesControl() {
|
|
|
266
258
|
<div className="space-y-3">
|
|
267
259
|
<div className="flex items-center gap-2">
|
|
268
260
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
|
269
|
-
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.
|
|
261
|
+
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
|
|
270
262
|
</span>
|
|
271
263
|
<div className="min-w-0 flex-1">
|
|
272
264
|
<p className="font-medium text-foreground text-sm">Guide images</p>
|
|
@@ -306,7 +298,7 @@ function GuidesControl() {
|
|
|
306
298
|
<img
|
|
307
299
|
alt=""
|
|
308
300
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
|
309
|
-
src="/icons/floorplan.
|
|
301
|
+
src="/icons/floorplan.webp"
|
|
310
302
|
/>
|
|
311
303
|
<p className="truncate font-medium text-foreground text-sm">
|
|
312
304
|
{guide.name || `Guide image ${index + 1}`}
|
|
@@ -345,7 +337,7 @@ function GuidesControl() {
|
|
|
345
337
|
</div>
|
|
346
338
|
) : (
|
|
347
339
|
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-4 text-muted-foreground text-sm">
|
|
348
|
-
|
|
340
|
+
{REFERENCES_EMPTY_TEXT}
|
|
349
341
|
</div>
|
|
350
342
|
)}
|
|
351
343
|
</div>
|
|
@@ -354,70 +346,6 @@ function GuidesControl() {
|
|
|
354
346
|
)
|
|
355
347
|
}
|
|
356
348
|
|
|
357
|
-
// ── Grid snap toggle ────────────────────────────────────────────────────────
|
|
358
|
-
|
|
359
|
-
function GridSnapControl() {
|
|
360
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
361
|
-
const gridSnapStep = useEditor((state) => state.gridSnapStep)
|
|
362
|
-
const setGridSnapStep = useEditor((state) => state.setGridSnapStep)
|
|
363
|
-
|
|
364
|
-
return (
|
|
365
|
-
<Popover onOpenChange={setIsOpen} open={isOpen}>
|
|
366
|
-
<Tooltip>
|
|
367
|
-
<TooltipTrigger asChild>
|
|
368
|
-
<PopoverTrigger asChild>
|
|
369
|
-
<button
|
|
370
|
-
aria-expanded={isOpen}
|
|
371
|
-
aria-label={`Grid snap: ${formatGridSnapStep(gridSnapStep)}`}
|
|
372
|
-
className={cn(
|
|
373
|
-
'flex h-11 w-11 flex-col items-center justify-center rounded-lg text-muted-foreground transition-all hover:bg-white/5 hover:text-foreground',
|
|
374
|
-
isOpen && 'bg-white/10 text-foreground',
|
|
375
|
-
)}
|
|
376
|
-
type="button"
|
|
377
|
-
>
|
|
378
|
-
<Icon height={16} icon="lucide:grid-2x2" width={16} />
|
|
379
|
-
<span className="mt-1 font-medium text-[9px] leading-none">
|
|
380
|
-
{formatGridSnapStep(gridSnapStep)}
|
|
381
|
-
</span>
|
|
382
|
-
</button>
|
|
383
|
-
</PopoverTrigger>
|
|
384
|
-
</TooltipTrigger>
|
|
385
|
-
<TooltipContent side="top">Grid snap: {formatGridSnapStep(gridSnapStep)}</TooltipContent>
|
|
386
|
-
</Tooltip>
|
|
387
|
-
|
|
388
|
-
<PopoverContent
|
|
389
|
-
align="center"
|
|
390
|
-
className="w-36 rounded-xl border-border/45 bg-background/96 p-2 shadow-elevation-3 backdrop-blur-xl"
|
|
391
|
-
side="top"
|
|
392
|
-
sideOffset={14}
|
|
393
|
-
>
|
|
394
|
-
<div className="space-y-1">
|
|
395
|
-
{GRID_SNAP_STEPS.map((step) => {
|
|
396
|
-
const isActive = step === gridSnapStep
|
|
397
|
-
return (
|
|
398
|
-
<button
|
|
399
|
-
className={cn(
|
|
400
|
-
'flex w-full items-center justify-between rounded-lg px-2.5 py-2 text-left text-sm transition-colors hover:bg-white/8',
|
|
401
|
-
isActive && 'bg-white/10 text-foreground',
|
|
402
|
-
)}
|
|
403
|
-
key={step}
|
|
404
|
-
onClick={() => {
|
|
405
|
-
setGridSnapStep(step)
|
|
406
|
-
setIsOpen(false)
|
|
407
|
-
}}
|
|
408
|
-
type="button"
|
|
409
|
-
>
|
|
410
|
-
<span>{formatGridSnapStep(step)}</span>
|
|
411
|
-
{isActive ? <Check className="h-3.5 w-3.5" /> : <span className="h-3.5 w-3.5" />}
|
|
412
|
-
</button>
|
|
413
|
-
)
|
|
414
|
-
})}
|
|
415
|
-
</div>
|
|
416
|
-
</PopoverContent>
|
|
417
|
-
</Popover>
|
|
418
|
-
)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
349
|
// ── Scans toggle + dropdown ─────────────────────────────────────────────────
|
|
422
350
|
|
|
423
351
|
function ScansControl() {
|
|
@@ -467,7 +395,7 @@ function ScansControl() {
|
|
|
467
395
|
variant="ghost"
|
|
468
396
|
>
|
|
469
397
|
<div className="relative">
|
|
470
|
-
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.
|
|
398
|
+
<img alt="Scans" className="h-[28px] w-[28px] object-contain" src="/icons/mesh.webp" />
|
|
471
399
|
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
472
400
|
{scans.length}
|
|
473
401
|
</span>
|
|
@@ -505,7 +433,7 @@ function ScansControl() {
|
|
|
505
433
|
<div className="space-y-3">
|
|
506
434
|
<div className="flex items-center gap-2">
|
|
507
435
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
|
508
|
-
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.
|
|
436
|
+
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
|
|
509
437
|
</span>
|
|
510
438
|
<div className="min-w-0 flex-1">
|
|
511
439
|
<p className="font-medium text-foreground text-sm">Scans</p>
|
|
@@ -545,7 +473,7 @@ function ScansControl() {
|
|
|
545
473
|
<img
|
|
546
474
|
alt=""
|
|
547
475
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
|
548
|
-
src="/icons/mesh.
|
|
476
|
+
src="/icons/mesh.webp"
|
|
549
477
|
/>
|
|
550
478
|
<p className="truncate font-medium text-foreground text-sm">
|
|
551
479
|
{scan.name || `Scan ${index + 1}`}
|
|
@@ -584,7 +512,7 @@ function ScansControl() {
|
|
|
584
512
|
</div>
|
|
585
513
|
) : (
|
|
586
514
|
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-4 text-muted-foreground text-sm">
|
|
587
|
-
|
|
515
|
+
{REFERENCES_EMPTY_TEXT}
|
|
588
516
|
</div>
|
|
589
517
|
)}
|
|
590
518
|
</div>
|
|
@@ -593,6 +521,247 @@ function ScansControl() {
|
|
|
593
521
|
)
|
|
594
522
|
}
|
|
595
523
|
|
|
524
|
+
// ── References (merged scans + guides) ──────────────────────────────────────
|
|
525
|
+
// Bottom-bar control that folds the separate Scans and Guides toggles into one
|
|
526
|
+
// "References" split button + a popover holding both, each with its own
|
|
527
|
+
// visibility toggle, upload, and per-item opacity/delete.
|
|
528
|
+
|
|
529
|
+
function ReferenceListSection({
|
|
530
|
+
title,
|
|
531
|
+
iconSrc,
|
|
532
|
+
noun,
|
|
533
|
+
emptyText,
|
|
534
|
+
nodes,
|
|
535
|
+
show,
|
|
536
|
+
setShow,
|
|
537
|
+
onError,
|
|
538
|
+
}: {
|
|
539
|
+
title: string
|
|
540
|
+
iconSrc: string
|
|
541
|
+
noun: string
|
|
542
|
+
emptyText: string
|
|
543
|
+
nodes: (GuideNode | ScanNode)[]
|
|
544
|
+
show: boolean
|
|
545
|
+
setShow: (show: boolean) => void
|
|
546
|
+
onError: (message: string | null) => void
|
|
547
|
+
}) {
|
|
548
|
+
const setSelection = useViewer((state) => state.setSelection)
|
|
549
|
+
const updateNode = useScene((state) => state.updateNode)
|
|
550
|
+
const deleteNode = useScene((state) => state.deleteNode)
|
|
551
|
+
const selectedReferenceId = useEditor((state) => state.selectedReferenceId)
|
|
552
|
+
const setSelectedReferenceId = useEditor((state) => state.setSelectedReferenceId)
|
|
553
|
+
const hasItems = nodes.length > 0
|
|
554
|
+
|
|
555
|
+
const handleSelect = useCallback(
|
|
556
|
+
(id: AnyNodeId) => {
|
|
557
|
+
setShow(true)
|
|
558
|
+
setSelectedReferenceId(id)
|
|
559
|
+
setSelection({ selectedIds: [], zoneId: null })
|
|
560
|
+
},
|
|
561
|
+
[setShow, setSelectedReferenceId, setSelection],
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<div className="space-y-2">
|
|
566
|
+
<div className="flex items-center gap-2">
|
|
567
|
+
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-background/80">
|
|
568
|
+
<img alt="" className="h-4 w-4 object-contain" src={iconSrc} />
|
|
569
|
+
</span>
|
|
570
|
+
<div className="min-w-0 flex-1">
|
|
571
|
+
<p className="font-medium text-foreground text-sm">{title}</p>
|
|
572
|
+
{hasItems && (
|
|
573
|
+
<p className="text-muted-foreground text-xs">
|
|
574
|
+
{nodes.length} {noun}
|
|
575
|
+
{nodes.length !== 1 ? 's' : ''} on this level
|
|
576
|
+
</p>
|
|
577
|
+
)}
|
|
578
|
+
</div>
|
|
579
|
+
<button
|
|
580
|
+
aria-label={show ? `Hide ${title.toLowerCase()}` : `Show ${title.toLowerCase()}`}
|
|
581
|
+
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border/40 text-muted-foreground transition-colors hover:bg-white/10 hover:text-foreground"
|
|
582
|
+
onClick={() => setShow(!show)}
|
|
583
|
+
type="button"
|
|
584
|
+
>
|
|
585
|
+
{show ? <Eye className="h-3.5 w-3.5" /> : <EyeOff className="h-3.5 w-3.5" />}
|
|
586
|
+
</button>
|
|
587
|
+
<UploadButton onError={onError} />
|
|
588
|
+
</div>
|
|
589
|
+
|
|
590
|
+
{hasItems ? (
|
|
591
|
+
<div className="max-h-40 space-y-2 overflow-y-auto pr-1">
|
|
592
|
+
{nodes.map((node, index) => (
|
|
593
|
+
<div
|
|
594
|
+
className={cn(
|
|
595
|
+
'group/item space-y-2 rounded-xl border bg-background/75 p-2.5 transition-colors',
|
|
596
|
+
selectedReferenceId === node.id
|
|
597
|
+
? 'border-foreground/35 bg-white/10'
|
|
598
|
+
: 'border-border/45',
|
|
599
|
+
)}
|
|
600
|
+
key={node.id}
|
|
601
|
+
>
|
|
602
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
603
|
+
<button
|
|
604
|
+
className="flex min-w-0 flex-1 items-center gap-2 text-left"
|
|
605
|
+
onClick={() => handleSelect(node.id)}
|
|
606
|
+
type="button"
|
|
607
|
+
>
|
|
608
|
+
<img
|
|
609
|
+
alt=""
|
|
610
|
+
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70"
|
|
611
|
+
src={iconSrc}
|
|
612
|
+
/>
|
|
613
|
+
<p className="truncate font-medium text-foreground text-sm">
|
|
614
|
+
{node.name || `${noun.charAt(0).toUpperCase()}${noun.slice(1)} ${index + 1}`}
|
|
615
|
+
</p>
|
|
616
|
+
{selectedReferenceId === node.id && (
|
|
617
|
+
<Check className="ml-auto h-3.5 w-3.5 shrink-0 text-foreground/80" />
|
|
618
|
+
)}
|
|
619
|
+
</button>
|
|
620
|
+
<button
|
|
621
|
+
aria-label={`Delete ${noun}`}
|
|
622
|
+
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-muted-foreground/50 opacity-0 transition-all hover:bg-destructive/10 hover:text-destructive group-hover/item:opacity-100"
|
|
623
|
+
onClick={(event) => {
|
|
624
|
+
event.stopPropagation()
|
|
625
|
+
deleteNode(node.id)
|
|
626
|
+
if (selectedReferenceId === node.id) {
|
|
627
|
+
setSelectedReferenceId(null)
|
|
628
|
+
}
|
|
629
|
+
}}
|
|
630
|
+
type="button"
|
|
631
|
+
>
|
|
632
|
+
<Trash2 className="h-3 w-3" />
|
|
633
|
+
</button>
|
|
634
|
+
</div>
|
|
635
|
+
<SliderControl
|
|
636
|
+
label="Opacity"
|
|
637
|
+
max={100}
|
|
638
|
+
min={0}
|
|
639
|
+
onChange={(value) =>
|
|
640
|
+
updateNode(node.id, { opacity: Math.round(Math.min(100, Math.max(0, value))) })
|
|
641
|
+
}
|
|
642
|
+
precision={0}
|
|
643
|
+
step={1}
|
|
644
|
+
unit="%"
|
|
645
|
+
value={node.opacity}
|
|
646
|
+
/>
|
|
647
|
+
</div>
|
|
648
|
+
))}
|
|
649
|
+
</div>
|
|
650
|
+
) : (
|
|
651
|
+
<div className="rounded-xl border border-border/45 border-dashed bg-background/60 px-3 py-3 text-muted-foreground text-sm">
|
|
652
|
+
{emptyText}
|
|
653
|
+
</div>
|
|
654
|
+
)}
|
|
655
|
+
</div>
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function ReferencesControl() {
|
|
660
|
+
const showScans = useViewer((state) => state.showScans)
|
|
661
|
+
const setShowScans = useViewer((state) => state.setShowScans)
|
|
662
|
+
const showGuides = useViewer((state) => state.showGuides)
|
|
663
|
+
const setShowGuides = useViewer((state) => state.setShowGuides)
|
|
664
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
665
|
+
const [uploadError, setUploadError] = useState<string | null>(null)
|
|
666
|
+
|
|
667
|
+
const scans = useLevelScans()
|
|
668
|
+
const guides = useLevelGuides()
|
|
669
|
+
const total = scans.length + guides.length
|
|
670
|
+
const anyVisible = showScans || showGuides
|
|
671
|
+
|
|
672
|
+
const toggleAll = useCallback(() => {
|
|
673
|
+
const next = !anyVisible
|
|
674
|
+
setShowScans(next)
|
|
675
|
+
setShowGuides(next)
|
|
676
|
+
}, [anyVisible, setShowScans, setShowGuides])
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<Popover onOpenChange={setIsOpen} open={isOpen}>
|
|
680
|
+
<div className="flex items-center">
|
|
681
|
+
<ActionButton
|
|
682
|
+
className={cn(
|
|
683
|
+
'rounded-r-none p-0',
|
|
684
|
+
anyVisible
|
|
685
|
+
? 'bg-white/15'
|
|
686
|
+
: 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0',
|
|
687
|
+
)}
|
|
688
|
+
label={`References: ${anyVisible ? 'Visible' : 'Hidden'}`}
|
|
689
|
+
onClick={toggleAll}
|
|
690
|
+
size="icon"
|
|
691
|
+
variant="ghost"
|
|
692
|
+
>
|
|
693
|
+
<div className="relative">
|
|
694
|
+
<img
|
|
695
|
+
alt="References"
|
|
696
|
+
className="h-[28px] w-[28px] object-contain"
|
|
697
|
+
src="/icons/floorplan.webp"
|
|
698
|
+
/>
|
|
699
|
+
<span className="absolute -right-1.5 -bottom-1 min-w-[14px] rounded-full bg-white/20 px-[3px] text-center font-medium text-[9px] text-white/70 leading-[14px]">
|
|
700
|
+
{total}
|
|
701
|
+
</span>
|
|
702
|
+
</div>
|
|
703
|
+
</ActionButton>
|
|
704
|
+
|
|
705
|
+
<PopoverTrigger asChild>
|
|
706
|
+
<button
|
|
707
|
+
aria-expanded={isOpen}
|
|
708
|
+
aria-label="Reference settings"
|
|
709
|
+
className={cn(
|
|
710
|
+
'flex h-11 w-6 items-center justify-center rounded-r-lg transition-colors',
|
|
711
|
+
anyVisible
|
|
712
|
+
? isOpen
|
|
713
|
+
? 'bg-white/10'
|
|
714
|
+
: 'bg-white/5 hover:bg-white/8'
|
|
715
|
+
: isOpen
|
|
716
|
+
? 'bg-white/8'
|
|
717
|
+
: 'opacity-60 hover:bg-white/5 hover:opacity-100',
|
|
718
|
+
)}
|
|
719
|
+
type="button"
|
|
720
|
+
>
|
|
721
|
+
<ChevronDown className={cn('h-3 w-3 transition-transform', isOpen && 'rotate-180')} />
|
|
722
|
+
</button>
|
|
723
|
+
</PopoverTrigger>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
726
|
+
<PopoverContent
|
|
727
|
+
align="center"
|
|
728
|
+
className="w-72 rounded-xl border-border/45 bg-background/96 p-3 shadow-elevation-3 backdrop-blur-xl"
|
|
729
|
+
side="top"
|
|
730
|
+
sideOffset={14}
|
|
731
|
+
>
|
|
732
|
+
<div className="space-y-3">
|
|
733
|
+
{uploadError && (
|
|
734
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/10 px-2.5 py-2 text-destructive text-xs">
|
|
735
|
+
{uploadError}
|
|
736
|
+
</div>
|
|
737
|
+
)}
|
|
738
|
+
<ReferenceListSection
|
|
739
|
+
emptyText={REFERENCES_EMPTY_TEXT}
|
|
740
|
+
iconSrc="/icons/mesh.webp"
|
|
741
|
+
nodes={scans}
|
|
742
|
+
noun="scan"
|
|
743
|
+
onError={setUploadError}
|
|
744
|
+
setShow={setShowScans}
|
|
745
|
+
show={showScans}
|
|
746
|
+
title="Scans"
|
|
747
|
+
/>
|
|
748
|
+
<div className="h-px bg-border/45" />
|
|
749
|
+
<ReferenceListSection
|
|
750
|
+
emptyText={REFERENCES_EMPTY_TEXT}
|
|
751
|
+
iconSrc="/icons/floorplan.webp"
|
|
752
|
+
nodes={guides}
|
|
753
|
+
noun="guide image"
|
|
754
|
+
onError={setUploadError}
|
|
755
|
+
setShow={setShowGuides}
|
|
756
|
+
show={showGuides}
|
|
757
|
+
title="Guide images"
|
|
758
|
+
/>
|
|
759
|
+
</div>
|
|
760
|
+
</PopoverContent>
|
|
761
|
+
</Popover>
|
|
762
|
+
)
|
|
763
|
+
}
|
|
764
|
+
|
|
596
765
|
// ── Reference floor control ────────────────────────────────────────────────────────────────────
|
|
597
766
|
|
|
598
767
|
function ReferenceFloorControl() {
|
|
@@ -749,15 +918,35 @@ function ReferenceFloorControl() {
|
|
|
749
918
|
)
|
|
750
919
|
}
|
|
751
920
|
|
|
752
|
-
// ──
|
|
921
|
+
// ── Riser diagram control ────────────────────────────────────────────────────
|
|
753
922
|
|
|
754
|
-
|
|
923
|
+
function RiserControl() {
|
|
924
|
+
const isRiserOpen = useEditor((state) => state.isRiserOpen)
|
|
925
|
+
const toggleRiserOpen = useEditor((state) => state.toggleRiserOpen)
|
|
926
|
+
|
|
927
|
+
return (
|
|
928
|
+
<ActionButton
|
|
929
|
+
className={cn(
|
|
930
|
+
isRiserOpen
|
|
931
|
+
? 'bg-white/15'
|
|
932
|
+
: 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0',
|
|
933
|
+
)}
|
|
934
|
+
label="Riser diagram"
|
|
935
|
+
onClick={toggleRiserOpen}
|
|
936
|
+
size="icon"
|
|
937
|
+
variant="ghost"
|
|
938
|
+
>
|
|
939
|
+
<Waypoints className="h-4 w-4" />
|
|
940
|
+
</ActionButton>
|
|
941
|
+
)
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
755
945
|
|
|
756
946
|
export function SecondaryToggles() {
|
|
757
947
|
return (
|
|
758
948
|
<div className="flex items-center gap-1">
|
|
759
|
-
<
|
|
760
|
-
<GuidesControl />
|
|
949
|
+
<ReferencesControl />
|
|
761
950
|
</div>
|
|
762
951
|
)
|
|
763
952
|
}
|
|
@@ -765,10 +954,10 @@ export function SecondaryToggles() {
|
|
|
765
954
|
export function ViewToggles() {
|
|
766
955
|
return (
|
|
767
956
|
<div className="flex items-center gap-1">
|
|
768
|
-
<GridSnapControl />
|
|
769
957
|
<ScansControl />
|
|
770
958
|
<GuidesControl />
|
|
771
959
|
<ReferenceFloorControl />
|
|
960
|
+
<RiserControl />
|
|
772
961
|
</div>
|
|
773
962
|
)
|
|
774
963
|
}
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
PencilLine,
|
|
28
28
|
Plus,
|
|
29
29
|
Redo2,
|
|
30
|
+
Sparkles,
|
|
30
31
|
Square,
|
|
31
32
|
SquareStack,
|
|
32
33
|
Sun,
|
|
@@ -190,8 +191,11 @@ export function EditorCommands() {
|
|
|
190
191
|
const { nodes } = useScene.getState()
|
|
191
192
|
const building = Object.values(nodes).find((n) => n.type === 'building')
|
|
192
193
|
if (!building) return
|
|
194
|
+
const levelCount = building.children.filter(
|
|
195
|
+
(childId) => nodes[childId as keyof typeof nodes]?.type === 'level',
|
|
196
|
+
).length
|
|
193
197
|
const newLevel = LevelNode.parse({
|
|
194
|
-
level:
|
|
198
|
+
level: levelCount,
|
|
195
199
|
children: [],
|
|
196
200
|
parentId: building.id,
|
|
197
201
|
})
|
|
@@ -241,10 +245,10 @@ export function EditorCommands() {
|
|
|
241
245
|
label: 'Wall Mode',
|
|
242
246
|
group: 'Viewer Controls',
|
|
243
247
|
icon: <Layers className="h-4 w-4" />,
|
|
244
|
-
keywords: ['wall', 'cutaway', 'up', 'down', 'view'],
|
|
248
|
+
keywords: ['wall', 'cutaway', 'up', 'down', 'translucent', 'view'],
|
|
245
249
|
badge: () => {
|
|
246
250
|
const mode = useViewer.getState().wallMode
|
|
247
|
-
return { cutaway: 'Cutaway', up: 'Up', down: 'Down' }[mode]
|
|
251
|
+
return { cutaway: 'Cutaway', up: 'Up', down: 'Down', translucent: 'Translucent' }[mode]
|
|
248
252
|
},
|
|
249
253
|
navigate: true,
|
|
250
254
|
execute: () => navigateTo('wall-mode'),
|
|
@@ -278,19 +282,20 @@ export function EditorCommands() {
|
|
|
278
282
|
}),
|
|
279
283
|
},
|
|
280
284
|
{
|
|
281
|
-
id: 'editor.viewer.
|
|
282
|
-
label:
|
|
283
|
-
const theme = useViewer.getState().theme
|
|
284
|
-
return theme === 'dark' ? 'Switch to Light Theme' : 'Switch to Dark Theme'
|
|
285
|
-
},
|
|
285
|
+
id: 'editor.viewer.shading-solid',
|
|
286
|
+
label: 'Switch to Solid',
|
|
286
287
|
group: 'Viewer Controls',
|
|
287
|
-
icon: <
|
|
288
|
-
keywords: ['
|
|
289
|
-
execute: () =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
288
|
+
icon: <Box className="h-4 w-4" />,
|
|
289
|
+
keywords: ['solid', 'shading', 'render', 'mode', 'performance'],
|
|
290
|
+
execute: () => run(() => useViewer.getState().setShading('solid')),
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'editor.viewer.shading-rendered',
|
|
294
|
+
label: 'Switch to Rendered',
|
|
295
|
+
group: 'Viewer Controls',
|
|
296
|
+
icon: <Sparkles className="h-4 w-4" />,
|
|
297
|
+
keywords: ['rendered', 'shading', 'render', 'mode', 'quality'],
|
|
298
|
+
execute: () => run(() => useViewer.getState().setShading('rendered')),
|
|
294
299
|
},
|
|
295
300
|
{
|
|
296
301
|
id: 'editor.viewer.camera-snapshot',
|
|
@@ -10,6 +10,7 @@ import { useEffect, useState } from 'react'
|
|
|
10
10
|
import { create } from 'zustand'
|
|
11
11
|
import { useShallow } from 'zustand/shallow'
|
|
12
12
|
import { Dialog, DialogContent, DialogTitle } from './../../../components/ui/primitives/dialog'
|
|
13
|
+
import { getLevelDisplayName } from '@pascal-app/core'
|
|
13
14
|
import { useCommandRegistry } from '../../../store/use-command-registry'
|
|
14
15
|
import { usePaletteViewRegistry } from '../../../store/use-palette-view-registry'
|
|
15
16
|
|
|
@@ -243,10 +244,11 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
|
|
|
243
244
|
setOpen(false)
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
const wallModeLabel: Record<'cutaway' | 'up' | 'down', string> = {
|
|
247
|
+
const wallModeLabel: Record<'cutaway' | 'up' | 'down' | 'translucent', string> = {
|
|
247
248
|
cutaway: 'Cutaway',
|
|
248
249
|
up: 'Up',
|
|
249
250
|
down: 'Down',
|
|
251
|
+
translucent: 'Translucent',
|
|
250
252
|
}
|
|
251
253
|
const levelModeLabel: Record<'manual' | 'stacked' | 'exploded' | 'solo', string> = {
|
|
252
254
|
manual: 'Manual',
|
|
@@ -372,7 +374,7 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
|
|
|
372
374
|
{/* ── Wall Mode sub-page ────────────────────────────────────── */}
|
|
373
375
|
{page === 'wall-mode' && (
|
|
374
376
|
<Command.Group heading="Wall Mode">
|
|
375
|
-
{(['cutaway', 'up', 'down'] as const).map((mode) => (
|
|
377
|
+
{(['cutaway', 'up', 'down', 'translucent'] as const).map((mode) => (
|
|
376
378
|
<OptionItem
|
|
377
379
|
isActive={wallMode === mode}
|
|
378
380
|
key={mode}
|
|
@@ -404,7 +406,7 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
|
|
|
404
406
|
<OptionItem
|
|
405
407
|
isActive={level.id === activeLevelId}
|
|
406
408
|
key={level.id}
|
|
407
|
-
label={level
|
|
409
|
+
label={getLevelDisplayName(level)}
|
|
408
410
|
onSelect={() =>
|
|
409
411
|
run(() => useViewer.getState().setSelection({ levelId: level.id }))
|
|
410
412
|
}
|