@pascal-app/editor 0.9.1 → 1.0.0-beta.1
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 +14 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +49 -0
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +332 -199
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
- package/src/components/editor/first-person/build-collider-world.ts +52 -5
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +410 -136
- package/src/components/editor/floating-action-menu.tsx +435 -64
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +72 -0
- package/src/components/editor/floorplan-background-selection.ts +7 -20
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +2517 -1492
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +597 -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 +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +49 -6
- package/src/components/editor/index.tsx +289 -35
- package/src/components/editor/measurement-pill.tsx +68 -23
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +409 -180
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +76 -0
- package/src/components/editor/quick-measurement-hud.tsx +34 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +814 -466
- package/src/components/editor/site-edge-labels.tsx +28 -6
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +118 -253
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
- package/src/components/editor/wall-measurement-label.tsx +10 -28
- package/src/components/editor/wall-move-side-handles.tsx +229 -39
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- 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-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +107 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +146 -57
- package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
- package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
- package/src/components/tools/roof/roof-tool.tsx +336 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- 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 +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- 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 +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +101 -42
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +164 -42
- package/src/components/tools/tool-manager.tsx +149 -30
- package/src/components/tools/wall/wall-drafting.test.ts +447 -0
- package/src/components/tools/wall/wall-drafting.ts +304 -108
- package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +98 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +44 -94
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +267 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +16 -3
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +93 -18
- package/src/components/ui/controls/material-picker.tsx +162 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- 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/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +74 -20
- 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 +273 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- 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 +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +36 -14
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +19 -4
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- 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/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- 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 +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
- 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 +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -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 +44 -3
- 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/tab-bar.tsx +42 -28
- 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/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +36 -596
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +438 -70
- package/src/index.tsx +355 -9
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +42 -0
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- 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 +275 -0
- package/src/lib/direct-manipulation.ts +118 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +978 -0
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +52 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +283 -3
- package/src/lib/fresh-planar-placement.ts +85 -1
- package/src/lib/glb-export.test.ts +556 -0
- package/src/lib/glb-export.ts +1071 -0
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +31 -0
- 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 +52 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +243 -0
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- 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 +188 -0
- package/src/lib/measurements.ts +210 -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/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- 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 +325 -0
- package/src/lib/scene-clipboard.ts +310 -21
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +313 -0
- package/src/lib/selection-routing.ts +180 -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 +82 -14
- package/src/lib/sfx-player.test.ts +266 -0
- package/src/lib/sfx-player.ts +262 -69
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -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 +165 -0
- package/src/lib/snapping-mode.ts +191 -0
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +58 -86
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +665 -126
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +51 -0
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +151 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +206 -0
- package/src/store/use-interaction-scope.ts +140 -0
- package/src/store/use-measurement-draft.test.ts +559 -0
- package/src/store/use-measurement-draft.ts +570 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-placement-preview.ts +11 -3
- 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 +53 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/floorplan/selection-tool.ts +0 -271
- package/src/lib/level-name.ts +0 -11
|
@@ -2,28 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
4
|
import {
|
|
5
|
+
acquireSceneReadOnlyLease,
|
|
6
|
+
getCatalogMaterialById,
|
|
7
|
+
getLibraryMaterialIdFromRef,
|
|
8
|
+
getSceneMaterialIdFromRef,
|
|
5
9
|
initSpaceDetectionSync,
|
|
6
10
|
initSpatialGridSync,
|
|
7
11
|
spatialGridManager,
|
|
8
12
|
useScene,
|
|
9
13
|
} from '@pascal-app/core'
|
|
10
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type HoverStyles,
|
|
16
|
+
InteractiveSystem,
|
|
17
|
+
SceneEnvironment,
|
|
18
|
+
useViewer,
|
|
19
|
+
Viewer,
|
|
20
|
+
} from '@pascal-app/viewer'
|
|
11
21
|
import { memo, type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
|
12
22
|
import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
13
23
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
14
24
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
15
25
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
26
|
+
import { type ActivePaintMaterial, hasActivePaintMaterial } from '../../lib/material-paint'
|
|
16
27
|
import {
|
|
17
28
|
applySceneGraphToEditor,
|
|
18
29
|
loadSceneFromLocalStorage,
|
|
19
30
|
type SceneGraph,
|
|
20
31
|
writePersistedSelection,
|
|
21
32
|
} from '../../lib/scene'
|
|
22
|
-
import { initSFXBus } from '../../lib/sfx-bus'
|
|
33
|
+
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
23
34
|
import useEditor from '../../store/use-editor'
|
|
35
|
+
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
24
36
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
25
37
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
26
38
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
39
|
+
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
27
40
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
28
41
|
import { ZoneLabelEditorSystem } from '../systems/zone/zone-label-editor-system'
|
|
29
42
|
import { ZoneSystem } from '../systems/zone/zone-system'
|
|
@@ -44,17 +57,24 @@ import type { ExtraPanel } from '../ui/sidebar/icon-rail'
|
|
|
44
57
|
import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/settings-panel'
|
|
45
58
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
46
59
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
60
|
+
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
47
61
|
import { CustomCameraControls } from './custom-camera-controls'
|
|
62
|
+
import { DeleteConfirmationDialog } from './delete-confirmation-dialog'
|
|
48
63
|
import { EditorLayoutV2 } from './editor-layout-v2'
|
|
49
64
|
import { ExportManager } from './export-manager'
|
|
65
|
+
import { FenceTangentLines3D } from './fence-tangent-lines-3d'
|
|
50
66
|
import { FirstPersonControls, FirstPersonOverlay } from './first-person-controls'
|
|
51
67
|
import { FloatingActionMenu } from './floating-action-menu'
|
|
52
68
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
69
|
+
import { FloorplanModeCoordinator } from './floorplan-mode-coordinator'
|
|
53
70
|
import { FloorplanPanel } from './floorplan-panel'
|
|
54
71
|
import { Grid } from './grid'
|
|
55
|
-
import {
|
|
72
|
+
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
56
73
|
import { GroupRotateHandle } from './group-rotate-handle'
|
|
74
|
+
import { GroupSelectionBox3D } from './group-selection-box-3d'
|
|
57
75
|
import { NodeArrowHandles } from './node-arrow-handles'
|
|
76
|
+
import { QuickMeasurementHud } from './quick-measurement-hud'
|
|
77
|
+
import { RiserDiagramPanel } from './riser-diagram-panel'
|
|
58
78
|
import { SelectionManager } from './selection-manager'
|
|
59
79
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
60
80
|
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
@@ -72,6 +92,8 @@ const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
|
72
92
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
73
93
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
74
94
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
95
|
+
const SCENE_READY_FALLBACK_MS = 8000
|
|
96
|
+
type PaintCursorBadgeState = 'empty' | 'ready' | 'blocked'
|
|
75
97
|
const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
76
98
|
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
77
99
|
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
@@ -102,6 +124,7 @@ function initializeEditorRuntime(): () => void {
|
|
|
102
124
|
unsubscribeSpaceDetection?.()
|
|
103
125
|
|
|
104
126
|
spatialGridManager.clear()
|
|
127
|
+
disposeSFXBus()
|
|
105
128
|
|
|
106
129
|
const outliner = useViewer.getState().outliner
|
|
107
130
|
outliner.selectedObjects.length = 0
|
|
@@ -121,12 +144,29 @@ export interface EditorProps {
|
|
|
121
144
|
sidebarTabs?: (SidebarTab & { component: React.ComponentType })[]
|
|
122
145
|
viewerToolbarLeft?: ReactNode
|
|
123
146
|
viewerToolbarRight?: ReactNode
|
|
147
|
+
/**
|
|
148
|
+
* Full-bleed surface swapped in over the 3D canvas (v2) — e.g. the studio
|
|
149
|
+
* gallery. The canvas stays mounted underneath (no WebGL re-init) and the
|
|
150
|
+
* viewer toolbar stays on top so the host's stage switch remains reachable.
|
|
151
|
+
*/
|
|
152
|
+
stageOverlay?: ReactNode
|
|
124
153
|
/**
|
|
125
154
|
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
126
155
|
* affordance here so it reads as part of the inspector surface and shows
|
|
127
156
|
* only while a node is selected.
|
|
128
157
|
*/
|
|
129
158
|
inspectorFooter?: ReactNode
|
|
159
|
+
/**
|
|
160
|
+
* Docked below the multi-selection panel (v2). Hosts mount whole-selection
|
|
161
|
+
* affordances here (e.g. "Save to my catalog"); shows only while more than
|
|
162
|
+
* one node is selected.
|
|
163
|
+
*/
|
|
164
|
+
multiSelectionFooter?: ReactNode
|
|
165
|
+
|
|
166
|
+
/** Host-owned content mounted inside the editor's React Three Fiber scene. */
|
|
167
|
+
viewerSceneSlot?: ReactNode
|
|
168
|
+
/** Host-owned SVG content mounted in the transformed floor-plan scene. */
|
|
169
|
+
floorplanSceneSlot?: ReactNode
|
|
130
170
|
|
|
131
171
|
projectId?: string | null
|
|
132
172
|
|
|
@@ -143,6 +183,10 @@ export interface EditorProps {
|
|
|
143
183
|
// Loading indicator (e.g. project fetching in community mode)
|
|
144
184
|
isLoading?: boolean
|
|
145
185
|
|
|
186
|
+
// Fires when the full-screen scene loader shows/hides — lets hosts measure
|
|
187
|
+
// open-to-interactive time without reaching into internal loader state.
|
|
188
|
+
onLoaderChange?: (visible: boolean) => void
|
|
189
|
+
|
|
146
190
|
// Thumbnail
|
|
147
191
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
148
192
|
|
|
@@ -526,14 +570,70 @@ function DeleteCursorBadge({ position }: { position: { x: number; y: number } })
|
|
|
526
570
|
)
|
|
527
571
|
}
|
|
528
572
|
|
|
573
|
+
function getActivePaintMaterialSwatchColor(
|
|
574
|
+
material: ActivePaintMaterial | null,
|
|
575
|
+
sceneMaterials: ReturnType<typeof useScene.getState>['materials'],
|
|
576
|
+
) {
|
|
577
|
+
const directColor = material?.material?.properties?.color
|
|
578
|
+
if (directColor) return directColor
|
|
579
|
+
|
|
580
|
+
const sceneMaterialId = getSceneMaterialIdFromRef(material?.materialPreset)
|
|
581
|
+
if (sceneMaterialId) {
|
|
582
|
+
const sceneMaterial = sceneMaterials[sceneMaterialId as keyof typeof sceneMaterials]
|
|
583
|
+
const sceneColor = sceneMaterial?.material.properties?.color
|
|
584
|
+
if (sceneColor) return sceneColor
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const catalogId =
|
|
588
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
589
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
590
|
+
return (
|
|
591
|
+
catalogMaterial?.previewColor ??
|
|
592
|
+
catalogMaterial?.preset.mapProperties.color ??
|
|
593
|
+
PAINT_CURSOR_BADGE_COLOR
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function getActivePaintMaterialSwatchImageUrl(
|
|
598
|
+
material: ActivePaintMaterial | null,
|
|
599
|
+
sceneMaterials: ReturnType<typeof useScene.getState>['materials'],
|
|
600
|
+
) {
|
|
601
|
+
const directTextureUrl = material?.material?.texture?.url
|
|
602
|
+
if (directTextureUrl) return directTextureUrl
|
|
603
|
+
|
|
604
|
+
const sceneMaterialId = getSceneMaterialIdFromRef(material?.materialPreset)
|
|
605
|
+
if (sceneMaterialId) {
|
|
606
|
+
const sceneMaterial = sceneMaterials[sceneMaterialId as keyof typeof sceneMaterials]
|
|
607
|
+
const sceneTextureUrl = sceneMaterial?.material.texture?.url
|
|
608
|
+
if (sceneTextureUrl) return sceneTextureUrl
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const catalogId =
|
|
612
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
613
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
614
|
+
return catalogMaterial?.previewThumbnailUrl ?? catalogMaterial?.preset.maps.albedoMap
|
|
615
|
+
}
|
|
616
|
+
|
|
529
617
|
function PaintCursorBadge({
|
|
530
618
|
position,
|
|
531
|
-
|
|
619
|
+
state,
|
|
620
|
+
swatchColor,
|
|
621
|
+
swatchImageUrl,
|
|
622
|
+
isEraser,
|
|
532
623
|
}: {
|
|
533
624
|
position: { x: number; y: number }
|
|
534
|
-
|
|
625
|
+
state: PaintCursorBadgeState
|
|
626
|
+
swatchColor: string
|
|
627
|
+
swatchImageUrl?: string
|
|
628
|
+
isEraser: boolean
|
|
535
629
|
}) {
|
|
536
|
-
const accentColor =
|
|
630
|
+
const accentColor =
|
|
631
|
+
state === 'ready'
|
|
632
|
+
? isEraser
|
|
633
|
+
? PAINT_CURSOR_BADGE_COLOR
|
|
634
|
+
: swatchColor
|
|
635
|
+
: PAINT_CURSOR_BADGE_DISABLED_COLOR
|
|
636
|
+
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' ? 0.62 : 0.42
|
|
537
637
|
const lineHeight = 18
|
|
538
638
|
|
|
539
639
|
return (
|
|
@@ -566,8 +666,49 @@ function PaintCursorBadge({
|
|
|
566
666
|
alt=""
|
|
567
667
|
aria-hidden="true"
|
|
568
668
|
className="h-5 w-5 object-contain drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
569
|
-
src="/icons/paint.
|
|
669
|
+
src="/icons/paint.webp"
|
|
670
|
+
style={{
|
|
671
|
+
filter: state === 'ready' ? undefined : 'grayscale(1)',
|
|
672
|
+
opacity: iconOpacity,
|
|
673
|
+
}}
|
|
570
674
|
/>
|
|
675
|
+
{state === 'ready' ? (
|
|
676
|
+
isEraser ? (
|
|
677
|
+
<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)]">
|
|
678
|
+
<Icon
|
|
679
|
+
aria-hidden="true"
|
|
680
|
+
color="currentColor"
|
|
681
|
+
height={10}
|
|
682
|
+
icon="mdi:eraser-variant"
|
|
683
|
+
width={10}
|
|
684
|
+
/>
|
|
685
|
+
</span>
|
|
686
|
+
) : (
|
|
687
|
+
<span
|
|
688
|
+
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)]"
|
|
689
|
+
style={{
|
|
690
|
+
backgroundColor: swatchColor,
|
|
691
|
+
backgroundImage: swatchImageUrl
|
|
692
|
+
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
693
|
+
: undefined,
|
|
694
|
+
}}
|
|
695
|
+
/>
|
|
696
|
+
)
|
|
697
|
+
) : state === 'blocked' ? (
|
|
698
|
+
<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)]">
|
|
699
|
+
<Icon
|
|
700
|
+
aria-hidden="true"
|
|
701
|
+
color="currentColor"
|
|
702
|
+
height={12}
|
|
703
|
+
icon="mdi:cancel"
|
|
704
|
+
width={12}
|
|
705
|
+
/>
|
|
706
|
+
</span>
|
|
707
|
+
) : (
|
|
708
|
+
<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)]">
|
|
709
|
+
?
|
|
710
|
+
</span>
|
|
711
|
+
)}
|
|
571
712
|
</div>
|
|
572
713
|
</div>
|
|
573
714
|
)
|
|
@@ -589,33 +730,43 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
589
730
|
isFirstPersonMode,
|
|
590
731
|
isStudioMode,
|
|
591
732
|
onThumbnailCapture,
|
|
733
|
+
viewerSceneSlot,
|
|
592
734
|
}: {
|
|
593
735
|
isVersionPreviewMode: boolean
|
|
594
736
|
isLoading: boolean
|
|
595
737
|
isFirstPersonMode: boolean
|
|
596
738
|
isStudioMode: boolean
|
|
597
739
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
740
|
+
viewerSceneSlot?: ReactNode
|
|
598
741
|
}) {
|
|
599
742
|
// Studio mode is a clean render/snapshot surface — no selection or editing
|
|
600
743
|
// affordances. It mirrors version-preview's chrome gating on the canvas.
|
|
601
|
-
|
|
744
|
+
// Capture (snapshot) mode is camera-only for the same reason: suppress
|
|
745
|
+
// selection, editing handles, and the tool manager (which mounts the site
|
|
746
|
+
// boundary flags) so the framed shot stays clean.
|
|
747
|
+
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
748
|
+
const noEditing = isVersionPreviewMode || isFirstPersonMode || isStudioMode || isCaptureMode
|
|
602
749
|
return (
|
|
603
750
|
<>
|
|
604
|
-
|
|
751
|
+
<SceneEnvironment />
|
|
752
|
+
{!(isFirstPersonMode || isStudioMode || isCaptureMode) && <SelectionManager />}
|
|
605
753
|
{!noEditing && <BoxSelectTool />}
|
|
606
754
|
{!noEditing && <NodeArrowHandles />}
|
|
607
755
|
{!noEditing && <GroupRotateHandle />}
|
|
608
|
-
{!noEditing && <
|
|
756
|
+
{!noEditing && <GroupSelectionBox3D />}
|
|
609
757
|
{!noEditing && <WallOpeningHighlights />}
|
|
610
758
|
{!noEditing && <SlabHoleHighlights />}
|
|
611
759
|
{!noEditing && <WallMoveSideHandles />}
|
|
760
|
+
{!noEditing && <FenceTangentLines3D />}
|
|
612
761
|
{!noEditing && <FloatingActionMenu />}
|
|
762
|
+
{!noEditing && <GroupFloatingActionMenu />}
|
|
613
763
|
{!noEditing && <FloatingBuildingActionMenu />}
|
|
614
764
|
{!isFirstPersonMode && <WallMeasurementLabel />}
|
|
615
765
|
<ExportManager />
|
|
616
766
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
617
767
|
<CeilingSystem />
|
|
618
768
|
<CeilingSelectionAffordanceSystem />
|
|
769
|
+
{!noEditing && <SelectionAffordanceManager />}
|
|
619
770
|
<RoofEditSystem />
|
|
620
771
|
<StairEditSystem />
|
|
621
772
|
{!(isLoading || isFirstPersonMode) && <SnapAwareGrid />}
|
|
@@ -625,6 +776,7 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
625
776
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
626
777
|
{!isFirstPersonMode && <SiteEdgeLabels />}
|
|
627
778
|
<InteractiveSystem />
|
|
779
|
+
{!noEditing && viewerSceneSlot}
|
|
628
780
|
</>
|
|
629
781
|
)
|
|
630
782
|
})
|
|
@@ -715,6 +867,9 @@ function PaintCursorLayer({
|
|
|
715
867
|
}) {
|
|
716
868
|
const mode = useEditor((s) => s.mode)
|
|
717
869
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
870
|
+
const paintEraser = useEditor((s) => s.paintEraser)
|
|
871
|
+
const paintHover = useEditor((s) => s.paintHover)
|
|
872
|
+
const sceneMaterials = useScene((s) => s.materials)
|
|
718
873
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
719
874
|
const active = mode === 'material-paint' && !isVersionPreviewMode
|
|
720
875
|
|
|
@@ -764,11 +919,14 @@ function PaintCursorLayer({
|
|
|
764
919
|
}
|
|
765
920
|
}, [active, containerRef])
|
|
766
921
|
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
922
|
+
const hasPaint = paintEraser || hasActivePaintMaterial(activePaintMaterial)
|
|
923
|
+
const badgeState: PaintCursorBadgeState = !hasPaint
|
|
924
|
+
? 'empty'
|
|
925
|
+
: paintHover != null
|
|
926
|
+
? 'ready'
|
|
927
|
+
: 'blocked'
|
|
928
|
+
const swatchColor = getActivePaintMaterialSwatchColor(activePaintMaterial, sceneMaterials)
|
|
929
|
+
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(activePaintMaterial, sceneMaterials)
|
|
772
930
|
|
|
773
931
|
if (!active || !position) return null
|
|
774
932
|
|
|
@@ -777,7 +935,13 @@ function PaintCursorLayer({
|
|
|
777
935
|
className="pointer-events-none absolute z-40"
|
|
778
936
|
style={{ left: 0, top: 0, transform: `translate(${position.x}px, ${position.y}px)` }}
|
|
779
937
|
>
|
|
780
|
-
<PaintCursorBadge
|
|
938
|
+
<PaintCursorBadge
|
|
939
|
+
isEraser={paintEraser}
|
|
940
|
+
position={{ x: 0, y: 0 }}
|
|
941
|
+
state={badgeState}
|
|
942
|
+
swatchColor={swatchColor}
|
|
943
|
+
swatchImageUrl={swatchImageUrl}
|
|
944
|
+
/>
|
|
781
945
|
</div>
|
|
782
946
|
)
|
|
783
947
|
}
|
|
@@ -795,6 +959,8 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
795
959
|
sceneReadyKey,
|
|
796
960
|
onSceneReadyChange,
|
|
797
961
|
onThumbnailCapture,
|
|
962
|
+
viewerSceneSlot,
|
|
963
|
+
floorplanSceneSlot,
|
|
798
964
|
}: {
|
|
799
965
|
isVersionPreviewMode: boolean
|
|
800
966
|
isLoading: boolean
|
|
@@ -805,6 +971,8 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
805
971
|
sceneReadyKey: number
|
|
806
972
|
onSceneReadyChange: (ready: boolean) => void
|
|
807
973
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
974
|
+
viewerSceneSlot?: ReactNode
|
|
975
|
+
floorplanSceneSlot?: ReactNode
|
|
808
976
|
}) {
|
|
809
977
|
const viewMode = useEditor((s) => s.viewMode)
|
|
810
978
|
const floorplanPaneRatio = useEditor((s) => s.floorplanPaneRatio)
|
|
@@ -816,6 +984,13 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
816
984
|
)
|
|
817
985
|
|
|
818
986
|
const viewerAreaRef = useRef<HTMLDivElement>(null)
|
|
987
|
+
// State mirror of `viewerAreaRef` so the floorplan compass portal re-renders
|
|
988
|
+
// once the container exists (a plain ref mutation wouldn't trigger it).
|
|
989
|
+
const [viewerAreaEl, setViewerAreaEl] = useState<HTMLDivElement | null>(null)
|
|
990
|
+
const setViewerAreaNode = useCallback((el: HTMLDivElement | null) => {
|
|
991
|
+
viewerAreaRef.current = el
|
|
992
|
+
setViewerAreaEl(el)
|
|
993
|
+
}, [])
|
|
819
994
|
const viewer3dRef = useRef<HTMLDivElement>(null)
|
|
820
995
|
const isResizingFloorplan = useRef(false)
|
|
821
996
|
|
|
@@ -861,7 +1036,11 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
861
1036
|
|
|
862
1037
|
return (
|
|
863
1038
|
<ErrorBoundary fallback={<EditorSceneCrashFallback />}>
|
|
864
|
-
|
|
1039
|
+
{/* `relative` so the floorplan compass (portaled here to stay visible in
|
|
1040
|
+
2d / 3d / split alike) can anchor to this container's bottom-left. */}
|
|
1041
|
+
<div className="relative flex h-full" ref={setViewerAreaNode}>
|
|
1042
|
+
<QuickMeasurementHud />
|
|
1043
|
+
<DeleteConfirmationDialog />
|
|
865
1044
|
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
866
1045
|
<div
|
|
867
1046
|
className="relative h-full flex-shrink-0"
|
|
@@ -871,7 +1050,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
871
1050
|
}}
|
|
872
1051
|
>
|
|
873
1052
|
<div className="h-full w-full overflow-hidden">
|
|
874
|
-
<FloorplanPanel />
|
|
1053
|
+
<FloorplanPanel compassHost={viewerAreaEl} floorplanSceneSlot={floorplanSceneSlot} />
|
|
875
1054
|
</div>
|
|
876
1055
|
{viewMode === 'split' && (
|
|
877
1056
|
<div
|
|
@@ -886,6 +1065,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
886
1065
|
{/* 3D viewer — always mounted, hidden via CSS to avoid destroying the WebGL context */}
|
|
887
1066
|
<div
|
|
888
1067
|
className="relative min-w-0 flex-1 overflow-hidden"
|
|
1068
|
+
data-pascal-viewer-3d
|
|
889
1069
|
ref={viewer3dRef}
|
|
890
1070
|
style={{ display: show3d ? undefined : 'none' }}
|
|
891
1071
|
>
|
|
@@ -918,6 +1098,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
918
1098
|
isStudioMode={isStudioMode}
|
|
919
1099
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
920
1100
|
onThumbnailCapture={onThumbnailCapture}
|
|
1101
|
+
viewerSceneSlot={viewerSceneSlot}
|
|
921
1102
|
/>
|
|
922
1103
|
</Viewer>
|
|
923
1104
|
</div>
|
|
@@ -935,7 +1116,11 @@ export default function Editor({
|
|
|
935
1116
|
sidebarTabs,
|
|
936
1117
|
viewerToolbarLeft,
|
|
937
1118
|
viewerToolbarRight,
|
|
1119
|
+
stageOverlay,
|
|
938
1120
|
inspectorFooter,
|
|
1121
|
+
multiSelectionFooter,
|
|
1122
|
+
viewerSceneSlot,
|
|
1123
|
+
floorplanSceneSlot,
|
|
939
1124
|
projectId,
|
|
940
1125
|
onLoad,
|
|
941
1126
|
onSave,
|
|
@@ -944,6 +1129,7 @@ export default function Editor({
|
|
|
944
1129
|
previewScene,
|
|
945
1130
|
isVersionPreviewMode = false,
|
|
946
1131
|
isLoading = false,
|
|
1132
|
+
onLoaderChange,
|
|
947
1133
|
onThumbnailCapture,
|
|
948
1134
|
sidebarOverlay,
|
|
949
1135
|
viewerBanner,
|
|
@@ -974,16 +1160,29 @@ export default function Editor({
|
|
|
974
1160
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
|
975
1161
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
976
1162
|
|
|
1163
|
+
// Host-registered rail panels. Called unconditionally so
|
|
1164
|
+
// hook order is stable across the v1 / v2 layout branches below; the v1
|
|
1165
|
+
// AppSidebar path merges its own copy internally, the v2 path merges these
|
|
1166
|
+
// into its tab bar.
|
|
1167
|
+
const hostRailPanels = useHostPanels()
|
|
1168
|
+
|
|
977
1169
|
useEffect(() => {
|
|
978
1170
|
const teardown = initializeEditorRuntime()
|
|
979
1171
|
return teardown
|
|
980
1172
|
}, [])
|
|
981
1173
|
|
|
1174
|
+
useEffect(() => {
|
|
1175
|
+
void useEditor.persist.rehydrate()
|
|
1176
|
+
void useSidebarStore.persist.rehydrate()
|
|
1177
|
+
}, [])
|
|
1178
|
+
|
|
982
1179
|
useEffect(() => {
|
|
983
1180
|
useViewer.getState().setProjectId(projectId ?? null)
|
|
1181
|
+
useFloorplanMode.getState().setProjectId(projectId ?? null)
|
|
984
1182
|
|
|
985
1183
|
return () => {
|
|
986
1184
|
useViewer.getState().setProjectId(null)
|
|
1185
|
+
useFloorplanMode.getState().setProjectId(null)
|
|
987
1186
|
}
|
|
988
1187
|
}, [projectId])
|
|
989
1188
|
|
|
@@ -1039,13 +1238,10 @@ export default function Editor({
|
|
|
1039
1238
|
|
|
1040
1239
|
// Lock scene graph and reset to select mode when entering version preview
|
|
1041
1240
|
useEffect(() => {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
return () => {
|
|
1047
|
-
useScene.getState().setReadOnly(false)
|
|
1048
|
-
}
|
|
1241
|
+
if (!isVersionPreviewMode) return
|
|
1242
|
+
const releaseReadOnly = acquireSceneReadOnlyLease()
|
|
1243
|
+
useEditor.getState().setMode('select')
|
|
1244
|
+
return releaseReadOnly
|
|
1049
1245
|
}, [isVersionPreviewMode])
|
|
1050
1246
|
|
|
1051
1247
|
useEffect(() => {
|
|
@@ -1059,8 +1255,25 @@ export default function Editor({
|
|
|
1059
1255
|
setIsViewerSceneReady(ready)
|
|
1060
1256
|
}, [])
|
|
1061
1257
|
|
|
1258
|
+
useEffect(() => {
|
|
1259
|
+
if (isLoading || isSceneLoading || !hasLoadedInitialScene || isViewerSceneReady) return
|
|
1260
|
+
|
|
1261
|
+
const timer = window.setTimeout(() => {
|
|
1262
|
+
console.warn('[editor] viewer scene readiness timed out; showing editor shell anyway', {
|
|
1263
|
+
sceneReadyKey,
|
|
1264
|
+
})
|
|
1265
|
+
setIsViewerSceneReady(true)
|
|
1266
|
+
}, SCENE_READY_FALLBACK_MS)
|
|
1267
|
+
|
|
1268
|
+
return () => window.clearTimeout(timer)
|
|
1269
|
+
}, [hasLoadedInitialScene, isLoading, isSceneLoading, isViewerSceneReady, sceneReadyKey])
|
|
1270
|
+
|
|
1062
1271
|
const showLoader = isLoading || isSceneLoading || !hasLoadedInitialScene || !isViewerSceneReady
|
|
1063
1272
|
|
|
1273
|
+
useEffect(() => {
|
|
1274
|
+
onLoaderChange?.(showLoader)
|
|
1275
|
+
}, [showLoader, onLoaderChange])
|
|
1276
|
+
|
|
1064
1277
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1065
1278
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
1066
1279
|
|
|
@@ -1109,6 +1322,7 @@ export default function Editor({
|
|
|
1109
1322
|
<CeilingSystem />
|
|
1110
1323
|
<RoofEditSystem />
|
|
1111
1324
|
<StairEditSystem />
|
|
1325
|
+
{isFirstPersonMode && <FirstPersonControls />}
|
|
1112
1326
|
<CustomCameraControls />
|
|
1113
1327
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1114
1328
|
<InteractiveSystem />
|
|
@@ -1126,12 +1340,23 @@ export default function Editor({
|
|
|
1126
1340
|
onThumbnailCapture={onThumbnailCapture}
|
|
1127
1341
|
sceneReadyKey={sceneReadyKey}
|
|
1128
1342
|
showLoader={showLoader}
|
|
1343
|
+
viewerSceneSlot={viewerSceneSlot}
|
|
1344
|
+
floorplanSceneSlot={floorplanSceneSlot}
|
|
1129
1345
|
/>
|
|
1130
1346
|
)
|
|
1131
1347
|
|
|
1132
1348
|
// ── V2 layout ──
|
|
1133
1349
|
if (layoutVersion === 'v2') {
|
|
1134
|
-
|
|
1350
|
+
// Registered host panels join the host's `sidebarTabs` as first-class tabs.
|
|
1351
|
+
// Explicit tabs keep precedence because they are already in the map first.
|
|
1352
|
+
const tabMap = new Map<string, SidebarTab & { component: React.ComponentType }>(
|
|
1353
|
+
sidebarTabs?.map((t) => [t.id, t]) ?? [],
|
|
1354
|
+
)
|
|
1355
|
+
for (const p of hostRailPanels) {
|
|
1356
|
+
if (!tabMap.has(p.id)) {
|
|
1357
|
+
tabMap.set(p.id, { id: p.id, label: p.label, icon: p.icon, component: p.component })
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1135
1360
|
|
|
1136
1361
|
const renderTabContent = (tabId: string) => {
|
|
1137
1362
|
// Built-in panels
|
|
@@ -1148,17 +1373,28 @@ export default function Editor({
|
|
|
1148
1373
|
return <Component />
|
|
1149
1374
|
}
|
|
1150
1375
|
|
|
1151
|
-
const tabBarTabs =
|
|
1152
|
-
sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon }) => ({
|
|
1376
|
+
const tabBarTabs = [
|
|
1377
|
+
...(sidebarTabs?.map(({ id, label, mobileDefaultSnap, mobileIcon, icon }) => ({
|
|
1153
1378
|
id,
|
|
1154
1379
|
label,
|
|
1155
1380
|
mobileDefaultSnap,
|
|
1156
1381
|
mobileIcon,
|
|
1157
1382
|
icon,
|
|
1158
|
-
})) ?? []
|
|
1383
|
+
})) ?? []),
|
|
1384
|
+
// Host panels appear after the explicit tabs in the rail. The icon
|
|
1385
|
+
// doubles as the mobile icon; a half-height sheet is a sensible default.
|
|
1386
|
+
...hostRailPanels.map((p) => ({
|
|
1387
|
+
id: p.id,
|
|
1388
|
+
label: p.label,
|
|
1389
|
+
mobileDefaultSnap: 0.5,
|
|
1390
|
+
mobileIcon: p.icon,
|
|
1391
|
+
icon: p.icon,
|
|
1392
|
+
})),
|
|
1393
|
+
]
|
|
1159
1394
|
|
|
1160
1395
|
return (
|
|
1161
1396
|
<>
|
|
1397
|
+
<FloorplanModeCoordinator />
|
|
1162
1398
|
{showLoader && (
|
|
1163
1399
|
<div className="fixed inset-0 z-60">
|
|
1164
1400
|
<SceneLoader className="bg-background" />
|
|
@@ -1167,8 +1403,14 @@ export default function Editor({
|
|
|
1167
1403
|
|
|
1168
1404
|
{!isLoading && isPreviewMode ? (
|
|
1169
1405
|
<div className="dark flex h-full w-full flex-col bg-neutral-100 text-foreground">
|
|
1170
|
-
|
|
1171
|
-
|
|
1406
|
+
{isFirstPersonMode ? (
|
|
1407
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1408
|
+
) : (
|
|
1409
|
+
<ViewerOverlay onBack={() => useEditor.getState().setPreviewMode(false)} />
|
|
1410
|
+
)}
|
|
1411
|
+
<div className="h-full w-full" data-pascal-viewer-3d>
|
|
1412
|
+
{previewViewerContent}
|
|
1413
|
+
</div>
|
|
1172
1414
|
</div>
|
|
1173
1415
|
) : (
|
|
1174
1416
|
<>
|
|
@@ -1176,7 +1418,7 @@ export default function Editor({
|
|
|
1176
1418
|
navbarSlot={navbarSlot}
|
|
1177
1419
|
overlays={
|
|
1178
1420
|
<>
|
|
1179
|
-
{!isCaptureMode && <FloatingLevelSelector />}
|
|
1421
|
+
{!(isCaptureMode || stageOverlay) && <FloatingLevelSelector />}
|
|
1180
1422
|
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1181
1423
|
<div className="pointer-events-auto">
|
|
1182
1424
|
<ActionMenu />
|
|
@@ -1184,7 +1426,10 @@ export default function Editor({
|
|
|
1184
1426
|
)}
|
|
1185
1427
|
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) && (
|
|
1186
1428
|
<div className="pointer-events-auto">
|
|
1187
|
-
<PanelManager
|
|
1429
|
+
<PanelManager
|
|
1430
|
+
inspectorFooter={inspectorFooter}
|
|
1431
|
+
multiSelectionFooter={multiSelectionFooter}
|
|
1432
|
+
/>
|
|
1188
1433
|
</div>
|
|
1189
1434
|
)}
|
|
1190
1435
|
{!isCaptureMode && (
|
|
@@ -1204,6 +1449,7 @@ export default function Editor({
|
|
|
1204
1449
|
renderTabContent={renderTabContent}
|
|
1205
1450
|
sidebarOverlay={sidebarOverlay}
|
|
1206
1451
|
sidebarTabs={tabBarTabs}
|
|
1452
|
+
stageOverlay={stageOverlay}
|
|
1207
1453
|
viewerContent={viewerCanvas}
|
|
1208
1454
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1209
1455
|
viewerToolbarRight={viewerToolbarRight}
|
|
@@ -1224,6 +1470,7 @@ export default function Editor({
|
|
|
1224
1470
|
|
|
1225
1471
|
return (
|
|
1226
1472
|
<div className="dark flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1473
|
+
<FloorplanModeCoordinator />
|
|
1227
1474
|
{showLoader && (
|
|
1228
1475
|
<div className="fixed inset-0 z-60">
|
|
1229
1476
|
<SceneLoader className="bg-background" />
|
|
@@ -1232,8 +1479,14 @@ export default function Editor({
|
|
|
1232
1479
|
|
|
1233
1480
|
{!isLoading && isPreviewMode ? (
|
|
1234
1481
|
<>
|
|
1235
|
-
|
|
1236
|
-
|
|
1482
|
+
{isFirstPersonMode ? (
|
|
1483
|
+
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1484
|
+
) : (
|
|
1485
|
+
<ViewerOverlay onBack={() => useEditor.getState().setPreviewMode(false)} />
|
|
1486
|
+
)}
|
|
1487
|
+
<div className="h-full w-full" data-pascal-viewer-3d>
|
|
1488
|
+
{previewViewerContent}
|
|
1489
|
+
</div>
|
|
1237
1490
|
</>
|
|
1238
1491
|
) : (
|
|
1239
1492
|
<>
|
|
@@ -1263,6 +1516,7 @@ export default function Editor({
|
|
|
1263
1516
|
<div className="pointer-events-auto">
|
|
1264
1517
|
<HelperManager />
|
|
1265
1518
|
</div>
|
|
1519
|
+
<RiserDiagramPanel />
|
|
1266
1520
|
{isFirstPersonMode && (
|
|
1267
1521
|
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
1268
1522
|
)}
|