@pascal-app/editor 1.0.4 → 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 +39 -37
- package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
- package/src/components/editor/bake-thumbnail.tsx +6 -15
- 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 +81 -8
- package/src/components/editor/editor-layout-mobile.tsx +1 -1
- package/src/components/editor/editor-layout-v2.tsx +1 -1
- package/src/components/editor/export-manager.tsx +14 -19
- package/src/components/editor/first-person/build-collider-world.test.ts +269 -0
- package/src/components/editor/first-person/build-collider-world.ts +22 -5
- 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 +89 -2
- package/src/components/editor/first-person/mechanism-targets.ts +52 -0
- package/src/components/editor/first-person-controls.tsx +19 -11
- package/src/components/editor/floor-aware-camera-controls.test.ts +24 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
- package/src/components/editor/floorplan-panel.tsx +72 -23
- package/src/components/editor/follow-pascal.test.tsx +912 -0
- package/src/components/editor/follow-pascal.tsx +354 -0
- package/src/components/editor/group-actions.ts +2 -0
- 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/index.tsx +166 -79
- package/src/components/editor/riser-diagram-panel.tsx +14 -7
- package/src/components/editor/room-highlight.tsx +13 -5
- package/src/components/editor/room-labels.test.ts +621 -128
- package/src/components/editor/room-labels.tsx +1090 -190
- package/src/components/editor/selection-manager.tsx +38 -117
- package/src/components/editor/slab-hole-highlights.tsx +2 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +3 -2
- package/src/components/editor/snapshot-capture.test.ts +69 -58
- package/src/components/editor/snapshot-capture.ts +7 -1
- package/src/components/editor/thumbnail-generator.test.ts +350 -4
- package/src/components/editor/thumbnail-generator.tsx +128 -54
- package/src/components/editor/use-floorplan-background-placement.ts +3 -1
- package/src/components/editor/views/plugins-view.tsx +1 -8
- package/src/components/editor/views/view-bar.test.tsx +54 -0
- package/src/components/editor/views/view-bar.tsx +140 -17
- package/src/components/editor/wall-snap-beacon-layer.tsx +5 -4
- package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +3 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +1 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +29 -7
- 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/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/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
- package/src/components/systems/roof/roof-edit-system.tsx +1 -0
- 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 +38 -0
- package/src/components/tools/item/placement-strategies.test.ts +46 -1
- package/src/components/tools/item/placement-strategies.ts +3 -6
- package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
- package/src/components/tools/item/use-draft-node.test.tsx +94 -94
- package/src/components/tools/item/use-placement-coordinator.tsx +55 -49
- 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 +63 -29
- package/src/components/tools/tool-manager.tsx +14 -5
- package/src/components/tools/wall/wall-drafting.test.ts +170 -57
- package/src/components/ui/action-menu/action-button.tsx +6 -1
- package/src/components/ui/action-menu/camera-actions.tsx +7 -3
- package/src/components/ui/action-menu/control-modes.tsx +13 -7
- 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 +11 -9
- 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 +10 -3
- package/src/components/ui/command-palette/editor-commands.tsx +3 -12
- package/src/components/ui/command-palette/index.tsx +45 -15
- 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/metric-control.tsx +1 -1
- package/src/components/ui/controls/segmented-control.tsx +21 -8
- package/src/components/ui/controls/select-control.tsx +1 -1
- 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 +150 -45
- package/src/components/ui/helpers/contextual-helper-panel.tsx +32 -7
- package/src/components/ui/helpers/helper-manager.test.tsx +54 -2
- package/src/components/ui/helpers/helper-manager.tsx +7 -1
- package/src/components/ui/helpers/item-helper.tsx +9 -1
- package/src/components/ui/panels/collections/collections-popover.tsx +8 -8
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +6 -3
- package/src/components/ui/panels/multi-selection-panel.tsx +120 -3
- package/src/components/ui/panels/panel-wrapper.tsx +53 -27
- package/src/components/ui/panels/parametric-field-control.tsx +2 -2
- package/src/components/ui/panels/parametric-inspector.tsx +3 -3
- package/src/components/ui/panels/room-panel.tsx +65 -0
- package/src/components/ui/primitives/badge.tsx +8 -0
- package/src/components/ui/primitives/sidebar.tsx +1 -1
- package/src/components/ui/right-stack.tsx +21 -2
- package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
- package/src/components/ui/sidebar/icon-rail.tsx +3 -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 +107 -23
- package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
- 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/index.tsx +5 -12
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -10
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -2
- package/src/components/ui/sidebar/tab-bar.tsx +4 -4
- package/src/components/viewer/viewer-controls-bar.tsx +18 -6
- package/src/components/viewer/viewer-rooms.tsx +16 -0
- package/src/hooks/use-auto-save.integration.test.tsx +96 -38
- package/src/hooks/use-auto-save.ts +26 -1
- 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 +65 -16
- package/src/hooks/use-keyboard.ts +29 -12
- package/src/hooks/use-placement-notice.test.ts +24 -1
- package/src/hooks/use-placement-notice.ts +37 -6
- package/src/index.tsx +71 -24
- 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/direct-manipulation.ts +2 -0
- package/src/lib/download-project-file.ts +17 -0
- package/src/lib/editor-level-display.ts +9 -2
- package/src/lib/editor-views.ts +35 -1
- package/src/lib/export-texture-utils.ts +2 -4
- package/src/lib/floor-foundation-support.test.ts +1 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +31 -16
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +39 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +4 -0
- package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
- package/src/lib/floorplan/sheet-export.test.ts +24 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +34 -45
- package/src/lib/floorplan/site-plan/coverage.ts +2 -4
- package/src/lib/floorplan/site-plan/front-edge.ts +2 -1
- package/src/lib/floorplan/site-plan/geometry.test.ts +9 -15
- package/src/lib/floorplan/site-plan/geometry.ts +1 -416
- package/src/lib/floorplan/site-plan/index.ts +0 -16
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +117 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -2
- package/src/lib/floorplan/site-plan/site-parts.ts +14 -106
- package/src/lib/floorplan/site-plan/yards.test.ts +2 -1
- 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 +112 -0
- package/src/lib/fresh-planar-placement.ts +57 -3
- package/src/lib/geometry-script/author.ts +6 -1
- package/src/lib/geometry-script/client.ts +8 -1
- package/src/lib/geometry-script/finish-scripts.ts +127 -0
- package/src/lib/geometry-script/protocol.ts +1 -0
- package/src/lib/glb-export.test.ts +187 -162
- package/src/lib/glb-export.ts +4 -1
- package/src/lib/history.test.ts +12 -7
- package/src/lib/interaction/scope.ts +4 -0
- package/src/lib/level-selection.test.ts +57 -0
- package/src/lib/level-selection.ts +16 -1
- package/src/lib/lot/clean-ring.test.ts +2 -1
- package/src/lib/lot/drop-in.ts +9 -24
- package/src/lib/lot/lot-patch.test.ts +1 -1
- package/src/lib/lot/lot-patch.ts +2 -2
- package/src/lib/measurement-kind.test.ts +6 -8
- package/src/lib/measurements.test.ts +23 -0
- package/src/lib/orbit-floor-clearance.test.ts +36 -0
- package/src/lib/orbit-floor-clearance.ts +48 -0
- package/src/lib/paint-pass-through.test.ts +11 -0
- package/src/lib/paint-pass-through.ts +4 -2
- package/src/lib/paint-scope.ts +1 -0
- package/src/lib/parametric-node-update.ts +16 -7
- package/src/lib/plugin-chat.ts +15 -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/room-assembly-overlay.ts +2 -3
- package/src/lib/room-selection.test.ts +45 -1
- package/src/lib/scene-clipboard.test.ts +246 -1
- package/src/lib/scene-clipboard.ts +44 -14
- package/src/lib/selection-routing.ts +1 -1
- 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/stair-levels.test.ts +25 -0
- package/src/lib/stair-levels.ts +22 -3
- package/src/lib/terrace-draft.ts +2 -1
- package/src/lib/viewer-selection.ts +22 -11
- package/src/lib/wall-placement-pointer.test.ts +42 -0
- package/src/lib/wall-placement-pointer.ts +57 -0
- package/src/lib/window-changes.ts +35 -0
- 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-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-stair-build-preview.ts +25 -5
- package/src/store/view-layout.test.ts +27 -1
- package/src/store/view-layout.ts +4 -1
- package/src/test-utils/render-current-markup.tsx +16 -0
- 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 -15
- package/src/components/tools/stair/stair-tool.tsx +0 -682
|
@@ -19,6 +19,7 @@ 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'
|
|
@@ -38,11 +39,14 @@ import { ViewerOverlay } from '../../components/viewer-overlay'
|
|
|
38
39
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
|
39
40
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
40
41
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
42
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
41
43
|
import { useSaveShortcut } from '../../hooks/use-save-shortcut'
|
|
42
44
|
import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
|
|
43
45
|
import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
|
|
44
46
|
import { isSceneView } from '../../lib/editor-views'
|
|
47
|
+
import { initFinishScriptRefresh } from '../../lib/geometry-script/finish-scripts'
|
|
45
48
|
import { useGestureLifecycleOwner } from '../../lib/gesture-lifecycle'
|
|
49
|
+
import { shouldShowEditingControls } from '../../lib/interaction/overlay-policy'
|
|
46
50
|
import {
|
|
47
51
|
createLocalProjectPresentationPersistence,
|
|
48
52
|
type LocalProjectPresentationPersistence,
|
|
@@ -58,12 +62,14 @@ import {
|
|
|
58
62
|
} from '../../lib/scene'
|
|
59
63
|
import { disposeSFXBus, initSFXBus } from '../../lib/sfx-bus'
|
|
60
64
|
import { useUnitFocusRules } from '../../lib/units'
|
|
65
|
+
import { cn } from '../../lib/utils'
|
|
61
66
|
import { useViewerFloorDisplay } from '../../lib/viewer-selection'
|
|
62
67
|
import { type CameraHintAction, useCameraHintFocus } from '../../store/use-camera-hint-focus'
|
|
63
68
|
import useEditor from '../../store/use-editor'
|
|
64
69
|
import useFloorplanMode from '../../store/use-floorplan-mode'
|
|
65
70
|
import useSessionGroups from '../../store/use-session-groups'
|
|
66
71
|
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
72
|
+
import { PointAskLayer } from '../point-ask/point-ask-layer'
|
|
67
73
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
68
74
|
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
69
75
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
@@ -73,6 +79,7 @@ import { ZoneSystem } from '../systems/zone/zone-system'
|
|
|
73
79
|
import { BoxSelectTool } from '../tools/select/box-select-tool'
|
|
74
80
|
import { ToolManager } from '../tools/tool-manager'
|
|
75
81
|
import { ActionMenu } from '../ui/action-menu'
|
|
82
|
+
import { type ActionMenuPlacement, TOP_MENU_CLEARANCE } from '../ui/action-menu/placement'
|
|
76
83
|
import { CommandPalette, type CommandPaletteEmptyAction } from '../ui/command-palette'
|
|
77
84
|
import { EditorCommands } from '../ui/command-palette/editor-commands'
|
|
78
85
|
import { FloatingLevelSelector } from '../ui/floating-level-selector'
|
|
@@ -105,6 +112,7 @@ import { FloatingActionMenu } from './floating-action-menu'
|
|
|
105
112
|
import { FloatingBuildingActionMenu } from './floating-building-action-menu'
|
|
106
113
|
import { FloorplanModeCoordinator } from './floorplan-mode-coordinator'
|
|
107
114
|
import { FloorplanPanel } from './floorplan-panel'
|
|
115
|
+
import { FollowPascal } from './follow-pascal'
|
|
108
116
|
import { FootprintHeightHandle } from './footprint-height-handle'
|
|
109
117
|
import { Grid } from './grid'
|
|
110
118
|
import { GroupFloatingActionMenu } from './group-floating-action-menu'
|
|
@@ -156,6 +164,8 @@ const EDITOR_HOVER_STYLES: HoverStyles = {
|
|
|
156
164
|
'paint-ready': { visibleColor: 0xf5_9e_0b, hiddenColor: 0xfd_e0_68, strength: 5, pulse: true },
|
|
157
165
|
// Erasing paints nothing: a neutral outline, no paint colour.
|
|
158
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 },
|
|
159
169
|
'paint-disabled': {
|
|
160
170
|
visibleColor: 0x94_a3_b8,
|
|
161
171
|
hiddenColor: 0x47_55_69,
|
|
@@ -175,11 +185,13 @@ const EDITOR_DEFAULT_RENDER = { shading: 'solid' } as const
|
|
|
175
185
|
function initializeEditorRuntime(): () => void {
|
|
176
186
|
const unsubscribeSpatialGrid = initSpatialGridSync()
|
|
177
187
|
const unsubscribeSpaceDetection = initSpaceDetectionSync(useScene, useEditor)
|
|
188
|
+
const stopFinishScriptRefresh = initFinishScriptRefresh()
|
|
178
189
|
initSFXBus()
|
|
179
190
|
|
|
180
191
|
return () => {
|
|
181
192
|
unsubscribeSpatialGrid()
|
|
182
193
|
unsubscribeSpaceDetection?.()
|
|
194
|
+
stopFinishScriptRefresh()
|
|
183
195
|
|
|
184
196
|
spatialGridManager.clear()
|
|
185
197
|
disposeSFXBus()
|
|
@@ -192,6 +204,8 @@ function initializeEditorRuntime(): () => void {
|
|
|
192
204
|
export interface EditorProps {
|
|
193
205
|
// Layout version — 'v1' (default) or 'v2' (navbar + two-column)
|
|
194
206
|
layoutVersion?: 'v1' | 'v2'
|
|
207
|
+
// Viewport edge the tool menu docks to (desktop only; mobile stays at the bottom)
|
|
208
|
+
actionMenuPlacement?: ActionMenuPlacement
|
|
195
209
|
|
|
196
210
|
// UI slots (v1)
|
|
197
211
|
appMenuButton?: ReactNode
|
|
@@ -257,13 +271,23 @@ export interface EditorProps {
|
|
|
257
271
|
// Loading indicator (e.g. project fetching in community mode)
|
|
258
272
|
isLoading?: boolean
|
|
259
273
|
|
|
260
|
-
|
|
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
|
|
261
282
|
// open-to-interactive time without reaching into internal loader state.
|
|
262
283
|
onLoaderChange?: (visible: boolean) => void
|
|
263
284
|
|
|
264
285
|
// Thumbnail
|
|
265
286
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
266
287
|
|
|
288
|
+
/** Viewer diagnostics for the host's error-reporter trail; see `<Viewer onBreadcrumb>`. */
|
|
289
|
+
onViewerBreadcrumb?: ViewerBreadcrumbHandler
|
|
290
|
+
|
|
267
291
|
/**
|
|
268
292
|
* When true, skip the viewer post-FX pipeline (same as `?disable=postFx`).
|
|
269
293
|
* Hosts use this for a stable local "light preview" without relying only on
|
|
@@ -576,7 +600,13 @@ function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
|
576
600
|
)
|
|
577
601
|
}
|
|
578
602
|
|
|
579
|
-
function ViewerCanvasControlsHint({
|
|
603
|
+
function ViewerCanvasControlsHint({
|
|
604
|
+
belowTopMenu = false,
|
|
605
|
+
onDismiss,
|
|
606
|
+
}: {
|
|
607
|
+
belowTopMenu?: boolean
|
|
608
|
+
onDismiss: () => void
|
|
609
|
+
}) {
|
|
580
610
|
const all = CAMERA_CONTROL_HINTS
|
|
581
611
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
582
612
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
@@ -588,7 +618,13 @@ function ViewerCanvasControlsHint({ onDismiss }: { onDismiss: () => void }) {
|
|
|
588
618
|
}
|
|
589
619
|
|
|
590
620
|
return (
|
|
591
|
-
<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
|
+
>
|
|
592
628
|
<section
|
|
593
629
|
aria-label="Camera controls hint"
|
|
594
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"
|
|
@@ -877,6 +913,7 @@ const ViewerSceneContent = memo(function ViewerSceneContent({
|
|
|
877
913
|
{isFirstPersonMode && <FirstPersonControls />}
|
|
878
914
|
{isCaptureMode && !isXRMode && <CaptureCameraRig />}
|
|
879
915
|
{!isXRMode && <CustomCameraControls />}
|
|
916
|
+
{!isXRMode && <FollowPascal />}
|
|
880
917
|
{!isXRMode && <ThumbnailGenerator onThumbnailCapture={onThumbnailCapture} />}
|
|
881
918
|
{!isXRMode && <VectorEdgeExtractor />}
|
|
882
919
|
{!(isFirstPersonMode || isXRMode) && <SiteEdgeLabels />}
|
|
@@ -1077,14 +1114,18 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1077
1114
|
isStudioMode,
|
|
1078
1115
|
hasLoadedInitialScene,
|
|
1079
1116
|
showLoader,
|
|
1117
|
+
isSceneDataLoading,
|
|
1118
|
+
loadingOverlay,
|
|
1080
1119
|
sceneReadyKey,
|
|
1081
1120
|
onSceneReadyChange,
|
|
1082
1121
|
onThumbnailCapture,
|
|
1122
|
+
onViewerBreadcrumb,
|
|
1083
1123
|
presentationsReady,
|
|
1084
1124
|
viewerSceneSlot,
|
|
1085
1125
|
floorplanSceneSlot,
|
|
1086
1126
|
disablePostFx = false,
|
|
1087
1127
|
immersive,
|
|
1128
|
+
hintBelowTopMenu = false,
|
|
1088
1129
|
}: {
|
|
1089
1130
|
isVersionPreviewMode: boolean
|
|
1090
1131
|
isLoading: boolean
|
|
@@ -1092,14 +1133,18 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1092
1133
|
isStudioMode: boolean
|
|
1093
1134
|
hasLoadedInitialScene: boolean
|
|
1094
1135
|
showLoader: boolean
|
|
1136
|
+
isSceneDataLoading: boolean
|
|
1137
|
+
loadingOverlay: ReactNode
|
|
1095
1138
|
sceneReadyKey: number
|
|
1096
1139
|
onSceneReadyChange: (ready: boolean) => void
|
|
1097
1140
|
onThumbnailCapture?: (blob: Blob, cameraData: SnapshotCameraData) => void
|
|
1141
|
+
onViewerBreadcrumb?: ViewerBreadcrumbHandler
|
|
1098
1142
|
presentationsReady: boolean
|
|
1099
1143
|
viewerSceneSlot?: ReactNode
|
|
1100
1144
|
floorplanSceneSlot?: ReactNode
|
|
1101
1145
|
disablePostFx?: boolean
|
|
1102
1146
|
immersive?: ViewerImmersiveSession
|
|
1147
|
+
hintBelowTopMenu?: boolean
|
|
1103
1148
|
}) {
|
|
1104
1149
|
const layout = useActiveViewLayout()
|
|
1105
1150
|
const views = useEditorViews()
|
|
@@ -1204,18 +1249,22 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1204
1249
|
>
|
|
1205
1250
|
{/* 2D floorplan — always mounted once shown, hidden via CSS to preserve state */}
|
|
1206
1251
|
<div
|
|
1252
|
+
aria-busy={isSceneDataLoading}
|
|
1207
1253
|
className="relative min-w-0 overflow-hidden"
|
|
1254
|
+
data-pascal-viewer-2d
|
|
1208
1255
|
data-view-pane={pane2d ?? undefined}
|
|
1209
1256
|
onPointerDownCapture={pane2d === null ? undefined : focusPane(pane2d)}
|
|
1210
1257
|
style={paneStyle(pane2d)}
|
|
1211
1258
|
>
|
|
1212
|
-
<div className="h-full w-full overflow-hidden">
|
|
1259
|
+
<div className="relative isolate h-full w-full overflow-hidden">
|
|
1213
1260
|
<FloorplanPanel compassHost={viewerAreaEl} floorplanSceneSlot={floorplanSceneSlot} />
|
|
1214
1261
|
</div>
|
|
1262
|
+
{isSceneDataLoading && loadingOverlay}
|
|
1215
1263
|
</div>
|
|
1216
1264
|
|
|
1217
1265
|
{/* 3D viewer — always mounted, hidden via CSS to avoid destroying the WebGL context */}
|
|
1218
1266
|
<div
|
|
1267
|
+
aria-busy={showLoader}
|
|
1219
1268
|
className="relative min-w-0 overflow-hidden"
|
|
1220
1269
|
data-pascal-viewer-3d
|
|
1221
1270
|
data-view-pane={pane3d ?? undefined}
|
|
@@ -1223,47 +1272,55 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1223
1272
|
ref={viewer3dRef}
|
|
1224
1273
|
style={paneStyle(pane3d)}
|
|
1225
1274
|
>
|
|
1226
|
-
<
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
/>
|
|
1234
|
-
{!showLoader && isCameraControlsHintVisible && !isFirstPersonMode ? (
|
|
1235
|
-
<ViewerCanvasControlsHint onDismiss={dismissCameraControlsHint} />
|
|
1236
|
-
) : null}
|
|
1237
|
-
<SelectionPersistenceManager enabled={hasLoadedInitialScene && !showLoader} />
|
|
1238
|
-
<Viewer
|
|
1239
|
-
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1240
|
-
disablePostFx={disablePostFx}
|
|
1241
|
-
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1242
|
-
isolate={presetIsolation}
|
|
1243
|
-
// Preset captures isolate one subtree and keep the exterior transparent.
|
|
1244
|
-
// Other modes retain the viewer's configured background policy.
|
|
1245
|
-
transparent={presetIsolation === null ? undefined : true}
|
|
1246
|
-
onSceneReadyChange={onSceneReadyChange}
|
|
1247
|
-
renderContext="editor"
|
|
1248
|
-
renderPaused={!show3d && !showLoader}
|
|
1249
|
-
sceneReadyKey={sceneReadyKey}
|
|
1250
|
-
immersive={immersive}
|
|
1251
|
-
// Walk/drone framing during snapshot capture is camera-only: the
|
|
1252
|
-
// viewer's default selection manager would hover-highlight whatever
|
|
1253
|
-
// the cursor crosses, which orbit capture never does.
|
|
1254
|
-
selectionManager={isFirstPersonMode && !isCaptureMode ? 'default' : 'custom'}
|
|
1255
|
-
>
|
|
1256
|
-
<ViewerSceneContent
|
|
1257
|
-
isFirstPersonMode={isFirstPersonMode}
|
|
1258
|
-
isLoading={showLoader}
|
|
1259
|
-
isXRMode={immersive != null}
|
|
1260
|
-
isStudioMode={isStudioMode}
|
|
1275
|
+
<div className="relative isolate h-full">
|
|
1276
|
+
<DeleteCursorLayer
|
|
1277
|
+
containerRef={viewer3dRef}
|
|
1278
|
+
isVersionPreviewMode={isVersionPreviewMode}
|
|
1279
|
+
/>
|
|
1280
|
+
<PaintCursorLayer
|
|
1281
|
+
containerRef={viewer3dRef}
|
|
1261
1282
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1262
|
-
onThumbnailCapture={onThumbnailCapture}
|
|
1263
|
-
presentationsReady={presentationsReady}
|
|
1264
|
-
viewerSceneSlot={viewerSceneSlot}
|
|
1265
1283
|
/>
|
|
1266
|
-
|
|
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>
|
|
1322
|
+
</div>
|
|
1323
|
+
{showLoader && loadingOverlay}
|
|
1267
1324
|
</div>
|
|
1268
1325
|
|
|
1269
1326
|
{hostPanes.map((pane) => {
|
|
@@ -1273,6 +1330,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1273
1330
|
className="relative min-w-0 overflow-hidden"
|
|
1274
1331
|
data-view-pane={pane}
|
|
1275
1332
|
key={viewId}
|
|
1333
|
+
onFocusCapture={focusPane(pane)}
|
|
1276
1334
|
onPointerDownCapture={focusPane(pane)}
|
|
1277
1335
|
style={paneStyle(pane)}
|
|
1278
1336
|
>
|
|
@@ -1302,6 +1360,7 @@ function PreviewStage({
|
|
|
1302
1360
|
onModeChange,
|
|
1303
1361
|
projectName,
|
|
1304
1362
|
showLoader,
|
|
1363
|
+
loadingOverlay,
|
|
1305
1364
|
viewerContent,
|
|
1306
1365
|
}: {
|
|
1307
1366
|
isFirstPersonMode: boolean
|
|
@@ -1309,6 +1368,7 @@ function PreviewStage({
|
|
|
1309
1368
|
onModeChange: (mode: ViewerStageMode) => void
|
|
1310
1369
|
projectName?: string | null
|
|
1311
1370
|
showLoader: boolean
|
|
1371
|
+
loadingOverlay: ReactNode
|
|
1312
1372
|
viewerContent: ReactNode
|
|
1313
1373
|
}) {
|
|
1314
1374
|
const hasFloorplan = useScene((state) =>
|
|
@@ -1350,6 +1410,7 @@ function PreviewStage({
|
|
|
1350
1410
|
switcherClassName={`${PREVIEW_STAGE_SWITCHER_POSITION} ${showLoader ? 'z-[70]' : ''}`}
|
|
1351
1411
|
>
|
|
1352
1412
|
{viewerContent}
|
|
1413
|
+
{showLoader && loadingOverlay}
|
|
1353
1414
|
</ViewerStage>
|
|
1354
1415
|
</div>
|
|
1355
1416
|
)
|
|
@@ -1358,6 +1419,7 @@ function PreviewStage({
|
|
|
1358
1419
|
function EditorContent({
|
|
1359
1420
|
guardAgainstSceneWipe,
|
|
1360
1421
|
layoutVersion = 'v1',
|
|
1422
|
+
actionMenuPlacement,
|
|
1361
1423
|
appMenuButton,
|
|
1362
1424
|
sidebarTop,
|
|
1363
1425
|
navbarSlot,
|
|
@@ -1380,8 +1442,10 @@ function EditorContent({
|
|
|
1380
1442
|
previewScene,
|
|
1381
1443
|
isVersionPreviewMode = false,
|
|
1382
1444
|
isLoading = false,
|
|
1445
|
+
loadingSlot,
|
|
1383
1446
|
onLoaderChange,
|
|
1384
1447
|
onThumbnailCapture,
|
|
1448
|
+
onViewerBreadcrumb,
|
|
1385
1449
|
disablePostFx = false,
|
|
1386
1450
|
immersive,
|
|
1387
1451
|
sidebarOverlay,
|
|
@@ -1392,6 +1456,9 @@ function EditorContent({
|
|
|
1392
1456
|
extraSidebarPanels,
|
|
1393
1457
|
commandPaletteEmptyAction,
|
|
1394
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
|
|
1395
1462
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
1396
1463
|
const isStudioMode = useEditor((s) => s.workspaceMode === 'studio')
|
|
1397
1464
|
const presentationProjectId = projectId ?? null
|
|
@@ -1446,6 +1513,12 @@ function EditorContent({
|
|
|
1446
1513
|
const [previewStageMode, setPreviewStageMode] = useState<ViewerStageMode>('3d')
|
|
1447
1514
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1448
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)
|
|
1449
1522
|
|
|
1450
1523
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
|
1451
1524
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
@@ -1481,6 +1554,10 @@ function EditorContent({
|
|
|
1481
1554
|
// Load on mount, project switches, and explicit retry attempts.
|
|
1482
1555
|
useEffect(() => {
|
|
1483
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()
|
|
1484
1561
|
|
|
1485
1562
|
async function load(attempt: number) {
|
|
1486
1563
|
beginSceneLoad()
|
|
@@ -1497,6 +1574,7 @@ function EditorContent({
|
|
|
1497
1574
|
try {
|
|
1498
1575
|
const sceneGraph = onLoad ? await onLoad() : loadSceneFromLocalStorage()
|
|
1499
1576
|
if (!cancelled && attempt === sceneLoadAttempt) {
|
|
1577
|
+
releaseLoadingLock()
|
|
1500
1578
|
applySceneGraphToEditor(sceneGraph)
|
|
1501
1579
|
// The store holds the loaded graph: autosave resumes now, not on a
|
|
1502
1580
|
// frame — a tab loaded while hidden never gets one.
|
|
@@ -1509,6 +1587,8 @@ function EditorContent({
|
|
|
1509
1587
|
// state: nothing may be written until a load actually succeeds.
|
|
1510
1588
|
failed = true
|
|
1511
1589
|
if (!cancelled) {
|
|
1590
|
+
releaseLoadingLock()
|
|
1591
|
+
releaseLoadingLock = acquireSceneReadOnlyLease()
|
|
1512
1592
|
console.error('[editor] scene load failed', error)
|
|
1513
1593
|
setSceneLoadError(error ?? new Error('Scene load failed'))
|
|
1514
1594
|
}
|
|
@@ -1531,6 +1611,7 @@ function EditorContent({
|
|
|
1531
1611
|
|
|
1532
1612
|
return () => {
|
|
1533
1613
|
cancelled = true
|
|
1614
|
+
releaseLoadingLock()
|
|
1534
1615
|
}
|
|
1535
1616
|
}, [onLoad, beginSceneLoad, completeSceneLoad, sceneLoadAttempt])
|
|
1536
1617
|
|
|
@@ -1560,13 +1641,6 @@ function EditorContent({
|
|
|
1560
1641
|
if (!isPreviewMode) setPreviewStageMode('3d')
|
|
1561
1642
|
}, [isPreviewMode])
|
|
1562
1643
|
|
|
1563
|
-
useEffect(() => {
|
|
1564
|
-
document.body.classList.add('dark')
|
|
1565
|
-
return () => {
|
|
1566
|
-
document.body.classList.remove('dark')
|
|
1567
|
-
}
|
|
1568
|
-
}, [])
|
|
1569
|
-
|
|
1570
1644
|
const handleSceneReadyChange = useCallback((ready: boolean) => {
|
|
1571
1645
|
setIsViewerSceneReady(ready)
|
|
1572
1646
|
}, [])
|
|
@@ -1584,7 +1658,8 @@ function EditorContent({
|
|
|
1584
1658
|
return () => window.clearTimeout(timer)
|
|
1585
1659
|
}, [hasLoadedInitialScene, isLoading, isSceneLoading, isViewerSceneReady, sceneReadyKey])
|
|
1586
1660
|
|
|
1587
|
-
const
|
|
1661
|
+
const isSceneDataLoading = isLoading || isSceneLoading || !hasLoadedInitialScene
|
|
1662
|
+
const showLoader = isSceneDataLoading || !isViewerSceneReady
|
|
1588
1663
|
const visibleLoader =
|
|
1589
1664
|
showLoader &&
|
|
1590
1665
|
!(
|
|
@@ -1644,6 +1719,7 @@ function EditorContent({
|
|
|
1644
1719
|
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
1645
1720
|
disablePostFx={disablePostFx}
|
|
1646
1721
|
hoverStyles={EDITOR_HOVER_STYLES}
|
|
1722
|
+
onBreadcrumb={onViewerBreadcrumb}
|
|
1647
1723
|
renderContext="editor"
|
|
1648
1724
|
selectionManager="custom"
|
|
1649
1725
|
>
|
|
@@ -1662,16 +1738,30 @@ function EditorContent({
|
|
|
1662
1738
|
</Viewer>
|
|
1663
1739
|
)
|
|
1664
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
|
+
|
|
1665
1751
|
const viewerCanvas = (
|
|
1666
1752
|
<ViewerCanvas
|
|
1667
1753
|
disablePostFx={disablePostFx}
|
|
1754
|
+
hintBelowTopMenu={topMenuShown}
|
|
1668
1755
|
hasLoadedInitialScene={hasLoadedInitialScene}
|
|
1669
1756
|
isFirstPersonMode={isFirstPersonMode}
|
|
1670
1757
|
isLoading={isLoading}
|
|
1758
|
+
isSceneDataLoading={isSceneDataLoading}
|
|
1671
1759
|
isStudioMode={isStudioMode}
|
|
1672
1760
|
isVersionPreviewMode={isVersionPreviewMode}
|
|
1761
|
+
loadingOverlay={loadingOverlay}
|
|
1673
1762
|
onSceneReadyChange={handleSceneReadyChange}
|
|
1674
1763
|
onThumbnailCapture={onThumbnailCapture}
|
|
1764
|
+
onViewerBreadcrumb={onViewerBreadcrumb}
|
|
1675
1765
|
presentationsReady={presentationsReady}
|
|
1676
1766
|
sceneReadyKey={sceneReadyKey}
|
|
1677
1767
|
showLoader={showLoader}
|
|
@@ -1736,19 +1826,10 @@ function EditorContent({
|
|
|
1736
1826
|
return (
|
|
1737
1827
|
<>
|
|
1738
1828
|
<FloorplanModeCoordinator />
|
|
1739
|
-
{visibleLoader && (
|
|
1740
|
-
<div className="fixed inset-0 z-60">
|
|
1741
|
-
{sceneLoadError ? (
|
|
1742
|
-
<SceneLoadFailed className="bg-background" onRetry={retrySceneLoad} />
|
|
1743
|
-
) : (
|
|
1744
|
-
<SceneLoader className="bg-background" />
|
|
1745
|
-
)}
|
|
1746
|
-
</div>
|
|
1747
|
-
)}
|
|
1748
|
-
|
|
1749
1829
|
{!isLoading && isPreviewMode ? (
|
|
1750
1830
|
<PreviewStage
|
|
1751
1831
|
isFirstPersonMode={isFirstPersonMode}
|
|
1832
|
+
loadingOverlay={loadingOverlay}
|
|
1752
1833
|
mode={previewStageMode}
|
|
1753
1834
|
onModeChange={setPreviewStageMode}
|
|
1754
1835
|
projectName={projectName}
|
|
@@ -1763,15 +1844,26 @@ function EditorContent({
|
|
|
1763
1844
|
<>
|
|
1764
1845
|
<SceneRegion>
|
|
1765
1846
|
{!(isCaptureMode || stageOverlay) && <FloatingLevelSelector />}
|
|
1766
|
-
{!(isVersionPreviewMode || isCaptureMode || isStudioMode) &&
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
+
))}
|
|
1771
1861
|
{sceneOverlay}
|
|
1772
1862
|
</SceneRegion>
|
|
1773
1863
|
{/* The inspector and the shortcuts card share one right column. */}
|
|
1774
1864
|
<RightStack
|
|
1865
|
+
belowTopMenu={topMenuShown}
|
|
1866
|
+
reserveBottomMenu={!dockMenuOnTop}
|
|
1775
1867
|
helper={isCaptureMode ? null : <HelperManager />}
|
|
1776
1868
|
inspector={
|
|
1777
1869
|
isVersionPreviewMode || isCaptureMode || isStudioMode ? null : (
|
|
@@ -1818,21 +1910,12 @@ function EditorContent({
|
|
|
1818
1910
|
const overlayLeft = LAYOUT_PADDING + (isSidebarCollapsed ? 8 : sidebarWidth) + LAYOUT_GAP
|
|
1819
1911
|
|
|
1820
1912
|
return (
|
|
1821
|
-
<div className="
|
|
1913
|
+
<div className="flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1822
1914
|
<FloorplanModeCoordinator />
|
|
1823
|
-
{visibleLoader && (
|
|
1824
|
-
<div className="fixed inset-0 z-60">
|
|
1825
|
-
{sceneLoadError ? (
|
|
1826
|
-
<SceneLoadFailed className="bg-background" onRetry={retrySceneLoad} />
|
|
1827
|
-
) : (
|
|
1828
|
-
<SceneLoader className="bg-background" />
|
|
1829
|
-
)}
|
|
1830
|
-
</div>
|
|
1831
|
-
)}
|
|
1832
|
-
|
|
1833
1915
|
{!isLoading && isPreviewMode ? (
|
|
1834
1916
|
<PreviewStage
|
|
1835
1917
|
isFirstPersonMode={isFirstPersonMode}
|
|
1918
|
+
loadingOverlay={loadingOverlay}
|
|
1836
1919
|
mode={previewStageMode}
|
|
1837
1920
|
onModeChange={setPreviewStageMode}
|
|
1838
1921
|
projectName={projectName}
|
|
@@ -1859,9 +1942,13 @@ function EditorContent({
|
|
|
1859
1942
|
{/* Fixed UI overlays scoped to the viewer area */}
|
|
1860
1943
|
<ViewerOverlays left={overlayLeft}>
|
|
1861
1944
|
<div className="pointer-events-auto">
|
|
1862
|
-
<ActionMenu />
|
|
1945
|
+
<ActionMenu placement={actionMenuPlacement} />
|
|
1863
1946
|
</div>
|
|
1864
|
-
<RightStack
|
|
1947
|
+
<RightStack
|
|
1948
|
+
helper={<HelperManager />}
|
|
1949
|
+
inspector={<PanelManager />}
|
|
1950
|
+
reserveBottomMenu={actionMenuPlacement !== 'top' || isMobile}
|
|
1951
|
+
/>
|
|
1865
1952
|
<RiserDiagramPanel />
|
|
1866
1953
|
{isFirstPersonMode && (
|
|
1867
1954
|
<FirstPersonOverlay onExit={() => useEditor.getState().setFirstPersonMode(false)} />
|
|
@@ -6,8 +6,10 @@ import { X } from 'lucide-react'
|
|
|
6
6
|
import { useMemo } from 'react'
|
|
7
7
|
import useEditor from '../../store/use-editor'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Set per theme on the panel root; the light values are darker so the 9-unit
|
|
10
|
+
// diameter labels read on white.
|
|
11
|
+
const WASTE_COLOR = 'var(--riser-waste)'
|
|
12
|
+
const VENT_COLOR = 'var(--riser-vent)'
|
|
11
13
|
const MARKER_COLOR = '#1e293b'
|
|
12
14
|
const PADDING = 32
|
|
13
15
|
/** Meters → SVG units. The iso projection is in meters; scale up so a
|
|
@@ -45,14 +47,14 @@ function RiserDiagramContent() {
|
|
|
45
47
|
const ty = diagram ? -diagram.bounds.minY * SCALE + PADDING : 0
|
|
46
48
|
|
|
47
49
|
return (
|
|
48
|
-
<div className="
|
|
50
|
+
<div className="pointer-events-auto absolute top-4 right-4 z-30 flex max-h-[80vh] w-[26rem] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 text-foreground shadow-lg backdrop-blur-xl [--riser-vent:#9333ea] [--riser-waste:#0369a1] dark:[--riser-vent:#a855f7] dark:[--riser-waste:#0ea5e9]">
|
|
49
51
|
<div className="flex items-center justify-between border-border/40 border-b px-4 py-2.5">
|
|
50
52
|
<div className="flex flex-col">
|
|
51
53
|
<span className="font-medium text-sm">Riser Diagram</span>
|
|
52
54
|
<span className="text-muted-foreground text-xs">DWV plumbing isometric</span>
|
|
53
55
|
</div>
|
|
54
56
|
<button
|
|
55
|
-
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
57
|
+
className="flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/6 dark:hover:bg-white/10"
|
|
56
58
|
onClick={() => setRiserOpen(false)}
|
|
57
59
|
>
|
|
58
60
|
<X className="h-4 w-4 text-muted-foreground" />
|
|
@@ -87,18 +89,18 @@ function RiserDiagramContent() {
|
|
|
87
89
|
<line
|
|
88
90
|
className="cursor-pointer"
|
|
89
91
|
onClick={() => select(line.nodeId)}
|
|
90
|
-
stroke={color}
|
|
91
92
|
strokeDasharray={line.system === 'vent' ? '5 4' : undefined}
|
|
92
93
|
strokeLinecap="round"
|
|
93
94
|
strokeWidth={(line.vertical ? 3.5 : 2.5) + (isSel ? 2 : 0)}
|
|
95
|
+
style={{ stroke: color }}
|
|
94
96
|
x1={line.from[0] * SCALE}
|
|
95
97
|
x2={line.to[0] * SCALE}
|
|
96
98
|
y1={line.from[1] * SCALE}
|
|
97
99
|
y2={line.to[1] * SCALE}
|
|
98
100
|
/>
|
|
99
101
|
<text
|
|
100
|
-
fill={color}
|
|
101
102
|
fontSize={9}
|
|
103
|
+
style={{ fill: color }}
|
|
102
104
|
x={((line.from[0] + line.to[0]) / 2) * SCALE + 4}
|
|
103
105
|
y={((line.from[1] + line.to[1]) / 2) * SCALE - 3}
|
|
104
106
|
>
|
|
@@ -115,7 +117,12 @@ function RiserDiagramContent() {
|
|
|
115
117
|
transform={`translate(${marker.point[0] * SCALE}, ${marker.point[1] * SCALE})`}
|
|
116
118
|
>
|
|
117
119
|
{marker.kind === 'vent-termination' ? (
|
|
118
|
-
<path
|
|
120
|
+
<path
|
|
121
|
+
d="M -5 0 L 0 -7 L 5 0"
|
|
122
|
+
fill="none"
|
|
123
|
+
strokeWidth={2}
|
|
124
|
+
style={{ stroke: VENT_COLOR }}
|
|
125
|
+
/>
|
|
119
126
|
) : (
|
|
120
127
|
<circle fill={MARKER_COLOR} r={3} stroke={MARKER_COLOR} strokeWidth={1.5} />
|
|
121
128
|
)}
|
|
@@ -28,9 +28,10 @@ import { RoomControls2D, RoomControls3D } from './room-controls'
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A zone reached as a zone (hovered or selected through its area or pill, not
|
|
31
|
-
* as a room) shows the room's outline, never
|
|
31
|
+
* as a room, or the one a chat chip points at) shows the room's outline, never
|
|
32
|
+
* a tinted volume. With zones hidden, a pointed zone has nothing else to light.
|
|
32
33
|
*/
|
|
33
|
-
function useZoneOutline() {
|
|
34
|
+
function useZoneOutline(pointedId: string | undefined) {
|
|
34
35
|
const hoveredId = useViewer((state) => state.hoveredId)
|
|
35
36
|
const selectedId = useViewer(
|
|
36
37
|
(state) =>
|
|
@@ -39,7 +40,7 @@ function useZoneOutline() {
|
|
|
39
40
|
)
|
|
40
41
|
const unitFocused = useViewer((state) => !!state.focusedUnitId)
|
|
41
42
|
const zone = useScene((state) => {
|
|
42
|
-
for (const id of [hoveredId, selectedId]) {
|
|
43
|
+
for (const id of [hoveredId, pointedId, selectedId]) {
|
|
43
44
|
const node = id ? state.nodes[id as AnyNodeId] : undefined
|
|
44
45
|
if (node?.type === 'zone') return node
|
|
45
46
|
}
|
|
@@ -53,13 +54,20 @@ function useZoneOutline() {
|
|
|
53
54
|
|
|
54
55
|
function useRoomHighlight() {
|
|
55
56
|
const room = useHighlightedRoom()
|
|
56
|
-
const
|
|
57
|
+
const pointedId = useViewer((state) =>
|
|
58
|
+
state.pointedIds.length === 1 ? state.pointedIds[0] : undefined,
|
|
59
|
+
)
|
|
60
|
+
const zoneOutline = useZoneOutline(pointedId)
|
|
57
61
|
const visible = useInteractionScope(
|
|
58
62
|
(state) => resolveOverlayPolicy(state.scope).conflictingControls === 'shown',
|
|
59
63
|
)
|
|
60
64
|
// No phase gate: site never hovers or keeps a room (`roomPickingEnabled`).
|
|
61
65
|
const enabled = useEditor((state) => state.mode === 'select')
|
|
62
|
-
|
|
66
|
+
if (!(enabled && visible)) return null
|
|
67
|
+
// A zone a chat points at shows over a kept room: it is what the person is reaching for.
|
|
68
|
+
if (zoneOutline && zoneOutline.zoneId === pointedId && room?.zoneId !== pointedId)
|
|
69
|
+
return zoneOutline
|
|
70
|
+
return room ?? zoneOutline
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
const noRaycast = () => {}
|