@pascal-app/editor 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -0
- package/package.json +42 -38
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +307 -5
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
- package/src/components/editor/construction-reveal-audio.tsx +40 -0
- package/src/components/editor/custom-camera-controls.tsx +275 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +37 -26
- package/src/components/editor/editor-layout-v2.tsx +132 -59
- package/src/components/editor/export-manager.tsx +47 -26
- package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
- package/src/components/editor/first-person/build-collider-world.ts +28 -7
- package/src/components/editor/first-person/ground-holes.test.ts +128 -0
- package/src/components/editor/first-person/ground-holes.ts +130 -0
- package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
- package/src/components/editor/first-person/mechanism-targets.ts +88 -0
- package/src/components/editor/first-person-controls.tsx +220 -9
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +414 -363
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +56 -2
- package/src/components/editor/group-move-3d.ts +2 -0
- package/src/components/editor/group-rotate-handle.tsx +2 -0
- package/src/components/editor/group-transform-shared.test.ts +460 -0
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +409 -197
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +233 -0
- package/src/components/editor/room-labels.test.ts +850 -0
- package/src/components/editor/room-labels.tsx +1890 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +474 -419
- package/src/components/editor/slab-hole-highlights.tsx +5 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
- package/src/components/editor/snapshot-capture.test.ts +119 -60
- package/src/components/editor/snapshot-capture.ts +36 -5
- package/src/components/editor/thumbnail-generator.test.ts +654 -0
- package/src/components/editor/thumbnail-generator.tsx +1103 -223
- package/src/components/editor/use-floorplan-background-placement.ts +34 -12
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +663 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +343 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
- package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
- package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/point-ask/bubble.tsx +538 -0
- package/src/components/point-ask/hover-chip.tsx +91 -0
- package/src/components/point-ask/mode-pill.tsx +92 -0
- package/src/components/point-ask/optional-motion.ts +22 -0
- package/src/components/point-ask/outline-corners.tsx +93 -0
- package/src/components/point-ask/pins.tsx +711 -0
- package/src/components/point-ask/point-ask-layer.tsx +83 -0
- package/src/components/point-ask/point-ask-styles.ts +105 -0
- package/src/components/point-ask/region-marquee-styles.ts +15 -0
- package/src/components/point-ask/region-marquee.tsx +64 -0
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
- package/src/components/tools/distribution/connection-compatibility.ts +43 -0
- package/src/components/tools/distribution/distribution-public.ts +9 -0
- package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
- package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
- package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
- package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
- package/src/components/tools/distribution/draw-alignment.ts +35 -0
- package/src/components/tools/distribution/ports.test.ts +56 -0
- package/src/components/tools/distribution/ports.ts +378 -0
- package/src/components/tools/distribution/run-cursor.test.ts +135 -0
- package/src/components/tools/distribution/run-cursor.ts +36 -0
- package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
- package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
- package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
- package/src/components/tools/distribution/run-port-snap.ts +25 -0
- package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
- package/src/components/tools/distribution/run-screen-direction.ts +30 -0
- package/src/components/tools/elevator/elevator-tool.tsx +3 -6
- package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
- package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
- package/src/components/tools/item/placement-history.test.tsx +512 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +11 -10
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/item-surface-move.test.ts +62 -63
- package/src/components/tools/registry/item-surface-move.ts +2 -0
- package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
- package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/tool-manager.tsx +59 -24
- package/src/components/tools/wall/wall-drafting.test.ts +175 -47
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +9 -1
- package/src/components/ui/action-menu/camera-actions.tsx +9 -4
- package/src/components/ui/action-menu/control-modes.tsx +15 -42
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
- package/src/components/ui/action-menu/index.tsx +56 -33
- package/src/components/ui/action-menu/measurement-control.tsx +14 -19
- package/src/components/ui/action-menu/placement.ts +20 -0
- package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
- package/src/components/ui/action-menu/view-toggles.tsx +47 -42
- package/src/components/ui/build-panel/build-panel.tsx +242 -0
- package/src/components/ui/command-palette/editor-commands.tsx +7 -42
- package/src/components/ui/command-palette/index.tsx +50 -39
- package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
- package/src/components/ui/controls/action-button.tsx +5 -2
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/metric-control.tsx +1 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/controls/slider-control.tsx +1 -1
- package/src/components/ui/controls/toggle-control.tsx +3 -3
- package/src/components/ui/controls/tool-options-panel.tsx +1 -1
- package/src/components/ui/floating-level-selector.tsx +159 -59
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
- package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
- package/src/components/ui/helpers/helper-manager.tsx +331 -14
- package/src/components/ui/helpers/item-helper.tsx +17 -1
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +130 -62
- package/src/components/ui/panels/parametric-field-control.tsx +3 -3
- package/src/components/ui/panels/parametric-inspector.tsx +7 -7
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +790 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +3 -1
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +164 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +8 -3
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
- package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
- package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
- package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
- package/src/components/ui/sidebar/tab-bar.tsx +14 -4
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +191 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +215 -43
- package/src/hooks/use-auto-save.ts +60 -9
- package/src/hooks/use-ceiling-events.test.ts +123 -118
- package/src/hooks/use-grid-events.test.ts +105 -105
- package/src/hooks/use-keyboard.test.ts +243 -19
- package/src/hooks/use-keyboard.ts +118 -83
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +71 -0
- package/src/hooks/use-placement-notice.ts +73 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +312 -58
- package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
- package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
- package/src/lib/add-storey.test.ts +177 -0
- package/src/lib/add-storey.ts +59 -0
- package/src/lib/below-grade.test.tsx +171 -0
- package/src/lib/below-grade.ts +133 -0
- package/src/lib/camera-facing.test.ts +24 -0
- package/src/lib/camera-facing.ts +24 -0
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/direct-manipulation.ts +2 -0
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +55 -0
- package/src/lib/editor-views.ts +178 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +284 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +124 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +230 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
- package/src/lib/floorplan/site-plan/geometry.ts +142 -0
- package/src/lib/floorplan/site-plan/index.ts +55 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
- package/src/lib/follow-pascal-exterior.test.ts +171 -0
- package/src/lib/follow-pascal-exterior.ts +39 -0
- package/src/lib/follow-pascal-presentation.test.ts +203 -0
- package/src/lib/follow-pascal-presentation.ts +129 -0
- package/src/lib/follow-pascal-sight.ts +84 -0
- package/src/lib/follow-pascal-targets.test.ts +406 -0
- package/src/lib/follow-pascal-targets.ts +198 -0
- package/src/lib/follow-pascal.test.ts +350 -0
- package/src/lib/follow-pascal.ts +284 -0
- package/src/lib/fresh-planar-placement.test.ts +148 -0
- package/src/lib/fresh-planar-placement.ts +114 -37
- package/src/lib/geometry-script/author.ts +157 -0
- package/src/lib/geometry-script/client.ts +159 -0
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +13 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +636 -126
- package/src/lib/glb-export.ts +241 -120
- package/src/lib/history.test.ts +18 -7
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +22 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +187 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +392 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +201 -0
- package/src/lib/orbit-floor-clearance.ts +154 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +122 -0
- package/src/lib/paint-pass-through.ts +78 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +267 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +65 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
- package/src/lib/point-ask/__fixtures__/house.ts +81 -0
- package/src/lib/point-ask/ask-created.test.ts +195 -0
- package/src/lib/point-ask/ask-flow.test.ts +380 -0
- package/src/lib/point-ask/build-context.test.ts +173 -0
- package/src/lib/point-ask/build-context.ts +94 -0
- package/src/lib/point-ask/capture-crop.ts +129 -0
- package/src/lib/point-ask/choreography.ts +69 -0
- package/src/lib/point-ask/compare.test.ts +145 -0
- package/src/lib/point-ask/compare.ts +73 -0
- package/src/lib/point-ask/describe-target.test.ts +165 -0
- package/src/lib/point-ask/describe-target.ts +234 -0
- package/src/lib/point-ask/dictation.ts +79 -0
- package/src/lib/point-ask/edge-marker.test.ts +61 -0
- package/src/lib/point-ask/edge-marker.ts +27 -0
- package/src/lib/point-ask/frame-loop.ts +25 -0
- package/src/lib/point-ask/history-marks.test.ts +94 -0
- package/src/lib/point-ask/history-marks.ts +39 -0
- package/src/lib/point-ask/host-api.ts +32 -0
- package/src/lib/point-ask/outline-box.test.ts +30 -0
- package/src/lib/point-ask/outline-box.ts +11 -0
- package/src/lib/point-ask/overlay-metrics.ts +6 -0
- package/src/lib/point-ask/pick.test.ts +220 -0
- package/src/lib/point-ask/pick.ts +121 -0
- package/src/lib/point-ask/pin-machine.test.ts +494 -0
- package/src/lib/point-ask/pin-machine.ts +217 -0
- package/src/lib/point-ask/placement.test.ts +203 -0
- package/src/lib/point-ask/placement.ts +141 -0
- package/src/lib/point-ask/pointer-tracker.ts +34 -0
- package/src/lib/point-ask/projector.ts +83 -0
- package/src/lib/point-ask/region.test.ts +157 -0
- package/src/lib/point-ask/region.ts +136 -0
- package/src/lib/point-ask/retry.test.ts +217 -0
- package/src/lib/point-ask/retry.ts +37 -0
- package/src/lib/point-ask/scene-commit.test.ts +312 -0
- package/src/lib/point-ask/scene-commit.ts +32 -0
- package/src/lib/point-ask/session.ts +1391 -0
- package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
- package/src/lib/point-ask/spring-keyframes.ts +38 -0
- package/src/lib/point-ask/spring.test.ts +63 -0
- package/src/lib/point-ask/spring.ts +107 -0
- package/src/lib/point-ask/suggestions.test.ts +80 -0
- package/src/lib/point-ask/suggestions.ts +125 -0
- package/src/lib/point-ask/target-scope.test.ts +622 -0
- package/src/lib/point-ask/target-scope.ts +338 -0
- package/src/lib/point-ask/types.ts +48 -0
- package/src/lib/portable-export.test.ts +34 -1
- package/src/lib/portable-export.ts +119 -53
- package/src/lib/print-feature-thickness.ts +10 -1
- package/src/lib/print-roof-solids.test.ts +39 -1
- package/src/lib/print-roof-solids.ts +43 -10
- package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +265 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +1017 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +610 -1
- package/src/lib/scene-clipboard.ts +269 -58
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +54 -75
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sfx-bus.ts +13 -0
- package/src/lib/sfx-player.test.ts +70 -9
- package/src/lib/sfx-player.ts +6 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +214 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +580 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-changes.ts +35 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-audio.test.ts +146 -0
- package/src/store/use-audio.tsx +127 -3
- package/src/store/use-construction-reveal.test.ts +173 -0
- package/src/store/use-construction-reveal.ts +145 -0
- package/src/store/use-corner-snap-hint.test.ts +24 -0
- package/src/store/use-corner-snap-hint.ts +47 -0
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-follow-pascal.test.ts +62 -0
- package/src/store/use-follow-pascal.ts +53 -0
- package/src/store/use-point-ask.ts +247 -0
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +33 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +201 -0
- package/src/store/view-layout.ts +199 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/render-current-markup.tsx +16 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
- package/src/components/tools/stair/stair-click-guard.ts +0 -72
- package/src/components/tools/stair/stair-defaults.ts +0 -18
- package/src/components/tools/stair/stair-tool.tsx +0 -634
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -19,10 +19,12 @@ import {
|
|
|
19
19
|
SceneEnvironment,
|
|
20
20
|
useViewer,
|
|
21
21
|
Viewer,
|
|
22
|
+
type ViewerBreadcrumbHandler,
|
|
22
23
|
type ViewerImmersiveSession,
|
|
23
24
|
ViewerPresentations,
|
|
24
25
|
} from '@pascal-app/viewer'
|
|
25
26
|
import {
|
|
27
|
+
type CSSProperties,
|
|
26
28
|
memo,
|
|
27
29
|
Profiler,
|
|
28
30
|
type ProfilerOnRenderCallback,
|
|
@@ -37,12 +39,21 @@ import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
|
37
39
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
38
40
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
39
41
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
42
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
40
43
|
import { useSaveShortcut } from '../../hooks/use-save-shortcut'
|
|
44
|
+
import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
|
|
45
|
+
import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
|
|
46
|
+
import { isSceneView } from '../../lib/editor-views'
|
|
47
|
+
import { initFinishScriptRefresh } from '../../lib/geometry-script/finish-scripts'
|
|
48
|
+
import { useGestureLifecycleOwner } from '../../lib/gesture-lifecycle'
|
|
49
|
+
import { shouldShowEditingControls } from '../../lib/interaction/overlay-policy'
|
|
41
50
|
import {
|
|
42
51
|
createLocalProjectPresentationPersistence,
|
|
43
52
|
type LocalProjectPresentationPersistence,
|
|
44
53
|
} from '../../lib/local-project-presentation-persistence'
|
|
45
54
|
import { type ActivePaintMaterial, hasActivePaintMaterial } from '../../lib/material-paint'
|
|
55
|
+
import { usePaintRegionHovering } from '../../lib/paint-region-hover'
|
|
56
|
+
import { resetPaintMode, usePaintRegionMode } from '../../lib/paint-region-mode'
|
|
46
57
|
import {
|
|
47
58
|
applySceneGraphToEditor,
|
|
48
59
|
loadSceneFromLocalStorage,
|
|
@@ -51,20 +62,24 @@ import {
|
|
|
51
62
|
} from '../../lib/scene'
|
|
52
63
|
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
53
64
|
import { useUnitFocusRules } from '../../lib/units'
|
|
65
|
+
import { cn } from '../../lib/utils'
|
|
66
|
+
import { useViewerFloorDisplay } from '../../lib/viewer-selection'
|
|
54
67
|
import { type CameraHintAction, useCameraHintFocus } from '../../store/use-camera-hint-focus'
|
|
55
68
|
import useEditor from '../../store/use-editor'
|
|
56
69
|
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
57
70
|
import useSessionGroups from '../../store/use-session-groups'
|
|
71
|
+
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
72
|
+
import { PointAskLayer } from '../point-ask/point-ask-layer'
|
|
58
73
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
59
74
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
60
75
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
61
76
|
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
62
77
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
63
|
-
import { ZoneLabelEditorSystem } from '../systems/zone/zone-label-editor-system'
|
|
64
78
|
import { ZoneSystem } from '../systems/zone/zone-system'
|
|
65
79
|
import { BoxSelectTool } from '../tools/select/box-select-tool'
|
|
66
80
|
import { ToolManager } from '../tools/tool-manager'
|
|
67
81
|
import { ActionMenu } from '../ui/action-menu'
|
|
82
|
+
import { type ActionMenuPlacement, TOP_MENU_CLEARANCE } from '../ui/action-menu/placement'
|
|
68
83
|
import { CommandPalette, type CommandPaletteEmptyAction } from '../ui/command-palette'
|
|
69
84
|
import { EditorCommands } from '../ui/command-palette/editor-commands'
|
|
70
85
|
import { FloatingLevelSelector } from '../ui/floating-level-selector'
|
|
@@ -73,6 +88,7 @@ import { PanelManager } from '../ui/panels/panel-manager'
|
|
|
73
88
|
import { ErrorBoundary } from '../ui/primitives/error-boundary'
|
|
74
89
|
import { useSidebarStore } from '../ui/primitives/sidebar'
|
|
75
90
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
|
|
91
|
+
import { RightStack } from '../ui/right-stack'
|
|
76
92
|
import { SceneLoader, SceneLoadFailed } from '../ui/scene-loader'
|
|
77
93
|
import { AppSidebar } from '../ui/sidebar/app-sidebar'
|
|
78
94
|
import type { ExtraPanel } from '../ui/sidebar/icon-rail'
|
|
@@ -80,6 +96,9 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
|
|
|
80
96
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
81
97
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
82
98
|
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
99
|
+
import { ViewerHoverLabel } from '../viewer/viewer-hover-label'
|
|
100
|
+
import { ParametricViewerRooms } from '../viewer/viewer-rooms'
|
|
101
|
+
import { ViewerSelectionManager } from '../viewer/viewer-selection-manager'
|
|
83
102
|
import { ViewerStage } from '../viewer/viewer-stage'
|
|
84
103
|
import type { ViewerStageMode } from '../viewer/viewer-stage-modes'
|
|
85
104
|
import { CaptureCameraRig } from './capture-camera-rig'
|
|
@@ -93,21 +112,30 @@ import { FloatingActionMenu } from './floating-action-menu'
|
|
|
93
112
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
94
113
|
import { FloorplanModeCoordinator } from './floorplan-mode-coordinator'
|
|
95
114
|
import { FloorplanPanel } from './floorplan-panel'
|
|
115
|
+
import { FollowPascal } from './follow-pascal'
|
|
116
|
+
import { FootprintHeightHandle } from './footprint-height-handle'
|
|
96
117
|
import { Grid } from './grid'
|
|
97
118
|
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
98
119
|
import { GroupRotateHandle } from './group-rotate-handle'
|
|
99
120
|
import { GroupSelectionBox3D } from './group-selection-box-3d'
|
|
121
|
+
import { EditorHandleHitPriority } from './handles/handle-hit-priority'
|
|
100
122
|
import { NodeArrowHandles } from './node-arrow-handles'
|
|
101
123
|
import { QuickMeasurementHud } from './quick-measurement-hud'
|
|
102
124
|
import { RiserDiagramPanel } from './riser-diagram-panel'
|
|
125
|
+
import { RoomLabels3D } from './room-labels'
|
|
103
126
|
import { SelectionManager } from './selection-manager'
|
|
104
127
|
import { SiteEdgeLabels } from './site-edge-labels'
|
|
105
128
|
import { SlabHoleHighlights } from './slab-hole-highlights'
|
|
106
129
|
import { SnapshotCaptureOverlay } from './snapshot-capture-overlay'
|
|
107
130
|
import { type SnapshotCameraData, ThumbnailGenerator } from './thumbnail-generator'
|
|
131
|
+
import { VectorEdgeExtractor } from './vector-edge-extractor'
|
|
132
|
+
import { HostView } from './views/host-view'
|
|
133
|
+
import { SceneRegion } from './views/scene-region'
|
|
134
|
+
import { useActiveViewLayout, useEditorViews } from './views/use-editor-views'
|
|
108
135
|
import { WallMeasurementLabel } from './wall-measurement-label'
|
|
109
136
|
import { WallMoveSideHandles } from './wall-move-side-handles'
|
|
110
137
|
import { WallOpeningHighlights } from './wall-opening-highlights'
|
|
138
|
+
import { WallRegionHandles } from './wall-region-handles'
|
|
111
139
|
|
|
112
140
|
const CAMERA_CONTROLS_HINT_DISMISSED_STORAGE_KEY = 'editor-camera-controls-hint-dismissed:v1'
|
|
113
141
|
const PREVIEW_STAGE_SWITCHER_POSITION =
|
|
@@ -117,12 +145,16 @@ const DELETE_CURSOR_BADGE_OFFSET_X = 14
|
|
|
117
145
|
const DELETE_CURSOR_BADGE_OFFSET_Y = 14
|
|
118
146
|
const PAINT_CURSOR_BADGE_COLOR = '#818cf8'
|
|
119
147
|
const PAINT_CURSOR_BADGE_DISABLED_COLOR = '#94a3b8'
|
|
148
|
+
// Neutral: erasing paints no colour.
|
|
149
|
+
const ERASE_CURSOR_BADGE_COLOR = '#e4e4e7'
|
|
120
150
|
const PAINT_CURSOR_BADGE_OFFSET_X = 14
|
|
121
151
|
const PAINT_CURSOR_BADGE_OFFSET_Y = 14
|
|
122
152
|
const SCENE_READY_FALLBACK_MS = 8000
|
|
123
153
|
const PRESENTATION_PROJECT_NOT_RESTORED = Symbol('presentation-project-not-restored')
|
|
124
154
|
const useClientLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect
|
|
125
|
-
|
|
155
|
+
// `aim`: a region sub-mode off what it draws on — the HUD says where to go; no
|
|
156
|
+
// forbidden sign, since nothing is refused.
|
|
157
|
+
type PaintCursorBadgeState = 'empty' | 'ready' | 'aim' | 'blocked'
|
|
126
158
|
const recordEditorRender: ProfilerOnRenderCallback = (_id, _phase, actualDuration) => {
|
|
127
159
|
if (PERF_OVERLAY_ENABLED) recordPerfSample('react-render', actualDuration)
|
|
128
160
|
}
|
|
@@ -130,6 +162,10 @@ const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
|
130
162
|
default: { visibleColor: 0x00_aa_ff, hiddenColor: 0xf3_ff_47, strength: 5, pulse: true },
|
|
131
163
|
delete: { visibleColor: 0xef_44_44, hiddenColor: 0x99_1b_1b, strength: 6, pulse: false },
|
|
132
164
|
'paint-ready': { visibleColor: 0xf5_9e_0b, hiddenColor: 0xfd_e0_68, strength: 5, pulse: true },
|
|
165
|
+
// Erasing paints nothing: a neutral outline, no paint colour.
|
|
166
|
+
'erase-ready': { visibleColor: 0xe4_e4_e7, hiddenColor: 0xa1_a1_aa, strength: 5, pulse: false },
|
|
167
|
+
// Point and ask: the agent accent, steady (nothing animates a hover), the occluded parts faint.
|
|
168
|
+
point: { visibleColor: 0xa9_9b_ff, hiddenColor: 0x5b_4f_c9, strength: 5, pulse: false },
|
|
133
169
|
'paint-disabled': {
|
|
134
170
|
visibleColor: 0x94_a3_b8,
|
|
135
171
|
hiddenColor: 0x47_55_69,
|
|
@@ -149,11 +185,13 @@ const EDITOR_DEFAULT_RENDER = { shading: 'solid' } as const
|
|
|
149
185
|
function initializeEditorRuntime(): () => void {
|
|
150
186
|
const unsubscribeSpatialGrid = initSpatialGridSync()
|
|
151
187
|
const unsubscribeSpaceDetection = initSpaceDetectionSync(useScene, useEditor)
|
|
188
|
+
const stopFinishScriptRefresh = initFinishScriptRefresh()
|
|
152
189
|
initSFXBus()
|
|
153
190
|
|
|
154
191
|
return () => {
|
|
155
192
|
unsubscribeSpatialGrid()
|
|
156
193
|
unsubscribeSpaceDetection?.()
|
|
194
|
+
stopFinishScriptRefresh()
|
|
157
195
|
|
|
158
196
|
spatialGridManager.clear()
|
|
159
197
|
disposeSFXBus()
|
|
@@ -166,6 +204,8 @@ function initializeEditorRuntime(): () => void {
|
|
|
166
204
|
export interface EditorProps {
|
|
167
205
|
// Layout version — 'v1' (default) or 'v2' (navbar + two-column)
|
|
168
206
|
layoutVersion?: 'v1' | 'v2'
|
|
207
|
+
// Viewport edge the tool menu docks to (desktop only; mobile stays at the bottom)
|
|
208
|
+
actionMenuPlacement?: ActionMenuPlacement
|
|
169
209
|
|
|
170
210
|
// UI slots (v1)
|
|
171
211
|
appMenuButton?: ReactNode
|
|
@@ -177,11 +217,20 @@ export interface EditorProps {
|
|
|
177
217
|
viewerToolbarLeft?: ReactNode
|
|
178
218
|
viewerToolbarRight?: ReactNode
|
|
179
219
|
/**
|
|
180
|
-
* Full-bleed surface swapped in over the
|
|
181
|
-
*
|
|
182
|
-
*
|
|
220
|
+
* Full-bleed surface swapped in over the whole stage (v2) — e.g. a builder
|
|
221
|
+
* with its own canvas. The scene stays mounted underneath (no WebGL re-init)
|
|
222
|
+
* and the view bar steps aside. Something to show *beside* the scene is a
|
|
223
|
+
* view instead (`registerEditorHostView`).
|
|
183
224
|
*/
|
|
184
225
|
stageOverlay?: ReactNode
|
|
226
|
+
/**
|
|
227
|
+
* Chrome that belongs to the scene (v2), positioned over the 3D and 2D panes
|
|
228
|
+
* with the level selector and tool dock — e.g. a capture bar. Hidden while
|
|
229
|
+
* no scene view is on screen.
|
|
230
|
+
*/
|
|
231
|
+
sceneOverlay?: ReactNode
|
|
232
|
+
/** Show each pane's view bar (v2, default true). Off for a host that presents one fixed view. */
|
|
233
|
+
viewBar?: boolean
|
|
185
234
|
/**
|
|
186
235
|
* Docked below the node inspector (v2). Hosts mount the "save as preset"
|
|
187
236
|
* affordance here so it reads as part of the inspector surface and shows
|
|
@@ -222,13 +271,23 @@ export interface EditorProps {
|
|
|
222
271
|
// Loading indicator (e.g. project fetching in community mode)
|
|
223
272
|
isLoading?: boolean
|
|
224
273
|
|
|
225
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Replaces the loading surface inside the canvas panes in both layouts.
|
|
276
|
+
* Loading failures always use the editor's error/retry surface.
|
|
277
|
+
* Omit (or pass null) to use the default SceneLoader.
|
|
278
|
+
*/
|
|
279
|
+
loadingSlot?: ReactNode
|
|
280
|
+
|
|
281
|
+
// Fires when scene loading starts/finishes — lets hosts measure
|
|
226
282
|
// open-to-interactive time without reaching into internal loader state.
|
|
227
283
|
onLoaderChange?: (visible: boolean) => void
|
|
228
284
|
|
|
229
285
|
// Thumbnail
|
|
230
286
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
231
287
|
|
|
288
|
+
/** Viewer diagnostics for the host's error-reporter trail; see `<Viewer onBreadcrumb>`. */
|
|
289
|
+
onViewerBreadcrumb?: ViewerBreadcrumbHandler
|
|
290
|
+
|
|
232
291
|
/**
|
|
233
292
|
* When true, skip the viewer post-FX pipeline (same as `?disable=postFx`).
|
|
234
293
|
* Hosts use this for a stable local "light preview" without relying only on
|
|
@@ -243,6 +302,9 @@ export interface EditorProps {
|
|
|
243
302
|
sidebarOverlay?: ReactNode
|
|
244
303
|
viewerBanner?: ReactNode
|
|
245
304
|
|
|
305
|
+
/** Shown in the preview header. */
|
|
306
|
+
projectName?: string | null
|
|
307
|
+
|
|
246
308
|
// Panel config (passed through to sidebar panels — v1 only)
|
|
247
309
|
settingsPanelProps?: SettingsPanelProps
|
|
248
310
|
sitePanelProps?: SitePanelProps
|
|
@@ -422,7 +484,7 @@ type CameraControlHint = {
|
|
|
422
484
|
alternativeKeys?: ShortcutKey[]
|
|
423
485
|
}
|
|
424
486
|
|
|
425
|
-
const
|
|
487
|
+
const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
426
488
|
{
|
|
427
489
|
action: 'Pan',
|
|
428
490
|
keys: [{ value: 'Space' }, { value: 'Left click' }],
|
|
@@ -432,12 +494,6 @@ const EDITOR_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
432
494
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
433
495
|
]
|
|
434
496
|
|
|
435
|
-
const PREVIEW_CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
436
|
-
{ action: 'Pan', keys: [{ value: 'Left click' }] },
|
|
437
|
-
{ action: 'Rotate', keys: [{ value: 'Right click' }] },
|
|
438
|
-
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
439
|
-
]
|
|
440
|
-
|
|
441
497
|
const CAMERA_SHORTCUT_KEY_META: Record<string, { icon?: string; label: string; text?: string }> = {
|
|
442
498
|
'Left click': {
|
|
443
499
|
icon: 'ph:mouse-left-click-fill',
|
|
@@ -545,13 +601,13 @@ function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
|
545
601
|
}
|
|
546
602
|
|
|
547
603
|
function ViewerCanvasControlsHint({
|
|
548
|
-
|
|
604
|
+
belowTopMenu = false,
|
|
549
605
|
onDismiss,
|
|
550
606
|
}: {
|
|
551
|
-
|
|
607
|
+
belowTopMenu?: boolean
|
|
552
608
|
onDismiss: () => void
|
|
553
609
|
}) {
|
|
554
|
-
const all =
|
|
610
|
+
const all = CAMERA_CONTROL_HINTS
|
|
555
611
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
556
612
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
557
613
|
const focus = useCameraHintFocus((state) => state.actions)
|
|
@@ -562,7 +618,13 @@ function ViewerCanvasControlsHint({
|
|
|
562
618
|
}
|
|
563
619
|
|
|
564
620
|
return (
|
|
565
|
-
<div
|
|
621
|
+
<div
|
|
622
|
+
className={cn(
|
|
623
|
+
'pointer-events-none absolute left-1/2 z-40 max-w-[calc(100%-2rem)] -translate-x-1/2',
|
|
624
|
+
!belowTopMenu && 'top-14',
|
|
625
|
+
)}
|
|
626
|
+
style={belowTopMenu ? { top: TOP_MENU_CLEARANCE } : undefined}
|
|
627
|
+
>
|
|
566
628
|
<section
|
|
567
629
|
aria-label="Camera controls hint"
|
|
568
630
|
className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-elevation-4 backdrop-blur-xl"
|
|
@@ -680,26 +742,31 @@ function PaintCursorBadge({
|
|
|
680
742
|
swatchColor,
|
|
681
743
|
swatchImageUrl,
|
|
682
744
|
isEraser,
|
|
745
|
+
isPicker = false,
|
|
683
746
|
}: {
|
|
684
747
|
position: { x: number; y: number }
|
|
685
748
|
state: PaintCursorBadgeState
|
|
686
749
|
swatchColor: string
|
|
687
750
|
swatchImageUrl?: string
|
|
688
751
|
isEraser: boolean
|
|
752
|
+
/** The eyedropper: the swatch is what a click would take. */
|
|
753
|
+
isPicker?: boolean
|
|
689
754
|
}) {
|
|
690
755
|
const accentColor =
|
|
691
756
|
state === 'ready'
|
|
692
757
|
? isEraser
|
|
693
|
-
?
|
|
758
|
+
? ERASE_CURSOR_BADGE_COLOR
|
|
694
759
|
: swatchColor
|
|
695
760
|
: PAINT_CURSOR_BADGE_DISABLED_COLOR
|
|
696
|
-
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' ? 0.62 : 0.42
|
|
761
|
+
const iconOpacity = state === 'ready' ? 1 : state === 'blocked' || state === 'aim' ? 0.62 : 0.42
|
|
697
762
|
const lineHeight = 18
|
|
698
763
|
|
|
699
764
|
return (
|
|
700
765
|
<div
|
|
701
766
|
aria-hidden="true"
|
|
702
767
|
className="pointer-events-none absolute z-40"
|
|
768
|
+
data-paint-cursor-state={state}
|
|
769
|
+
data-paint-cursor-tool={isEraser ? 'erase' : isPicker ? 'pick' : 'paint'}
|
|
703
770
|
style={{
|
|
704
771
|
left: position.x,
|
|
705
772
|
top: position.y,
|
|
@@ -721,29 +788,32 @@ function PaintCursorBadge({
|
|
|
721
788
|
transform: `translate(-50%, calc(-100% - ${lineHeight}px))`,
|
|
722
789
|
}}
|
|
723
790
|
>
|
|
724
|
-
{
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
opacity: iconOpacity
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
791
|
+
{isPicker || isEraser ? (
|
|
792
|
+
// The eyedropper and the eraser carry their own tool, never a paint colour.
|
|
793
|
+
<Icon
|
|
794
|
+
aria-hidden="true"
|
|
795
|
+
className="text-white"
|
|
796
|
+
color="currentColor"
|
|
797
|
+
height={18}
|
|
798
|
+
icon={isEraser ? 'mdi:eraser-variant' : 'lucide:pipette'}
|
|
799
|
+
style={{ opacity: iconOpacity }}
|
|
800
|
+
width={18}
|
|
801
|
+
/>
|
|
802
|
+
) : (
|
|
803
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
804
|
+
<img
|
|
805
|
+
alt=""
|
|
806
|
+
aria-hidden="true"
|
|
807
|
+
className="h-5 w-5 object-contain drop-shadow-[0_2px_4px_rgba(0,0,0,0.5)]"
|
|
808
|
+
src="/icons/paint.webp"
|
|
809
|
+
style={{
|
|
810
|
+
filter: state === 'ready' ? undefined : 'grayscale(1)',
|
|
811
|
+
opacity: iconOpacity,
|
|
812
|
+
}}
|
|
813
|
+
/>
|
|
814
|
+
)}
|
|
815
|
+
{state === 'ready' || state === 'aim' ? (
|
|
816
|
+
isEraser ? null : (
|
|
747
817
|
<span
|
|
748
818
|
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)]"
|
|
749
819
|
style={{
|
|
@@ -751,6 +821,7 @@ function PaintCursorBadge({
|
|
|
751
821
|
backgroundImage: swatchImageUrl
|
|
752
822
|
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
753
823
|
: undefined,
|
|
824
|
+
opacity: state === 'aim' ? 0.55 : 1,
|
|
754
825
|
}}
|
|
755
826
|
/>
|
|
756
827
|
)
|
|
@@ -815,16 +886,20 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
815
886
|
<SceneEnvironment />
|
|
816
887
|
{!(isFirstPersonMode || isStudioMode || isCaptureMode) && <SelectionManager />}
|
|
817
888
|
{!(noEditing || isXRMode) && <BoxSelectTool />}
|
|
889
|
+
{!noEditing && <EditorHandleHitPriority />}
|
|
818
890
|
{!noEditing && <NodeArrowHandles />}
|
|
891
|
+
{!noEditing && <FootprintHeightHandle />}
|
|
819
892
|
{!noEditing && <GroupRotateHandle />}
|
|
820
893
|
{!noEditing && <GroupSelectionBox3D />}
|
|
821
894
|
{!noEditing && <WallOpeningHighlights />}
|
|
822
895
|
{!noEditing && <SlabHoleHighlights />}
|
|
823
896
|
{!noEditing && <WallMoveSideHandles />}
|
|
897
|
+
{!noEditing && <WallRegionHandles />}
|
|
824
898
|
{!noEditing && <FenceTangentLines3D />}
|
|
825
899
|
{!(noEditing || isXRMode) && <FloatingActionMenu />}
|
|
826
900
|
{!(noEditing || isXRMode) && <GroupFloatingActionMenu />}
|
|
827
901
|
{!(noEditing || isXRMode) && <FloatingBuildingActionMenu />}
|
|
902
|
+
{!(noEditing || isXRMode) && <RoomLabels3D />}
|
|
828
903
|
{!(isFirstPersonMode || isXRMode) && <WallMeasurementLabel />}
|
|
829
904
|
<ExportManager />
|
|
830
905
|
{isFirstPersonMode ? <ViewerZoneSystem /> : <ZoneSystem />}
|
|
@@ -838,9 +913,10 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
838
913
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
839
914
|
{isCaptureMode && !isXRMode && <CaptureCameraRig />}
|
|
840
915
|
{!isXRMode && <CustomCameraControls />}
|
|
916
|
+
{!isXRMode && <FollowPascal />}
|
|
841
917
|
{!isXRMode && <ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />}
|
|
918
|
+
{!isXRMode && <VectorEdgeExtractor />}
|
|
842
919
|
{!(isFirstPersonMode || isXRMode) && <SiteEdgeLabels />}
|
|
843
|
-
<InteractiveSystem />
|
|
844
920
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
845
921
|
{!noEditing && viewerSceneSlot}
|
|
846
922
|
</>
|
|
@@ -933,7 +1009,10 @@ function PaintCursorLayer({
|
|
|
933
1009
|
}) {
|
|
934
1010
|
const mode = useEditor((s) => s.mode)
|
|
935
1011
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
936
|
-
const
|
|
1012
|
+
const paintMode = usePaintRegionMode((s) => s.mode)
|
|
1013
|
+
const picked = usePaintRegionMode((s) => s.picked)
|
|
1014
|
+
const paintEraser = paintMode === 'erase'
|
|
1015
|
+
const picking = paintMode === 'pick'
|
|
937
1016
|
const paintHover = useEditor((s) => s.paintHover)
|
|
938
1017
|
const sceneMaterials = useScene((s) => s.materials)
|
|
939
1018
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null)
|
|
@@ -985,14 +1064,26 @@ function PaintCursorLayer({
|
|
|
985
1064
|
}
|
|
986
1065
|
}, [active, containerRef])
|
|
987
1066
|
|
|
988
|
-
const hasPaint = paintEraser || hasActivePaintMaterial(activePaintMaterial)
|
|
1067
|
+
const hasPaint = paintEraser || picking || hasActivePaintMaterial(activePaintMaterial)
|
|
1068
|
+
// A region sub-mode reads its own surfaces (walls, floors), not the whole-surface hover.
|
|
1069
|
+
const regionMode = paintMode === 'rectangle' || paintMode === 'polygon'
|
|
1070
|
+
const regionHovering = usePaintRegionHovering()
|
|
989
1071
|
const badgeState: PaintCursorBadgeState = !hasPaint
|
|
990
1072
|
? 'empty'
|
|
991
|
-
:
|
|
992
|
-
?
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1073
|
+
: picking
|
|
1074
|
+
? picked
|
|
1075
|
+
? 'ready'
|
|
1076
|
+
: 'aim'
|
|
1077
|
+
: regionMode
|
|
1078
|
+
? regionHovering
|
|
1079
|
+
? 'ready'
|
|
1080
|
+
: 'aim'
|
|
1081
|
+
: paintHover != null
|
|
1082
|
+
? 'ready'
|
|
1083
|
+
: 'blocked'
|
|
1084
|
+
const swatchMaterial = picking ? picked : activePaintMaterial
|
|
1085
|
+
const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials)
|
|
1086
|
+
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials)
|
|
996
1087
|
|
|
997
1088
|
if (!active || !position) return null
|
|
998
1089
|
|
|
@@ -1003,6 +1094,7 @@ function PaintCursorLayer({
|
|
|
1003
1094
|
>
|
|
1004
1095
|
<PaintCursorBadge
|
|
1005
1096
|
isEraser={paintEraser}
|
|
1097
|
+
isPicker={picking}
|
|
1006
1098
|
position={{ x: 0, y: 0 }}
|
|
1007
1099
|
state={badgeState}
|
|
1008
1100
|
swatchColor={swatchColor}
|
|
@@ -1012,8 +1104,8 @@ function PaintCursorLayer({
|
|
|
1012
1104
|
)
|
|
1013
1105
|
}
|
|
1014
1106
|
|
|
1015
|
-
// ── Viewer canvas: memoized, subscribes to
|
|
1016
|
-
// This prevents Editor from re-rendering when
|
|
1107
|
+
// ── Viewer canvas: memoized, subscribes to the view layout internally ──
|
|
1108
|
+
// This prevents Editor from re-rendering when the layout changes.
|
|
1017
1109
|
|
|
1018
1110
|
const ViewerCanvas = memo(function ViewerCanvas({
|
|
1019
1111
|
isVersionPreviewMode,
|
|
@@ -1022,14 +1114,18 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1022
1114
|
isStudioMode,
|
|
1023
1115
|
hasLoadedInitialScene,
|
|
1024
1116
|
showLoader,
|
|
1117
|
+
isSceneDataLoading,
|
|
1118
|
+
loadingOverlay,
|
|
1025
1119
|
sceneReadyKey,
|
|
1026
1120
|
onSceneReadyChange,
|
|
1027
1121
|
onThumbnailCapture,
|
|
1122
|
+
onViewerBreadcrumb,
|
|
1028
1123
|
presentationsReady,
|
|
1029
1124
|
viewerSceneSlot,
|
|
1030
1125
|
floorplanSceneSlot,
|
|
1031
1126
|
disablePostFx = false,
|
|
1032
1127
|
immersive,
|
|
1128
|
+
hintBelowTopMenu = false,
|
|
1033
1129
|
}: {
|
|
1034
1130
|
isVersionPreviewMode: boolean
|
|
1035
1131
|
isLoading: boolean
|
|
@@ -1037,21 +1133,33 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1037
1133
|
isStudioMode: boolean
|
|
1038
1134
|
hasLoadedInitialScene: boolean
|
|
1039
1135
|
showLoader: boolean
|
|
1136
|
+
isSceneDataLoading: boolean
|
|
1137
|
+
loadingOverlay: ReactNode
|
|
1040
1138
|
sceneReadyKey: number
|
|
1041
1139
|
onSceneReadyChange: (ready: boolean) => void
|
|
1042
1140
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
1141
|
+
onViewerBreadcrumb?: ViewerBreadcrumbHandler
|
|
1043
1142
|
presentationsReady: boolean
|
|
1044
1143
|
viewerSceneSlot?: ReactNode
|
|
1045
1144
|
floorplanSceneSlot?: ReactNode
|
|
1046
1145
|
disablePostFx?: boolean
|
|
1047
1146
|
immersive?: ViewerImmersiveSession
|
|
1147
|
+
hintBelowTopMenu?: boolean
|
|
1048
1148
|
}) {
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const setFloorplanPaneRatio = useEditor((s) => s.setFloorplanPaneRatio)
|
|
1149
|
+
const layout = useActiveViewLayout()
|
|
1150
|
+
const views = useEditorViews()
|
|
1052
1151
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1053
1152
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1054
1153
|
useUnitFocusRules()
|
|
1154
|
+
const wholeBuilding = useEditor((s) =>
|
|
1155
|
+
showsWholeBuilding({
|
|
1156
|
+
isPreviewMode: s.isPreviewMode,
|
|
1157
|
+
isFirstPersonMode,
|
|
1158
|
+
captureMode: s.captureMode,
|
|
1159
|
+
captureLevelId: s.captureLevelId,
|
|
1160
|
+
}),
|
|
1161
|
+
)
|
|
1162
|
+
useEditorLevelDisplay(wholeBuilding)
|
|
1055
1163
|
const presetIsolation = useEditor((s) =>
|
|
1056
1164
|
s.captureMode.mode === 'preset' ? s.captureMode.isolated : null,
|
|
1057
1165
|
)
|
|
@@ -1069,25 +1177,24 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1069
1177
|
setViewerAreaEl(el)
|
|
1070
1178
|
}, [])
|
|
1071
1179
|
const viewer3dRef = useRef<HTMLDivElement>(null)
|
|
1072
|
-
const
|
|
1180
|
+
const isResizingPanes = useRef(false)
|
|
1073
1181
|
|
|
1074
|
-
const
|
|
1182
|
+
const handlePaneDividerDown = useCallback((e: React.PointerEvent) => {
|
|
1075
1183
|
e.preventDefault()
|
|
1076
|
-
|
|
1184
|
+
isResizingPanes.current = true
|
|
1077
1185
|
document.body.style.cursor = 'col-resize'
|
|
1078
1186
|
document.body.style.userSelect = 'none'
|
|
1079
1187
|
}, [])
|
|
1080
1188
|
|
|
1081
1189
|
useEffect(() => {
|
|
1082
1190
|
const handlePointerMove = (e: PointerEvent) => {
|
|
1083
|
-
if (!
|
|
1191
|
+
if (!isResizingPanes.current) return
|
|
1084
1192
|
if (!viewerAreaRef.current) return
|
|
1085
1193
|
const rect = viewerAreaRef.current.getBoundingClientRect()
|
|
1086
|
-
|
|
1087
|
-
setFloorplanPaneRatio(Math.max(0.15, Math.min(0.85, newRatio)))
|
|
1194
|
+
useEditor.getState().setViewPaneRatio((e.clientX - rect.left) / rect.width)
|
|
1088
1195
|
}
|
|
1089
1196
|
const handlePointerUp = () => {
|
|
1090
|
-
|
|
1197
|
+
isResizingPanes.current = false
|
|
1091
1198
|
document.body.style.cursor = ''
|
|
1092
1199
|
document.body.style.userSelect = ''
|
|
1093
1200
|
}
|
|
@@ -1097,7 +1204,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1097
1204
|
window.removeEventListener('pointermove', handlePointerMove)
|
|
1098
1205
|
window.removeEventListener('pointerup', handlePointerUp)
|
|
1099
1206
|
}
|
|
1100
|
-
}, [
|
|
1207
|
+
}, [])
|
|
1101
1208
|
|
|
1102
1209
|
useEffect(() => {
|
|
1103
1210
|
setIsCameraControlsHintVisible(!readCameraControlsHintDismissed())
|
|
@@ -1108,91 +1215,141 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1108
1215
|
writeCameraControlsHintDismissed(true)
|
|
1109
1216
|
}, [])
|
|
1110
1217
|
|
|
1111
|
-
const
|
|
1112
|
-
const
|
|
1218
|
+
const pane2d = paneOfView(layout, VIEW_2D)
|
|
1219
|
+
const pane3d = paneOfView(layout, VIEW_3D)
|
|
1220
|
+
const show3d = pane3d !== null
|
|
1221
|
+
// Panes are grid cells placed by `gridColumn`, in a fixed DOM order: moving a
|
|
1222
|
+
// view to the other pane restyles it rather than reparenting it, so the 3D
|
|
1223
|
+
// canvas keeps its WebGL context and the plan its session through a swap.
|
|
1224
|
+
const paneStyle = (pane: ViewPaneIndex | null): CSSProperties =>
|
|
1225
|
+
pane === null ? { display: 'none' } : { gridColumn: pane + 1, gridRow: 1 }
|
|
1226
|
+
const hostPanes = ([0, 1] as const).filter(
|
|
1227
|
+
(pane) => (pane === 0 || layout.split) && !isSceneView(layout.panes[pane]),
|
|
1228
|
+
)
|
|
1229
|
+
const focusPane = (pane: ViewPaneIndex) => () => {
|
|
1230
|
+
if (layout.split) useEditor.getState().focusViewPane(pane)
|
|
1231
|
+
}
|
|
1113
1232
|
|
|
1114
1233
|
return (
|
|
1115
1234
|
<ErrorBoundary fallback={<EditorSceneCrashFallback />}>
|
|
1116
1235
|
{/* `relative` so the floorplan compass (portaled here to stay visible in
|
|
1117
|
-
|
|
1118
|
-
<div className="relative
|
|
1236
|
+
every layout) can anchor to this container's bottom-left. */}
|
|
1237
|
+
<div className="relative h-full" ref={setViewerAreaNode}>
|
|
1119
1238
|
<QuickMeasurementHud />
|
|
1120
1239
|
<DeleteConfirmationDialog />
|
|
1121
|
-
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1122
1240
|
<div
|
|
1123
|
-
className="
|
|
1241
|
+
className="absolute inset-0 grid"
|
|
1242
|
+
data-view-split={layout.split || undefined}
|
|
1124
1243
|
style={{
|
|
1125
|
-
|
|
1126
|
-
|
|
1244
|
+
gridTemplateColumns: layout.split
|
|
1245
|
+
? `minmax(0, ${layout.ratio}fr) minmax(0, ${1 - layout.ratio}fr)`
|
|
1246
|
+
: 'minmax(0, 1fr)',
|
|
1247
|
+
gridTemplateRows: 'minmax(0, 1fr)',
|
|
1127
1248
|
}}
|
|
1128
1249
|
>
|
|
1129
|
-
|
|
1130
|
-
|
|
1250
|
+
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1251
|
+
<div
|
|
1252
|
+
aria-busy={isSceneDataLoading}
|
|
1253
|
+
className="relative min-w-0 overflow-hidden"
|
|
1254
|
+
data-pascal-viewer-2d
|
|
1255
|
+
data-view-pane={pane2d ?? undefined}
|
|
1256
|
+
onPointerDownCapture={pane2d === null ? undefined : focusPane(pane2d)}
|
|
1257
|
+
style={paneStyle(pane2d)}
|
|
1258
|
+
>
|
|
1259
|
+
<div className="relative isolate h-full w-full overflow-hidden">
|
|
1260
|
+
<FloorplanPanel compassHost={viewerAreaEl} floorplanSceneSlot={floorplanSceneSlot} />
|
|
1261
|
+
</div>
|
|
1262
|
+
{isSceneDataLoading && loadingOverlay}
|
|
1131
1263
|
</div>
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1264
|
+
|
|
1265
|
+
{/* 3D viewer — always mounted, hidden via CSS to avoid destroying the WebGL context */}
|
|
1266
|
+
<div
|
|
1267
|
+
aria-busy={showLoader}
|
|
1268
|
+
className="relative min-w-0 overflow-hidden"
|
|
1269
|
+
data-pascal-viewer-3d
|
|
1270
|
+
data-view-pane={pane3d ?? undefined}
|
|
1271
|
+
onPointerDownCapture={pane3d === null ? undefined : focusPane(pane3d)}
|
|
1272
|
+
ref={viewer3dRef}
|
|
1273
|
+
style={paneStyle(pane3d)}
|
|
1274
|
+
>
|
|
1275
|
+
<div className="relative isolate h-full">
|
|
1276
|
+
<DeleteCursorLayer
|
|
1277
|
+
containerRef={viewer3dRef}
|
|
1278
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1279
|
+
/>
|
|
1280
|
+
<PaintCursorLayer
|
|
1281
|
+
containerRef={viewer3dRef}
|
|
1282
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1283
|
+
/>
|
|
1284
|
+
{!isVersionPreviewMode && <PointAskLayer />}
|
|
1285
|
+
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1286
|
+
<ViewerCanvasControlsHint
|
|
1287
|
+
belowTopMenu={hintBelowTopMenu}
|
|
1288
|
+
onDismiss={dismissCameraControlsHint}
|
|
1289
|
+
/>
|
|
1290
|
+
) : null}
|
|
1291
|
+
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1292
|
+
<Viewer
|
|
1293
|
+
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1294
|
+
disablePostFx={disablePostFx}
|
|
1295
|
+
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1296
|
+
isolate={presetIsolation}
|
|
1297
|
+
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1298
|
+
// Other modes retain the viewer's configured background policy.
|
|
1299
|
+
transparent={presetIsolation === null ? undefined : true}
|
|
1300
|
+
onSceneReadyChange={onSceneReadyChange}
|
|
1301
|
+
onBreadcrumb={onViewerBreadcrumb}
|
|
1302
|
+
renderContext="editor"
|
|
1303
|
+
renderPaused={!show3d && !showLoader}
|
|
1304
|
+
sceneReadyKey={sceneReadyKey}
|
|
1305
|
+
immersive={immersive}
|
|
1306
|
+
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1307
|
+
// viewer's default selection manager would hover-highlight whatever
|
|
1308
|
+
// the cursor crosses, which orbit capture never does.
|
|
1309
|
+
selectionManager={isFirstPersonMode && !isCaptureMode ? 'default' : 'custom'}
|
|
1310
|
+
>
|
|
1311
|
+
<ViewerSceneContent
|
|
1312
|
+
isFirstPersonMode={isFirstPersonMode}
|
|
1313
|
+
isLoading={showLoader}
|
|
1314
|
+
isXRMode={immersive != null}
|
|
1315
|
+
isStudioMode={isStudioMode}
|
|
1316
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1317
|
+
onThumbnailCapture={onThumbnailCapture}
|
|
1318
|
+
presentationsReady={presentationsReady}
|
|
1319
|
+
viewerSceneSlot={viewerSceneSlot}
|
|
1320
|
+
/>
|
|
1321
|
+
</Viewer>
|
|
1138
1322
|
</div>
|
|
1139
|
-
|
|
1323
|
+
{showLoader && loadingOverlay}
|
|
1324
|
+
</div>
|
|
1325
|
+
|
|
1326
|
+
{hostPanes.map((pane) => {
|
|
1327
|
+
const viewId = layout.panes[pane]
|
|
1328
|
+
return (
|
|
1329
|
+
<div
|
|
1330
|
+
className="relative min-w-0 overflow-hidden"
|
|
1331
|
+
data-view-pane={pane}
|
|
1332
|
+
key={viewId}
|
|
1333
|
+
onFocusCapture={focusPane(pane)}
|
|
1334
|
+
onPointerDownCapture={focusPane(pane)}
|
|
1335
|
+
style={paneStyle(pane)}
|
|
1336
|
+
>
|
|
1337
|
+
<HostView key={viewId} view={views.find((view) => view.id === viewId)} />
|
|
1338
|
+
</div>
|
|
1339
|
+
)
|
|
1340
|
+
})}
|
|
1140
1341
|
</div>
|
|
1141
1342
|
|
|
1142
|
-
{
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
style={{ display: show3d ? undefined : 'none' }}
|
|
1148
|
-
>
|
|
1149
|
-
<DeleteCursorLayer
|
|
1150
|
-
containerRef={viewer3dRef}
|
|
1151
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1152
|
-
/>
|
|
1153
|
-
<PaintCursorLayer
|
|
1154
|
-
containerRef={viewer3dRef}
|
|
1155
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1156
|
-
/>
|
|
1157
|
-
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1158
|
-
<ViewerCanvasControlsHint
|
|
1159
|
-
isPreviewMode={isPreviewMode}
|
|
1160
|
-
onDismiss={dismissCameraControlsHint}
|
|
1161
|
-
/>
|
|
1162
|
-
) : null}
|
|
1163
|
-
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1164
|
-
<Viewer
|
|
1165
|
-
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1166
|
-
disablePostFx={disablePostFx}
|
|
1167
|
-
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1168
|
-
isolate={presetIsolation}
|
|
1169
|
-
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1170
|
-
// Other modes retain the viewer's configured background policy.
|
|
1171
|
-
transparent={presetIsolation === null ? undefined : true}
|
|
1172
|
-
onSceneReadyChange={onSceneReadyChange}
|
|
1173
|
-
renderContext="editor"
|
|
1174
|
-
renderPaused={!show3d && !showLoader}
|
|
1175
|
-
sceneReadyKey={sceneReadyKey}
|
|
1176
|
-
immersive={immersive}
|
|
1177
|
-
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1178
|
-
// viewer's default selection manager would hover-highlight whatever
|
|
1179
|
-
// the cursor crosses, which orbit capture never does.
|
|
1180
|
-
selectionManager={isFirstPersonMode && !isCaptureMode ? 'default' : 'custom'}
|
|
1343
|
+
{layout.split && (
|
|
1344
|
+
<div
|
|
1345
|
+
className="absolute inset-y-0 z-10 flex w-6 -translate-x-1/2 cursor-col-resize items-center justify-center"
|
|
1346
|
+
onPointerDown={handlePaneDividerDown}
|
|
1347
|
+
style={{ left: `${layout.ratio * 100}%` }}
|
|
1181
1348
|
>
|
|
1182
|
-
<
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
isXRMode={immersive != null}
|
|
1186
|
-
isStudioMode={isStudioMode}
|
|
1187
|
-
isVersionPreviewMode={isVersionPreviewMode}
|
|
1188
|
-
onThumbnailCapture={onThumbnailCapture}
|
|
1189
|
-
presentationsReady={presentationsReady}
|
|
1190
|
-
viewerSceneSlot={viewerSceneSlot}
|
|
1191
|
-
/>
|
|
1192
|
-
</Viewer>
|
|
1193
|
-
</div>
|
|
1349
|
+
<div className="h-8 w-1 rounded-full bg-neutral-400" />
|
|
1350
|
+
</div>
|
|
1351
|
+
)}
|
|
1194
1352
|
</div>
|
|
1195
|
-
{!(showLoader || isVersionPreviewMode) && <ZoneLabelEditorSystem />}
|
|
1196
1353
|
</ErrorBoundary>
|
|
1197
1354
|
)
|
|
1198
1355
|
})
|
|
@@ -1201,13 +1358,17 @@ function PreviewStage({
|
|
|
1201
1358
|
isFirstPersonMode,
|
|
1202
1359
|
mode,
|
|
1203
1360
|
onModeChange,
|
|
1361
|
+
projectName,
|
|
1204
1362
|
showLoader,
|
|
1363
|
+
loadingOverlay,
|
|
1205
1364
|
viewerContent,
|
|
1206
1365
|
}: {
|
|
1207
1366
|
isFirstPersonMode: boolean
|
|
1208
1367
|
mode: ViewerStageMode
|
|
1209
1368
|
onModeChange: (mode: ViewerStageMode) => void
|
|
1369
|
+
projectName?: string | null
|
|
1210
1370
|
showLoader: boolean
|
|
1371
|
+
loadingOverlay: ReactNode
|
|
1211
1372
|
viewerContent: ReactNode
|
|
1212
1373
|
}) {
|
|
1213
1374
|
const hasFloorplan = useScene((state) =>
|
|
@@ -1222,6 +1383,7 @@ function PreviewStage({
|
|
|
1222
1383
|
[onModeChange],
|
|
1223
1384
|
)
|
|
1224
1385
|
|
|
1386
|
+
useViewerFloorDisplay()
|
|
1225
1387
|
const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
|
|
1226
1388
|
const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
|
|
1227
1389
|
|
|
@@ -1233,9 +1395,11 @@ function PreviewStage({
|
|
|
1233
1395
|
<ViewerOverlay
|
|
1234
1396
|
hideBottomBar={stageMode !== '3d'}
|
|
1235
1397
|
onBack={() => useEditor.getState().setPreviewMode(false)}
|
|
1398
|
+
projectName={projectName}
|
|
1236
1399
|
/>
|
|
1237
1400
|
)}
|
|
1238
1401
|
|
|
1402
|
+
{stageMode === '3d' && !isFirstPersonMode ? <ViewerHoverLabel /> : null}
|
|
1239
1403
|
<ViewerStage
|
|
1240
1404
|
className="absolute inset-0"
|
|
1241
1405
|
mode={stageMode}
|
|
@@ -1246,6 +1410,7 @@ function PreviewStage({
|
|
|
1246
1410
|
switcherClassName={`${PREVIEW_STAGE_SWITCHER_POSITION} ${showLoader ? 'z-[70]' : ''}`}
|
|
1247
1411
|
>
|
|
1248
1412
|
{viewerContent}
|
|
1413
|
+
{showLoader && loadingOverlay}
|
|
1249
1414
|
</ViewerStage>
|
|
1250
1415
|
</div>
|
|
1251
1416
|
)
|
|
@@ -1254,6 +1419,7 @@ function PreviewStage({
|
|
|
1254
1419
|
function EditorContent({
|
|
1255
1420
|
guardAgainstSceneWipe,
|
|
1256
1421
|
layoutVersion = 'v1',
|
|
1422
|
+
actionMenuPlacement,
|
|
1257
1423
|
appMenuButton,
|
|
1258
1424
|
sidebarTop,
|
|
1259
1425
|
navbarSlot,
|
|
@@ -1261,6 +1427,8 @@ function EditorContent({
|
|
|
1261
1427
|
viewerToolbarLeft,
|
|
1262
1428
|
viewerToolbarRight,
|
|
1263
1429
|
stageOverlay,
|
|
1430
|
+
sceneOverlay,
|
|
1431
|
+
viewBar = true,
|
|
1264
1432
|
inspectorFooter,
|
|
1265
1433
|
multiSelectionFooter,
|
|
1266
1434
|
viewerSceneSlot,
|
|
@@ -1274,17 +1442,23 @@ function EditorContent({
|
|
|
1274
1442
|
previewScene,
|
|
1275
1443
|
isVersionPreviewMode = false,
|
|
1276
1444
|
isLoading = false,
|
|
1445
|
+
loadingSlot,
|
|
1277
1446
|
onLoaderChange,
|
|
1278
1447
|
onThumbnailCapture,
|
|
1448
|
+
onViewerBreadcrumb,
|
|
1279
1449
|
disablePostFx = false,
|
|
1280
1450
|
immersive,
|
|
1281
1451
|
sidebarOverlay,
|
|
1282
1452
|
viewerBanner,
|
|
1453
|
+
projectName,
|
|
1283
1454
|
settingsPanelProps,
|
|
1284
1455
|
sitePanelProps,
|
|
1285
1456
|
extraSidebarPanels,
|
|
1286
1457
|
commandPaletteEmptyAction,
|
|
1287
1458
|
}: EditorProps) {
|
|
1459
|
+
const isMobile = useIsMobile()
|
|
1460
|
+
// A top-docked menu sits at the top of the scene panes on desktop (layout v2).
|
|
1461
|
+
const dockMenuOnTop = actionMenuPlacement === 'top' && !isMobile
|
|
1288
1462
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
1289
1463
|
const isStudioMode = useEditor((s) => s.workspaceMode === 'studio')
|
|
1290
1464
|
const presentationProjectId = projectId ?? null
|
|
@@ -1311,8 +1485,9 @@ function EditorContent({
|
|
|
1311
1485
|
}, [presentationProjectId])
|
|
1312
1486
|
|
|
1313
1487
|
useKeyboard({ isVersionPreviewMode, disabled: isFirstPersonMode || isStudioMode })
|
|
1488
|
+
useGestureLifecycleOwner()
|
|
1314
1489
|
|
|
1315
|
-
const {
|
|
1490
|
+
const { beginSceneLoad, completeSceneLoad, saveNow } = useAutoSave({
|
|
1316
1491
|
guardAgainstSceneWipe,
|
|
1317
1492
|
onSave,
|
|
1318
1493
|
onDirty,
|
|
@@ -1338,6 +1513,12 @@ function EditorContent({
|
|
|
1338
1513
|
const [previewStageMode, setPreviewStageMode] = useState<ViewerStageMode>('3d')
|
|
1339
1514
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1340
1515
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1516
|
+
const readOnly = useScene((s) => s.readOnly)
|
|
1517
|
+
// Chrome only steps down for the top menu while the menu is actually there.
|
|
1518
|
+
const topMenuShown =
|
|
1519
|
+
dockMenuOnTop &&
|
|
1520
|
+
!(isVersionPreviewMode || isCaptureMode || isStudioMode) &&
|
|
1521
|
+
shouldShowEditingControls(readOnly)
|
|
1341
1522
|
|
|
1342
1523
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
|
1343
1524
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
@@ -1352,6 +1533,7 @@ function EditorContent({
|
|
|
1352
1533
|
const teardown = initializeEditorRuntime()
|
|
1353
1534
|
return teardown
|
|
1354
1535
|
}, [])
|
|
1536
|
+
useCeilingEditSessionOwner()
|
|
1355
1537
|
|
|
1356
1538
|
useEffect(() => {
|
|
1357
1539
|
void useEditor.persist.rehydrate()
|
|
@@ -1361,6 +1543,7 @@ function EditorContent({
|
|
|
1361
1543
|
useEffect(() => {
|
|
1362
1544
|
useViewer.getState().setProjectId(projectId ?? null)
|
|
1363
1545
|
useFloorplanMode.getState().setProjectId(projectId ?? null)
|
|
1546
|
+
resetPaintMode()
|
|
1364
1547
|
|
|
1365
1548
|
return () => {
|
|
1366
1549
|
useViewer.getState().setProjectId(null)
|
|
@@ -1371,9 +1554,13 @@ function EditorContent({
|
|
|
1371
1554
|
// Load on mount, project switches, and explicit retry attempts.
|
|
1372
1555
|
useEffect(() => {
|
|
1373
1556
|
let cancelled = false
|
|
1557
|
+
// The shell stays usable while loading; scene commands must not edit the
|
|
1558
|
+
// unloaded graph. Release synchronously before hydration so its normal
|
|
1559
|
+
// migrations and derived geometry can write, and retain the lock on error.
|
|
1560
|
+
let releaseLoadingLock = acquireSceneReadOnlyLease()
|
|
1374
1561
|
|
|
1375
1562
|
async function load(attempt: number) {
|
|
1376
|
-
|
|
1563
|
+
beginSceneLoad()
|
|
1377
1564
|
setSceneLoadError(null)
|
|
1378
1565
|
setHasLoadedInitialScene(false)
|
|
1379
1566
|
setIsViewerSceneReady(false)
|
|
@@ -1387,7 +1574,11 @@ function EditorContent({
|
|
|
1387
1574
|
try {
|
|
1388
1575
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
1389
1576
|
if (!cancelled && attempt === sceneLoadAttempt) {
|
|
1577
|
+
releaseLoadingLock()
|
|
1390
1578
|
applySceneGraphToEditor(sceneGraph)
|
|
1579
|
+
// The store holds the loaded graph: autosave resumes now, not on a
|
|
1580
|
+
// frame — a tab loaded while hidden never gets one.
|
|
1581
|
+
completeSceneLoad()
|
|
1391
1582
|
setIsViewerSceneReady(false)
|
|
1392
1583
|
setSceneReadyKey((key) => key + 1)
|
|
1393
1584
|
}
|
|
@@ -1396,6 +1587,8 @@ function EditorContent({
|
|
|
1396
1587
|
// state: nothing may be written until a load actually succeeds.
|
|
1397
1588
|
failed = true
|
|
1398
1589
|
if (!cancelled) {
|
|
1590
|
+
releaseLoadingLock()
|
|
1591
|
+
releaseLoadingLock = acquireSceneReadOnlyLease()
|
|
1399
1592
|
console.error('[editor] scene load failed', error)
|
|
1400
1593
|
setSceneLoadError(error ?? new Error('Scene load failed'))
|
|
1401
1594
|
}
|
|
@@ -1404,9 +1597,11 @@ function EditorContent({
|
|
|
1404
1597
|
setIsSceneLoading(false)
|
|
1405
1598
|
if (!failed) {
|
|
1406
1599
|
setHasLoadedInitialScene(true)
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1600
|
+
// A project opens in select mode. Rehydrate already drops the
|
|
1601
|
+
// persisted tool, but a host's rail panel may arm a mode of its
|
|
1602
|
+
// own when it mounts, and with a restored rail tab that mount can
|
|
1603
|
+
// land after rehydrate.
|
|
1604
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
1410
1605
|
}
|
|
1411
1606
|
}
|
|
1412
1607
|
}
|
|
@@ -1416,8 +1611,9 @@ function EditorContent({
|
|
|
1416
1611
|
|
|
1417
1612
|
return () => {
|
|
1418
1613
|
cancelled = true
|
|
1614
|
+
releaseLoadingLock()
|
|
1419
1615
|
}
|
|
1420
|
-
}, [onLoad,
|
|
1616
|
+
}, [onLoad, beginSceneLoad, completeSceneLoad, sceneLoadAttempt])
|
|
1421
1617
|
|
|
1422
1618
|
const retrySceneLoad = useCallback(() => {
|
|
1423
1619
|
setSceneLoadAttempt((attempt) => attempt + 1)
|
|
@@ -1445,13 +1641,6 @@ function EditorContent({
|
|
|
1445
1641
|
if (!isPreviewMode) setPreviewStageMode('3d')
|
|
1446
1642
|
}, [isPreviewMode])
|
|
1447
1643
|
|
|
1448
|
-
useEffect(() => {
|
|
1449
|
-
document.body.classList.add('dark')
|
|
1450
|
-
return () => {
|
|
1451
|
-
document.body.classList.remove('dark')
|
|
1452
|
-
}
|
|
1453
|
-
}, [])
|
|
1454
|
-
|
|
1455
1644
|
const handleSceneReadyChange = useCallback((ready: boolean) => {
|
|
1456
1645
|
setIsViewerSceneReady(ready)
|
|
1457
1646
|
}, [])
|
|
@@ -1469,7 +1658,8 @@ function EditorContent({
|
|
|
1469
1658
|
return () => window.clearTimeout(timer)
|
|
1470
1659
|
}, [hasLoadedInitialScene, isLoading, isSceneLoading, isViewerSceneReady, sceneReadyKey])
|
|
1471
1660
|
|
|
1472
|
-
const
|
|
1661
|
+
const isSceneDataLoading = isLoading || isSceneLoading || !hasLoadedInitialScene
|
|
1662
|
+
const showLoader = isSceneDataLoading || !isViewerSceneReady
|
|
1473
1663
|
const visibleLoader =
|
|
1474
1664
|
showLoader &&
|
|
1475
1665
|
!(
|
|
@@ -1486,10 +1676,13 @@ function EditorContent({
|
|
|
1486
1676
|
|
|
1487
1677
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
|
1488
1678
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
|
1679
|
+
const firstPersonWasPreviewRef = useRef(isPreviewMode)
|
|
1489
1680
|
|
|
1490
1681
|
useEffect(() => {
|
|
1491
1682
|
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
|
1683
|
+
const wasPreviewMode = firstPersonWasPreviewRef.current
|
|
1492
1684
|
wasFirstPersonModeRef.current = isFirstPersonMode
|
|
1685
|
+
firstPersonWasPreviewRef.current = isPreviewMode
|
|
1493
1686
|
|
|
1494
1687
|
if (isFirstPersonMode && !wasFirstPersonMode) {
|
|
1495
1688
|
const viewer = useViewer.getState()
|
|
@@ -1508,7 +1701,8 @@ function EditorContent({
|
|
|
1508
1701
|
firstPersonPreviousLevelRef.current = null
|
|
1509
1702
|
viewer.setWalkthroughMode(false)
|
|
1510
1703
|
|
|
1511
|
-
|
|
1704
|
+
// Leaving Preview has already restored the editor's complete selection.
|
|
1705
|
+
if ((wasPreviewMode && !isPreviewMode) || !previousLevelId) return
|
|
1512
1706
|
|
|
1513
1707
|
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
|
1514
1708
|
if (previousLevelNode?.type === 'level') {
|
|
@@ -1518,39 +1712,56 @@ function EditorContent({
|
|
|
1518
1712
|
selectedIds: [],
|
|
1519
1713
|
})
|
|
1520
1714
|
}
|
|
1521
|
-
}, [isFirstPersonMode])
|
|
1715
|
+
}, [isFirstPersonMode, isPreviewMode])
|
|
1522
1716
|
|
|
1523
1717
|
const previewViewerContent = (
|
|
1524
1718
|
<Viewer
|
|
1525
1719
|
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1526
1720
|
disablePostFx={disablePostFx}
|
|
1527
1721
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1722
|
+
onBreadcrumb={onViewerBreadcrumb}
|
|
1528
1723
|
renderContext="editor"
|
|
1529
|
-
selectionManager="
|
|
1724
|
+
selectionManager="custom"
|
|
1530
1725
|
>
|
|
1531
1726
|
<ExportManager />
|
|
1532
|
-
<
|
|
1727
|
+
<ViewerSelectionManager />
|
|
1728
|
+
<ParametricViewerRooms />
|
|
1533
1729
|
<CeilingSystem />
|
|
1534
1730
|
<RoofEditSystem />
|
|
1535
1731
|
<StairEditSystem />
|
|
1536
1732
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
1537
1733
|
<CustomCameraControls />
|
|
1538
1734
|
<ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />
|
|
1735
|
+
<VectorEdgeExtractor />
|
|
1539
1736
|
<InteractiveSystem />
|
|
1540
1737
|
{presentationsReady ? <ViewerPresentations /> : null}
|
|
1541
1738
|
</Viewer>
|
|
1542
1739
|
)
|
|
1543
1740
|
|
|
1741
|
+
const loadingOverlay = showLoader ? (
|
|
1742
|
+
<div className="absolute inset-0 z-50" data-scene-loading>
|
|
1743
|
+
{sceneLoadError ? (
|
|
1744
|
+
<SceneLoadFailed className="bg-background" onRetry={retrySceneLoad} />
|
|
1745
|
+
) : (
|
|
1746
|
+
(loadingSlot ?? <SceneLoader className="bg-background" />)
|
|
1747
|
+
)}
|
|
1748
|
+
</div>
|
|
1749
|
+
) : null
|
|
1750
|
+
|
|
1544
1751
|
const viewerCanvas = (
|
|
1545
1752
|
<ViewerCanvas
|
|
1546
1753
|
disablePostFx={disablePostFx}
|
|
1754
|
+
hintBelowTopMenu={topMenuShown}
|
|
1547
1755
|
hasLoadedInitialScene={hasLoadedInitialScene}
|
|
1548
1756
|
isFirstPersonMode={isFirstPersonMode}
|
|
1549
1757
|
isLoading={isLoading}
|
|
1758
|
+
isSceneDataLoading={isSceneDataLoading}
|
|
1550
1759
|
isStudioMode={isStudioMode}
|
|
1551
1760
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1761
|
+
loadingOverlay={loadingOverlay}
|
|
1552
1762
|
onSceneReadyChange={handleSceneReadyChange}
|
|
1553
1763
|
onThumbnailCapture={onThumbnailCapture}
|
|
1764
|
+
onViewerBreadcrumb={onViewerBreadcrumb}
|
|
1554
1765
|
presentationsReady={presentationsReady}
|
|
1555
1766
|
sceneReadyKey={sceneReadyKey}
|
|
1556
1767
|
showLoader={showLoader}
|
|
@@ -1605,27 +1816,23 @@ function EditorContent({
|
|
|
1605
1816
|
mobileDefaultSnap: 0.5,
|
|
1606
1817
|
mobileIcon: p.icon,
|
|
1607
1818
|
icon: p.icon,
|
|
1819
|
+
noPanel: p.noPanel,
|
|
1820
|
+
onSelect: p.onSelect,
|
|
1821
|
+
stageActive: p.stageActive,
|
|
1822
|
+
onDeselect: p.onDeselect,
|
|
1608
1823
|
})),
|
|
1609
1824
|
]
|
|
1610
1825
|
|
|
1611
1826
|
return (
|
|
1612
1827
|
<>
|
|
1613
1828
|
<FloorplanModeCoordinator />
|
|
1614
|
-
{visibleLoader && (
|
|
1615
|
-
<div className="fixed inset-0 z-60">
|
|
1616
|
-
{sceneLoadError ? (
|
|
1617
|
-
<SceneLoadFailed className="bg-background" onRetry={retrySceneLoad} />
|
|
1618
|
-
) : (
|
|
1619
|
-
<SceneLoader className="bg-background" />
|
|
1620
|
-
)}
|
|
1621
|
-
</div>
|
|
1622
|
-
)}
|
|
1623
|
-
|
|
1624
1829
|
{!isLoading && isPreviewMode ? (
|
|
1625
1830
|
<PreviewStage
|
|
1626
1831
|
isFirstPersonMode={isFirstPersonMode}
|
|
1832
|
+
loadingOverlay={loadingOverlay}
|
|
1627
1833
|
mode={previewStageMode}
|
|
1628
1834
|
onModeChange={setPreviewStageMode}
|
|
1835
|
+
projectName={projectName}
|
|
1629
1836
|
showLoader={visibleLoader}
|
|
1630
1837
|
viewerContent={previewViewerContent}
|
|
1631
1838
|
/>
|
|
@@ -1635,25 +1842,38 @@ function EditorContent({
|
|
|
1635
1842
|
navbarSlot={navbarSlot}
|
|
1636
1843
|
overlays={
|
|
1637
1844
|
<>
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1845
|
+
<SceneRegion>
|
|
1846
|
+
{!(isCaptureMode || stageOverlay) && <FloatingLevelSelector />}
|
|
1847
|
+
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) &&
|
|
1848
|
+
(dockMenuOnTop ? (
|
|
1849
|
+
// Own row under the toolbar, below the level selector's opening list (z-20).
|
|
1850
|
+
// No transform: the menu's fixed pill must anchor to the scene region.
|
|
1851
|
+
<div className="pointer-events-none absolute inset-x-0 top-14 z-10 flex justify-center">
|
|
1852
|
+
<div className="pointer-events-auto">
|
|
1853
|
+
<ActionMenu inline placement="top" />
|
|
1854
|
+
</div>
|
|
1855
|
+
</div>
|
|
1856
|
+
) : (
|
|
1857
|
+
<div className="pointer-events-auto">
|
|
1858
|
+
<ActionMenu placement={actionMenuPlacement} />
|
|
1859
|
+
</div>
|
|
1860
|
+
))}
|
|
1861
|
+
{sceneOverlay}
|
|
1862
|
+
</SceneRegion>
|
|
1863
|
+
{/* The inspector and the shortcuts card share one right column. */}
|
|
1864
|
+
<RightStack
|
|
1865
|
+
belowTopMenu={topMenuShown}
|
|
1866
|
+
reserveBottomMenu={!dockMenuOnTop}
|
|
1867
|
+
helper={isCaptureMode ? null : <HelperManager />}
|
|
1868
|
+
inspector={
|
|
1869
|
+
isVersionPreviewMode || isCaptureMode || isStudioMode ? null : (
|
|
1870
|
+
<PanelManager
|
|
1871
|
+
inspectorFooter={inspectorFooter}
|
|
1872
|
+
multiSelectionFooter={multiSelectionFooter}
|
|
1873
|
+
/>
|
|
1874
|
+
)
|
|
1875
|
+
}
|
|
1876
|
+
/>
|
|
1657
1877
|
{/* Capture mode drives walk / drone from its own overlay, which
|
|
1658
1878
|
owns the framing chrome — the walkthrough HUD would both
|
|
1659
1879
|
clutter the frame and offer a second, conflicting exit. */}
|
|
@@ -1670,6 +1890,7 @@ function EditorContent({
|
|
|
1670
1890
|
sidebarOverlay={sidebarOverlay}
|
|
1671
1891
|
sidebarTabs={tabBarTabs}
|
|
1672
1892
|
stageOverlay={stageOverlay}
|
|
1893
|
+
viewBar={viewBar}
|
|
1673
1894
|
viewerContent={viewerCanvas}
|
|
1674
1895
|
viewerToolbarLeft={viewerToolbarLeft}
|
|
1675
1896
|
viewerToolbarRight={viewerToolbarRight}
|
|
@@ -1689,23 +1910,15 @@ function EditorContent({
|
|
|
1689
1910
|
const overlayLeft = LAYOUT_PADDING + (isSidebarCollapsed ? 8 : sidebarWidth) + LAYOUT_GAP
|
|
1690
1911
|
|
|
1691
1912
|
return (
|
|
1692
|
-
<div className="
|
|
1913
|
+
<div className="flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1693
1914
|
<FloorplanModeCoordinator />
|
|
1694
|
-
{visibleLoader && (
|
|
1695
|
-
<div className="fixed inset-0 z-60">
|
|
1696
|
-
{sceneLoadError ? (
|
|
1697
|
-
<SceneLoadFailed className="bg-background" onRetry={retrySceneLoad} />
|
|
1698
|
-
) : (
|
|
1699
|
-
<SceneLoader className="bg-background" />
|
|
1700
|
-
)}
|
|
1701
|
-
</div>
|
|
1702
|
-
)}
|
|
1703
|
-
|
|
1704
1915
|
{!isLoading && isPreviewMode ? (
|
|
1705
1916
|
<PreviewStage
|
|
1706
1917
|
isFirstPersonMode={isFirstPersonMode}
|
|
1918
|
+
loadingOverlay={loadingOverlay}
|
|
1707
1919
|
mode={previewStageMode}
|
|
1708
1920
|
onModeChange={setPreviewStageMode}
|
|
1921
|
+
projectName={projectName}
|
|
1709
1922
|
showLoader={visibleLoader}
|
|
1710
1923
|
viewerContent={previewViewerContent}
|
|
1711
1924
|
/>
|
|
@@ -1729,14 +1942,13 @@ function EditorContent({
|
|
|
1729
1942
|
{/* Fixed UI overlays scoped to the viewer area */}
|
|
1730
1943
|
<ViewerOverlays left={overlayLeft}>
|
|
1731
1944
|
<div className="pointer-events-auto">
|
|
1732
|
-
<ActionMenu />
|
|
1733
|
-
</div>
|
|
1734
|
-
<div className="pointer-events-auto">
|
|
1735
|
-
<PanelManager />
|
|
1736
|
-
</div>
|
|
1737
|
-
<div className="pointer-events-auto">
|
|
1738
|
-
<HelperManager />
|
|
1945
|
+
<ActionMenu placement={actionMenuPlacement} />
|
|
1739
1946
|
</div>
|
|
1947
|
+
<RightStack
|
|
1948
|
+
helper={<HelperManager />}
|
|
1949
|
+
inspector={<PanelManager />}
|
|
1950
|
+
reserveBottomMenu={actionMenuPlacement !== 'top' || isMobile}
|
|
1951
|
+
/>
|
|
1740
1952
|
<RiserDiagramPanel />
|
|
1741
1953
|
{isFirstPersonMode && (
|
|
1742
1954
|
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|