@pascal-app/editor 1.0.5 → 1.0.7
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 +20 -17
- 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 +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- 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 +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.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 +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- 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 +1 -1
- 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 +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- 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 +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- 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-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- 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/tools/distribution/distribution-run-tool.tsx +50 -26
- 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 +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- 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 +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- 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/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 +30 -14
- 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 +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -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 +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- 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 +8 -6
- 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 +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- 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 +464 -65
- 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 +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- 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/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/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- 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 +117 -64
- 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 +8 -3
- 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 +18 -8
- 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 +61 -4
- 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 +15117 -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 +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -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 +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- 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/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/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/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/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-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- 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/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -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 +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- 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 +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- 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/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 +20 -5
- 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 +14 -5
- 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 +42 -0
- package/src/lib/window-interaction.ts +47 -5
- 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 +48 -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-wall-snap-indicator.ts +15 -2
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type AnyNode,
|
|
4
5
|
type AnyNodeId,
|
|
5
|
-
|
|
6
|
+
BEAM_SIZE_PRESETS,
|
|
6
7
|
floorFootprintSupportClass,
|
|
8
|
+
formatList,
|
|
7
9
|
floorPlateAtGroundContact,
|
|
8
10
|
MIN_GROUND_FLOOR_THICKNESS,
|
|
9
11
|
MIN_SLAB_THICKNESS,
|
|
12
|
+
type MessageDescriptor,
|
|
13
|
+
type OutdoorRoomType,
|
|
14
|
+
type PorchFramingSettings,
|
|
15
|
+
porchFramingSettings,
|
|
10
16
|
roomDrawnFloor,
|
|
17
|
+
setZoneIntent,
|
|
18
|
+
type PascalI18n,
|
|
19
|
+
useLocale,
|
|
20
|
+
usePascalI18n,
|
|
11
21
|
useScene,
|
|
12
22
|
} from '@pascal-app/core'
|
|
13
23
|
import { useViewer } from '@pascal-app/viewer'
|
|
@@ -27,7 +37,7 @@ import {
|
|
|
27
37
|
import { formatLinearMeasurement } from '../../../lib/measurements'
|
|
28
38
|
import { startMezzanineDraft } from '../../../lib/mezzanine-draft'
|
|
29
39
|
import { resolveRoomAssemblyHeights } from '../../../lib/room-assembly-overlay'
|
|
30
|
-
import { roomBuiltOn, separateFloorSummary } from '../../../lib/room-built-on'
|
|
40
|
+
import { type FloorChoice, roomBuiltOn, separateFloorSummary } from '../../../lib/room-built-on'
|
|
31
41
|
import type { RoomConstructionState } from '../../../lib/room-construction'
|
|
32
42
|
import { roomConstructionState } from '../../../lib/room-construction'
|
|
33
43
|
import {
|
|
@@ -50,7 +60,10 @@ import {
|
|
|
50
60
|
import { ownFloorElevationBounds } from '../../../lib/room-handle-drag'
|
|
51
61
|
import type { RoomSelectionRecord } from '../../../lib/room-selection'
|
|
52
62
|
import { cn } from '../../../lib/utils'
|
|
63
|
+
import { roomPorchFramingCounts } from '../../../lib/room-construction'
|
|
64
|
+
import { applyRoomPlan } from '../../../lib/room-structure-commands'
|
|
53
65
|
import { MetricControl } from '../controls/metric-control'
|
|
66
|
+
import { SegmentedControl } from '../controls/segmented-control'
|
|
54
67
|
import {
|
|
55
68
|
DropdownMenu,
|
|
56
69
|
DropdownMenuContent,
|
|
@@ -60,8 +73,13 @@ import {
|
|
|
60
73
|
} from '../primitives/dropdown-menu'
|
|
61
74
|
import { Switch } from '../primitives/switch'
|
|
62
75
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip'
|
|
76
|
+
import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name'
|
|
77
|
+
import { messageWithSlot } from '../../../i18n/message-slot'
|
|
78
|
+
import { nodeLabel } from '../../../i18n/node-messages'
|
|
79
|
+
import { DeckBoardsFields } from './deck-boards-fields'
|
|
63
80
|
import { RoomOpeningsField } from './room-openings-field'
|
|
64
81
|
import { ShapeChoice } from './shape-choice'
|
|
82
|
+
import { displayRoomName } from '../../../lib/room-name-messages'
|
|
65
83
|
|
|
66
84
|
/** The buttons a row offers — derived together with its presence. */
|
|
67
85
|
export function constructionActions(state: RoomConstructionState, part: RoomConstructionPart) {
|
|
@@ -84,12 +102,22 @@ export function wallsSwitch(state: RoomConstructionState) {
|
|
|
84
102
|
}
|
|
85
103
|
}
|
|
86
104
|
|
|
105
|
+
/** A "Built on" choice in the UI language: its plate's footprint name, or the shared floor. */
|
|
106
|
+
function floorChoiceName(
|
|
107
|
+
nodes: Readonly<Record<string, AnyNode>>,
|
|
108
|
+
choice: Pick<FloorChoice, 'plateId' | 'name'>,
|
|
109
|
+
i18n: PascalI18n,
|
|
110
|
+
): string {
|
|
111
|
+
const plate = choice.plateId ? nodes[choice.plateId] : undefined
|
|
112
|
+
if (plate?.type === 'slab') return localizedFloorFootprintName(nodes, plate, i18n)
|
|
113
|
+
return choice.plateId
|
|
114
|
+
? choice.name
|
|
115
|
+
: i18n._({ id: 'editor.floorFootprint.shared', message: 'Shared floor' })
|
|
116
|
+
}
|
|
117
|
+
|
|
87
118
|
export const pill =
|
|
88
119
|
'rounded-full border border-border/50 px-2.5 py-0.5 text-xs transition-colors hover:bg-accent disabled:pointer-events-none disabled:opacity-50'
|
|
89
120
|
|
|
90
|
-
const count = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`
|
|
91
|
-
|
|
92
|
-
export const DRAWN_FLOOR_SWITCH_HINT = 'This floor is a drawn slab: select it to edit or delete it.'
|
|
93
121
|
|
|
94
122
|
/**
|
|
95
123
|
* One "Built with" row: its icon, name and one-line summary, a chevron that
|
|
@@ -122,6 +150,7 @@ export function ConstructionPart({
|
|
|
122
150
|
/** The settings the chevron opens; none means the row does not open. */
|
|
123
151
|
children?: ReactNode
|
|
124
152
|
}) {
|
|
153
|
+
const i18n = usePascalI18n()
|
|
125
154
|
const opens = on && !!children
|
|
126
155
|
const open = opens && expanded
|
|
127
156
|
return (
|
|
@@ -151,7 +180,7 @@ export function ConstructionPart({
|
|
|
151
180
|
<span className="flex min-w-0 flex-1 flex-col">
|
|
152
181
|
<span className="font-medium text-[13px] text-foreground">{label}</span>
|
|
153
182
|
<span className="text-pretty text-muted-foreground text-xs">
|
|
154
|
-
{on ? meta : 'Not included'}
|
|
183
|
+
{on ? meta : i18n._({ id: 'editor.panels.roomConstructionRows.notIncluded', message: 'Not included' })}
|
|
155
184
|
</span>
|
|
156
185
|
</span>
|
|
157
186
|
{opens && (
|
|
@@ -198,6 +227,7 @@ export function FloorHeightField({
|
|
|
198
227
|
/** Allowed heights, in the same relative terms. */
|
|
199
228
|
bounds?: { min: number; max: number } | null
|
|
200
229
|
}) {
|
|
230
|
+
const i18n = usePascalI18n()
|
|
201
231
|
const height = useScene((scene) => roomRelativeFloorHeight(scene.nodes, zoneId))
|
|
202
232
|
const unit = useViewer((s) => s.unit)
|
|
203
233
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
@@ -208,7 +238,7 @@ export function FloorHeightField({
|
|
|
208
238
|
<div className="flex flex-col gap-1">
|
|
209
239
|
<MetricControl
|
|
210
240
|
className="h-8 text-xs"
|
|
211
|
-
label=
|
|
241
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.height', message: 'Height' })}
|
|
212
242
|
max={bounds?.max}
|
|
213
243
|
min={bounds?.min ?? -3}
|
|
214
244
|
onChange={setDraft}
|
|
@@ -238,6 +268,7 @@ export function FloorHeightField({
|
|
|
238
268
|
* how many other rooms stand on it) and selects it, as the tree would.
|
|
239
269
|
*/
|
|
240
270
|
export function DrawnFloorRow({ zoneId }: { zoneId: string }) {
|
|
271
|
+
const i18n = usePascalI18n()
|
|
241
272
|
const floor = useScene(
|
|
242
273
|
useShallow((scene) => {
|
|
243
274
|
const drawn = roomDrawnFloor(scene.nodes, zoneId)
|
|
@@ -255,9 +286,21 @@ export function DrawnFloorRow({ zoneId }: { zoneId: string }) {
|
|
|
255
286
|
return (
|
|
256
287
|
<div className="flex min-h-7 items-center justify-between gap-3" data-drawn-floor={floor.id}>
|
|
257
288
|
<span className="min-w-0">
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
289
|
+
{messageWithSlot(
|
|
290
|
+
i18n,
|
|
291
|
+
/* i18n */ {
|
|
292
|
+
id: 'editor.panels.roomConstructionRows.drawnFloor',
|
|
293
|
+
message:
|
|
294
|
+
'{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}',
|
|
295
|
+
values: {
|
|
296
|
+
ambiguous: floor.ambiguous ? 'yes' : 'no',
|
|
297
|
+
shared: floor.shared,
|
|
298
|
+
sharedCount: String(floor.shared),
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
'name',
|
|
302
|
+
<span className="text-foreground">{floor.name}</span>,
|
|
303
|
+
)}
|
|
261
304
|
</span>
|
|
262
305
|
<button
|
|
263
306
|
className={cn(pill, 'shrink-0')}
|
|
@@ -265,7 +308,7 @@ export function DrawnFloorRow({ zoneId }: { zoneId: string }) {
|
|
|
265
308
|
onClick={() => openFloorFoundation(floor.id)}
|
|
266
309
|
type="button"
|
|
267
310
|
>
|
|
268
|
-
Select
|
|
311
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.select', message: 'Select' })}
|
|
269
312
|
</button>
|
|
270
313
|
</div>
|
|
271
314
|
)
|
|
@@ -279,6 +322,7 @@ export function DrawnFloorRow({ zoneId }: { zoneId: string }) {
|
|
|
279
322
|
* right here.
|
|
280
323
|
*/
|
|
281
324
|
export function BuiltOnField({ zoneId }: { zoneId: string }) {
|
|
325
|
+
const i18n = usePascalI18n()
|
|
282
326
|
const nodes = useScene((scene) => scene.nodes)
|
|
283
327
|
const model = useMemo(() => roomBuiltOn(nodes, zoneId), [nodes, zoneId])
|
|
284
328
|
const [error, setError] = useState<string | null>(null)
|
|
@@ -289,16 +333,16 @@ export function BuiltOnField({ zoneId }: { zoneId: string }) {
|
|
|
289
333
|
}
|
|
290
334
|
return (
|
|
291
335
|
<div className="flex flex-col gap-1" data-built-on={model.current.key ?? 'shared'}>
|
|
292
|
-
<PartField label=
|
|
336
|
+
<PartField label={i18n._({ id: 'editor.panels.roomConstructionRows.builtOn', message: 'Built on' })}>
|
|
293
337
|
<DropdownMenu>
|
|
294
338
|
<DropdownMenuTrigger asChild>
|
|
295
339
|
<button
|
|
296
|
-
aria-label=
|
|
340
|
+
aria-label={i18n._({ id: 'editor.panels.roomConstructionRows.builtOn', message: 'Built on' })}
|
|
297
341
|
className="flex min-w-0 items-center gap-1.5 rounded-full border border-border/50 px-2.5 py-0.5 text-foreground text-xs transition-colors hover:bg-accent"
|
|
298
342
|
data-built-on-trigger
|
|
299
343
|
type="button"
|
|
300
344
|
>
|
|
301
|
-
<span className="truncate">{model.current
|
|
345
|
+
<span className="truncate">{floorChoiceName(nodes, model.current, i18n)}</span>
|
|
302
346
|
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
|
303
347
|
</button>
|
|
304
348
|
</DropdownMenuTrigger>
|
|
@@ -311,13 +355,13 @@ export function BuiltOnField({ zoneId }: { zoneId: string }) {
|
|
|
311
355
|
if (choice.plateId !== model.current.plateId) choose(choice.key)
|
|
312
356
|
}}
|
|
313
357
|
>
|
|
314
|
-
<span className="flex-1 truncate">{choice
|
|
358
|
+
<span className="flex-1 truncate">{floorChoiceName(nodes, choice, i18n)}</span>
|
|
315
359
|
{choice.plateId === model.current.plateId && <Check className="size-3.5" />}
|
|
316
360
|
</DropdownMenuItem>
|
|
317
361
|
))}
|
|
318
362
|
<DropdownMenuSeparator />
|
|
319
363
|
<DropdownMenuItem data-built-on-choice="new" onSelect={() => choose('new')}>
|
|
320
|
-
+ New floor
|
|
364
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.newFloor', message: '+ New floor' })}
|
|
321
365
|
</DropdownMenuItem>
|
|
322
366
|
</DropdownMenuContent>
|
|
323
367
|
</DropdownMenu>
|
|
@@ -356,6 +400,7 @@ export function roomInsideHeight(
|
|
|
356
400
|
* rather than naming the footprint next to it.
|
|
357
401
|
*/
|
|
358
402
|
function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) {
|
|
403
|
+
const i18n = usePascalI18n()
|
|
359
404
|
const unit = useViewer((s) => s.unit)
|
|
360
405
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
361
406
|
const summary = useScene(
|
|
@@ -365,30 +410,33 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) {
|
|
|
365
410
|
if (plate.plateRole !== 'base')
|
|
366
411
|
return {
|
|
367
412
|
id: plate.id as string,
|
|
368
|
-
name: plate.name?.trim() || 'Slab',
|
|
413
|
+
name: plate.name?.trim() || nodeLabel('slab', 'Slab', i18n),
|
|
369
414
|
drawn: true,
|
|
370
415
|
lift: 0,
|
|
371
416
|
supported: false,
|
|
372
417
|
}
|
|
373
418
|
return {
|
|
374
419
|
id: plate.id as string,
|
|
375
|
-
name:
|
|
420
|
+
name: localizedFloorFootprintName(scene.nodes, plate, i18n),
|
|
376
421
|
drawn: false,
|
|
377
422
|
...footprintStanding(scene.nodes, plate),
|
|
378
423
|
}
|
|
379
424
|
}),
|
|
380
425
|
)
|
|
426
|
+
const lift = formatLinearMeasurement(Math.abs(summary.lift), unit, metricNotation)
|
|
381
427
|
const preset = summary.drawn
|
|
382
|
-
? 'Drawn slab'
|
|
428
|
+
? i18n._({ id: 'editor.panels.roomConstructionRows.drawnSlab', message: 'Drawn slab' })
|
|
383
429
|
: Math.abs(summary.lift) > 0.005
|
|
384
|
-
?
|
|
430
|
+
? summary.lift > 0
|
|
431
|
+
? i18n._({ id: 'editor.panels.roomConstructionRows.raised', message: 'Raised {lift}', values: { lift } })
|
|
432
|
+
: i18n._({ id: 'editor.panels.roomConstructionRows.lowered', message: 'Lowered {lift}', values: { lift } })
|
|
385
433
|
: summary.supported
|
|
386
|
-
? 'On the walls below'
|
|
387
|
-
: 'On the ground'
|
|
434
|
+
? i18n._({ id: 'editor.panels.roomConstructionRows.onTheWallsBelow', message: 'On the walls below' })
|
|
435
|
+
: i18n._({ id: 'editor.panels.roomConstructionRows.onTheGround', message: 'On the ground' })
|
|
388
436
|
if (noFloor)
|
|
389
437
|
return (
|
|
390
438
|
<span className="mb-2 ml-[42px] text-muted-foreground text-xs" data-sits-on="none">
|
|
391
|
-
No floor
|
|
439
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.noFloor', message: 'No floor' })}
|
|
392
440
|
</span>
|
|
393
441
|
)
|
|
394
442
|
if (!summary.id) return null
|
|
@@ -400,13 +448,281 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) {
|
|
|
400
448
|
type="button"
|
|
401
449
|
>
|
|
402
450
|
<span>
|
|
403
|
-
|
|
451
|
+
{messageWithSlot(
|
|
452
|
+
i18n,
|
|
453
|
+
/* i18n */ {
|
|
454
|
+
id: 'editor.panels.roomConstructionRows.sitsOnPreset',
|
|
455
|
+
message: 'Sits on: {name} · {preset}',
|
|
456
|
+
values: { preset },
|
|
457
|
+
},
|
|
458
|
+
'name',
|
|
459
|
+
<span className="text-foreground">{summary.name}</span>,
|
|
460
|
+
)}
|
|
404
461
|
</span>
|
|
405
462
|
<ChevronRight className="size-3 shrink-0" />
|
|
406
463
|
</button>
|
|
407
464
|
)
|
|
408
465
|
}
|
|
409
466
|
|
|
467
|
+
const ROOM_TYPE_OPTIONS: ReadonlyArray<{
|
|
468
|
+
label: MessageDescriptor
|
|
469
|
+
value: 'room' | OutdoorRoomType
|
|
470
|
+
}> = [
|
|
471
|
+
{
|
|
472
|
+
label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypeRoom', message: 'Room' },
|
|
473
|
+
value: 'room',
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypePorch', message: 'Porch' },
|
|
477
|
+
value: 'porch',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypeDeck', message: 'Deck' },
|
|
481
|
+
value: 'deck',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypePatio', message: 'Patio' },
|
|
485
|
+
value: 'patio',
|
|
486
|
+
},
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
const ROOM_TYPE_HINTS: Record<'room' | OutdoorRoomType, MessageDescriptor> = {
|
|
490
|
+
room: /* i18n */ {
|
|
491
|
+
id: 'editor.panels.roomConstructionRows.roomTypeRoomHint',
|
|
492
|
+
message: 'An ordinary room inside the house.',
|
|
493
|
+
},
|
|
494
|
+
porch: /* i18n */ {
|
|
495
|
+
id: 'editor.panels.roomConstructionRows.roomTypePorchHint',
|
|
496
|
+
message:
|
|
497
|
+
'Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s.',
|
|
498
|
+
},
|
|
499
|
+
deck: /* i18n */ {
|
|
500
|
+
id: 'editor.panels.roomConstructionRows.roomTypeDeckHint',
|
|
501
|
+
message:
|
|
502
|
+
'Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling.',
|
|
503
|
+
},
|
|
504
|
+
patio: /* i18n */ {
|
|
505
|
+
id: 'editor.panels.roomConstructionRows.roomTypePatioHint',
|
|
506
|
+
message: 'Outdoors and open: a concrete slab 1½ in down from the house, no ceiling.',
|
|
507
|
+
},
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* "Room | Porch | Deck | Patio": the room type (`roomType`, through core's
|
|
512
|
+
* `setZoneIntent`). A porch, deck or patio is outdoors whatever covers it, steps its floor down
|
|
513
|
+
* from the house's and takes its floor and ceiling defaults in the same step; each stays
|
|
514
|
+
* adjustable below.
|
|
515
|
+
*/
|
|
516
|
+
export function RoomTypeChoice({ zoneId }: { zoneId: string }) {
|
|
517
|
+
const i18n = usePascalI18n()
|
|
518
|
+
const value = useScene((scene) => {
|
|
519
|
+
const zone = scene.nodes[zoneId as AnyNodeId]
|
|
520
|
+
return zone?.type === 'zone' ? (zone.roomType ?? 'room') : null
|
|
521
|
+
})
|
|
522
|
+
if (!value) return null
|
|
523
|
+
return (
|
|
524
|
+
<div className="mt-3 flex flex-col gap-1" data-room-type={value}>
|
|
525
|
+
<h3 className="font-medium text-muted-foreground text-xs">
|
|
526
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.roomType', message: 'Room type' })}
|
|
527
|
+
</h3>
|
|
528
|
+
<SegmentedControl
|
|
529
|
+
onChange={(next: 'room' | OutdoorRoomType) =>
|
|
530
|
+
applyRoomPlan(
|
|
531
|
+
setZoneIntent(useScene.getState().nodes, {
|
|
532
|
+
zoneId,
|
|
533
|
+
patch: { roomType: next === 'room' ? null : next },
|
|
534
|
+
}),
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
options={ROOM_TYPE_OPTIONS.map((option) => ({
|
|
538
|
+
label: i18n._(option.label),
|
|
539
|
+
value: option.value,
|
|
540
|
+
}))}
|
|
541
|
+
value={value}
|
|
542
|
+
/>
|
|
543
|
+
<p className="text-muted-foreground text-xs">{i18n._(ROOM_TYPE_HINTS[value])}</p>
|
|
544
|
+
</div>
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const POST_SIZE_OPTIONS: ReadonlyArray<{
|
|
549
|
+
label: string
|
|
550
|
+
value: PorchFramingSettings['postSize']
|
|
551
|
+
}> = [
|
|
552
|
+
{ label: '4x4', value: '4x4' },
|
|
553
|
+
{ label: '6x6', value: '6x6' },
|
|
554
|
+
{ label: '8x8', value: '8x8' },
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
type PorchFramingMode = 'off' | 'auto' | 'manual'
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* A covered porch's posts and beam (`porchFraming`, through core's `setZoneIntent`), and a deck's
|
|
561
|
+
* or patio's once switched on, to carry a roof over it (Off by default): while Automatic the
|
|
562
|
+
* structure kernel keeps a post on each outside corner, more at most the spacing apart, and a beam
|
|
563
|
+
* along each open side under the ceiling. A size or the spacing rebuilds them; a post or beam
|
|
564
|
+
* edited by hand turns the room to Manual, and Automatic rebuilds them again.
|
|
565
|
+
*/
|
|
566
|
+
export function PorchFramingFields({ zoneId }: { zoneId: string }) {
|
|
567
|
+
const i18n = usePascalI18n()
|
|
568
|
+
const porch = useScene(
|
|
569
|
+
useShallow((scene) => {
|
|
570
|
+
const zone = scene.nodes[zoneId as AnyNodeId]
|
|
571
|
+
if (zone?.type !== 'zone') return null
|
|
572
|
+
const optional = zone.roomType === 'deck' || zone.roomType === 'patio'
|
|
573
|
+
if (!(optional || (zone.roomType === 'porch' && zone.hasCeiling !== false))) return null
|
|
574
|
+
const { posts, beams } = roomPorchFramingCounts(scene.nodes, zone)
|
|
575
|
+
const auto = zone.porchFraming?.auto
|
|
576
|
+
const mode: PorchFramingMode =
|
|
577
|
+
auto === false ? 'manual' : optional && auto !== true ? 'off' : 'auto'
|
|
578
|
+
return {
|
|
579
|
+
mode,
|
|
580
|
+
optional,
|
|
581
|
+
room: zone.roomType ?? 'porch',
|
|
582
|
+
...porchFramingSettings(zone),
|
|
583
|
+
posts,
|
|
584
|
+
beams,
|
|
585
|
+
}
|
|
586
|
+
}),
|
|
587
|
+
)
|
|
588
|
+
const [spacing, setSpacing] = useState<number | null>(null)
|
|
589
|
+
if (!porch) return null
|
|
590
|
+
const set = (porchFraming: NonNullable<Parameters<typeof setZoneIntent>[1]['patch']['porchFraming']>) =>
|
|
591
|
+
applyRoomPlan(setZoneIntent(useScene.getState().nodes, { zoneId, patch: { porchFraming } }))
|
|
592
|
+
const beam = BEAM_SIZE_PRESETS.find((preset) => preset.id === porch.beamSize)
|
|
593
|
+
return (
|
|
594
|
+
<div className="mt-3 flex flex-col gap-1.5" data-porch-framing={porch.mode}>
|
|
595
|
+
<h3 className="font-medium text-muted-foreground text-xs">
|
|
596
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.postsAndBeam', message: 'Posts and beam' })}
|
|
597
|
+
</h3>
|
|
598
|
+
<SegmentedControl
|
|
599
|
+
onChange={(next: PorchFramingMode) =>
|
|
600
|
+
set({ auto: next === 'off' ? null : next === 'auto' })
|
|
601
|
+
}
|
|
602
|
+
options={[
|
|
603
|
+
...(porch.optional
|
|
604
|
+
? [
|
|
605
|
+
{
|
|
606
|
+
label: i18n._({
|
|
607
|
+
id: 'editor.panels.roomConstructionRows.framingOff',
|
|
608
|
+
message: 'Off',
|
|
609
|
+
}),
|
|
610
|
+
value: 'off' as const,
|
|
611
|
+
},
|
|
612
|
+
]
|
|
613
|
+
: []),
|
|
614
|
+
{
|
|
615
|
+
label: i18n._({
|
|
616
|
+
id: 'editor.panels.roomConstructionRows.framingAutomatic',
|
|
617
|
+
message: 'Automatic',
|
|
618
|
+
}),
|
|
619
|
+
value: 'auto' as const,
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
label: i18n._({
|
|
623
|
+
id: 'editor.panels.roomConstructionRows.framingManual',
|
|
624
|
+
message: 'Manual',
|
|
625
|
+
}),
|
|
626
|
+
value: 'manual' as const,
|
|
627
|
+
},
|
|
628
|
+
]}
|
|
629
|
+
value={porch.mode}
|
|
630
|
+
/>
|
|
631
|
+
<p className="text-muted-foreground text-xs">
|
|
632
|
+
{porch.mode === 'off'
|
|
633
|
+
? i18n._({
|
|
634
|
+
id: 'editor.panels.roomConstructionRows.framingOffHint',
|
|
635
|
+
message:
|
|
636
|
+
'None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}.',
|
|
637
|
+
values: { room: porch.room },
|
|
638
|
+
})
|
|
639
|
+
: porch.mode === 'auto'
|
|
640
|
+
? i18n._({
|
|
641
|
+
id: 'editor.panels.roomConstructionRows.framingAutoHint',
|
|
642
|
+
message:
|
|
643
|
+
'{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}.',
|
|
644
|
+
values: {
|
|
645
|
+
posts: porch.posts,
|
|
646
|
+
postCount: String(porch.posts),
|
|
647
|
+
beams: porch.beams,
|
|
648
|
+
beamCount: String(porch.beams),
|
|
649
|
+
room: porch.room,
|
|
650
|
+
},
|
|
651
|
+
})
|
|
652
|
+
: i18n._({
|
|
653
|
+
id: 'editor.panels.roomConstructionRows.framingManualHint',
|
|
654
|
+
message: 'Kept as you edited them. Automatic rebuilds them from these settings.',
|
|
655
|
+
})}
|
|
656
|
+
</p>
|
|
657
|
+
{porch.mode !== 'off' && (
|
|
658
|
+
<>
|
|
659
|
+
<PartField
|
|
660
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.postSize', message: 'Post size' })}
|
|
661
|
+
>
|
|
662
|
+
<SegmentedControl
|
|
663
|
+
onChange={(postSize: PorchFramingSettings['postSize']) => set({ postSize })}
|
|
664
|
+
options={[...POST_SIZE_OPTIONS]}
|
|
665
|
+
value={porch.postSize}
|
|
666
|
+
/>
|
|
667
|
+
</PartField>
|
|
668
|
+
<MetricControl
|
|
669
|
+
className="h-8 text-xs"
|
|
670
|
+
label={i18n._({
|
|
671
|
+
id: 'editor.panels.roomConstructionRows.postSpacingMax',
|
|
672
|
+
message: 'Post spacing (max)',
|
|
673
|
+
})}
|
|
674
|
+
max={6}
|
|
675
|
+
min={0.6}
|
|
676
|
+
onChange={setSpacing}
|
|
677
|
+
onCommit={(value) => {
|
|
678
|
+
setSpacing(null)
|
|
679
|
+
if (Math.abs(value - porch.postSpacing) > 1e-6) set({ postSpacing: value })
|
|
680
|
+
}}
|
|
681
|
+
precision={2}
|
|
682
|
+
step={0.05}
|
|
683
|
+
unit="m"
|
|
684
|
+
value={spacing ?? porch.postSpacing}
|
|
685
|
+
/>
|
|
686
|
+
<PartField
|
|
687
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.beam', message: 'Beam' })}
|
|
688
|
+
>
|
|
689
|
+
<DropdownMenu>
|
|
690
|
+
<DropdownMenuTrigger asChild>
|
|
691
|
+
<button
|
|
692
|
+
aria-label={i18n._({
|
|
693
|
+
id: 'editor.panels.roomConstructionRows.beamSize',
|
|
694
|
+
message: 'Beam size',
|
|
695
|
+
})}
|
|
696
|
+
className="flex min-w-0 items-center gap-1.5 rounded-full border border-border/50 px-2.5 py-0.5 text-foreground text-xs transition-colors hover:bg-accent"
|
|
697
|
+
data-porch-beam-size
|
|
698
|
+
type="button"
|
|
699
|
+
>
|
|
700
|
+
<span className="truncate">{beam?.label ?? porch.beamSize}</span>
|
|
701
|
+
<ChevronDown className="size-3 shrink-0 text-muted-foreground" />
|
|
702
|
+
</button>
|
|
703
|
+
</DropdownMenuTrigger>
|
|
704
|
+
<DropdownMenuContent align="end" className="max-h-72 min-w-40 overflow-y-auto">
|
|
705
|
+
{BEAM_SIZE_PRESETS.map((preset) => (
|
|
706
|
+
<DropdownMenuItem
|
|
707
|
+
data-porch-beam-choice={preset.id}
|
|
708
|
+
key={preset.id}
|
|
709
|
+
onSelect={() => {
|
|
710
|
+
if (preset.id !== porch.beamSize) set({ beamSize: preset.id })
|
|
711
|
+
}}
|
|
712
|
+
>
|
|
713
|
+
<span className="flex-1 truncate">{preset.label}</span>
|
|
714
|
+
{preset.id === porch.beamSize && <Check className="size-3.5" />}
|
|
715
|
+
</DropdownMenuItem>
|
|
716
|
+
))}
|
|
717
|
+
</DropdownMenuContent>
|
|
718
|
+
</DropdownMenu>
|
|
719
|
+
</PartField>
|
|
720
|
+
</>
|
|
721
|
+
)}
|
|
722
|
+
</div>
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
|
|
410
726
|
/**
|
|
411
727
|
* The room's floor construction, edited from the room: its thickness, through
|
|
412
728
|
* core's `setRoomFloorConstruction` (the command MCP uses too), on whatever the
|
|
@@ -414,6 +730,7 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) {
|
|
|
414
730
|
* no footprint height or foundation, and says so.
|
|
415
731
|
*/
|
|
416
732
|
export function RoomFloorConstructionField({ zoneId }: { zoneId: string }) {
|
|
733
|
+
const i18n = usePascalI18n()
|
|
417
734
|
const owner = useScene(
|
|
418
735
|
useShallow((scene) => {
|
|
419
736
|
// Several drawn slabs show in the room: no one thickness to edit here.
|
|
@@ -440,7 +757,7 @@ export function RoomFloorConstructionField({ zoneId }: { zoneId: string }) {
|
|
|
440
757
|
<div className="flex flex-col gap-1" data-room-floor-construction={owner.id}>
|
|
441
758
|
<MetricControl
|
|
442
759
|
className="h-8 text-xs"
|
|
443
|
-
label=
|
|
760
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.floorThickness', message: 'Floor thickness' })}
|
|
444
761
|
max={1}
|
|
445
762
|
min={owner.min}
|
|
446
763
|
onChange={setDraft}
|
|
@@ -456,8 +773,7 @@ export function RoomFloorConstructionField({ zoneId }: { zoneId: string }) {
|
|
|
456
773
|
/>
|
|
457
774
|
{owner.drawn && (
|
|
458
775
|
<p data-drawn-slab-note>
|
|
459
|
-
A drawn slab: set its thickness and finishes here. It has no footprint height or
|
|
460
|
-
foundation.
|
|
776
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness', message: 'A drawn slab: set its thickness and finishes here. It has no footprint height or foundation.' })}
|
|
461
777
|
</p>
|
|
462
778
|
)}
|
|
463
779
|
{error && (
|
|
@@ -495,7 +811,7 @@ function sharingRoomNames(
|
|
|
495
811
|
node.id !== zoneId &&
|
|
496
812
|
node.spaceRole === 'room' &&
|
|
497
813
|
node.boundaryWallIds.some((id) => wallIds.includes(id))
|
|
498
|
-
? [node.name?.trim()
|
|
814
|
+
? [node.name?.trim() ?? '']
|
|
499
815
|
: [],
|
|
500
816
|
)
|
|
501
817
|
}
|
|
@@ -505,6 +821,8 @@ function sharingRoomNames(
|
|
|
505
821
|
* with its switch, and its settings a chevron away. Then Add mezzanine.
|
|
506
822
|
*/
|
|
507
823
|
export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
824
|
+
const i18n = usePascalI18n()
|
|
825
|
+
const locale = useLocale()
|
|
508
826
|
const zoneId = room.zoneId
|
|
509
827
|
const state = useScene((scene) => roomConstructionState(scene.nodes, zoneId))
|
|
510
828
|
const nodes = useScene((scene) => scene.nodes)
|
|
@@ -514,7 +832,10 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
514
832
|
const [message, setMessage] = useState<{ text: string; conflict: boolean } | null>(null)
|
|
515
833
|
if (!state) return null
|
|
516
834
|
const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation)
|
|
517
|
-
const report = (
|
|
835
|
+
const report = (
|
|
836
|
+
result: RoomConstructionResult,
|
|
837
|
+
unchanged = i18n._({ id: 'editor.panels.roomConstructionRows.nothingToChange', message: 'Nothing to change.' }),
|
|
838
|
+
) =>
|
|
518
839
|
setMessage(
|
|
519
840
|
result.status === 'conflict'
|
|
520
841
|
? { text: result.message, conflict: true }
|
|
@@ -525,14 +846,14 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
525
846
|
const toggle = (part: RoomConstructionPart) => () =>
|
|
526
847
|
setExpanded((current) => (current === part ? null : part))
|
|
527
848
|
const drawnFloor = roomDrawnFloor(nodes, zoneId)
|
|
528
|
-
const partSwitch = (part: RoomConstructionPart
|
|
849
|
+
const partSwitch = (part: RoomConstructionPart) => {
|
|
529
850
|
const { state: presence, actions } = state[part]
|
|
530
851
|
if (part === 'walls') {
|
|
531
852
|
const walls = wallsSwitch(state)
|
|
532
853
|
return (
|
|
533
854
|
<Switch
|
|
534
855
|
aria-checked={walls.mixed ? 'mixed' : walls.checked}
|
|
535
|
-
aria-label=
|
|
856
|
+
aria-label={i18n._({ id: 'editor.panels.roomConstructionRows.includeWalls', message: 'Include walls' })}
|
|
536
857
|
checked={walls.checked}
|
|
537
858
|
className={cn(
|
|
538
859
|
walls.mixed && 'data-[state=unchecked]:bg-primary/45 [&>span]:translate-x-2!',
|
|
@@ -561,7 +882,7 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
561
882
|
data-drawn-floor-switch
|
|
562
883
|
>
|
|
563
884
|
<Switch
|
|
564
|
-
aria-label=
|
|
885
|
+
aria-label={i18n._({ id: 'editor.panels.roomConstructionRows.includeFloor', message: 'Include floor' })}
|
|
565
886
|
checked={state.floor.intent}
|
|
566
887
|
className="disabled:pointer-events-none"
|
|
567
888
|
disabled={!actions.add}
|
|
@@ -571,7 +892,10 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
571
892
|
</TooltipTrigger>
|
|
572
893
|
{!actions.add && (
|
|
573
894
|
<TooltipContent side="top" sideOffset={6}>
|
|
574
|
-
{
|
|
895
|
+
{i18n._({
|
|
896
|
+
id: 'editor.panels.roomConstructionRows.drawnFloorSwitchHint',
|
|
897
|
+
message: 'This floor is a drawn slab: select it to edit or delete it.',
|
|
898
|
+
})}
|
|
575
899
|
</TooltipContent>
|
|
576
900
|
)}
|
|
577
901
|
</Tooltip>
|
|
@@ -580,7 +904,11 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
580
904
|
const on = presence !== 'absent'
|
|
581
905
|
return (
|
|
582
906
|
<Switch
|
|
583
|
-
aria-label={
|
|
907
|
+
aria-label={i18n._({
|
|
908
|
+
id: 'editor.panels.roomConstructionRows.includePart',
|
|
909
|
+
message: '{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}',
|
|
910
|
+
values: { part },
|
|
911
|
+
})}
|
|
584
912
|
checked={on}
|
|
585
913
|
disabled={on ? !actions.remove : !actions.add}
|
|
586
914
|
onCheckedChange={(next) =>
|
|
@@ -595,17 +923,40 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
595
923
|
const builtOn = roomBuiltOn(nodes, zoneId)
|
|
596
924
|
const floorMeta =
|
|
597
925
|
state.floor.state === 'partial'
|
|
598
|
-
?
|
|
926
|
+
? i18n._({
|
|
927
|
+
id: 'editor.panels.roomConstructionRows.partialCovered',
|
|
928
|
+
message: 'Partial · {coverage, number, percent} covered',
|
|
929
|
+
values: { coverage: state.floor.coverage },
|
|
930
|
+
})
|
|
599
931
|
: drawnSlab?.type === 'slab'
|
|
600
|
-
?
|
|
932
|
+
? i18n._({
|
|
933
|
+
id: 'editor.panels.roomConstructionRows.drawnSlabAt',
|
|
934
|
+
message: 'Drawn slab at {height}',
|
|
935
|
+
values: { height: length(drawnSlab.elevation) },
|
|
936
|
+
})
|
|
601
937
|
: builtOn && builtOn.current.key !== null
|
|
602
938
|
? separateFloorSummary(
|
|
603
|
-
{
|
|
939
|
+
{
|
|
940
|
+
current: { name: floorChoiceName(nodes, builtOn.current, i18n) },
|
|
941
|
+
shared: builtOn.shared && { name: floorChoiceName(nodes, builtOn.shared, i18n) },
|
|
942
|
+
offset: separateFloorOffset(nodes, zoneId),
|
|
943
|
+
},
|
|
604
944
|
length,
|
|
945
|
+
i18n,
|
|
605
946
|
)
|
|
606
947
|
: Math.abs(floorElevation) < 0.005
|
|
607
|
-
? 'At floor level'
|
|
608
|
-
:
|
|
948
|
+
? i18n._({ id: 'editor.panels.roomConstructionRows.atFloorLevel', message: 'At floor level' })
|
|
949
|
+
: floorElevation > 0
|
|
950
|
+
? i18n._({
|
|
951
|
+
id: 'editor.panels.roomConstructionRows.floorUp',
|
|
952
|
+
message: '{distance} up',
|
|
953
|
+
values: { distance: length(Math.abs(floorElevation)) },
|
|
954
|
+
})
|
|
955
|
+
: i18n._({
|
|
956
|
+
id: 'editor.panels.roomConstructionRows.floorDown',
|
|
957
|
+
message: '{distance} down',
|
|
958
|
+
values: { distance: length(Math.abs(floorElevation)) },
|
|
959
|
+
})
|
|
609
960
|
// A room on its own floor moves within its plate's range (on the ground, its foundation's).
|
|
610
961
|
const ownBounds = ownFloorElevationBounds(nodes, zoneId)
|
|
611
962
|
const floorBase = ownBounds ? roomFloorBase(nodes, zoneId) : 0
|
|
@@ -619,27 +970,48 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
619
970
|
const shared = sharingRoomNames(nodes, zoneId, state.walls.sharedWallIds)
|
|
620
971
|
const wallsMeta =
|
|
621
972
|
state.walls.state === 'partial'
|
|
622
|
-
?
|
|
623
|
-
|
|
973
|
+
? i18n._({
|
|
974
|
+
id: 'editor.panels.roomConstructionRows.partialWalls',
|
|
975
|
+
message:
|
|
976
|
+
'Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}',
|
|
977
|
+
values: {
|
|
978
|
+
walls: wallIds.length,
|
|
979
|
+
openSides,
|
|
980
|
+
wallCount: String(wallIds.length),
|
|
981
|
+
openSideCount: String(openSides),
|
|
982
|
+
},
|
|
983
|
+
})
|
|
984
|
+
: i18n._({
|
|
985
|
+
id: 'editor.panels.roomConstructionRows.wallCount',
|
|
986
|
+
message: '{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}',
|
|
987
|
+
values: { walls: wallIds.length, wallCount: String(wallIds.length) },
|
|
988
|
+
})
|
|
624
989
|
const locked = roomOutsideFacesLocked(nodes, wallIds)
|
|
625
990
|
|
|
626
991
|
const ceilingHeight = state.ceiling.ceilingId ? roomInsideHeight(room, nodes).ceiling : null
|
|
627
992
|
const ceilingMeta =
|
|
628
993
|
state.ceiling.state === 'partial'
|
|
629
|
-
? 'Hand-drawn ceiling'
|
|
994
|
+
? i18n._({ id: 'editor.panels.roomConstructionRows.handDrawnCeiling', message: 'Hand-drawn ceiling' })
|
|
630
995
|
: ceilingHeight !== null
|
|
631
|
-
?
|
|
632
|
-
|
|
996
|
+
? i18n._({
|
|
997
|
+
id: 'editor.panels.roomConstructionRows.ceilingAt',
|
|
998
|
+
message: 'At {height}',
|
|
999
|
+
values: { height: length(ceilingHeight) },
|
|
1000
|
+
})
|
|
1001
|
+
: i18n._({ id: 'editor.panels.roomConstructionRows.flatAtWallHeight', message: 'Flat, at wall height' })
|
|
633
1002
|
|
|
634
1003
|
return (
|
|
635
1004
|
<div className="flex flex-col" data-room-construction={zoneId}>
|
|
636
|
-
<
|
|
1005
|
+
<RoomTypeChoice zoneId={zoneId} />
|
|
1006
|
+
<DeckBoardsFields zoneId={zoneId} />
|
|
1007
|
+
<PorchFramingFields zoneId={zoneId} />
|
|
1008
|
+
<h3 className="mt-4 mb-1.5 font-medium text-muted-foreground text-xs">{i18n._({ id: 'editor.panels.roomConstructionRows.builtWith', message: 'Built with' })}</h3>
|
|
637
1009
|
<div className="flex flex-col gap-1">
|
|
638
1010
|
<ConstructionPart
|
|
639
|
-
control={partSwitch('floor'
|
|
1011
|
+
control={partSwitch('floor')}
|
|
640
1012
|
expanded={expanded === 'floor'}
|
|
641
1013
|
icon="/icons/floor.webp"
|
|
642
|
-
label=
|
|
1014
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.floor', message: 'Floor' })}
|
|
643
1015
|
meta={floorMeta}
|
|
644
1016
|
on={state.floor.state !== 'absent'}
|
|
645
1017
|
footer={<SitsOn noFloor={state.floor.state === 'absent'} zoneId={zoneId} />}
|
|
@@ -656,44 +1028,71 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
656
1028
|
<RoomOpeningsField surface="floor" zoneId={zoneId} />
|
|
657
1029
|
</ConstructionPart>
|
|
658
1030
|
<ConstructionPart
|
|
659
|
-
control={partSwitch('walls'
|
|
1031
|
+
control={partSwitch('walls')}
|
|
660
1032
|
expanded={expanded === 'walls'}
|
|
661
1033
|
icon="/icons/wall.webp"
|
|
662
|
-
label=
|
|
1034
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.walls', message: 'Walls' })}
|
|
663
1035
|
meta={wallsMeta}
|
|
664
1036
|
on={state.walls.state !== 'absent'}
|
|
665
1037
|
onToggleExpanded={toggle('walls')}
|
|
666
1038
|
part="walls"
|
|
667
1039
|
>
|
|
668
|
-
<PartField label=
|
|
1040
|
+
<PartField label={i18n._({ id: 'editor.panels.roomConstructionRows.walls', message: 'Walls' })}>
|
|
669
1041
|
<span className="font-mono text-foreground tabular-nums">{wallIds.length}</span>
|
|
670
1042
|
</PartField>
|
|
671
1043
|
{state.walls.state === 'partial' && state.walls.actions.add && (
|
|
672
|
-
<PartField
|
|
1044
|
+
<PartField
|
|
1045
|
+
label={i18n._({
|
|
1046
|
+
id: 'editor.panels.roomConstructionRows.openSides',
|
|
1047
|
+
message:
|
|
1048
|
+
'{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}',
|
|
1049
|
+
values: { openSides, openSideCount: String(openSides) },
|
|
1050
|
+
})}
|
|
1051
|
+
>
|
|
673
1052
|
<button
|
|
674
1053
|
className={pill}
|
|
675
1054
|
onClick={() => report(addRoomConstruction(zoneId, 'walls'))}
|
|
676
1055
|
type="button"
|
|
677
1056
|
>
|
|
678
|
-
Close with walls
|
|
1057
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.closeWithWalls', message: 'Close with walls' })}
|
|
679
1058
|
</button>
|
|
680
1059
|
</PartField>
|
|
681
1060
|
)}
|
|
682
1061
|
{shared.length > 0 && (
|
|
683
1062
|
<p data-shared-walls-note>
|
|
684
|
-
|
|
1063
|
+
{i18n._({
|
|
1064
|
+
id: 'editor.panels.roomConstructionRows.sharedWallsNote',
|
|
1065
|
+
message: 'Turning walls off keeps the ones shared with {rooms}.',
|
|
1066
|
+
values: {
|
|
1067
|
+
rooms: formatList(
|
|
1068
|
+
[
|
|
1069
|
+
...new Set(
|
|
1070
|
+
shared.map((name) =>
|
|
1071
|
+
name
|
|
1072
|
+
? displayRoomName(name, i18n)
|
|
1073
|
+
: i18n._({
|
|
1074
|
+
id: 'editor.panels.roomConstructionRows.theNextRoom',
|
|
1075
|
+
message: 'the next room',
|
|
1076
|
+
}),
|
|
1077
|
+
),
|
|
1078
|
+
),
|
|
1079
|
+
],
|
|
1080
|
+
locale,
|
|
1081
|
+
),
|
|
1082
|
+
},
|
|
1083
|
+
})}
|
|
685
1084
|
</p>
|
|
686
1085
|
)}
|
|
687
1086
|
{locked !== null && (
|
|
688
1087
|
<div className="mt-1 flex items-start gap-3" data-lock-outside-faces>
|
|
689
1088
|
<span className="flex flex-1 flex-col gap-0.5">
|
|
690
1089
|
<span className="font-medium text-[13px] text-foreground">
|
|
691
|
-
Keep outside dimensions
|
|
1090
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.keepOutsideDimensions', message: 'Keep outside dimensions' })}
|
|
692
1091
|
</span>
|
|
693
|
-
<span>Walls thicken inward, so the building's outer size stays the same
|
|
1092
|
+
<span>{i18n._({ id: 'editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding', message: 'Walls thicken inward, so the building\'s outer size stays the same.' })}</span>
|
|
694
1093
|
</span>
|
|
695
1094
|
<Switch
|
|
696
|
-
aria-label=
|
|
1095
|
+
aria-label={i18n._({ id: 'editor.panels.roomConstructionRows.keepOutsideDimensions', message: 'Keep outside dimensions' })}
|
|
697
1096
|
checked={locked}
|
|
698
1097
|
onCheckedChange={(next) =>
|
|
699
1098
|
report(next ? lockRoomOutsideFaces(zoneId) : unlockRoomOutsideFaces(wallIds))
|
|
@@ -703,40 +1102,40 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
703
1102
|
)}
|
|
704
1103
|
</ConstructionPart>
|
|
705
1104
|
<ConstructionPart
|
|
706
|
-
control={partSwitch('ceiling'
|
|
1105
|
+
control={partSwitch('ceiling')}
|
|
707
1106
|
expanded={expanded === 'ceiling'}
|
|
708
1107
|
icon="/icons/ceiling.webp"
|
|
709
|
-
label=
|
|
1108
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.ceiling', message: 'Ceiling' })}
|
|
710
1109
|
meta={ceilingMeta}
|
|
711
1110
|
on={state.ceiling.state !== 'absent'}
|
|
712
1111
|
onToggleExpanded={toggle('ceiling')}
|
|
713
1112
|
part="ceiling"
|
|
714
1113
|
>
|
|
715
1114
|
{ceilingHeight !== null && (
|
|
716
|
-
<PartField label=
|
|
1115
|
+
<PartField label={i18n._({ id: 'editor.panels.roomConstructionRows.height', message: 'Height' })}>
|
|
717
1116
|
<span className="font-mono text-foreground tabular-nums">
|
|
718
1117
|
{length(ceilingHeight)}
|
|
719
1118
|
</span>
|
|
720
1119
|
</PartField>
|
|
721
1120
|
)}
|
|
722
1121
|
{state.ceiling.ceilingId && (
|
|
723
|
-
<PartField label=
|
|
1122
|
+
<PartField label={i18n._({ id: 'editor.panels.roomConstructionRows.shape', message: 'Shape' })}>
|
|
724
1123
|
<button className={pill} onClick={() => startCeilingEdit(zoneId)} type="button">
|
|
725
|
-
Edit shape
|
|
1124
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.editShape', message: 'Edit shape' })}
|
|
726
1125
|
</button>
|
|
727
1126
|
</PartField>
|
|
728
1127
|
)}
|
|
729
1128
|
{state.ceiling.ceilingId && <RoomOpeningsField surface="ceiling" zoneId={zoneId} />}
|
|
730
1129
|
{(state.ceiling.actions.useExisting || state.ceiling.actions.replace) && (
|
|
731
1130
|
<div className="flex items-center gap-2" data-construction-manual-ceiling>
|
|
732
|
-
<span className="flex-1">Hand-drawn ceiling</span>
|
|
1131
|
+
<span className="flex-1">{i18n._({ id: 'editor.panels.roomConstructionRows.handDrawnCeiling', message: 'Hand-drawn ceiling' })}</span>
|
|
733
1132
|
{state.ceiling.actions.useExisting && (
|
|
734
1133
|
<button
|
|
735
1134
|
className={pill}
|
|
736
1135
|
onClick={() => report(adoptExistingCeiling(zoneId))}
|
|
737
1136
|
type="button"
|
|
738
1137
|
>
|
|
739
|
-
Use existing
|
|
1138
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.useExisting', message: 'Use existing' })}
|
|
740
1139
|
</button>
|
|
741
1140
|
)}
|
|
742
1141
|
{state.ceiling.actions.replace && (
|
|
@@ -745,7 +1144,7 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
745
1144
|
onClick={() => report(replaceRoomCeiling(zoneId))}
|
|
746
1145
|
type="button"
|
|
747
1146
|
>
|
|
748
|
-
Replace
|
|
1147
|
+
{i18n._({ id: 'editor.panels.roomConstructionRows.replace', message: 'Replace' })}
|
|
749
1148
|
</button>
|
|
750
1149
|
)}
|
|
751
1150
|
</div>
|
|
@@ -766,7 +1165,7 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) {
|
|
|
766
1165
|
<ShapeChoice
|
|
767
1166
|
className="mt-3 rounded-full border border-border border-dashed py-1.5 pr-1.5 pl-3.5 font-medium text-[13px] text-foreground"
|
|
768
1167
|
data="add-mezzanine"
|
|
769
|
-
label=
|
|
1168
|
+
label={i18n._({ id: 'editor.panels.roomConstructionRows.addMezzanine', message: '+ Add mezzanine' })}
|
|
770
1169
|
onPick={(shape) => startMezzanineDraft(zoneId, shape)}
|
|
771
1170
|
/>
|
|
772
1171
|
</div>
|