@pascal-app/editor 1.0.6 → 1.1.0
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 +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +110 -42
- package/src/components/editor/duplicate-node.ts +2 -1
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +77 -68
- package/src/components/editor/fit-scene-framing.test.ts +318 -0
- package/src/components/editor/fit-scene-framing.ts +142 -0
- package/src/components/editor/floating-action-menu.tsx +31 -11
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floor-openings-overlay.tsx +1 -1
- package/src/components/editor/floor-region-controls.tsx +2 -2
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +458 -140
- package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
- package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
- package/src/components/editor/footprint-height-handle.tsx +2 -2
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +23 -1
- package/src/components/editor/index.tsx +150 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/measurement-pill.tsx +1 -1
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +2 -3
- package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
- package/src/components/editor/open-wall-ends-hint.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/quick-measurement-card.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +4 -3
- package/src/components/editor/room-handles-2d.tsx +158 -7
- package/src/components/editor/room-handles.tsx +244 -54
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +33 -17
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +7 -3
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +31 -15
- package/src/components/editor/wall-move-side-handles.tsx +15 -7
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-region-handles.tsx +2 -2
- package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
- package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
- package/src/components/systems/roof/roof-edit-system.tsx +30 -19
- package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
- package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
- package/src/components/tools/item/move-tool.tsx +1 -6
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
- package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
- package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +2 -6
- package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/shared/segment-angle.ts +0 -4
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/tool-manager.tsx +1 -24
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
- package/src/components/tools/wall/wall-typed-entry.ts +70 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +47 -19
- package/src/components/ui/controls/tool-options-panel.tsx +74 -44
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
- package/src/components/ui/helpers/helper-manager.tsx +136 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
- package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +11 -10
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +9 -5
- package/src/components/ui/panels/panel-wrapper.tsx +63 -127
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +465 -66
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +22 -13
- package/src/components/ui/panels/room-panel.tsx +207 -100
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/right-stack.test.ts +13 -6
- package/src/components/ui/right-stack.tsx +21 -10
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +62 -5
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15849 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +39331 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +39331 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +39331 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +39331 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +224 -45
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +101 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/elevation-guides.ts +21 -6
- package/src/lib/elevator-support.ts +1 -72
- package/src/lib/floor-footprints.test.ts +3 -2
- package/src/lib/floor-footprints.ts +1 -9
- package/src/lib/floor-foundation-support.test.ts +15 -6
- package/src/lib/floor-opening-draft.ts +1 -1
- package/src/lib/floor-region-session.ts +1 -1
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/open-wall-ends.ts +2 -1
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/gesture-lifecycle.ts +3 -1
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/interaction/scope.ts +1 -0
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/level-selection.test.ts +18 -2
- package/src/lib/level-selection.ts +31 -8
- package/src/lib/linear-display.ts +1 -1
- package/src/lib/live-gesture-round-trip.test.ts +1 -1
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-label.test.ts +1 -1
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +24 -6
- package/src/lib/measurements.ts +1 -203
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/node-action-movement.test.tsx +115 -0
- package/src/lib/node-action-movement.ts +2 -0
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/placement-metadata.ts +4 -0
- package/src/lib/point-ask/describe-target.ts +6 -8
- package/src/lib/point-ask/region.ts +1 -1
- package/src/lib/print-shell-compiler.ts +12 -1
- package/src/lib/print-wall-solids.test.ts +11 -0
- package/src/lib/print-wall-solids.ts +9 -0
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-assembly-overlay.ts +1 -1
- package/src/lib/room-built-on.test.ts +28 -17
- package/src/lib/room-built-on.ts +21 -17
- package/src/lib/room-construction-commands.ts +3 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +141 -1
- package/src/lib/room-handle-drag.ts +554 -58
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +38 -1
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-bounds.test.ts +144 -0
- package/src/lib/scene-bounds.ts +79 -15
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +11 -5
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/snapping-mode.test.ts +23 -1
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +21 -6
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +17 -6
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +44 -0
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +53 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-placement-typing.test.ts +165 -0
- package/src/store/use-placement-typing.ts +186 -0
- package/src/store/use-wall-snap-indicator.ts +15 -2
- package/src/components/tools/elevator/elevator-defaults.ts +0 -8
- package/src/components/tools/elevator/elevator-tool.tsx +0 -372
- package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
- package/src/lib/door-interaction.ts +0 -102
- package/src/lib/measurement-label.ts +0 -68
- package/src/lib/window-interaction.ts +0 -100
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatLength, type MessageDescriptor, usePascalI18n } from '@pascal-app/core'
|
|
1
2
|
import { Icon } from '@iconify/react'
|
|
2
3
|
import type { ToolHint } from '@pascal-app/core'
|
|
3
4
|
import { Fragment, useEffect, useId, useState, useSyncExternalStore } from 'react'
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
type ContinuationContext,
|
|
7
8
|
} from '../../../lib/continuation'
|
|
8
9
|
import type { ContextualShortcutHint } from '../../../lib/contextual-help'
|
|
10
|
+
import { hintText, nodeLabel, toolHintChipLabel, toolHintLabel } from '../../../i18n/node-messages'
|
|
9
11
|
import type { HudTitle } from '../../../lib/hud-title'
|
|
10
12
|
import { hasActivePaintMaterial } from '../../../lib/material-paint'
|
|
11
13
|
import { usePaintRegionMode } from '../../../lib/paint-region-mode'
|
|
@@ -169,10 +171,10 @@ const SNAPPING_MODE_ICONS = {
|
|
|
169
171
|
} as const
|
|
170
172
|
|
|
171
173
|
const SNAPPING_MODE_LABELS = {
|
|
172
|
-
grid: 'Grid',
|
|
173
|
-
lines: 'Lines',
|
|
174
|
-
angles: 'Angles',
|
|
175
|
-
off: 'Off',
|
|
174
|
+
grid: /* i18n */ { id: 'editor.helpers.contextualHelperPanel.snappingGrid', message: 'Snapping: Grid' },
|
|
175
|
+
lines: /* i18n */ { id: 'editor.helpers.contextualHelperPanel.snappingLines', message: 'Snapping: Lines' },
|
|
176
|
+
angles: /* i18n */ { id: 'editor.helpers.contextualHelperPanel.snappingAngles', message: 'Snapping: Angles' },
|
|
177
|
+
off: /* i18n */ { id: 'editor.helpers.contextualHelperPanel.snappingOff', message: 'Snapping: Off' },
|
|
176
178
|
} as const
|
|
177
179
|
|
|
178
180
|
const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05]
|
|
@@ -185,6 +187,7 @@ function nextGridSnapStep(step: GridSnapStep): GridSnapStep {
|
|
|
185
187
|
// The active interaction's snapping controls, scoped to its context (wall / item
|
|
186
188
|
// / polygon) so each action shows only the modes that make sense for it.
|
|
187
189
|
function SnappingChips({ context }: { context: SnapContext }) {
|
|
190
|
+
const i18n = usePascalI18n()
|
|
188
191
|
const snappingMode = useEditor((s) => s.snappingModeByContext[context])
|
|
189
192
|
const setSnappingMode = useEditor((s) => s.setSnappingMode)
|
|
190
193
|
const gridSnapStep = useEditor((s) => s.gridSnapStep)
|
|
@@ -195,28 +198,36 @@ function SnappingChips({ context }: { context: SnapContext }) {
|
|
|
195
198
|
return (
|
|
196
199
|
<>
|
|
197
200
|
<ChipRow
|
|
198
|
-
ariaLabel={
|
|
201
|
+
ariaLabel={i18n._(SNAPPING_MODE_LABELS[snappingMode])}
|
|
199
202
|
guideTarget="snap-mode"
|
|
200
203
|
icon={SNAPPING_MODE_ICONS[snappingMode]}
|
|
201
|
-
label={
|
|
204
|
+
label={i18n._(SNAPPING_MODE_LABELS[snappingMode])}
|
|
202
205
|
onClick={() => {
|
|
203
206
|
setSnappingMode(context, cycleSnappingModeIn(context, snappingMode))
|
|
204
207
|
sfxEmitter.emit('sfx:grid-snap')
|
|
205
208
|
}}
|
|
206
209
|
shortcut="Shift"
|
|
207
|
-
tooltip=
|
|
210
|
+
tooltip={i18n._({ id: 'editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift', message: 'Snapping mode — click or press Shift to cycle' })}
|
|
208
211
|
/>
|
|
209
212
|
{gridActive ? (
|
|
210
213
|
<ChipRow
|
|
211
|
-
ariaLabel={
|
|
214
|
+
ariaLabel={i18n._({
|
|
215
|
+
id: 'editor.helpers.contextualHelperPanel.gridStepLength',
|
|
216
|
+
message: 'Grid step: {length}',
|
|
217
|
+
values: { length: formatLength(gridSnapStep, 'm', { locale: i18n.getLocale(), min: 2, max: 2 }) },
|
|
218
|
+
})}
|
|
212
219
|
guideTarget="snap-grid-step"
|
|
213
|
-
label={
|
|
220
|
+
label={i18n._({
|
|
221
|
+
id: 'editor.helpers.contextualHelperPanel.gridLength',
|
|
222
|
+
message: 'Grid: {length}',
|
|
223
|
+
values: { length: formatLength(gridSnapStep, 'm', { locale: i18n.getLocale(), min: 2, max: 2 }) },
|
|
224
|
+
})}
|
|
214
225
|
onClick={() => {
|
|
215
226
|
setGridSnapStep(nextGridSnapStep(gridSnapStep))
|
|
216
227
|
sfxEmitter.emit('sfx:grid-snap')
|
|
217
228
|
}}
|
|
218
229
|
shortcut="Ctrl"
|
|
219
|
-
tooltip=
|
|
230
|
+
tooltip={i18n._({ id: 'editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl', message: 'Grid step — click or tap Ctrl to cycle' })}
|
|
220
231
|
/>
|
|
221
232
|
) : null}
|
|
222
233
|
</>
|
|
@@ -229,8 +240,12 @@ function SnappingChips({ context }: { context: SnapContext }) {
|
|
|
229
240
|
// the tool itself) cycles it.
|
|
230
241
|
function ToolHintChipRow({ hint }: { hint: ToolHint & { chip: NonNullable<ToolHint['chip']> } }) {
|
|
231
242
|
const { chip } = hint
|
|
243
|
+
const i18n = usePascalI18n()
|
|
232
244
|
const value = useSyncExternalStore(chip.subscribe, chip.value, chip.value)
|
|
233
|
-
const
|
|
245
|
+
const english = chip.labels[value]
|
|
246
|
+
const label = english
|
|
247
|
+
? toolHintChipLabel(value, english, i18n)
|
|
248
|
+
: toolHintLabel(hint.label, i18n)
|
|
234
249
|
return (
|
|
235
250
|
<ChipRow
|
|
236
251
|
ariaLabel={label}
|
|
@@ -238,31 +253,38 @@ function ToolHintChipRow({ hint }: { hint: ToolHint & { chip: NonNullable<ToolHi
|
|
|
238
253
|
label={label}
|
|
239
254
|
onClick={chip.cycle}
|
|
240
255
|
shortcut={hint.key}
|
|
241
|
-
tooltip={chip.tooltip}
|
|
256
|
+
tooltip={chip.tooltip ? toolHintLabel(chip.tooltip, i18n) : undefined}
|
|
242
257
|
/>
|
|
243
258
|
)
|
|
244
259
|
}
|
|
245
260
|
|
|
246
261
|
function ContinuationChip({ context }: { context: ContinuationContext }) {
|
|
262
|
+
const i18n = usePascalI18n()
|
|
247
263
|
const mode = useEditor((s) => s.getContinuation(context))
|
|
248
264
|
const cycleContinuation = useEditor((s) => s.cycleContinuation)
|
|
249
265
|
const profile = CONTINUATION_PROFILES[context]
|
|
250
|
-
const
|
|
266
|
+
const descriptor = profile.labels[mode]
|
|
267
|
+
const label = descriptor ? i18n._(descriptor) : mode
|
|
251
268
|
const icon = profile.icons[mode] ?? 'lucide:repeat'
|
|
252
269
|
|
|
253
270
|
return (
|
|
254
271
|
<ChipRow
|
|
255
|
-
ariaLabel={
|
|
272
|
+
ariaLabel={i18n._({
|
|
273
|
+
id: 'editor.helpers.contextualHelperPanel.continuationLabel',
|
|
274
|
+
message: 'Continuation: {label}',
|
|
275
|
+
values: { label },
|
|
276
|
+
})}
|
|
256
277
|
icon={icon}
|
|
257
278
|
label={label}
|
|
258
279
|
onClick={() => cycleContinuation(context)}
|
|
259
280
|
shortcut="C"
|
|
260
|
-
tooltip=
|
|
281
|
+
tooltip={i18n._({ id: 'editor.helpers.contextualHelperPanel.continuationClickOrPressCTo', message: 'Continuation — click or press C to cycle' })}
|
|
261
282
|
/>
|
|
262
283
|
)
|
|
263
284
|
}
|
|
264
285
|
|
|
265
286
|
function FenceContinuationChips() {
|
|
287
|
+
const i18n = usePascalI18n()
|
|
266
288
|
const mode = useEditor((s) => s.getContinuation('fence'))
|
|
267
289
|
const setContinuation = useEditor((s) => s.setContinuation)
|
|
268
290
|
const curveStarted = useFenceCurveDraft((s) => s.pointCount > 0)
|
|
@@ -287,13 +309,13 @@ function FenceContinuationChips() {
|
|
|
287
309
|
label={typeLabel}
|
|
288
310
|
onClick={() => setContinuation('fence', nextType)}
|
|
289
311
|
shortcut="T"
|
|
290
|
-
tooltip=
|
|
312
|
+
tooltip={i18n._({ id: 'editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT', message: 'Fence type — click or press T to switch between straight, curved and freehand' })}
|
|
291
313
|
/>
|
|
292
314
|
<ChipRow
|
|
293
315
|
ariaLabel={`Fence continuation: ${straightMode === 'single' ? 'Single' : 'Continuous'}`}
|
|
294
316
|
disabled={isCurved || isFreehand}
|
|
295
317
|
icon={straightMode === 'single' ? 'lucide:minus' : 'lucide:waypoints'}
|
|
296
|
-
label={straightMode === 'single' ? 'Straight: Single' : 'Straight: Continuous'}
|
|
318
|
+
label={straightMode === 'single' ? i18n._({ id: 'editor.helpers.contextualHelperPanel.straightSingle', message: 'Straight: Single' }) : i18n._({ id: 'editor.helpers.contextualHelperPanel.straightContinuous', message: 'Straight: Continuous' })}
|
|
297
319
|
onClick={
|
|
298
320
|
isCurved || isFreehand
|
|
299
321
|
? undefined
|
|
@@ -302,8 +324,8 @@ function FenceContinuationChips() {
|
|
|
302
324
|
shortcut="C"
|
|
303
325
|
tooltip={
|
|
304
326
|
isCurved || isFreehand
|
|
305
|
-
? 'Straight continuation is unavailable for curved or freehand fences'
|
|
306
|
-
: 'Straight fence continuation — click or press C to toggle'
|
|
327
|
+
? i18n._({ id: 'editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved', message: 'Straight continuation is unavailable for curved or freehand fences' })
|
|
328
|
+
: i18n._({ id: 'editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress', message: 'Straight fence continuation — click or press C to toggle' })
|
|
307
329
|
}
|
|
308
330
|
/>
|
|
309
331
|
{/* Curved fences are committed by a closing gesture rather than per-click,
|
|
@@ -312,7 +334,7 @@ function FenceContinuationChips() {
|
|
|
312
334
|
{(isCurved || isFreehand) && curveStarted ? (
|
|
313
335
|
<ChipRow
|
|
314
336
|
icon="lucide:circle-check"
|
|
315
|
-
label={isFreehand ? 'Drag to draw fence' : 'Finish curve (or double-click)'}
|
|
337
|
+
label={isFreehand ? i18n._({ id: 'editor.helpers.contextualHelperPanel.dragToDrawFence', message: 'Drag to draw fence' }) : i18n._({ id: 'editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick', message: 'Finish curve (or double-click)' })}
|
|
316
338
|
shortcut={isFreehand ? 'Release' : 'Enter'}
|
|
317
339
|
/>
|
|
318
340
|
) : null}
|
|
@@ -340,6 +362,7 @@ function PaintNotice() {
|
|
|
340
362
|
// derived `paintHover` (scopes + labels), so it works for any kind without a
|
|
341
363
|
// per-target table.
|
|
342
364
|
function PaintScopeChip() {
|
|
365
|
+
const i18n = usePascalI18n()
|
|
343
366
|
// What the cursor is over (that's what the next click paints). `null` when not
|
|
344
367
|
// over a paintable surface — including an item with no slots.
|
|
345
368
|
const paintHover = useEditor((s) => s.paintHover)
|
|
@@ -349,12 +372,30 @@ function PaintScopeChip() {
|
|
|
349
372
|
const paintMode = usePaintRegionMode((s) => s.mode)
|
|
350
373
|
const paintEraser = paintMode === 'erase'
|
|
351
374
|
const picking = paintMode === 'pick'
|
|
352
|
-
const verb = picking ? '
|
|
375
|
+
const verb = picking ? 'pick' : paintEraser ? 'erase' : 'paint'
|
|
376
|
+
const scopeText = (scope: PaintScope) =>
|
|
377
|
+
({
|
|
378
|
+
pick: i18n._({
|
|
379
|
+
id: 'editor.helpers.contextualHelperPanel.pickScope',
|
|
380
|
+
message: 'Pick: {scope}',
|
|
381
|
+
values: { scope: paintScopeLabel(scope, paintHover!) },
|
|
382
|
+
}),
|
|
383
|
+
erase: i18n._({
|
|
384
|
+
id: 'editor.helpers.contextualHelperPanel.eraseScope',
|
|
385
|
+
message: 'Erase: {scope}',
|
|
386
|
+
values: { scope: paintScopeLabel(scope, paintHover!) },
|
|
387
|
+
}),
|
|
388
|
+
paint: i18n._({
|
|
389
|
+
id: 'editor.helpers.contextualHelperPanel.paintScope',
|
|
390
|
+
message: 'Paint: {scope}',
|
|
391
|
+
values: { scope: paintScopeLabel(scope, paintHover!) },
|
|
392
|
+
}),
|
|
393
|
+
})[verb]
|
|
353
394
|
|
|
354
395
|
// Nothing to paint with yet (no material picked, not erasing) → the first step
|
|
355
396
|
// is choosing a material, so say that before anything about scope or hovering.
|
|
356
397
|
if (!(paintEraser || picking || hasActivePaintMaterial(activePaintMaterial))) {
|
|
357
|
-
return <ChipRow icon="lucide:palette" label=
|
|
398
|
+
return <ChipRow icon="lucide:palette" label={i18n._({ id: 'editor.helpers.contextualHelperPanel.selectAMaterialToPaint', message: 'Select a material to paint' })} />
|
|
358
399
|
}
|
|
359
400
|
|
|
360
401
|
// Not over anything paintable → guide the user to hover, still teaching Shift.
|
|
@@ -364,10 +405,10 @@ function PaintScopeChip() {
|
|
|
364
405
|
icon="lucide:mouse-pointer-click"
|
|
365
406
|
label={
|
|
366
407
|
picking
|
|
367
|
-
? 'Hover a surface to pick its material'
|
|
408
|
+
? i18n._({ id: 'editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts', message: 'Hover a surface to pick its material' })
|
|
368
409
|
: paintEraser
|
|
369
|
-
? 'Hover a painted surface to erase'
|
|
370
|
-
: 'Hover a surface to paint'
|
|
410
|
+
? i18n._({ id: 'editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase', message: 'Hover a painted surface to erase' })
|
|
411
|
+
: i18n._({ id: 'editor.helpers.contextualHelperPanel.hoverASurfaceToPaint', message: 'Hover a surface to paint' })
|
|
371
412
|
}
|
|
372
413
|
shortcut={picking ? undefined : 'Shift'}
|
|
373
414
|
/>
|
|
@@ -385,29 +426,43 @@ function PaintScopeChip() {
|
|
|
385
426
|
return (
|
|
386
427
|
<ChipRow
|
|
387
428
|
icon={picking ? 'lucide:pipette' : PAINT_SCOPE_ICONS[effective]}
|
|
388
|
-
label={
|
|
429
|
+
label={scopeText(picking ? 'single' : effective)}
|
|
389
430
|
/>
|
|
390
431
|
)
|
|
391
432
|
}
|
|
392
433
|
|
|
393
434
|
return (
|
|
394
435
|
<ChipRow
|
|
395
|
-
ariaLabel={
|
|
436
|
+
ariaLabel={
|
|
437
|
+
paintEraser
|
|
438
|
+
? i18n._({ id: 'editor.helpers.contextualHelperPanel.eraseScopeLabel', message: 'Erase scope: {scope}', values: { scope: paintScopeLabel(effective, paintHover) } })
|
|
439
|
+
: i18n._({ id: 'editor.helpers.contextualHelperPanel.paintScopeLabel', message: 'Paint scope: {scope}', values: { scope: paintScopeLabel(effective, paintHover) } })
|
|
440
|
+
}
|
|
396
441
|
icon={PAINT_SCOPE_ICONS[effective]}
|
|
397
|
-
label={
|
|
442
|
+
label={scopeText(effective)}
|
|
398
443
|
onClick={() => cyclePaintScope()}
|
|
399
444
|
shortcut="Shift"
|
|
400
|
-
tooltip={
|
|
445
|
+
tooltip={
|
|
446
|
+
paintEraser
|
|
447
|
+
? i18n._({ id: 'editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift', message: 'Erase scope — click or press Shift to cycle' })
|
|
448
|
+
: i18n._({ id: 'editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift', message: 'Paint scope — click or press Shift to cycle' })
|
|
449
|
+
}
|
|
401
450
|
/>
|
|
402
451
|
)
|
|
403
452
|
}
|
|
404
453
|
|
|
405
454
|
// The tool in hand: its icon, name and arming key, over a hairline.
|
|
406
455
|
function HudHeader({ title }: { title: HudTitle }) {
|
|
456
|
+
const i18n = usePascalI18n()
|
|
457
|
+
const kind = title.kind
|
|
458
|
+
? nodeLabel(title.kind.kind, title.kind.english, i18n).toLocaleLowerCase(i18n.getLocale())
|
|
459
|
+
: undefined
|
|
460
|
+
const descriptor: MessageDescriptor = { ...title.label, values: { ...title.label.values, kind } }
|
|
461
|
+
const label = i18n._(descriptor)
|
|
407
462
|
return (
|
|
408
463
|
<div
|
|
409
464
|
className="col-span-2 mb-0.5 flex items-center gap-2.5 border-border border-b pb-2.5"
|
|
410
|
-
data-hud-title={
|
|
465
|
+
data-hud-title={label}
|
|
411
466
|
>
|
|
412
467
|
{title.icon ? (
|
|
413
468
|
<span className="flex size-7 shrink-0 items-center justify-center">
|
|
@@ -415,7 +470,7 @@ function HudHeader({ title }: { title: HudTitle }) {
|
|
|
415
470
|
</span>
|
|
416
471
|
) : null}
|
|
417
472
|
<span className="min-w-0 flex-1 truncate font-medium text-[13px] text-foreground leading-tight">
|
|
418
|
-
{
|
|
473
|
+
{label}
|
|
419
474
|
</span>
|
|
420
475
|
{title.shortcut ? <ShortcutToken className={TOKEN_CLASS} value={title.shortcut} /> : null}
|
|
421
476
|
</div>
|
|
@@ -430,6 +485,7 @@ const isEscHint = (hint: ContextualShortcutHint) =>
|
|
|
430
485
|
const isNoteHint = (hint: ContextualShortcutHint) => hint.keys.length === 1 && hint.keys[0] === '!'
|
|
431
486
|
|
|
432
487
|
function HintRow({ hint }: { hint: ContextualShortcutHint }) {
|
|
488
|
+
const i18n = usePascalI18n()
|
|
433
489
|
return (
|
|
434
490
|
<div className={cn(ROW_CLASS, 'items-start')}>
|
|
435
491
|
<ShortcutSequence active={hint.active} keys={hint.keys} />
|
|
@@ -440,17 +496,20 @@ function HintRow({ hint }: { hint: ContextualShortcutHint }) {
|
|
|
440
496
|
hint.active ? 'font-medium text-foreground dark:text-white' : 'text-muted-foreground',
|
|
441
497
|
)}
|
|
442
498
|
>
|
|
443
|
-
{hint.label}
|
|
499
|
+
{hintText(hint.label, i18n)}
|
|
444
500
|
</div>
|
|
445
501
|
{hint.subtitle ? (
|
|
446
|
-
<div className="text-[10px] text-muted-foreground/70 leading-snug">
|
|
502
|
+
<div className="text-[10px] text-muted-foreground/70 leading-snug">
|
|
503
|
+
{hintText(hint.subtitle, i18n)}
|
|
504
|
+
</div>
|
|
447
505
|
) : null}
|
|
448
506
|
</div>
|
|
449
507
|
</div>
|
|
450
508
|
)
|
|
451
509
|
}
|
|
452
510
|
|
|
453
|
-
const hintKey = (hint: ContextualShortcutHint) =>
|
|
511
|
+
const hintKey = (hint: ContextualShortcutHint) =>
|
|
512
|
+
`${hint.keys.join('+')}:${typeof hint.label === 'string' ? hint.label : hint.label.id}`
|
|
454
513
|
|
|
455
514
|
export function ContextualHelperPanel({
|
|
456
515
|
hints,
|
|
@@ -475,6 +534,7 @@ export function ContextualHelperPanel({
|
|
|
475
534
|
// A warning about what is in hand (a floor item in a door's way), in the "!" row.
|
|
476
535
|
notice?: string | null
|
|
477
536
|
}) {
|
|
537
|
+
const i18n = usePascalI18n()
|
|
478
538
|
const inStack = useInRightStack()
|
|
479
539
|
const [showAllHints, setShowAllHints] = useState(false)
|
|
480
540
|
const hintsId = useId()
|
|
@@ -486,8 +546,8 @@ export function ContextualHelperPanel({
|
|
|
486
546
|
if (fenceFeature === 'gate' || fenceFeature === 'opening') return (
|
|
487
547
|
<div className={cn(CARD_CLASS, !inStack && FLOATING_CLASS)} data-hud-card>
|
|
488
548
|
{title ? <HudHeader title={title} /> : null}
|
|
489
|
-
<ChipRow shortcut="Left click" label={fenceFeature === 'gate' ? 'Place gate on a fence' : 'Place passage on a fence'} />
|
|
490
|
-
<ChipRow shortcut="Esc" label=
|
|
549
|
+
<ChipRow shortcut="Left click" label={fenceFeature === 'gate' ? i18n._({ id: 'editor.helpers.contextualHelperPanel.placeGateOnAFence', message: 'Place gate on a fence' }) : i18n._({ id: 'editor.helpers.contextualHelperPanel.placePassageOnAFence', message: 'Place passage on a fence' })} />
|
|
550
|
+
<ChipRow shortcut="Esc" label={i18n._({ id: 'editor.helpers.contextualHelperPanel.cancelPlacement', message: 'Cancel placement' })} />
|
|
491
551
|
</div>
|
|
492
552
|
)
|
|
493
553
|
if (hints.length === 0 && !hasChips && !notice) return null
|
|
@@ -518,7 +578,7 @@ export function ContextualHelperPanel({
|
|
|
518
578
|
onClick={() => setShowAllHints((value) => !value)}
|
|
519
579
|
type="button"
|
|
520
580
|
>
|
|
521
|
-
{showAllHints ? 'Fewer shortcuts' : 'More shortcuts'}
|
|
581
|
+
{showAllHints ? i18n._({ id: 'editor.helpers.contextualHelperPanel.fewerShortcuts', message: 'Fewer shortcuts' }) : i18n._({ id: 'editor.helpers.contextualHelperPanel.moreShortcuts', message: 'More shortcuts' })}
|
|
522
582
|
</button>
|
|
523
583
|
) : null}
|
|
524
584
|
{notice ? <HintRow hint={{ keys: ['!'], label: notice, active: true }} /> : null}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test'
|
|
2
2
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import {
|
|
4
|
+
AXIS_MOVE_DRAG_LABEL,
|
|
5
|
+
PATH_POINT_DRAG_LABEL,
|
|
6
|
+
ROTATE_HANDLE_DRAG_LABEL,
|
|
7
|
+
} from '../../../lib/contextual-help'
|
|
3
8
|
import { SELECT_HUD_TITLE, toolHudTitle } from '../../../lib/hud-title'
|
|
4
9
|
import { snapContextOf } from '../../../lib/snapping-mode'
|
|
5
10
|
import { cornerHintRows } from '../../../store/use-corner-snap-hint'
|
|
6
11
|
import { TooltipProvider } from '../primitives/tooltip'
|
|
7
12
|
import { ContextualHelperPanel } from './contextual-helper-panel'
|
|
8
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
mepHandleGesture,
|
|
15
|
+
mepHandleGestureHints,
|
|
16
|
+
mezzanineHints,
|
|
17
|
+
openingHints,
|
|
18
|
+
ROOM_DIVIDE_HINTS,
|
|
19
|
+
toolHintsOwnPlacing,
|
|
20
|
+
} from './helper-manager'
|
|
9
21
|
|
|
10
22
|
describe('Divide HUD', () => {
|
|
11
23
|
test('uses the wall draft snapping context', () => {
|
|
@@ -28,7 +40,7 @@ describe('Divide HUD', () => {
|
|
|
28
40
|
</TooltipProvider>,
|
|
29
41
|
)
|
|
30
42
|
expect(html).toContain('Snapping: Grid')
|
|
31
|
-
expect(html).toContain('Grid: 0.50
|
|
43
|
+
expect(html).toContain('Grid: 0.50\u00a0m')
|
|
32
44
|
for (const label of ['Add point', 'More shortcuts', 'Cancel'])
|
|
33
45
|
expect(html).toContain(label)
|
|
34
46
|
expect(html).toContain('aria-expanded="false"')
|
|
@@ -116,6 +128,97 @@ describe('HUD header', () => {
|
|
|
116
128
|
})
|
|
117
129
|
|
|
118
130
|
test('select mode is titled Select with its V key', () => {
|
|
119
|
-
expect(SELECT_HUD_TITLE).toMatchObject({
|
|
131
|
+
expect(SELECT_HUD_TITLE).toMatchObject({
|
|
132
|
+
label: expect.objectContaining({ message: 'Select' }),
|
|
133
|
+
shortcut: 'V',
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('MEP handle HUD', () => {
|
|
139
|
+
const english = (hints: ReturnType<typeof mepHandleGestureHints>) =>
|
|
140
|
+
hints.map((hint) => (typeof hint.label === 'string' ? hint.label : hint.label.message))
|
|
141
|
+
const drag = (label: string) => ({ nodeId: 'pipe_a', label })
|
|
142
|
+
|
|
143
|
+
test('names a run point drag and an axis slide, and leaves rotate and resize to their own HUDs', () => {
|
|
144
|
+
expect(mepHandleGesture(drag(PATH_POINT_DRAG_LABEL))).toBe('edit-path')
|
|
145
|
+
expect(mepHandleGesture(drag(AXIS_MOVE_DRAG_LABEL))).toBe('move')
|
|
146
|
+
expect(mepHandleGesture(drag(ROTATE_HANDLE_DRAG_LABEL))).toBeNull()
|
|
147
|
+
expect(mepHandleGesture(drag('resize-diameter'))).toBeNull()
|
|
148
|
+
expect(mepHandleGesture(null)).toBeNull()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('keeps "Alt: Detach the joint" on screen for the whole run point drag', () => {
|
|
152
|
+
const idle = mepHandleGestureHints('edit-path', false)
|
|
153
|
+
expect(english(idle)).toEqual(['Detach the joint', 'Cancel'])
|
|
154
|
+
expect(idle.map((hint) => hint.keys)).toEqual([['Alt'], ['Esc']])
|
|
155
|
+
expect(idle[0]?.active).toBe(false)
|
|
156
|
+
expect(mepHandleGestureHints('edit-path', true)[0]?.active).toBe(true)
|
|
157
|
+
expect(english(mepHandleGestureHints('move', false))).toEqual(['Cancel'])
|
|
158
|
+
|
|
159
|
+
const html = renderToStaticMarkup(
|
|
160
|
+
<TooltipProvider>
|
|
161
|
+
<ContextualHelperPanel hints={idle} snapContext="polygon" />
|
|
162
|
+
</TooltipProvider>,
|
|
163
|
+
)
|
|
164
|
+
for (const label of ['Detach the joint', 'Cancel', 'Snapping: Grid']) expect(html).toContain(label)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('a handle drag snaps in its kind context, and a rotate arc in angles', () => {
|
|
168
|
+
const contextOf = (handle: string, profile: 'item' | 'structural') =>
|
|
169
|
+
snapContextOf({
|
|
170
|
+
scope: { kind: 'handle-drag', nodeId: 'pipe_a', handle },
|
|
171
|
+
mode: 'select',
|
|
172
|
+
tool: null,
|
|
173
|
+
profileOf: () => undefined,
|
|
174
|
+
profileOfNode: () => profile,
|
|
175
|
+
})
|
|
176
|
+
expect(contextOf(PATH_POINT_DRAG_LABEL, 'structural')).toBe('polygon')
|
|
177
|
+
expect(contextOf(AXIS_MOVE_DRAG_LABEL, 'structural')).toBe('polygon')
|
|
178
|
+
expect(contextOf(AXIS_MOVE_DRAG_LABEL, 'item')).toBe('item')
|
|
179
|
+
expect(contextOf('resize-diameter', 'item')).toBe('item')
|
|
180
|
+
expect(contextOf(ROTATE_HANDLE_DRAG_LABEL, 'item')).toBe('rotation')
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('Cut opening HUD', () => {
|
|
185
|
+
test('names the drag gesture "Draw the opening", not the mezzanine', () => {
|
|
186
|
+
// The drag row folds under "More shortcuts", so read the rows the HUD is given.
|
|
187
|
+
const english = (hints: ReturnType<typeof openingHints>) =>
|
|
188
|
+
hints.map((hint) => (typeof hint.label === 'string' ? hint.label : hint.label.message))
|
|
189
|
+
expect(english(openingHints('rectangle'))).toContain('Draw the opening')
|
|
190
|
+
expect(english(openingHints('rectangle'))).not.toContain('Draw the mezzanine')
|
|
191
|
+
expect(english(mezzanineHints('rectangle'))).toContain('Draw the mezzanine')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
// The roof tool holds its draft in a placing scope, so the HUD read "Move roof · Left click: Place"
|
|
196
|
+
// while a click set a footprint corner; the tool's own hints say what the click does.
|
|
197
|
+
describe('a registry tool holding its draft', () => {
|
|
198
|
+
const placing = (driver: 'registry-tool' | 'move-tool', nodeType = 'roof') => ({
|
|
199
|
+
kind: 'placing' as const,
|
|
200
|
+
node: { id: 'roof_draft', type: nodeType } as never,
|
|
201
|
+
nodeId: 'roof_draft',
|
|
202
|
+
nodeType,
|
|
203
|
+
view: '3d' as const,
|
|
204
|
+
pressDrag: false,
|
|
205
|
+
driver,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("shows the armed tool's hints", () => {
|
|
209
|
+
expect(toolHintsOwnPlacing(placing('registry-tool'), 'roof', 3)).toBe(true)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('leaves a move, a catalog placement or a tool with no hints to the move rows', () => {
|
|
213
|
+
expect(toolHintsOwnPlacing(placing('move-tool'), 'roof', 3)).toBe(false)
|
|
214
|
+
expect(toolHintsOwnPlacing(placing('registry-tool', 'item'), 'roof', 3)).toBe(false)
|
|
215
|
+
expect(toolHintsOwnPlacing(placing('registry-tool'), 'roof', 0)).toBe(false)
|
|
216
|
+
expect(
|
|
217
|
+
toolHintsOwnPlacing(
|
|
218
|
+
{ kind: 'moving', node: { id: 'r', type: 'roof' } as never, nodeId: 'r', nodeType: 'roof', view: '3d' },
|
|
219
|
+
'roof',
|
|
220
|
+
3,
|
|
221
|
+
),
|
|
222
|
+
).toBe(false)
|
|
120
223
|
})
|
|
121
224
|
})
|