@pascal-app/editor 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -17
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +30 -14
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +464 -65
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +61 -4
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15117 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +20 -5
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +14 -5
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +42 -0
- package/src/lib/window-interaction.ts +47 -5
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +48 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-wall-snap-indicator.ts +15 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { type AnyNodeId, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, usePascalI18n, useScene } from '@pascal-app/core'
|
|
2
2
|
import { Pencil } from 'lucide-react'
|
|
3
3
|
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { displayRoomName } from './../../../../../lib/room-name-messages'
|
|
4
5
|
import { cn } from './../../../../../lib/utils'
|
|
6
|
+
import { commitTreeRoomRename } from './../../../panels/room-name-options'
|
|
5
7
|
|
|
6
8
|
interface InlineRenameFieldProps {
|
|
7
9
|
name: string | undefined
|
|
@@ -18,15 +20,19 @@ interface InlineRenameFieldProps {
|
|
|
18
20
|
|
|
19
21
|
type InlineRenameInputProps = Omit<InlineRenameFieldProps, 'name' | 'onRename'> & {
|
|
20
22
|
nodeId: AnyNodeId
|
|
23
|
+
/** The part of the stored name the user edits; a generated default edits as empty. */
|
|
24
|
+
editableName?: (name: string | undefined) => string | undefined
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** Renames a scene node through `updateNode`. */
|
|
24
28
|
export const InlineRenameInput = memo(function InlineRenameInput({
|
|
25
29
|
nodeId,
|
|
30
|
+
editableName,
|
|
26
31
|
...props
|
|
27
32
|
}: InlineRenameInputProps) {
|
|
28
33
|
const updateNode = useScene((s) => s.updateNode)
|
|
29
|
-
const
|
|
34
|
+
const stored = useScene((s) => s.nodes[nodeId]?.name)
|
|
35
|
+
const name = editableName ? editableName(stored) : stored
|
|
30
36
|
const handleRename = useCallback(
|
|
31
37
|
(next: string | undefined) => updateNode(nodeId, { name: next }),
|
|
32
38
|
[nodeId, updateNode],
|
|
@@ -34,6 +40,30 @@ export const InlineRenameInput = memo(function InlineRenameInput({
|
|
|
34
40
|
return <InlineRenameField {...props} name={name} onRename={handleRename} />
|
|
35
41
|
})
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Renames a room or zone from the layer tree. The field edits the name in the
|
|
45
|
+
* UI language, as the room panel shows it, and stores what an edit there
|
|
46
|
+
* stores: a catalog name stays English ("Salon 3" on a Lounge stores "Lounge 3").
|
|
47
|
+
*/
|
|
48
|
+
export const RoomRenameInput = memo(function RoomRenameInput({
|
|
49
|
+
nodeId,
|
|
50
|
+
...props
|
|
51
|
+
}: Omit<InlineRenameInputProps, 'editableName' | 'displayName'>) {
|
|
52
|
+
const i18n = usePascalI18n()
|
|
53
|
+
const stored = useScene((s) => s.nodes[nodeId]?.name)
|
|
54
|
+
const handleRename = useCallback(
|
|
55
|
+
(next: string | undefined) => commitTreeRoomRename(nodeId, next, i18n),
|
|
56
|
+
[nodeId, i18n],
|
|
57
|
+
)
|
|
58
|
+
return (
|
|
59
|
+
<InlineRenameField
|
|
60
|
+
{...props}
|
|
61
|
+
name={stored ? displayRoomName(stored, i18n) : stored}
|
|
62
|
+
onRename={handleRename}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
37
67
|
export const InlineRenameField = memo(function InlineRenameField({
|
|
38
68
|
name,
|
|
39
69
|
onRename,
|
|
@@ -44,6 +74,7 @@ export const InlineRenameField = memo(function InlineRenameField({
|
|
|
44
74
|
className,
|
|
45
75
|
onStartEditing,
|
|
46
76
|
}: InlineRenameFieldProps) {
|
|
77
|
+
const i18n = usePascalI18n()
|
|
47
78
|
const [value, setValue] = useState(name || '')
|
|
48
79
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
49
80
|
const inputSize = Math.max((value || defaultName).length, 1)
|
|
@@ -63,7 +94,7 @@ export const InlineRenameField = memo(function InlineRenameField({
|
|
|
63
94
|
|
|
64
95
|
const handleSave = useCallback(() => {
|
|
65
96
|
const trimmed = value.trim()
|
|
66
|
-
if (trimmed !== name) {
|
|
97
|
+
if ((trimmed || undefined) !== (name || undefined)) {
|
|
67
98
|
onRename(trimmed || undefined)
|
|
68
99
|
}
|
|
69
100
|
onStopEditing()
|
|
@@ -87,6 +118,7 @@ export const InlineRenameField = memo(function InlineRenameField({
|
|
|
87
118
|
</span>
|
|
88
119
|
{onStartEditing && (
|
|
89
120
|
<button
|
|
121
|
+
aria-label={i18n._({ id: 'editor.layers.rename', message: 'Rename' })}
|
|
90
122
|
className="shrink-0 text-muted-foreground opacity-0 transition-opacity hover:text-foreground group-hover/rename:opacity-100"
|
|
91
123
|
onClick={(e) => {
|
|
92
124
|
e.stopPropagation()
|
|
@@ -102,6 +134,7 @@ export const InlineRenameField = memo(function InlineRenameField({
|
|
|
102
134
|
|
|
103
135
|
return (
|
|
104
136
|
<input
|
|
137
|
+
aria-label={i18n._({ id: 'editor.layers.rename', message: 'Rename' })}
|
|
105
138
|
className={cn(
|
|
106
139
|
'm-0 h-5 min-w-[1ch] max-w-full flex-none rounded-none border-primary/50 border-b bg-transparent px-0 py-0 text-foreground text-sm outline-none focus:border-primary',
|
|
107
140
|
className,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, type ItemNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, type ItemNode, useScene, usePascalI18n } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
TreeNodeWrapper,
|
|
13
13
|
} from './tree-node'
|
|
14
14
|
import { TreeNodeActions } from './tree-node-actions'
|
|
15
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
15
16
|
|
|
16
17
|
const CATEGORY_ICONS: Record<string, string> = {
|
|
17
18
|
door: '/icons/door.webp',
|
|
@@ -34,6 +35,7 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
34
35
|
depth,
|
|
35
36
|
isLast,
|
|
36
37
|
}: ItemTreeNodeProps) {
|
|
38
|
+
const i18n = usePascalI18n()
|
|
37
39
|
const [isEditing, setIsEditing] = useState(false)
|
|
38
40
|
const [expanded, setExpanded] = useState(true)
|
|
39
41
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -83,7 +85,7 @@ export const ItemTreeNode = memo(function ItemTreeNode({
|
|
|
83
85
|
|
|
84
86
|
const iconSrc = CATEGORY_ICONS[asset?.category ?? ''] || '/icons/couch.webp'
|
|
85
87
|
const snapTarget = resolveNodeSnapTarget(node)
|
|
86
|
-
const defaultName = asset?.name || 'Item'
|
|
88
|
+
const defaultName = asset?.name || i18n._(nodeLabelDescriptor('item', 'Item'))
|
|
87
89
|
const hasChildren = children.length > 0
|
|
88
90
|
|
|
89
91
|
return (
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type LevelNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type LevelNode, useLocale, usePascalI18n, useScene } from '@pascal-app/core'
|
|
2
2
|
import { markPerfAction, useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { Layers } from 'lucide-react'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
5
5
|
import { useShallow } from 'zustand/react/shallow'
|
|
6
|
-
import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
|
|
7
6
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
7
|
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
9
8
|
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
import { authoredLevelName, localizedLevelName } from '../../../../../i18n/level-name'
|
|
10
10
|
|
|
11
11
|
interface LevelTreeNodeProps {
|
|
12
12
|
nodeId: LevelNode['id']
|
|
@@ -19,6 +19,8 @@ export const LevelTreeNode = memo(function LevelTreeNode({
|
|
|
19
19
|
depth,
|
|
20
20
|
isLast,
|
|
21
21
|
}: LevelTreeNodeProps) {
|
|
22
|
+
const i18n = usePascalI18n()
|
|
23
|
+
const locale = useLocale()
|
|
22
24
|
const [expanded, setExpanded] = useState(true)
|
|
23
25
|
const [isEditing, setIsEditing] = useState(false)
|
|
24
26
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -40,8 +42,8 @@ export const LevelTreeNode = memo(function LevelTreeNode({
|
|
|
40
42
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
41
43
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
42
44
|
|
|
43
|
-
const defaultName =
|
|
44
|
-
const displayName =
|
|
45
|
+
const defaultName = localizedLevelName({ level }, i18n, locale)
|
|
46
|
+
const displayName = localizedLevelName({ name: levelName, level }, i18n, locale)
|
|
45
47
|
|
|
46
48
|
return (
|
|
47
49
|
<TreeNodeWrapper
|
|
@@ -58,6 +60,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
|
|
|
58
60
|
<InlineRenameInput
|
|
59
61
|
defaultName={defaultName}
|
|
60
62
|
displayName={displayName}
|
|
63
|
+
editableName={(name) => authoredLevelName({ name, level })}
|
|
61
64
|
isEditing={isEditing}
|
|
62
65
|
nodeId={nodeId}
|
|
63
66
|
onStartEditing={handleStartEditing}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Icon as IconifyIcon } from '@iconify/react'
|
|
2
|
-
import { type AnyNodeId, nodeRegistry, useScene } from '@pascal-app/core'
|
|
2
|
+
import { type AnyNodeId, nodeRegistry, useScene, usePascalI18n} from '@pascal-app/core'
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
4
|
import Image from 'next/image'
|
|
5
5
|
import { memo, useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from './tree-node'
|
|
16
16
|
import { TreeNodeActions } from './tree-node-actions'
|
|
17
17
|
import { resolveTreeChildIds, treeContainsDescendant } from './tree-structure'
|
|
18
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
18
19
|
|
|
19
20
|
interface RegistryTreeNodeProps {
|
|
20
21
|
nodeId: AnyNodeId
|
|
@@ -34,6 +35,7 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
|
34
35
|
depth,
|
|
35
36
|
isLast,
|
|
36
37
|
}: RegistryTreeNodeProps) {
|
|
38
|
+
const i18n = usePascalI18n()
|
|
37
39
|
const [isEditing, setIsEditing] = useState(false)
|
|
38
40
|
const [expanded, setExpanded] = useState(true)
|
|
39
41
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -72,7 +74,11 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
|
|
|
72
74
|
)
|
|
73
75
|
const snapTarget = resolveNodeSnapTarget(node)
|
|
74
76
|
const defaultName =
|
|
75
|
-
|
|
77
|
+
(node &&
|
|
78
|
+
(tree?.label?.(node, useScene.getState().nodes) ||
|
|
79
|
+
node.name ||
|
|
80
|
+
(presentation?.label && i18n._(nodeLabelDescriptor(node.type, presentation.label))))) ||
|
|
81
|
+
i18n._({ id: 'editor.layers.defaultName.node', message: 'Node' })
|
|
76
82
|
const hasChildren = children.length > 0 || hasHostChildren
|
|
77
83
|
|
|
78
84
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, type RoofNode, type RoofSegmentNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, type RoofNode, type RoofSegmentNode, useScene, usePascalI18n} from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { AnimatePresence } from 'motion/react'
|
|
4
4
|
import Image from 'next/image'
|
|
@@ -9,6 +9,7 @@ import { InlineRenameInput } from './inline-rename-input'
|
|
|
9
9
|
import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
|
|
10
10
|
import { TreeNodeActions } from './tree-node-actions'
|
|
11
11
|
import { DropIndicatorLine, useTreeNodeDrag } from './tree-node-drag'
|
|
12
|
+
import { roofDefaultName, roofSegmentDefaultName } from './default-names'
|
|
12
13
|
|
|
13
14
|
interface RoofTreeNodeProps {
|
|
14
15
|
nodeId: AnyNodeId
|
|
@@ -21,6 +22,7 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
|
|
21
22
|
depth,
|
|
22
23
|
isLast,
|
|
23
24
|
}: RoofTreeNodeProps) {
|
|
25
|
+
const i18n = usePascalI18n()
|
|
24
26
|
const [isEditing, setIsEditing] = useState(false)
|
|
25
27
|
const [expanded, setExpanded] = useState(false)
|
|
26
28
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -84,7 +86,7 @@ export const RoofTreeNode = memo(function RoofTreeNode({
|
|
|
84
86
|
}, [isDropTarget, expanded])
|
|
85
87
|
|
|
86
88
|
const segmentCount = segments.length
|
|
87
|
-
const defaultName =
|
|
89
|
+
const defaultName = roofDefaultName(i18n, segmentCount)
|
|
88
90
|
|
|
89
91
|
// Hide the dragged segment from every roof while dragging
|
|
90
92
|
const visibleSegments = drag ? segments.filter((seg) => seg.id !== drag.nodeId) : segments
|
|
@@ -163,6 +165,7 @@ function RoofSegmentTreeNode({
|
|
|
163
165
|
depth: number
|
|
164
166
|
isLast?: boolean
|
|
165
167
|
}) {
|
|
168
|
+
const i18n = usePascalI18n()
|
|
166
169
|
const [isEditing, setIsEditing] = useState(false)
|
|
167
170
|
const [expanded, setExpanded] = useState(true)
|
|
168
171
|
const accessoryIds = useScene(
|
|
@@ -206,16 +209,16 @@ function RoofSegmentTreeNode({
|
|
|
206
209
|
const handlePointerDown = useCallback(
|
|
207
210
|
(e: React.PointerEvent) => {
|
|
208
211
|
if (e.button !== 0) return
|
|
209
|
-
const label =
|
|
212
|
+
const label = roofSegmentDefaultName(i18n, node.roofType, node.width, node.depth)
|
|
210
213
|
startDrag(node.id, node.type, node.parentId as string, label, e.clientX, e.clientY)
|
|
211
214
|
},
|
|
212
|
-
[node.id, node.type, node.parentId, node.roofType, node.width, node.depth, startDrag],
|
|
215
|
+
[node.id, node.type, node.parentId, node.roofType, node.width, node.depth, startDrag, i18n],
|
|
213
216
|
)
|
|
214
217
|
|
|
215
218
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
216
219
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
217
220
|
|
|
218
|
-
const defaultName =
|
|
221
|
+
const defaultName = roofSegmentDefaultName(i18n, node.roofType, node.width, node.depth)
|
|
219
222
|
|
|
220
223
|
const hasAccessories = accessoryIds.length > 0
|
|
221
224
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { type AnyNodeId, type ShelfNode, useScene } from '@pascal-app/core'
|
|
3
|
+
import { type AnyNodeId, type ShelfNode, useScene, usePascalI18n } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import Image from 'next/image'
|
|
6
6
|
import { memo, useCallback, useEffect, useState } from 'react'
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
TreeNodeWrapper,
|
|
14
14
|
} from './tree-node'
|
|
15
15
|
import { TreeNodeActions } from './tree-node-actions'
|
|
16
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
16
17
|
|
|
17
18
|
interface ShelfTreeNodeProps {
|
|
18
19
|
nodeId: ShelfNode['id']
|
|
@@ -32,6 +33,7 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
|
32
33
|
depth,
|
|
33
34
|
isLast,
|
|
34
35
|
}: ShelfTreeNodeProps) {
|
|
36
|
+
const i18n = usePascalI18n()
|
|
35
37
|
const [isEditing, setIsEditing] = useState(false)
|
|
36
38
|
const [expanded, setExpanded] = useState(true)
|
|
37
39
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -97,7 +99,7 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
|
|
|
97
99
|
isVisible={isVisible}
|
|
98
100
|
label={
|
|
99
101
|
<InlineRenameInput
|
|
100
|
-
defaultName=
|
|
102
|
+
defaultName={i18n._(nodeLabelDescriptor('shelf', 'Shelf'))}
|
|
101
103
|
isEditing={isEditing}
|
|
102
104
|
nodeId={nodeId}
|
|
103
105
|
onStartEditing={handleStartEditing}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId,
|
|
1
|
+
import { type AnyNodeId, type SlabNode, usePascalI18n, useScene } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
@@ -6,6 +6,8 @@ import { formatAreaLabel } from './../../../../../lib/measurements'
|
|
|
6
6
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
7
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
8
|
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
import { localizedFloorFootprintName } from '../../../../../i18n/floor-footprint-name'
|
|
10
|
+
import { slabDefaultName } from './default-names'
|
|
9
11
|
|
|
10
12
|
interface SlabTreeNodeProps {
|
|
11
13
|
nodeId: AnyNodeId
|
|
@@ -18,6 +20,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
18
20
|
depth,
|
|
19
21
|
isLast,
|
|
20
22
|
}: SlabTreeNodeProps) {
|
|
23
|
+
const i18n = usePascalI18n()
|
|
21
24
|
const [isEditing, setIsEditing] = useState(false)
|
|
22
25
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
23
26
|
const polygon = useScene((s) => (s.nodes[nodeId] as SlabNode | undefined)?.polygon ?? [])
|
|
@@ -30,7 +33,9 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
30
33
|
// footprint's name ("Shared floor", "Lanai floor"), opening that panel.
|
|
31
34
|
const footprint = useScene((s) => {
|
|
32
35
|
const node = s.nodes[nodeId]
|
|
33
|
-
return node?.type === 'slab' && node.plateRole === 'base'
|
|
36
|
+
return node?.type === 'slab' && node.plateRole === 'base'
|
|
37
|
+
? localizedFloorFootprintName(s.nodes, node, i18n)
|
|
38
|
+
: null
|
|
34
39
|
})
|
|
35
40
|
|
|
36
41
|
const handleClick = useCallback(
|
|
@@ -49,7 +54,7 @@ export const SlabTreeNode = memo(function SlabTreeNode({
|
|
|
49
54
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
50
55
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
51
56
|
|
|
52
|
-
const defaultName =
|
|
57
|
+
const defaultName = slabDefaultName(i18n, formatAreaLabel(calculatePolygonArea(polygon), unit))
|
|
53
58
|
|
|
54
59
|
return (
|
|
55
60
|
<TreeNodeWrapper
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, type SolarPanelNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, type SolarPanelNode, useScene, usePascalI18n } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import { memo, useCallback, useState } from 'react'
|
|
@@ -6,6 +6,7 @@ import { SnapTargetIcon } from '../../../snap-target-badge'
|
|
|
6
6
|
import { InlineRenameInput } from './inline-rename-input'
|
|
7
7
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
8
8
|
import { TreeNodeActions } from './tree-node-actions'
|
|
9
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
9
10
|
|
|
10
11
|
interface SolarPanelTreeNodeProps {
|
|
11
12
|
nodeId: AnyNodeId
|
|
@@ -18,6 +19,7 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
|
|
|
18
19
|
depth,
|
|
19
20
|
isLast,
|
|
20
21
|
}: SolarPanelTreeNodeProps) {
|
|
22
|
+
const i18n = usePascalI18n()
|
|
21
23
|
const [isEditing, setIsEditing] = useState(false)
|
|
22
24
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
23
25
|
const node = useScene((s) => s.nodes[nodeId] as SolarPanelNode | undefined)
|
|
@@ -39,7 +41,7 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
|
|
|
39
41
|
[nodeId, setSelection],
|
|
40
42
|
)
|
|
41
43
|
|
|
42
|
-
const defaultName = node?.name || 'Solar Panel'
|
|
44
|
+
const defaultName = node?.name || i18n._(nodeLabelDescriptor('solar-panel', 'Solar Panel'))
|
|
43
45
|
|
|
44
46
|
return (
|
|
45
47
|
<TreeNodeWrapper
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { type SpawnNode, useScene } from '@pascal-app/core'
|
|
3
|
+
import { type SpawnNode, useScene, usePascalI18n } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import Image from 'next/image'
|
|
6
6
|
import { memo, useCallback, useState } from 'react'
|
|
7
7
|
import { InlineRenameInput } from './inline-rename-input'
|
|
8
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
9
9
|
import { TreeNodeActions } from './tree-node-actions'
|
|
10
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
10
11
|
|
|
11
12
|
interface SpawnTreeNodeProps {
|
|
12
13
|
nodeId: SpawnNode['id']
|
|
@@ -19,6 +20,7 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
|
|
|
19
20
|
depth,
|
|
20
21
|
isLast,
|
|
21
22
|
}: SpawnTreeNodeProps) {
|
|
23
|
+
const i18n = usePascalI18n()
|
|
22
24
|
const [isEditing, setIsEditing] = useState(false)
|
|
23
25
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
24
26
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(nodeId))
|
|
@@ -60,7 +62,7 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
|
|
|
60
62
|
isVisible={isVisible}
|
|
61
63
|
label={
|
|
62
64
|
<InlineRenameInput
|
|
63
|
-
defaultName=
|
|
65
|
+
defaultName={i18n._(nodeLabelDescriptor('spawn', 'Spawn Point'))}
|
|
64
66
|
isEditing={isEditing}
|
|
65
67
|
nodeId={nodeId}
|
|
66
68
|
onStartEditing={() => setIsEditing(true)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, type StairNode, type StairSegmentNode, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, type StairNode, type StairSegmentNode, useScene, usePascalI18n} from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { AnimatePresence } from 'motion/react'
|
|
4
4
|
import Image from 'next/image'
|
|
@@ -8,6 +8,7 @@ import { InlineRenameInput } from './inline-rename-input'
|
|
|
8
8
|
import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
|
|
9
9
|
import { TreeNodeActions } from './tree-node-actions'
|
|
10
10
|
import { DropIndicatorLine, useTreeNodeDrag } from './tree-node-drag'
|
|
11
|
+
import { staircaseDefaultName, stairSegmentDefaultName } from './default-names'
|
|
11
12
|
|
|
12
13
|
interface StairTreeNodeProps {
|
|
13
14
|
nodeId: AnyNodeId
|
|
@@ -20,6 +21,7 @@ export const StairTreeNode = memo(function StairTreeNode({
|
|
|
20
21
|
depth,
|
|
21
22
|
isLast,
|
|
22
23
|
}: StairTreeNodeProps) {
|
|
24
|
+
const i18n = usePascalI18n()
|
|
23
25
|
const [isEditing, setIsEditing] = useState(false)
|
|
24
26
|
const [expanded, setExpanded] = useState(false)
|
|
25
27
|
const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
|
|
@@ -79,7 +81,7 @@ export const StairTreeNode = memo(function StairTreeNode({
|
|
|
79
81
|
}, [isDropTarget, expanded])
|
|
80
82
|
|
|
81
83
|
const segmentCount = segments.length
|
|
82
|
-
const defaultName =
|
|
84
|
+
const defaultName = staircaseDefaultName(i18n, segmentCount)
|
|
83
85
|
|
|
84
86
|
// Hide the dragged segment from every stair while dragging
|
|
85
87
|
const visibleSegments = drag ? segments.filter((seg) => seg.id !== drag.nodeId) : segments
|
|
@@ -158,6 +160,7 @@ function StairSegmentTreeNode({
|
|
|
158
160
|
depth: number
|
|
159
161
|
isLast?: boolean
|
|
160
162
|
}) {
|
|
163
|
+
const i18n = usePascalI18n()
|
|
161
164
|
const [isEditing, setIsEditing] = useState(false)
|
|
162
165
|
const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id))
|
|
163
166
|
const isHovered = useViewer((state) => state.hoveredId === node.id)
|
|
@@ -177,18 +180,16 @@ function StairSegmentTreeNode({
|
|
|
177
180
|
const handlePointerDown = useCallback(
|
|
178
181
|
(e: React.PointerEvent) => {
|
|
179
182
|
if (e.button !== 0) return
|
|
180
|
-
const
|
|
181
|
-
const label = `${typeLabel} (${node.width.toFixed(1)}×${node.length.toFixed(1)}m)`
|
|
183
|
+
const label = stairSegmentDefaultName(i18n, node.segmentType, node.width, node.length)
|
|
182
184
|
startDrag(node.id, node.type, node.parentId as string, label, e.clientX, e.clientY)
|
|
183
185
|
},
|
|
184
|
-
[node.id, node.type, node.parentId, node.segmentType, node.width, node.length, startDrag],
|
|
186
|
+
[node.id, node.type, node.parentId, node.segmentType, node.width, node.length, startDrag, i18n],
|
|
185
187
|
)
|
|
186
188
|
|
|
187
189
|
const handleStartEditing = useCallback(() => setIsEditing(true), [])
|
|
188
190
|
const handleStopEditing = useCallback(() => setIsEditing(false), [])
|
|
189
191
|
|
|
190
|
-
const
|
|
191
|
-
const defaultName = `${typeLabel} (${node.width.toFixed(1)}×${node.length.toFixed(1)}m)`
|
|
192
|
+
const defaultName = stairSegmentDefaultName(i18n, node.segmentType, node.width, node.length)
|
|
192
193
|
|
|
193
194
|
return (
|
|
194
195
|
<div data-drop-child={node.id}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNodeId, emitter, useScene } from '@pascal-app/core'
|
|
1
|
+
import { type AnyNodeId, emitter, useScene, usePascalI18n } from '@pascal-app/core'
|
|
2
2
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
3
|
import { Camera, Eye, EyeOff, Trash2 } from 'lucide-react'
|
|
4
4
|
import { memo, useState } from 'react'
|
|
@@ -13,6 +13,7 @@ interface TreeNodeActionsProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNodeActionsProps) {
|
|
16
|
+
const i18n = usePascalI18n()
|
|
16
17
|
const [open, setOpen] = useState(false)
|
|
17
18
|
const updateNode = useScene((state) => state.updateNode)
|
|
18
19
|
const updateNodes = useScene((state) => state.updateNodes)
|
|
@@ -57,7 +58,7 @@ export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNod
|
|
|
57
58
|
<button
|
|
58
59
|
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"
|
|
59
60
|
onClick={toggleVisibility}
|
|
60
|
-
title={isVisible ? 'Hide' : 'Show'}
|
|
61
|
+
title={isVisible ? i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.hide', message: 'Hide' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.show', message: 'Show' })}
|
|
61
62
|
>
|
|
62
63
|
{isVisible ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3 opacity-50" />}
|
|
63
64
|
</button>
|
|
@@ -67,7 +68,7 @@ export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNod
|
|
|
67
68
|
<button
|
|
68
69
|
className="relative 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"
|
|
69
70
|
onClick={(e) => e.stopPropagation()}
|
|
70
|
-
title=
|
|
71
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot', message: 'Camera snapshot' })}
|
|
71
72
|
>
|
|
72
73
|
<Camera className="h-3 w-3" />
|
|
73
74
|
{hasCamera && (
|
|
@@ -88,7 +89,7 @@ export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNod
|
|
|
88
89
|
onClick={handleViewCamera}
|
|
89
90
|
>
|
|
90
91
|
<Camera className="h-3.5 w-3.5" />
|
|
91
|
-
View snapshot
|
|
92
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot', message: 'View snapshot' })}
|
|
92
93
|
</button>
|
|
93
94
|
)}
|
|
94
95
|
<button
|
|
@@ -96,7 +97,7 @@ export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNod
|
|
|
96
97
|
onClick={handleCaptureCamera}
|
|
97
98
|
>
|
|
98
99
|
<Camera className="h-3.5 w-3.5" />
|
|
99
|
-
{hasCamera ? 'Update snapshot' : 'Take snapshot'}
|
|
100
|
+
{hasCamera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot', message: 'Take snapshot' })}
|
|
100
101
|
</button>
|
|
101
102
|
{hasCamera && (
|
|
102
103
|
<button
|
|
@@ -104,7 +105,7 @@ export const TreeNodeActions = memo(function TreeNodeActions({ nodeId }: TreeNod
|
|
|
104
105
|
onClick={handleClearCamera}
|
|
105
106
|
>
|
|
106
107
|
<Trash2 className="h-3.5 w-3.5" />
|
|
107
|
-
Clear snapshot
|
|
108
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot', message: 'Clear snapshot' })}
|
|
108
109
|
</button>
|
|
109
110
|
)}
|
|
110
111
|
</div>
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyNode,
|
|
3
|
+
type AnyNodeId,
|
|
4
|
+
emitter,
|
|
5
|
+
nodeRegistry,
|
|
6
|
+
usePascalI18n,
|
|
7
|
+
useScene,
|
|
8
|
+
} from '@pascal-app/core'
|
|
2
9
|
import { useViewer } from '@pascal-app/viewer'
|
|
3
10
|
import { ChevronRight } from 'lucide-react'
|
|
4
11
|
import { AnimatePresence, motion } from 'motion/react'
|
|
@@ -231,6 +238,7 @@ export const TreeNodeWrapper = forwardRef<HTMLDivElement, TreeNodeWrapperProps>(
|
|
|
231
238
|
},
|
|
232
239
|
ref,
|
|
233
240
|
) {
|
|
241
|
+
const i18n = usePascalI18n()
|
|
234
242
|
const rowRef = useRef<HTMLDivElement>(null)
|
|
235
243
|
|
|
236
244
|
useEffect(() => {
|
|
@@ -286,7 +294,14 @@ export const TreeNodeWrapper = forwardRef<HTMLDivElement, TreeNodeWrapperProps>(
|
|
|
286
294
|
{/* A row with nothing to fold keeps the space, and a click there selects the row. */}
|
|
287
295
|
{hasChildren ? (
|
|
288
296
|
<button
|
|
297
|
+
aria-expanded={expanded}
|
|
298
|
+
aria-label={
|
|
299
|
+
expanded
|
|
300
|
+
? i18n._({ id: 'editor.layers.collapse', message: 'Collapse' })
|
|
301
|
+
: i18n._({ id: 'editor.layers.expand', message: 'Expand' })
|
|
302
|
+
}
|
|
289
303
|
className="z-10 flex h-4 w-4 shrink-0 items-center justify-center bg-inherit"
|
|
304
|
+
type="button"
|
|
290
305
|
onClick={(e) => {
|
|
291
306
|
e.stopPropagation()
|
|
292
307
|
onToggle()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type AnyNodeId,
|
|
3
|
-
getLevelDisplayName,
|
|
4
3
|
type LevelNode,
|
|
5
4
|
type UnitNode,
|
|
6
5
|
unitWarnings,
|
|
7
6
|
useScene,
|
|
8
|
-
type ZoneNode,
|
|
7
|
+
type ZoneNode, usePascalI18n,
|
|
8
|
+
useLocale,
|
|
9
9
|
} from '@pascal-app/core'
|
|
10
10
|
import { useViewer } from '@pascal-app/viewer'
|
|
11
11
|
import { AlertTriangle, Trash2, X } from 'lucide-react'
|
|
@@ -15,6 +15,9 @@ import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
|
|
|
15
15
|
import { assignZoneToUnit, enterUnitFocus } from './../../../../../lib/units'
|
|
16
16
|
import { InlineRenameInput } from './inline-rename-input'
|
|
17
17
|
import { TreeNodeWrapper } from './tree-node'
|
|
18
|
+
import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
|
|
19
|
+
import { localizedLevelName } from '../../../../../i18n/level-name'
|
|
20
|
+
import { displayRoomName } from '../../../../../lib/room-name-messages'
|
|
18
21
|
|
|
19
22
|
type UnitWarningCode = ReturnType<typeof unitWarnings>[number]['code']
|
|
20
23
|
|
|
@@ -40,10 +43,12 @@ export const UnitZoneRow = memo(function UnitZoneRow({
|
|
|
40
43
|
isLast,
|
|
41
44
|
onRemove,
|
|
42
45
|
}: UnitZoneRowProps) {
|
|
46
|
+
const i18n = usePascalI18n()
|
|
47
|
+
const locale = useLocale()
|
|
43
48
|
const zone = useScene((s) => s.nodes[zoneId] as ZoneNode | undefined)
|
|
44
49
|
const levelLabel = useScene((s) => {
|
|
45
50
|
const level = zone?.parentId ? (s.nodes[zone.parentId as AnyNodeId] as LevelNode | undefined) : undefined
|
|
46
|
-
return level?.type === 'level' ?
|
|
51
|
+
return level?.type === 'level' ? localizedLevelName(level, i18n, locale) : null
|
|
47
52
|
})
|
|
48
53
|
const isSelected = useViewer((s) => s.selection.zoneId === zoneId)
|
|
49
54
|
const isHovered = useViewer((s) => s.hoveredId === zoneId)
|
|
@@ -71,7 +76,7 @@ export const UnitZoneRow = memo(function UnitZoneRow({
|
|
|
71
76
|
event.stopPropagation()
|
|
72
77
|
onRemove(zoneId)
|
|
73
78
|
}}
|
|
74
|
-
title=
|
|
79
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit', message: 'Remove from unit' })}
|
|
75
80
|
type="button"
|
|
76
81
|
>
|
|
77
82
|
<X className="h-3 w-3" />
|
|
@@ -93,7 +98,7 @@ export const UnitZoneRow = memo(function UnitZoneRow({
|
|
|
93
98
|
keepIconColor
|
|
94
99
|
label={
|
|
95
100
|
<span className="flex min-w-0 items-baseline gap-1.5">
|
|
96
|
-
<span className="truncate">{zone.name || (zone.spaceRole === 'room' ? 'Room' : 'Zone')}</span>
|
|
101
|
+
<span className="truncate">{(zone.name && displayRoomName(zone.name, i18n)) || (zone.spaceRole === 'room' ? i18n._({ id: 'editor.sidebar.panels.sitePanel.unitTreeNode.room', message: 'Room' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.unitTreeNode.zone', message: 'Zone' }))}</span>
|
|
97
102
|
{levelLabel && (
|
|
98
103
|
<span className="shrink-0 text-muted-foreground text-xs">{levelLabel}</span>
|
|
99
104
|
)}
|
|
@@ -119,6 +124,7 @@ export const UnitTreeNode = memo(function UnitTreeNode({
|
|
|
119
124
|
depth,
|
|
120
125
|
isLast,
|
|
121
126
|
}: UnitTreeNodeProps) {
|
|
127
|
+
const i18n = usePascalI18n()
|
|
122
128
|
const unitId = nodeId as UnitNode['id']
|
|
123
129
|
const [isEditing, setIsEditing] = useState(false)
|
|
124
130
|
const [expanded, setExpanded] = useState(false)
|
|
@@ -163,7 +169,7 @@ export const UnitTreeNode = memo(function UnitTreeNode({
|
|
|
163
169
|
event.stopPropagation()
|
|
164
170
|
deleteNode(unitId)
|
|
165
171
|
}}
|
|
166
|
-
title=
|
|
172
|
+
title={i18n._({ id: 'editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit', message: 'Delete unit' })}
|
|
167
173
|
type="button"
|
|
168
174
|
>
|
|
169
175
|
<Trash2 className="h-3 w-3" />
|
|
@@ -175,6 +181,7 @@ export const UnitTreeNode = memo(function UnitTreeNode({
|
|
|
175
181
|
icon={
|
|
176
182
|
<ColorDot
|
|
177
183
|
color={color ?? '#f59e0b'}
|
|
184
|
+
label={i18n._({ id: 'editor.layers.unitColour', message: 'Unit colour' })}
|
|
178
185
|
onChange={(next) => updateNode(unitId, { color: next })}
|
|
179
186
|
/>
|
|
180
187
|
}
|
|
@@ -185,7 +192,7 @@ export const UnitTreeNode = memo(function UnitTreeNode({
|
|
|
185
192
|
label={
|
|
186
193
|
<span className="flex min-w-0 items-center gap-1.5">
|
|
187
194
|
<InlineRenameInput
|
|
188
|
-
defaultName=
|
|
195
|
+
defaultName={i18n._(nodeLabelDescriptor('unit', 'Unit'))}
|
|
189
196
|
isEditing={isEditing}
|
|
190
197
|
nodeId={nodeId}
|
|
191
198
|
onStartEditing={() => setIsEditing(true)}
|
|
@@ -193,7 +200,7 @@ export const UnitTreeNode = memo(function UnitTreeNode({
|
|
|
193
200
|
/>
|
|
194
201
|
{isFocused && (
|
|
195
202
|
<span className="shrink-0 rounded-full bg-primary/15 px-1.5 text-[10px] text-primary leading-4">
|
|
196
|
-
Focused
|
|
203
|
+
{i18n._({ id: 'editor.sidebar.panels.sitePanel.unitTreeNode.focused', message: 'Focused' })}
|
|
197
204
|
</span>
|
|
198
205
|
)}
|
|
199
206
|
{warningCodes.length > 0 && (
|