@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuildStats, SchemaIssue, ValidateBuildJsonResult } from '@pascal-app/core'
|
|
2
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
2
3
|
import {
|
|
3
4
|
AlertTriangle,
|
|
4
5
|
AppWindow,
|
|
@@ -22,6 +23,11 @@ import {
|
|
|
22
23
|
DialogHeader,
|
|
23
24
|
DialogTitle,
|
|
24
25
|
} from '../../../../../components/ui/primitives/dialog'
|
|
26
|
+
import {
|
|
27
|
+
getAreaUnitLabel,
|
|
28
|
+
type LinearUnit,
|
|
29
|
+
squareMetersToAreaUnit,
|
|
30
|
+
} from '../../../../../lib/measurements'
|
|
25
31
|
|
|
26
32
|
export type PendingImport = {
|
|
27
33
|
fileName: string
|
|
@@ -80,15 +86,17 @@ function formatFileSize(bytes: number): string {
|
|
|
80
86
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
function formatFloorArea(m2: number): string {
|
|
89
|
+
function formatFloorArea(m2: number, unit: LinearUnit): string {
|
|
84
90
|
if (m2 === 0) return '—'
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
const value = squareMetersToAreaUnit(m2, unit)
|
|
92
|
+
const label = getAreaUnitLabel(unit)
|
|
93
|
+
return `${value < 10 ? value.toFixed(2) : value.toFixed(1)} ${label}`
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
export function LoadBuildDialog({ pending, onCancel, onConfirm }: Props) {
|
|
90
97
|
const [showAllWarnings, setShowAllWarnings] = useState(false)
|
|
91
98
|
const [showSchemaIssues, setShowSchemaIssues] = useState(false)
|
|
99
|
+
const unit = useViewer((state) => state.unit)
|
|
92
100
|
|
|
93
101
|
if (!pending) return null
|
|
94
102
|
|
|
@@ -165,7 +173,7 @@ export function LoadBuildDialog({ pending, onCancel, onConfirm }: Props) {
|
|
|
165
173
|
<div className="flex items-center justify-between border-t px-3 py-2">
|
|
166
174
|
<span className="text-muted-foreground text-sm">Floor area</span>
|
|
167
175
|
<span className="font-medium text-sm">
|
|
168
|
-
{formatFloorArea(stats.floorAreaM2)}
|
|
176
|
+
{formatFloorArea(stats.floorAreaM2, unit)}
|
|
169
177
|
</span>
|
|
170
178
|
</div>
|
|
171
179
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BuildingNode, LevelNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type BuildingNode, DEFAULT_LEVEL_HEIGHT, LevelNode, useScene } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { Building2, Plus } from 'lucide-react'
|
|
4
4
|
import { memo, useState } from 'react'
|
|
@@ -43,6 +43,7 @@ export const BuildingTreeNode = memo(function BuildingTreeNode({
|
|
|
43
43
|
const levelCount = children.filter((childId) => nodes[childId]?.type === 'level').length
|
|
44
44
|
const newLevel = LevelNode.parse({
|
|
45
45
|
level: levelCount,
|
|
46
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
46
47
|
children: [],
|
|
47
48
|
parentId: nodeId,
|
|
48
49
|
})
|
|
@@ -3,6 +3,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
+
import { formatAreaLabel } from './../../../../../lib/measurements'
|
|
6
7
|
import useEditor from './../../../../../store/use-editor'
|
|
7
8
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
9
|
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
@@ -32,6 +33,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
|
|
|
32
33
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
33
34
|
const setSelection = useViewer((state) => state.setSelection)
|
|
34
35
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
36
|
+
const unit = useViewer((state) => state.unit)
|
|
35
37
|
|
|
36
38
|
// Expand when a descendant is selected — imperative to avoid subscribing to the full selectedIds array
|
|
37
39
|
useEffect(() => {
|
|
@@ -75,8 +77,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
|
|
|
75
77
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
76
78
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
77
79
|
|
|
78
|
-
const
|
|
79
|
-
const defaultName = `Ceiling (${area}m²)`
|
|
80
|
+
const defaultName = `Ceiling (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
|
|
80
81
|
|
|
81
82
|
return (
|
|
82
83
|
<TreeNodeWrapper
|
|
@@ -85,7 +86,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
|
|
|
85
86
|
expanded={expanded}
|
|
86
87
|
hasChildren={children.length > 0}
|
|
87
88
|
icon={
|
|
88
|
-
<Image alt="" className="object-contain" height={14} src="/icons/ceiling.
|
|
89
|
+
<Image alt="" className="object-contain" height={14} src="/icons/ceiling.webp" width={14} />
|
|
89
90
|
}
|
|
90
91
|
isHovered={isHovered}
|
|
91
92
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type ChimneyNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -51,13 +52,15 @@ export const ChimneyTreeNode = memo(function ChimneyTreeNode({
|
|
|
51
52
|
expanded={false}
|
|
52
53
|
hasChildren={false}
|
|
53
54
|
icon={
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
<SnapTargetIcon target="roof">
|
|
56
|
+
<Image
|
|
57
|
+
alt=""
|
|
58
|
+
className="object-contain opacity-60"
|
|
59
|
+
height={14}
|
|
60
|
+
src="/icons/roof.webp"
|
|
61
|
+
width={14}
|
|
62
|
+
/>
|
|
63
|
+
</SnapTargetIcon>
|
|
61
64
|
}
|
|
62
65
|
isHovered={isHovered}
|
|
63
66
|
isLast={isLast}
|
|
@@ -51,7 +51,7 @@ export const ColumnTreeNode = memo(function ColumnTreeNode({
|
|
|
51
51
|
expanded={false}
|
|
52
52
|
hasChildren={false}
|
|
53
53
|
icon={
|
|
54
|
-
<Image alt="" className="object-contain" height={14} src="/icons/column.
|
|
54
|
+
<Image alt="" className="object-contain" height={14} src="/icons/column.webp" width={14} />
|
|
55
55
|
}
|
|
56
56
|
isHovered={isHovered}
|
|
57
57
|
isLast={isLast}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { type AnyNodeId, useScene } from '@pascal-app/core'
|
|
3
|
+
import { type AnyNodeId, type DoorNode, useScene } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import Image from 'next/image'
|
|
6
6
|
import { memo, useCallback, useState } from 'react'
|
|
7
|
+
import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
|
|
7
8
|
import useEditor from './../../../../../store/use-editor'
|
|
8
9
|
import { InlineRenameInput } from './inline-rename-input'
|
|
9
10
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -22,6 +23,7 @@ export const DoorTreeNode = memo(function DoorTreeNode({
|
|
|
22
23
|
}: DoorTreeNodeProps) {
|
|
23
24
|
const [isEditing, setIsEditing] = useState(false)
|
|
24
25
|
const isVisible = useScene((s) => s.nodes[nodeId as AnyNodeId]?.visible !== false)
|
|
26
|
+
const node = useScene((s) => s.nodes[nodeId] as DoorNode | undefined)
|
|
25
27
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
26
28
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
27
29
|
const setSelection = useViewer((state) => state.setSelection)
|
|
@@ -45,6 +47,7 @@ export const DoorTreeNode = memo(function DoorTreeNode({
|
|
|
45
47
|
|
|
46
48
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
47
49
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
50
|
+
const snapTarget = resolveNodeSnapTarget(node) ?? 'wall'
|
|
48
51
|
|
|
49
52
|
return (
|
|
50
53
|
<TreeNodeWrapper
|
|
@@ -53,7 +56,9 @@ export const DoorTreeNode = memo(function DoorTreeNode({
|
|
|
53
56
|
expanded={false}
|
|
54
57
|
hasChildren={false}
|
|
55
58
|
icon={
|
|
56
|
-
<
|
|
59
|
+
<SnapTargetIcon target={snapTarget}>
|
|
60
|
+
<Image alt="" className="object-contain" height={14} src="/icons/door.webp" width={14} />
|
|
61
|
+
</SnapTargetIcon>
|
|
57
62
|
}
|
|
58
63
|
isHovered={isHovered}
|
|
59
64
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type DormerNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -57,13 +58,15 @@ export const DormerTreeNode = memo(function DormerTreeNode({
|
|
|
57
58
|
expanded={false}
|
|
58
59
|
hasChildren={false}
|
|
59
60
|
icon={
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
<SnapTargetIcon target="roof">
|
|
62
|
+
<Image
|
|
63
|
+
alt=""
|
|
64
|
+
className="object-contain opacity-60"
|
|
65
|
+
height={14}
|
|
66
|
+
src="/icons/roof.webp"
|
|
67
|
+
width={14}
|
|
68
|
+
/>
|
|
69
|
+
</SnapTargetIcon>
|
|
67
70
|
}
|
|
68
71
|
isHovered={isHovered}
|
|
69
72
|
isLast={isLast}
|
|
@@ -49,7 +49,7 @@ export const ElevatorTreeNode = memo(function ElevatorTreeNode({
|
|
|
49
49
|
expanded={false}
|
|
50
50
|
hasChildren={false}
|
|
51
51
|
icon={
|
|
52
|
-
<Image alt="" className="object-contain" height={14} src="/icons/elevator.
|
|
52
|
+
<Image alt="" className="object-contain" height={14} src="/icons/elevator.webp" width={14} />
|
|
53
53
|
}
|
|
54
54
|
isHovered={isHovered}
|
|
55
55
|
isLast={isLast}
|
|
@@ -43,7 +43,7 @@ export const FenceTreeNode = memo(function FenceTreeNode({
|
|
|
43
43
|
expanded={false}
|
|
44
44
|
hasChildren={false}
|
|
45
45
|
icon={
|
|
46
|
-
<Image alt="" className="object-contain" height={14} src="/icons/fence.
|
|
46
|
+
<Image alt="" className="object-contain" height={14} src="/icons/fence.webp" width={14} />
|
|
47
47
|
}
|
|
48
48
|
isHovered={isHovered}
|
|
49
49
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import { type AnyNodeId, type GutterNode, useScene } from '@pascal-app/core'
|
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
|
+
import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
5
6
|
import useEditor from './../../../../../store/use-editor'
|
|
6
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
@@ -51,13 +52,15 @@ export const GutterTreeNode = memo(function GutterTreeNode({
|
|
|
51
52
|
expanded={false}
|
|
52
53
|
hasChildren={false}
|
|
53
54
|
icon={
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
<SnapTargetIcon target="roof">
|
|
56
|
+
<Image
|
|
57
|
+
alt=""
|
|
58
|
+
className="object-contain opacity-60"
|
|
59
|
+
height={14}
|
|
60
|
+
src="/icons/roof.webp"
|
|
61
|
+
width={14}
|
|
62
|
+
/>
|
|
63
|
+
</SnapTargetIcon>
|
|
61
64
|
}
|
|
62
65
|
isHovered={isHovered}
|
|
63
66
|
isLast={isLast}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type AnyNode,
|
|
3
3
|
type AnyNodeId,
|
|
4
4
|
type BuildingNode,
|
|
5
|
+
DEFAULT_LEVEL_HEIGHT,
|
|
5
6
|
emitter,
|
|
6
7
|
type GuideNode,
|
|
7
8
|
LevelNode,
|
|
@@ -36,8 +37,16 @@ import {
|
|
|
36
37
|
buildLevelDuplicateCreateOps,
|
|
37
38
|
type LevelDuplicatePreset,
|
|
38
39
|
} from './../../../../../lib/level-duplication'
|
|
39
|
-
import { getDefaultLevelName } from '
|
|
40
|
+
import { getDefaultLevelName } from '@pascal-app/core'
|
|
40
41
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
42
|
+
import {
|
|
43
|
+
formatAreaLabel,
|
|
44
|
+
getAreaUnitLabel,
|
|
45
|
+
getLinearUnitLabel,
|
|
46
|
+
linearUnitToMeters,
|
|
47
|
+
metersToLinearUnit,
|
|
48
|
+
squareMetersToAreaUnit,
|
|
49
|
+
} from './../../../../../lib/measurements'
|
|
41
50
|
import { createLocalGuideImage } from './../../../../../lib/local-guide-image'
|
|
42
51
|
import { cn } from './../../../../../lib/utils'
|
|
43
52
|
import useEditor from './../../../../../store/use-editor'
|
|
@@ -93,6 +102,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
93
102
|
const updateNode = useScene((state) => state.updateNode)
|
|
94
103
|
const mode = useEditor((state) => state.mode)
|
|
95
104
|
const setMode = useEditor((state) => state.setMode)
|
|
105
|
+
const viewerUnit = useViewer((state) => state.unit)
|
|
96
106
|
|
|
97
107
|
if (!siteNode) return null
|
|
98
108
|
|
|
@@ -101,6 +111,14 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
101
111
|
const perimeter = calculatePerimeter(points)
|
|
102
112
|
const isEditing = mode === 'edit'
|
|
103
113
|
|
|
114
|
+
// Property-line coordinates and readouts follow the metric/imperial toggle.
|
|
115
|
+
const isImperial = viewerUnit === 'imperial'
|
|
116
|
+
const linearLabel = getLinearUnitLabel(viewerUnit)
|
|
117
|
+
const toDisplayLinear = (meters: number) => metersToLinearUnit(meters, viewerUnit)
|
|
118
|
+
const toStoredLinear = (display: number) => linearUnitToMeters(display, viewerUnit)
|
|
119
|
+
const displayArea = squareMetersToAreaUnit(area, viewerUnit)
|
|
120
|
+
const displayPerimeter = toDisplayLinear(perimeter)
|
|
121
|
+
|
|
104
122
|
const handleToggleEdit = () => {
|
|
105
123
|
setMode(isEditing ? 'select' : 'edit')
|
|
106
124
|
}
|
|
@@ -166,10 +184,16 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
166
184
|
{/* Measurements */}
|
|
167
185
|
<div className="relative flex gap-3 pr-3 pb-2 pl-10">
|
|
168
186
|
<div className="text-muted-foreground text-xs">
|
|
169
|
-
Area:
|
|
187
|
+
Area:{' '}
|
|
188
|
+
<span className="text-foreground">
|
|
189
|
+
{displayArea.toFixed(1)} {getAreaUnitLabel(viewerUnit)}
|
|
190
|
+
</span>
|
|
170
191
|
</div>
|
|
171
192
|
<div className="text-muted-foreground text-xs">
|
|
172
|
-
Perimeter:
|
|
193
|
+
Perimeter:{' '}
|
|
194
|
+
<span className="text-foreground">
|
|
195
|
+
{displayPerimeter.toFixed(1)} {linearLabel}
|
|
196
|
+
</span>
|
|
173
197
|
</div>
|
|
174
198
|
</div>
|
|
175
199
|
|
|
@@ -184,21 +208,21 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
184
208
|
<input
|
|
185
209
|
className="w-16 rounded border border-border/50 bg-accent/50 px-1.5 py-0.5 text-foreground text-xs focus:border-primary focus:outline-none"
|
|
186
210
|
onChange={(e) =>
|
|
187
|
-
handlePointChange(index, 0, Number.parseFloat(e.target.value) || 0)
|
|
211
|
+
handlePointChange(index, 0, toStoredLinear(Number.parseFloat(e.target.value) || 0))
|
|
188
212
|
}
|
|
189
213
|
step={0.5}
|
|
190
214
|
type="number"
|
|
191
|
-
value={point[0]}
|
|
215
|
+
value={Number(toDisplayLinear(point[0]).toFixed(2))}
|
|
192
216
|
/>
|
|
193
217
|
<label className="shrink-0 text-muted-foreground">Z</label>
|
|
194
218
|
<input
|
|
195
219
|
className="w-16 rounded border border-border/50 bg-accent/50 px-1.5 py-0.5 text-foreground text-xs focus:border-primary focus:outline-none"
|
|
196
220
|
onChange={(e) =>
|
|
197
|
-
handlePointChange(index, 1, Number.parseFloat(e.target.value) || 0)
|
|
221
|
+
handlePointChange(index, 1, toStoredLinear(Number.parseFloat(e.target.value) || 0))
|
|
198
222
|
}
|
|
199
223
|
step={0.5}
|
|
200
224
|
type="number"
|
|
201
|
-
value={point[1]}
|
|
225
|
+
value={Number(toDisplayLinear(point[1]).toFixed(2))}
|
|
202
226
|
/>
|
|
203
227
|
<button
|
|
204
228
|
className={cn(
|
|
@@ -356,13 +380,13 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
356
380
|
<img
|
|
357
381
|
alt="Scan"
|
|
358
382
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
359
|
-
src="/icons/mesh.
|
|
383
|
+
src="/icons/mesh.webp"
|
|
360
384
|
/>
|
|
361
385
|
) : (
|
|
362
386
|
<img
|
|
363
387
|
alt="Guide"
|
|
364
388
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
365
|
-
src="/icons/floorplan.
|
|
389
|
+
src="/icons/floorplan.webp"
|
|
366
390
|
/>
|
|
367
391
|
)}
|
|
368
392
|
<InlineRenameInput
|
|
@@ -721,7 +745,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
721
745
|
'h-4 w-4 shrink-0 object-contain transition-all duration-200',
|
|
722
746
|
!isSelected && 'opacity-60 grayscale',
|
|
723
747
|
)}
|
|
724
|
-
src="/icons/level.
|
|
748
|
+
src="/icons/level.webp"
|
|
725
749
|
/>
|
|
726
750
|
<InlineRenameInput
|
|
727
751
|
defaultName={getDefaultLevelName(level.level)}
|
|
@@ -903,6 +927,7 @@ const LevelsSection = memo(function LevelsSection({
|
|
|
903
927
|
const handleAddLevel = () => {
|
|
904
928
|
const newLevel = LevelNode.parse({
|
|
905
929
|
level: levels.length,
|
|
930
|
+
height: DEFAULT_LEVEL_HEIGHT,
|
|
906
931
|
children: [],
|
|
907
932
|
parentId: building.id,
|
|
908
933
|
})
|
|
@@ -999,7 +1024,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
999
1024
|
'mb-1 h-6 w-6 transition-all',
|
|
1000
1025
|
activeTab !== 'structure' && 'opacity-50 grayscale',
|
|
1001
1026
|
)}
|
|
1002
|
-
src="/icons/room.
|
|
1027
|
+
src="/icons/room.webp"
|
|
1003
1028
|
/>
|
|
1004
1029
|
Structure
|
|
1005
1030
|
</div>
|
|
@@ -1035,7 +1060,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
1035
1060
|
'mb-1 h-6 w-6 transition-all',
|
|
1036
1061
|
activeTab !== 'furnish' && 'opacity-50 grayscale',
|
|
1037
1062
|
)}
|
|
1038
|
-
src="/icons/couch.
|
|
1063
|
+
src="/icons/couch.webp"
|
|
1039
1064
|
/>
|
|
1040
1065
|
Furnish
|
|
1041
1066
|
</div>
|
|
@@ -1072,7 +1097,7 @@ const LayerToggle = memo(function LayerToggle() {
|
|
|
1072
1097
|
'mb-1 h-6 w-6 transition-all',
|
|
1073
1098
|
activeTab !== 'zones' && 'opacity-50 grayscale',
|
|
1074
1099
|
)}
|
|
1075
|
-
src="/icons/kitchen.
|
|
1100
|
+
src="/icons/kitchen.webp"
|
|
1076
1101
|
/>
|
|
1077
1102
|
Zones
|
|
1078
1103
|
</div>
|
|
@@ -1097,6 +1122,7 @@ const ZoneItem = memo(function ZoneItem({ zone, isLast }: { zone: ZoneNode; isLa
|
|
|
1097
1122
|
const setHoveredId = useViewer((state) => state.setHoveredId)
|
|
1098
1123
|
const setPhase = useEditor((state) => state.setPhase)
|
|
1099
1124
|
const setMode = useEditor((state) => state.setMode)
|
|
1125
|
+
const unit = useViewer((state) => state.unit)
|
|
1100
1126
|
|
|
1101
1127
|
const isSelected = selectedZoneId === zone.id
|
|
1102
1128
|
const isHovered = hoveredId === zone.id
|
|
@@ -1109,8 +1135,7 @@ const ZoneItem = memo(function ZoneItem({ zone, isLast }: { zone: ZoneNode; isLa
|
|
|
1109
1135
|
}
|
|
1110
1136
|
}, [isSelected])
|
|
1111
1137
|
|
|
1112
|
-
const
|
|
1113
|
-
const defaultName = `Zone (${area}m²)`
|
|
1138
|
+
const defaultName = `Zone (${formatAreaLabel(calculatePolygonArea(zone.polygon), unit)})`
|
|
1114
1139
|
|
|
1115
1140
|
const handleClick = () => {
|
|
1116
1141
|
setSelection({ zoneId: zone.id })
|
|
@@ -1413,7 +1438,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1413
1438
|
'h-5 w-5 object-contain transition-all',
|
|
1414
1439
|
!isBuildingActive && 'opacity-60 grayscale',
|
|
1415
1440
|
)}
|
|
1416
|
-
src="/icons/building.
|
|
1441
|
+
src="/icons/building.webp"
|
|
1417
1442
|
/>
|
|
1418
1443
|
<span className="truncate font-medium text-sm">{building.name || 'Building'}</span>
|
|
1419
1444
|
</div>
|
|
@@ -1569,7 +1594,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1569
1594
|
'h-5 w-5 object-contain transition-all',
|
|
1570
1595
|
phase !== 'site' && 'opacity-60 grayscale',
|
|
1571
1596
|
)}
|
|
1572
|
-
src="/icons/site-flag.
|
|
1597
|
+
src="/icons/site-flag.webp"
|
|
1573
1598
|
/>
|
|
1574
1599
|
<span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
|
|
1575
1600
|
</div>
|
|
@@ -3,19 +3,25 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
-
import
|
|
6
|
+
import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
|
|
7
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
focusTreeNode,
|
|
10
|
+
handleTreeSelection,
|
|
11
|
+
routeTreeSelectionToNode,
|
|
12
|
+
TreeNode,
|
|
13
|
+
TreeNodeWrapper,
|
|
14
|
+
} from './tree-node'
|
|
9
15
|
import { TreeNodeActions } from './tree-node-actions'
|
|
10
16
|
|
|
11
17
|
const CATEGORY_ICONS: Record<string, string> = {
|
|
12
|
-
door: '/icons/door.
|
|
13
|
-
window: '/icons/window.
|
|
14
|
-
furniture: '/icons/couch.
|
|
15
|
-
appliance: '/icons/appliance.
|
|
16
|
-
kitchen: '/icons/kitchen.
|
|
17
|
-
bathroom: '/icons/bathroom.
|
|
18
|
-
outdoor: '/icons/tree.
|
|
18
|
+
door: '/icons/door.webp',
|
|
19
|
+
window: '/icons/window.webp',
|
|
20
|
+
furniture: '/icons/couch.webp',
|
|
21
|
+
appliance: '/icons/appliance.webp',
|
|
22
|
+
kitchen: '/icons/kitchen.webp',
|
|
23
|
+
bathroom: '/icons/bathroom.webp',
|
|
24
|
+
outdoor: '/icons/tree.webp',
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
interface ItemTreeNodeProps {
|
|
@@ -35,7 +41,8 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
35
41
|
const children = useScene(
|
|
36
42
|
useShallow((s) => (s.nodes[nodeId] as ItemNode | undefined)?.children ?? []),
|
|
37
43
|
)
|
|
38
|
-
const
|
|
44
|
+
const node = useScene((s) => s.nodes[nodeId] as ItemNode | undefined)
|
|
45
|
+
const asset = node?.asset
|
|
39
46
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
40
47
|
const isHovered = useViewer((state) => state.hoveredId === nodeId)
|
|
41
48
|
const setSelection = useViewer((state) => state.setSelection)
|
|
@@ -63,17 +70,15 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
63
70
|
const handleClick = useCallback(
|
|
64
71
|
(e: React.MouseEvent) => {
|
|
65
72
|
e.stopPropagation()
|
|
66
|
-
|
|
73
|
+
handleTreeSelection(
|
|
67
74
|
e,
|
|
68
75
|
nodeId,
|
|
69
76
|
useViewer.getState().selection.selectedIds,
|
|
70
77
|
setSelection,
|
|
71
78
|
)
|
|
72
|
-
|
|
73
|
-
useEditor.getState().setPhase('furnish')
|
|
74
|
-
}
|
|
79
|
+
routeTreeSelectionToNode(node)
|
|
75
80
|
},
|
|
76
|
-
[nodeId, setSelection],
|
|
81
|
+
[node, nodeId, setSelection],
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
|
|
@@ -83,7 +88,8 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
83
88
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
84
89
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
85
90
|
|
|
86
|
-
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.
|
|
91
|
+
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.webp'
|
|
92
|
+
const snapTarget = resolveNodeSnapTarget(node)
|
|
87
93
|
const defaultName = asset?.name || 'Item'
|
|
88
94
|
const hasChildren = children.length > 0
|
|
89
95
|
|
|
@@ -93,7 +99,15 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
93
99
|
depth={depth}
|
|
94
100
|
expanded={expanded}
|
|
95
101
|
hasChildren={hasChildren}
|
|
96
|
-
icon={
|
|
102
|
+
icon={
|
|
103
|
+
snapTarget ? (
|
|
104
|
+
<SnapTargetIcon target={snapTarget}>
|
|
105
|
+
<Image alt="" className="object-contain" height={14} src={iconSrc} width={14} />
|
|
106
|
+
</SnapTargetIcon>
|
|
107
|
+
) : (
|
|
108
|
+
<Image alt="" className="object-contain" height={14} src={iconSrc} width={14} />
|
|
109
|
+
)
|
|
110
|
+
}
|
|
97
111
|
isHovered={isHovered}
|
|
98
112
|
isLast={isLast}
|
|
99
113
|
isSelected={isSelected}
|
|
@@ -3,7 +3,7 @@ import { useViewer } from '@pascal-app/viewer'
|
|
|
3
3
|
import { Layers } from 'lucide-react'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
-
import { getDefaultLevelName } from '
|
|
6
|
+
import { getDefaultLevelName } from '@pascal-app/core'
|
|
7
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
8
|
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
9
9
|
import { TreeNodeActions } from './tree-node-actions'
|