@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
|
@@ -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 {
|
|
@@ -18,6 +18,9 @@ import { formatAreaLabel } from '../../../lib/measurements'
|
|
|
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"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import type { AnyNode } from '@pascal-app/core'
|
|
4
5
|
import { Copy, Move, SlidersHorizontal, Trash2 } from 'lucide-react'
|
|
5
6
|
import type { MouseEventHandler } from 'react'
|
|
@@ -29,7 +30,8 @@ export function MobileSelectionBar({
|
|
|
29
30
|
onDelete,
|
|
30
31
|
onEdit,
|
|
31
32
|
}: MobileSelectionBarProps) {
|
|
32
|
-
const
|
|
33
|
+
const i18n = usePascalI18n()
|
|
34
|
+
const display = getNodeDisplay(node, i18n)
|
|
33
35
|
const resolvedLabel = label ?? display.label
|
|
34
36
|
const resolvedIcon = icon ?? display.icon
|
|
35
37
|
|
|
@@ -38,7 +40,7 @@ export function MobileSelectionBar({
|
|
|
38
40
|
return (
|
|
39
41
|
<div className="pointer-events-auto absolute right-3 bottom-6 left-3 z-50 flex h-12 items-stretch gap-1 rounded-2xl border border-border/50 bg-background/95 px-2 shadow-2xl backdrop-blur-xl">
|
|
40
42
|
<button
|
|
41
|
-
aria-label={
|
|
43
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.editResolvedlabel', message: 'Edit {resolvedLabel}', values: { resolvedLabel } })}
|
|
42
44
|
className={cn(
|
|
43
45
|
'flex min-w-0 flex-1 items-center gap-2 rounded-lg px-2 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/8',
|
|
44
46
|
)}
|
|
@@ -57,7 +59,7 @@ export function MobileSelectionBar({
|
|
|
57
59
|
|
|
58
60
|
<div className="flex items-center gap-0.5 border-border/40 border-l pl-1">
|
|
59
61
|
<button
|
|
60
|
-
aria-label=
|
|
62
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.move', message: 'Move' })}
|
|
61
63
|
className={ACTION_BTN}
|
|
62
64
|
onClick={(e) => {
|
|
63
65
|
stop(e)
|
|
@@ -68,7 +70,7 @@ export function MobileSelectionBar({
|
|
|
68
70
|
<Move className="h-4 w-4" />
|
|
69
71
|
</button>
|
|
70
72
|
<button
|
|
71
|
-
aria-label=
|
|
73
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.duplicate', message: 'Duplicate' })}
|
|
72
74
|
className={ACTION_BTN}
|
|
73
75
|
onClick={(e) => {
|
|
74
76
|
stop(e)
|
|
@@ -79,7 +81,7 @@ export function MobileSelectionBar({
|
|
|
79
81
|
<Copy className="h-4 w-4" />
|
|
80
82
|
</button>
|
|
81
83
|
<button
|
|
82
|
-
aria-label=
|
|
84
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.delete', message: 'Delete' })}
|
|
83
85
|
className={cn(
|
|
84
86
|
ACTION_BTN,
|
|
85
87
|
'hover:bg-red-500/15 hover:text-red-600 dark:hover:bg-red-500/15 dark:hover:text-red-400',
|
|
@@ -93,7 +95,7 @@ export function MobileSelectionBar({
|
|
|
93
95
|
<Trash2 className="h-4 w-4" />
|
|
94
96
|
</button>
|
|
95
97
|
<button
|
|
96
|
-
aria-label=
|
|
98
|
+
aria-label={i18n._({ id: 'editor.panels.mobileSelectionBar.editProperties', message: 'Edit properties' })}
|
|
97
99
|
className={ACTION_BTN}
|
|
98
100
|
onClick={(e) => {
|
|
99
101
|
stop(e)
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
buildMultiNodePatches,
|
|
17
17
|
commitMultiNodeFields,
|
|
18
|
+
fieldTargetIds,
|
|
18
19
|
fieldVisibleForAll,
|
|
19
20
|
reduceFieldValue,
|
|
20
21
|
reduceHeightBoundMode,
|
|
@@ -57,6 +58,32 @@ describe('reduceFieldValue', () => {
|
|
|
57
58
|
expect(reduceFieldValue(['a', 'b'], 'thickness', nodes)).toEqual({ kind: 'mixed' })
|
|
58
59
|
})
|
|
59
60
|
|
|
61
|
+
test('an omitted value reads as the field default, not as zero', () => {
|
|
62
|
+
const nodes = {
|
|
63
|
+
a: makeNode('a', 'wall'),
|
|
64
|
+
b: makeNode('b', 'wall', { thickness: 0.1 }),
|
|
65
|
+
c: makeNode('c', 'wall', { thickness: 0.2 }),
|
|
66
|
+
}
|
|
67
|
+
expect(reduceFieldValue(['a', 'b'], 'thickness', nodes, 0.1)).toEqual({
|
|
68
|
+
kind: 'same',
|
|
69
|
+
value: 0.1,
|
|
70
|
+
})
|
|
71
|
+
expect(reduceFieldValue(['a', 'c'], 'thickness', nodes, 0.1)).toEqual({ kind: 'mixed' })
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('a field that names the nodes it applies to edits only those', () => {
|
|
75
|
+
const nodes = {
|
|
76
|
+
a: makeNode('a', 'wall', { thickness: 0.2 }),
|
|
77
|
+
b: makeNode('b', 'wall', { thickness: 0.18, assembly: { layers: [] } }),
|
|
78
|
+
c: makeNode('c', 'wall', { thickness: 0.2 }),
|
|
79
|
+
}
|
|
80
|
+
const plain = (node: AnyNode) => !(node as { assembly?: unknown }).assembly
|
|
81
|
+
const targets = fieldTargetIds(['a', 'b', 'c'], plain, nodes)
|
|
82
|
+
expect(targets).toEqual(['a', 'c'])
|
|
83
|
+
expect(reduceFieldValue(targets, 'thickness', nodes)).toEqual({ kind: 'same', value: 0.2 })
|
|
84
|
+
expect(fieldTargetIds(['a', 'b'], undefined, nodes)).toEqual(['a', 'b'])
|
|
85
|
+
})
|
|
86
|
+
|
|
60
87
|
test('vec3 compares elementwise', () => {
|
|
61
88
|
const nodes = {
|
|
62
89
|
a: makeNode('a', 'item', { position: [1, 0, 2] }),
|
|
@@ -20,17 +20,23 @@ function fieldValuesEqual(a: unknown, b: unknown): boolean {
|
|
|
20
20
|
return false
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The value every node holds for `key`, or mixed. `fallback` is what a node
|
|
25
|
+
* that omits the key shows (the field's default), so an absent value and an
|
|
26
|
+
* explicit default read as the same.
|
|
27
|
+
*/
|
|
23
28
|
export function reduceFieldValue(
|
|
24
29
|
nodeIds: readonly string[],
|
|
25
30
|
key: string,
|
|
26
31
|
nodes: Readonly<Record<string, AnyNode | undefined>>,
|
|
32
|
+
fallback?: unknown,
|
|
27
33
|
): ReducedFieldValue {
|
|
28
34
|
let seen = false
|
|
29
35
|
let shared: unknown
|
|
30
36
|
for (const id of nodeIds) {
|
|
31
37
|
const node = nodes[id]
|
|
32
38
|
if (!node) continue
|
|
33
|
-
const value = (node as Record<string, unknown>)[key]
|
|
39
|
+
const value = (node as Record<string, unknown>)[key] ?? fallback
|
|
34
40
|
if (!seen) {
|
|
35
41
|
seen = true
|
|
36
42
|
shared = value
|
|
@@ -78,6 +84,19 @@ export function fieldVisibleForAll(
|
|
|
78
84
|
return true
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/** The selected nodes a field edits: every one, unless it names those it `appliesTo`. */
|
|
88
|
+
export function fieldTargetIds<T extends string>(
|
|
89
|
+
nodeIds: readonly T[],
|
|
90
|
+
appliesTo: ((node: AnyNode) => boolean) | undefined,
|
|
91
|
+
nodes: Readonly<Record<string, AnyNode | undefined>>,
|
|
92
|
+
): T[] {
|
|
93
|
+
if (!appliesTo) return [...nodeIds]
|
|
94
|
+
return nodeIds.filter((id) => {
|
|
95
|
+
const node = nodes[id]
|
|
96
|
+
return node ? appliesTo(node) : false
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
81
100
|
export function firstNumericFieldValue(
|
|
82
101
|
nodeIds: readonly string[],
|
|
83
102
|
key: string,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveCeilingHeight,
|
|
12
12
|
terrainSupportLift,
|
|
13
13
|
useScene,
|
|
14
|
-
type WallNode,
|
|
14
|
+
type WallNode, usePascalI18n,
|
|
15
15
|
} from '@pascal-app/core'
|
|
16
16
|
import { useViewer } from '@pascal-app/viewer'
|
|
17
17
|
import { useCallback } from 'react'
|
|
@@ -99,6 +99,7 @@ export function MultiHeightModeField({
|
|
|
99
99
|
max?: number
|
|
100
100
|
step?: number
|
|
101
101
|
}) {
|
|
102
|
+
const i18n = usePascalI18n()
|
|
102
103
|
const unit = useViewer((s) => s.unit)
|
|
103
104
|
const metricNotation = useViewer((s) => s.metricNotation)
|
|
104
105
|
const mode = useScene(useShallow((s) => reduceHeightBoundMode(nodeIds, s.nodes)))
|
|
@@ -151,29 +152,29 @@ export function MultiHeightModeField({
|
|
|
151
152
|
const sliderMixed = storedHeight.kind === 'mixed' || mixedMode
|
|
152
153
|
const currentLabel = Number.isFinite(liveHeight)
|
|
153
154
|
? formatLinearMeasurement(liveHeight, unit, metricNotation)
|
|
154
|
-
: 'Mixed'
|
|
155
|
+
: i18n._({ id: 'editor.panels.mixed', message: 'Mixed' })
|
|
155
156
|
|
|
156
157
|
return (
|
|
157
158
|
<>
|
|
158
159
|
{nodeType === 'wall' && (
|
|
159
160
|
<div className="px-1 font-medium text-[10px] text-muted-foreground/80 uppercase tracking-wider">
|
|
160
|
-
Top
|
|
161
|
+
{i18n._({ id: 'editor.panels.multiHeightMode.top', message: 'Top' })}
|
|
161
162
|
</div>
|
|
162
163
|
)}
|
|
163
164
|
<SegmentedControl
|
|
164
165
|
mixed={mixedMode}
|
|
165
166
|
onChange={applyMode}
|
|
166
167
|
options={[
|
|
167
|
-
{ label: 'Follows level', value: 'storey' },
|
|
168
|
-
{ label: 'Custom height', value: 'custom' },
|
|
168
|
+
{ label: i18n._({ id: 'editor.panels.multiHeightMode.followsLevel', message: 'Follows level' }), value: 'storey' },
|
|
169
|
+
{ label: i18n._({ id: 'editor.panels.multiHeightMode.customHeight', message: 'Custom height' }), value: 'custom' },
|
|
169
170
|
]}
|
|
170
171
|
value={mode.kind === 'same' ? mode.value : 'storey'}
|
|
171
172
|
/>
|
|
172
173
|
{isFollows ? (
|
|
173
|
-
<div className="px-1 text-[11px] text-muted-foreground">Currently {currentLabel}</div>
|
|
174
|
+
<div className="px-1 text-[11px] text-muted-foreground">{i18n._({ id: 'editor.panels.multiHeightMode.currentlyCurrentlabel', message: 'Currently {currentLabel}', values: { currentLabel } })}</div>
|
|
174
175
|
) : isCustom ? (
|
|
175
176
|
<SliderControl
|
|
176
|
-
label=
|
|
177
|
+
label={i18n._({ id: 'editor.panels.multiHeightMode.height', message: 'Height' })}
|
|
177
178
|
max={max}
|
|
178
179
|
min={min}
|
|
179
180
|
mixed={sliderMixed}
|