@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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Icon } from '@iconify/react'
|
|
2
2
|
import type { ToolHint } from '@pascal-app/core'
|
|
3
|
-
import { Fragment, useSyncExternalStore } from 'react'
|
|
3
|
+
import { Fragment, useEffect, useSyncExternalStore } from 'react'
|
|
4
4
|
import {
|
|
5
5
|
CONTINUATION_PROFILES,
|
|
6
6
|
type ContinuationContext,
|
|
7
7
|
} from '../../../lib/continuation'
|
|
8
8
|
import type { ContextualShortcutHint } from '../../../lib/contextual-help'
|
|
9
|
+
import type { HudTitle } from '../../../lib/hud-title'
|
|
9
10
|
import { hasActivePaintMaterial } from '../../../lib/material-paint'
|
|
11
|
+
import { usePaintRegionMode } from '../../../lib/paint-region-mode'
|
|
10
12
|
import { paintScopeLabel, type PaintScope } from '../../../lib/paint-scope'
|
|
11
13
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
12
14
|
import {
|
|
@@ -17,19 +19,23 @@ import {
|
|
|
17
19
|
import { cn } from '../../../lib/utils'
|
|
18
20
|
import useEditor, { type GridSnapStep } from '../../../store/use-editor'
|
|
19
21
|
import useFenceCurveDraft from '../../../store/use-fence-curve-draft'
|
|
22
|
+
import { IconRefGlyph } from '../icon-ref'
|
|
20
23
|
import { ShortcutToken } from '../primitives/shortcut-token'
|
|
21
24
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
25
|
+
import { useInRightStack } from '../right-stack'
|
|
22
26
|
|
|
23
27
|
// One muted container holds every row — passive key hints and interactive chips
|
|
24
|
-
// alike — so the HUD reads as a single panel, not a stack of floating pills.
|
|
25
|
-
//
|
|
26
|
-
//
|
|
28
|
+
// alike — so the HUD reads as a single panel, not a stack of floating pills. It
|
|
29
|
+
// opens with the tool in hand (icon, name, arming key), then the gesture rows,
|
|
30
|
+
// then — past a hairline — the mode chips (snapping, continuation, …) and Esc.
|
|
27
31
|
// A 2-track grid: column 1 sizes to `max-content` (the widest key across ALL
|
|
28
32
|
// rows), column 2 (`1fr`) is the label. Every row is a subgrid sharing those
|
|
29
33
|
// tracks, so labels align even when keys differ in width (⌘ vs Shift) or wrap to
|
|
30
34
|
// two lines. Near-opaque bg + single backdrop blur keeps active rows readable.
|
|
31
|
-
const
|
|
32
|
-
'pointer-events-none
|
|
35
|
+
const CARD_CLASS =
|
|
36
|
+
'pointer-events-none grid w-[252px] grid-cols-[max-content_1fr] gap-x-2.5 gap-y-1.5 rounded-xl border border-border bg-background/95 p-3 shadow-lg backdrop-blur-md'
|
|
37
|
+
// On its own (no shared right column) it floats centred on the right edge.
|
|
38
|
+
const FLOATING_CLASS = 'fixed top-1/2 right-4 z-40 -translate-y-1/2'
|
|
33
39
|
|
|
34
40
|
const TOKEN_CLASS = 'h-5 px-1.5 text-[10px]'
|
|
35
41
|
|
|
@@ -260,47 +266,52 @@ function FenceContinuationChips() {
|
|
|
260
266
|
const curveStarted = useFenceCurveDraft((s) => s.pointCount > 0)
|
|
261
267
|
|
|
262
268
|
const isCurved = mode === 'curved'
|
|
269
|
+
const isFreehand = mode === 'freehand'
|
|
263
270
|
const straightMode = isCurved ? 'continuous' : mode
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
271
|
+
const typeLabel = isFreehand ? 'Type: Freehand' : isCurved ? 'Type: Curved' : 'Type: Straight'
|
|
272
|
+
const typeIcon = isFreehand ? 'lucide:scribble' : isCurved ? 'lucide:spline' : 'lucide:minus'
|
|
273
|
+
const nextType =
|
|
274
|
+
mode === 'continuous' || mode === 'single'
|
|
275
|
+
? 'curved'
|
|
276
|
+
: mode === 'curved'
|
|
277
|
+
? 'freehand'
|
|
278
|
+
: 'continuous'
|
|
268
279
|
|
|
269
280
|
return (
|
|
270
281
|
<>
|
|
271
282
|
<ChipRow
|
|
272
|
-
ariaLabel={`Fence type: ${
|
|
283
|
+
ariaLabel={`Fence type: ${typeLabel.replace('Type: ', '')}`}
|
|
273
284
|
icon={typeIcon}
|
|
274
285
|
label={typeLabel}
|
|
275
|
-
onClick={() => setContinuation('fence',
|
|
286
|
+
onClick={() => setContinuation('fence', nextType)}
|
|
276
287
|
shortcut="T"
|
|
277
|
-
tooltip="Fence type — click or press T to switch between straight and
|
|
288
|
+
tooltip="Fence type — click or press T to switch between straight, curved and freehand"
|
|
278
289
|
/>
|
|
279
290
|
<ChipRow
|
|
280
|
-
ariaLabel={`Fence continuation: ${
|
|
281
|
-
disabled={isCurved}
|
|
282
|
-
icon={
|
|
283
|
-
label={
|
|
291
|
+
ariaLabel={`Fence continuation: ${straightMode === 'single' ? 'Single' : 'Continuous'}`}
|
|
292
|
+
disabled={isCurved || isFreehand}
|
|
293
|
+
icon={straightMode === 'single' ? 'lucide:minus' : 'lucide:waypoints'}
|
|
294
|
+
label={straightMode === 'single' ? 'Straight: Single' : 'Straight: Continuous'}
|
|
284
295
|
onClick={
|
|
285
|
-
isCurved
|
|
296
|
+
isCurved || isFreehand
|
|
286
297
|
? undefined
|
|
287
298
|
: () => setContinuation('fence', straightMode === 'single' ? 'continuous' : 'single')
|
|
288
299
|
}
|
|
289
300
|
shortcut="C"
|
|
290
301
|
tooltip={
|
|
291
|
-
isCurved
|
|
292
|
-
? 'Straight continuation is unavailable
|
|
302
|
+
isCurved || isFreehand
|
|
303
|
+
? 'Straight continuation is unavailable for curved or freehand fences'
|
|
293
304
|
: 'Straight fence continuation — click or press C to toggle'
|
|
294
305
|
}
|
|
295
306
|
/>
|
|
296
307
|
{/* Curved fences are committed by a closing gesture rather than per-click,
|
|
297
308
|
so the finish keys aren't discoverable on their own — surface them, but
|
|
298
309
|
only once the user has placed a point and a curve is actually in flight. */}
|
|
299
|
-
{isCurved && curveStarted ? (
|
|
310
|
+
{(isCurved || isFreehand) && curveStarted ? (
|
|
300
311
|
<ChipRow
|
|
301
312
|
icon="lucide:circle-check"
|
|
302
|
-
label=
|
|
303
|
-
shortcut=
|
|
313
|
+
label={isFreehand ? 'Drag to draw fence' : 'Finish curve (or double-click)'}
|
|
314
|
+
shortcut={isFreehand ? 'Release' : 'Enter'}
|
|
304
315
|
/>
|
|
305
316
|
) : null}
|
|
306
317
|
</>
|
|
@@ -314,6 +325,15 @@ const PAINT_SCOPE_ICONS: Record<PaintScope, string> = {
|
|
|
314
325
|
room: 'lucide:scan',
|
|
315
326
|
}
|
|
316
327
|
|
|
328
|
+
// Why the last paint click did nothing (a surface that has no finish of its
|
|
329
|
+
// own to paint), in the same "!" row the region gestures use for a refusal.
|
|
330
|
+
function PaintNotice() {
|
|
331
|
+
const notice = usePaintRegionMode((s) => s.notice)
|
|
332
|
+
// Leaving the painter forgets it.
|
|
333
|
+
useEffect(() => () => usePaintRegionMode.getState().setNotice(null), [])
|
|
334
|
+
return notice ? <HintRow hint={{ keys: ['!'], label: notice, active: true }} /> : null
|
|
335
|
+
}
|
|
336
|
+
|
|
317
337
|
// The painter's application-scope chip. Driven entirely by the hovered node's
|
|
318
338
|
// derived `paintHover` (scopes + labels), so it works for any kind without a
|
|
319
339
|
// per-target table.
|
|
@@ -324,18 +344,31 @@ function PaintScopeChip() {
|
|
|
324
344
|
const paintScope = useEditor((s) => s.paintScope)
|
|
325
345
|
const cyclePaintScope = useEditor((s) => s.cyclePaintScope)
|
|
326
346
|
const activePaintMaterial = useEditor((s) => s.activePaintMaterial)
|
|
327
|
-
const
|
|
347
|
+
const paintMode = usePaintRegionMode((s) => s.mode)
|
|
348
|
+
const paintEraser = paintMode === 'erase'
|
|
349
|
+
const picking = paintMode === 'pick'
|
|
350
|
+
const verb = picking ? 'Pick' : paintEraser ? 'Erase' : 'Paint'
|
|
328
351
|
|
|
329
352
|
// Nothing to paint with yet (no material picked, not erasing) → the first step
|
|
330
353
|
// is choosing a material, so say that before anything about scope or hovering.
|
|
331
|
-
if (!(paintEraser || hasActivePaintMaterial(activePaintMaterial))) {
|
|
354
|
+
if (!(paintEraser || picking || hasActivePaintMaterial(activePaintMaterial))) {
|
|
332
355
|
return <ChipRow icon="lucide:palette" label="Select a material to paint" />
|
|
333
356
|
}
|
|
334
357
|
|
|
335
358
|
// Not over anything paintable → guide the user to hover, still teaching Shift.
|
|
336
359
|
if (!paintHover) {
|
|
337
360
|
return (
|
|
338
|
-
<ChipRow
|
|
361
|
+
<ChipRow
|
|
362
|
+
icon="lucide:mouse-pointer-click"
|
|
363
|
+
label={
|
|
364
|
+
picking
|
|
365
|
+
? 'Hover a surface to pick its material'
|
|
366
|
+
: paintEraser
|
|
367
|
+
? 'Hover a painted surface to erase'
|
|
368
|
+
: 'Hover a surface to paint'
|
|
369
|
+
}
|
|
370
|
+
shortcut={picking ? undefined : 'Shift'}
|
|
371
|
+
/>
|
|
339
372
|
)
|
|
340
373
|
}
|
|
341
374
|
|
|
@@ -346,33 +379,81 @@ function PaintScopeChip() {
|
|
|
346
379
|
|
|
347
380
|
// Paintable but with no scope choice (roof, a one-slot node, …) → a passive
|
|
348
381
|
// row that still names the surface, so the user always sees what they'll paint.
|
|
349
|
-
if (scopes.length <= 1) {
|
|
382
|
+
if (scopes.length <= 1 || picking) {
|
|
350
383
|
return (
|
|
351
384
|
<ChipRow
|
|
352
|
-
icon={PAINT_SCOPE_ICONS[effective]}
|
|
353
|
-
label={
|
|
385
|
+
icon={picking ? 'lucide:pipette' : PAINT_SCOPE_ICONS[effective]}
|
|
386
|
+
label={`${verb}: ${paintScopeLabel(picking ? 'single' : effective, paintHover)}`}
|
|
354
387
|
/>
|
|
355
388
|
)
|
|
356
389
|
}
|
|
357
390
|
|
|
358
391
|
return (
|
|
359
392
|
<ChipRow
|
|
360
|
-
ariaLabel={
|
|
393
|
+
ariaLabel={`${verb} scope: ${paintScopeLabel(effective, paintHover)}`}
|
|
361
394
|
icon={PAINT_SCOPE_ICONS[effective]}
|
|
362
|
-
label={
|
|
395
|
+
label={`${verb}: ${paintScopeLabel(effective, paintHover)}`}
|
|
363
396
|
onClick={() => cyclePaintScope()}
|
|
364
397
|
shortcut="Shift"
|
|
365
|
-
tooltip=
|
|
398
|
+
tooltip={`${verb} scope — click or press Shift to cycle`}
|
|
366
399
|
/>
|
|
367
400
|
)
|
|
368
401
|
}
|
|
369
402
|
|
|
403
|
+
// The tool in hand: its icon, name and arming key, over a hairline.
|
|
404
|
+
function HudHeader({ title }: { title: HudTitle }) {
|
|
405
|
+
return (
|
|
406
|
+
<div
|
|
407
|
+
className="col-span-2 mb-0.5 flex items-center gap-2.5 border-border border-b pb-2.5"
|
|
408
|
+
data-hud-title={title.label}
|
|
409
|
+
>
|
|
410
|
+
{title.icon ? (
|
|
411
|
+
<span className="flex size-7 shrink-0 items-center justify-center">
|
|
412
|
+
<IconRefGlyph icon={title.icon} size={26} />
|
|
413
|
+
</span>
|
|
414
|
+
) : null}
|
|
415
|
+
<span className="min-w-0 flex-1 truncate font-medium text-[13px] text-foreground leading-tight">
|
|
416
|
+
{title.label}
|
|
417
|
+
</span>
|
|
418
|
+
{title.shortcut ? <ShortcutToken className={TOKEN_CLASS} value={title.shortcut} /> : null}
|
|
419
|
+
</div>
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const isEscHint = (hint: ContextualShortcutHint) =>
|
|
424
|
+
hint.keys.length === 1 && hint.keys[0] === 'Esc'
|
|
425
|
+
|
|
426
|
+
function HintRow({ hint }: { hint: ContextualShortcutHint }) {
|
|
427
|
+
return (
|
|
428
|
+
<div className={cn(ROW_CLASS, 'items-start')}>
|
|
429
|
+
<ShortcutSequence active={hint.active} keys={hint.keys} />
|
|
430
|
+
<div className="min-w-0">
|
|
431
|
+
<div
|
|
432
|
+
className={cn(
|
|
433
|
+
'text-xs leading-5',
|
|
434
|
+
hint.active ? 'font-medium text-white' : 'text-muted-foreground',
|
|
435
|
+
)}
|
|
436
|
+
>
|
|
437
|
+
{hint.label}
|
|
438
|
+
</div>
|
|
439
|
+
{hint.subtitle ? (
|
|
440
|
+
<div className="text-[10px] text-muted-foreground/70 leading-snug">{hint.subtitle}</div>
|
|
441
|
+
) : null}
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const hintKey = (hint: ContextualShortcutHint) => `${hint.keys.join('+')}:${hint.label}`
|
|
448
|
+
|
|
370
449
|
export function ContextualHelperPanel({
|
|
371
450
|
hints,
|
|
372
451
|
chipHints = [],
|
|
373
452
|
snapContext = null,
|
|
374
453
|
showPaintScope = false,
|
|
375
454
|
continuationContext = null,
|
|
455
|
+
title = null,
|
|
456
|
+
notice = null,
|
|
376
457
|
}: {
|
|
377
458
|
hints: ContextualShortcutHint[]
|
|
378
459
|
// Kind-owned live mode chips (`ToolHint.chip`), rendered alongside the
|
|
@@ -383,54 +464,57 @@ export function ContextualHelperPanel({
|
|
|
383
464
|
snapContext?: SnapContext | null
|
|
384
465
|
showPaintScope?: boolean
|
|
385
466
|
continuationContext?: ContinuationContext | null
|
|
467
|
+
// The tool or gesture in hand, shown as the panel's header.
|
|
468
|
+
title?: HudTitle | null
|
|
469
|
+
// A warning about what is in hand (a floor item in a door's way), in the "!" row.
|
|
470
|
+
notice?: string | null
|
|
386
471
|
}) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
472
|
+
const inStack = useInRightStack()
|
|
473
|
+
const modeChips = chipHints.filter((hint) => hint.chip)
|
|
474
|
+
const hasChips = !!snapContext || !!continuationContext || modeChips.length > 0 || showPaintScope
|
|
475
|
+
const fenceFeature = useEditor((state) =>
|
|
476
|
+
state.mode === 'build' && state.tool === 'fence' ? state.toolDefaults.fence?.featurePlacement : null,
|
|
477
|
+
)
|
|
478
|
+
if (fenceFeature === 'gate' || fenceFeature === 'opening') return (
|
|
479
|
+
<div className={cn(CARD_CLASS, !inStack && FLOATING_CLASS)} data-hud-card>
|
|
480
|
+
{title ? <HudHeader title={title} /> : null}
|
|
481
|
+
<ChipRow shortcut="Left click" label={fenceFeature === 'gate' ? 'Place gate on a fence' : 'Place passage on a fence'} />
|
|
482
|
+
<ChipRow shortcut="Esc" label="Cancel placement" />
|
|
483
|
+
</div>
|
|
393
484
|
)
|
|
394
|
-
|
|
485
|
+
if (hints.length === 0 && !hasChips && !notice) return null
|
|
486
|
+
|
|
487
|
+
const actionHints = hints.filter((hint) => !isEscHint(hint))
|
|
488
|
+
const escHints = hints.filter(isEscHint)
|
|
395
489
|
|
|
396
490
|
return (
|
|
397
|
-
<div
|
|
491
|
+
<div
|
|
492
|
+
className={cn(CARD_CLASS, !inStack && FLOATING_CLASS)}
|
|
493
|
+
data-hud-card
|
|
494
|
+
>
|
|
495
|
+
{title ? <HudHeader title={title} /> : null}
|
|
496
|
+
{actionHints.map((hint) => (
|
|
497
|
+
<HintRow hint={hint} key={hintKey(hint)} />
|
|
498
|
+
))}
|
|
499
|
+
{notice ? <HintRow hint={{ keys: ['!'], label: notice, active: true }} /> : null}
|
|
500
|
+
{actionHints.length > 0 && (hasChips || escHints.length > 0) ? (
|
|
501
|
+
<div className="col-span-2 my-0.5 h-px bg-border" />
|
|
502
|
+
) : null}
|
|
398
503
|
{snapContext ? <SnappingChips context={snapContext} /> : null}
|
|
399
504
|
{continuationContext === 'fence' ? <FenceContinuationChips /> : null}
|
|
400
505
|
{continuationContext && continuationContext !== 'fence' ? (
|
|
401
506
|
<ContinuationChip context={continuationContext} />
|
|
402
507
|
) : null}
|
|
403
|
-
{
|
|
404
|
-
|
|
405
|
-
<
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
)}
|
|
508
|
+
{modeChips.map((hint) => (
|
|
509
|
+
<ToolHintChipRow
|
|
510
|
+
hint={hint as ToolHint & { chip: NonNullable<ToolHint['chip']> }}
|
|
511
|
+
key={`${hint.key}:${hint.label}`}
|
|
512
|
+
/>
|
|
513
|
+
))}
|
|
514
|
+
{showPaintScope ? <PaintNotice /> : null}
|
|
411
515
|
{showPaintScope ? <PaintScopeChip /> : null}
|
|
412
|
-
{
|
|
413
|
-
<
|
|
414
|
-
className={cn(ROW_CLASS, 'items-start')}
|
|
415
|
-
key={`${hint.keys.join('+')}:${hint.label}`}
|
|
416
|
-
>
|
|
417
|
-
<ShortcutSequence active={hint.active} keys={hint.keys} />
|
|
418
|
-
<div className="min-w-0">
|
|
419
|
-
<div
|
|
420
|
-
className={cn(
|
|
421
|
-
'text-xs leading-5',
|
|
422
|
-
hint.active ? 'font-medium text-white' : 'text-muted-foreground',
|
|
423
|
-
)}
|
|
424
|
-
>
|
|
425
|
-
{hint.label}
|
|
426
|
-
</div>
|
|
427
|
-
{hint.subtitle ? (
|
|
428
|
-
<div className="text-[10px] text-muted-foreground/70 leading-snug">
|
|
429
|
-
{hint.subtitle}
|
|
430
|
-
</div>
|
|
431
|
-
) : null}
|
|
432
|
-
</div>
|
|
433
|
-
</div>
|
|
516
|
+
{escHints.map((hint) => (
|
|
517
|
+
<HintRow hint={hint} key={hintKey(hint)} />
|
|
434
518
|
))}
|
|
435
519
|
</div>
|
|
436
520
|
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { SELECT_HUD_TITLE, toolHudTitle } from '../../../lib/hud-title'
|
|
4
|
+
import { snapContextOf } from '../../../lib/snapping-mode'
|
|
5
|
+
import { TooltipProvider } from '../primitives/tooltip'
|
|
6
|
+
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
7
|
+
import { ROOM_DIVIDE_HINTS } from './helper-manager'
|
|
8
|
+
|
|
9
|
+
describe('Divide HUD', () => {
|
|
10
|
+
test('uses the wall draft snapping context', () => {
|
|
11
|
+
expect(
|
|
12
|
+
snapContextOf({
|
|
13
|
+
scope: { kind: 'room-divide' },
|
|
14
|
+
mode: 'select',
|
|
15
|
+
tool: null,
|
|
16
|
+
profileOf: () => undefined,
|
|
17
|
+
}),
|
|
18
|
+
).toBe('wall')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('reads like the wall draft: snapping chips and the path keys', () => {
|
|
22
|
+
// Server rendering reads each store's initial state: the wall context starts
|
|
23
|
+
// on Grid with a 0.5 m step.
|
|
24
|
+
const html = renderToStaticMarkup(
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<ContextualHelperPanel hints={ROOM_DIVIDE_HINTS} snapContext="wall" />
|
|
27
|
+
</TooltipProvider>,
|
|
28
|
+
)
|
|
29
|
+
expect(html).toContain('Snapping: Grid')
|
|
30
|
+
expect(html).toContain('Grid: 0.50 m')
|
|
31
|
+
for (const label of ['Add point', 'Remove last point', 'Finish at the edge', 'Cancel'])
|
|
32
|
+
expect(html).toContain(label)
|
|
33
|
+
expect(ROOM_DIVIDE_HINTS.map((hint) => hint.keys)).toEqual([
|
|
34
|
+
['Left click'],
|
|
35
|
+
['Backspace'],
|
|
36
|
+
['Enter'],
|
|
37
|
+
['Esc'],
|
|
38
|
+
])
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('HUD header', () => {
|
|
43
|
+
test('names the tool in hand with its icon and key, above the gesture rows', () => {
|
|
44
|
+
const html = renderToStaticMarkup(
|
|
45
|
+
<TooltipProvider>
|
|
46
|
+
<ContextualHelperPanel
|
|
47
|
+
hints={[
|
|
48
|
+
{ keys: ['Left click'], label: 'Set one corner, then the opposite' },
|
|
49
|
+
{ keys: ['Esc'], label: 'Cancel' },
|
|
50
|
+
]}
|
|
51
|
+
snapContext="wall"
|
|
52
|
+
title={toolHudTitle('wall', 'rectangle')}
|
|
53
|
+
/>
|
|
54
|
+
</TooltipProvider>,
|
|
55
|
+
)
|
|
56
|
+
expect(html).toContain('data-hud-title="Rectangle room"')
|
|
57
|
+
expect(html).toContain('src="/icons/room.webp"')
|
|
58
|
+
// Header, then the gesture, then the mode chips, with Esc last.
|
|
59
|
+
const order = ['Rectangle room', 'Set one corner', 'Snapping: Grid', 'Cancel'].map((text) =>
|
|
60
|
+
html.indexOf(text),
|
|
61
|
+
)
|
|
62
|
+
expect(order.every((at) => at >= 0)).toBe(true)
|
|
63
|
+
expect([...order].sort((a, b) => a - b)).toEqual(order)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('select mode is titled Select with its V key', () => {
|
|
67
|
+
expect(SELECT_HUD_TITLE).toMatchObject({ label: 'Select', shortcut: 'V' })
|
|
68
|
+
})
|
|
69
|
+
})
|