@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,15 +1,20 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useScene } from '@pascal-app/core'
|
|
3
|
+
import { usePascalI18n, useScene } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
5
|
import { nodeDisplayLabel } from '../../lib/node-display-label'
|
|
6
|
+
import type { DescriptorTranslator } from '../../lib/room-name-messages'
|
|
6
7
|
import { type ViewerNodes, zoneLabel } from '../../lib/viewer-selection'
|
|
7
8
|
|
|
8
9
|
/** The hovered node's name, for the bottom pill. A room reads as the room. */
|
|
9
|
-
export function viewerHoverLabel(
|
|
10
|
+
export function viewerHoverLabel(
|
|
11
|
+
hoveredId: string | null,
|
|
12
|
+
nodes: ViewerNodes,
|
|
13
|
+
i18n: DescriptorTranslator,
|
|
14
|
+
): string | null {
|
|
10
15
|
const node = hoveredId ? nodes[hoveredId] : undefined
|
|
11
16
|
if (!node) return null
|
|
12
|
-
return node.type === 'zone' ? zoneLabel(node) : nodeDisplayLabel(node)
|
|
17
|
+
return node.type === 'zone' ? zoneLabel(node, i18n) : nodeDisplayLabel(node)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -19,9 +24,12 @@ export function viewerHoverLabel(hoveredId: string | null, nodes: ViewerNodes):
|
|
|
19
24
|
* the parametric scene store unless the surface passes its own graph.
|
|
20
25
|
*/
|
|
21
26
|
export function ViewerHoverLabel({ nodes: nodesProp }: { nodes?: ViewerNodes }) {
|
|
27
|
+
const i18n = usePascalI18n()
|
|
22
28
|
const sceneNodes = useScene((s) => s.nodes)
|
|
23
29
|
const nodes = nodesProp ?? sceneNodes
|
|
24
|
-
const label = useViewer((s) =>
|
|
30
|
+
const label = useViewer((s) =>
|
|
31
|
+
s.walkthroughMode ? null : viewerHoverLabel(s.hoveredId, nodes, i18n),
|
|
32
|
+
)
|
|
25
33
|
if (!label) return null
|
|
26
34
|
return (
|
|
27
35
|
<div
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
import {
|
|
4
4
|
type AnyNode,
|
|
5
5
|
buildUnitReport,
|
|
6
|
+
polygonInteriorPoint,
|
|
6
7
|
sceneRegistry,
|
|
8
|
+
useLocale,
|
|
9
|
+
usePascalI18n,
|
|
7
10
|
useScene,
|
|
8
11
|
type ZoneNode,
|
|
9
12
|
} from '@pascal-app/core'
|
|
10
|
-
import { useViewer } from '@pascal-app/viewer'
|
|
11
|
-
import { useEffect, useMemo } from 'react'
|
|
13
|
+
import { SelectionCallout, useViewer } from '@pascal-app/viewer'
|
|
14
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
15
|
+
import { Vector3 } from 'three'
|
|
12
16
|
import { useShallow } from 'zustand/react/shallow'
|
|
13
17
|
import { resolveRoomAssemblyHeights } from '../../lib/room-assembly-overlay'
|
|
14
18
|
import { roomFloorElevation } from '../../lib/room-handle-drag'
|
|
@@ -21,7 +25,9 @@ import {
|
|
|
21
25
|
} from '../../lib/viewer-selection'
|
|
22
26
|
import { RoomAssemblyMesh } from '../editor/room-highlight'
|
|
23
27
|
import {
|
|
28
|
+
formatRoomArea,
|
|
24
29
|
RoomLabelAnchor3D,
|
|
30
|
+
type RoomLabelEntry,
|
|
25
31
|
RoomPillChip,
|
|
26
32
|
RoomPillDetail,
|
|
27
33
|
RoomPillsLayer3D,
|
|
@@ -38,6 +44,7 @@ import {
|
|
|
38
44
|
* mounted at all, so they never catch a click.
|
|
39
45
|
*/
|
|
40
46
|
export function ViewerRooms({ source }: { source: ViewerSource }) {
|
|
47
|
+
const i18n = usePascalI18n()
|
|
41
48
|
useEffect(() => {
|
|
42
49
|
const before = useViewer.getState().showZones
|
|
43
50
|
useViewer.getState().setShowZones(false)
|
|
@@ -119,7 +126,7 @@ export function ViewerRooms({ source }: { source: ViewerSource }) {
|
|
|
119
126
|
<RoomPillDetail
|
|
120
127
|
areaM2={entry.area}
|
|
121
128
|
color={zone.color}
|
|
122
|
-
name={zoneLabel(zone)}
|
|
129
|
+
name={zoneLabel(zone, i18n)}
|
|
123
130
|
view="3d"
|
|
124
131
|
zoneId={zone.id}
|
|
125
132
|
/>
|
|
@@ -138,7 +145,7 @@ export function ViewerRooms({ source }: { source: ViewerSource }) {
|
|
|
138
145
|
areaM2={entry.area}
|
|
139
146
|
color={zone.color}
|
|
140
147
|
elements={elements}
|
|
141
|
-
name={zoneLabel(zone)}
|
|
148
|
+
name={zoneLabel(zone, i18n)}
|
|
142
149
|
onClick={() => selectViewerRoom(zone.id, source)}
|
|
143
150
|
onHover={() => useViewer.getState().setHoveredId(zone.id)}
|
|
144
151
|
onLeave={() => {
|
|
@@ -157,10 +164,60 @@ export function ViewerRooms({ source }: { source: ViewerSource }) {
|
|
|
157
164
|
{highlighted.map((room) => (
|
|
158
165
|
<ViewerRoomHighlight key={room.zoneId} room={room} source={source} />
|
|
159
166
|
))}
|
|
167
|
+
{zoneId && levelId && !walkthrough && selectedIds.length === 0
|
|
168
|
+
? (() => {
|
|
169
|
+
const entry = entries.find((candidate) => candidate.zoneId === zoneId)
|
|
170
|
+
const zone = nodes[zoneId]
|
|
171
|
+
const level = source.objectFor(levelId)
|
|
172
|
+
return entry && zone?.type === 'zone' && level ? (
|
|
173
|
+
<ViewerRoomCallout
|
|
174
|
+
entry={entry}
|
|
175
|
+
floorY={roomFloorElevation(nodes as Record<string, AnyNode>, zoneId)}
|
|
176
|
+
key={zoneId}
|
|
177
|
+
level={level}
|
|
178
|
+
name={zoneLabel(zone, i18n)}
|
|
179
|
+
/>
|
|
180
|
+
) : null
|
|
181
|
+
})()
|
|
182
|
+
: null}
|
|
160
183
|
</>
|
|
161
184
|
)
|
|
162
185
|
}
|
|
163
186
|
|
|
187
|
+
/** The selected room's callout: its name and area at a point inside it, and the close button. */
|
|
188
|
+
function ViewerRoomCallout({
|
|
189
|
+
entry,
|
|
190
|
+
floorY,
|
|
191
|
+
level,
|
|
192
|
+
name,
|
|
193
|
+
}: {
|
|
194
|
+
entry: RoomLabelEntry
|
|
195
|
+
floorY: number
|
|
196
|
+
level: NonNullable<ReturnType<ViewerSource['objectFor']>>
|
|
197
|
+
name: string
|
|
198
|
+
}) {
|
|
199
|
+
const unit = useViewer((s) => s.unit)
|
|
200
|
+
const locale = useLocale()
|
|
201
|
+
const anchor = useMemo(() => {
|
|
202
|
+
const [x, z] = polygonInteriorPoint({ polygon: entry.polygon, holes: entry.holes })
|
|
203
|
+
return new Vector3(x, floorY + 0.05, z)
|
|
204
|
+
}, [entry.polygon, entry.holes, floorY])
|
|
205
|
+
const dismiss = useCallback(() => {
|
|
206
|
+
const { selection, setSelection } = useViewer.getState()
|
|
207
|
+
if (selection.zoneId === entry.zoneId) setSelection({ zoneId: null })
|
|
208
|
+
}, [entry.zoneId])
|
|
209
|
+
return (
|
|
210
|
+
<SelectionCallout
|
|
211
|
+
actions={[]}
|
|
212
|
+
anchor={anchor}
|
|
213
|
+
object={level}
|
|
214
|
+
onDismiss={dismiss}
|
|
215
|
+
subtitle={formatRoomArea(entry.area, unit, locale)}
|
|
216
|
+
title={name}
|
|
217
|
+
/>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
164
221
|
const parametricObject = (id: string) => sceneRegistry.nodes.get(id)
|
|
165
222
|
|
|
166
223
|
/** `ViewerRooms` over the parametric scene store. */
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type LevelNode,
|
|
5
|
+
type PascalI18n,
|
|
6
|
+
sceneRegistry,
|
|
7
|
+
useLocale,
|
|
8
|
+
usePascalI18n,
|
|
9
|
+
useScene,
|
|
10
|
+
} from '@pascal-app/core'
|
|
4
11
|
import { markPerfAction, useViewer } from '@pascal-app/viewer'
|
|
5
12
|
import { ArrowLeft, ChevronRight, Layers } from 'lucide-react'
|
|
6
13
|
import Link from 'next/link'
|
|
7
14
|
import { type ReactNode, useMemo } from 'react'
|
|
15
|
+
import { localizedLevelName } from '../../i18n/level-name'
|
|
8
16
|
import { cn } from '../../lib/utils'
|
|
9
17
|
import {
|
|
10
18
|
applyViewerClick,
|
|
@@ -36,10 +44,10 @@ export type ViewerSceneHeaderProps = {
|
|
|
36
44
|
source?: ViewerSource
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
units: 'Units'
|
|
41
|
-
rooms: 'Rooms'
|
|
42
|
-
zones: 'Zones'
|
|
47
|
+
function sectionTitle(kind: ViewerSidebarSection['kind'], i18n: PascalI18n): string {
|
|
48
|
+
if (kind === 'units') return i18n._({ id: 'viewer.header.units', message: 'Units' })
|
|
49
|
+
if (kind === 'rooms') return i18n._({ id: 'viewer.header.rooms', message: 'Rooms' })
|
|
50
|
+
return i18n._({ id: 'viewer.header.zones', message: 'Zones' })
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
const parametricObject = (id: string) => sceneRegistry.nodes.get(id)
|
|
@@ -58,6 +66,9 @@ export const ViewerSceneHeader = ({
|
|
|
58
66
|
stats,
|
|
59
67
|
source: sourceProp,
|
|
60
68
|
}: ViewerSceneHeaderProps) => {
|
|
69
|
+
const i18n = usePascalI18n()
|
|
70
|
+
const catalogVersion = i18n.getVersion()
|
|
71
|
+
const locale = useLocale()
|
|
61
72
|
const sceneNodes = useScene((s) => s.nodes)
|
|
62
73
|
const source = useMemo<ViewerSource>(
|
|
63
74
|
() => sourceProp ?? { nodes: sceneNodes, objectFor: parametricObject },
|
|
@@ -70,10 +81,10 @@ export const ViewerSceneHeader = ({
|
|
|
70
81
|
|
|
71
82
|
const crumbs = useMemo(
|
|
72
83
|
() =>
|
|
73
|
-
viewerBreadcrumb(selection, focusedUnitId, nodes, (nodeId, levelId) =>
|
|
84
|
+
viewerBreadcrumb(selection, focusedUnitId, nodes, i18n, (nodeId, levelId) =>
|
|
74
85
|
roomOfObject(nodeId, levelId, source),
|
|
75
86
|
),
|
|
76
|
-
[selection, focusedUnitId, nodes, source],
|
|
87
|
+
[selection, focusedUnitId, nodes, source, i18n, locale, catalogVersion],
|
|
77
88
|
)
|
|
78
89
|
const buildingId = crumbs.find((crumb) => crumb.kind === 'building')?.id ?? null
|
|
79
90
|
// Highest first so the list reads top-down like a building section.
|
|
@@ -86,8 +97,8 @@ export const ViewerSceneHeader = ({
|
|
|
86
97
|
.sort((a, b) => b.level - a.level)
|
|
87
98
|
}, [buildingId, nodes])
|
|
88
99
|
const sections = useMemo(
|
|
89
|
-
() => (selection.levelId ? viewerSidebarSections(selection.levelId, nodes) : []),
|
|
90
|
-
[selection.levelId, nodes],
|
|
100
|
+
() => (selection.levelId ? viewerSidebarSections(selection.levelId, nodes, i18n) : []),
|
|
101
|
+
[selection.levelId, nodes, i18n, locale, catalogVersion],
|
|
91
102
|
)
|
|
92
103
|
|
|
93
104
|
const handleCrumb = (crumb: ViewerCrumb) => {
|
|
@@ -113,7 +124,7 @@ export const ViewerSceneHeader = ({
|
|
|
113
124
|
<div className="flex items-center gap-3 px-3 py-2.5">
|
|
114
125
|
{onBack ? (
|
|
115
126
|
<button
|
|
116
|
-
aria-label=
|
|
127
|
+
aria-label={i18n._({ id: 'viewer.header.back', message: 'Back' })}
|
|
117
128
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
118
129
|
onClick={onBack}
|
|
119
130
|
type="button"
|
|
@@ -122,7 +133,7 @@ export const ViewerSceneHeader = ({
|
|
|
122
133
|
</button>
|
|
123
134
|
) : backHref === null ? null : (
|
|
124
135
|
<Link
|
|
125
|
-
aria-label=
|
|
136
|
+
aria-label={i18n._({ id: 'viewer.header.back', message: 'Back' })}
|
|
126
137
|
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
127
138
|
href={backHref}
|
|
128
139
|
prefetch={false}
|
|
@@ -132,7 +143,7 @@ export const ViewerSceneHeader = ({
|
|
|
132
143
|
)}
|
|
133
144
|
<div className="min-w-0 flex-1">
|
|
134
145
|
<div className="truncate font-medium text-foreground text-sm">
|
|
135
|
-
{projectName || 'Untitled'}
|
|
146
|
+
{projectName || i18n._({ id: 'viewer.header.untitled', message: 'Untitled' })}
|
|
136
147
|
</div>
|
|
137
148
|
{owner?.username && (
|
|
138
149
|
<Link
|
|
@@ -151,7 +162,7 @@ export const ViewerSceneHeader = ({
|
|
|
151
162
|
|
|
152
163
|
{crumbs.length > 0 && (
|
|
153
164
|
<nav
|
|
154
|
-
aria-label=
|
|
165
|
+
aria-label={i18n._({ id: 'viewer.header.selection', message: 'Selection' })}
|
|
155
166
|
className="flex flex-wrap items-center gap-x-1.5 gap-y-1 border-border/40 border-t px-3 py-2 text-xs"
|
|
156
167
|
data-testid="viewer-breadcrumb"
|
|
157
168
|
>
|
|
@@ -186,7 +197,7 @@ export const ViewerSceneHeader = ({
|
|
|
186
197
|
{levels.length > 0 && (
|
|
187
198
|
<div className="corner-smooth pointer-events-auto flex min-h-0 w-56 flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 py-1 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out">
|
|
188
199
|
<span className="px-3 py-2 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
189
|
-
Levels
|
|
200
|
+
{i18n._({ id: 'viewer.header.levels', message: 'Levels' })}
|
|
190
201
|
</span>
|
|
191
202
|
<div className="flex min-h-0 flex-col overflow-y-auto">
|
|
192
203
|
{levels.map((lvl) => {
|
|
@@ -216,7 +227,7 @@ export const ViewerSceneHeader = ({
|
|
|
216
227
|
<Layers className="h-3.5 w-3.5" />
|
|
217
228
|
</span>
|
|
218
229
|
<div className="min-w-0 flex-1 truncate text-left">
|
|
219
|
-
{
|
|
230
|
+
{localizedLevelName(lvl, i18n, locale)}
|
|
220
231
|
</div>
|
|
221
232
|
</div>
|
|
222
233
|
</button>
|
|
@@ -228,7 +239,7 @@ export const ViewerSceneHeader = ({
|
|
|
228
239
|
key={section.kind}
|
|
229
240
|
>
|
|
230
241
|
<span className="block px-3 pt-2 pb-1 pl-9 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
231
|
-
{
|
|
242
|
+
{sectionTitle(section.kind, i18n)}
|
|
232
243
|
</span>
|
|
233
244
|
{section.rows.map((row) => {
|
|
234
245
|
const active =
|
|
@@ -257,7 +268,7 @@ export const ViewerSceneHeader = ({
|
|
|
257
268
|
/>
|
|
258
269
|
<span className="min-w-0 flex-1 truncate">{row.label}</span>
|
|
259
270
|
<span className="shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums">
|
|
260
|
-
{formatRoomArea(row.areaM2, unit)}
|
|
271
|
+
{formatRoomArea(row.areaM2, unit, locale)}
|
|
261
272
|
</span>
|
|
262
273
|
</button>
|
|
263
274
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
3
4
|
import { Box, Columns2, Map as MapIcon } from 'lucide-react'
|
|
4
5
|
import type { ReactNode } from 'react'
|
|
5
6
|
import { cn } from '../../lib/utils'
|
|
@@ -22,11 +23,12 @@ export function ViewerStageSwitcher({
|
|
|
22
23
|
modes,
|
|
23
24
|
onChange,
|
|
24
25
|
}: ViewerStageSwitcherProps) {
|
|
26
|
+
const i18n = usePascalI18n()
|
|
25
27
|
const enabledModes = normalizeViewerStageModes(modes)
|
|
26
28
|
|
|
27
29
|
return (
|
|
28
30
|
<div
|
|
29
|
-
aria-label=
|
|
31
|
+
aria-label={i18n._({ id: 'viewer.stage.layout', message: 'Viewer layout' })}
|
|
30
32
|
className={cn(
|
|
31
33
|
'dark absolute top-4 left-1/2 z-30 flex -translate-x-1/2 items-center gap-1 rounded-full border border-white/10 bg-neutral-950/82 p-1 text-white shadow-elevation-4 backdrop-blur-xl',
|
|
32
34
|
className,
|
|
@@ -54,7 +56,7 @@ export function ViewerStageSwitcher({
|
|
|
54
56
|
active={mode === 'split'}
|
|
55
57
|
className={hideSplitOnMobile && enabledModes.length > 1 ? 'hidden md:flex' : undefined}
|
|
56
58
|
icon={<Columns2 />}
|
|
57
|
-
label=
|
|
59
|
+
label={i18n._({ id: 'viewer.stage.split', message: 'Split' })}
|
|
58
60
|
onClick={() => onChange('split')}
|
|
59
61
|
/>
|
|
60
62
|
) : null}
|
|
@@ -36,6 +36,14 @@ export const ViewerOverlay = ({
|
|
|
36
36
|
<ViewerControlsBar
|
|
37
37
|
canShowGuides={canShowGuides}
|
|
38
38
|
canShowScans={canShowScans}
|
|
39
|
+
onDroneToggle={() => {
|
|
40
|
+
// Arm drone before first person so the controller never builds the walk's collider.
|
|
41
|
+
flushSync(() => {
|
|
42
|
+
useEditor.getState().setFirstPersonMovementMode('drone')
|
|
43
|
+
useEditor.getState().setFirstPersonMode(true)
|
|
44
|
+
})
|
|
45
|
+
requestWalkthroughPointerLock()
|
|
46
|
+
}}
|
|
39
47
|
onWalkthroughToggle={() => {
|
|
40
48
|
flushSync(() => useEditor.getState().setFirstPersonMode(true))
|
|
41
49
|
requestWalkthroughPointerLock()
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
4
|
+
import { type ReactNode, useMemo } from 'react'
|
|
5
|
+
import { displayRoomName } from '../lib/room-name-messages'
|
|
4
6
|
import { cn } from '../lib/utils'
|
|
5
7
|
import type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
6
8
|
|
|
7
9
|
export type { WalkthroughInteract } from '../store/use-first-person-hud'
|
|
8
10
|
|
|
11
|
+
/** A control shown in the HUD's key strip: its keys (as printed on the keycaps) and what they do. */
|
|
12
|
+
export type WalkthroughHint = { keys: readonly string[]; action: string }
|
|
13
|
+
|
|
9
14
|
export type WalkthroughHudProps = {
|
|
10
15
|
floorLabel?: string | null
|
|
11
16
|
zoneLabel?: string | null
|
|
12
17
|
interact?: WalkthroughInteract
|
|
18
|
+
/** Movement controls listed before the cursor and exit pills. */
|
|
19
|
+
hints?: readonly WalkthroughHint[]
|
|
13
20
|
/** Pointer lock temporarily released (OS screenshot) — the pill flips to
|
|
14
21
|
* "Click to resume" and lets clicks fall through to the canvas. */
|
|
15
22
|
suspended?: boolean
|
|
@@ -39,6 +46,7 @@ export function WalkthroughHud({
|
|
|
39
46
|
floorLabel,
|
|
40
47
|
zoneLabel,
|
|
41
48
|
interact = null,
|
|
49
|
+
hints,
|
|
42
50
|
suspended = false,
|
|
43
51
|
onExit,
|
|
44
52
|
children,
|
|
@@ -63,7 +71,7 @@ export function WalkthroughHud({
|
|
|
63
71
|
)}
|
|
64
72
|
{zoneLabel && (
|
|
65
73
|
<div className="corner-smooth rounded-full border border-border/40 bg-background/80 px-3 py-1 font-medium text-sm shadow-elevation-3 backdrop-blur-xl">
|
|
66
|
-
{zoneLabel}
|
|
74
|
+
<WalkthroughZoneName name={zoneLabel} />
|
|
67
75
|
</div>
|
|
68
76
|
)}
|
|
69
77
|
{children}
|
|
@@ -83,6 +91,22 @@ export function WalkthroughHud({
|
|
|
83
91
|
</div>
|
|
84
92
|
) : (
|
|
85
93
|
<>
|
|
94
|
+
{hints && hints.length > 0 ? (
|
|
95
|
+
<div className={pillClass}>
|
|
96
|
+
{hints.map(({ keys, action }, index) => (
|
|
97
|
+
<span className="flex items-center gap-1.5" key={action}>
|
|
98
|
+
{index > 0 && <span className="text-muted-foreground/60">·</span>}
|
|
99
|
+
{keys.map((key, keyIndex) => (
|
|
100
|
+
<span className="flex items-center gap-1" key={key}>
|
|
101
|
+
{keyIndex > 0 && <span className="text-muted-foreground/60">/</span>}
|
|
102
|
+
<kbd className={kbdClass}>{key}</kbd>
|
|
103
|
+
</span>
|
|
104
|
+
))}
|
|
105
|
+
{action}
|
|
106
|
+
</span>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
) : null}
|
|
86
110
|
<div className={pillClass}>
|
|
87
111
|
<kbd className={kbdClass}>P</kbd>
|
|
88
112
|
free cursor
|
|
@@ -118,3 +142,52 @@ export function WalkthroughHud({
|
|
|
118
142
|
</div>
|
|
119
143
|
)
|
|
120
144
|
}
|
|
145
|
+
|
|
146
|
+
/** The drone's key strip: the movement controls of the viewer's `DroneController`. */
|
|
147
|
+
function useDroneHints(): readonly WalkthroughHint[] {
|
|
148
|
+
const i18n = usePascalI18n()
|
|
149
|
+
return useMemo(
|
|
150
|
+
() => [
|
|
151
|
+
{
|
|
152
|
+
keys: ['WASD'],
|
|
153
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.move', message: 'move' }),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
keys: ['Space', 'E'],
|
|
157
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.up', message: 'up' }),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
keys: ['Q'],
|
|
161
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.down', message: 'down' }),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
keys: ['Shift'],
|
|
165
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.boost', message: 'boost' }),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
keys: ['Alt'],
|
|
169
|
+
action: i18n._({ id: 'editor.walkthroughHud.drone.slow', message: 'slow' }),
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
[i18n],
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The walkthrough HUD for the drone: the plain pointer dot and the flight keys, no room or door prompts. */
|
|
177
|
+
export function DroneHud({
|
|
178
|
+
suspended,
|
|
179
|
+
onExit,
|
|
180
|
+
children,
|
|
181
|
+
}: Pick<WalkthroughHudProps, 'suspended' | 'onExit' | 'children'>) {
|
|
182
|
+
return (
|
|
183
|
+
<WalkthroughHud hints={useDroneHints()} onExit={onExit} suspended={suspended}>
|
|
184
|
+
{children}
|
|
185
|
+
</WalkthroughHud>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The room the walker is in, in the UI language (the scene keeps its stored name). */
|
|
190
|
+
function WalkthroughZoneName({ name }: { name: string }) {
|
|
191
|
+
const i18n = usePascalI18n()
|
|
192
|
+
return displayRoomName(name, i18n)
|
|
193
|
+
}
|
|
@@ -18,7 +18,9 @@ import { useSidebarStore } from '../components/ui/primitives/sidebar'
|
|
|
18
18
|
import { runRedo, runUndo } from '../lib/history'
|
|
19
19
|
import { meshEditScope } from '../lib/interaction/scope'
|
|
20
20
|
import { setSidebarTabIds } from '../lib/sidebar-panel'
|
|
21
|
-
import
|
|
21
|
+
import { armToolShortcut, toolShortcutForKey, withToolShortcut } from '../lib/tool-shortcuts'
|
|
22
|
+
import { getWallDrawVariant, selectWallDrawVariant } from '../lib/wall-draw-variant'
|
|
23
|
+
import useEditor, { normalizePersistedEditorLayoutState } from '../store/use-editor'
|
|
22
24
|
import useInteractionScope from '../store/use-interaction-scope'
|
|
23
25
|
import {
|
|
24
26
|
armPaintFromShortcut,
|
|
@@ -164,6 +166,14 @@ describe('rotation shortcut ownership', () => {
|
|
|
164
166
|
expect(isToolOwnedRotation()).toBe(true)
|
|
165
167
|
})
|
|
166
168
|
|
|
169
|
+
test('leaves R and T, and so Shift+R, to the stair and elevator placement tools', () => {
|
|
170
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'stair' })
|
|
171
|
+
expect(isToolOwnedRotation()).toBe(true)
|
|
172
|
+
|
|
173
|
+
useEditor.getState().armToolMode({ mode: 'build', tool: 'elevator' })
|
|
174
|
+
expect(isToolOwnedRotation()).toBe(true)
|
|
175
|
+
})
|
|
176
|
+
|
|
167
177
|
test('does not reserve R for the wall tool: its shape is picked in the Build panel', () => {
|
|
168
178
|
useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
|
|
169
179
|
|
|
@@ -418,20 +428,35 @@ describe('B and P open their panel', () => {
|
|
|
418
428
|
expect(useSidebarStore.getState().isCollapsed).toBe(false)
|
|
419
429
|
})
|
|
420
430
|
|
|
421
|
-
test('B arms the wall tool on
|
|
431
|
+
test('B arms the wall tool on the Rooms shape picked last and shows the Build panel', () => {
|
|
422
432
|
setSidebarTabIds(['site', 'build', 'paint', 'items'])
|
|
423
433
|
useEditor.getState().setActiveSidebarPanel('items')
|
|
424
434
|
useSidebarStore.getState().setIsCollapsed(true)
|
|
425
|
-
|
|
435
|
+
selectWallDrawVariant('walls')
|
|
436
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
426
437
|
|
|
427
438
|
armWallToolFromShortcut()
|
|
428
439
|
|
|
429
440
|
const editor = useEditor.getState()
|
|
430
441
|
expect(editor.mode).toBe('build')
|
|
431
442
|
expect(editor.tool).toBe('wall')
|
|
432
|
-
expect(
|
|
443
|
+
expect(getWallDrawVariant()).toBe('walls')
|
|
433
444
|
expect(editor.activeSidebarPanel).toBe('build')
|
|
434
445
|
expect(useSidebarStore.getState().isCollapsed).toBe(false)
|
|
446
|
+
// The pick is a preference: it comes back with the saved editor layout.
|
|
447
|
+
expect(
|
|
448
|
+
normalizePersistedEditorLayoutState({ continuationByContext: { wall: 'single' } } as never)
|
|
449
|
+
.continuationByContext.wall,
|
|
450
|
+
).toBe('single')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
test('B draws a Rectangle until a Rooms shape is picked', () => {
|
|
454
|
+
expect(normalizePersistedEditorLayoutState({}).continuationByContext.wall).toBe('rectangle')
|
|
455
|
+
useEditor.setState({
|
|
456
|
+
continuationByContext: normalizePersistedEditorLayoutState({}).continuationByContext,
|
|
457
|
+
})
|
|
458
|
+
armWallToolFromShortcut()
|
|
459
|
+
expect(getWallDrawVariant()).toBe('rectangle')
|
|
435
460
|
})
|
|
436
461
|
|
|
437
462
|
// A host may drop the armed tool when the panel changes (the community does,
|
|
@@ -473,3 +498,59 @@ describe('B and P open their panel', () => {
|
|
|
473
498
|
expect(useEditor.getState().activeSidebarPanel).toBe('build')
|
|
474
499
|
})
|
|
475
500
|
})
|
|
501
|
+
|
|
502
|
+
describe('drafting tool shortcuts', () => {
|
|
503
|
+
const press = (code: string, mods: Partial<KeyboardEvent> = {}) =>
|
|
504
|
+
toolShortcutForKey({
|
|
505
|
+
code,
|
|
506
|
+
shiftKey: false,
|
|
507
|
+
metaKey: false,
|
|
508
|
+
ctrlKey: false,
|
|
509
|
+
altKey: false,
|
|
510
|
+
repeat: false,
|
|
511
|
+
...mods,
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
afterEach(() => {
|
|
515
|
+
setSidebarTabIds([])
|
|
516
|
+
useInteractionScope.getState().endIf(() => true)
|
|
517
|
+
useEditor.getState().setContinuation('wall', 'room')
|
|
518
|
+
useEditor.getState().armToolMode({ mode: 'select' })
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
test('keep the bare keys they would collide with: pan, rotate and operate', () => {
|
|
522
|
+
expect(press('KeyD', { shiftKey: true })).toBe('door')
|
|
523
|
+
expect(press('KeyW', { shiftKey: true })).toBe('window')
|
|
524
|
+
expect(press('KeyS', { shiftKey: true })).toBe('stair')
|
|
525
|
+
expect(press('KeyR', { shiftKey: true })).toBe('roof')
|
|
526
|
+
expect(press('KeyE', { shiftKey: true })).toBeNull()
|
|
527
|
+
for (const code of ['KeyD', 'KeyW', 'KeyS', 'KeyR', 'KeyE', 'KeyI', 'KeyA'])
|
|
528
|
+
expect(press(code)).toBeNull()
|
|
529
|
+
expect(press('KeyD', { shiftKey: true, ctrlKey: true })).toBeNull()
|
|
530
|
+
expect(press('KeyD', { shiftKey: true, repeat: true })).toBeNull()
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
test('stand down while a gesture holds Shift', () => {
|
|
534
|
+
useInteractionScope.getState().begin({ kind: 'box-select' })
|
|
535
|
+
expect(press('KeyD', { shiftKey: true })).toBeNull()
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
test('arm their tool with the Build panel showing, and name their key in tooltips', () => {
|
|
539
|
+
setSidebarTabIds(['build', 'items'])
|
|
540
|
+
useEditor.getState().setActiveSidebarPanel('items')
|
|
541
|
+
for (const [id, tool] of [
|
|
542
|
+
['door', 'door'],
|
|
543
|
+
['window', 'window'],
|
|
544
|
+
['stair', 'stair'],
|
|
545
|
+
['roof', 'roof'],
|
|
546
|
+
] as const) {
|
|
547
|
+
armToolShortcut(id)
|
|
548
|
+
expect(useEditor.getState().mode).toBe('build')
|
|
549
|
+
expect(useEditor.getState().tool).toBe(tool)
|
|
550
|
+
expect(useEditor.getState().activeSidebarPanel).toBe('build')
|
|
551
|
+
}
|
|
552
|
+
expect(withToolShortcut('Door', 'door')).toBe('Door (Shift+D)')
|
|
553
|
+
expect(withToolShortcut('Wall', 'wall')).toBe('Wall (B)')
|
|
554
|
+
expect(withToolShortcut('Fence', 'fence')).toBe('Fence')
|
|
555
|
+
})
|
|
556
|
+
})
|