@pascal-app/editor 1.0.3 → 1.0.4
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 +11 -9
- package/src/components/editor/action-menu-button.tsx +83 -0
- package/src/components/editor/bake-thumbnail.tsx +315 -4
- package/src/components/editor/capture-level-picker.tsx +72 -0
- package/src/components/editor/custom-camera-controls.tsx +202 -57
- package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
- package/src/components/editor/duplicate-node.ts +244 -0
- package/src/components/editor/editor-layout-mobile.tsx +36 -25
- package/src/components/editor/editor-layout-v2.tsx +131 -58
- package/src/components/editor/export-manager.tsx +34 -8
- package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
- package/src/components/editor/first-person/build-collider-world.ts +6 -2
- package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
- package/src/components/editor/first-person/mechanism-targets.ts +36 -0
- package/src/components/editor/first-person-controls.tsx +205 -2
- package/src/components/editor/floating-action-menu.tsx +63 -207
- package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
- package/src/components/editor/floor-aware-camera-controls.ts +324 -0
- package/src/components/editor/floor-openings-overlay.tsx +243 -0
- package/src/components/editor/floor-region-controls.tsx +846 -0
- package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
- package/src/components/editor/floor-surface.test.ts +165 -0
- package/src/components/editor/floor-surface.ts +182 -0
- package/src/components/editor/floorplan-background-selection.test.ts +11 -2
- package/src/components/editor/floorplan-background-selection.ts +1 -3
- package/src/components/editor/floorplan-panel.tsx +344 -342
- package/src/components/editor/footprint-height-handle.tsx +204 -0
- package/src/components/editor/group-actions.ts +54 -2
- package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
- package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
- package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
- package/src/components/editor/handles/use-handle-drag.ts +269 -197
- package/src/components/editor/index.tsx +294 -169
- package/src/components/editor/join-walls-pill.tsx +44 -0
- package/src/components/editor/node-action-menu.tsx +68 -70
- package/src/components/editor/node-arrow-handles.tsx +25 -6
- package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
- package/src/components/editor/open-wall-ends-hint.tsx +89 -0
- package/src/components/editor/quick-measurement-hud.tsx +3 -2
- package/src/components/editor/registry-action-contributions.test.tsx +63 -0
- package/src/components/editor/registry-action-contributions.tsx +80 -1
- package/src/components/editor/room-controls.tsx +847 -0
- package/src/components/editor/room-floor-highlight.tsx +128 -0
- package/src/components/editor/room-handles-2d.tsx +354 -0
- package/src/components/editor/room-handles.tsx +675 -0
- package/src/components/editor/room-highlight.tsx +225 -0
- package/src/components/editor/room-labels.test.ts +357 -0
- package/src/components/editor/room-labels.tsx +990 -0
- package/src/components/editor/room-transform-ghost.tsx +256 -0
- package/src/components/editor/room-webgpu-materials.test.ts +27 -0
- package/src/components/editor/selection-manager.tsx +451 -317
- package/src/components/editor/slab-hole-highlights.tsx +3 -0
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
- package/src/components/editor/snapshot-capture.test.ts +52 -4
- package/src/components/editor/snapshot-capture.ts +30 -5
- package/src/components/editor/thumbnail-generator.test.ts +308 -0
- package/src/components/editor/thumbnail-generator.tsx +1035 -229
- package/src/components/editor/use-floorplan-background-placement.ts +31 -11
- package/src/components/editor/vector-edge-extractor.tsx +121 -0
- package/src/components/editor/views/host-view.tsx +61 -0
- package/src/components/editor/views/plugins-view.tsx +670 -0
- package/src/components/editor/views/scene-region.tsx +59 -0
- package/src/components/editor/views/use-editor-views.ts +57 -0
- package/src/components/editor/views/view-bar.tsx +220 -0
- package/src/components/editor/wall-justification.test.ts +140 -0
- package/src/components/editor/wall-move-side-handles.tsx +43 -142
- package/src/components/editor/wall-push-arrow.tsx +92 -0
- package/src/components/editor/wall-region-handles.tsx +509 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
- package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
- package/src/components/editor-2d/svg-paths.ts +12 -15
- package/src/components/procedural-items/inspector.tsx +40 -15
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
- package/src/components/systems/zone/zone-system.test.ts +47 -0
- package/src/components/systems/zone/zone-system.tsx +29 -68
- package/src/components/tools/item/placement-history.test.tsx +474 -0
- package/src/components/tools/item/placement-strategies.ts +8 -4
- package/src/components/tools/item/use-draft-node.test.tsx +432 -1
- package/src/components/tools/item/use-draft-node.ts +166 -95
- package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
- package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
- package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
- package/src/components/tools/select/box-select-tool.tsx +2 -12
- package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
- package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
- package/src/components/tools/select/select-candidates.test.ts +37 -4
- package/src/components/tools/select/select-candidates.ts +15 -11
- package/src/components/tools/shared/cursor-sphere.tsx +36 -12
- package/src/components/tools/site/site-boundary-editor.tsx +1 -2
- package/src/components/tools/stair/stair-defaults.ts +0 -3
- package/src/components/tools/stair/stair-tool.tsx +90 -42
- package/src/components/tools/tool-manager.tsx +46 -20
- package/src/components/tools/wall/wall-drafting.test.ts +19 -4
- package/src/components/tools/zone/zone-tool.tsx +4 -12
- package/src/components/ui/action-menu/action-button.tsx +3 -0
- package/src/components/ui/action-menu/camera-actions.tsx +2 -1
- package/src/components/ui/action-menu/control-modes.tsx +2 -35
- package/src/components/ui/action-menu/measurement-control.tsx +3 -10
- package/src/components/ui/build-panel/build-panel.tsx +235 -0
- package/src/components/ui/command-palette/editor-commands.tsx +4 -30
- package/src/components/ui/command-palette/index.tsx +5 -24
- package/src/components/ui/controls/finish-swatch.tsx +110 -0
- package/src/components/ui/controls/material-paint-panel.tsx +97 -26
- package/src/components/ui/controls/material-picker.tsx +4 -1
- package/src/components/ui/controls/scene-material-list.tsx +4 -32
- package/src/components/ui/controls/select-control.tsx +65 -0
- package/src/components/ui/floating-level-selector.tsx +9 -14
- package/src/components/ui/helpers/building-helper.tsx +4 -1
- package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
- package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
- package/src/components/ui/helpers/helper-manager.tsx +325 -14
- package/src/components/ui/helpers/item-helper.tsx +8 -0
- package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
- package/src/components/ui/icon-ref.tsx +45 -1
- package/src/components/ui/level-duplicate-dialog.tsx +1 -1
- package/src/components/ui/node-icon-image.tsx +16 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
- package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
- package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
- package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
- package/src/components/ui/panels/multi-field-value.test.ts +26 -3
- package/src/components/ui/panels/multi-field-value.ts +6 -3
- package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
- package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
- package/src/components/ui/panels/node-display.ts +8 -3
- package/src/components/ui/panels/panel-manager.tsx +103 -68
- package/src/components/ui/panels/panel-wrapper.tsx +78 -36
- package/src/components/ui/panels/parametric-field-control.tsx +1 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -4
- package/src/components/ui/panels/reference-panel.tsx +2 -3
- package/src/components/ui/panels/room-construction-rows.tsx +774 -0
- package/src/components/ui/panels/room-name-options.test.ts +55 -0
- package/src/components/ui/panels/room-name-options.ts +47 -0
- package/src/components/ui/panels/room-openings-field.tsx +110 -0
- package/src/components/ui/panels/room-panel.tsx +725 -0
- package/src/components/ui/panels/shape-choice.tsx +58 -0
- package/src/components/ui/panels/wall-region-list.tsx +109 -0
- package/src/components/ui/primitives/color-dot.tsx +36 -24
- package/src/components/ui/primitives/sidebar.tsx +2 -0
- package/src/components/ui/right-stack.test.ts +29 -0
- package/src/components/ui/right-stack.tsx +145 -0
- package/src/components/ui/sidebar/icon-rail.tsx +5 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
- package/src/components/ui/sidebar/tab-bar.tsx +10 -0
- package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
- package/src/components/viewer/floorplan-compass-button.tsx +9 -1
- package/src/components/viewer/floorplan-preview.tsx +5 -6
- package/src/components/viewer/viewer-hover-label.tsx +34 -0
- package/src/components/viewer/viewer-rooms.tsx +175 -0
- package/src/components/viewer/viewer-scene-header.tsx +181 -145
- package/src/components/viewer/viewer-selection-manager.tsx +124 -0
- package/src/components/viewer/viewer-stage.tsx +2 -2
- package/src/components/viewer-zone-system.tsx +3 -8
- package/src/hooks/use-auto-save.integration.test.tsx +123 -9
- package/src/hooks/use-auto-save.ts +34 -8
- package/src/hooks/use-keyboard.test.ts +178 -3
- package/src/hooks/use-keyboard.ts +89 -71
- package/src/hooks/use-open-wall-ends.test.ts +99 -0
- package/src/hooks/use-open-wall-ends.ts +238 -0
- package/src/hooks/use-placement-notice.test.ts +48 -0
- package/src/hooks/use-placement-notice.ts +42 -0
- package/src/hooks/use-selected-room.ts +143 -0
- package/src/index.tsx +246 -39
- package/src/lib/ceiling-edit-session.test.ts +321 -0
- package/src/lib/ceiling-edit-session.ts +206 -0
- package/src/lib/ceiling-region-session.test.ts +179 -0
- package/src/lib/continuation.test.ts +19 -1
- package/src/lib/continuation.ts +25 -4
- package/src/lib/door-interaction.ts +14 -0
- package/src/lib/editor-level-display.test.ts +96 -0
- package/src/lib/editor-level-display.ts +48 -0
- package/src/lib/editor-views.ts +144 -0
- package/src/lib/export-geometry-wait.test.ts +90 -0
- package/src/lib/floor-edit-session.ts +153 -0
- package/src/lib/floor-footprints.test.ts +563 -0
- package/src/lib/floor-footprints.ts +395 -0
- package/src/lib/floor-foundation-support.test.ts +283 -0
- package/src/lib/floor-opening-draft.ts +218 -0
- package/src/lib/floor-openings.test.ts +216 -0
- package/src/lib/floor-region-geometry.ts +191 -0
- package/src/lib/floor-region-session.test.ts +485 -0
- package/src/lib/floor-region-session.ts +589 -0
- package/src/lib/floor-region-snap.ts +101 -0
- package/src/lib/floor-step-hover.test.ts +32 -0
- package/src/lib/floorplan/annotation-visibility.ts +17 -0
- package/src/lib/floorplan/dimension-drive.test.ts +360 -0
- package/src/lib/floorplan/dimension-drive.ts +479 -0
- package/src/lib/floorplan/floorplan-export.test.ts +12 -8
- package/src/lib/floorplan/floorplan-export.tsx +32 -8
- package/src/lib/floorplan/floorplan-extension.ts +32 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
- package/src/lib/floorplan/floorplan-mode.ts +2 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
- package/src/lib/floorplan/floorplan-readonly.ts +6 -0
- package/src/lib/floorplan/geometry.ts +13 -6
- package/src/lib/floorplan/index.ts +0 -9
- package/src/lib/floorplan/items.ts +5 -2
- package/src/lib/floorplan/marks.test.ts +213 -0
- package/src/lib/floorplan/marks.ts +238 -0
- package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
- package/src/lib/floorplan/open-wall-ends.ts +162 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
- package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
- package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
- package/src/lib/floorplan/schedules.test.ts +189 -0
- package/src/lib/floorplan/schedules.ts +339 -0
- package/src/lib/floorplan/sheet-export.test.ts +100 -0
- package/src/lib/floorplan/sheet-export.ts +318 -0
- package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
- package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
- package/src/lib/floorplan/site-plan/contributors.ts +97 -0
- package/src/lib/floorplan/site-plan/coverage.ts +232 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
- package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
- package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
- package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
- package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
- package/src/lib/floorplan/site-plan/geometry.ts +557 -0
- package/src/lib/floorplan/site-plan/index.ts +71 -0
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
- package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
- package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
- package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
- package/src/lib/floorplan/site-plan/street-name.ts +302 -0
- package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
- package/src/lib/floorplan/types.ts +1 -27
- package/src/lib/fresh-planar-placement.test.ts +36 -0
- package/src/lib/fresh-planar-placement.ts +59 -36
- package/src/lib/geometry-script/author.ts +152 -0
- package/src/lib/geometry-script/client.ts +152 -0
- package/src/lib/geometry-script/protocol.ts +12 -0
- package/src/lib/gesture-lifecycle.test.ts +595 -0
- package/src/lib/gesture-lifecycle.ts +290 -0
- package/src/lib/glb-export-zone-holes.test.ts +41 -0
- package/src/lib/glb-export.test.ts +485 -0
- package/src/lib/glb-export.ts +237 -119
- package/src/lib/history.test.ts +6 -0
- package/src/lib/history.ts +7 -0
- package/src/lib/hud-title.ts +109 -0
- package/src/lib/hydration-gated-storage.ts +49 -0
- package/src/lib/ifc-export.test.ts +174 -0
- package/src/lib/ifc-export.ts +201 -0
- package/src/lib/ifc-parts.ts +15 -0
- package/src/lib/inspector-expanded.test.ts +44 -0
- package/src/lib/inspector-expanded.ts +36 -0
- package/src/lib/interaction/scope.ts +18 -0
- package/src/lib/keyboard-pan.test.ts +129 -7
- package/src/lib/keyboard-pan.ts +161 -12
- package/src/lib/live-gesture-round-trip.test.ts +360 -0
- package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
- package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
- package/src/lib/lot/clean-ring.test.ts +186 -0
- package/src/lib/lot/clean-ring.ts +217 -0
- package/src/lib/lot/dossier.test.ts +202 -0
- package/src/lib/lot/dossier.ts +498 -0
- package/src/lib/lot/drop-in.test.ts +326 -0
- package/src/lib/lot/drop-in.ts +407 -0
- package/src/lib/lot/index.ts +43 -0
- package/src/lib/lot/lot-patch.test.ts +340 -0
- package/src/lib/lot/lot-patch.ts +394 -0
- package/src/lib/lot/parcel-provider.ts +43 -0
- package/src/lib/lot/terrain.test.ts +161 -0
- package/src/lib/lot/terrain.ts +266 -0
- package/src/lib/material-catalog-model.ts +3 -1
- package/src/lib/material-paint-panel-model.ts +9 -5
- package/src/lib/material-paint.ts +4 -0
- package/src/lib/mezzanine-actions.test.ts +342 -0
- package/src/lib/mezzanine-draft.test.ts +501 -0
- package/src/lib/mezzanine-draft.ts +278 -0
- package/src/lib/mezzanine-messages.ts +26 -0
- package/src/lib/mezzanine-selection.test.ts +207 -0
- package/src/lib/mezzanine-stairs.ts +46 -0
- package/src/lib/model-export.ts +3 -1
- package/src/lib/node-action-movement.ts +17 -6
- package/src/lib/node-display-label.ts +37 -0
- package/src/lib/orbit-floor-clearance.test.ts +165 -0
- package/src/lib/orbit-floor-clearance.ts +106 -0
- package/src/lib/paint-commit-route.test.ts +37 -0
- package/src/lib/paint-commit-route.ts +17 -0
- package/src/lib/paint-eyedropper.test.tsx +165 -0
- package/src/lib/paint-eyedropper.ts +99 -0
- package/src/lib/paint-pass-through.test.ts +111 -0
- package/src/lib/paint-pass-through.ts +76 -0
- package/src/lib/paint-region-hover.ts +25 -0
- package/src/lib/paint-region-mode.ts +154 -0
- package/src/lib/paint-regions.test.ts +166 -0
- package/src/lib/paint-regions.ts +192 -0
- package/src/lib/paint-scope.test.ts +317 -35
- package/src/lib/paint-scope.ts +266 -25
- package/src/lib/panel-rows.ts +21 -0
- package/src/lib/parametric-node-update.ts +1 -1
- package/src/lib/plate-paint-affected.test.ts +300 -0
- package/src/lib/plate-paint-affected.ts +177 -0
- package/src/lib/plugin-chat.ts +50 -0
- package/src/lib/plugin-panels.test.ts +25 -0
- package/src/lib/plugin-panels.ts +94 -0
- package/src/lib/print-shell-compiler.ts +1 -0
- package/src/lib/registered-draft-snap.ts +12 -0
- package/src/lib/roof-duplication.ts +10 -2
- package/src/lib/room-assembly-overlay.test.ts +275 -0
- package/src/lib/room-assembly-overlay.ts +266 -0
- package/src/lib/room-built-on.test.ts +379 -0
- package/src/lib/room-built-on.ts +138 -0
- package/src/lib/room-construction-commands.ts +712 -0
- package/src/lib/room-construction.test.ts +746 -0
- package/src/lib/room-construction.ts +385 -0
- package/src/lib/room-controls-phase6.test.ts +630 -0
- package/src/lib/room-divide-session.ts +523 -0
- package/src/lib/room-handle-drag.ts +1068 -0
- package/src/lib/room-name-catalog.test.ts +90 -0
- package/src/lib/room-name-catalog.ts +168 -0
- package/src/lib/room-push-dimensions.test.ts +311 -0
- package/src/lib/room-push-dimensions.ts +251 -0
- package/src/lib/room-selection-commands.ts +62 -0
- package/src/lib/room-selection.test.ts +973 -0
- package/src/lib/room-selection.ts +417 -0
- package/src/lib/room-structure-commands.ts +94 -0
- package/src/lib/room-structure.test.ts +921 -0
- package/src/lib/room-transform-session.ts +570 -0
- package/src/lib/room-zone-routing.test.ts +294 -0
- package/src/lib/room-zone-routing.ts +197 -0
- package/src/lib/scene-clipboard.test.ts +364 -0
- package/src/lib/scene-clipboard.ts +229 -48
- package/src/lib/scene-material-refs.test.ts +79 -0
- package/src/lib/scene-material-refs.ts +79 -0
- package/src/lib/scene.test.ts +24 -36
- package/src/lib/scene.ts +33 -57
- package/src/lib/selection-routing.test.ts +129 -45
- package/src/lib/selection-routing.ts +53 -74
- package/src/lib/session-writes.test.ts +120 -0
- package/src/lib/session-writes.ts +205 -0
- package/src/lib/sidebar-panel.ts +62 -0
- package/src/lib/site-boundary.test.ts +4 -6
- package/src/lib/site-boundary.ts +1 -1
- package/src/lib/snapping-mode.test.ts +4 -0
- package/src/lib/snapping-mode.ts +8 -1
- package/src/lib/terrace-draft.test.ts +145 -0
- package/src/lib/terrace-draft.ts +213 -0
- package/src/lib/tool-cursor-icon.test.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +11 -0
- package/src/lib/typing-target.ts +9 -0
- package/src/lib/units.ts +77 -39
- package/src/lib/vector-edges.test.ts +58 -0
- package/src/lib/vector-edges.ts +400 -0
- package/src/lib/viewer-selection.test.ts +577 -0
- package/src/lib/viewer-selection.ts +569 -0
- package/src/lib/wall-draw-variant.test.ts +71 -0
- package/src/lib/wall-draw-variant.ts +91 -0
- package/src/lib/wall-paint-region-session.ts +246 -0
- package/src/lib/wall-paint-region.test.ts +373 -0
- package/src/lib/wall-polygon-draft.test.ts +262 -0
- package/src/lib/wall-polygon-draft.ts +286 -0
- package/src/lib/wall-push.test.ts +453 -0
- package/src/lib/wall-region-face.ts +352 -0
- package/src/lib/wall-region-handles.test.ts +304 -0
- package/src/lib/wall-region-handles.ts +368 -0
- package/src/lib/wall-region-list.test.ts +119 -0
- package/src/lib/wall-region-list.ts +136 -0
- package/src/lib/wall-region-snap.ts +110 -0
- package/src/lib/window-interaction.ts +14 -0
- package/src/lib/zone-content.ts +4 -1
- package/src/lib/zone-name.test.ts +17 -0
- package/src/lib/zone-name.ts +17 -0
- package/src/store/opens-in-select-mode.test.ts +223 -0
- package/src/store/paint-mode.test.ts +188 -0
- package/src/store/terrain-sculpt-mode.test.ts +19 -21
- package/src/store/tool-mode.test.ts +137 -6
- package/src/store/use-delete-confirmation.ts +28 -1
- package/src/store/use-drawing-view.test.ts +24 -3
- package/src/store/use-drawing-view.ts +29 -1
- package/src/store/use-editor.tsx +432 -321
- package/src/store/use-floorplan-draft-preview.ts +13 -0
- package/src/store/use-floorplan-marquee.ts +12 -2
- package/src/store/use-quick-measurement-hud.ts +3 -3
- package/src/store/use-stair-build-preview.ts +13 -5
- package/src/store/use-wall-move-ghosts.ts +4 -2
- package/src/store/use-wall-region-handles-selection.test.ts +222 -0
- package/src/store/use-wall-region-handles-selection.ts +139 -0
- package/src/store/view-layout.test.ts +175 -0
- package/src/store/view-layout.ts +196 -0
- package/src/test-utils/immediate-animation-frames.ts +16 -0
- package/src/test-utils/live-gesture-round-trip.ts +224 -0
- package/src/test-utils/selection-harness.ts +82 -0
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
- package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
- package/src/components/tools/fence/fence-drafting.test.ts +0 -100
- package/src/components/tools/fence/fence-drafting.ts +0 -266
- package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
- package/src/components/viewer/viewer-units-panel.tsx +0 -116
- package/src/lib/floorplan/stairs.ts +0 -489
- package/src/lib/level-duplication.test.ts +0 -144
- package/src/lib/level-duplication.ts +0 -195
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
|
+
floorPlacedCollides,
|
|
6
7
|
nodeRegistry,
|
|
7
8
|
type TerrainVerb,
|
|
8
9
|
useScene,
|
|
@@ -20,10 +21,50 @@ import {
|
|
|
20
21
|
resolveSelectModeHelpHints,
|
|
21
22
|
} from '../../../lib/contextual-help'
|
|
22
23
|
import { getContextualHelpNodeExtension } from '../../../lib/contextual-help-extension'
|
|
23
|
-
import { continuationContextOf } from '../../../lib/continuation'
|
|
24
|
+
import { continuationContextOf, keyCyclableContinuationContext } from '../../../lib/continuation'
|
|
24
25
|
import { canDirectMoveNode, canDirectRotateNode } from '../../../lib/direct-manipulation'
|
|
26
|
+
import {
|
|
27
|
+
DIVIDE_ROOM_HUD_TITLE,
|
|
28
|
+
MEZZANINE_HUD_TITLE,
|
|
29
|
+
OPENING_HUD_TITLE,
|
|
30
|
+
TERRACE_HUD_TITLE,
|
|
31
|
+
MOVE_ROOM_HUD_TITLE,
|
|
32
|
+
MOVE_SELECTION_HUD_TITLE,
|
|
33
|
+
nodeKindHudTitle,
|
|
34
|
+
ERASE_HUD_TITLE,
|
|
35
|
+
PAINT_HUD_TITLE,
|
|
36
|
+
PICK_MATERIAL_HUD_TITLE,
|
|
37
|
+
paintRegionHudTitle,
|
|
38
|
+
RESIZE_HUD_TITLE,
|
|
39
|
+
ROTATE_HUD_TITLE,
|
|
40
|
+
SELECT_HUD_TITLE,
|
|
41
|
+
SMART_MEASURE_HUD_TITLE,
|
|
42
|
+
terrainHudTitle,
|
|
43
|
+
toolHudTitle,
|
|
44
|
+
} from '../../../lib/hud-title'
|
|
25
45
|
import type { ReshapeKind } from '../../../lib/interaction/scope'
|
|
46
|
+
import {
|
|
47
|
+
type MezzanineShape,
|
|
48
|
+
useMezzanineDraft,
|
|
49
|
+
} from '../../../lib/mezzanine-draft'
|
|
50
|
+
import {
|
|
51
|
+
TERRACE_SHAPE_HINT,
|
|
52
|
+
type TerraceShape,
|
|
53
|
+
useTerraceDraft,
|
|
54
|
+
} from '../../../lib/terrace-draft'
|
|
55
|
+
import {
|
|
56
|
+
type OpeningShape,
|
|
57
|
+
useOpeningDraft,
|
|
58
|
+
} from '../../../lib/floor-opening-draft'
|
|
26
59
|
import { isFreshPlacementMetadata } from '../../../lib/placement-metadata'
|
|
60
|
+
import { MEZZANINE_EDGE_DRAG_LABEL, ROOM_ELEVATION_DRAG_LABEL } from '../../../lib/room-handle-drag'
|
|
61
|
+
import { ROOM_MOVE_DRAG_LABEL } from '../../../lib/room-transform-session'
|
|
62
|
+
import { usePaintRegionHovering } from '../../../lib/paint-region-hover'
|
|
63
|
+
import {
|
|
64
|
+
type PaintMode,
|
|
65
|
+
paintRegionHoverHint,
|
|
66
|
+
usePaintRegionMode,
|
|
67
|
+
} from '../../../lib/paint-region-mode'
|
|
27
68
|
import { snapContextOf } from '../../../lib/snapping-mode'
|
|
28
69
|
import useEditor, { getActiveContinuationContext } from '../../../store/use-editor'
|
|
29
70
|
import useInteractionScope, {
|
|
@@ -33,6 +74,7 @@ import useInteractionScope, {
|
|
|
33
74
|
} from '../../../store/use-interaction-scope'
|
|
34
75
|
import { BuildingHelper } from './building-helper'
|
|
35
76
|
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
77
|
+
import { transientDraftId, usePlacementNotice } from '../../../hooks/use-placement-notice'
|
|
36
78
|
import { ItemHelper } from './item-helper'
|
|
37
79
|
import { RegisteredToolHelper } from './registered-tool-helper'
|
|
38
80
|
|
|
@@ -91,6 +133,89 @@ type ActiveModifierKeys = {
|
|
|
91
133
|
}
|
|
92
134
|
|
|
93
135
|
const EMPTY_CONTEXTUAL_HINTS: ContextualShortcutHint[] = []
|
|
136
|
+
|
|
137
|
+
// The paint tool's region sub-modes: what one gesture draws, then Esc. A
|
|
138
|
+
// refused gesture (the per-face cap) shows as the first row.
|
|
139
|
+
/** What a click does in each whole-surface sub-mode; only painting teaches the held eyedropper. */
|
|
140
|
+
export function paintHints(mode: PaintMode): ContextualShortcutHint[] {
|
|
141
|
+
if (mode === 'pick')
|
|
142
|
+
return [
|
|
143
|
+
{ keys: ['Left click'], label: 'Paint with this material' },
|
|
144
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
145
|
+
]
|
|
146
|
+
if (mode === 'erase')
|
|
147
|
+
return [{ keys: ['Left click'], label: 'Remove a painted part, or reset a surface' }]
|
|
148
|
+
if (mode === 'surface') return [{ keys: [['Alt', 'Option']], label: 'Hold to pick a material' }]
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function paintRegionHints(
|
|
153
|
+
mode: 'rectangle' | 'polygon',
|
|
154
|
+
notice: string | null,
|
|
155
|
+
hovering = true,
|
|
156
|
+
): ContextualShortcutHint[] {
|
|
157
|
+
const gesture: Record<typeof mode, ContextualShortcutHint> = {
|
|
158
|
+
rectangle: { keys: ['Drag'], label: 'Press anywhere, drag to the opposite corner' },
|
|
159
|
+
polygon: { keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
160
|
+
}
|
|
161
|
+
// Off anything the sub-mode draws on, the HUD says where to go instead.
|
|
162
|
+
if (!hovering)
|
|
163
|
+
return [
|
|
164
|
+
...(notice ? [{ keys: ['!'], label: notice, active: true }] : []),
|
|
165
|
+
{ keys: ['Hover'], label: paintRegionHoverHint(mode), active: true },
|
|
166
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
167
|
+
]
|
|
168
|
+
return [
|
|
169
|
+
...(notice ? [{ keys: ['!'], label: notice, active: true }] : []),
|
|
170
|
+
gesture[mode],
|
|
171
|
+
...(mode === 'polygon' ? [{ keys: ['Backspace'], label: 'Remove last point' }] : []),
|
|
172
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
// "Add mezzanine": the outline gesture for the shape picked in the room panel,
|
|
176
|
+
// then Esc; the snapping chip sits below.
|
|
177
|
+
export function mezzanineHints(shape: MezzanineShape): ContextualShortcutHint[] {
|
|
178
|
+
return shape === 'rectangle'
|
|
179
|
+
? [
|
|
180
|
+
{ keys: ['Left click'], label: 'Set one corner, then the opposite' },
|
|
181
|
+
{ keys: ['Drag'], label: 'Draw the mezzanine' },
|
|
182
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
183
|
+
]
|
|
184
|
+
: [
|
|
185
|
+
{ keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
186
|
+
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
187
|
+
{ keys: ['Enter'], label: 'Finish' },
|
|
188
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
/** "Cut opening": the outline gesture for the chosen shape, then Esc. */
|
|
192
|
+
export function openingHints(shape: OpeningShape): ContextualShortcutHint[] {
|
|
193
|
+
return mezzanineHints(shape).map((hint) =>
|
|
194
|
+
hint.label === 'Draw the mezzanine' ? { ...hint, label: 'Draw the opening' } : hint,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
const TERRACE_CHIP_HINTS = [TERRACE_SHAPE_HINT]
|
|
198
|
+
// The terrace tool: the outline gesture for the chosen shape, then Esc.
|
|
199
|
+
export function terraceHints(shape: TerraceShape): ContextualShortcutHint[] {
|
|
200
|
+
return shape === 'rectangle'
|
|
201
|
+
? [
|
|
202
|
+
{ keys: ['Left click'], label: 'Set one corner, then the opposite' },
|
|
203
|
+
{ keys: ['Drag'], label: 'Draw the terrace' },
|
|
204
|
+
{ keys: ['Esc'], label: 'Done' },
|
|
205
|
+
]
|
|
206
|
+
: [
|
|
207
|
+
{ keys: ['Left click'], label: 'Add point · click the first to close' },
|
|
208
|
+
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
209
|
+
{ keys: ['Enter'], label: 'Finish' },
|
|
210
|
+
{ keys: ['Esc'], label: 'Done' },
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
export const ROOM_DIVIDE_HINTS: ContextualShortcutHint[] = [
|
|
214
|
+
{ keys: ['Left click'], label: 'Add point' },
|
|
215
|
+
{ keys: ['Backspace'], label: 'Remove last point' },
|
|
216
|
+
{ keys: ['Enter'], label: 'Finish at the edge' },
|
|
217
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
218
|
+
]
|
|
94
219
|
const NO_CONTEXTUAL_HELP_SUBSCRIPTION = () => () => {}
|
|
95
220
|
|
|
96
221
|
function useActiveModifierKeys(): ActiveModifierKeys {
|
|
@@ -129,6 +254,43 @@ function useActiveModifierKeys(): ActiveModifierKeys {
|
|
|
129
254
|
return modifiers
|
|
130
255
|
}
|
|
131
256
|
|
|
257
|
+
export type MezzanineGesture = 'raise' | 'resize' | 'move'
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A handle drag on a mezzanine — its floor handle, an edge arrow, or its pick-up
|
|
261
|
+
* (Move / Duplicate) — or null. Its HUD names the mezzanine, not "Resize" or
|
|
262
|
+
* "Move room".
|
|
263
|
+
*/
|
|
264
|
+
export function mezzanineGesture(
|
|
265
|
+
drag: { nodeId: string; label: string } | null,
|
|
266
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
267
|
+
): MezzanineGesture | null {
|
|
268
|
+
if (!drag) return null
|
|
269
|
+
const zone = nodes[drag.nodeId]
|
|
270
|
+
if (zone?.type !== 'zone' || zone.floor?.support !== 'open') return null
|
|
271
|
+
if (drag.label === ROOM_ELEVATION_DRAG_LABEL) return 'raise'
|
|
272
|
+
if (drag.label === MEZZANINE_EDGE_DRAG_LABEL) return 'resize'
|
|
273
|
+
if (drag.label === ROOM_MOVE_DRAG_LABEL) return 'move'
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export const MEZZANINE_GESTURE_HINTS: Record<MezzanineGesture, ContextualShortcutHint[]> = {
|
|
278
|
+
raise: [
|
|
279
|
+
{ keys: ['Drag'], label: 'Raise or lower' },
|
|
280
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
281
|
+
],
|
|
282
|
+
resize: [
|
|
283
|
+
{ keys: ['Drag'], label: 'Resize' },
|
|
284
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
285
|
+
],
|
|
286
|
+
move: [
|
|
287
|
+
{ keys: ['Left click'], label: 'Place inside the room' },
|
|
288
|
+
{ keys: [['R', 'T']], label: 'Rotate ±45°' },
|
|
289
|
+
{ keys: ['Alt'], label: 'Free move' },
|
|
290
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
291
|
+
],
|
|
292
|
+
}
|
|
293
|
+
|
|
132
294
|
export function HelperManager() {
|
|
133
295
|
const mode = useEditor((s) => s.mode)
|
|
134
296
|
const tool = useEditor((s) => s.tool)
|
|
@@ -136,11 +298,24 @@ export function HelperManager() {
|
|
|
136
298
|
const terrainSampling = useEditor((s) => s.terrainSampling)
|
|
137
299
|
const isFirstPersonMode = useEditor((s) => s.isFirstPersonMode)
|
|
138
300
|
const measurementToolKind = useEditor((s) => s.toolDefaults.measurement?.kind)
|
|
301
|
+
const wallMode = useEditor((s) => s.continuationByContext.wall)
|
|
139
302
|
const workspaceMode = useEditor((s) => s.workspaceMode)
|
|
140
303
|
const scope = useInteractionScope((s) => s.scope)
|
|
304
|
+
const paintRegionMode = usePaintRegionMode((s) => s.mode)
|
|
305
|
+
const paintRegionDrawing = paintRegionMode === 'rectangle' || paintRegionMode === 'polygon'
|
|
306
|
+
const paintRegionNotice = usePaintRegionMode((s) => s.notice)
|
|
307
|
+
const paintRegionHovering = usePaintRegionHovering()
|
|
308
|
+
const mezzanineShape = useMezzanineDraft((s) => (s.host ? s.shape : null))
|
|
309
|
+
const openingShape = useOpeningDraft((s) => (s.host ? s.shape : null))
|
|
310
|
+
const terraceShape = useTerraceDraft((s) => (s.host ? s.shape : null))
|
|
141
311
|
const movingNode = useMovingNode()
|
|
312
|
+
// A tool whose kind gives placement notices: its transient draft is the node in hand.
|
|
313
|
+
const toolNotices = !!(tool && nodeRegistry.get(tool)?.placementNotice)
|
|
314
|
+
const toolDraftId = useScene((s) => (toolNotices ? transientDraftId(s.nodes) : null))
|
|
315
|
+
const placementNotice = usePlacementNotice(movingNode?.id ?? toolDraftId)
|
|
142
316
|
const reshapingNode = useReshapingNode()
|
|
143
317
|
const activeHandleDrag = useActiveHandleDrag()
|
|
318
|
+
const mezzanineHandle = useScene((s) => mezzanineGesture(activeHandleDrag, s.nodes))
|
|
144
319
|
const selectedIds = useViewer((s) => s.selection.selectedIds)
|
|
145
320
|
const isMobile = useIsMobile()
|
|
146
321
|
const modifiers = useActiveModifierKeys()
|
|
@@ -180,11 +355,14 @@ export function HelperManager() {
|
|
|
180
355
|
return node ? nodeRegistry.get(node.type)?.snapProfile : undefined
|
|
181
356
|
},
|
|
182
357
|
draftDirectionalOf: (typeOrTool) => nodeRegistry.get(typeOrTool)?.snapDraftDirectional ?? true,
|
|
358
|
+
paintRegion: mode === 'material-paint' && paintRegionDrawing,
|
|
183
359
|
}),
|
|
184
|
-
[scope, mode, tool],
|
|
360
|
+
[scope, mode, tool, paintRegionDrawing],
|
|
185
361
|
)
|
|
362
|
+
// Contexts whose mode is picked in the Build panel (the wall's Rooms variant)
|
|
363
|
+
// get no HUD chip: the header names the variant instead.
|
|
186
364
|
const continuationContext = useMemo(
|
|
187
|
-
() => getActiveContinuationContext(),
|
|
365
|
+
() => keyCyclableContinuationContext(getActiveContinuationContext()),
|
|
188
366
|
[scope, mode, tool],
|
|
189
367
|
)
|
|
190
368
|
const selectModeHints = useMemo(() => {
|
|
@@ -226,6 +404,7 @@ export function HelperManager() {
|
|
|
226
404
|
<ContextualHelperPanel
|
|
227
405
|
hints={resolveRotateHandleHelpHints(modifiers.alt)}
|
|
228
406
|
snapContext={snapContext}
|
|
407
|
+
title={ROTATE_HUD_TITLE}
|
|
229
408
|
/>
|
|
230
409
|
)
|
|
231
410
|
}
|
|
@@ -238,6 +417,79 @@ export function HelperManager() {
|
|
|
238
417
|
<ContextualHelperPanel
|
|
239
418
|
hints={[{ keys: ['R / T'], label: 'Rotate the selection ±45°' }]}
|
|
240
419
|
snapContext={snapContext}
|
|
420
|
+
title={MOVE_SELECTION_HUD_TITLE}
|
|
421
|
+
/>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (mezzanineHandle) {
|
|
426
|
+
return (
|
|
427
|
+
<ContextualHelperPanel
|
|
428
|
+
hints={MEZZANINE_GESTURE_HINTS[mezzanineHandle]}
|
|
429
|
+
snapContext={snapContext}
|
|
430
|
+
title={MEZZANINE_HUD_TITLE}
|
|
431
|
+
/>
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// A picked-up room (Move / Duplicate / Rotate from its pill): the same keys
|
|
436
|
+
// as a group pick-up.
|
|
437
|
+
if (activeHandleDrag?.label === ROOM_MOVE_DRAG_LABEL) {
|
|
438
|
+
return (
|
|
439
|
+
<ContextualHelperPanel
|
|
440
|
+
hints={[
|
|
441
|
+
{ keys: ['Left click'], label: 'Place' },
|
|
442
|
+
{ keys: [['R', 'T']], label: 'Rotate ±45°' },
|
|
443
|
+
{ keys: ['Alt'], label: 'Free move · slide doors and windows' },
|
|
444
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
445
|
+
]}
|
|
446
|
+
snapContext={snapContext}
|
|
447
|
+
title={MOVE_ROOM_HUD_TITLE}
|
|
448
|
+
/>
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
// A paint region gesture holds a handle-drag scope while it draws; its HUD
|
|
452
|
+
// stays the region one (Backspace / close hints for a polygon mid-draft).
|
|
453
|
+
if (mode === 'material-paint' && paintRegionDrawing) {
|
|
454
|
+
return (
|
|
455
|
+
<ContextualHelperPanel
|
|
456
|
+
hints={paintRegionHints(paintRegionMode, paintRegionNotice, paintRegionHovering)}
|
|
457
|
+
snapContext={snapContext}
|
|
458
|
+
title={paintRegionHudTitle(paintRegionMode)}
|
|
459
|
+
/>
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// The terrace tool draws like "Add mezzanine": same outline gestures and chip.
|
|
464
|
+
if (terraceShape) {
|
|
465
|
+
return (
|
|
466
|
+
<ContextualHelperPanel
|
|
467
|
+
chipHints={TERRACE_CHIP_HINTS}
|
|
468
|
+
hints={terraceHints(terraceShape)}
|
|
469
|
+
snapContext={snapContext}
|
|
470
|
+
title={TERRACE_HUD_TITLE}
|
|
471
|
+
/>
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (openingShape) {
|
|
476
|
+
return (
|
|
477
|
+
<ContextualHelperPanel
|
|
478
|
+
hints={openingHints(openingShape)}
|
|
479
|
+
snapContext={snapContext}
|
|
480
|
+
title={OPENING_HUD_TITLE}
|
|
481
|
+
/>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// "Add mezzanine" holds a handle-drag scope on its host room for the whole
|
|
486
|
+
// tool, so it must win over the generic resize HUD below.
|
|
487
|
+
if (mezzanineShape) {
|
|
488
|
+
return (
|
|
489
|
+
<ContextualHelperPanel
|
|
490
|
+
hints={mezzanineHints(mezzanineShape)}
|
|
491
|
+
snapContext={snapContext}
|
|
492
|
+
title={MEZZANINE_HUD_TITLE}
|
|
241
493
|
/>
|
|
242
494
|
)
|
|
243
495
|
}
|
|
@@ -254,6 +506,7 @@ export function HelperManager() {
|
|
|
254
506
|
{ keys: ['Esc'], label: 'Cancel' },
|
|
255
507
|
]}
|
|
256
508
|
snapContext={snapContext}
|
|
509
|
+
title={RESIZE_HUD_TITLE}
|
|
257
510
|
/>
|
|
258
511
|
)
|
|
259
512
|
}
|
|
@@ -266,30 +519,50 @@ export function HelperManager() {
|
|
|
266
519
|
const hints = reshapingNode
|
|
267
520
|
? nodeRegistry.get(reshapingNode.type)?.affordanceHints?.[scope.reshape]
|
|
268
521
|
: undefined
|
|
522
|
+
const title = reshapingNode
|
|
523
|
+
? nodeKindHudTitle(reshapingNode.type, scope.reshape === 'split' ? 'Split' : 'Edit')
|
|
524
|
+
: null
|
|
269
525
|
if (hints) {
|
|
270
526
|
return (
|
|
271
|
-
<RegisteredToolHelper
|
|
527
|
+
<RegisteredToolHelper
|
|
528
|
+
hints={hints}
|
|
529
|
+
shiftPressed={modifiers.shift}
|
|
530
|
+
snapContext={snapContext}
|
|
531
|
+
title={title}
|
|
532
|
+
/>
|
|
272
533
|
)
|
|
273
534
|
}
|
|
274
|
-
return
|
|
535
|
+
return (
|
|
536
|
+
<ContextualHelperPanel
|
|
537
|
+
hints={reshapingHints(scope.reshape)}
|
|
538
|
+
snapContext={snapContext}
|
|
539
|
+
title={title}
|
|
540
|
+
/>
|
|
541
|
+
)
|
|
275
542
|
}
|
|
276
543
|
|
|
277
544
|
if (movingNode) {
|
|
278
|
-
if (movingNode.type === 'building') return <BuildingHelper showRotate />
|
|
279
545
|
// A fresh placement (e.g. a positioned preset like a shelf) advertises its
|
|
280
546
|
// once/repeat continuation, exactly like the GLB item tool — but an existing
|
|
281
547
|
// node being *moved* is not a placement, so it gets no continuation chip.
|
|
282
|
-
const
|
|
283
|
-
|
|
548
|
+
const isFreshPlacement = isFreshPlacementMetadata(movingNode.metadata)
|
|
549
|
+
const movingTitle = nodeKindHudTitle(movingNode.type, isFreshPlacement ? undefined : 'Move')
|
|
550
|
+
if (movingNode.type === 'building') return <BuildingHelper showRotate title={movingTitle} />
|
|
551
|
+
const movingContinuationContext = isFreshPlacement
|
|
552
|
+
? keyCyclableContinuationContext(continuationContextOf(movingNode.type))
|
|
284
553
|
: null
|
|
285
|
-
const collisionValidatesDrop =
|
|
286
|
-
nodeRegistry.get(movingNode.type)?.capabilities.floorPlaced
|
|
554
|
+
const collisionValidatesDrop = floorPlacedCollides(
|
|
555
|
+
nodeRegistry.get(movingNode.type)?.capabilities.floorPlaced,
|
|
556
|
+
movingNode,
|
|
557
|
+
)
|
|
287
558
|
return (
|
|
288
559
|
<ItemHelper
|
|
289
560
|
continuationContext={movingContinuationContext}
|
|
561
|
+
notice={placementNotice?.line}
|
|
290
562
|
showEsc
|
|
291
563
|
showForce={collisionValidatesDrop}
|
|
292
564
|
snapContext={snapContext}
|
|
565
|
+
title={movingTitle}
|
|
293
566
|
/>
|
|
294
567
|
)
|
|
295
568
|
}
|
|
@@ -298,7 +571,19 @@ export function HelperManager() {
|
|
|
298
571
|
// only contextual control here. The chip hides itself for targets that only
|
|
299
572
|
// paint one surface, so this renders nothing until a scoped target is active.
|
|
300
573
|
if (mode === 'material-paint') {
|
|
301
|
-
return
|
|
574
|
+
return (
|
|
575
|
+
<ContextualHelperPanel
|
|
576
|
+
hints={paintHints(paintRegionMode)}
|
|
577
|
+
showPaintScope
|
|
578
|
+
title={
|
|
579
|
+
paintRegionMode === 'pick'
|
|
580
|
+
? PICK_MATERIAL_HUD_TITLE
|
|
581
|
+
: paintRegionMode === 'erase'
|
|
582
|
+
? ERASE_HUD_TITLE
|
|
583
|
+
: PAINT_HUD_TITLE
|
|
584
|
+
}
|
|
585
|
+
/>
|
|
586
|
+
)
|
|
302
587
|
}
|
|
303
588
|
|
|
304
589
|
// Sculpt mode. The HUD is what makes a sustained brush mode legible: it names
|
|
@@ -307,18 +592,41 @@ export function HelperManager() {
|
|
|
307
592
|
// mode-specific — bracket resize, and an Esc that abandons the stroke rather
|
|
308
593
|
// than exiting the mode.
|
|
309
594
|
if (mode === 'terrain-sculpt') {
|
|
310
|
-
return
|
|
595
|
+
return (
|
|
596
|
+
<ContextualHelperPanel
|
|
597
|
+
hints={terrainSculptHints(terrainVerb, terrainSampling)}
|
|
598
|
+
title={terrainHudTitle(terrainVerb)}
|
|
599
|
+
/>
|
|
600
|
+
)
|
|
311
601
|
}
|
|
312
602
|
|
|
313
603
|
if (scope.kind === 'mesh-editing') {
|
|
314
|
-
return
|
|
604
|
+
return (
|
|
605
|
+
<ContextualHelperPanel
|
|
606
|
+
hints={contextualEditHints}
|
|
607
|
+
snapContext={snapContext}
|
|
608
|
+
title={contextualHelpNode ? nodeKindHudTitle(contextualHelpNode.type, 'Edit') : null}
|
|
609
|
+
/>
|
|
610
|
+
)
|
|
315
611
|
}
|
|
316
612
|
|
|
317
613
|
|
|
614
|
+
// Divide draws a separator path across the selected room — it reads as a
|
|
615
|
+
// wall draft: the wall's snapping chips (Shift / Ctrl) plus the path keys.
|
|
616
|
+
if (scope.kind === 'room-divide') {
|
|
617
|
+
return (
|
|
618
|
+
<ContextualHelperPanel
|
|
619
|
+
hints={ROOM_DIVIDE_HINTS}
|
|
620
|
+
snapContext={snapContext}
|
|
621
|
+
title={DIVIDE_ROOM_HUD_TITLE}
|
|
622
|
+
/>
|
|
623
|
+
)
|
|
624
|
+
}
|
|
625
|
+
|
|
318
626
|
// Idle select only — an active scope (handle-drag, box-select, …) must not show
|
|
319
627
|
// the idle selection hints.
|
|
320
628
|
if (mode === 'select' && scope.kind === 'idle') {
|
|
321
|
-
return <ContextualHelperPanel hints={selectModeHints} />
|
|
629
|
+
return <ContextualHelperPanel hints={selectModeHints} title={SELECT_HUD_TITLE} />
|
|
322
630
|
}
|
|
323
631
|
|
|
324
632
|
if (tool === 'measurement' && measurementToolKind === 'smart') {
|
|
@@ -329,6 +637,7 @@ export function HelperManager() {
|
|
|
329
637
|
{ keys: ['Click'], label: 'Pin measurement lens' },
|
|
330
638
|
{ keys: ['Esc'], label: 'Exit smart measure' },
|
|
331
639
|
]}
|
|
640
|
+
title={SMART_MEASURE_HUD_TITLE}
|
|
332
641
|
/>
|
|
333
642
|
)
|
|
334
643
|
}
|
|
@@ -346,8 +655,10 @@ export function HelperManager() {
|
|
|
346
655
|
<RegisteredToolHelper
|
|
347
656
|
continuationContext={continuationContext}
|
|
348
657
|
hints={hints}
|
|
658
|
+
notice={placementNotice?.line ?? null}
|
|
349
659
|
shiftPressed={modifiers.shift}
|
|
350
660
|
snapContext={snapContext}
|
|
661
|
+
title={toolHudTitle(tool, wallMode)}
|
|
351
662
|
/>
|
|
352
663
|
)
|
|
353
664
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContinuationContext } from '../../../lib/continuation'
|
|
2
|
+
import type { HudTitle } from '../../../lib/hud-title'
|
|
2
3
|
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
3
4
|
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
4
5
|
|
|
@@ -11,6 +12,9 @@ interface ItemHelperProps {
|
|
|
11
12
|
// Set for a fresh point-kind placement (e.g. a positioned preset) so the
|
|
12
13
|
// once/repeat continuation chip shows; null for an existing-node move.
|
|
13
14
|
continuationContext?: ContinuationContext | null
|
|
15
|
+
title?: HudTitle | null
|
|
16
|
+
// Why the item would not fit where it is (a door's way, a room too small): a warning, not a block.
|
|
17
|
+
notice?: string | null
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
// Snapping mode is the chip on the right (Shift cycles it), so it's not repeated
|
|
@@ -20,10 +24,13 @@ export function ItemHelper({
|
|
|
20
24
|
snapContext,
|
|
21
25
|
showForce,
|
|
22
26
|
continuationContext = null,
|
|
27
|
+
title = null,
|
|
28
|
+
notice = null,
|
|
23
29
|
}: ItemHelperProps) {
|
|
24
30
|
return (
|
|
25
31
|
<ContextualHelperPanel
|
|
26
32
|
continuationContext={continuationContext}
|
|
33
|
+
notice={notice}
|
|
27
34
|
hints={[
|
|
28
35
|
{ keys: ['Left click'], label: 'Place' },
|
|
29
36
|
{ keys: ['R', 'T'], label: 'Rotate' },
|
|
@@ -31,6 +38,7 @@ export function ItemHelper({
|
|
|
31
38
|
{ keys: [showEsc ? 'Esc' : 'Right click'], label: 'Cancel' },
|
|
32
39
|
]}
|
|
33
40
|
snapContext={snapContext}
|
|
41
|
+
title={title}
|
|
34
42
|
/>
|
|
35
43
|
)
|
|
36
44
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolHint } from '@pascal-app/core'
|
|
2
2
|
import type { ContinuationContext } from '../../../lib/continuation'
|
|
3
|
+
import type { HudTitle } from '../../../lib/hud-title'
|
|
3
4
|
import type { SnapContext } from '../../../lib/snapping-mode'
|
|
4
5
|
import { useVisibleToolHints } from '../../../lib/panel-tool-options'
|
|
5
6
|
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
@@ -18,14 +19,18 @@ export function RegisteredToolHelper({
|
|
|
18
19
|
shiftPressed = false,
|
|
19
20
|
snapContext = null,
|
|
20
21
|
continuationContext = null,
|
|
22
|
+
title = null,
|
|
23
|
+
notice = null,
|
|
21
24
|
}: {
|
|
22
25
|
hints: ToolHint[]
|
|
23
26
|
shiftPressed?: boolean
|
|
24
27
|
snapContext?: SnapContext | null
|
|
25
28
|
continuationContext?: ContinuationContext | null
|
|
29
|
+
title?: HudTitle | null
|
|
30
|
+
notice?: string | null
|
|
26
31
|
}) {
|
|
27
32
|
const visible = useVisibleToolHints(hints)
|
|
28
|
-
if (visible.length === 0 && !snapContext && !continuationContext) return null
|
|
33
|
+
if (visible.length === 0 && !snapContext && !continuationContext && !notice) return null
|
|
29
34
|
// Hints carrying a live-state `chip` render as mode chips next to the
|
|
30
35
|
// snapping / continuation rows; the rest stay static key rows.
|
|
31
36
|
const chipHints = visible.filter((hint) => hint.chip)
|
|
@@ -44,7 +49,9 @@ export function RegisteredToolHelper({
|
|
|
44
49
|
}
|
|
45
50
|
})}
|
|
46
51
|
continuationContext={continuationContext}
|
|
52
|
+
notice={notice}
|
|
47
53
|
snapContext={snapContext}
|
|
54
|
+
title={title}
|
|
48
55
|
/>
|
|
49
56
|
)
|
|
50
57
|
}
|
|
@@ -3,6 +3,50 @@
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
4
|
import type { IconRef } from '@pascal-app/core'
|
|
5
5
|
import { type ComponentType, lazy, Suspense } from 'react'
|
|
6
|
+
import { cn } from '../../lib/utils'
|
|
7
|
+
|
|
8
|
+
const SVG_DATA_URI = /^data:image\/svg\+xml[^,]*,/
|
|
9
|
+
|
|
10
|
+
/** Whether a `data:image/svg+xml` URI's markup paints with `currentColor`. */
|
|
11
|
+
function isCurrentColorSvg(src: string): boolean {
|
|
12
|
+
const head = SVG_DATA_URI.exec(src)
|
|
13
|
+
if (!head) return false
|
|
14
|
+
const body = src.slice(head[0].length)
|
|
15
|
+
try {
|
|
16
|
+
const markup = head[0].includes(';base64') ? atob(body) : decodeURIComponent(body)
|
|
17
|
+
return markup.includes('currentColor')
|
|
18
|
+
} catch {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A `url`-kind icon. An SVG behind an `<img>` renders in its own document, so
|
|
25
|
+
* `currentColor` in the markup resolves to black rather than the surrounding
|
|
26
|
+
* text colour — a monochrome plugin glyph then disappears against the dark
|
|
27
|
+
* sidebar. Such an SVG becomes a CSS mask over the text colour instead. Plugin
|
|
28
|
+
* manifests are third-party, so the markup is never inlined into the page.
|
|
29
|
+
*/
|
|
30
|
+
export function IconRefImage({
|
|
31
|
+
className,
|
|
32
|
+
size,
|
|
33
|
+
src,
|
|
34
|
+
}: {
|
|
35
|
+
className?: string
|
|
36
|
+
size?: number
|
|
37
|
+
src: string
|
|
38
|
+
}) {
|
|
39
|
+
const style = size === undefined ? undefined : { height: size, width: size }
|
|
40
|
+
if (!isCurrentColorSvg(src)) return <img alt="" className={className} src={src} style={style} />
|
|
41
|
+
const mask = `url("${src.replace(/["\\\n\r]/g, encodeURIComponent)}") center / contain no-repeat`
|
|
42
|
+
return (
|
|
43
|
+
<span
|
|
44
|
+
aria-hidden
|
|
45
|
+
className={cn('inline-block bg-current', className)}
|
|
46
|
+
style={{ ...style, mask, WebkitMask: mask }}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
6
50
|
|
|
7
51
|
// `React.lazy` must be called once per loader so the resolved component keeps
|
|
8
52
|
// a stable identity across renders (otherwise every parent re-render remounts
|
|
@@ -26,7 +70,7 @@ function resolveLazyIcon(module: () => Promise<{ default: ComponentType }>): Com
|
|
|
26
70
|
*/
|
|
27
71
|
export function IconRefGlyph({ icon, size = 16 }: { icon: IconRef; size?: number }) {
|
|
28
72
|
if (icon.kind === 'url') {
|
|
29
|
-
return <
|
|
73
|
+
return <IconRefImage className="shrink-0" size={size} src={icon.src} />
|
|
30
74
|
}
|
|
31
75
|
if (icon.kind === 'iconify') {
|
|
32
76
|
return <Icon height={size} icon={icon.name} width={size} />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { LevelNode } from '@pascal-app/core'
|
|
4
4
|
import { useEffect, useState } from 'react'
|
|
5
|
-
import type { LevelDuplicatePreset } from '
|
|
5
|
+
import type { LevelDuplicatePreset } from '@pascal-app/core/building'
|
|
6
6
|
import { getLevelDisplayName } from '@pascal-app/core'
|
|
7
7
|
import { cn } from '../../lib/utils'
|
|
8
8
|
import {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ARTIFACT_URL_PREFIX, resolveArtifactUrl } from '@pascal-app/core'
|
|
4
|
+
import Image, { type ImageProps } from 'next/image'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A node's icon or thumbnail. A scripted object's thumbnail is an
|
|
8
|
+
* `artifact://` image: it resolves through the project's artifact store and
|
|
9
|
+
* skips image optimization, whose server-side fetch has no session to read a
|
|
10
|
+
* private project's artifact with.
|
|
11
|
+
*/
|
|
12
|
+
export function NodeIconImage({ src, ...props }: Omit<ImageProps, 'src'> & { src: string }) {
|
|
13
|
+
if (!src.startsWith(ARTIFACT_URL_PREFIX)) return <Image src={src} {...props} />
|
|
14
|
+
const url = resolveArtifactUrl(src)
|
|
15
|
+
return url ? <Image src={url} unoptimized {...props} /> : null
|
|
16
|
+
}
|