@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
|
@@ -2,38 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
4
|
import {
|
|
5
|
+
getCatalogMaterialById,
|
|
6
|
+
getLibraryMaterialIdFromRef,
|
|
7
|
+
getSceneMaterialIdFromRef,
|
|
5
8
|
initSpaceDetectionSync,
|
|
6
9
|
initSpatialGridSync,
|
|
7
10
|
spatialGridManager,
|
|
8
11
|
useScene,
|
|
9
12
|
} from '@pascal-app/core'
|
|
10
|
-
import { type HoverStyles, InteractiveSystem, useViewer, Viewer } from '@pascal-app/viewer'
|
|
11
13
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from 'react'
|
|
14
|
+
type HoverStyles,
|
|
15
|
+
InteractiveSystem,
|
|
16
|
+
SceneEnvironment,
|
|
17
|
+
useViewer,
|
|
18
|
+
Viewer,
|
|
19
|
+
} from '@pascal-app/viewer'
|
|
20
|
+
import { memo, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
20
21
|
import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
21
22
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
22
|
-
import { type PresetsAdapter, PresetsProvider } from '../../contexts/presets-context'
|
|
23
|
-
import { useAutoFrame } from '../../hooks/use-auto-frame'
|
|
24
23
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
25
24
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
25
|
+
import { type ActivePaintMaterial, hasActivePaintMaterial } from '../../lib/material-paint'
|
|
26
26
|
import {
|
|
27
27
|
applySceneGraphToEditor,
|
|
28
28
|
loadSceneFromLocalStorage,
|
|
29
29
|
type SceneGraph,
|
|
30
30
|
writePersistedSelection,
|
|
31
31
|
} from '../../lib/scene'
|
|
32
|
-
import { initSFXBus } from '../../lib/sfx-bus'
|
|
32
|
+
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
33
33
|
import useEditor from '../../store/use-editor'
|
|
34
34
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
35
35
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
36
36
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
37
|
+
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
37
38
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
38
39
|
import { ZoneLabelEditorSystem } from '../systems/zone/zone-label-editor-system'
|
|
39
40
|
import { ZoneSystem } from '../systems/zone/zone-system'
|
|
@@ -54,29 +55,41 @@ import type { ExtraPanel } from '../ui/sidebar/icon-rail'
|
|
|
54
55
|
import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/settings-panel'
|
|
55
56
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
56
57
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
58
|
+
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
57
59
|
import { CustomCameraControls } from './custom-camera-controls'
|
|
58
60
|
import { EditorLayoutV2 } from './editor-layout-v2'
|
|
59
61
|
import { ExportManager } from './export-manager'
|
|
62
|
+
import { FenceTangentLines3D } from './fence-tangent-lines-3d'
|
|
60
63
|
import { FirstPersonControls, FirstPersonOverlay } from './first-person-controls'
|
|
61
64
|
import { FloatingActionMenu } from './floating-action-menu'
|
|
62
65
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
63
66
|
import { FloorplanPanel } from './floorplan-panel'
|
|
64
67
|
import { Grid } from './grid'
|
|
65
|
-
import {
|
|
68
|
+
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
69
|
+
import { GroupRotateHandle } from './group-rotate-handle'
|
|
70
|
+
import { GroupSelectionBox3D } from './group-selection-box-3d'
|
|
71
|
+
import { NodeArrowHandles } from './node-arrow-handles'
|
|
72
|
+
import { QuickMeasurementHud } from './quick-measurement-hud'
|
|
73
|
+
import { RiserDiagramPanel } from './riser-diagram-panel'
|
|
66
74
|
import { SelectionManager } from './selection-manager'
|
|
67
75
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
76
|
+
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
68
77
|
import { SnapshotCaptureOverlay } from './snapshot-capture-overlay'
|
|
69
78
|
import { type SnapshotCameraData, ThumbnailGenerator } from './thumbnail-generator'
|
|
70
79
|
import { WallMeasurementLabel } from './wall-measurement-label'
|
|
80
|
+
import { WallMoveSideHandles } from './wall-move-side-handles'
|
|
81
|
+
import { WallOpeningHighlights } from './wall-opening-highlights'
|
|
71
82
|
|
|
72
83
|
const CAMERA_CONTROLS_HINT_DISMISSED_STORAGE_KEY = 'editor-camera-controls-hint-dismissed:v1'
|
|
73
84
|
const DELETE_CURSOR_BADGE_COLOR = '#ef4444'
|
|
74
85
|
const DELETE_CURSOR_BADGE_OFFSET_X = 14
|
|
75
86
|
const DELETE_CURSOR_BADGE_OFFSET_Y = 14
|
|
76
|
-
const PAINT_CURSOR_BADGE_COLOR = '#
|
|
87
|
+
const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
77
88
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
78
89
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
79
90
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
91
|
+
const SCENE_READY_FALLBACK_MS = 8000
|
|
92
|
+
type PaintCursorBadgeState = 'empty' | 'ready' | 'blocked'
|
|
80
93
|
const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
81
94
|
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
82
95
|
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
@@ -88,6 +101,7 @@ const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
|
88
101
|
pulse: false,
|
|
89
102
|
},
|
|
90
103
|
}
|
|
104
|
+
const EDITOR_DEFAULT_RENDER = { shading: 'solid' } as const
|
|
91
105
|
|
|
92
106
|
/**
|
|
93
107
|
* Wire up module-level singletons (spatial grid, space detection, SFX) for
|
|
@@ -106,6 +120,7 @@ function initializeEditorRuntime(): () => void {
|
|
|
106
120
|
unsubscribeSpaceDetection?.()
|
|
107
121
|
|
|
108
122
|
spatialGridManager.clear()
|
|
123
|
+
disposeSFXBus()
|
|
109
124
|
|
|
110
125
|
const outliner = useViewer.getState().outliner
|
|
111
126
|
outliner.selectedObjects.length = 0
|
|
@@ -125,12 +140,24 @@ export interface EditorProps {
|
|
|
125
140
|
sidebarTabs?: (SidebarTab & { component: React.ComponentType })[]
|
|
126
141
|
viewerToolbarLeft?: ReactNode
|
|
127
142
|
viewerToolbarRight?: ReactNode
|
|
143
|
+
/**
|
|
144
|
+
* Full-bleed surface swapped in over the 3D canvas (v2) — e.g. the studio
|
|
145
|
+
* gallery. The canvas stays mounted underneath (no WebGL re-init) and the
|
|
146
|
+
* viewer toolbar stays on top so the host's stage switch remains reachable.
|
|
147
|
+
*/
|
|
148
|
+
stageOverlay?: ReactNode
|
|
149
|
+
/**
|
|
150
|
+
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
151
|
+
* affordance here so it reads as part of the inspector surface and shows
|
|
152
|
+
* only while a node is selected.
|
|
153
|
+
*/
|
|
154
|
+
inspectorFooter?: ReactNode
|
|
128
155
|
|
|
129
156
|
projectId?: string | null
|
|
130
157
|
|
|
131
158
|
// Persistence — defaults to localStorage when omitted
|
|
132
159
|
onLoad?: () => Promise<SceneGraph | null>
|
|
133
|
-
onSave?: (scene: SceneGraph) => Promise<void>
|
|
160
|
+
onSave?: (scene: SceneGraph, options?: { keepalive?: boolean }) => Promise<void>
|
|
134
161
|
onDirty?: () => void
|
|
135
162
|
onSaveStatusChange?: (status: SaveStatus) => void
|
|
136
163
|
|
|
@@ -141,6 +168,10 @@ export interface EditorProps {
|
|
|
141
168
|
// Loading indicator (e.g. project fetching in community mode)
|
|
142
169
|
isLoading?: boolean
|
|
143
170
|
|
|
171
|
+
// Fires when the full-screen scene loader shows/hides — lets hosts measure
|
|
172
|
+
// open-to-interactive time without reaching into internal loader state.
|
|
173
|
+
onLoaderChange?: (visible: boolean) => void
|
|
174
|
+
|
|
144
175
|
// Thumbnail
|
|
145
176
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
146
177
|
|
|
@@ -153,9 +184,6 @@ export interface EditorProps {
|
|
|
153
184
|
sitePanelProps?: SitePanelProps
|
|
154
185
|
extraSidebarPanels?: ExtraPanel[]
|
|
155
186
|
|
|
156
|
-
// Presets storage backend (defaults to localStorage)
|
|
157
|
-
presetsAdapter?: PresetsAdapter
|
|
158
|
-
|
|
159
187
|
// Command palette fallback when no commands match
|
|
160
188
|
commandPaletteEmptyAction?: CommandPaletteEmptyAction
|
|
161
189
|
}
|
|
@@ -334,6 +362,7 @@ const EDITOR_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
334
362
|
{
|
|
335
363
|
action: 'Pan',
|
|
336
364
|
keys: [{ value: 'Space' }, { value: 'Left click' }],
|
|
365
|
+
alternativeKeys: [{ value: 'Middle click' }],
|
|
337
366
|
},
|
|
338
367
|
{ action: 'Rotate', keys: [{ value: 'Right click' }] },
|
|
339
368
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
@@ -526,86 +555,210 @@ function DeleteCursorBadge({ position }: { position: { x: number; y: number } })
|
|
|
526
555
|
)
|
|
527
556
|
}
|
|
528
557
|
|
|
558
|
+
function getActivePaintMaterialSwatchColor(
|
|
559
|
+
material: ActivePaintMaterial | null,
|
|
560
|
+
sceneMaterials: ReturnType<typeof useScene.getState>['materials'],
|
|
561
|
+
) {
|
|
562
|
+
const directColor = material?.material?.properties?.color
|
|
563
|
+
if (directColor) return directColor
|
|
564
|
+
|
|
565
|
+
const sceneMaterialId = getSceneMaterialIdFromRef(material?.materialPreset)
|
|
566
|
+
if (sceneMaterialId) {
|
|
567
|
+
const sceneMaterial = sceneMaterials[sceneMaterialId as keyof typeof sceneMaterials]
|
|
568
|
+
const sceneColor = sceneMaterial?.material.properties?.color
|
|
569
|
+
if (sceneColor) return sceneColor
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const catalogId =
|
|
573
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
574
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
575
|
+
return (
|
|
576
|
+
catalogMaterial?.previewColor ??
|
|
577
|
+
catalogMaterial?.preset.mapProperties.color ??
|
|
578
|
+
PAINT_CURSOR_BADGE_COLOR
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function getActivePaintMaterialSwatchImageUrl(
|
|
583
|
+
material: ActivePaintMaterial | null,
|
|
584
|
+
sceneMaterials: ReturnType<typeof useScene.getState>['materials'],
|
|
585
|
+
) {
|
|
586
|
+
const directTextureUrl = material?.material?.texture?.url
|
|
587
|
+
if (directTextureUrl) return directTextureUrl
|
|
588
|
+
|
|
589
|
+
const sceneMaterialId = getSceneMaterialIdFromRef(material?.materialPreset)
|
|
590
|
+
if (sceneMaterialId) {
|
|
591
|
+
const sceneMaterial = sceneMaterials[sceneMaterialId as keyof typeof sceneMaterials]
|
|
592
|
+
const sceneTextureUrl = sceneMaterial?.material.texture?.url
|
|
593
|
+
if (sceneTextureUrl) return sceneTextureUrl
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const catalogId =
|
|
597
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
598
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
599
|
+
return catalogMaterial?.previewThumbnailUrl ?? catalogMaterial?.preset.maps.albedoMap
|
|
600
|
+
}
|
|
601
|
+
|
|
529
602
|
function PaintCursorBadge({
|
|
530
603
|
position,
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
604
|
+
state,
|
|
605
|
+
swatchColor,
|
|
606
|
+
swatchImageUrl,
|
|
607
|
+
isEraser,
|
|
534
608
|
}: {
|
|
535
609
|
position: { x: number; y: number }
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
610
|
+
state: PaintCursorBadgeState
|
|
611
|
+
swatchColor: string
|
|
612
|
+
swatchImageUrl?: string
|
|
613
|
+
isEraser: boolean
|
|
539
614
|
}) {
|
|
540
|
-
const accentColor =
|
|
615
|
+
const accentColor =
|
|
616
|
+
state === 'ready'
|
|
617
|
+
? isEraser
|
|
618
|
+
? PAINT_CURSOR_BADGE_COLOR
|
|
619
|
+
: swatchColor
|
|
620
|
+
: PAINT_CURSOR_BADGE_DISABLED_COLOR
|
|
621
|
+
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' ? 0.62 : 0.42
|
|
622
|
+
const lineHeight = 18
|
|
541
623
|
|
|
542
624
|
return (
|
|
543
625
|
<div
|
|
544
626
|
aria-hidden="true"
|
|
545
627
|
className="pointer-events-none absolute z-40"
|
|
546
628
|
style={{
|
|
547
|
-
left: position.x
|
|
548
|
-
top: position.y
|
|
629
|
+
left: position.x,
|
|
630
|
+
top: position.y,
|
|
549
631
|
}}
|
|
550
632
|
>
|
|
551
633
|
<div
|
|
552
|
-
className="
|
|
634
|
+
className="-translate-x-1/2 -translate-y-full absolute top-0 left-1/2"
|
|
635
|
+
style={{
|
|
636
|
+
backgroundColor: accentColor,
|
|
637
|
+
boxShadow: `0 0 12px ${accentColor}cc`,
|
|
638
|
+
height: lineHeight,
|
|
639
|
+
width: 2,
|
|
640
|
+
}}
|
|
641
|
+
/>
|
|
642
|
+
<div
|
|
643
|
+
className="absolute top-0 left-1/2 flex h-8 w-8 items-center justify-center rounded-xl border border-white/5 bg-zinc-900/95 shadow-[0_8px_16px_-4px_rgba(0,0,0,0.3),0_4px_8px_-4px_rgba(0,0,0,0.2)]"
|
|
553
644
|
style={{
|
|
554
645
|
boxShadow: `0 8px 16px -4px rgba(0,0,0,0.3), 0 4px 8px -4px rgba(0,0,0,0.2), 0 0 18px ${accentColor}22`,
|
|
646
|
+
transform: `translate(-50%, calc(-100% - ${lineHeight}px))`,
|
|
555
647
|
}}
|
|
556
648
|
>
|
|
557
|
-
|
|
649
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
650
|
+
<img
|
|
651
|
+
alt=""
|
|
558
652
|
aria-hidden="true"
|
|
559
|
-
className="drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
653
|
+
className="h-5 w-5 object-contain drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
654
|
+
src="/icons/paint.webp"
|
|
655
|
+
style={{
|
|
656
|
+
filter: state === 'ready' ? undefined : 'grayscale(1)',
|
|
657
|
+
opacity: iconOpacity,
|
|
658
|
+
}}
|
|
564
659
|
/>
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
660
|
+
{state === 'ready' ? (
|
|
661
|
+
isEraser ? (
|
|
662
|
+
<span className="-right-1 -bottom-1 absolute flex h-3.5 w-3.5 items-center justify-center rounded-full border border-white/35 bg-zinc-950 text-white shadow-[0_2px_6px_rgba(0,0,0,0.45)]">
|
|
663
|
+
<Icon
|
|
664
|
+
aria-hidden="true"
|
|
665
|
+
color="currentColor"
|
|
666
|
+
height={10}
|
|
667
|
+
icon="mdi:eraser-variant"
|
|
668
|
+
width={10}
|
|
669
|
+
/>
|
|
670
|
+
</span>
|
|
671
|
+
) : (
|
|
672
|
+
<span
|
|
673
|
+
className="-right-1 -bottom-1 absolute h-3.5 w-3.5 rounded-full border border-white/70 bg-cover bg-center shadow-[0_2px_6px_rgba(0,0,0,0.45)]"
|
|
674
|
+
style={{
|
|
675
|
+
backgroundColor: swatchColor,
|
|
676
|
+
backgroundImage: swatchImageUrl
|
|
677
|
+
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
678
|
+
: undefined,
|
|
679
|
+
}}
|
|
680
|
+
/>
|
|
681
|
+
)
|
|
682
|
+
) : state === 'blocked' ? (
|
|
683
|
+
<span className="-right-1 -bottom-1 absolute flex h-3.5 w-3.5 items-center justify-center rounded-full border border-white/30 bg-zinc-950 text-rose-300 shadow-[0_2px_6px_rgba(0,0,0,0.45)]">
|
|
684
|
+
<Icon
|
|
685
|
+
aria-hidden="true"
|
|
686
|
+
color="currentColor"
|
|
687
|
+
height={12}
|
|
688
|
+
icon="mdi:cancel"
|
|
689
|
+
width={12}
|
|
690
|
+
/>
|
|
691
|
+
</span>
|
|
692
|
+
) : (
|
|
693
|
+
<span className="-right-1 -bottom-1 absolute flex h-3.5 w-3.5 items-center justify-center rounded-full border border-white/30 bg-zinc-950 font-semibold text-[9px] text-slate-300 shadow-[0_2px_6px_rgba(0,0,0,0.45)]">
|
|
694
|
+
?
|
|
695
|
+
</span>
|
|
696
|
+
)}
|
|
568
697
|
</div>
|
|
569
698
|
</div>
|
|
570
699
|
)
|
|
571
700
|
}
|
|
572
701
|
|
|
702
|
+
// Subscribes to `gridSnapStep` so the visible grid cell size matches whatever
|
|
703
|
+
// the wall draft tool snaps to — otherwise the cursor lands between visible
|
|
704
|
+
// grid lines when the user picks a finer snap (0.25 / 0.1 / 0.05).
|
|
705
|
+
function SnapAwareGrid() {
|
|
706
|
+
const gridSnapStep = useEditor((s) => s.gridSnapStep)
|
|
707
|
+
return <Grid cellColor="#aaa" cellSize={gridSnapStep} fadeDistance={500} sectionColor="#ccc" />
|
|
708
|
+
}
|
|
709
|
+
|
|
573
710
|
// ── Viewer scene content: memoized so <Viewer> doesn't re-render on mode/viewMode changes ──
|
|
574
711
|
|
|
575
712
|
const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
576
713
|
isVersionPreviewMode,
|
|
577
714
|
isLoading,
|
|
578
715
|
isFirstPersonMode,
|
|
716
|
+
isStudioMode,
|
|
579
717
|
onThumbnailCapture,
|
|
580
718
|
}: {
|
|
581
719
|
isVersionPreviewMode: boolean
|
|
582
720
|
isLoading: boolean
|
|
583
721
|
isFirstPersonMode: boolean
|
|
722
|
+
isStudioMode: boolean
|
|
584
723
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
585
724
|
}) {
|
|
725
|
+
// Studio mode is a clean render/snapshot surface — no selection or editing
|
|
726
|
+
// affordances. It mirrors version-preview's chrome gating on the canvas.
|
|
727
|
+
// Capture (snapshot) mode is camera-only for the same reason: suppress
|
|
728
|
+
// selection, editing handles, and the tool manager (which mounts the site
|
|
729
|
+
// boundary flags) so the framed shot stays clean.
|
|
730
|
+
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
731
|
+
const noEditing = isVersionPreviewMode || isFirstPersonMode || isStudioMode || isCaptureMode
|
|
586
732
|
return (
|
|
587
733
|
<>
|
|
588
|
-
|
|
589
|
-
{!(
|
|
590
|
-
{!
|
|
591
|
-
{!
|
|
734
|
+
<SceneEnvironment />
|
|
735
|
+
{!(isFirstPersonMode || isStudioMode || isCaptureMode) && <SelectionManager />}
|
|
736
|
+
{!noEditing && <BoxSelectTool />}
|
|
737
|
+
{!noEditing && <NodeArrowHandles />}
|
|
738
|
+
{!noEditing && <GroupRotateHandle />}
|
|
739
|
+
{!noEditing && <GroupSelectionBox3D />}
|
|
740
|
+
{!noEditing && <WallOpeningHighlights />}
|
|
741
|
+
{!noEditing && <SlabHoleHighlights />}
|
|
742
|
+
{!noEditing && <WallMoveSideHandles />}
|
|
743
|
+
{!noEditing && <FenceTangentLines3D />}
|
|
744
|
+
{!noEditing && <FloatingActionMenu />}
|
|
745
|
+
{!noEditing && <GroupFloatingActionMenu />}
|
|
746
|
+
{!noEditing && <FloatingBuildingActionMenu />}
|
|
592
747
|
{!isFirstPersonMode && <WallMeasurementLabel />}
|
|
593
748
|
<ExportManager />
|
|
594
749
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
595
750
|
<CeilingSystem />
|
|
596
751
|
<CeilingSelectionAffordanceSystem />
|
|
752
|
+
{!noEditing && <SelectionAffordanceManager />}
|
|
597
753
|
<RoofEditSystem />
|
|
598
754
|
<StairEditSystem />
|
|
599
|
-
{!(isLoading || isFirstPersonMode) &&
|
|
600
|
-
|
|
601
|
-
)}
|
|
602
|
-
{!(isLoading || isVersionPreviewMode || isFirstPersonMode) && <ToolManager />}
|
|
755
|
+
{!(isLoading || isFirstPersonMode) && <SnapAwareGrid />}
|
|
756
|
+
{!(isLoading || noEditing) && <ToolManager />}
|
|
603
757
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
604
758
|
<CustomCameraControls />
|
|
605
759
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
606
|
-
<PresetThumbnailGenerator />
|
|
607
760
|
{!isFirstPersonMode && <SiteEdgeLabels />}
|
|
608
|
-
|
|
761
|
+
<InteractiveSystem />
|
|
609
762
|
</>
|
|
610
763
|
)
|
|
611
764
|
})
|
|
@@ -678,7 +831,7 @@ function DeleteCursorLayer({
|
|
|
678
831
|
|
|
679
832
|
return (
|
|
680
833
|
<div
|
|
681
|
-
className="pointer-events-none"
|
|
834
|
+
className="pointer-events-none z-40"
|
|
682
835
|
ref={badgeRef}
|
|
683
836
|
style={{ display: 'none', position: 'absolute', left: 0, top: 0 }}
|
|
684
837
|
>
|
|
@@ -696,15 +849,15 @@ function PaintCursorLayer({
|
|
|
696
849
|
}) {
|
|
697
850
|
const mode = useEditor((s) => s.mode)
|
|
698
851
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
699
|
-
const
|
|
700
|
-
const
|
|
852
|
+
const paintEraser = useEditor((s) => s.paintEraser)
|
|
853
|
+
const paintHover = useEditor((s) => s.paintHover)
|
|
854
|
+
const sceneMaterials = useScene((s) => s.materials)
|
|
855
|
+
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
701
856
|
const active = mode === 'material-paint' && !isVersionPreviewMode
|
|
702
857
|
|
|
703
858
|
useEffect(() => {
|
|
704
859
|
if (!active) {
|
|
705
|
-
|
|
706
|
-
badgeRef.current.style.display = 'none'
|
|
707
|
-
}
|
|
860
|
+
setPosition(null)
|
|
708
861
|
return
|
|
709
862
|
}
|
|
710
863
|
const el = containerRef.current
|
|
@@ -712,20 +865,16 @@ function PaintCursorLayer({
|
|
|
712
865
|
let frame = 0
|
|
713
866
|
let nextX = 0
|
|
714
867
|
let nextY = 0
|
|
715
|
-
const badge = badgeRef.current
|
|
716
868
|
|
|
717
869
|
const flushPosition = () => {
|
|
718
870
|
frame = 0
|
|
719
|
-
|
|
720
|
-
badge.style.display = 'block'
|
|
721
|
-
badge.style.transform = `translate(${nextX + PAINT_CURSOR_BADGE_OFFSET_X}px, ${nextY + PAINT_CURSOR_BADGE_OFFSET_Y}px)`
|
|
871
|
+
setPosition({ x: nextX, y: nextY })
|
|
722
872
|
}
|
|
723
873
|
|
|
724
|
-
const
|
|
874
|
+
const updateFromEvent = (e: PointerEvent) => {
|
|
725
875
|
const rect = el.getBoundingClientRect()
|
|
726
876
|
nextX = e.clientX - rect.left
|
|
727
877
|
nextY = e.clientY - rect.top
|
|
728
|
-
|
|
729
878
|
if (frame === 0) {
|
|
730
879
|
frame = window.requestAnimationFrame(flushPosition)
|
|
731
880
|
}
|
|
@@ -735,48 +884,45 @@ function PaintCursorLayer({
|
|
|
735
884
|
window.cancelAnimationFrame(frame)
|
|
736
885
|
frame = 0
|
|
737
886
|
}
|
|
738
|
-
|
|
739
|
-
badge.style.display = 'none'
|
|
740
|
-
}
|
|
887
|
+
setPosition(null)
|
|
741
888
|
}
|
|
742
|
-
el.addEventListener('pointermove',
|
|
889
|
+
el.addEventListener('pointermove', updateFromEvent)
|
|
890
|
+
el.addEventListener('pointerenter', updateFromEvent)
|
|
891
|
+
el.addEventListener('pointerdown', updateFromEvent)
|
|
743
892
|
el.addEventListener('pointerleave', onLeave)
|
|
744
893
|
return () => {
|
|
745
894
|
if (frame !== 0) {
|
|
746
895
|
window.cancelAnimationFrame(frame)
|
|
747
896
|
}
|
|
748
|
-
el.removeEventListener('pointermove',
|
|
897
|
+
el.removeEventListener('pointermove', updateFromEvent)
|
|
898
|
+
el.removeEventListener('pointerenter', updateFromEvent)
|
|
899
|
+
el.removeEventListener('pointerdown', updateFromEvent)
|
|
749
900
|
el.removeEventListener('pointerleave', onLeave)
|
|
750
901
|
}
|
|
751
902
|
}, [active, containerRef])
|
|
752
903
|
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const
|
|
904
|
+
const hasPaint = paintEraser || hasActivePaintMaterial(activePaintMaterial)
|
|
905
|
+
const badgeState: PaintCursorBadgeState = !hasPaint
|
|
906
|
+
? 'empty'
|
|
907
|
+
: paintHover != null
|
|
908
|
+
? 'ready'
|
|
909
|
+
: 'blocked'
|
|
910
|
+
const swatchColor = getActivePaintMaterialSwatchColor(activePaintMaterial, sceneMaterials)
|
|
911
|
+
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(activePaintMaterial, sceneMaterials)
|
|
760
912
|
|
|
761
|
-
|
|
762
|
-
if (!active && badgeRef.current) {
|
|
763
|
-
badgeRef.current.style.display = 'none'
|
|
764
|
-
}
|
|
765
|
-
}, [active])
|
|
766
|
-
|
|
767
|
-
if (!active) return null
|
|
913
|
+
if (!active || !position) return null
|
|
768
914
|
|
|
769
915
|
return (
|
|
770
916
|
<div
|
|
771
|
-
className="pointer-events-none"
|
|
772
|
-
|
|
773
|
-
style={{ display: 'none', position: 'absolute', left: 0, top: 0 }}
|
|
917
|
+
className="pointer-events-none absolute z-40"
|
|
918
|
+
style={{ left: 0, top: 0, transform: `translate(${position.x}px, ${position.y}px)` }}
|
|
774
919
|
>
|
|
775
920
|
<PaintCursorBadge
|
|
776
|
-
|
|
777
|
-
icon={icon}
|
|
778
|
-
label={label}
|
|
921
|
+
isEraser={paintEraser}
|
|
779
922
|
position={{ x: 0, y: 0 }}
|
|
923
|
+
state={badgeState}
|
|
924
|
+
swatchColor={swatchColor}
|
|
925
|
+
swatchImageUrl={swatchImageUrl}
|
|
780
926
|
/>
|
|
781
927
|
</div>
|
|
782
928
|
)
|
|
@@ -789,15 +935,21 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
789
935
|
isVersionPreviewMode,
|
|
790
936
|
isLoading,
|
|
791
937
|
isFirstPersonMode,
|
|
938
|
+
isStudioMode,
|
|
792
939
|
hasLoadedInitialScene,
|
|
793
940
|
showLoader,
|
|
941
|
+
sceneReadyKey,
|
|
942
|
+
onSceneReadyChange,
|
|
794
943
|
onThumbnailCapture,
|
|
795
944
|
}: {
|
|
796
945
|
isVersionPreviewMode: boolean
|
|
797
946
|
isLoading: boolean
|
|
798
947
|
isFirstPersonMode: boolean
|
|
948
|
+
isStudioMode: boolean
|
|
799
949
|
hasLoadedInitialScene: boolean
|
|
800
950
|
showLoader: boolean
|
|
951
|
+
sceneReadyKey: number
|
|
952
|
+
onSceneReadyChange: (ready: boolean) => void
|
|
801
953
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
802
954
|
}) {
|
|
803
955
|
const viewMode = useEditor((s) => s.viewMode)
|
|
@@ -810,6 +962,13 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
810
962
|
)
|
|
811
963
|
|
|
812
964
|
const viewerAreaRef = useRef<HTMLDivElement>(null)
|
|
965
|
+
// State mirror of `viewerAreaRef` so the floorplan compass portal re-renders
|
|
966
|
+
// once the container exists (a plain ref mutation wouldn't trigger it).
|
|
967
|
+
const [viewerAreaEl, setViewerAreaEl] = useState<HTMLDivElement | null>(null)
|
|
968
|
+
const setViewerAreaNode = useCallback((el: HTMLDivElement | null) => {
|
|
969
|
+
viewerAreaRef.current = el
|
|
970
|
+
setViewerAreaEl(el)
|
|
971
|
+
}, [])
|
|
813
972
|
const viewer3dRef = useRef<HTMLDivElement>(null)
|
|
814
973
|
const isResizingFloorplan = useRef(false)
|
|
815
974
|
|
|
@@ -839,7 +998,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
839
998
|
window.removeEventListener('pointermove', handlePointerMove)
|
|
840
999
|
window.removeEventListener('pointerup', handlePointerUp)
|
|
841
1000
|
}
|
|
842
|
-
}, [])
|
|
1001
|
+
}, [setFloorplanPaneRatio])
|
|
843
1002
|
|
|
844
1003
|
useEffect(() => {
|
|
845
1004
|
setIsCameraControlsHintVisible(!readCameraControlsHintDismissed())
|
|
@@ -855,7 +1014,10 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
855
1014
|
|
|
856
1015
|
return (
|
|
857
1016
|
<ErrorBoundary fallback={<EditorSceneCrashFallback />}>
|
|
858
|
-
|
|
1017
|
+
{/* `relative` so the floorplan compass (portaled here to stay visible in
|
|
1018
|
+
2d / 3d / split alike) can anchor to this container's bottom-left. */}
|
|
1019
|
+
<div className="relative flex h-full" ref={setViewerAreaNode}>
|
|
1020
|
+
<QuickMeasurementHud />
|
|
859
1021
|
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
860
1022
|
<div
|
|
861
1023
|
className="relative h-full flex-shrink-0"
|
|
@@ -865,7 +1027,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
865
1027
|
}}
|
|
866
1028
|
>
|
|
867
1029
|
<div className="h-full w-full overflow-hidden">
|
|
868
|
-
<FloorplanPanel />
|
|
1030
|
+
<FloorplanPanel compassHost={viewerAreaEl} />
|
|
869
1031
|
</div>
|
|
870
1032
|
{viewMode === 'split' && (
|
|
871
1033
|
<div
|
|
@@ -880,6 +1042,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
880
1042
|
{/* 3D viewer — always mounted, hidden via CSS to avoid destroying the WebGL context */}
|
|
881
1043
|
<div
|
|
882
1044
|
className="relative min-w-0 flex-1 overflow-hidden"
|
|
1045
|
+
data-pascal-viewer-3d
|
|
883
1046
|
ref={viewer3dRef}
|
|
884
1047
|
style={{ display: show3d ? undefined : 'none' }}
|
|
885
1048
|
>
|
|
@@ -899,19 +1062,24 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
899
1062
|
) : null}
|
|
900
1063
|
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
901
1064
|
<Viewer
|
|
1065
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
902
1066
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1067
|
+
onSceneReadyChange={onSceneReadyChange}
|
|
1068
|
+
renderContext="editor"
|
|
1069
|
+
sceneReadyKey={sceneReadyKey}
|
|
903
1070
|
selectionManager={isFirstPersonMode ? 'default' : 'custom'}
|
|
904
1071
|
>
|
|
905
1072
|
<ViewerSceneContent
|
|
906
1073
|
isFirstPersonMode={isFirstPersonMode}
|
|
907
|
-
isLoading={
|
|
1074
|
+
isLoading={showLoader}
|
|
1075
|
+
isStudioMode={isStudioMode}
|
|
908
1076
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
909
1077
|
onThumbnailCapture={onThumbnailCapture}
|
|
910
1078
|
/>
|
|
911
1079
|
</Viewer>
|
|
912
1080
|
</div>
|
|
913
1081
|
</div>
|
|
914
|
-
{!(
|
|
1082
|
+
{!(showLoader || isVersionPreviewMode) && <ZoneLabelEditorSystem />}
|
|
915
1083
|
</ErrorBoundary>
|
|
916
1084
|
)
|
|
917
1085
|
})
|
|
@@ -924,6 +1092,8 @@ export default function Editor({
|
|
|
924
1092
|
sidebarTabs,
|
|
925
1093
|
viewerToolbarLeft,
|
|
926
1094
|
viewerToolbarRight,
|
|
1095
|
+
stageOverlay,
|
|
1096
|
+
inspectorFooter,
|
|
927
1097
|
projectId,
|
|
928
1098
|
onLoad,
|
|
929
1099
|
onSave,
|
|
@@ -932,18 +1102,19 @@ export default function Editor({
|
|
|
932
1102
|
previewScene,
|
|
933
1103
|
isVersionPreviewMode = false,
|
|
934
1104
|
isLoading = false,
|
|
1105
|
+
onLoaderChange,
|
|
935
1106
|
onThumbnailCapture,
|
|
936
1107
|
sidebarOverlay,
|
|
937
1108
|
viewerBanner,
|
|
938
1109
|
settingsPanelProps,
|
|
939
1110
|
sitePanelProps,
|
|
940
1111
|
extraSidebarPanels,
|
|
941
|
-
presetsAdapter,
|
|
942
1112
|
commandPaletteEmptyAction,
|
|
943
1113
|
}: EditorProps) {
|
|
944
1114
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
1115
|
+
const isStudioMode = useEditor((s) => s.workspaceMode === 'studio')
|
|
945
1116
|
|
|
946
|
-
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode })
|
|
1117
|
+
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
947
1118
|
|
|
948
1119
|
const { isLoadingSceneRef } = useAutoSave({
|
|
949
1120
|
onSave,
|
|
@@ -954,17 +1125,30 @@ export default function Editor({
|
|
|
954
1125
|
|
|
955
1126
|
const [isSceneLoading, setIsSceneLoading] = useState(false)
|
|
956
1127
|
const [hasLoadedInitialScene, setHasLoadedInitialScene] = useState(false)
|
|
1128
|
+
const [sceneReadyKey, setSceneReadyKey] = useState(0)
|
|
1129
|
+
const [isViewerSceneReady, setIsViewerSceneReady] = useState(false)
|
|
957
1130
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
958
1131
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
959
1132
|
|
|
960
1133
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
|
961
1134
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
962
1135
|
|
|
1136
|
+
// Host-registered rail panels. Called unconditionally so
|
|
1137
|
+
// hook order is stable across the v1 / v2 layout branches below; the v1
|
|
1138
|
+
// AppSidebar path merges its own copy internally, the v2 path merges these
|
|
1139
|
+
// into its tab bar.
|
|
1140
|
+
const hostRailPanels = useHostPanels()
|
|
1141
|
+
|
|
963
1142
|
useEffect(() => {
|
|
964
1143
|
const teardown = initializeEditorRuntime()
|
|
965
1144
|
return teardown
|
|
966
1145
|
}, [])
|
|
967
1146
|
|
|
1147
|
+
useEffect(() => {
|
|
1148
|
+
void useEditor.persist.rehydrate()
|
|
1149
|
+
void useSidebarStore.persist.rehydrate()
|
|
1150
|
+
}, [])
|
|
1151
|
+
|
|
968
1152
|
useEffect(() => {
|
|
969
1153
|
useViewer.getState().setProjectId(projectId ?? null)
|
|
970
1154
|
|
|
@@ -980,15 +1164,24 @@ export default function Editor({
|
|
|
980
1164
|
async function load() {
|
|
981
1165
|
isLoadingSceneRef.current = true
|
|
982
1166
|
setHasLoadedInitialScene(false)
|
|
1167
|
+
setIsViewerSceneReady(false)
|
|
983
1168
|
setIsSceneLoading(true)
|
|
1169
|
+
useScene.getState().unloadScene()
|
|
1170
|
+
useViewer.getState().resetSelection()
|
|
984
1171
|
|
|
985
1172
|
try {
|
|
986
1173
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
987
1174
|
if (!cancelled) {
|
|
988
1175
|
applySceneGraphToEditor(sceneGraph)
|
|
1176
|
+
setIsViewerSceneReady(false)
|
|
1177
|
+
setSceneReadyKey((key) => key + 1)
|
|
989
1178
|
}
|
|
990
1179
|
} catch {
|
|
991
|
-
if (!cancelled)
|
|
1180
|
+
if (!cancelled) {
|
|
1181
|
+
applySceneGraphToEditor(null)
|
|
1182
|
+
setIsViewerSceneReady(false)
|
|
1183
|
+
setSceneReadyKey((key) => key + 1)
|
|
1184
|
+
}
|
|
992
1185
|
} finally {
|
|
993
1186
|
if (!cancelled) {
|
|
994
1187
|
setIsSceneLoading(false)
|
|
@@ -1032,7 +1225,28 @@ export default function Editor({
|
|
|
1032
1225
|
}
|
|
1033
1226
|
}, [])
|
|
1034
1227
|
|
|
1035
|
-
const
|
|
1228
|
+
const handleSceneReadyChange = useCallback((ready: boolean) => {
|
|
1229
|
+
setIsViewerSceneReady(ready)
|
|
1230
|
+
}, [])
|
|
1231
|
+
|
|
1232
|
+
useEffect(() => {
|
|
1233
|
+
if (isLoading || isSceneLoading || !hasLoadedInitialScene || isViewerSceneReady) return
|
|
1234
|
+
|
|
1235
|
+
const timer = window.setTimeout(() => {
|
|
1236
|
+
console.warn('[editor] viewer scene readiness timed out; showing editor shell anyway', {
|
|
1237
|
+
sceneReadyKey,
|
|
1238
|
+
})
|
|
1239
|
+
setIsViewerSceneReady(true)
|
|
1240
|
+
}, SCENE_READY_FALLBACK_MS)
|
|
1241
|
+
|
|
1242
|
+
return () => window.clearTimeout(timer)
|
|
1243
|
+
}, [hasLoadedInitialScene, isLoading, isSceneLoading, isViewerSceneReady, sceneReadyKey])
|
|
1244
|
+
|
|
1245
|
+
const showLoader = isLoading || isSceneLoading || !hasLoadedInitialScene || !isViewerSceneReady
|
|
1246
|
+
|
|
1247
|
+
useEffect(() => {
|
|
1248
|
+
onLoaderChange?.(showLoader)
|
|
1249
|
+
}, [showLoader, onLoaderChange])
|
|
1036
1250
|
|
|
1037
1251
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1038
1252
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
@@ -1071,15 +1285,20 @@ export default function Editor({
|
|
|
1071
1285
|
}, [isFirstPersonMode])
|
|
1072
1286
|
|
|
1073
1287
|
const previewViewerContent = (
|
|
1074
|
-
<Viewer
|
|
1288
|
+
<Viewer
|
|
1289
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1290
|
+
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1291
|
+
renderContext="editor"
|
|
1292
|
+
selectionManager="default"
|
|
1293
|
+
>
|
|
1075
1294
|
<ExportManager />
|
|
1076
1295
|
<ViewerZoneSystem />
|
|
1077
1296
|
<CeilingSystem />
|
|
1078
1297
|
<RoofEditSystem />
|
|
1079
1298
|
<StairEditSystem />
|
|
1299
|
+
{isFirstPersonMode && <FirstPersonControls />}
|
|
1080
1300
|
<CustomCameraControls />
|
|
1081
1301
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1082
|
-
<PresetThumbnailGenerator />
|
|
1083
1302
|
<InteractiveSystem />
|
|
1084
1303
|
</Viewer>
|
|
1085
1304
|
)
|
|
@@ -1089,15 +1308,27 @@ export default function Editor({
|
|
|
1089
1308
|
hasLoadedInitialScene={hasLoadedInitialScene}
|
|
1090
1309
|
isFirstPersonMode={isFirstPersonMode}
|
|
1091
1310
|
isLoading={isLoading}
|
|
1311
|
+
isStudioMode={isStudioMode}
|
|
1092
1312
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1313
|
+
onSceneReadyChange={handleSceneReadyChange}
|
|
1093
1314
|
onThumbnailCapture={onThumbnailCapture}
|
|
1315
|
+
sceneReadyKey={sceneReadyKey}
|
|
1094
1316
|
showLoader={showLoader}
|
|
1095
1317
|
/>
|
|
1096
1318
|
)
|
|
1097
1319
|
|
|
1098
1320
|
// ── V2 layout ──
|
|
1099
1321
|
if (layoutVersion === 'v2') {
|
|
1100
|
-
|
|
1322
|
+
// Registered host panels join the host's `sidebarTabs` as first-class tabs.
|
|
1323
|
+
// Explicit tabs keep precedence because they are already in the map first.
|
|
1324
|
+
const tabMap = new Map<string, SidebarTab & { component: React.ComponentType }>(
|
|
1325
|
+
sidebarTabs?.map((t) => [t.id, t]) ?? [],
|
|
1326
|
+
)
|
|
1327
|
+
for (const p of hostRailPanels) {
|
|
1328
|
+
if (!tabMap.has(p.id)) {
|
|
1329
|
+
tabMap.set(p.id, { id: p.id, label: p.label, icon: p.icon, component: p.component })
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1101
1332
|
|
|
1102
1333
|
const renderTabContent = (tabId: string) => {
|
|
1103
1334
|
// Built-in panels
|
|
@@ -1114,26 +1345,43 @@ export default function Editor({
|
|
|
1114
1345
|
return <Component />
|
|
1115
1346
|
}
|
|
1116
1347
|
|
|
1117
|
-
const tabBarTabs =
|
|
1118
|
-
sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon }) => ({
|
|
1348
|
+
const tabBarTabs = [
|
|
1349
|
+
...(sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon }) => ({
|
|
1119
1350
|
id,
|
|
1120
1351
|
label,
|
|
1121
1352
|
mobileDefaultSnap,
|
|
1122
1353
|
mobileIcon,
|
|
1123
|
-
|
|
1354
|
+
icon,
|
|
1355
|
+
})) ?? []),
|
|
1356
|
+
// Host panels appear after the explicit tabs in the rail. The icon
|
|
1357
|
+
// doubles as the mobile icon; a half-height sheet is a sensible default.
|
|
1358
|
+
...hostRailPanels.map((p) => ({
|
|
1359
|
+
id: p.id,
|
|
1360
|
+
label: p.label,
|
|
1361
|
+
mobileDefaultSnap: 0.5,
|
|
1362
|
+
mobileIcon: p.icon,
|
|
1363
|
+
icon: p.icon,
|
|
1364
|
+
})),
|
|
1365
|
+
]
|
|
1124
1366
|
|
|
1125
1367
|
return (
|
|
1126
|
-
|
|
1368
|
+
<>
|
|
1127
1369
|
{showLoader && (
|
|
1128
1370
|
<div className="fixed inset-0 z-60">
|
|
1129
|
-
<SceneLoader />
|
|
1371
|
+
<SceneLoader className="bg-background" />
|
|
1130
1372
|
</div>
|
|
1131
1373
|
)}
|
|
1132
1374
|
|
|
1133
1375
|
{!isLoading && isPreviewMode ? (
|
|
1134
1376
|
<div className="dark flex h-full w-full flex-col bg-neutral-100 text-foreground">
|
|
1135
|
-
|
|
1136
|
-
|
|
1377
|
+
{isFirstPersonMode ? (
|
|
1378
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1379
|
+
) : (
|
|
1380
|
+
<ViewerOverlay onBack={() => useEditor.getState().setPreviewMode(false)} />
|
|
1381
|
+
)}
|
|
1382
|
+
<div className="h-full w-full" data-pascal-viewer-3d>
|
|
1383
|
+
{previewViewerContent}
|
|
1384
|
+
</div>
|
|
1137
1385
|
</div>
|
|
1138
1386
|
) : (
|
|
1139
1387
|
<>
|
|
@@ -1141,15 +1389,15 @@ export default function Editor({
|
|
|
1141
1389
|
navbarSlot={navbarSlot}
|
|
1142
1390
|
overlays={
|
|
1143
1391
|
<>
|
|
1144
|
-
{!isCaptureMode && <FloatingLevelSelector />}
|
|
1145
|
-
{!(isVersionPreviewMode || isCaptureMode) && (
|
|
1392
|
+
{!(isCaptureMode || stageOverlay) && <FloatingLevelSelector />}
|
|
1393
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1146
1394
|
<div className="pointer-events-auto">
|
|
1147
1395
|
<ActionMenu />
|
|
1148
1396
|
</div>
|
|
1149
1397
|
)}
|
|
1150
|
-
{!(isVersionPreviewMode || isCaptureMode) && (
|
|
1398
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1151
1399
|
<div className="pointer-events-auto">
|
|
1152
|
-
<PanelManager />
|
|
1400
|
+
<PanelManager inspectorFooter={inspectorFooter} />
|
|
1153
1401
|
</div>
|
|
1154
1402
|
)}
|
|
1155
1403
|
{!isCaptureMode && (
|
|
@@ -1157,6 +1405,11 @@ export default function Editor({
|
|
|
1157
1405
|
<HelperManager />
|
|
1158
1406
|
</div>
|
|
1159
1407
|
)}
|
|
1408
|
+
{isFirstPersonMode && (
|
|
1409
|
+
<FirstPersonOverlay
|
|
1410
|
+
onExit={() => useEditor.getState().setFirstPersonMode(false)}
|
|
1411
|
+
/>
|
|
1412
|
+
)}
|
|
1160
1413
|
{viewerBanner}
|
|
1161
1414
|
{projectId ? <SnapshotCaptureOverlay projectId={projectId} /> : null}
|
|
1162
1415
|
</>
|
|
@@ -1164,21 +1417,16 @@ export default function Editor({
|
|
|
1164
1417
|
renderTabContent={renderTabContent}
|
|
1165
1418
|
sidebarOverlay={sidebarOverlay}
|
|
1166
1419
|
sidebarTabs={tabBarTabs}
|
|
1420
|
+
stageOverlay={stageOverlay}
|
|
1167
1421
|
viewerContent={viewerCanvas}
|
|
1168
1422
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1169
1423
|
viewerToolbarRight={viewerToolbarRight}
|
|
1170
1424
|
/>
|
|
1171
1425
|
<EditorCommands />
|
|
1172
1426
|
<CommandPalette emptyAction={commandPaletteEmptyAction} />
|
|
1173
|
-
{/* First-person overlay — rendered on top of normal layout */}
|
|
1174
|
-
{isFirstPersonMode && (
|
|
1175
|
-
<div className="pointer-events-none fixed inset-0 z-50">
|
|
1176
|
-
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1177
|
-
</div>
|
|
1178
|
-
)}
|
|
1179
1427
|
</>
|
|
1180
1428
|
)}
|
|
1181
|
-
|
|
1429
|
+
</>
|
|
1182
1430
|
)
|
|
1183
1431
|
}
|
|
1184
1432
|
|
|
@@ -1189,57 +1437,59 @@ export default function Editor({
|
|
|
1189
1437
|
const overlayLeft = LAYOUT_PADDING + (isSidebarCollapsed ? 8 : sidebarWidth) + LAYOUT_GAP
|
|
1190
1438
|
|
|
1191
1439
|
return (
|
|
1192
|
-
<
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
<
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
)}
|
|
1440
|
+
<div className="dark flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1441
|
+
{showLoader && (
|
|
1442
|
+
<div className="fixed inset-0 z-60">
|
|
1443
|
+
<SceneLoader className="bg-background" />
|
|
1444
|
+
</div>
|
|
1445
|
+
)}
|
|
1199
1446
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1447
|
+
{!isLoading && isPreviewMode ? (
|
|
1448
|
+
<>
|
|
1449
|
+
{isFirstPersonMode ? (
|
|
1450
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1451
|
+
) : (
|
|
1202
1452
|
<ViewerOverlay onBack={() => useEditor.getState().setPreviewMode(false)} />
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1453
|
+
)}
|
|
1454
|
+
<div className="h-full w-full" data-pascal-viewer-3d>
|
|
1455
|
+
{previewViewerContent}
|
|
1456
|
+
</div>
|
|
1457
|
+
</>
|
|
1458
|
+
) : (
|
|
1459
|
+
<>
|
|
1460
|
+
{/* Sidebar */}
|
|
1461
|
+
<SidebarSlot>
|
|
1462
|
+
<AppSidebar
|
|
1463
|
+
appMenuButton={appMenuButton}
|
|
1464
|
+
commandPaletteEmptyAction={commandPaletteEmptyAction}
|
|
1465
|
+
extraPanels={extraSidebarPanels}
|
|
1466
|
+
settingsPanelProps={settingsPanelProps}
|
|
1467
|
+
sidebarTop={sidebarTop}
|
|
1468
|
+
sitePanelProps={sitePanelProps}
|
|
1469
|
+
/>
|
|
1470
|
+
</SidebarSlot>
|
|
1471
|
+
|
|
1472
|
+
{/* Viewer area */}
|
|
1473
|
+
<div className="relative flex-1 overflow-hidden rounded-xl">{viewerCanvas}</div>
|
|
1474
|
+
|
|
1475
|
+
{/* Fixed UI overlays scoped to the viewer area */}
|
|
1476
|
+
<ViewerOverlays left={overlayLeft}>
|
|
1477
|
+
<div className="pointer-events-auto">
|
|
1478
|
+
<ActionMenu />
|
|
1479
|
+
</div>
|
|
1480
|
+
<div className="pointer-events-auto">
|
|
1481
|
+
<PanelManager />
|
|
1482
|
+
</div>
|
|
1483
|
+
<div className="pointer-events-auto">
|
|
1484
|
+
<HelperManager />
|
|
1485
|
+
</div>
|
|
1486
|
+
<RiserDiagramPanel />
|
|
1235
1487
|
{isFirstPersonMode && (
|
|
1236
|
-
<
|
|
1237
|
-
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1238
|
-
</div>
|
|
1488
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1239
1489
|
)}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
</
|
|
1490
|
+
</ViewerOverlays>
|
|
1491
|
+
</>
|
|
1492
|
+
)}
|
|
1493
|
+
</div>
|
|
1244
1494
|
)
|
|
1245
1495
|
}
|