@pascal-app/editor 0.9.1 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -9
- package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
- package/src/components/editor/bake-exporter.tsx +49 -0
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +332 -199
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/editor-layout-mobile.tsx +5 -0
- package/src/components/editor/editor-layout-v2.tsx +18 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/export-manager.tsx +71 -63
- package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
- package/src/components/editor/first-person/build-collider-world.ts +52 -5
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +410 -136
- package/src/components/editor/floating-action-menu.tsx +435 -64
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-background-selection.test.ts +72 -0
- package/src/components/editor/floorplan-background-selection.ts +7 -20
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +2517 -1492
- package/src/components/editor/grid.tsx +166 -39
- package/src/components/editor/group-actions.ts +597 -0
- package/src/components/editor/group-floating-action-menu.tsx +130 -0
- package/src/components/editor/group-move-3d.ts +397 -0
- package/src/components/editor/group-rotate-handle.tsx +102 -64
- package/src/components/editor/group-selection-box-3d.tsx +173 -0
- package/src/components/editor/group-transform-shared.test.ts +192 -1
- package/src/components/editor/group-transform-shared.ts +240 -7
- package/src/components/editor/handles/handle-arrow.tsx +81 -23
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +49 -6
- package/src/components/editor/index.tsx +289 -35
- package/src/components/editor/measurement-pill.tsx +68 -23
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/node-arrow-handles.tsx +409 -180
- package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
- package/src/components/editor/quick-measurement-card.tsx +76 -0
- package/src/components/editor/quick-measurement-hud.tsx +34 -0
- package/src/components/editor/riser-diagram-panel.tsx +137 -0
- package/src/components/editor/selection-manager.tsx +814 -466
- package/src/components/editor/site-edge-labels.tsx +28 -6
- package/src/components/editor/slab-hole-highlights.tsx +13 -5
- package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
- package/src/components/editor/three-context-bridge.ts +22 -0
- package/src/components/editor/thumbnail-generator.tsx +118 -253
- package/src/components/editor/use-floorplan-background-placement.ts +113 -45
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-floorplan-scene-data.ts +5 -5
- package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
- package/src/components/editor/wall-measurement-label.tsx +10 -28
- package/src/components/editor/wall-move-side-handles.tsx +229 -39
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
- package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
- package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
- package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
- package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
- package/src/components/systems/selection-affordance-manager.tsx +38 -0
- package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
- package/src/components/systems/zone/zone-system.tsx +42 -13
- package/src/components/tools/elevator/elevator-tool.tsx +34 -8
- package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
- package/src/components/tools/fence/fence-drafting.ts +107 -8
- package/src/components/tools/item/move-tool.tsx +17 -13
- package/src/components/tools/item/placement-math.test.ts +13 -1
- package/src/components/tools/item/placement-math.ts +28 -2
- package/src/components/tools/item/placement-strategies.ts +246 -5
- package/src/components/tools/item/placement-types.ts +13 -1
- package/src/components/tools/item/use-draft-node.ts +146 -57
- package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
- package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
- package/src/components/tools/roof/roof-tool.tsx +336 -94
- package/src/components/tools/select/box-select-state.ts +18 -7
- package/src/components/tools/select/box-select-tool.tsx +176 -40
- package/src/components/tools/select/marquee-geometry.test.ts +161 -0
- package/src/components/tools/select/marquee-geometry.ts +155 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
- package/src/components/tools/select/select-candidates.ts +1 -1
- package/src/components/tools/shared/cursor-sphere.tsx +62 -26
- package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
- package/src/components/tools/shared/facing-indicator.tsx +87 -0
- package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/placement-box.tsx +183 -1
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/shared/polygon-editor.tsx +276 -35
- package/src/components/tools/site/site-boundary-editor.tsx +101 -42
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-defaults.ts +1 -0
- package/src/components/tools/stair/stair-tool.tsx +164 -42
- package/src/components/tools/tool-manager.tsx +149 -30
- package/src/components/tools/wall/wall-drafting.test.ts +447 -0
- package/src/components/tools/wall/wall-drafting.ts +304 -108
- package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
- package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
- package/src/components/tools/zone/zone-tool.tsx +98 -88
- package/src/components/ui/action-menu/camera-actions.tsx +24 -17
- package/src/components/ui/action-menu/control-modes.tsx +44 -94
- package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
- package/src/components/ui/action-menu/index.tsx +2 -4
- package/src/components/ui/action-menu/measurement-control.tsx +267 -0
- package/src/components/ui/action-menu/structure-tools.tsx +22 -13
- package/src/components/ui/action-menu/view-toggles.tsx +42 -90
- package/src/components/ui/command-palette/editor-commands.tsx +16 -3
- package/src/components/ui/command-palette/index.tsx +4 -3
- package/src/components/ui/controls/material-paint-panel.tsx +93 -18
- package/src/components/ui/controls/material-picker.tsx +162 -152
- package/src/components/ui/controls/material-properties-editor.tsx +108 -0
- package/src/components/ui/controls/metric-control.tsx +118 -44
- package/src/components/ui/controls/scene-material-list.tsx +247 -0
- package/src/components/ui/controls/slider-control.tsx +88 -38
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +74 -20
- package/src/components/ui/helpers/building-helper.tsx +10 -23
- package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
- package/src/components/ui/helpers/helper-manager.tsx +273 -14
- package/src/components/ui/helpers/item-helper.tsx +28 -32
- package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
- package/src/components/ui/helpers/roof-helper.tsx +10 -12
- package/src/components/ui/icon-ref.tsx +47 -0
- package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
- package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/node-display.ts +17 -17
- package/src/components/ui/panels/panel-manager.tsx +36 -14
- package/src/components/ui/panels/panel-wrapper.tsx +24 -3
- package/src/components/ui/panels/parametric-inspector.tsx +19 -4
- package/src/components/ui/panels/reference-panel.tsx +54 -19
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/primitives/shortcut-token.tsx +39 -3
- package/src/components/ui/primitives/sidebar.tsx +1 -0
- package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
- package/src/components/ui/sidebar/icon-rail.tsx +50 -31
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
- package/src/components/ui/sidebar/tab-bar.tsx +42 -28
- package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
- package/src/components/ui/snap-target-badge.test.tsx +40 -0
- package/src/components/ui/snap-target-badge.tsx +88 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +36 -596
- package/src/components/viewer-zone-system.tsx +6 -1
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-auto-save.test.ts +14 -0
- package/src/hooks/use-auto-save.ts +54 -7
- package/src/hooks/use-ceiling-events.ts +3 -2
- package/src/hooks/use-drag-action.ts +4 -1
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +438 -70
- package/src/index.tsx +355 -9
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +42 -0
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/ceiling-plan-snap.ts +24 -0
- package/src/lib/contextual-help.test.ts +112 -0
- package/src/lib/contextual-help.ts +144 -0
- package/src/lib/continuation.ts +64 -0
- package/src/lib/direct-manipulation.test.ts +275 -0
- package/src/lib/direct-manipulation.ts +118 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/editor-api.ts +23 -35
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/apply-alignment.test.ts +25 -0
- package/src/lib/floorplan/apply-alignment.ts +18 -12
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +978 -0
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +52 -0
- package/src/lib/floorplan/index.ts +3 -0
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/plan-coords.ts +21 -0
- package/src/lib/fresh-planar-placement.test.ts +283 -3
- package/src/lib/fresh-planar-placement.ts +85 -1
- package/src/lib/glb-export.test.ts +556 -0
- package/src/lib/glb-export.ts +1071 -0
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +31 -0
- package/src/lib/interaction/hot-set.test.ts +133 -0
- package/src/lib/interaction/hot-set.ts +67 -0
- package/src/lib/interaction/overlay-policy.test.ts +52 -0
- package/src/lib/interaction/overlay-policy.ts +59 -0
- package/src/lib/interaction/scope.ts +243 -0
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/level-selection.ts +2 -2
- package/src/lib/material-paint.ts +36 -49
- package/src/lib/measurement-kind.test.ts +30 -0
- package/src/lib/measurement-kind.ts +19 -0
- package/src/lib/measurement-label.test.ts +47 -0
- package/src/lib/measurement-label.ts +68 -0
- package/src/lib/measurement-parser.ts +116 -0
- package/src/lib/measurements.test.ts +188 -0
- package/src/lib/measurements.ts +210 -0
- package/src/lib/paint-scope.test.ts +454 -0
- package/src/lib/paint-scope.ts +461 -0
- package/src/lib/placement-drag-release.ts +23 -0
- package/src/lib/planar-cursor-placement.test.ts +57 -0
- package/src/lib/plugin-panels.test.ts +19 -0
- package/src/lib/plugin-panels.ts +91 -0
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/quick-measurement.test.ts +77 -0
- package/src/lib/quick-measurement.ts +109 -0
- package/src/lib/roof-duplication.ts +6 -6
- package/src/lib/roof-hover-outline-proxy.ts +22 -0
- package/src/lib/roof-wall-hit.ts +164 -0
- package/src/lib/scene-clipboard.test.ts +325 -0
- package/src/lib/scene-clipboard.ts +310 -21
- package/src/lib/scene.ts +28 -5
- package/src/lib/selection-routing.test.ts +313 -0
- package/src/lib/selection-routing.ts +180 -0
- package/src/lib/sfx/index.ts +1 -0
- package/src/lib/sfx/movement-tick.test.ts +65 -0
- package/src/lib/sfx/movement-tick.ts +18 -0
- package/src/lib/sfx-bus.ts +82 -14
- package/src/lib/sfx-player.test.ts +266 -0
- package/src/lib/sfx-player.ts +262 -69
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/slab-plan-snap.test.ts +93 -0
- package/src/lib/slab-plan-snap.ts +108 -0
- package/src/lib/snapping-mode.test.ts +165 -0
- package/src/lib/snapping-mode.ts +191 -0
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +58 -86
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/surface-plan-snap.test.ts +71 -0
- package/src/lib/surface-plan-snap.ts +256 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/use-linear-display.ts +40 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/lib/world-grid-snap.ts +37 -5
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-direct-manipulation-feedback.ts +20 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +665 -126
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-facing-pose.ts +44 -0
- package/src/store/use-fence-curve-draft.ts +51 -0
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +151 -0
- package/src/store/use-floorplan-marquee.ts +50 -0
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +206 -0
- package/src/store/use-interaction-scope.ts +140 -0
- package/src/store/use-measurement-draft.test.ts +559 -0
- package/src/store/use-measurement-draft.ts +570 -0
- package/src/store/use-opening-guides.ts +41 -0
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-placement-preview.ts +11 -3
- package/src/store/use-quick-measurement-hud.test.ts +53 -0
- package/src/store/use-quick-measurement-hud.ts +77 -0
- package/src/store/use-segment-draft-chain.ts +28 -0
- package/src/store/use-stair-build-preview.ts +53 -0
- package/src/store/use-wall-snap-indicator.ts +2 -0
- package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
- package/src/components/editor/group-move-handle.tsx +0 -316
- package/src/components/ui/panels/paint-panel.tsx +0 -163
- package/src/lib/floorplan/selection-tool.ts +0 -271
- package/src/lib/level-name.ts +0 -11
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import { cn } from '../lib/utils'
|
|
5
|
+
import type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
6
|
+
|
|
7
|
+
export type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
8
|
+
|
|
9
|
+
export type WalkthroughHudProps = {
|
|
10
|
+
floorLabel?: string | null
|
|
11
|
+
zoneLabel?: string | null
|
|
12
|
+
interact?: WalkthroughInteract
|
|
13
|
+
/** Pointer lock temporarily released (OS screenshot) — the pill flips to
|
|
14
|
+
* "Click to resume" and lets clicks fall through to the canvas. */
|
|
15
|
+
suspended?: boolean
|
|
16
|
+
onExit?: () => void
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function WalkthroughHud({
|
|
21
|
+
floorLabel,
|
|
22
|
+
zoneLabel,
|
|
23
|
+
interact = null,
|
|
24
|
+
suspended = false,
|
|
25
|
+
onExit,
|
|
26
|
+
children,
|
|
27
|
+
}: WalkthroughHudProps) {
|
|
28
|
+
const kbdClass = 'rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]'
|
|
29
|
+
const pillClass =
|
|
30
|
+
'flex items-center gap-1.5 rounded-full border border-border/40 bg-background/70 px-3 py-1 text-muted-foreground text-xs backdrop-blur-xl'
|
|
31
|
+
const exitContent = (
|
|
32
|
+
<>
|
|
33
|
+
<kbd className={kbdClass}>Esc</kbd>
|
|
34
|
+
to exit
|
|
35
|
+
</>
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="dark pointer-events-none absolute inset-0 z-30 text-foreground">
|
|
40
|
+
<div className="absolute top-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-1.5">
|
|
41
|
+
{floorLabel && (
|
|
42
|
+
<div className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
43
|
+
{floorLabel}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
{zoneLabel && (
|
|
47
|
+
<div className="corner-smooth rounded-full border border-border/40 bg-background/80 px-3 py-1 font-medium text-sm shadow-elevation-3 backdrop-blur-xl">
|
|
48
|
+
{zoneLabel}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
'rounded-full transition-all duration-150',
|
|
58
|
+
interact
|
|
59
|
+
? 'h-4 w-4 border-2 border-emerald-400 bg-emerald-400/10'
|
|
60
|
+
: 'h-1.5 w-1.5 bg-white/80 shadow-[0_0_2px_rgba(0,0,0,0.6)]',
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-2">
|
|
66
|
+
{suspended ? (
|
|
67
|
+
<div className={pillClass}>
|
|
68
|
+
<span className="font-medium text-foreground">Click</span>
|
|
69
|
+
<span>or</span>
|
|
70
|
+
<kbd className={kbdClass}>P</kbd>
|
|
71
|
+
<span>to resume</span>
|
|
72
|
+
<span className="text-muted-foreground/60">·</span>
|
|
73
|
+
{exitContent}
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
<>
|
|
77
|
+
<div className={pillClass}>
|
|
78
|
+
<kbd className={kbdClass}>P</kbd>
|
|
79
|
+
free cursor
|
|
80
|
+
</div>
|
|
81
|
+
{onExit ? (
|
|
82
|
+
<button
|
|
83
|
+
className={cn(pillClass, 'pointer-events-auto')}
|
|
84
|
+
onClick={onExit}
|
|
85
|
+
type="button"
|
|
86
|
+
>
|
|
87
|
+
{exitContent}
|
|
88
|
+
</button>
|
|
89
|
+
) : (
|
|
90
|
+
<div className={pillClass}>{exitContent}</div>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{interact && (
|
|
97
|
+
<div className="absolute top-1/2 left-1/2 mt-7 -translate-x-1/2 whitespace-nowrap">
|
|
98
|
+
<div className="corner-smooth flex items-center gap-1.5 rounded-full border border-border/40 bg-background/80 px-3 py-1 text-xs shadow-elevation-3 backdrop-blur-xl">
|
|
99
|
+
<kbd className="rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]">
|
|
100
|
+
E
|
|
101
|
+
</kbd>
|
|
102
|
+
<span className="text-muted-foreground">or click to</span>
|
|
103
|
+
<span className="font-medium">
|
|
104
|
+
{interact.verb} {interact.label}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { isSuspiciousNodeDrop } from './use-auto-save'
|
|
3
|
+
|
|
4
|
+
describe('isSuspiciousNodeDrop', () => {
|
|
5
|
+
test('blocks populated scenes from being flushed as empty skeletons', () => {
|
|
6
|
+
expect(isSuspiciousNodeDrop(12, 0)).toBe(true)
|
|
7
|
+
expect(isSuspiciousNodeDrop(12, 4)).toBe(true)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('allows ordinary edits and intentionally empty starting scenes', () => {
|
|
11
|
+
expect(isSuspiciousNodeDrop(12, 11)).toBe(false)
|
|
12
|
+
expect(isSuspiciousNodeDrop(4, 0)).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -5,6 +5,11 @@ import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
|
|
5
5
|
import { type SceneGraph, saveSceneToLocalStorage } from '../lib/scene'
|
|
6
6
|
|
|
7
7
|
const AUTOSAVE_DEBOUNCE_MS = 1000
|
|
8
|
+
const STRUCTURAL_NODE_COUNT = 4
|
|
9
|
+
|
|
10
|
+
export function isSuspiciousNodeDrop(previousNodeCount: number, currentNodeCount: number) {
|
|
11
|
+
return previousNodeCount > STRUCTURAL_NODE_COUNT && currentNodeCount <= STRUCTURAL_NODE_COUNT
|
|
12
|
+
}
|
|
8
13
|
|
|
9
14
|
export type SaveStatus = 'idle' | 'pending' | 'saving' | 'saved' | 'paused' | 'error'
|
|
10
15
|
|
|
@@ -61,6 +66,13 @@ export function useAutoSave({
|
|
|
61
66
|
useEffect(() => {
|
|
62
67
|
let lastNodesSnapshot = JSON.stringify(useScene.getState().nodes)
|
|
63
68
|
let lastNodeCount = Object.keys(useScene.getState().nodes).length
|
|
69
|
+
// Collections + scene materials are document-level state that persists with
|
|
70
|
+
// the graph but lives outside `nodes`. Track them by reference (zustand
|
|
71
|
+
// hands out a new object on every mutation) so a material edit or a
|
|
72
|
+
// collection change still triggers a save.
|
|
73
|
+
let lastCollectionsRef = useScene.getState().collections
|
|
74
|
+
let lastMaterialsRef = useScene.getState().materials
|
|
75
|
+
let lastInstalledPluginsRef = useScene.getState().installedPlugins
|
|
64
76
|
|
|
65
77
|
async function executeSave() {
|
|
66
78
|
if (isLoadingSceneRef.current || isVersionPreviewModeRef.current) {
|
|
@@ -69,14 +81,19 @@ export function useAutoSave({
|
|
|
69
81
|
return
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
const { nodes, rootNodeIds } = useScene.getState()
|
|
73
|
-
const sceneGraph = {
|
|
84
|
+
const { nodes, rootNodeIds, collections, materials, installedPlugins } = useScene.getState()
|
|
85
|
+
const sceneGraph = {
|
|
86
|
+
nodes,
|
|
87
|
+
rootNodeIds,
|
|
88
|
+
collections,
|
|
89
|
+
materials,
|
|
90
|
+
installedPlugins,
|
|
91
|
+
} as SceneGraph
|
|
74
92
|
|
|
75
93
|
// Guard: refuse to autosave if the scene went from populated to nearly empty.
|
|
76
94
|
// This catches accidental full deletions before they're persisted.
|
|
77
95
|
const currentNodeCount = Object.keys(nodes).length
|
|
78
|
-
|
|
79
|
-
if (lastNodeCount > STRUCTURAL_NODE_COUNT && currentNodeCount <= STRUCTURAL_NODE_COUNT) {
|
|
96
|
+
if (isSuspiciousNodeDrop(lastNodeCount, currentNodeCount)) {
|
|
80
97
|
console.warn(
|
|
81
98
|
`[autosave] Blocked: scene dropped from ${lastNodeCount} to ${currentNodeCount} nodes. Likely accidental deletion.`,
|
|
82
99
|
)
|
|
@@ -118,19 +135,33 @@ export function useAutoSave({
|
|
|
118
135
|
const unsubscribe = useScene.subscribe((state) => {
|
|
119
136
|
if (isLoadingSceneRef.current) {
|
|
120
137
|
lastNodesSnapshot = JSON.stringify(state.nodes)
|
|
138
|
+
lastCollectionsRef = state.collections
|
|
139
|
+
lastMaterialsRef = state.materials
|
|
140
|
+
lastInstalledPluginsRef = state.installedPlugins
|
|
121
141
|
return
|
|
122
142
|
}
|
|
123
143
|
|
|
124
144
|
if (isVersionPreviewModeRef.current) {
|
|
125
145
|
setSaveStatus('paused')
|
|
126
146
|
lastNodesSnapshot = JSON.stringify(state.nodes)
|
|
147
|
+
lastCollectionsRef = state.collections
|
|
148
|
+
lastMaterialsRef = state.materials
|
|
149
|
+
lastInstalledPluginsRef = state.installedPlugins
|
|
127
150
|
return
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
const currentNodesSnapshot = JSON.stringify(state.nodes)
|
|
131
|
-
|
|
154
|
+
const changed =
|
|
155
|
+
currentNodesSnapshot !== lastNodesSnapshot ||
|
|
156
|
+
state.collections !== lastCollectionsRef ||
|
|
157
|
+
state.materials !== lastMaterialsRef ||
|
|
158
|
+
state.installedPlugins !== lastInstalledPluginsRef
|
|
159
|
+
if (!changed) return
|
|
132
160
|
|
|
133
161
|
lastNodesSnapshot = currentNodesSnapshot
|
|
162
|
+
lastCollectionsRef = state.collections
|
|
163
|
+
lastMaterialsRef = state.materials
|
|
164
|
+
lastInstalledPluginsRef = state.installedPlugins
|
|
134
165
|
hasDirtyChangesRef.current = true
|
|
135
166
|
onDirtyRef.current?.()
|
|
136
167
|
setSaveStatus('pending')
|
|
@@ -155,9 +186,25 @@ export function useAutoSave({
|
|
|
155
186
|
// (mobile Safari, bfcache) where `beforeunload` does not.
|
|
156
187
|
function flushOnExit() {
|
|
157
188
|
if (!hasDirtyChangesRef.current) return
|
|
189
|
+
const { nodes, rootNodeIds, collections, materials, installedPlugins } = useScene.getState()
|
|
190
|
+
const currentNodeCount = Object.keys(nodes).length
|
|
191
|
+
if (isSuspiciousNodeDrop(lastNodeCount, currentNodeCount)) {
|
|
192
|
+
console.warn(
|
|
193
|
+
`[autosave] Blocked unload flush: scene dropped from ${lastNodeCount} to ${currentNodeCount} nodes. Likely accidental deletion.`,
|
|
194
|
+
)
|
|
195
|
+
setSaveStatus('error')
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
158
199
|
hasDirtyChangesRef.current = false
|
|
159
|
-
|
|
160
|
-
const sceneGraph = {
|
|
200
|
+
lastNodeCount = currentNodeCount
|
|
201
|
+
const sceneGraph = {
|
|
202
|
+
nodes,
|
|
203
|
+
rootNodeIds,
|
|
204
|
+
collections,
|
|
205
|
+
materials,
|
|
206
|
+
installedPlugins,
|
|
207
|
+
} as SceneGraph
|
|
161
208
|
if (onSaveRef.current) {
|
|
162
209
|
onSaveRef.current(sceneGraph, { keepalive: true }).catch(() => {})
|
|
163
210
|
} else {
|
|
@@ -12,6 +12,7 @@ import { useThree } from '@react-three/fiber'
|
|
|
12
12
|
import { useEffect, useRef } from 'react'
|
|
13
13
|
import { type Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
|
|
14
14
|
import useEditor from '../store/use-editor'
|
|
15
|
+
import { getMovingNode } from '../store/use-interaction-scope'
|
|
15
16
|
|
|
16
17
|
const UP = new Vector3(0, 1, 0)
|
|
17
18
|
|
|
@@ -59,7 +60,7 @@ export function useCeilingEvents() {
|
|
|
59
60
|
const isActive = (): boolean => {
|
|
60
61
|
const ed = useEditor.getState()
|
|
61
62
|
if (ed.selectedItem?.attachTo === 'ceiling') return true
|
|
62
|
-
const moving =
|
|
63
|
+
const moving = getMovingNode()
|
|
63
64
|
return moving?.type === 'item' && moving.asset?.attachTo === 'ceiling'
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -85,7 +86,7 @@ export function useCeilingEvents() {
|
|
|
85
86
|
|
|
86
87
|
for (const id of ceilingIds) {
|
|
87
88
|
const node = nodes[id as AnyNodeId] as CeilingNode | undefined
|
|
88
|
-
if (
|
|
89
|
+
if (node?.type !== 'ceiling' || node.polygon.length < 3) continue
|
|
89
90
|
if (resolveLevelId(node, nodes) !== activeLevelId) continue
|
|
90
91
|
const mesh = sceneRegistry.nodes.get(id)
|
|
91
92
|
if (!mesh) continue
|
|
@@ -19,7 +19,10 @@ import { useEffect, useRef } from 'react'
|
|
|
19
19
|
const sceneApi = createSceneApi(useScene)
|
|
20
20
|
|
|
21
21
|
function modifiersFromGridEvent(event: GridEvent): Modifiers {
|
|
22
|
-
|
|
22
|
+
// Both grid-event emit paths (use-grid-events.ts and the floorplan panel)
|
|
23
|
+
// store the raw DOM event directly at `.nativeEvent` — there is no second
|
|
24
|
+
// `.nativeEvent` hop.
|
|
25
|
+
const ne = event.nativeEvent as Partial<MouseEvent> | undefined
|
|
23
26
|
return {
|
|
24
27
|
shift: ne?.shiftKey ?? false,
|
|
25
28
|
alt: ne?.altKey ?? false,
|
|
@@ -9,6 +9,8 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
9
9
|
import { useThree } from '@react-three/fiber'
|
|
10
10
|
import { useEffect, useRef } from 'react'
|
|
11
11
|
import { Plane, Raycaster, Vector2, Vector3 } from 'three'
|
|
12
|
+
import { getPlacementSurface } from '../lib/active-placement-surface'
|
|
13
|
+
import { resolveTerrainGroundHit } from '../lib/ground-surface'
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Custom grid events hook that uses manual raycasting instead of mesh events.
|
|
@@ -38,6 +40,28 @@ export function useGridEvents(gridY: number) {
|
|
|
38
40
|
// Update raycaster
|
|
39
41
|
raycaster.current.setFromCamera(pointer.current, camera)
|
|
40
42
|
|
|
43
|
+
// Sculpted ground wins over the plane, but only while the plane IS the
|
|
44
|
+
// ground (see `isSiteGroundPlane`): a plane riding a storey base or a slab
|
|
45
|
+
// top is a real flat surface and must stay planar. The march is what removes
|
|
46
|
+
// the perspective skew — a ray intersected at the datum instead of at the
|
|
47
|
+
// hillside reports an XZ that slides along the ray, so every tool consuming
|
|
48
|
+
// `position`/`localPosition` was placing short of the cursor on a slope.
|
|
49
|
+
//
|
|
50
|
+
// The plane's own height is the query height, not the `gridY` argument: this
|
|
51
|
+
// effect is keyed on `[camera, gl]` (re-attaching listeners mid-drag would
|
|
52
|
+
// drop the gesture), so the argument in this closure is the value from mount.
|
|
53
|
+
// `constant = -gridY` by construction in the effect above.
|
|
54
|
+
const { origin, direction } = raycaster.current.ray
|
|
55
|
+
const fixedConstructionPlane = getPlacementSurface()?.projection === 'fixed-plane'
|
|
56
|
+
const hit = fixedConstructionPlane
|
|
57
|
+
? null
|
|
58
|
+
: resolveTerrainGroundHit(
|
|
59
|
+
[origin.x, origin.y, origin.z],
|
|
60
|
+
[direction.x, direction.y, direction.z],
|
|
61
|
+
-groundPlane.current.constant,
|
|
62
|
+
)
|
|
63
|
+
if (hit) return intersectionPoint.current.set(hit.x, hit.y, hit.z).clone()
|
|
64
|
+
|
|
41
65
|
// Intersect with ground plane
|
|
42
66
|
if (raycaster.current.ray.intersectPlane(groundPlane.current, intersectionPoint.current)) {
|
|
43
67
|
return intersectionPoint.current.clone()
|