@pascal-app/editor 1.0.6 → 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 +18 -15
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +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,3 +1,5 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
type AnyNode,
|
|
3
5
|
type AnyNodeId,
|
|
@@ -9,6 +11,8 @@ import {
|
|
|
9
11
|
type SiteNode,
|
|
10
12
|
useScene,
|
|
11
13
|
type ZoneNode,
|
|
14
|
+
useLocale,
|
|
15
|
+
usePascalI18n,
|
|
12
16
|
} from '@pascal-app/core'
|
|
13
17
|
import { markPerfAction, useViewer } from '@pascal-app/viewer'
|
|
14
18
|
import {
|
|
@@ -53,8 +57,8 @@ import {
|
|
|
53
57
|
buildLevelDuplicateCreateOps,
|
|
54
58
|
type LevelDuplicatePreset,
|
|
55
59
|
} from '@pascal-app/core/building'
|
|
56
|
-
import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
|
|
57
60
|
import { addStoreyOnTop } from './../../../../../lib/add-storey'
|
|
61
|
+
import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt'
|
|
58
62
|
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
|
|
59
63
|
import {
|
|
60
64
|
formatAreaLabel,
|
|
@@ -70,7 +74,6 @@ import { requestRoomDeletion } from './../../../../../lib/room-structure-command
|
|
|
70
74
|
import {
|
|
71
75
|
selectZoneOrRoom,
|
|
72
76
|
useZoneSelected,
|
|
73
|
-
zoneKindLabel,
|
|
74
77
|
} from './../../../../../lib/room-zone-routing'
|
|
75
78
|
import { createUnitInBuilding, toggleZoneMembership } from './../../../../../lib/units'
|
|
76
79
|
import { cn } from './../../../../../lib/utils'
|
|
@@ -79,10 +82,13 @@ import { useUploadStore } from '../../../../../store/use-upload'
|
|
|
79
82
|
import { MetricControl } from '../../../controls/metric-control'
|
|
80
83
|
import { LevelDuplicateDialog } from '../../../level-duplicate-dialog'
|
|
81
84
|
import { CollectionsSection } from './collection-tree-node'
|
|
82
|
-
import { InlineRenameInput } from './inline-rename-input'
|
|
85
|
+
import { InlineRenameInput, RoomRenameInput } from './inline-rename-input'
|
|
83
86
|
import { ZoneMembershipCheckbox } from './zone-membership-checkbox'
|
|
84
87
|
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
85
88
|
import { TreeNodeDragProvider } from './tree-node-drag'
|
|
89
|
+
import { zoneDefaultName } from './default-names'
|
|
90
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
91
|
+
import { authoredLevelName, localizedLevelName } from '../../../../../i18n/level-name'
|
|
86
92
|
|
|
87
93
|
// ============================================================================
|
|
88
94
|
// PROPERTY LINE SECTION
|
|
@@ -126,6 +132,7 @@ function useSiteNode(): SiteNode | null {
|
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
const PropertyLineSection = memo(function PropertyLineSection() {
|
|
135
|
+
const i18n = usePascalI18n()
|
|
129
136
|
const siteNode = useSiteNode()
|
|
130
137
|
const updateNode = useScene((state) => state.updateNode)
|
|
131
138
|
const mode = useEditor((state) => state.mode)
|
|
@@ -194,7 +201,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
194
201
|
|
|
195
202
|
<div className="flex items-center gap-2">
|
|
196
203
|
<Pentagon className="h-4 w-4 text-muted-foreground" />
|
|
197
|
-
<span className="font-medium text-sm">Property Line</span>
|
|
204
|
+
<span className="font-medium text-sm">{i18n._({ id: 'editor.sidebar.panels.sitePanel.propertyLine', message: 'Property Line' })}</span>
|
|
198
205
|
</div>
|
|
199
206
|
<button
|
|
200
207
|
className={cn(
|
|
@@ -212,13 +219,13 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
212
219
|
{/* Measurements */}
|
|
213
220
|
<div className="relative flex gap-3 pr-3 pb-2 pl-10">
|
|
214
221
|
<div className="text-muted-foreground text-xs">
|
|
215
|
-
Area:{' '}
|
|
222
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.area', message: 'Area:' })}{' '}
|
|
216
223
|
<span className="text-foreground">
|
|
217
224
|
{displayArea.toFixed(1)} {getAreaUnitLabel(viewerUnit)}
|
|
218
225
|
</span>
|
|
219
226
|
</div>
|
|
220
227
|
<div className="text-muted-foreground text-xs">
|
|
221
|
-
Perimeter:{' '}
|
|
228
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.perimeter', message: 'Perimeter:' })}{' '}
|
|
222
229
|
<span className="text-foreground">
|
|
223
230
|
{displayPerimeter.toFixed(1)} {linearLabel}
|
|
224
231
|
</span>
|
|
@@ -272,7 +279,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
|
|
|
272
279
|
onClick={handleAddPoint}
|
|
273
280
|
>
|
|
274
281
|
<Plus className="h-3 w-3" />
|
|
275
|
-
Add point
|
|
282
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.addPoint', message: 'Add point' })}
|
|
276
283
|
</button>
|
|
277
284
|
</div>
|
|
278
285
|
)}
|
|
@@ -297,6 +304,7 @@ const CameraPopover = memo(function CameraPopover({
|
|
|
297
304
|
onOpenChange: (open: boolean) => void
|
|
298
305
|
buttonClassName?: string
|
|
299
306
|
}) {
|
|
307
|
+
const i18n = usePascalI18n()
|
|
300
308
|
const updateNode = useScene((state) => state.updateNode)
|
|
301
309
|
return (
|
|
302
310
|
<Popover onOpenChange={onOpenChange} open={open}>
|
|
@@ -307,7 +315,7 @@ const CameraPopover = memo(function CameraPopover({
|
|
|
307
315
|
buttonClassName,
|
|
308
316
|
)}
|
|
309
317
|
onClick={(e) => e.stopPropagation()}
|
|
310
|
-
title=
|
|
318
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
|
|
311
319
|
>
|
|
312
320
|
<Camera className="h-3.5 w-3.5" />
|
|
313
321
|
{hasCamera && (
|
|
@@ -332,7 +340,7 @@ const CameraPopover = memo(function CameraPopover({
|
|
|
332
340
|
}}
|
|
333
341
|
>
|
|
334
342
|
<Camera className="h-3.5 w-3.5" />
|
|
335
|
-
View snapshot
|
|
343
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
|
|
336
344
|
</button>
|
|
337
345
|
)}
|
|
338
346
|
<button
|
|
@@ -344,7 +352,7 @@ const CameraPopover = memo(function CameraPopover({
|
|
|
344
352
|
}}
|
|
345
353
|
>
|
|
346
354
|
<Camera className="h-3.5 w-3.5" />
|
|
347
|
-
{hasCamera ? 'Update snapshot' : 'Take snapshot'}
|
|
355
|
+
{hasCamera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
|
|
348
356
|
</button>
|
|
349
357
|
{hasCamera && (
|
|
350
358
|
<button
|
|
@@ -356,7 +364,7 @@ const CameraPopover = memo(function CameraPopover({
|
|
|
356
364
|
}}
|
|
357
365
|
>
|
|
358
366
|
<Trash2 className="h-3.5 w-3.5" />
|
|
359
|
-
Clear snapshot
|
|
367
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
|
|
360
368
|
</button>
|
|
361
369
|
)}
|
|
362
370
|
</div>
|
|
@@ -376,6 +384,7 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
376
384
|
setSelectedReferenceId: (id: string) => void
|
|
377
385
|
handleDelete: (id: string, e: React.MouseEvent) => void
|
|
378
386
|
}) {
|
|
387
|
+
const i18n = usePascalI18n()
|
|
379
388
|
const [isEditing, setIsEditing] = useState(false)
|
|
380
389
|
const [isExpanded, setIsExpanded] = useState(true)
|
|
381
390
|
const updateNode = useScene((state) => state.updateNode)
|
|
@@ -450,13 +459,13 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
450
459
|
>
|
|
451
460
|
{isCapture ? (
|
|
452
461
|
<img
|
|
453
|
-
alt=
|
|
462
|
+
alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.capture', message: 'Capture' })}
|
|
454
463
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
455
464
|
src="/icons/mesh.webp"
|
|
456
465
|
/>
|
|
457
466
|
) : (
|
|
458
467
|
<img
|
|
459
|
-
alt=
|
|
468
|
+
alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.guide', message: 'Guide' })}
|
|
460
469
|
className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
|
|
461
470
|
src="/icons/floorplan.webp"
|
|
462
471
|
/>
|
|
@@ -477,7 +486,7 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
477
486
|
event.stopPropagation()
|
|
478
487
|
updateNode(refNode.id, { visible: !isVisible })
|
|
479
488
|
}}
|
|
480
|
-
title={isVisible ? 'Hide' : 'Show'}
|
|
489
|
+
title={isVisible ? i18n._({ id: 'editor.sidebar.panels.sitePanel.hide', message: 'Hide' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.show', message: 'Show' })}
|
|
481
490
|
type="button"
|
|
482
491
|
>
|
|
483
492
|
{isVisible ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3" />}
|
|
@@ -486,7 +495,7 @@ const ReferenceItem = memo(function ReferenceItem({
|
|
|
486
495
|
<button
|
|
487
496
|
className="z-20 flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/ref:opacity-100 dark:hover:bg-white/10"
|
|
488
497
|
onClick={(e) => handleDelete(refNode.id, e)}
|
|
489
|
-
title=
|
|
498
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
|
|
490
499
|
type="button"
|
|
491
500
|
>
|
|
492
501
|
<Trash2 className="h-3 w-3" />
|
|
@@ -516,6 +525,7 @@ const LevelReferences = memo(function LevelReferences({
|
|
|
516
525
|
onUploadAsset,
|
|
517
526
|
onDeleteAsset,
|
|
518
527
|
}: LevelReferencesProps) {
|
|
528
|
+
const i18n = usePascalI18n()
|
|
519
529
|
const createNode = useScene((s) => s.createNode)
|
|
520
530
|
const deleteNode = useScene((s) => s.deleteNode)
|
|
521
531
|
const setSelection = useViewer((s) => s.setSelection)
|
|
@@ -676,7 +686,12 @@ const LevelReferences = memo(function LevelReferences({
|
|
|
676
686
|
) : (
|
|
677
687
|
<Plus className="h-3.5 w-3.5" />
|
|
678
688
|
)}
|
|
679
|
-
{uploading ?
|
|
689
|
+
{uploading ? i18n._({
|
|
690
|
+
id: 'editor.sidebar.panels.sitePanel.uploadingProgress',
|
|
691
|
+
message:
|
|
692
|
+
'{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}',
|
|
693
|
+
values: { assetType: uploadingType, progress },
|
|
694
|
+
}) : i18n._({ id: 'editor.sidebar.panels.sitePanel.uploadScanFloorplan', message: 'Upload scan/floorplan' })}
|
|
680
695
|
</button>
|
|
681
696
|
|
|
682
697
|
<input
|
|
@@ -736,6 +751,8 @@ const LevelItem = memo(function LevelItem({
|
|
|
736
751
|
onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
|
|
737
752
|
onDeleteAsset?: (projectId: string, url: string) => void
|
|
738
753
|
}) {
|
|
754
|
+
const i18n = usePascalI18n()
|
|
755
|
+
const locale = useLocale()
|
|
739
756
|
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false)
|
|
740
757
|
const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false)
|
|
741
758
|
const [isEditing, setIsEditing] = useState(false)
|
|
@@ -820,7 +837,14 @@ const LevelItem = memo(function LevelItem({
|
|
|
820
837
|
|
|
821
838
|
<div className="relative z-20 flex h-8 items-center pr-1 pl-[28px]">
|
|
822
839
|
<button
|
|
840
|
+
aria-expanded={isExpanded}
|
|
841
|
+
aria-label={
|
|
842
|
+
isExpanded
|
|
843
|
+
? i18n._({ id: 'editor.layers.collapse', message: 'Collapse' })
|
|
844
|
+
: i18n._({ id: 'editor.layers.expand', message: 'Expand' })
|
|
845
|
+
}
|
|
823
846
|
className="z-20 flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center bg-inherit"
|
|
847
|
+
type="button"
|
|
824
848
|
onClick={(e) => {
|
|
825
849
|
e.stopPropagation()
|
|
826
850
|
if (isSelected) {
|
|
@@ -840,7 +864,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
840
864
|
|
|
841
865
|
<div className="flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-2 py-0 pl-0.5 text-sm">
|
|
842
866
|
<img
|
|
843
|
-
alt=
|
|
867
|
+
alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.level', message: 'Level' })}
|
|
844
868
|
className={cn(
|
|
845
869
|
'h-4 w-4 shrink-0 object-contain transition-all duration-200',
|
|
846
870
|
!isSelected && 'opacity-60 grayscale',
|
|
@@ -848,8 +872,9 @@ const LevelItem = memo(function LevelItem({
|
|
|
848
872
|
src="/icons/level.webp"
|
|
849
873
|
/>
|
|
850
874
|
<InlineRenameInput
|
|
851
|
-
defaultName={
|
|
852
|
-
displayName={
|
|
875
|
+
defaultName={localizedLevelName({ level: level.level }, i18n, locale)}
|
|
876
|
+
displayName={localizedLevelName(level, i18n, locale)}
|
|
877
|
+
editableName={(name) => authoredLevelName({ name, level: level.level })}
|
|
853
878
|
isEditing={isEditing}
|
|
854
879
|
nodeId={level.id}
|
|
855
880
|
onStartEditing={() => setIsEditing(true)}
|
|
@@ -867,7 +892,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
867
892
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
868
893
|
)}
|
|
869
894
|
onClick={(e) => e.stopPropagation()}
|
|
870
|
-
title=
|
|
895
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
|
|
871
896
|
>
|
|
872
897
|
<Camera className="h-3.5 w-3.5" />
|
|
873
898
|
{level.camera && (
|
|
@@ -892,7 +917,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
892
917
|
}}
|
|
893
918
|
>
|
|
894
919
|
<Camera className="h-3.5 w-3.5" />
|
|
895
|
-
View snapshot
|
|
920
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
|
|
896
921
|
</button>
|
|
897
922
|
)}
|
|
898
923
|
<button
|
|
@@ -904,7 +929,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
904
929
|
}}
|
|
905
930
|
>
|
|
906
931
|
<Camera className="h-3.5 w-3.5" />
|
|
907
|
-
{level.camera ? 'Update snapshot' : 'Take snapshot'}
|
|
932
|
+
{level.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
|
|
908
933
|
</button>
|
|
909
934
|
{level.camera && (
|
|
910
935
|
<button
|
|
@@ -916,7 +941,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
916
941
|
}}
|
|
917
942
|
>
|
|
918
943
|
<Trash2 className="h-3.5 w-3.5" />
|
|
919
|
-
Clear snapshot
|
|
944
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
|
|
920
945
|
</button>
|
|
921
946
|
)}
|
|
922
947
|
</div>
|
|
@@ -925,6 +950,11 @@ const LevelItem = memo(function LevelItem({
|
|
|
925
950
|
<Popover>
|
|
926
951
|
<PopoverTrigger asChild>
|
|
927
952
|
<button
|
|
953
|
+
aria-label={i18n._({
|
|
954
|
+
id: 'editor.layers.levelOptions',
|
|
955
|
+
message: '{levelName} options',
|
|
956
|
+
values: { levelName: localizedLevelName(level, i18n, locale) },
|
|
957
|
+
})}
|
|
928
958
|
className={cn(
|
|
929
959
|
'mr-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md opacity-0 transition-colors group-hover/level:opacity-100',
|
|
930
960
|
selectedLevelId === level.id
|
|
@@ -936,31 +966,31 @@ const LevelItem = memo(function LevelItem({
|
|
|
936
966
|
<MoreHorizontal className="h-3.5 w-3.5" />
|
|
937
967
|
</button>
|
|
938
968
|
</PopoverTrigger>
|
|
939
|
-
<PopoverContent align="start" className="w-48 p-1" side="right">
|
|
969
|
+
<PopoverContent align="start" className="w-max min-w-48 max-w-72 p-1" side="right">
|
|
940
970
|
<button
|
|
941
971
|
className="flex w-full cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors hover:bg-accent"
|
|
942
972
|
onClick={() => handleDuplicateLevel()}
|
|
943
|
-
title=
|
|
973
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateLevel', message: 'Duplicate level' })}
|
|
944
974
|
>
|
|
945
975
|
<Copy className="h-3.5 w-3.5" />
|
|
946
|
-
Duplicate
|
|
976
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicate', message: 'Duplicate' })}
|
|
947
977
|
</button>
|
|
948
978
|
<button
|
|
949
979
|
className="flex w-full cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors hover:bg-accent"
|
|
950
980
|
onClick={() => setDuplicateDialogOpen(true)}
|
|
951
|
-
title=
|
|
981
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateLevelWithOptions', message: 'Duplicate level with options' })}
|
|
952
982
|
>
|
|
953
983
|
<Copy className="h-3.5 w-3.5" />
|
|
954
|
-
Duplicate with options...
|
|
984
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateWithOptions', message: 'Duplicate with options...' })}
|
|
955
985
|
</button>
|
|
956
986
|
<button
|
|
957
987
|
className="flex w-full items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors enabled:cursor-pointer enabled:hover:bg-accent enabled:hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
|
958
988
|
disabled={!canDeleteLevel}
|
|
959
989
|
onClick={() => deleteLevelWithFallbackSelection(level.id)}
|
|
960
|
-
title={canDeleteLevel ? 'Delete level' : 'The ground level cannot be deleted'}
|
|
990
|
+
title={canDeleteLevel ? i18n._({ id: 'editor.sidebar.panels.sitePanel.deleteLevel', message: 'Delete level' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted', message: 'The ground level cannot be deleted' })}
|
|
961
991
|
>
|
|
962
992
|
<Trash2 className="h-3.5 w-3.5" />
|
|
963
|
-
Delete
|
|
993
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
|
|
964
994
|
</button>
|
|
965
995
|
</PopoverContent>
|
|
966
996
|
</Popover>
|
|
@@ -977,7 +1007,7 @@ const LevelItem = memo(function LevelItem({
|
|
|
977
1007
|
<div className="relative border-border/50 border-b py-2 pr-3 pl-[60px]">
|
|
978
1008
|
<div className="pointer-events-none absolute top-0 bottom-0 left-[45px] z-10 w-px bg-border/50" />
|
|
979
1009
|
<MetricControl
|
|
980
|
-
label=
|
|
1010
|
+
label={i18n._({ id: 'editor.sidebar.panels.sitePanel.baseElevation', message: 'Base elevation' })}
|
|
981
1011
|
onChange={(value) => updateNode(level.id, { baseElevation: value })}
|
|
982
1012
|
precision={2}
|
|
983
1013
|
step={0.05}
|
|
@@ -1014,6 +1044,7 @@ const LevelsSection = memo(function LevelsSection({
|
|
|
1014
1044
|
onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
|
|
1015
1045
|
onDeleteAsset?: (projectId: string, url: string) => void
|
|
1016
1046
|
} = {}) {
|
|
1047
|
+
const i18n = usePascalI18n()
|
|
1017
1048
|
const updateNode = useScene((state) => state.updateNode)
|
|
1018
1049
|
const selectedBuildingId = useViewer((state) => state.selection.buildingId)
|
|
1019
1050
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
@@ -1038,6 +1069,7 @@ const LevelsSection = memo(function LevelsSection({
|
|
|
1038
1069
|
const handleAddLevel = () => {
|
|
1039
1070
|
const newLevel = addStoreyOnTop(building.id)
|
|
1040
1071
|
setSelection({ buildingId: building.id, levelId: newLevel.id })
|
|
1072
|
+
offerExteriorWallCopy(newLevel.id)
|
|
1041
1073
|
}
|
|
1042
1074
|
|
|
1043
1075
|
return (
|
|
@@ -1056,7 +1088,7 @@ const LevelsSection = memo(function LevelsSection({
|
|
|
1056
1088
|
<div className="relative z-10 flex items-center pr-1 pl-[38px]">
|
|
1057
1089
|
<Plus className="h-3.5 w-3.5" />
|
|
1058
1090
|
</div>
|
|
1059
|
-
<span className="truncate">Add level</span>
|
|
1091
|
+
<span className="truncate">{i18n._({ id: 'editor.sidebar.panels.sitePanel.addLevel', message: 'Add level' })}</span>
|
|
1060
1092
|
</button>
|
|
1061
1093
|
{levels.length === 0 && (
|
|
1062
1094
|
<div className="relative flex h-8 select-none items-center border-border/50 border-b py-0 pr-2 pl-[38px] text-muted-foreground text-xs">
|
|
@@ -1064,7 +1096,7 @@ const LevelsSection = memo(function LevelsSection({
|
|
|
1064
1096
|
<div className="pointer-events-none absolute top-0 bottom-1/2 left-[21px] w-px bg-border/50" />
|
|
1065
1097
|
{/* Horizontal branch line */}
|
|
1066
1098
|
<div className="pointer-events-none absolute top-1/2 left-[21px] h-px w-[11px] bg-border/50" />
|
|
1067
|
-
No levels yet
|
|
1099
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.noLevelsYet', message: 'No levels yet' })}
|
|
1068
1100
|
</div>
|
|
1069
1101
|
)}
|
|
1070
1102
|
{[...levels].reverse().map((level, index) => (
|
|
@@ -1091,6 +1123,7 @@ const UnitsSection = memo(function UnitsSection({
|
|
|
1091
1123
|
}: {
|
|
1092
1124
|
buildingId: BuildingNode['id']
|
|
1093
1125
|
}) {
|
|
1126
|
+
const i18n = usePascalI18n()
|
|
1094
1127
|
const unitIds = useScene(
|
|
1095
1128
|
useShallow((s) => {
|
|
1096
1129
|
const building = s.nodes[buildingId] as BuildingNode | undefined
|
|
@@ -1117,7 +1150,7 @@ const UnitsSection = memo(function UnitsSection({
|
|
|
1117
1150
|
<button
|
|
1118
1151
|
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10"
|
|
1119
1152
|
onClick={handleNewUnit}
|
|
1120
|
-
title=
|
|
1153
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.newUnit', message: 'New unit' })}
|
|
1121
1154
|
type="button"
|
|
1122
1155
|
>
|
|
1123
1156
|
<Plus className="h-3 w-3" />
|
|
@@ -1129,7 +1162,7 @@ const UnitsSection = memo(function UnitsSection({
|
|
|
1129
1162
|
icon={<Group className="h-3.5 w-3.5" />}
|
|
1130
1163
|
label={
|
|
1131
1164
|
<span className="flex items-center gap-1.5">
|
|
1132
|
-
Units
|
|
1165
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.units', message: 'Units' })}
|
|
1133
1166
|
{hasUnits && <span className="text-muted-foreground text-xs">{unitIds.length}</span>}
|
|
1134
1167
|
</span>
|
|
1135
1168
|
}
|
|
@@ -1145,7 +1178,7 @@ const UnitsSection = memo(function UnitsSection({
|
|
|
1145
1178
|
hasChildren={false}
|
|
1146
1179
|
icon={<Plus className="h-3.5 w-3.5" />}
|
|
1147
1180
|
isLast
|
|
1148
|
-
label=
|
|
1181
|
+
label={i18n._({ id: 'editor.sidebar.panels.sitePanel.newUnit', message: 'New unit' })}
|
|
1149
1182
|
onClick={handleNewUnit}
|
|
1150
1183
|
onToggle={() => {}}
|
|
1151
1184
|
/>
|
|
@@ -1163,6 +1196,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1163
1196
|
depth: number
|
|
1164
1197
|
isLast?: boolean
|
|
1165
1198
|
}) {
|
|
1199
|
+
const i18n = usePascalI18n()
|
|
1166
1200
|
const [isEditing, setIsEditing] = useState(false)
|
|
1167
1201
|
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false)
|
|
1168
1202
|
const deleteNode = useScene((state) => state.deleteNode)
|
|
@@ -1190,7 +1224,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1190
1224
|
}
|
|
1191
1225
|
}, [isSelected])
|
|
1192
1226
|
|
|
1193
|
-
const defaultName =
|
|
1227
|
+
const defaultName = zoneDefaultName(i18n, zone, formatAreaLabel(calculatePolygonArea(zone.polygon), unit))
|
|
1194
1228
|
|
|
1195
1229
|
const handleClick = () => {
|
|
1196
1230
|
setMode('select')
|
|
@@ -1250,14 +1284,22 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1250
1284
|
<ZoneMembershipCheckbox
|
|
1251
1285
|
checked={focusedUnit.members.includes(zone.id)}
|
|
1252
1286
|
onToggle={() => toggleZoneMembership(focusedUnit.id, zone.id)}
|
|
1253
|
-
unitName={focusedUnit.name || 'Unit'}
|
|
1287
|
+
unitName={focusedUnit.name || i18n._(nodeLabelDescriptor('unit', 'Unit'))}
|
|
1254
1288
|
/>
|
|
1255
1289
|
)}
|
|
1256
1290
|
<span className={cn('mr-2', !isSelected && 'opacity-40')}>
|
|
1257
|
-
<ColorDot
|
|
1291
|
+
<ColorDot
|
|
1292
|
+
color={zone.color}
|
|
1293
|
+
label={i18n._({
|
|
1294
|
+
id: 'editor.layers.spaceColour',
|
|
1295
|
+
message: '{role, select, room {Room colour} other {Zone colour}}',
|
|
1296
|
+
values: { role: zone.spaceRole ?? 'generic' },
|
|
1297
|
+
})}
|
|
1298
|
+
onChange={handleColorChange}
|
|
1299
|
+
/>
|
|
1258
1300
|
</span>
|
|
1259
1301
|
<div className="min-w-0 flex-1 pr-1">
|
|
1260
|
-
<
|
|
1302
|
+
<RoomRenameInput
|
|
1261
1303
|
defaultName={defaultName}
|
|
1262
1304
|
isEditing={isEditing}
|
|
1263
1305
|
nodeId={zone.id}
|
|
@@ -1272,7 +1314,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1272
1314
|
<button
|
|
1273
1315
|
className="relative flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/row:opacity-100 dark:hover:bg-white/10"
|
|
1274
1316
|
onClick={(e) => e.stopPropagation()}
|
|
1275
|
-
title=
|
|
1317
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
|
|
1276
1318
|
>
|
|
1277
1319
|
<Camera className="h-3 w-3" />
|
|
1278
1320
|
{zone.camera && (
|
|
@@ -1297,7 +1339,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1297
1339
|
}}
|
|
1298
1340
|
>
|
|
1299
1341
|
<Camera className="h-3.5 w-3.5" />
|
|
1300
|
-
View snapshot
|
|
1342
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
|
|
1301
1343
|
</button>
|
|
1302
1344
|
)}
|
|
1303
1345
|
<button
|
|
@@ -1309,7 +1351,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1309
1351
|
}}
|
|
1310
1352
|
>
|
|
1311
1353
|
<Camera className="h-3.5 w-3.5" />
|
|
1312
|
-
{zone.camera ? 'Update snapshot' : 'Take snapshot'}
|
|
1354
|
+
{zone.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
|
|
1313
1355
|
</button>
|
|
1314
1356
|
{zone.camera && (
|
|
1315
1357
|
<button
|
|
@@ -1321,15 +1363,17 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1321
1363
|
}}
|
|
1322
1364
|
>
|
|
1323
1365
|
<Trash2 className="h-3.5 w-3.5" />
|
|
1324
|
-
Clear snapshot
|
|
1366
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
|
|
1325
1367
|
</button>
|
|
1326
1368
|
)}
|
|
1327
1369
|
</div>
|
|
1328
1370
|
</PopoverContent>
|
|
1329
1371
|
</Popover>
|
|
1330
1372
|
<button
|
|
1331
|
-
|
|
1373
|
+
aria-label={i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
|
|
1374
|
+
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/row:opacity-100 focus-visible:opacity-100 dark:hover:bg-white/10"
|
|
1332
1375
|
onClick={handleDelete}
|
|
1376
|
+
type="button"
|
|
1333
1377
|
>
|
|
1334
1378
|
<Trash2 className="h-3 w-3" />
|
|
1335
1379
|
</button>
|
|
@@ -1339,6 +1383,7 @@ const ZoneItem = memo(function ZoneItem({
|
|
|
1339
1383
|
})
|
|
1340
1384
|
|
|
1341
1385
|
const MultiSelectionBadge = memo(function MultiSelectionBadge() {
|
|
1386
|
+
const i18n = usePascalI18n()
|
|
1342
1387
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
|
1343
1388
|
const setSelection = useViewer((state) => state.setSelection)
|
|
1344
1389
|
|
|
@@ -1347,11 +1392,15 @@ const MultiSelectionBadge = memo(function MultiSelectionBadge() {
|
|
|
1347
1392
|
return (
|
|
1348
1393
|
<div className="pointer-events-none sticky top-4 z-50 flex h-0 w-full justify-center overflow-visible">
|
|
1349
1394
|
<div className="pointer-events-auto flex items-center gap-2.5 rounded-full border border-primary/20 bg-primary px-0.5 py-4 pl-2 font-medium text-primary-foreground text-xs shadow-black/10 shadow-lg backdrop-blur-md">
|
|
1350
|
-
<span>{
|
|
1395
|
+
<span>{i18n._({
|
|
1396
|
+
id: 'editor.sidebar.panels.sitePanel.objectsSelected',
|
|
1397
|
+
message: '{count, plural, one {# object selected} other {# objects selected}}',
|
|
1398
|
+
values: { count: selectedIds.length },
|
|
1399
|
+
})}</span>
|
|
1351
1400
|
<button
|
|
1352
1401
|
className="cursor-pointer rounded-full p-1.5 transition-colors hover:bg-primary-foreground/20"
|
|
1353
1402
|
onClick={() => setSelection({ selectedIds: [] })}
|
|
1354
|
-
title=
|
|
1403
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSelection', message: 'Clear selection' })}
|
|
1355
1404
|
>
|
|
1356
1405
|
<X className="h-4 w-4" />
|
|
1357
1406
|
</button>
|
|
@@ -1361,6 +1410,7 @@ const MultiSelectionBadge = memo(function MultiSelectionBadge() {
|
|
|
1361
1410
|
})
|
|
1362
1411
|
|
|
1363
1412
|
const RoomsSection = memo(function RoomsSection() {
|
|
1413
|
+
const i18n = usePascalI18n()
|
|
1364
1414
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
1365
1415
|
const armToolMode = useEditor((state) => state.armToolMode)
|
|
1366
1416
|
const levelZones = useScene(
|
|
@@ -1390,7 +1440,7 @@ const RoomsSection = memo(function RoomsSection() {
|
|
|
1390
1440
|
<button
|
|
1391
1441
|
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10"
|
|
1392
1442
|
onClick={handleDrawZone}
|
|
1393
|
-
title=
|
|
1443
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.drawAZone', message: 'Draw a zone' })}
|
|
1394
1444
|
type="button"
|
|
1395
1445
|
>
|
|
1396
1446
|
<Plus className="h-3 w-3" />
|
|
@@ -1402,7 +1452,7 @@ const RoomsSection = memo(function RoomsSection() {
|
|
|
1402
1452
|
icon={<LayoutGrid className="h-3.5 w-3.5" />}
|
|
1403
1453
|
label={
|
|
1404
1454
|
<span className="flex items-center gap-1.5">
|
|
1405
|
-
Rooms
|
|
1455
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.rooms', message: 'Rooms' })}
|
|
1406
1456
|
{rooms.length > 0 && (
|
|
1407
1457
|
<span className="text-muted-foreground text-xs">{rooms.length}</span>
|
|
1408
1458
|
)}
|
|
@@ -1413,7 +1463,7 @@ const RoomsSection = memo(function RoomsSection() {
|
|
|
1413
1463
|
>
|
|
1414
1464
|
{levelZones.length === 0 && (
|
|
1415
1465
|
<div className="border-border/50 border-b py-2 pr-3 pl-[52px] text-muted-foreground text-xs">
|
|
1416
|
-
Close walls around a space to make a room.
|
|
1466
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo', message: 'Close walls around a space to make a room.' })}
|
|
1417
1467
|
</div>
|
|
1418
1468
|
)}
|
|
1419
1469
|
{rooms.map((zone, index) => (
|
|
@@ -1430,7 +1480,7 @@ const RoomsSection = memo(function RoomsSection() {
|
|
|
1430
1480
|
className="pt-3 pb-1 pl-[52px] font-medium text-[11px] text-muted-foreground"
|
|
1431
1481
|
data-other-zones
|
|
1432
1482
|
>
|
|
1433
|
-
Other zones
|
|
1483
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.otherZones', message: 'Other zones' })}
|
|
1434
1484
|
</div>
|
|
1435
1485
|
{others.map((zone, index) => (
|
|
1436
1486
|
<ZoneItem depth={2} isLast={index === others.length - 1} key={zone.id} zone={zone} />
|
|
@@ -1443,6 +1493,7 @@ const RoomsSection = memo(function RoomsSection() {
|
|
|
1443
1493
|
})
|
|
1444
1494
|
|
|
1445
1495
|
const ContentSection = memo(function ContentSection() {
|
|
1496
|
+
const i18n = usePascalI18n()
|
|
1446
1497
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
|
1447
1498
|
const level = useScene((s) =>
|
|
1448
1499
|
selectedLevelId ? ((s.nodes[selectedLevelId] as LevelNode | undefined) ?? null) : null,
|
|
@@ -1462,7 +1513,7 @@ const ContentSection = memo(function ContentSection() {
|
|
|
1462
1513
|
|
|
1463
1514
|
if (!level) {
|
|
1464
1515
|
return (
|
|
1465
|
-
<div className="px-3 py-4 text-muted-foreground text-sm">Select a level to view content</div>
|
|
1516
|
+
<div className="px-3 py-4 text-muted-foreground text-sm">{i18n._({ id: 'editor.sidebar.panels.sitePanel.selectALevelToViewContent', message: 'Select a level to view content' })}</div>
|
|
1466
1517
|
)
|
|
1467
1518
|
}
|
|
1468
1519
|
|
|
@@ -1474,7 +1525,7 @@ const ContentSection = memo(function ContentSection() {
|
|
|
1474
1525
|
icon={<Boxes className="h-3.5 w-3.5" />}
|
|
1475
1526
|
label={
|
|
1476
1527
|
<span className="flex items-center gap-1.5">
|
|
1477
|
-
Elements
|
|
1528
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.elements', message: 'Elements' })}
|
|
1478
1529
|
{elementChildren.length > 0 && (
|
|
1479
1530
|
<span className="text-muted-foreground text-xs">{elementChildren.length}</span>
|
|
1480
1531
|
)}
|
|
@@ -1485,7 +1536,7 @@ const ContentSection = memo(function ContentSection() {
|
|
|
1485
1536
|
>
|
|
1486
1537
|
{elementChildren.length === 0 ? (
|
|
1487
1538
|
<div className="border-border/50 border-b py-2 pr-3 pl-[52px] text-muted-foreground text-xs">
|
|
1488
|
-
No elements on this level
|
|
1539
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.noElementsOnThisLevel', message: 'No elements on this level' })}
|
|
1489
1540
|
</div>
|
|
1490
1541
|
) : (
|
|
1491
1542
|
<TreeNodeDragProvider>
|
|
@@ -1520,6 +1571,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1520
1571
|
onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
|
|
1521
1572
|
onDeleteAsset?: (projectId: string, url: string) => void
|
|
1522
1573
|
}) {
|
|
1574
|
+
const i18n = usePascalI18n()
|
|
1523
1575
|
const setSelection = useViewer((state) => state.setSelection)
|
|
1524
1576
|
const phase = useEditor((state) => state.phase)
|
|
1525
1577
|
const setPhase = useEditor((state) => state.setPhase)
|
|
@@ -1560,14 +1612,14 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1560
1612
|
>
|
|
1561
1613
|
<div className="flex h-full min-w-0 flex-1 cursor-pointer items-center gap-2 py-2 pl-3">
|
|
1562
1614
|
<img
|
|
1563
|
-
alt=
|
|
1615
|
+
alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.building', message: 'Building' })}
|
|
1564
1616
|
className={cn(
|
|
1565
1617
|
'h-5 w-5 object-contain transition-all',
|
|
1566
1618
|
!isBuildingActive && 'opacity-60 grayscale',
|
|
1567
1619
|
)}
|
|
1568
1620
|
src="/icons/building.webp"
|
|
1569
1621
|
/>
|
|
1570
|
-
<span className="truncate font-medium text-sm">{building.name || 'Building'}</span>
|
|
1622
|
+
<span className="truncate font-medium text-sm">{building.name || i18n._({ id: 'editor.sidebar.panels.sitePanel.building', message: 'Building' })}</span>
|
|
1571
1623
|
</div>
|
|
1572
1624
|
<Popover
|
|
1573
1625
|
onOpenChange={(open) => setBuildingCameraOpen(open ? building.id : null)}
|
|
@@ -1582,7 +1634,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1582
1634
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
|
1583
1635
|
)}
|
|
1584
1636
|
onClick={(e) => e.stopPropagation()}
|
|
1585
|
-
title=
|
|
1637
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
|
|
1586
1638
|
>
|
|
1587
1639
|
<Camera className="h-4 w-4" />
|
|
1588
1640
|
{building.camera && (
|
|
@@ -1607,7 +1659,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1607
1659
|
}}
|
|
1608
1660
|
>
|
|
1609
1661
|
<Camera className="h-3.5 w-3.5" />
|
|
1610
|
-
View snapshot
|
|
1662
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
|
|
1611
1663
|
</button>
|
|
1612
1664
|
)}
|
|
1613
1665
|
<button
|
|
@@ -1619,7 +1671,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1619
1671
|
}}
|
|
1620
1672
|
>
|
|
1621
1673
|
<Camera className="h-3.5 w-3.5" />
|
|
1622
|
-
{building.camera ? 'Update snapshot' : 'Take snapshot'}
|
|
1674
|
+
{building.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
|
|
1623
1675
|
</button>
|
|
1624
1676
|
{building.camera && (
|
|
1625
1677
|
<button
|
|
@@ -1631,7 +1683,7 @@ const BuildingItem = memo(function BuildingItem({
|
|
|
1631
1683
|
}}
|
|
1632
1684
|
>
|
|
1633
1685
|
<Trash2 className="h-3.5 w-3.5" />
|
|
1634
|
-
Clear snapshot
|
|
1686
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
|
|
1635
1687
|
</button>
|
|
1636
1688
|
)}
|
|
1637
1689
|
</div>
|
|
@@ -1679,6 +1731,7 @@ export interface SitePanelProps {
|
|
|
1679
1731
|
}
|
|
1680
1732
|
|
|
1681
1733
|
export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanelProps = {}) {
|
|
1734
|
+
const i18n = usePascalI18n()
|
|
1682
1735
|
const rootNodeIds = useScene((state) => state.rootNodeIds)
|
|
1683
1736
|
const updateNode = useScene((state) => state.updateNode)
|
|
1684
1737
|
const selectedBuildingId = useViewer((state) => state.selection.buildingId)
|
|
@@ -1703,7 +1756,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1703
1756
|
|
|
1704
1757
|
return (
|
|
1705
1758
|
<LayoutGroup>
|
|
1706
|
-
<div className="flex h-full flex-col">
|
|
1759
|
+
<div className="flex h-full flex-col" data-surface="site-panel">
|
|
1707
1760
|
{/* Site Header */}
|
|
1708
1761
|
{siteNode && (
|
|
1709
1762
|
<motion.div
|
|
@@ -1725,14 +1778,14 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1725
1778
|
>
|
|
1726
1779
|
<div className="flex items-center gap-2">
|
|
1727
1780
|
<img
|
|
1728
|
-
alt=
|
|
1781
|
+
alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.site', message: 'Site' })}
|
|
1729
1782
|
className={cn(
|
|
1730
1783
|
'h-5 w-5 object-contain transition-all',
|
|
1731
1784
|
phase !== 'site' && 'opacity-60 grayscale',
|
|
1732
1785
|
)}
|
|
1733
1786
|
src="/icons/site-flag.webp"
|
|
1734
1787
|
/>
|
|
1735
|
-
<span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
|
|
1788
|
+
<span className="font-medium text-sm">{siteNode.name || i18n._({ id: 'editor.sidebar.panels.sitePanel.site', message: 'Site' })}</span>
|
|
1736
1789
|
</div>
|
|
1737
1790
|
<CameraPopover
|
|
1738
1791
|
buttonClassName={cn(
|
|
@@ -1776,7 +1829,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
|
|
|
1776
1829
|
{/* Buildings List */}
|
|
1777
1830
|
{buildings.length === 0 ? (
|
|
1778
1831
|
<motion.div className="px-3 py-4 text-muted-foreground text-sm" layout="position">
|
|
1779
|
-
No buildings yet
|
|
1832
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.noBuildingsYet', message: 'No buildings yet' })}
|
|
1780
1833
|
</motion.div>
|
|
1781
1834
|
) : (
|
|
1782
1835
|
<div className="flex min-h-0 flex-1 flex-col">
|