@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,44 +1,303 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
nodeRegistry,
|
|
7
|
+
type TerrainVerb,
|
|
8
|
+
useScene,
|
|
9
|
+
} from '@pascal-app/core'
|
|
10
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
11
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
12
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
4
13
|
import { useIsMobile } from '../../../hooks/use-mobile'
|
|
5
|
-
import
|
|
14
|
+
import {
|
|
15
|
+
type ContextualShortcutHint,
|
|
16
|
+
GROUP_MOVE_DRAG_LABEL,
|
|
17
|
+
GROUP_ROTATE_DRAG_LABEL,
|
|
18
|
+
ROTATE_HANDLE_DRAG_LABEL,
|
|
19
|
+
resolveRotateHandleHelpHints,
|
|
20
|
+
resolveSelectModeHelpHints,
|
|
21
|
+
} from '../../../lib/contextual-help'
|
|
22
|
+
import { continuationContextOf } from '../../../lib/continuation'
|
|
23
|
+
import { canDirectMoveNode, canDirectRotateNode } from '../../../lib/direct-manipulation'
|
|
24
|
+
import type { ReshapeKind } from '../../../lib/interaction/scope'
|
|
25
|
+
import { isFreshPlacementMetadata } from '../../../lib/placement-metadata'
|
|
26
|
+
import { snapContextOf } from '../../../lib/snapping-mode'
|
|
27
|
+
import useEditor, { getActiveContinuationContext } from '../../../store/use-editor'
|
|
28
|
+
import useInteractionScope, {
|
|
29
|
+
useActiveHandleDrag,
|
|
30
|
+
useMovingNode,
|
|
31
|
+
} from '../../../store/use-interaction-scope'
|
|
6
32
|
import { BuildingHelper } from './building-helper'
|
|
33
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
7
34
|
import { ItemHelper } from './item-helper'
|
|
8
35
|
import { RegisteredToolHelper } from './registered-tool-helper'
|
|
9
36
|
import { RoofHelper } from './roof-helper'
|
|
10
37
|
|
|
38
|
+
// Reshaping a selected node's geometry (endpoint / curve / polygon corner). The
|
|
39
|
+
// snapping chip is the main control; these just name the gesture + Esc.
|
|
40
|
+
function reshapingHints(reshape: ReshapeKind): ContextualShortcutHint[] {
|
|
41
|
+
const action =
|
|
42
|
+
reshape === 'curve'
|
|
43
|
+
? 'Curve'
|
|
44
|
+
: reshape === 'control-point'
|
|
45
|
+
? 'Move control point'
|
|
46
|
+
: reshape === 'tangent'
|
|
47
|
+
? 'Move tangent'
|
|
48
|
+
: reshape === 'endpoint'
|
|
49
|
+
? 'Move endpoint'
|
|
50
|
+
: 'Move corner'
|
|
51
|
+
return [
|
|
52
|
+
{ keys: ['Drag'], label: action },
|
|
53
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Sculpt mode's HUD. The verb is named rather than described because it is the one
|
|
58
|
+
// piece of state a user loses track of between strokes. This stays even though the
|
|
59
|
+
// brush ring now carries the verb in colour (`brushRingColor`): the ring says
|
|
60
|
+
// *which* verb only to someone who already knows the mapping, and the HUD is what
|
|
61
|
+
// teaches it.
|
|
62
|
+
function terrainSculptHints(verb: TerrainVerb, sampling: boolean): ContextualShortcutHint[] {
|
|
63
|
+
if (sampling) {
|
|
64
|
+
return [
|
|
65
|
+
{ keys: ['Click'], label: 'Pick target height' },
|
|
66
|
+
{ keys: ['Esc'], label: 'Cancel picking' },
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
const action =
|
|
70
|
+
verb === 'raise'
|
|
71
|
+
? 'Raise ground'
|
|
72
|
+
: verb === 'lower'
|
|
73
|
+
? 'Lower ground'
|
|
74
|
+
: verb === 'flatten'
|
|
75
|
+
? 'Level ground'
|
|
76
|
+
: 'Smooth ground'
|
|
77
|
+
return [
|
|
78
|
+
{ keys: ['Drag'], label: action },
|
|
79
|
+
// Nested, so the two render as alternatives ("[ / ]") rather than a chord —
|
|
80
|
+
// a flat `['[', ']']` joins with "+" and would read as "press both".
|
|
81
|
+
{ keys: [['[', ']']], label: 'Brush size' },
|
|
82
|
+
{ keys: ['Esc'], label: 'Cancel stroke' },
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ActiveModifierKeys = {
|
|
87
|
+
command: boolean
|
|
88
|
+
shift: boolean
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function useActiveModifierKeys(): ActiveModifierKeys {
|
|
92
|
+
const [modifiers, setModifiers] = useState<ActiveModifierKeys>({
|
|
93
|
+
command: false,
|
|
94
|
+
shift: false,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const updateModifiers = (event: KeyboardEvent) => {
|
|
99
|
+
const isKeyDown = event.type === 'keydown'
|
|
100
|
+
setModifiers({
|
|
101
|
+
command:
|
|
102
|
+
event.metaKey ||
|
|
103
|
+
event.ctrlKey ||
|
|
104
|
+
(isKeyDown && (event.key === 'Meta' || event.key === 'Control')),
|
|
105
|
+
shift: event.shiftKey || (isKeyDown && event.key === 'Shift'),
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
const clearModifiers = () => {
|
|
109
|
+
setModifiers({ command: false, shift: false })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
window.addEventListener('keydown', updateModifiers)
|
|
113
|
+
window.addEventListener('keyup', updateModifiers)
|
|
114
|
+
window.addEventListener('blur', clearModifiers)
|
|
115
|
+
return () => {
|
|
116
|
+
window.removeEventListener('keydown', updateModifiers)
|
|
117
|
+
window.removeEventListener('keyup', updateModifiers)
|
|
118
|
+
window.removeEventListener('blur', clearModifiers)
|
|
119
|
+
}
|
|
120
|
+
}, [])
|
|
121
|
+
|
|
122
|
+
return modifiers
|
|
123
|
+
}
|
|
124
|
+
|
|
11
125
|
export function HelperManager() {
|
|
12
126
|
const mode = useEditor((s) => s.mode)
|
|
13
127
|
const tool = useEditor((s) => s.tool)
|
|
14
|
-
const
|
|
128
|
+
const terrainVerb = useEditor((s) => s.terrainVerb)
|
|
129
|
+
const terrainSampling = useEditor((s) => s.terrainSampling)
|
|
130
|
+
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
131
|
+
const measurementToolKind = useEditor((s) => s.toolDefaults.measurement?.kind)
|
|
132
|
+
const workspaceMode = useEditor((s) => s.workspaceMode)
|
|
133
|
+
const scope = useInteractionScope((s) => s.scope)
|
|
134
|
+
const movingNode = useMovingNode()
|
|
135
|
+
const activeHandleDrag = useActiveHandleDrag()
|
|
136
|
+
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
15
137
|
const isMobile = useIsMobile()
|
|
138
|
+
const modifiers = useActiveModifierKeys()
|
|
139
|
+
const selectedNodes = useScene(
|
|
140
|
+
useShallow((s) =>
|
|
141
|
+
selectedIds
|
|
142
|
+
.map((id) => s.nodes[id as AnyNodeId])
|
|
143
|
+
.filter((node): node is AnyNode => node !== undefined),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
// The snapping context for whatever's active (wall / item / polygon) — drives
|
|
147
|
+
// which snapping chips the HUD shows, derived once and shared by every branch.
|
|
148
|
+
const snapContext = useMemo(
|
|
149
|
+
() =>
|
|
150
|
+
snapContextOf({
|
|
151
|
+
scope,
|
|
152
|
+
mode,
|
|
153
|
+
tool,
|
|
154
|
+
profileOf: (typeOrTool) => nodeRegistry.get(typeOrTool)?.snapProfile,
|
|
155
|
+
draftDirectionalOf: (typeOrTool) => nodeRegistry.get(typeOrTool)?.snapDraftDirectional ?? true,
|
|
156
|
+
}),
|
|
157
|
+
[scope, mode, tool],
|
|
158
|
+
)
|
|
159
|
+
const continuationContext = useMemo(
|
|
160
|
+
() => getActiveContinuationContext(),
|
|
161
|
+
[scope, mode, tool],
|
|
162
|
+
)
|
|
163
|
+
const selectModeHints = useMemo(() => {
|
|
164
|
+
const single = selectedNodes.length === 1 ? selectedNodes[0] : null
|
|
165
|
+
const mepSelection =
|
|
166
|
+
single?.type === 'duct-segment' || single?.type === 'pipe-segment'
|
|
167
|
+
? 'run'
|
|
168
|
+
: single?.type === 'duct-fitting' || single?.type === 'pipe-fitting'
|
|
169
|
+
? 'fitting'
|
|
170
|
+
: null
|
|
171
|
+
return resolveSelectModeHelpHints({
|
|
172
|
+
selectedCount: selectedNodes.length,
|
|
173
|
+
hasMovableSelection: selectedNodes.some((node) => canDirectMoveNode(node)),
|
|
174
|
+
hasRotatableSelection: selectedNodes.some((node) => canDirectRotateNode(node)),
|
|
175
|
+
commandPressed: modifiers.command,
|
|
176
|
+
shiftPressed: modifiers.shift,
|
|
177
|
+
mepSelection,
|
|
178
|
+
})
|
|
179
|
+
}, [modifiers.command, modifiers.shift, selectedNodes])
|
|
16
180
|
|
|
17
181
|
// Helpers are keyboard-driven hints (Esc, R, etc.) — irrelevant on touch.
|
|
18
182
|
if (isMobile) return null
|
|
19
183
|
|
|
184
|
+
// First-person walkthrough has its own HUD; editor shortcut hints (e.g. the
|
|
185
|
+
// Ctrl multi-select hint — Ctrl is crouch there) don't apply while walking.
|
|
186
|
+
if (isFirstPersonMode) return null
|
|
187
|
+
|
|
188
|
+
// The studio workspace (compose panel / gallery) has no scene selection or
|
|
189
|
+
// tools — editor shortcut hints would only mislead there.
|
|
190
|
+
if (workspaceMode === 'studio') return null
|
|
191
|
+
|
|
192
|
+
// Rotating a node (or a multi-selection group) via its in-world gizmo:
|
|
193
|
+
// advertise Shift = free rotation, the same angle-step bypass wall drafting
|
|
194
|
+
// exposes. Takes priority over the idle select-mode hints since a handle
|
|
195
|
+
// drag is the active interaction.
|
|
196
|
+
if (
|
|
197
|
+
activeHandleDrag?.label === ROTATE_HANDLE_DRAG_LABEL ||
|
|
198
|
+
activeHandleDrag?.label === GROUP_ROTATE_DRAG_LABEL
|
|
199
|
+
) {
|
|
200
|
+
return <ContextualHelperPanel hints={resolveRotateHandleHelpHints(modifiers.shift)} />
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Group-move drag / pick-up: the drag resolves to the 'item' snap context
|
|
204
|
+
// (see `snapContextOf`), so surface the snapping chips — mode + grid step,
|
|
205
|
+
// with their Shift / Ctrl cycle shortcuts — plus the mid-move R/T rotate.
|
|
206
|
+
if (activeHandleDrag?.label === GROUP_MOVE_DRAG_LABEL) {
|
|
207
|
+
return (
|
|
208
|
+
<ContextualHelperPanel
|
|
209
|
+
hints={[{ keys: ['R / T'], label: 'Rotate the selection ±45°' }]}
|
|
210
|
+
snapContext={snapContext}
|
|
211
|
+
/>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Reshaping a node's geometry (endpoint / curve / polygon corner). Checked
|
|
216
|
+
// before the select branch so the idle "drag selected / add objects" hints
|
|
217
|
+
// never leak over an in-progress reshape — and it gets its own snapping chip.
|
|
218
|
+
if (scope.kind === 'reshaping') {
|
|
219
|
+
return <ContextualHelperPanel hints={reshapingHints(scope.reshape)} snapContext={snapContext} />
|
|
220
|
+
}
|
|
221
|
+
|
|
20
222
|
if (movingNode) {
|
|
21
223
|
if (movingNode.type === 'building') return <BuildingHelper showRotate />
|
|
22
|
-
|
|
224
|
+
// A fresh placement (e.g. a positioned preset like a shelf) advertises its
|
|
225
|
+
// once/repeat continuation, exactly like the GLB item tool — but an existing
|
|
226
|
+
// node being *moved* is not a placement, so it gets no continuation chip.
|
|
227
|
+
const movingContinuationContext = isFreshPlacementMetadata(movingNode.metadata)
|
|
228
|
+
? continuationContextOf(movingNode.type)
|
|
229
|
+
: null
|
|
230
|
+
// Force-place only makes sense for kinds that collision-validate their drop;
|
|
231
|
+
// structural kinds (wall/slab/…) never reject, so don't advertise Alt.
|
|
232
|
+
return (
|
|
233
|
+
<ItemHelper
|
|
234
|
+
continuationContext={movingContinuationContext}
|
|
235
|
+
showEsc
|
|
236
|
+
showForce={nodeRegistry.get(movingNode.type)?.snapProfile !== 'structural'}
|
|
237
|
+
snapContext={snapContext}
|
|
238
|
+
/>
|
|
239
|
+
)
|
|
23
240
|
}
|
|
24
241
|
|
|
242
|
+
// Paint mode advertises (and cycles, via Shift) the application scope — the
|
|
243
|
+
// only contextual control here. The chip hides itself for targets that only
|
|
244
|
+
// paint one surface, so this renders nothing until a scoped target is active.
|
|
25
245
|
if (mode === 'material-paint') {
|
|
26
|
-
return
|
|
246
|
+
return <ContextualHelperPanel hints={[]} showPaintScope />
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Sculpt mode. The HUD is what makes a sustained brush mode legible: it names
|
|
250
|
+
// the active verb (the same word the panel's segmented control shows, so the
|
|
251
|
+
// two never disagree), and it advertises the two keys whose behaviour is
|
|
252
|
+
// mode-specific — bracket resize, and an Esc that abandons the stroke rather
|
|
253
|
+
// than exiting the mode.
|
|
254
|
+
if (mode === 'terrain-sculpt') {
|
|
255
|
+
return <ContextualHelperPanel hints={terrainSculptHints(terrainVerb, terrainSampling)} />
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Idle select only — an active scope (handle-drag, box-select, …) must not show
|
|
259
|
+
// the idle selection hints.
|
|
260
|
+
if (mode === 'select' && scope.kind === 'idle') {
|
|
261
|
+
return <ContextualHelperPanel hints={selectModeHints} />
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (tool === 'measurement' && measurementToolKind === 'smart') {
|
|
265
|
+
return (
|
|
266
|
+
<ContextualHelperPanel
|
|
267
|
+
hints={[
|
|
268
|
+
{ keys: ['Hover'], label: 'Inspect surface dimensions' },
|
|
269
|
+
{ keys: ['Click'], label: 'Pin measurement lens' },
|
|
270
|
+
{ keys: ['Esc'], label: 'Exit smart measure' },
|
|
271
|
+
]}
|
|
272
|
+
/>
|
|
273
|
+
)
|
|
27
274
|
}
|
|
28
275
|
|
|
29
|
-
//
|
|
30
|
-
// `
|
|
31
|
-
//
|
|
276
|
+
// Legacy fallback — only `roof` remains because it hasn't migrated to
|
|
277
|
+
// `def.tool` / `def.toolHints` yet (no Stage D port). Checked before the
|
|
278
|
+
// generic tool branch so the snap-context fallback below doesn't capture it
|
|
279
|
+
// and drop its bespoke `RoofHelper` hints. When roof migrates, this deletes.
|
|
280
|
+
if (tool === 'roof') return <RoofHelper snapContext={snapContext} />
|
|
281
|
+
|
|
282
|
+
// Registry-first: a kind renders the generic `RegisteredToolHelper` when it
|
|
283
|
+
// declares `def.toolHints`, OR whenever its draft resolves to a snap /
|
|
284
|
+
// continuation context — so a snappable tool with NO hand-written hints (e.g.
|
|
285
|
+
// `zone`) still advertises the snapping chip it already honors (Shift = cycle).
|
|
286
|
+
// `RegisteredToolHelper` self-hides when there's genuinely nothing to show.
|
|
32
287
|
if (tool) {
|
|
33
288
|
const def = nodeRegistry.get(tool)
|
|
34
|
-
|
|
35
|
-
|
|
289
|
+
const hints = def?.toolHints ?? []
|
|
290
|
+
if (hints.length > 0 || snapContext || continuationContext) {
|
|
291
|
+
return (
|
|
292
|
+
<RegisteredToolHelper
|
|
293
|
+
continuationContext={continuationContext}
|
|
294
|
+
hints={hints}
|
|
295
|
+
shiftPressed={modifiers.shift}
|
|
296
|
+
snapContext={snapContext}
|
|
297
|
+
/>
|
|
298
|
+
)
|
|
36
299
|
}
|
|
37
300
|
}
|
|
38
301
|
|
|
39
|
-
// Legacy fallback — only `roof` remains because it hasn't migrated to
|
|
40
|
-
// `def.tool` / `def.toolHints` yet (no Stage D port). When roof
|
|
41
|
-
// migrates, this switch deletes outright.
|
|
42
|
-
if (tool === 'roof') return <RoofHelper />
|
|
43
302
|
return null
|
|
44
303
|
}
|
|
@@ -1,40 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ContinuationContext } from '../../../lib/continuation'
|
|
2
|
+
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
3
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
2
4
|
|
|
3
5
|
interface ItemHelperProps {
|
|
4
6
|
showEsc?: boolean
|
|
7
|
+
snapContext?: SnapContext | null
|
|
8
|
+
// Whether to advertise Alt = force-place. Only meaningful for kinds that
|
|
9
|
+
// collision-validate their drop (structural kinds never reject, so it's hidden).
|
|
10
|
+
showForce?: boolean
|
|
11
|
+
// Set for a fresh point-kind placement (e.g. a positioned preset) so the
|
|
12
|
+
// once/repeat continuation chip shows; null for an existing-node move.
|
|
13
|
+
continuationContext?: ContinuationContext | null
|
|
5
14
|
}
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
// Snapping mode is the chip on the right (Shift cycles it), so it's not repeated
|
|
17
|
+
// as a key hint. Rotate is the two keys; Alt forces an invalid (red) drop.
|
|
18
|
+
export function ItemHelper({
|
|
19
|
+
showEsc,
|
|
20
|
+
snapContext,
|
|
21
|
+
showForce,
|
|
22
|
+
continuationContext = null,
|
|
23
|
+
}: ItemHelperProps) {
|
|
8
24
|
return (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<ShortcutToken value="T" />
|
|
20
|
-
<span className="text-muted-foreground">Rotate clockwise</span>
|
|
21
|
-
</div>
|
|
22
|
-
<div className="flex items-center gap-2 text-sm">
|
|
23
|
-
<ShortcutToken value="Shift" />
|
|
24
|
-
<span className="text-muted-foreground">Free place</span>
|
|
25
|
-
</div>
|
|
26
|
-
{showEsc && (
|
|
27
|
-
<div className="flex items-center gap-2 text-sm">
|
|
28
|
-
<ShortcutToken value="Esc" />
|
|
29
|
-
<span className="text-muted-foreground">Cancel</span>
|
|
30
|
-
</div>
|
|
31
|
-
)}
|
|
32
|
-
{!showEsc && (
|
|
33
|
-
<div className="flex items-center gap-2 text-sm">
|
|
34
|
-
<ShortcutToken value="Right click" />
|
|
35
|
-
<span className="text-muted-foreground">Cancel</span>
|
|
36
|
-
</div>
|
|
37
|
-
)}
|
|
38
|
-
</div>
|
|
25
|
+
<ContextualHelperPanel
|
|
26
|
+
continuationContext={continuationContext}
|
|
27
|
+
hints={[
|
|
28
|
+
{ keys: ['Left click'], label: 'Place' },
|
|
29
|
+
{ keys: ['R', 'T'], label: 'Rotate' },
|
|
30
|
+
...(showForce ? [{ keys: ['Alt'], label: 'Force place' }] : []),
|
|
31
|
+
{ keys: [showEsc ? 'Esc' : 'Right click'], label: 'Cancel' },
|
|
32
|
+
]}
|
|
33
|
+
snapContext={snapContext}
|
|
34
|
+
/>
|
|
39
35
|
)
|
|
40
36
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ToolHint } from '@pascal-app/core'
|
|
2
|
-
import {
|
|
2
|
+
import type { ContinuationContext } from '../../../lib/continuation'
|
|
3
|
+
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
4
|
+
import useEditor from '../../../store/use-editor'
|
|
5
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Generic helper panel rendered from `def.toolHints` data. Matches the
|
|
@@ -10,16 +13,47 @@ import { ShortcutToken } from '../primitives/shortcut-token'
|
|
|
10
13
|
* Drops the need for per-kind helper files entirely — kinds declare
|
|
11
14
|
* their hints as static data in their `NodeDefinition`.
|
|
12
15
|
*/
|
|
13
|
-
export function RegisteredToolHelper({
|
|
14
|
-
|
|
16
|
+
export function RegisteredToolHelper({
|
|
17
|
+
hints,
|
|
18
|
+
shiftPressed = false,
|
|
19
|
+
snapContext = null,
|
|
20
|
+
continuationContext = null,
|
|
21
|
+
}: {
|
|
22
|
+
hints: ToolHint[]
|
|
23
|
+
shiftPressed?: boolean
|
|
24
|
+
snapContext?: SnapContext | null
|
|
25
|
+
continuationContext?: ContinuationContext | null
|
|
26
|
+
}) {
|
|
27
|
+
// Live vertex count of an in-progress polygon draft, so hints gated on a
|
|
28
|
+
// minimum (e.g. "Finish" at ≥ 3) only appear once they're actually possible.
|
|
29
|
+
const draftVertexCount = useEditor((s) => s.draftVertexCount)
|
|
30
|
+
// Some hints are replaced by live contextual chips, so keep the generic
|
|
31
|
+
// registry renderer from duplicating stale/static versions.
|
|
32
|
+
const visible = hints.filter(
|
|
33
|
+
(hint) =>
|
|
34
|
+
!(hint.key === 'Shift' && hint.label === 'Cycle snapping mode') &&
|
|
35
|
+
(hint.minDraftVertices == null || draftVertexCount >= hint.minDraftVertices),
|
|
36
|
+
)
|
|
37
|
+
if (visible.length === 0 && !snapContext && !continuationContext) return null
|
|
38
|
+
// Hints carrying a live-state `chip` render as mode chips next to the
|
|
39
|
+
// snapping / continuation rows; the rest stay static key rows.
|
|
40
|
+
const chipHints = visible.filter((hint) => hint.chip)
|
|
41
|
+
const staticHints = visible.filter((hint) => !hint.chip)
|
|
15
42
|
return (
|
|
16
|
-
<
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
<ContextualHelperPanel
|
|
44
|
+
chipHints={chipHints}
|
|
45
|
+
hints={staticHints.map((hint) => {
|
|
46
|
+
// Shift is a per-kind bypass for opening / zone / duct placement ("Free
|
|
47
|
+
// place", "Free angle", …) — those flip to a bypassed state while held.
|
|
48
|
+
const isBypassHint = hint.key === 'Shift'
|
|
49
|
+
return {
|
|
50
|
+
keys: [hint.key],
|
|
51
|
+
label: shiftPressed && isBypassHint ? 'Guided constraints bypassed' : hint.label,
|
|
52
|
+
active: shiftPressed && isBypassHint,
|
|
53
|
+
}
|
|
54
|
+
})}
|
|
55
|
+
continuationContext={continuationContext}
|
|
56
|
+
snapContext={snapContext}
|
|
57
|
+
/>
|
|
24
58
|
)
|
|
25
59
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
2
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
2
3
|
|
|
3
|
-
export function RoofHelper() {
|
|
4
|
+
export function RoofHelper({ snapContext }: { snapContext?: SnapContext | null }) {
|
|
4
5
|
return (
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<span className="text-muted-foreground">Cancel</span>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
6
|
+
<ContextualHelperPanel
|
|
7
|
+
hints={[
|
|
8
|
+
{ keys: ['Left click'], label: 'Set corner' },
|
|
9
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
10
|
+
]}
|
|
11
|
+
snapContext={snapContext}
|
|
12
|
+
/>
|
|
15
13
|
)
|
|
16
14
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@iconify/react'
|
|
4
|
+
import type { IconRef } from '@pascal-app/core'
|
|
5
|
+
import { type ComponentType, lazy, Suspense } from 'react'
|
|
6
|
+
|
|
7
|
+
// `React.lazy` must be called once per loader so the resolved component keeps
|
|
8
|
+
// a stable identity across renders (otherwise every parent re-render remounts
|
|
9
|
+
// the icon). Cache by the loader function — same pattern as the plugin-panel
|
|
10
|
+
// component cache.
|
|
11
|
+
const lazyIconCache = new WeakMap<() => Promise<{ default: ComponentType }>, ComponentType>()
|
|
12
|
+
|
|
13
|
+
function resolveLazyIcon(module: () => Promise<{ default: ComponentType }>): ComponentType {
|
|
14
|
+
const cached = lazyIconCache.get(module)
|
|
15
|
+
if (cached) return cached
|
|
16
|
+
const Lazy = lazy(module)
|
|
17
|
+
lazyIconCache.set(module, Lazy)
|
|
18
|
+
return Lazy
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generic renderer for a registry {@link IconRef} — url / iconify / inline-svg
|
|
23
|
+
* marks are sized by `size` (px); `component`-kind icons size themselves.
|
|
24
|
+
* Shared by the quick-action menus; the icon rail and inspector keep their
|
|
25
|
+
* own copies with bespoke wrappers for now.
|
|
26
|
+
*/
|
|
27
|
+
export function IconRefGlyph({ icon, size = 16 }: { icon: IconRef; size?: number }) {
|
|
28
|
+
if (icon.kind === 'url') {
|
|
29
|
+
return <img alt="" className="shrink-0 object-contain" height={size} src={icon.src} width={size} />
|
|
30
|
+
}
|
|
31
|
+
if (icon.kind === 'iconify') {
|
|
32
|
+
return <Icon height={size} icon={icon.name} width={size} />
|
|
33
|
+
}
|
|
34
|
+
if (icon.kind === 'svg') {
|
|
35
|
+
return (
|
|
36
|
+
<svg height={size} viewBox={icon.viewBox} width={size}>
|
|
37
|
+
<path d={icon.path} fill="currentColor" />
|
|
38
|
+
</svg>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
const LazyIcon = resolveLazyIcon(icon.module)
|
|
42
|
+
return (
|
|
43
|
+
<Suspense fallback={null}>
|
|
44
|
+
<LazyIcon />
|
|
45
|
+
</Suspense>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AssetInput } from '@pascal-app/core'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* A catalog tile: the asset plus optional editor placement metadata.
|
|
5
|
+
* `tool` names the placement tool the tile arms (defaults to the generic
|
|
6
|
+
* `'item'` drop tool) — kinds drawn by their own registry tool (e.g. the
|
|
7
|
+
* modular cabinet) point at that tool id instead.
|
|
8
|
+
*/
|
|
9
|
+
export type CatalogItem = AssetInput & { tool?: string }
|
|
10
|
+
|
|
11
|
+
export const CATALOG_ITEMS: CatalogItem[] = [
|
|
4
12
|
{
|
|
5
13
|
id: 'cactus',
|
|
6
14
|
category: 'furniture',
|
|
@@ -437,6 +445,36 @@ export const CATALOG_ITEMS: AssetInput[] = [
|
|
|
437
445
|
scale: [1, 1, 1],
|
|
438
446
|
surface: { height: 0.48 },
|
|
439
447
|
},
|
|
448
|
+
{
|
|
449
|
+
id: 'cabinet',
|
|
450
|
+
tool: 'cabinet',
|
|
451
|
+
category: 'furniture',
|
|
452
|
+
name: 'Modular Cabinet',
|
|
453
|
+
tags: [
|
|
454
|
+
'cabinet',
|
|
455
|
+
'cupboard',
|
|
456
|
+
'storage',
|
|
457
|
+
'kitchen',
|
|
458
|
+
'pantry',
|
|
459
|
+
'wood',
|
|
460
|
+
'timber',
|
|
461
|
+
'modern',
|
|
462
|
+
'contemporary',
|
|
463
|
+
'minimalist',
|
|
464
|
+
'organization',
|
|
465
|
+
'furniture',
|
|
466
|
+
],
|
|
467
|
+
thumbnail:
|
|
468
|
+
'https://byrpxoiotywskoojsrzd.supabase.co/storage/v1/object/public/items/system/kitchen-cabinet/thumbnail.png',
|
|
469
|
+
src: 'https://byrpxoiotywskoojsrzd.supabase.co/storage/v1/object/public/items/system/kitchen-cabinet/model.glb',
|
|
470
|
+
floorPlanUrl:
|
|
471
|
+
'https://byrpxoiotywskoojsrzd.supabase.co/storage/v1/object/public/items/system/kitchen-cabinet/floor-plan.png',
|
|
472
|
+
dimensions: [1.65, 1.09, 0.77],
|
|
473
|
+
offset: [0, 0.0004, 0],
|
|
474
|
+
rotation: [0, 0, 0],
|
|
475
|
+
scale: [1, 1, 1],
|
|
476
|
+
surface: { height: 1.09 },
|
|
477
|
+
},
|
|
440
478
|
{
|
|
441
479
|
id: 'sofa',
|
|
442
480
|
category: 'furniture',
|