@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
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
4
|
import {
|
|
5
5
|
acquireSceneReadOnlyLease,
|
|
6
|
+
createI18n,
|
|
6
7
|
getCatalogMaterialById,
|
|
7
8
|
getLibraryMaterialIdFromRef,
|
|
9
|
+
getMaterialSwatch,
|
|
8
10
|
getSceneMaterialIdFromRef,
|
|
9
11
|
initSpaceDetectionSync,
|
|
10
12
|
initSpatialGridSync,
|
|
13
|
+
type MessageDescriptor,
|
|
14
|
+
PascalI18nProvider,
|
|
11
15
|
spatialGridManager,
|
|
16
|
+
usePascalI18n,
|
|
12
17
|
useScene,
|
|
13
18
|
} from '@pascal-app/core'
|
|
14
19
|
import {
|
|
@@ -32,6 +37,7 @@ import {
|
|
|
32
37
|
useCallback,
|
|
33
38
|
useEffect,
|
|
34
39
|
useLayoutEffect,
|
|
40
|
+
useMemo,
|
|
35
41
|
useRef,
|
|
36
42
|
useState,
|
|
37
43
|
} from 'react'
|
|
@@ -41,6 +47,7 @@ import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
|
|
41
47
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
|
42
48
|
import { useIsMobile } from '../../hooks/use-mobile'
|
|
43
49
|
import { useSaveShortcut } from '../../hooks/use-save-shortcut'
|
|
50
|
+
import { type HINT_KEY_NAMES, toolHintKey, toolHintKeyDescriptor } from '../../i18n/node-messages'
|
|
44
51
|
import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
|
|
45
52
|
import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
|
|
46
53
|
import { isSceneView } from '../../lib/editor-views'
|
|
@@ -71,7 +78,7 @@ import useSessionGroups from '../../store/use-session-groups'
|
|
|
71
78
|
import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
|
|
72
79
|
import { PointAskLayer } from '../point-ask/point-ask-layer'
|
|
73
80
|
import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
|
|
74
|
-
import { CeilingSystem } from '../systems/ceiling/ceiling-system'
|
|
81
|
+
import { CeilingSystem, CeilingTopsSync } from '../systems/ceiling/ceiling-system'
|
|
75
82
|
import { RoofEditSystem } from '../systems/roof/roof-edit-system'
|
|
76
83
|
import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
|
|
77
84
|
import { StairEditSystem } from '../systems/stair/stair-edit-system'
|
|
@@ -96,6 +103,8 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
|
|
|
96
103
|
import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
|
|
97
104
|
import type { SidebarTab } from '../ui/sidebar/tab-bar'
|
|
98
105
|
import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
|
|
106
|
+
import { SKY_DOCK_HEIGHT, SkyControl } from '../viewer/sky-control'
|
|
107
|
+
import { useViewerUiSounds } from '../viewer/use-viewer-ui-sounds'
|
|
99
108
|
import { ViewerHoverLabel } from '../viewer/viewer-hover-label'
|
|
100
109
|
import { ParametricViewerRooms } from '../viewer/viewer-rooms'
|
|
101
110
|
import { ViewerSelectionManager } from '../viewer/viewer-selection-manager'
|
|
@@ -202,6 +211,8 @@ function initializeEditorRuntime(): () => void {
|
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
213
|
export interface EditorProps {
|
|
214
|
+
/** UI locale. Hosts may also call `setLocale` before mounting. */
|
|
215
|
+
locale?: string
|
|
205
216
|
// Layout version — 'v1' (default) or 'v2' (navbar + two-column)
|
|
206
217
|
layoutVersion?: 'v1' | 'v2'
|
|
207
218
|
// Viewport edge the tool menu docks to (desktop only; mobile stays at the bottom)
|
|
@@ -315,12 +326,22 @@ export interface EditorProps {
|
|
|
315
326
|
}
|
|
316
327
|
|
|
317
328
|
function EditorSceneCrashFallback() {
|
|
329
|
+
const i18n = usePascalI18n()
|
|
318
330
|
return (
|
|
319
331
|
<div className="fixed inset-0 z-80 flex items-center justify-center bg-background/95 p-4 text-foreground">
|
|
320
332
|
<div className="w-full max-w-md rounded-2xl border border-border/60 bg-background p-6 shadow-xl">
|
|
321
|
-
<h2 className="font-semibold text-lg">
|
|
333
|
+
<h2 className="font-semibold text-lg">
|
|
334
|
+
{i18n._({
|
|
335
|
+
id: 'editor.theEditorSceneFailedToRender',
|
|
336
|
+
message: 'The editor scene failed to render',
|
|
337
|
+
})}
|
|
338
|
+
</h2>
|
|
322
339
|
<p className="mt-2 text-muted-foreground text-sm">
|
|
323
|
-
|
|
340
|
+
{i18n._({
|
|
341
|
+
id: 'editor.youCanRetryTheSceneOr',
|
|
342
|
+
message:
|
|
343
|
+
'You can retry the scene or return home without reloading the whole app shell.',
|
|
344
|
+
})}
|
|
324
345
|
</p>
|
|
325
346
|
<div className="mt-4 flex items-center gap-2">
|
|
326
347
|
<button
|
|
@@ -328,13 +349,13 @@ function EditorSceneCrashFallback() {
|
|
|
328
349
|
onClick={() => window.location.reload()}
|
|
329
350
|
type="button"
|
|
330
351
|
>
|
|
331
|
-
Reload editor
|
|
352
|
+
{i18n._({ id: 'editor.reloadEditor', message: 'Reload editor' })}
|
|
332
353
|
</button>
|
|
333
354
|
<a
|
|
334
355
|
className="rounded-md border border-border bg-background px-3 py-2 font-medium text-sm hover:bg-accent/40"
|
|
335
356
|
href="/"
|
|
336
357
|
>
|
|
337
|
-
Back to home
|
|
358
|
+
{i18n._({ id: 'editor.backToHome', message: 'Back to home' })}
|
|
338
359
|
</a>
|
|
339
360
|
</div>
|
|
340
361
|
</div>
|
|
@@ -345,6 +366,7 @@ function EditorSceneCrashFallback() {
|
|
|
345
366
|
// ── Sidebar slot: in-flow, resizable, collapses to a grab strip ──────────────
|
|
346
367
|
|
|
347
368
|
function SidebarSlot({ children }: { children: ReactNode }) {
|
|
369
|
+
const i18n = usePascalI18n()
|
|
348
370
|
const width = useSidebarStore((s) => s.width)
|
|
349
371
|
const isCollapsed = useSidebarStore((s) => s.isCollapsed)
|
|
350
372
|
const setIsCollapsed = useSidebarStore((s) => s.setIsCollapsed)
|
|
@@ -416,7 +438,7 @@ function SidebarSlot({ children }: { children: ReactNode }) {
|
|
|
416
438
|
<div
|
|
417
439
|
className="absolute inset-0 z-10 cursor-col-resize transition-colors hover:bg-primary/20"
|
|
418
440
|
onPointerDown={handleGrabDown}
|
|
419
|
-
title=
|
|
441
|
+
title={i18n._({ id: 'editor.expandSidebar', message: 'Expand sidebar' })}
|
|
420
442
|
/>
|
|
421
443
|
) : (
|
|
422
444
|
children
|
|
@@ -494,26 +516,29 @@ const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
|
|
|
494
516
|
{ action: 'Zoom', keys: [{ value: 'Scroll' }] },
|
|
495
517
|
]
|
|
496
518
|
|
|
497
|
-
const CAMERA_SHORTCUT_KEY_META: Record<
|
|
519
|
+
const CAMERA_SHORTCUT_KEY_META: Record<
|
|
520
|
+
string,
|
|
521
|
+
{ icon?: string; label: MessageDescriptor; text?: string }
|
|
522
|
+
> = {
|
|
498
523
|
'Left click': {
|
|
499
524
|
icon: 'ph:mouse-left-click-fill',
|
|
500
|
-
label: 'Left click',
|
|
525
|
+
label: hintKeyName('Left click'),
|
|
501
526
|
},
|
|
502
527
|
'Middle click': {
|
|
503
528
|
icon: 'qlementine-icons:mouse-middle-button-16',
|
|
504
|
-
label: 'Middle click',
|
|
529
|
+
label: hintKeyName('Middle click'),
|
|
505
530
|
},
|
|
506
531
|
'Right click': {
|
|
507
532
|
icon: 'ph:mouse-right-click-fill',
|
|
508
|
-
label: 'Right click',
|
|
533
|
+
label: hintKeyName('Right click'),
|
|
509
534
|
},
|
|
510
535
|
Scroll: {
|
|
511
536
|
icon: 'qlementine-icons:mouse-middle-button-16',
|
|
512
|
-
label: 'Scroll wheel',
|
|
537
|
+
label: hintKeyName('Scroll wheel'),
|
|
513
538
|
},
|
|
514
539
|
Space: {
|
|
515
540
|
icon: 'lucide:space',
|
|
516
|
-
label: 'Space',
|
|
541
|
+
label: hintKeyName('Space'),
|
|
517
542
|
},
|
|
518
543
|
}
|
|
519
544
|
|
|
@@ -544,8 +569,21 @@ function writeCameraControlsHintDismissed(dismissed: boolean) {
|
|
|
544
569
|
} catch {}
|
|
545
570
|
}
|
|
546
571
|
|
|
572
|
+
/** Key names shared with the tool HUD (`editor.toolHint.key.*`). */
|
|
573
|
+
function hintKeyName(key: (typeof HINT_KEY_NAMES)[number]): MessageDescriptor {
|
|
574
|
+
return toolHintKeyDescriptor(key) ?? { id: key, message: key }
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const CAMERA_HINT_ACTION_LABELS: Record<CameraHintAction, MessageDescriptor> = {
|
|
578
|
+
Pan: /* i18n */ { id: 'editor.cameraHint.pan', message: 'Pan' },
|
|
579
|
+
Rotate: /* i18n */ { id: 'editor.cameraHint.rotate', message: 'Rotate' },
|
|
580
|
+
Zoom: /* i18n */ { id: 'editor.cameraHint.zoom', message: 'Zoom' },
|
|
581
|
+
}
|
|
582
|
+
|
|
547
583
|
function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
|
|
548
|
-
const
|
|
584
|
+
const i18n = usePascalI18n()
|
|
585
|
+
const entry = CAMERA_SHORTCUT_KEY_META[shortcutKey.value]
|
|
586
|
+
const meta = entry ? { ...entry, label: i18n._(entry.label) } : undefined
|
|
549
587
|
|
|
550
588
|
if (meta?.icon) {
|
|
551
589
|
return (
|
|
@@ -563,7 +601,7 @@ function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
|
|
|
563
601
|
|
|
564
602
|
return (
|
|
565
603
|
<span className="font-medium text-[11px] text-foreground/90">
|
|
566
|
-
{meta?.text ?? shortcutKey.value}
|
|
604
|
+
{meta?.text ?? toolHintKey(shortcutKey.value, i18n)}
|
|
567
605
|
</span>
|
|
568
606
|
)
|
|
569
607
|
}
|
|
@@ -582,10 +620,11 @@ function ShortcutSequence({ keys }: { keys: ShortcutKey[] }) {
|
|
|
582
620
|
}
|
|
583
621
|
|
|
584
622
|
function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
|
|
623
|
+
const i18n = usePascalI18n()
|
|
585
624
|
return (
|
|
586
625
|
<div className="flex min-w-0 flex-col items-center gap-1.5 px-4 text-center first:pl-0 last:pr-0">
|
|
587
626
|
<span className="font-medium text-[10px] text-muted-foreground/60 tracking-[0.03em]">
|
|
588
|
-
{hint.action}
|
|
627
|
+
{i18n._(CAMERA_HINT_ACTION_LABELS[hint.action])}
|
|
589
628
|
</span>
|
|
590
629
|
<div className="flex flex-wrap items-center justify-center gap-1.5">
|
|
591
630
|
<ShortcutSequence keys={hint.keys} />
|
|
@@ -607,6 +646,7 @@ function ViewerCanvasControlsHint({
|
|
|
607
646
|
belowTopMenu?: boolean
|
|
608
647
|
onDismiss: () => void
|
|
609
648
|
}) {
|
|
649
|
+
const i18n = usePascalI18n()
|
|
610
650
|
const all = CAMERA_CONTROL_HINTS
|
|
611
651
|
// A host teaching one gesture at a time narrows this to the one it is asking
|
|
612
652
|
// for, and to nothing once it is done. Null — the default — is all of them.
|
|
@@ -626,7 +666,7 @@ function ViewerCanvasControlsHint({
|
|
|
626
666
|
style={belowTopMenu ? { top: TOP_MENU_CLEARANCE } : undefined}
|
|
627
667
|
>
|
|
628
668
|
<section
|
|
629
|
-
aria-label=
|
|
669
|
+
aria-label={i18n._({ id: 'editor.cameraControlsHint', message: 'Camera controls hint' })}
|
|
630
670
|
className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-elevation-4 backdrop-blur-xl"
|
|
631
671
|
>
|
|
632
672
|
<div
|
|
@@ -640,7 +680,10 @@ function ViewerCanvasControlsHint({
|
|
|
640
680
|
<Tooltip>
|
|
641
681
|
<TooltipTrigger asChild>
|
|
642
682
|
<button
|
|
643
|
-
aria-label=
|
|
683
|
+
aria-label={i18n._({
|
|
684
|
+
id: 'editor.dismissCameraControlsHint',
|
|
685
|
+
message: 'Dismiss camera controls hint',
|
|
686
|
+
})}
|
|
644
687
|
className="flex h-5 shrink-0 items-center justify-center self-center border-border/18 border-l pl-3 text-muted-foreground/70 transition-colors hover:text-foreground"
|
|
645
688
|
onClick={onDismiss}
|
|
646
689
|
type="button"
|
|
@@ -655,7 +698,7 @@ function ViewerCanvasControlsHint({
|
|
|
655
698
|
</button>
|
|
656
699
|
</TooltipTrigger>
|
|
657
700
|
<TooltipContent side="bottom" sideOffset={8}>
|
|
658
|
-
Dismiss
|
|
701
|
+
{i18n._({ id: 'editor.dismiss', message: 'Dismiss' })}
|
|
659
702
|
</TooltipContent>
|
|
660
703
|
</Tooltip>
|
|
661
704
|
</section>
|
|
@@ -733,7 +776,17 @@ function getActivePaintMaterialSwatchImageUrl(
|
|
|
733
776
|
const catalogId =
|
|
734
777
|
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
735
778
|
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
736
|
-
return catalogMaterial
|
|
779
|
+
return catalogMaterial ? (getMaterialSwatch(catalogMaterial).imageUrl ?? undefined) : undefined
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** Whether the badge's image is a white albedo its colour tints (a siding's profile). */
|
|
783
|
+
function isActivePaintMaterialSwatchTinted(material: ActivePaintMaterial | null) {
|
|
784
|
+
if (material?.material?.texture?.url || getSceneMaterialIdFromRef(material?.materialPreset))
|
|
785
|
+
return false
|
|
786
|
+
const catalogId =
|
|
787
|
+
getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
|
|
788
|
+
const catalogMaterial = getCatalogMaterialById(catalogId)
|
|
789
|
+
return catalogMaterial ? getMaterialSwatch(catalogMaterial).tinted : false
|
|
737
790
|
}
|
|
738
791
|
|
|
739
792
|
function PaintCursorBadge({
|
|
@@ -741,6 +794,7 @@ function PaintCursorBadge({
|
|
|
741
794
|
state,
|
|
742
795
|
swatchColor,
|
|
743
796
|
swatchImageUrl,
|
|
797
|
+
swatchTinted = false,
|
|
744
798
|
isEraser,
|
|
745
799
|
isPicker = false,
|
|
746
800
|
}: {
|
|
@@ -748,6 +802,8 @@ function PaintCursorBadge({
|
|
|
748
802
|
state: PaintCursorBadgeState
|
|
749
803
|
swatchColor: string
|
|
750
804
|
swatchImageUrl?: string
|
|
805
|
+
/** Multiply the image by the colour (a tinted albedo). */
|
|
806
|
+
swatchTinted?: boolean
|
|
751
807
|
isEraser: boolean
|
|
752
808
|
/** The eyedropper: the swatch is what a click would take. */
|
|
753
809
|
isPicker?: boolean
|
|
@@ -821,6 +877,7 @@ function PaintCursorBadge({
|
|
|
821
877
|
backgroundImage: swatchImageUrl
|
|
822
878
|
? `url(${JSON.stringify(swatchImageUrl)})`
|
|
823
879
|
: undefined,
|
|
880
|
+
backgroundBlendMode: swatchImageUrl && swatchTinted ? 'multiply' : undefined,
|
|
824
881
|
opacity: state === 'aim' ? 0.55 : 1,
|
|
825
882
|
}}
|
|
826
883
|
/>
|
|
@@ -1084,6 +1141,7 @@ function PaintCursorLayer({
|
|
|
1084
1141
|
const swatchMaterial = picking ? picked : activePaintMaterial
|
|
1085
1142
|
const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials)
|
|
1086
1143
|
const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials)
|
|
1144
|
+
const swatchTinted = isActivePaintMaterialSwatchTinted(swatchMaterial)
|
|
1087
1145
|
|
|
1088
1146
|
if (!active || !position) return null
|
|
1089
1147
|
|
|
@@ -1099,6 +1157,7 @@ function PaintCursorLayer({
|
|
|
1099
1157
|
state={badgeState}
|
|
1100
1158
|
swatchColor={swatchColor}
|
|
1101
1159
|
swatchImageUrl={swatchImageUrl}
|
|
1160
|
+
swatchTinted={swatchTinted}
|
|
1102
1161
|
/>
|
|
1103
1162
|
</div>
|
|
1104
1163
|
)
|
|
@@ -1150,6 +1209,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1150
1209
|
const views = useEditorViews()
|
|
1151
1210
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
|
1152
1211
|
const isCaptureMode = useEditor((s) => s.isCaptureMode)
|
|
1212
|
+
const textures = useViewer((s) => s.textures)
|
|
1213
|
+
const isMobile = useIsMobile()
|
|
1214
|
+
const planOnRight = layout.split && layout.panes[1] === VIEW_2D
|
|
1153
1215
|
useUnitFocusRules()
|
|
1154
1216
|
const wholeBuilding = useEditor((s) =>
|
|
1155
1217
|
showsWholeBuilding({
|
|
@@ -1319,6 +1381,22 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1319
1381
|
viewerSceneSlot={viewerSceneSlot}
|
|
1320
1382
|
/>
|
|
1321
1383
|
</Viewer>
|
|
1384
|
+
{!showLoader &&
|
|
1385
|
+
textures &&
|
|
1386
|
+
!isMobile &&
|
|
1387
|
+
!(
|
|
1388
|
+
isFirstPersonMode ||
|
|
1389
|
+
isStudioMode ||
|
|
1390
|
+
isVersionPreviewMode ||
|
|
1391
|
+
isCaptureMode ||
|
|
1392
|
+
isPreviewMode
|
|
1393
|
+
) ? (
|
|
1394
|
+
// Beside the compass (which moves to the plan's corner when the plan is on the right).
|
|
1395
|
+
<SkyControl
|
|
1396
|
+
height={SKY_DOCK_HEIGHT.editor}
|
|
1397
|
+
style={{ left: planOnRight ? 12 : 52 }}
|
|
1398
|
+
/>
|
|
1399
|
+
) : null}
|
|
1322
1400
|
</div>
|
|
1323
1401
|
{showLoader && loadingOverlay}
|
|
1324
1402
|
</div>
|
|
@@ -1327,6 +1405,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1327
1405
|
const viewId = layout.panes[pane]
|
|
1328
1406
|
return (
|
|
1329
1407
|
<div
|
|
1408
|
+
aria-busy={isSceneDataLoading}
|
|
1330
1409
|
className="relative min-w-0 overflow-hidden"
|
|
1331
1410
|
data-view-pane={pane}
|
|
1332
1411
|
key={viewId}
|
|
@@ -1335,6 +1414,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
|
|
|
1335
1414
|
style={paneStyle(pane)}
|
|
1336
1415
|
>
|
|
1337
1416
|
<HostView key={viewId} view={views.find((view) => view.id === viewId)} />
|
|
1417
|
+
{/* A restored pane's plugin is known only once the project has loaded: until
|
|
1418
|
+
then it would read "This view isn't available". */}
|
|
1419
|
+
{isSceneDataLoading && loadingOverlay}
|
|
1338
1420
|
</div>
|
|
1339
1421
|
)
|
|
1340
1422
|
})}
|
|
@@ -1384,6 +1466,7 @@ function PreviewStage({
|
|
|
1384
1466
|
)
|
|
1385
1467
|
|
|
1386
1468
|
useViewerFloorDisplay()
|
|
1469
|
+
useViewerUiSounds()
|
|
1387
1470
|
const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
|
|
1388
1471
|
const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
|
|
1389
1472
|
|
|
@@ -1714,6 +1797,13 @@ function EditorContent({
|
|
|
1714
1797
|
}
|
|
1715
1798
|
}, [isFirstPersonMode, isPreviewMode])
|
|
1716
1799
|
|
|
1800
|
+
// The viewer's walkthrough says how the first-person camera moves, for the
|
|
1801
|
+
// viewer-side consumers that tell a walk from a drone flight.
|
|
1802
|
+
const firstPersonMovementMode = useEditor((s) => s.firstPersonMovementMode)
|
|
1803
|
+
useEffect(() => {
|
|
1804
|
+
if (isFirstPersonMode) useViewer.getState().setWalkthroughMovement(firstPersonMovementMode)
|
|
1805
|
+
}, [isFirstPersonMode, firstPersonMovementMode])
|
|
1806
|
+
|
|
1717
1807
|
const previewViewerContent = (
|
|
1718
1808
|
<Viewer
|
|
1719
1809
|
defaultRender={EDITOR_DEFAULT_RENDER}
|
|
@@ -1826,6 +1916,7 @@ function EditorContent({
|
|
|
1826
1916
|
return (
|
|
1827
1917
|
<>
|
|
1828
1918
|
<FloorplanModeCoordinator />
|
|
1919
|
+
<CeilingTopsSync />
|
|
1829
1920
|
{!isLoading && isPreviewMode ? (
|
|
1830
1921
|
<PreviewStage
|
|
1831
1922
|
isFirstPersonMode={isFirstPersonMode}
|
|
@@ -1912,6 +2003,7 @@ function EditorContent({
|
|
|
1912
2003
|
return (
|
|
1913
2004
|
<div className="flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
|
|
1914
2005
|
<FloorplanModeCoordinator />
|
|
2006
|
+
<CeilingTopsSync />
|
|
1915
2007
|
{!isLoading && isPreviewMode ? (
|
|
1916
2008
|
<PreviewStage
|
|
1917
2009
|
isFirstPersonMode={isFirstPersonMode}
|
|
@@ -1960,10 +2052,28 @@ function EditorContent({
|
|
|
1960
2052
|
)
|
|
1961
2053
|
}
|
|
1962
2054
|
|
|
1963
|
-
|
|
2055
|
+
function EditorFrame(props: EditorProps) {
|
|
1964
2056
|
return (
|
|
1965
2057
|
<Profiler id="editor" onRender={recordEditorRender}>
|
|
1966
2058
|
<EditorContent {...props} />
|
|
1967
2059
|
</Profiler>
|
|
1968
2060
|
)
|
|
1969
2061
|
}
|
|
2062
|
+
|
|
2063
|
+
function LocalizedEditor(props: EditorProps & { locale: string }) {
|
|
2064
|
+
const i18n = useMemo(() => createI18n({ locale: props.locale }), [props.locale])
|
|
2065
|
+
|
|
2066
|
+
return (
|
|
2067
|
+
<PascalI18nProvider i18n={i18n}>
|
|
2068
|
+
<EditorFrame {...props} />
|
|
2069
|
+
</PascalI18nProvider>
|
|
2070
|
+
)
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
export default function Editor(props: EditorProps) {
|
|
2074
|
+
return props.locale ? (
|
|
2075
|
+
<LocalizedEditor {...props} locale={props.locale} />
|
|
2076
|
+
) : (
|
|
2077
|
+
<EditorFrame {...props} />
|
|
2078
|
+
)
|
|
2079
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
4
|
+
import { openExtension } from '../../lib/inspector-card-mode'
|
|
5
|
+
import { useInspectorExpanded } from '../../lib/inspector-expanded'
|
|
6
|
+
import { renderExtensionIcon, useInspectorExtensions } from '../ui/panels/inspector-extensions'
|
|
7
|
+
import { ActionMenuButton } from './action-menu-button'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One action-menu button per plugin inspector extension of the selected node
|
|
11
|
+
* (`Plugin.inspectorExtensions`, e.g. a wall's Engineering): it opens the
|
|
12
|
+
* inspector card on that extension, or keeps it there. The extension that owns
|
|
13
|
+
* the card body for this node (`primaryWhen`) is already showing and gets none;
|
|
14
|
+
* the mobile sheet lists every extension under the kind's controls instead.
|
|
15
|
+
*/
|
|
16
|
+
export function InspectorExtensionActions() {
|
|
17
|
+
const isMobile = useIsMobile()
|
|
18
|
+
const { extensions, primaryExtension } = useInspectorExtensions()
|
|
19
|
+
const active = useInspectorExpanded((state) => (state.expanded ? state.activeExtensionId : null))
|
|
20
|
+
if (isMobile) return null
|
|
21
|
+
return extensions
|
|
22
|
+
.filter((extension) => extension !== primaryExtension)
|
|
23
|
+
.map((extension) => (
|
|
24
|
+
<ActionMenuButton
|
|
25
|
+
key={extension.id}
|
|
26
|
+
label={extension.title}
|
|
27
|
+
onClick={(event) => {
|
|
28
|
+
event.stopPropagation()
|
|
29
|
+
useInspectorExpanded.getState().applyMode(openExtension(extension.id))
|
|
30
|
+
}}
|
|
31
|
+
pressed={active === extension.id}
|
|
32
|
+
>
|
|
33
|
+
{renderExtensionIcon(extension.icon)}
|
|
34
|
+
</ActionMenuButton>
|
|
35
|
+
))
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { PascalI18nProvider, usePascalI18n } from '@pascal-app/core'
|
|
4
|
+
import { Html as DreiHtml } from '@react-three/drei'
|
|
5
|
+
import { type ComponentProps, forwardRef } from 'react'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* drei's `Html`, keeping the editor's i18n scope. `Html` renders its children
|
|
9
|
+
* in a separate React root, which sees none of the surrounding context, so a
|
|
10
|
+
* component inside it would translate with the default (English) scope instead
|
|
11
|
+
* of the editor's `?lang=` or `<Editor locale>` one.
|
|
12
|
+
*/
|
|
13
|
+
export const Html = forwardRef<HTMLDivElement, Omit<ComponentProps<typeof DreiHtml>, 'ref'>>(
|
|
14
|
+
function Html({ children, ...props }, ref) {
|
|
15
|
+
const i18n = usePascalI18n()
|
|
16
|
+
return (
|
|
17
|
+
<DreiHtml {...props} ref={ref}>
|
|
18
|
+
<PascalI18nProvider i18n={i18n}>{children}</PascalI18nProvider>
|
|
19
|
+
</DreiHtml>
|
|
20
|
+
)
|
|
21
|
+
},
|
|
22
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@iconify/react'
|
|
4
|
+
import { usePascalI18n } from '@pascal-app/core'
|
|
4
5
|
import {
|
|
5
6
|
Copy,
|
|
6
7
|
Group,
|
|
@@ -65,70 +66,103 @@ export function NodeActionMenu({
|
|
|
65
66
|
onPointerEnter,
|
|
66
67
|
onPointerLeave,
|
|
67
68
|
children,
|
|
68
|
-
deleteLabel
|
|
69
|
+
deleteLabel,
|
|
69
70
|
deleteDisabledReason,
|
|
70
71
|
}: NodeActionMenuProps) {
|
|
72
|
+
const i18n = usePascalI18n()
|
|
71
73
|
return (
|
|
72
74
|
<div
|
|
73
75
|
className="pointer-events-auto flex items-center gap-1 rounded-lg border border-border bg-background/95 p-1 shadow-xl backdrop-blur-md"
|
|
76
|
+
data-node-action-menu
|
|
74
77
|
onPointerDown={onPointerDown}
|
|
75
78
|
onPointerEnter={onPointerEnter}
|
|
76
79
|
onPointerLeave={onPointerLeave}
|
|
77
80
|
onPointerUp={onPointerUp}
|
|
78
81
|
>
|
|
79
82
|
{onFind && (
|
|
80
|
-
<ActionMenuButton
|
|
83
|
+
<ActionMenuButton
|
|
84
|
+
label={i18n._({ id: 'editor.nodeActionMenu.findInCatalog', message: 'Find in catalog' })}
|
|
85
|
+
onClick={onFind}
|
|
86
|
+
>
|
|
81
87
|
<Search className="h-4 w-4" />
|
|
82
88
|
</ActionMenuButton>
|
|
83
89
|
)}
|
|
84
90
|
{onMove && (
|
|
85
|
-
<ActionMenuButton
|
|
91
|
+
<ActionMenuButton
|
|
92
|
+
label={i18n._({ id: 'editor.nodeActionMenu.move', message: 'Move' })}
|
|
93
|
+
onClick={onMove}
|
|
94
|
+
>
|
|
86
95
|
<Move className="h-4 w-4" />
|
|
87
96
|
</ActionMenuButton>
|
|
88
97
|
)}
|
|
89
98
|
{onEditMesh && (
|
|
90
|
-
<ActionMenuButton
|
|
99
|
+
<ActionMenuButton
|
|
100
|
+
label={i18n._({ id: 'editor.nodeActionMenu.editMesh', message: 'Edit mesh' })}
|
|
101
|
+
onClick={onEditMesh}
|
|
102
|
+
>
|
|
91
103
|
<PencilRuler className="h-4 w-4" />
|
|
92
104
|
</ActionMenuButton>
|
|
93
105
|
)}
|
|
94
106
|
{onGroup && (
|
|
95
|
-
<ActionMenuButton
|
|
107
|
+
<ActionMenuButton
|
|
108
|
+
keys={['Cmd/Ctrl', 'G']}
|
|
109
|
+
label={i18n._({ id: 'editor.nodeActionMenu.groupSelection', message: 'Group selection' })}
|
|
110
|
+
onClick={onGroup}
|
|
111
|
+
>
|
|
96
112
|
<Group className="h-4 w-4" />
|
|
97
113
|
</ActionMenuButton>
|
|
98
114
|
)}
|
|
99
115
|
{onUngroup && (
|
|
100
116
|
<ActionMenuButton
|
|
101
117
|
keys={['Cmd/Ctrl', 'Shift', 'G']}
|
|
102
|
-
label=
|
|
118
|
+
label={i18n._({
|
|
119
|
+
id: 'editor.nodeActionMenu.ungroupSelection',
|
|
120
|
+
message: 'Ungroup selection',
|
|
121
|
+
})}
|
|
103
122
|
onClick={onUngroup}
|
|
104
123
|
>
|
|
105
124
|
<Ungroup className="h-4 w-4" />
|
|
106
125
|
</ActionMenuButton>
|
|
107
126
|
)}
|
|
108
127
|
{onCurve && (
|
|
109
|
-
<ActionMenuButton
|
|
128
|
+
<ActionMenuButton
|
|
129
|
+
label={i18n._({ id: 'editor.nodeActionMenu.curve', message: 'Curve' })}
|
|
130
|
+
onClick={onCurve}
|
|
131
|
+
>
|
|
110
132
|
<Spline className="h-4 w-4" />
|
|
111
133
|
</ActionMenuButton>
|
|
112
134
|
)}
|
|
113
135
|
{onRotateLeft && (
|
|
114
|
-
<ActionMenuButton
|
|
136
|
+
<ActionMenuButton
|
|
137
|
+
label={i18n._({ id: 'editor.nodeActionMenu.rotateLeft', message: 'Rotate left' })}
|
|
138
|
+
onClick={onRotateLeft}
|
|
139
|
+
>
|
|
115
140
|
<RotateCcw className="h-4 w-4" />
|
|
116
141
|
</ActionMenuButton>
|
|
117
142
|
)}
|
|
118
143
|
{onRotateRight && (
|
|
119
|
-
<ActionMenuButton
|
|
144
|
+
<ActionMenuButton
|
|
145
|
+
label={i18n._({ id: 'editor.nodeActionMenu.rotateRight', message: 'Rotate right' })}
|
|
146
|
+
onClick={onRotateRight}
|
|
147
|
+
>
|
|
120
148
|
<RotateCw className="h-4 w-4" />
|
|
121
149
|
</ActionMenuButton>
|
|
122
150
|
)}
|
|
123
151
|
<RegistryActionContributions />
|
|
124
152
|
{children}
|
|
125
153
|
{onDuplicate && (
|
|
126
|
-
<ActionMenuButton
|
|
154
|
+
<ActionMenuButton
|
|
155
|
+
label={i18n._({ id: 'editor.nodeActionMenu.duplicate', message: 'Duplicate' })}
|
|
156
|
+
onClick={onDuplicate}
|
|
157
|
+
>
|
|
127
158
|
<Copy className="h-4 w-4" />
|
|
128
159
|
</ActionMenuButton>
|
|
129
160
|
)}
|
|
130
161
|
{onAddHole && (
|
|
131
|
-
<ActionMenuButton
|
|
162
|
+
<ActionMenuButton
|
|
163
|
+
label={i18n._({ id: 'editor.nodeActionMenu.cutOut', message: 'Cut out' })}
|
|
164
|
+
onClick={onAddHole}
|
|
165
|
+
>
|
|
132
166
|
<Icon height={16} icon="carbon:cut-out" width={16} />
|
|
133
167
|
</ActionMenuButton>
|
|
134
168
|
)}
|
|
@@ -138,7 +172,7 @@ export function NodeActionMenu({
|
|
|
138
172
|
disabled={!!deleteDisabledReason}
|
|
139
173
|
disabledReason={deleteDisabledReason}
|
|
140
174
|
keys={['Delete / Backspace']}
|
|
141
|
-
label={deleteLabel}
|
|
175
|
+
label={deleteLabel ?? i18n._({ id: 'editor.nodeActionMenu.delete', message: 'Delete' })}
|
|
142
176
|
onClick={onDelete}
|
|
143
177
|
>
|
|
144
178
|
<Trash2 className="h-4 w-4" />
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
useScene,
|
|
22
22
|
} from '@pascal-app/core'
|
|
23
23
|
import { useViewer } from '@pascal-app/viewer'
|
|
24
|
-
import { Html } from '@react-three/drei'
|
|
25
24
|
import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
|
|
26
25
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
27
26
|
import {
|
|
@@ -41,7 +40,6 @@ import {
|
|
|
41
40
|
Vector3,
|
|
42
41
|
} from 'three'
|
|
43
42
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
44
|
-
|
|
45
43
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
46
44
|
import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
|
|
47
45
|
import { EDITOR_LAYER } from '../../lib/constants'
|
|
@@ -78,6 +76,7 @@ import {
|
|
|
78
76
|
import { createLinearResizeDragBinding, linearResizeFactor } from './handles/linear-resize-drag'
|
|
79
77
|
import { resolveResizeSnapValue } from './handles/resize-snap'
|
|
80
78
|
import { type HandleDragControls, useHandleDrag } from './handles/use-handle-drag'
|
|
79
|
+
import { Html } from './localized-html'
|
|
81
80
|
|
|
82
81
|
// Pooled scratch for the handle rig's world-relative pose mapping.
|
|
83
82
|
const _rigRelative = new Matrix4()
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { type OpenWallEnd, sceneRegistry } from '@pascal-app/core'
|
|
4
4
|
import { useViewer } from '@pascal-app/viewer'
|
|
5
|
-
import { Html } from '@react-three/drei'
|
|
6
5
|
import { useFrame } from '@react-three/fiber'
|
|
7
6
|
import { memo, useEffect, useMemo, useRef } from 'react'
|
|
8
7
|
import { BoxGeometry, CircleGeometry, type Group, RingGeometry } from 'three'
|
|
@@ -19,6 +18,7 @@ import { cn } from '../../lib/utils'
|
|
|
19
18
|
import useEditor from '../../store/use-editor'
|
|
20
19
|
import { isViewVisible, VIEW_3D, visibleScene } from '../../store/view-layout'
|
|
21
20
|
import { JoinWallsPill } from './join-walls-pill'
|
|
21
|
+
import { Html } from './localized-html'
|
|
22
22
|
import { OpenWallEndsHint } from './open-wall-ends-hint'
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useViewer } from '@pascal-app/viewer'
|
|
4
|
-
import { Html } from '@react-three/drei'
|
|
5
4
|
import { memo, useEffect, useLayoutEffect, useMemo } from 'react'
|
|
6
5
|
import { BufferGeometry, Float32BufferAttribute, Line as ThreeLine } from 'three'
|
|
7
6
|
import { LineBasicNodeMaterial } from 'three/webgpu'
|
|
@@ -10,6 +9,7 @@ import useOpeningGuides, {
|
|
|
10
9
|
type OpeningGuide3D,
|
|
11
10
|
type OpeningGuideVec3,
|
|
12
11
|
} from '../../store/use-opening-guides'
|
|
12
|
+
import { Html } from './localized-html'
|
|
13
13
|
import { formatMeasurement } from './measurement-pill'
|
|
14
14
|
|
|
15
15
|
const DIMENSION_COLOR = 0x81_8c_f8 // indigo — a neutral measurement
|