@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
|
@@ -4,19 +4,22 @@ import {
|
|
|
4
4
|
type AnyNode,
|
|
5
5
|
type AnyNodeId,
|
|
6
6
|
type FloorFoundationPatch,
|
|
7
|
-
floorFootprintName,
|
|
8
7
|
floorFootprintSupportClass,
|
|
9
8
|
upperFloorHeightControl,
|
|
10
|
-
getLevelDisplayName,
|
|
11
9
|
getLevelElevations,
|
|
12
10
|
getStoredLevelHeight,
|
|
13
11
|
type LevelNode,
|
|
14
12
|
MIN_GROUND_FLOOR_THICKNESS,
|
|
15
13
|
MIN_SLAB_THICKNESS,
|
|
14
|
+
type PascalI18n,
|
|
16
15
|
type SlabNode,
|
|
16
|
+
useLocale,
|
|
17
|
+
usePascalI18n,
|
|
17
18
|
useScene,
|
|
18
19
|
} from '@pascal-app/core'
|
|
19
20
|
import { useViewer } from '@pascal-app/viewer'
|
|
21
|
+
import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name'
|
|
22
|
+
import { localizedLevelName } from '../../../i18n/level-name'
|
|
20
23
|
import { useRef, useState } from 'react'
|
|
21
24
|
import {
|
|
22
25
|
applyFloorFoundation,
|
|
@@ -33,7 +36,7 @@ import {
|
|
|
33
36
|
THICK_FLOOR_HINT,
|
|
34
37
|
thickFloorAdvice,
|
|
35
38
|
} from '../../../lib/floor-footprints'
|
|
36
|
-
import { formatLinearMeasurement } from '
|
|
39
|
+
import { formatLinearMeasurement } from '@pascal-app/core'
|
|
37
40
|
import { FinishSwatch } from '../controls/finish-swatch'
|
|
38
41
|
import { MetricControl } from '../controls/metric-control'
|
|
39
42
|
import { SegmentedControl } from '../controls/segmented-control'
|
|
@@ -78,6 +81,7 @@ export function FloorSection({
|
|
|
78
81
|
thickness: number
|
|
79
82
|
thicknessLabel: string
|
|
80
83
|
}) {
|
|
84
|
+
const i18n = usePascalI18n()
|
|
81
85
|
const base = 100
|
|
82
86
|
const foundation = !supported && raised ? Math.min(40, 14 + foundationHeight * 30) : 0
|
|
83
87
|
const band = Math.max(7, Math.min(16, thickness * 60))
|
|
@@ -98,32 +102,67 @@ export function FloorSection({
|
|
|
98
102
|
const left = 64
|
|
99
103
|
const right = 164
|
|
100
104
|
const labels: { y: number; text: string; key: string }[] = [
|
|
101
|
-
{
|
|
105
|
+
{
|
|
106
|
+
key: 'top',
|
|
107
|
+
y: bandTop - 9,
|
|
108
|
+
text: i18n._({ id: 'editor.panels.floorFoundationPanel.section.floorTop', message: 'Floor top' }),
|
|
109
|
+
},
|
|
102
110
|
{
|
|
103
111
|
key: 'plate',
|
|
104
112
|
y: bandTop + band / 2,
|
|
105
|
-
text: supported
|
|
113
|
+
text: supported
|
|
114
|
+
? i18n._({
|
|
115
|
+
id: 'editor.panels.floorFoundationPanel.section.floorPlate',
|
|
116
|
+
message: 'Floor plate · {thickness}',
|
|
117
|
+
values: { thickness: thicknessLabel },
|
|
118
|
+
})
|
|
119
|
+
: i18n._({
|
|
120
|
+
id: 'editor.panels.floorFoundationPanel.section.slab',
|
|
121
|
+
message: 'Slab · {thickness}',
|
|
122
|
+
values: { thickness: thicknessLabel },
|
|
123
|
+
}),
|
|
106
124
|
},
|
|
107
125
|
...(foundation
|
|
108
126
|
? [
|
|
109
127
|
{
|
|
110
128
|
key: 'foundation',
|
|
111
129
|
y: bandBottom + foundation / 2,
|
|
112
|
-
text: foundationLabel
|
|
130
|
+
text: foundationLabel
|
|
131
|
+
? i18n._({
|
|
132
|
+
id: 'editor.panels.floorFoundationPanel.section.foundationKind',
|
|
133
|
+
message: 'Foundation · {foundation}',
|
|
134
|
+
values: { foundation: foundationLabel },
|
|
135
|
+
})
|
|
136
|
+
: i18n._({ id: 'editor.panels.floorFoundationPanel.section.foundation', message: 'Foundation' }),
|
|
113
137
|
},
|
|
114
138
|
]
|
|
115
139
|
: []),
|
|
116
|
-
{
|
|
140
|
+
{
|
|
141
|
+
key: 'base',
|
|
142
|
+
y: base + 9,
|
|
143
|
+
text: supported
|
|
144
|
+
? i18n._({ id: 'editor.panels.floorFoundationPanel.section.wallsBelow', message: 'Walls below' })
|
|
145
|
+
: i18n._({ id: 'editor.panels.floorFoundationPanel.section.ground', message: 'Ground' }),
|
|
146
|
+
},
|
|
117
147
|
]
|
|
118
148
|
return (
|
|
119
149
|
<figure className="flex flex-col gap-1" data-floor-section>
|
|
120
150
|
<svg
|
|
121
151
|
aria-label={
|
|
122
152
|
supported
|
|
123
|
-
?
|
|
153
|
+
? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn', message: 'Floor plate {thicknessLabel} thick, resting on the walls below', values: { thicknessLabel } })
|
|
124
154
|
: raised
|
|
125
|
-
?
|
|
126
|
-
|
|
155
|
+
? i18n._({
|
|
156
|
+
id: 'editor.panels.floorFoundationPanel.sectionRaised',
|
|
157
|
+
message:
|
|
158
|
+
'Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground',
|
|
159
|
+
values: { thickness: thicknessLabel, foundation: foundationLabel, top: topLabel ?? '' },
|
|
160
|
+
})
|
|
161
|
+
: i18n._({
|
|
162
|
+
id: 'editor.panels.floorFoundationPanel.sectionOnGround',
|
|
163
|
+
message: 'Slab {thickness} thick on the ground, floor top {top} above the ground',
|
|
164
|
+
values: { thickness: thicknessLabel, top: topLabel ?? '' },
|
|
165
|
+
})
|
|
127
166
|
}
|
|
128
167
|
className="h-auto w-full text-muted-foreground"
|
|
129
168
|
role="img"
|
|
@@ -259,7 +298,7 @@ export function FloorSection({
|
|
|
259
298
|
))}
|
|
260
299
|
</svg>
|
|
261
300
|
<figcaption className="text-right text-[10px] text-muted-foreground/70">
|
|
262
|
-
Not to scale
|
|
301
|
+
{i18n._({ id: 'editor.panels.floorFoundationPanel.notToScale', message: 'Not to scale' })}
|
|
263
302
|
</figcaption>
|
|
264
303
|
</figure>
|
|
265
304
|
)
|
|
@@ -271,15 +310,29 @@ type Notice = {
|
|
|
271
310
|
together: { ids: string[]; patch: FloorFoundationPatch; lower: boolean } | null
|
|
272
311
|
}
|
|
273
312
|
|
|
274
|
-
export function moveTogetherLabel(count: number, lower: boolean) {
|
|
275
|
-
const
|
|
276
|
-
return
|
|
313
|
+
export function moveTogetherLabel(count: number, lower: boolean, i18n: PascalI18n) {
|
|
314
|
+
const values = { count, number: String(count) }
|
|
315
|
+
return lower
|
|
316
|
+
? i18n._({
|
|
317
|
+
id: 'editor.panels.floorFoundationPanel.lowerTogether',
|
|
318
|
+
message: '{count, plural, =2 {Lower both} other {Lower all {number}}}',
|
|
319
|
+
values,
|
|
320
|
+
})
|
|
321
|
+
: i18n._({
|
|
322
|
+
id: 'editor.panels.floorFoundationPanel.raiseTogether',
|
|
323
|
+
message: '{count, plural, =2 {Raise both} other {Raise all {number}}}',
|
|
324
|
+
values,
|
|
325
|
+
})
|
|
277
326
|
}
|
|
278
327
|
|
|
279
328
|
/** "Floor plate · Ground floor", upstairs "Floor plate · Floor 1 · rests on Ground floor". */
|
|
280
|
-
export function floorPlateKicker(
|
|
329
|
+
export function floorPlateKicker(
|
|
330
|
+
nodes: Record<string, AnyNode>,
|
|
331
|
+
plate: SlabNode,
|
|
332
|
+
i18n: PascalI18n,
|
|
333
|
+
locale: string,
|
|
334
|
+
): string {
|
|
281
335
|
const level = plate.parentId ? nodes[plate.parentId] : undefined
|
|
282
|
-
const levelName = level?.type === 'level' ? ` · ${getLevelDisplayName(level)}` : ''
|
|
283
336
|
// A supported floor rests on the storey right below it.
|
|
284
337
|
let below: LevelNode | null = null
|
|
285
338
|
if (level?.type === 'level' && floorFootprintSupportClass(nodes, plate) === 'supported')
|
|
@@ -291,8 +344,20 @@ export function floorPlateKicker(nodes: Record<string, AnyNode>, plate: SlabNode
|
|
|
291
344
|
(!below || node.level > below.level)
|
|
292
345
|
)
|
|
293
346
|
below = node
|
|
294
|
-
|
|
295
|
-
|
|
347
|
+
if (level?.type !== 'level')
|
|
348
|
+
return i18n._({ id: 'editor.panels.floorFoundationPanel.kicker', message: 'Floor plate' })
|
|
349
|
+
const levelName = localizedLevelName(level, i18n, locale)
|
|
350
|
+
return below
|
|
351
|
+
? i18n._({
|
|
352
|
+
id: 'editor.panels.floorFoundationPanel.kickerRestsOn',
|
|
353
|
+
message: 'Floor plate · {level} · rests on {below}',
|
|
354
|
+
values: { level: levelName, below: localizedLevelName(below, i18n, locale) },
|
|
355
|
+
})
|
|
356
|
+
: i18n._({
|
|
357
|
+
id: 'editor.panels.floorFoundationPanel.kickerLevel',
|
|
358
|
+
message: 'Floor plate · {level}',
|
|
359
|
+
values: { level: levelName },
|
|
360
|
+
})
|
|
296
361
|
}
|
|
297
362
|
|
|
298
363
|
/**
|
|
@@ -311,7 +376,6 @@ export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: Slab
|
|
|
311
376
|
: 0
|
|
312
377
|
return {
|
|
313
378
|
upper,
|
|
314
|
-
kicker: floorPlateKicker(nodes, plate),
|
|
315
379
|
preset: upper ? ('ground' as const) : footprintPreset(nodes, plate),
|
|
316
380
|
/** Upstairs: the floor top (level-local). On the ground: the foundation height. */
|
|
317
381
|
height,
|
|
@@ -332,6 +396,8 @@ export function floorFoundationModel(nodes: Record<string, AnyNode>, plate: Slab
|
|
|
332
396
|
* the building live and lands as one undo step.
|
|
333
397
|
*/
|
|
334
398
|
export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClose: () => void }) {
|
|
399
|
+
const i18n = usePascalI18n()
|
|
400
|
+
const locale = useLocale()
|
|
335
401
|
const nodes = useScene((state) => state.nodes)
|
|
336
402
|
const unit = useViewer((s) => s.unit)
|
|
337
403
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
@@ -341,7 +407,8 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
341
407
|
const plate = nodes[node.id as AnyNodeId]
|
|
342
408
|
if (plate?.type !== 'slab' || plate.plateRole !== 'base') return null
|
|
343
409
|
const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation)
|
|
344
|
-
const
|
|
410
|
+
const kicker = floorPlateKicker(nodes, plate, i18n, locale)
|
|
411
|
+
const { upper, preset, height, floorTop, minHeight, minThickness, underFloor, thickFloor } =
|
|
345
412
|
floorFoundationModel(nodes, plate)
|
|
346
413
|
const level = plate.parentId ? nodes[plate.parentId as AnyNodeId] : undefined
|
|
347
414
|
const maxHeight = level?.type === 'level' ? getStoredLevelHeight(level) - 0.5 : 3
|
|
@@ -389,7 +456,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
389
456
|
icon="/icons/floor.webp"
|
|
390
457
|
kicker={kicker}
|
|
391
458
|
onClose={onClose}
|
|
392
|
-
title={
|
|
459
|
+
title={localizedFloorFootprintName(nodes, plate, i18n)}
|
|
393
460
|
>
|
|
394
461
|
<div
|
|
395
462
|
className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm"
|
|
@@ -407,13 +474,13 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
407
474
|
/>
|
|
408
475
|
<section className="flex flex-col gap-1.5" data-floor-plate-group>
|
|
409
476
|
<h3 className="font-medium text-muted-foreground text-xs">
|
|
410
|
-
{upper ? 'Floor plate' : 'Slab'}
|
|
477
|
+
{upper ? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlate', message: 'Floor plate' }) : i18n._({ id: 'editor.panels.floorFoundationPanel.slab', message: 'Slab' })}
|
|
411
478
|
</h3>
|
|
412
479
|
{upper ? (
|
|
413
|
-
<div title=
|
|
480
|
+
<div title={i18n._({ id: 'editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe', message: 'A thicker floor grows upward: the rooms below keep their height.' })}>
|
|
414
481
|
<MetricControl
|
|
415
482
|
className="h-9 text-xs"
|
|
416
|
-
label=
|
|
483
|
+
label={i18n._({ id: 'editor.panels.floorFoundationPanel.floorThickness', message: 'Floor thickness' })}
|
|
417
484
|
max={Math.max(MIN_SLAB_THICKNESS, maxHeight - underside)}
|
|
418
485
|
min={Math.max(MIN_SLAB_THICKNESS, minHeight - underside)}
|
|
419
486
|
onChange={(next) => previewHeight(underside + next)}
|
|
@@ -426,10 +493,10 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
426
493
|
/>
|
|
427
494
|
</div>
|
|
428
495
|
) : (
|
|
429
|
-
<div title=
|
|
496
|
+
<div title={i18n._({ id: 'editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation', message: 'The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.' })}>
|
|
430
497
|
<MetricControl
|
|
431
498
|
className="h-9 text-xs"
|
|
432
|
-
label=
|
|
499
|
+
label={i18n._({ id: 'editor.panels.floorFoundationPanel.slabThickness', message: 'Slab thickness' })}
|
|
433
500
|
max={1}
|
|
434
501
|
min={minThickness}
|
|
435
502
|
onChange={(next) => {
|
|
@@ -454,15 +521,19 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
454
521
|
)}
|
|
455
522
|
{upper && (
|
|
456
523
|
<p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
|
|
457
|
-
|
|
524
|
+
{i18n._({
|
|
525
|
+
id: 'editor.panels.floorFoundationPanel.floorTopAbove',
|
|
526
|
+
message: 'Floor top +{height}',
|
|
527
|
+
values: { height: length(floorTop) },
|
|
528
|
+
})}
|
|
458
529
|
</p>
|
|
459
530
|
)}
|
|
460
531
|
<FinishSwatch
|
|
461
532
|
fallback={EDGE_DEFAULT}
|
|
462
|
-
hint=
|
|
463
|
-
label=
|
|
533
|
+
hint={i18n._({ id: 'editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll', message: 'The floor\'s outside edge, all around the footprint.' })}
|
|
534
|
+
label={i18n._({ id: 'editor.panels.floorFoundationPanel.edgeFinish', message: 'Edge finish' })}
|
|
464
535
|
node={plate}
|
|
465
|
-
|
|
536
|
+
paintRole="edge"
|
|
466
537
|
value={plate.slots?.edge}
|
|
467
538
|
/>
|
|
468
539
|
{thickFloor && (
|
|
@@ -473,7 +544,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
473
544
|
</section>
|
|
474
545
|
{underFloor && (
|
|
475
546
|
<section className="flex flex-col gap-1.5" data-under-floor-group>
|
|
476
|
-
<h3 className="font-medium text-muted-foreground text-xs">Under the floor</h3>
|
|
547
|
+
<h3 className="font-medium text-muted-foreground text-xs">{i18n._({ id: 'editor.panels.floorFoundationPanel.underTheFloor', message: 'Under the floor' })}</h3>
|
|
477
548
|
<SegmentedControl
|
|
478
549
|
onChange={(next: FootprintPreset) => {
|
|
479
550
|
if (next === preset) return
|
|
@@ -481,17 +552,17 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
481
552
|
report(applyFloorFoundation(plate.id, patch), patch)
|
|
482
553
|
}}
|
|
483
554
|
options={[
|
|
484
|
-
{ label: 'On the ground', value: 'ground' },
|
|
485
|
-
{ label: 'Raised on a foundation', value: 'raised' },
|
|
555
|
+
{ label: i18n._({ id: 'editor.panels.floorFoundationPanel.onTheGround', message: 'On the ground' }), value: 'ground' },
|
|
556
|
+
{ label: i18n._({ id: 'editor.panels.floorFoundationPanel.raisedOnAFoundation', message: 'Raised on a foundation' }), value: 'raised' },
|
|
486
557
|
]}
|
|
487
558
|
value={preset}
|
|
488
559
|
/>
|
|
489
560
|
{preset === 'raised' && (
|
|
490
561
|
<>
|
|
491
|
-
<div title=
|
|
562
|
+
<div title={i18n._({ id: 'editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround', message: 'The foundation stands on the ground; the slab sits on it.' })}>
|
|
492
563
|
<MetricControl
|
|
493
564
|
className="h-9 text-xs"
|
|
494
|
-
label=
|
|
565
|
+
label={i18n._({ id: 'editor.panels.floorFoundationPanel.foundationHeight', message: 'Foundation height' })}
|
|
495
566
|
max={Math.max(0.05, maxHeight)}
|
|
496
567
|
min={minHeight}
|
|
497
568
|
onChange={previewHeight}
|
|
@@ -505,9 +576,9 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
505
576
|
</div>
|
|
506
577
|
<FinishSwatch
|
|
507
578
|
fallback={FOUNDATION_DEFAULT}
|
|
508
|
-
label=
|
|
579
|
+
label={i18n._({ id: 'editor.panels.floorFoundationPanel.foundationFinish', message: 'Foundation finish' })}
|
|
509
580
|
node={plate}
|
|
510
|
-
|
|
581
|
+
paintRole="foundation"
|
|
511
582
|
value={
|
|
512
583
|
typeof plate.foundation?.material === 'string'
|
|
513
584
|
? plate.foundation.material
|
|
@@ -517,7 +588,11 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
517
588
|
</>
|
|
518
589
|
)}
|
|
519
590
|
<p className="px-0.5 text-muted-foreground text-xs" data-floor-top>
|
|
520
|
-
|
|
591
|
+
{i18n._({
|
|
592
|
+
id: 'editor.panels.floorFoundationPanel.floorTopValue',
|
|
593
|
+
message: 'Floor top {height}',
|
|
594
|
+
values: { height: length(floorTop) },
|
|
595
|
+
})}
|
|
521
596
|
</p>
|
|
522
597
|
</section>
|
|
523
598
|
)}
|
|
@@ -540,7 +615,7 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos
|
|
|
540
615
|
type="button"
|
|
541
616
|
variant="outline"
|
|
542
617
|
>
|
|
543
|
-
{moveTogetherLabel(notice.together.ids.length, notice.together.lower)}
|
|
618
|
+
{moveTogetherLabel(notice.together.ids.length, notice.together.lower, i18n)}
|
|
544
619
|
</Button>
|
|
545
620
|
)}
|
|
546
621
|
</div>
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
adjacentLevelId,
|
|
6
6
|
area,
|
|
7
7
|
floorOpeningHints,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
useScene, usePascalI18n,
|
|
9
|
+
useLocale,
|
|
10
10
|
} from '@pascal-app/core'
|
|
11
11
|
import { useViewer } from '@pascal-app/viewer'
|
|
12
12
|
import {
|
|
@@ -14,10 +14,13 @@ import {
|
|
|
14
14
|
finishFloorOpening,
|
|
15
15
|
openingRoom,
|
|
16
16
|
} from '../../../lib/floor-edit-session'
|
|
17
|
-
import { formatAreaLabel } from '
|
|
17
|
+
import { formatAreaLabel } from '@pascal-app/core'
|
|
18
18
|
import { Button } from '../primitives/button'
|
|
19
19
|
import { Switch } from '../primitives/switch'
|
|
20
20
|
import { PanelWrapper } from './panel-wrapper'
|
|
21
|
+
import { localizedLevelName } from '../../../i18n/level-name'
|
|
22
|
+
import { nodeLabel } from '../../../i18n/node-messages'
|
|
23
|
+
import { displayRoomName } from '../../../lib/room-name-messages'
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* A floor (or ceiling) opening: where it is, what it cuts, and the switch for
|
|
@@ -27,6 +30,8 @@ import { PanelWrapper } from './panel-wrapper'
|
|
|
27
30
|
* handles in the scene. Done and Delete return to the room.
|
|
28
31
|
*/
|
|
29
32
|
export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
33
|
+
const i18n = usePascalI18n()
|
|
34
|
+
const locale = useLocale()
|
|
30
35
|
const nodes = useScene((state) => state.nodes)
|
|
31
36
|
const unit = useViewer((s) => s.unit)
|
|
32
37
|
const readOnly = useScene((state) => state.readOnly)
|
|
@@ -35,7 +40,13 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
35
40
|
const onCeiling = opening.drawnOn === 'ceiling'
|
|
36
41
|
const zoneId = openingRoom(nodes, opening)
|
|
37
42
|
const zone = zoneId ? nodes[zoneId as AnyNodeId] : undefined
|
|
38
|
-
const
|
|
43
|
+
const storedRoomName = zone?.type === 'zone' ? zone.name?.trim() : undefined
|
|
44
|
+
const roomName =
|
|
45
|
+
zone?.type !== 'zone'
|
|
46
|
+
? null
|
|
47
|
+
: storedRoomName
|
|
48
|
+
? displayRoomName(storedRoomName, i18n)
|
|
49
|
+
: i18n._({ id: 'editor.panels.floorOpeningPanel.room', message: 'Room' })
|
|
39
50
|
const level = opening.parentId ? nodes[opening.parentId as AnyNodeId] : undefined
|
|
40
51
|
const adjacentId = opening.parentId
|
|
41
52
|
? adjacentLevelId(nodes, opening.parentId, onCeiling ? 1 : -1)
|
|
@@ -47,19 +58,35 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
47
58
|
!!opening.ownerId &&
|
|
48
59
|
nodes[opening.ownerId as AnyNodeId]?.type === opening.source
|
|
49
60
|
const hints = floorOpeningHints(nodes, opening)
|
|
50
|
-
const title =
|
|
51
|
-
|
|
52
|
-
:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const title = i18n._({
|
|
62
|
+
id: 'editor.panels.floorOpeningPanel.title',
|
|
63
|
+
message:
|
|
64
|
+
'{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}',
|
|
65
|
+
values: { source: opening.source === 'stair' || opening.source === 'elevator' ? opening.source : onCeiling ? 'ceiling' : 'floor' },
|
|
66
|
+
})
|
|
67
|
+
const levelName = level?.type === 'level' ? localizedLevelName(level, i18n, locale) : null
|
|
68
|
+
const kicker = roomName
|
|
69
|
+
? levelName
|
|
70
|
+
? i18n._({
|
|
71
|
+
id: 'editor.panels.floorOpeningPanel.kickerRoomLevel',
|
|
72
|
+
message: 'Opening · in {room} · {level}',
|
|
73
|
+
values: { room: roomName, level: levelName },
|
|
74
|
+
})
|
|
75
|
+
: i18n._({
|
|
76
|
+
id: 'editor.panels.floorOpeningPanel.kickerRoom',
|
|
77
|
+
message: 'Opening · in {room}',
|
|
78
|
+
values: { room: roomName },
|
|
79
|
+
})
|
|
80
|
+
: levelName
|
|
81
|
+
? i18n._({
|
|
82
|
+
id: 'editor.panels.floorOpeningPanel.kickerLevel',
|
|
83
|
+
message: 'Opening · {level}',
|
|
84
|
+
values: { level: levelName },
|
|
85
|
+
})
|
|
86
|
+
: i18n._({ id: 'editor.panels.floorOpeningPanel.kicker', message: 'Opening' })
|
|
87
|
+
const switchLabel = onCeiling
|
|
88
|
+
? i18n._({ id: 'editor.panels.floorOpeningPanel.alsoCutTheFloorAbove', message: 'Also cut the floor above' })
|
|
89
|
+
: i18n._({ id: 'editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow', message: 'Also cut the ceiling below' })
|
|
63
90
|
const setAdjacent = (next: boolean) =>
|
|
64
91
|
useScene.getState().updateNode(opening.id, { cutsAdjacent: next })
|
|
65
92
|
|
|
@@ -72,8 +99,23 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
72
99
|
>
|
|
73
100
|
<div className="flex flex-col gap-3 px-4 pt-3 pb-4 text-sm" data-floor-opening-panel={opening.id}>
|
|
74
101
|
<p className="text-muted-foreground text-xs">
|
|
75
|
-
{
|
|
76
|
-
|
|
102
|
+
{owned
|
|
103
|
+
? opening.source === 'stair'
|
|
104
|
+
? i18n._({
|
|
105
|
+
id: 'editor.panels.floorOpeningPanel.areaStairResize',
|
|
106
|
+
message: '{area} · Drag its sides or corners to resize it; it moves with the stair.',
|
|
107
|
+
values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) },
|
|
108
|
+
})
|
|
109
|
+
: i18n._({
|
|
110
|
+
id: 'editor.panels.floorOpeningPanel.areaOwned',
|
|
111
|
+
message: '{area} · Shape follows its owner.',
|
|
112
|
+
values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) },
|
|
113
|
+
})
|
|
114
|
+
: i18n._({
|
|
115
|
+
id: 'editor.panels.floorOpeningPanel.areaDragCorners',
|
|
116
|
+
message: '{area} · Drag its corners to change the shape.',
|
|
117
|
+
values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) },
|
|
118
|
+
})}
|
|
77
119
|
</p>
|
|
78
120
|
{owned && (
|
|
79
121
|
<button
|
|
@@ -82,12 +124,14 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
82
124
|
onClick={() => useViewer.getState().setSelection({ selectedIds: [opening.ownerId!] })}
|
|
83
125
|
type="button"
|
|
84
126
|
>
|
|
85
|
-
|
|
127
|
+
{opening.source === 'stair'
|
|
128
|
+
? i18n._({ id: 'editor.panels.floorOpeningPanel.ownedByStaircase', message: 'Owned by Staircase' })
|
|
129
|
+
: i18n._({ id: 'editor.panels.floorOpeningPanel.ownedByElevator', message: 'Owned by Elevator' })}
|
|
86
130
|
</button>
|
|
87
131
|
)}
|
|
88
132
|
{hosted ? (
|
|
89
133
|
<p className="text-muted-foreground text-xs" data-opening-hosted>
|
|
90
|
-
A hatch in the mezzanine: it cuts only the mezzanine’s floor.
|
|
134
|
+
{i18n._({ id: 'editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt', message: 'A hatch in the mezzanine: it cuts only the mezzanine’s floor.' })}
|
|
91
135
|
</p>
|
|
92
136
|
) : owned ? null : (
|
|
93
137
|
<label className="flex items-start gap-3" data-opening-adjacent>
|
|
@@ -95,10 +139,14 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
95
139
|
<span className="font-medium text-[13px] text-foreground">{switchLabel}</span>
|
|
96
140
|
<span className="text-muted-foreground text-xs">
|
|
97
141
|
{adjacent?.type === 'level'
|
|
98
|
-
?
|
|
142
|
+
? i18n._({
|
|
143
|
+
id: 'editor.panels.floorOpeningPanel.oneVoidWith',
|
|
144
|
+
message: 'On {level}, as one void with this opening.',
|
|
145
|
+
values: { level: localizedLevelName(adjacent, i18n, locale) },
|
|
146
|
+
})
|
|
99
147
|
: onCeiling
|
|
100
|
-
? 'There is no floor above this storey.'
|
|
101
|
-
: 'There is no storey below.'}
|
|
148
|
+
? i18n._({ id: 'editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis', message: 'There is no floor above this storey.' })
|
|
149
|
+
: i18n._({ id: 'editor.panels.floorOpeningPanel.thereIsNoStoreyBelow', message: 'There is no storey below.' })}
|
|
102
150
|
</span>
|
|
103
151
|
</span>
|
|
104
152
|
<Switch
|
|
@@ -123,7 +171,7 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
123
171
|
type="button"
|
|
124
172
|
variant="outline"
|
|
125
173
|
>
|
|
126
|
-
Done
|
|
174
|
+
{i18n._({ id: 'editor.panels.floorOpeningPanel.done', message: 'Done' })}
|
|
127
175
|
</Button>
|
|
128
176
|
{!owned && <Button
|
|
129
177
|
className="flex-1 rounded-full"
|
|
@@ -134,7 +182,7 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {
|
|
|
134
182
|
type="button"
|
|
135
183
|
variant="outline"
|
|
136
184
|
>
|
|
137
|
-
Delete
|
|
185
|
+
{i18n._({ id: 'editor.panels.floorOpeningPanel.delete', message: 'Delete' })}
|
|
138
186
|
</Button>}
|
|
139
187
|
</div>
|
|
140
188
|
</div>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@iconify/react'
|
|
4
|
+
import {
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
getInspectorExtensions,
|
|
7
|
+
type IconRef,
|
|
8
|
+
useRegistryVersion,
|
|
9
|
+
useScene,
|
|
10
|
+
} from '@pascal-app/core'
|
|
11
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
12
|
+
import { lazy, Suspense, useMemo } from 'react'
|
|
13
|
+
import { IconRefImage } from '../icon-ref'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The plugin inspector extensions (`Plugin.inspectorExtensions`) for the single selected node,
|
|
17
|
+
* gated on their plugin being installed, and the one that owns the card body for it
|
|
18
|
+
* (`primaryWhen`). Read by the inspector card and by the action menu that opens them.
|
|
19
|
+
*/
|
|
20
|
+
export function useInspectorExtensions() {
|
|
21
|
+
const registryVersion = useRegistryVersion()
|
|
22
|
+
const selectedId = useViewer((s) =>
|
|
23
|
+
s.selection.selectedIds.length === 1 ? s.selection.selectedIds[0] : undefined,
|
|
24
|
+
) as AnyNodeId | undefined
|
|
25
|
+
// Subscribe to the selected node's *type* only — a string primitive that
|
|
26
|
+
// doesn't change as fields are edited (same trick as ParametricInspector).
|
|
27
|
+
const selectedType = useScene((s) => (selectedId ? (s.nodes[selectedId]?.type ?? null) : null))
|
|
28
|
+
const installedPlugins = useScene((s) => s.installedPlugins)
|
|
29
|
+
const extensions = useMemo(() => {
|
|
30
|
+
// re-derive when plugin extensions register after mount (async plugin load)
|
|
31
|
+
void registryVersion
|
|
32
|
+
if (!selectedType) return []
|
|
33
|
+
return getInspectorExtensions(selectedType).filter(
|
|
34
|
+
(extension) => !extension.pluginId || installedPlugins.includes(extension.pluginId),
|
|
35
|
+
)
|
|
36
|
+
}, [selectedType, installedPlugins, registryVersion])
|
|
37
|
+
const primaryExtensionId = useScene((state) => {
|
|
38
|
+
const node = selectedId ? state.nodes[selectedId] : undefined
|
|
39
|
+
return node ? extensions.find((extension) => extension.primaryWhen?.(node))?.id : undefined
|
|
40
|
+
})
|
|
41
|
+
const primaryExtension = extensions.find((extension) => extension.id === primaryExtensionId)
|
|
42
|
+
return { selectedId, extensions, primaryExtension }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** An extension's 16px icon — mirrors the parametric inspector's `renderIcon` (plain <img> so no next/image server deps). */
|
|
46
|
+
export function renderExtensionIcon(ref: IconRef): React.ReactNode {
|
|
47
|
+
if (ref.kind === 'url') {
|
|
48
|
+
return <IconRefImage className="h-4 w-4 shrink-0" src={ref.src} />
|
|
49
|
+
}
|
|
50
|
+
if (ref.kind === 'iconify') {
|
|
51
|
+
return <Icon height={16} icon={ref.name} width={16} />
|
|
52
|
+
}
|
|
53
|
+
if (ref.kind === 'svg') {
|
|
54
|
+
return (
|
|
55
|
+
<svg height={16} viewBox={ref.viewBox} width={16}>
|
|
56
|
+
<path d={ref.path} fill="currentColor" />
|
|
57
|
+
</svg>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
const LazyIcon = lazy(ref.module)
|
|
61
|
+
return (
|
|
62
|
+
<Suspense fallback={null}>
|
|
63
|
+
<LazyIcon />
|
|
64
|
+
</Suspense>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import { X } from 'lucide-react'
|
|
4
5
|
import { AnimatePresence, motion } from 'motion/react'
|
|
5
6
|
import { type ReactNode, useEffect, useState } from 'react'
|
|
@@ -19,6 +20,7 @@ const HEIGHT_VH = 50
|
|
|
19
20
|
const DRAG_CLOSE_THRESHOLD_PX = 120
|
|
20
21
|
|
|
21
22
|
export function MobilePanelSheet({ open, onClose, icon, title, children }: MobilePanelSheetProps) {
|
|
23
|
+
const i18n = usePascalI18n()
|
|
22
24
|
const [mounted, setMounted] = useState(false)
|
|
23
25
|
const setMobilePanelSheetHeight = useEditor((s) => s.setMobilePanelSheetHeight)
|
|
24
26
|
|
|
@@ -88,7 +90,7 @@ export function MobilePanelSheet({ open, onClose, icon, title, children }: Mobil
|
|
|
88
90
|
</h2>
|
|
89
91
|
</div>
|
|
90
92
|
<button
|
|
91
|
-
aria-label=
|
|
93
|
+
aria-label={i18n._({ id: 'editor.panels.mobilePanelSheet.close', message: 'Close' })}
|
|
92
94
|
className="flex h-8 w-8 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
|
|
93
95
|
onClick={onClose}
|
|
94
96
|
type="button"
|