@pascal-app/editor 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -17
- package/src/components/editor/action-menu-button.tsx +4 -1
- package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
- package/src/components/editor/bake-thumbnail.tsx +3 -2
- package/src/components/editor/custom-camera-controls.tsx +20 -8
- package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
- package/src/components/editor/first-person-controls.tsx +72 -61
- package/src/components/editor/floating-action-menu.tsx +27 -9
- package/src/components/editor/floating-building-action-menu.tsx +1 -1
- package/src/components/editor/floorplan-panel.test.ts +220 -0
- package/src/components/editor/floorplan-panel.tsx +285 -132
- package/src/components/editor/footprint-height-handle.tsx +1 -1
- package/src/components/editor/grid.tsx +24 -15
- package/src/components/editor/group-actions.ts +19 -1
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-move-3d.ts +27 -2
- package/src/components/editor/group-selection-box-3d.tsx +4 -1
- package/src/components/editor/group-transform-shared.ts +20 -0
- package/src/components/editor/index.tsx +130 -20
- package/src/components/editor/inspector-extension-actions.tsx +36 -0
- package/src/components/editor/localized-html.tsx +22 -0
- package/src/components/editor/node-action-menu.tsx +46 -12
- package/src/components/editor/node-arrow-handles.tsx +1 -2
- package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
- package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
- package/src/components/editor/registry-action-contributions.test.tsx +67 -6
- package/src/components/editor/registry-action-contributions.tsx +115 -15
- package/src/components/editor/riser-diagram-panel.tsx +59 -9
- package/src/components/editor/room-controls.tsx +1 -1
- package/src/components/editor/room-handles-2d.tsx +156 -6
- package/src/components/editor/room-handles.tsx +241 -51
- package/src/components/editor/room-highlight.tsx +2 -0
- package/src/components/editor/room-labels.tsx +29 -15
- package/src/components/editor/selection-manager.tsx +15 -102
- package/src/components/editor/separator-end-grip.tsx +74 -0
- package/src/components/editor/site-edge-labels.tsx +1 -1
- package/src/components/editor/thumbnail-generator.test.ts +15 -6
- package/src/components/editor/thumbnail-generator.tsx +26 -1
- package/src/components/editor/use-floorplan-background-placement.ts +55 -52
- package/src/components/editor/views/view-bar.tsx +111 -26
- package/src/components/editor/wall-measurement-label.tsx +1 -1
- package/src/components/editor/wall-move-side-handles.tsx +5 -1
- package/src/components/editor/wall-pull-grip.tsx +193 -0
- package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
- package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
- package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
- package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
- package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
- package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
- package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
- package/src/components/procedural-items/inspector.tsx +59 -12
- package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
- package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
- package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
- package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
- package/src/components/tools/item/placement-history.test.tsx +1 -1
- package/src/components/tools/item/placement-strategies.test.ts +64 -1
- package/src/components/tools/item/placement-strategies.ts +5 -9
- package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
- package/src/components/tools/registry/item-surface-move.test.ts +11 -0
- package/src/components/tools/select/select-candidates.test.ts +45 -0
- package/src/components/tools/select/select-candidates.ts +13 -2
- package/src/components/tools/shared/cursor-sphere.tsx +10 -3
- package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
- package/src/components/tools/shared/placement-box.tsx +1 -1
- package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
- package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
- package/src/components/tools/wall/wall-drafting.test.ts +299 -23
- package/src/components/tools/wall/wall-drafting.ts +132 -24
- package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
- package/src/components/tools/wall/wall-pull-snap.ts +563 -0
- package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
- package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
- package/src/components/tools/zone/zone-tool.tsx +3 -3
- package/src/components/ui/action-menu/action-button.tsx +2 -0
- package/src/components/ui/action-menu/camera-actions.tsx +8 -7
- package/src/components/ui/action-menu/control-modes.tsx +9 -5
- package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
- package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
- package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
- package/src/components/ui/action-menu/index.tsx +1 -0
- package/src/components/ui/action-menu/measurement-control.tsx +35 -29
- package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
- package/src/components/ui/action-menu/structure-tools.tsx +25 -23
- package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
- package/src/components/ui/action-menu/view-toggles.tsx +256 -51
- package/src/components/ui/build-panel/build-panel.tsx +163 -25
- package/src/components/ui/command-palette/editor-commands.tsx +38 -11
- package/src/components/ui/controls/finish-swatch.tsx +45 -19
- package/src/components/ui/controls/material-picker.tsx +24 -17
- package/src/components/ui/controls/metric-control.test.ts +52 -0
- package/src/components/ui/controls/metric-control.tsx +62 -24
- package/src/components/ui/controls/segmented-control.tsx +5 -2
- package/src/components/ui/controls/slider-control.test.tsx +23 -0
- package/src/components/ui/controls/slider-control.tsx +30 -14
- package/src/components/ui/floating-level-selector.tsx +100 -48
- package/src/components/ui/helpers/building-helper.tsx +5 -3
- package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
- package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
- package/src/components/ui/helpers/helper-manager.tsx +82 -57
- package/src/components/ui/helpers/item-helper.tsx +6 -4
- package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
- package/src/components/ui/level-duplicate-dialog.tsx +32 -23
- package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
- package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
- package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
- package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
- package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
- package/src/components/ui/panels/inspector-extensions.tsx +66 -0
- package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
- package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
- package/src/components/ui/panels/multi-field-value.test.ts +27 -0
- package/src/components/ui/panels/multi-field-value.ts +20 -1
- package/src/components/ui/panels/multi-height-mode.tsx +8 -7
- package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
- package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
- package/src/components/ui/panels/node-display.ts +35 -24
- package/src/components/ui/panels/panel-manager.tsx +5 -3
- package/src/components/ui/panels/panel-wrapper.tsx +22 -109
- package/src/components/ui/panels/parametric-field-control.tsx +13 -10
- package/src/components/ui/panels/parametric-inspector.tsx +18 -12
- package/src/components/ui/panels/reference-panel.tsx +51 -35
- package/src/components/ui/panels/room-construction-rows.tsx +464 -65
- package/src/components/ui/panels/room-name-options.test.ts +36 -3
- package/src/components/ui/panels/room-name-options.ts +110 -11
- package/src/components/ui/panels/room-openings-field.tsx +21 -12
- package/src/components/ui/panels/room-panel.tsx +206 -99
- package/src/components/ui/panels/shape-choice.tsx +7 -5
- package/src/components/ui/panels/wall-region-list.tsx +7 -7
- package/src/components/ui/primitives/shortcut-token.tsx +10 -6
- package/src/components/ui/sidebar/icon-rail.tsx +11 -9
- package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
- package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
- package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
- package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
- package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
- package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
- package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
- package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
- package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
- package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
- package/src/components/ui/sidebar/tab-bar.tsx +4 -1
- package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
- package/src/components/ui/snap-target-badge.tsx +7 -6
- package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
- package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
- package/src/components/viewer/floorplan-compass-button.tsx +12 -3
- package/src/components/viewer/floorplan-preview.tsx +27 -19
- package/src/components/viewer/render-settings-menu.tsx +294 -0
- package/src/components/viewer/sky-control.tsx +710 -0
- package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
- package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
- package/src/components/viewer/viewer-controls-bar.tsx +278 -260
- package/src/components/viewer/viewer-hover-label.tsx +12 -4
- package/src/components/viewer/viewer-rooms.tsx +61 -4
- package/src/components/viewer/viewer-scene-header.tsx +28 -17
- package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
- package/src/components/viewer-overlay.tsx +8 -0
- package/src/components/walkthrough-hud.tsx +75 -2
- package/src/hooks/use-keyboard.test.ts +85 -4
- package/src/hooks/use-keyboard.ts +65 -7
- package/src/hooks/use-selected-room.ts +12 -1
- package/src/i18n/floor-footprint-name.ts +30 -0
- package/src/i18n/level-name.ts +39 -0
- package/src/i18n/locales/en/messages.po +15117 -0
- package/src/i18n/locales/en/messages.ts +1 -0
- package/src/i18n/locales/en-XA/messages.ts +1 -0
- package/src/i18n/locales/es/messages.po +37501 -0
- package/src/i18n/locales/es/messages.ts +1 -0
- package/src/i18n/locales/fr/messages.po +37501 -0
- package/src/i18n/locales/fr/messages.ts +1 -0
- package/src/i18n/locales/ja/messages.po +37501 -0
- package/src/i18n/locales/ja/messages.ts +1 -0
- package/src/i18n/locales/zh-CN/messages.po +37501 -0
- package/src/i18n/locales/zh-CN/messages.ts +1 -0
- package/src/i18n/locales.test.ts +63 -0
- package/src/i18n/locales.ts +32 -0
- package/src/i18n/message-slot.tsx +25 -0
- package/src/i18n/node-messages.ts +169 -0
- package/src/i18n/pseudo-locale.ts +17 -0
- package/src/i18n/published-entry.test.ts +17 -0
- package/src/i18n/scene-theme-name.ts +16 -0
- package/src/index.tsx +156 -4
- package/src/lib/add-storey.test.ts +3 -3
- package/src/lib/contextual-help.test.ts +20 -1
- package/src/lib/contextual-help.ts +95 -19
- package/src/lib/continuation.ts +38 -11
- package/src/lib/floor-footprints.test.ts +2 -1
- package/src/lib/floor-foundation-support.test.ts +7 -3
- package/src/lib/floorplan/dimension-drive.test.ts +36 -2
- package/src/lib/floorplan/dimension-drive.ts +68 -3
- package/src/lib/floorplan/floorplan-export.test.ts +16 -5
- package/src/lib/floorplan/floorplan-export.tsx +13 -16
- package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
- package/src/lib/floorplan/floorplan-extension.ts +37 -18
- package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
- package/src/lib/floorplan/floorplan-mode.ts +14 -12
- package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
- package/src/lib/floorplan/floorplan-readonly.ts +3 -3
- package/src/lib/floorplan/schedules.ts +10 -6
- package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
- package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
- package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
- package/src/lib/geometry-script/author.test.ts +114 -0
- package/src/lib/geometry-script/author.ts +55 -23
- package/src/lib/geometry-script/client.ts +3 -0
- package/src/lib/geometry-script/finish-scripts.ts +7 -7
- package/src/lib/glb-export.test.ts +327 -0
- package/src/lib/glb-export.ts +70 -2
- package/src/lib/grid-frame.test.ts +56 -3
- package/src/lib/grid-frame.ts +28 -1
- package/src/lib/history.test.ts +2 -1
- package/src/lib/hud-title.ts +48 -24
- package/src/lib/ifc-export.test.ts +41 -1
- package/src/lib/inspector-card-mode.test.ts +3 -31
- package/src/lib/inspector-card-mode.ts +5 -10
- package/src/lib/inspector-expanded.test.ts +13 -0
- package/src/lib/inspector-expanded.ts +13 -1
- package/src/lib/keyboard-pan.test.ts +7 -0
- package/src/lib/keyboard-pan.ts +9 -2
- package/src/lib/layer-thickness.test.ts +22 -0
- package/src/lib/layer-thickness.ts +79 -0
- package/src/lib/lot/drop-in.test.ts +59 -1
- package/src/lib/lot/drop-in.ts +42 -50
- package/src/lib/material-swatch-style.ts +24 -0
- package/src/lib/measurement-parser.ts +7 -3
- package/src/lib/measurements.test.ts +17 -0
- package/src/lib/measurements.ts +20 -5
- package/src/lib/mezzanine-selection.test.ts +3 -1
- package/src/lib/orbit-near-plane.test.ts +12 -0
- package/src/lib/orbit-near-plane.ts +13 -0
- package/src/lib/paint-regions.test.ts +8 -5
- package/src/lib/paint-scope.test.ts +53 -0
- package/src/lib/paint-scope.ts +18 -4
- package/src/lib/panel-rows.ts +17 -8
- package/src/lib/reference-scale.ts +83 -0
- package/src/lib/room-built-on.test.ts +26 -12
- package/src/lib/room-built-on.ts +20 -2
- package/src/lib/room-construction.test.ts +28 -1
- package/src/lib/room-construction.ts +14 -0
- package/src/lib/room-controls-phase6.test.ts +140 -0
- package/src/lib/room-handle-drag.ts +557 -42
- package/src/lib/room-name-catalog.test.ts +8 -0
- package/src/lib/room-name-catalog.ts +8 -1
- package/src/lib/room-name-messages.test.ts +132 -0
- package/src/lib/room-name-messages.ts +147 -0
- package/src/lib/room-name-storage.test.ts +334 -0
- package/src/lib/room-push-dimensions.test.ts +37 -0
- package/src/lib/room-push-dimensions.ts +226 -2
- package/src/lib/room-selection.test.ts +23 -6
- package/src/lib/scene-clipboard.test.ts +70 -49
- package/src/lib/scene-clipboard.ts +9 -4
- package/src/lib/selection-cycle.ts +63 -0
- package/src/lib/separator-edit.test.ts +431 -0
- package/src/lib/separator-edit.ts +179 -0
- package/src/lib/separator-lines.ts +38 -0
- package/src/lib/sfx-bus.ts +27 -1
- package/src/lib/sfx-player.ts +16 -0
- package/src/lib/tool-cursor-icon.ts +5 -1
- package/src/lib/tool-shortcuts.ts +130 -0
- package/src/lib/viewer-selection.test.ts +28 -24
- package/src/lib/viewer-selection.ts +20 -5
- package/src/lib/wall-draw-variant.test.ts +7 -6
- package/src/lib/wall-draw-variant.ts +107 -6
- package/src/lib/wall-nudge.test.ts +44 -0
- package/src/lib/wall-nudge.ts +131 -0
- package/src/lib/wall-parallel-snap.test.ts +83 -0
- package/src/lib/wall-parallel-snap.ts +176 -0
- package/src/lib/wall-placement-pointer.test.ts +23 -0
- package/src/lib/wall-placement-pointer.ts +18 -0
- package/src/lib/wall-pull.test.ts +245 -0
- package/src/lib/wall-pull.ts +209 -0
- package/src/lib/wall-push-line-up.test.ts +132 -0
- package/src/lib/wall-region-list.ts +14 -5
- package/src/lib/wall-run-snap.test.ts +188 -0
- package/src/lib/wall-run-snap.ts +83 -0
- package/src/lib/wall-type.test.ts +125 -0
- package/src/lib/wall-type.ts +119 -0
- package/src/lib/window-interaction.test.ts +42 -0
- package/src/lib/window-interaction.ts +47 -5
- package/src/lib/world-grid-snap.test.ts +139 -2
- package/src/lib/world-grid-snap.ts +102 -105
- package/src/store/use-alignment-guides.ts +27 -1
- package/src/store/use-editor.tsx +48 -0
- package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
- package/src/store/use-floorplan-annotation-visibility.ts +47 -22
- package/src/store/use-wall-snap-indicator.ts +15 -2
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { emitter } from '@pascal-app/core'
|
|
3
|
+
import { emitter, type PascalI18n, usePascalI18n } from '@pascal-app/core'
|
|
4
|
+
import { CLAY_PALETTE, getSceneTheme, SCENE_THEMES, useViewer } from '@pascal-app/viewer'
|
|
4
5
|
import {
|
|
5
|
-
CLAY_PALETTE,
|
|
6
|
-
type EdgeMode,
|
|
7
|
-
getSceneTheme,
|
|
8
|
-
SCENE_THEMES,
|
|
9
|
-
useViewer,
|
|
10
|
-
} from '@pascal-app/viewer'
|
|
11
|
-
import {
|
|
12
|
-
Box,
|
|
13
6
|
Camera,
|
|
14
7
|
Check,
|
|
15
8
|
Contrast,
|
|
16
9
|
Diamond,
|
|
10
|
+
Drone,
|
|
17
11
|
Eye,
|
|
18
12
|
EyeOff,
|
|
19
13
|
Footprints,
|
|
20
14
|
Layers,
|
|
21
15
|
Layers2,
|
|
22
16
|
Palette,
|
|
23
|
-
PenLine,
|
|
24
17
|
SlidersHorizontal,
|
|
25
|
-
Sparkles,
|
|
26
18
|
Square,
|
|
27
19
|
SwatchBook,
|
|
28
20
|
} from 'lucide-react'
|
|
21
|
+
import { useIsMobile } from '../../hooks/use-mobile'
|
|
22
|
+
import { sceneThemeName } from '../../i18n/scene-theme-name'
|
|
29
23
|
import { cn } from '../../lib/utils'
|
|
30
24
|
import { ActionButton } from '../ui/action-menu/action-button'
|
|
31
25
|
import { FollowPascalToggle } from '../ui/action-menu/follow-pascal-toggle'
|
|
@@ -40,44 +34,36 @@ import {
|
|
|
40
34
|
DropdownMenuTrigger,
|
|
41
35
|
} from '../ui/primitives/dropdown-menu'
|
|
42
36
|
import { TooltipProvider } from '../ui/primitives/tooltip'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
stacked: 'Stacked',
|
|
46
|
-
exploded: 'Exploded',
|
|
47
|
-
solo: 'Solo',
|
|
48
|
-
}
|
|
37
|
+
import { DaylightMenu, EdgesToggleItem, QualityMenu } from './render-settings-menu'
|
|
38
|
+
import { SKY_DOCK_HEIGHT, SkyControl } from './sky-control'
|
|
49
39
|
|
|
50
40
|
const wallModeConfig = {
|
|
51
41
|
up: {
|
|
52
|
-
icon:
|
|
53
|
-
<img alt="Full height" height={28} src="/icons/room.webp" width={28} {...props} />
|
|
54
|
-
),
|
|
55
|
-
label: 'Full height',
|
|
42
|
+
icon: '/icons/room.webp',
|
|
56
43
|
},
|
|
57
44
|
cutaway: {
|
|
58
|
-
icon:
|
|
59
|
-
<img alt="Cutaway" height={28} src="/icons/wallcut.webp" width={28} {...props} />
|
|
60
|
-
),
|
|
61
|
-
label: 'Cutaway',
|
|
45
|
+
icon: '/icons/wallcut.webp',
|
|
62
46
|
},
|
|
63
47
|
down: {
|
|
64
|
-
icon:
|
|
65
|
-
<img alt="Low" height={28} src="/icons/walllow.webp" width={28} {...props} />
|
|
66
|
-
),
|
|
67
|
-
label: 'Low',
|
|
48
|
+
icon: '/icons/walllow.webp',
|
|
68
49
|
},
|
|
69
|
-
}
|
|
50
|
+
} as const
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
52
|
+
function levelModeLabel(
|
|
53
|
+
mode: 'stacked' | 'exploded' | 'solo' | 'manual',
|
|
54
|
+
i18n: PascalI18n,
|
|
55
|
+
): string {
|
|
56
|
+
if (mode === 'stacked') return i18n._({ id: 'viewer.levels.stacked', message: 'Stacked' })
|
|
57
|
+
if (mode === 'exploded') return i18n._({ id: 'viewer.levels.exploded', message: 'Exploded' })
|
|
58
|
+
if (mode === 'solo') return i18n._({ id: 'viewer.levels.solo', message: 'Solo' })
|
|
59
|
+
return i18n._({ id: 'viewer.levels.manual', message: 'Manual' })
|
|
60
|
+
}
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{ id: '
|
|
80
|
-
|
|
62
|
+
function wallModeLabel(mode: keyof typeof wallModeConfig, i18n: PascalI18n): string {
|
|
63
|
+
if (mode === 'up') return i18n._({ id: 'viewer.walls.fullHeight', message: 'Full height' })
|
|
64
|
+
if (mode === 'down') return i18n._({ id: 'viewer.walls.low', message: 'Low' })
|
|
65
|
+
return i18n._({ id: 'viewer.walls.cutaway', message: 'Cutaway' })
|
|
66
|
+
}
|
|
81
67
|
|
|
82
68
|
// Keep the dropdown open when flipping an in-place toggle row.
|
|
83
69
|
const keepOpen = (event: Event, fn: () => void) => {
|
|
@@ -95,6 +81,7 @@ function VisibilityMenu({
|
|
|
95
81
|
canShowScans: boolean
|
|
96
82
|
canShowGuides: boolean
|
|
97
83
|
}) {
|
|
84
|
+
const i18n = usePascalI18n()
|
|
98
85
|
const showScans = useViewer((s) => s.showScans)
|
|
99
86
|
const showGuides = useViewer((s) => s.showGuides)
|
|
100
87
|
return (
|
|
@@ -102,7 +89,7 @@ function VisibilityMenu({
|
|
|
102
89
|
<DropdownMenuTrigger asChild>
|
|
103
90
|
<ActionButton
|
|
104
91
|
className="hover:bg-white/5 hover:text-foreground"
|
|
105
|
-
label=
|
|
92
|
+
label={i18n._({ id: 'viewer.visibility.title', message: 'Visibility' })}
|
|
106
93
|
size="icon"
|
|
107
94
|
tooltipClassName="dark"
|
|
108
95
|
tooltipSide="top"
|
|
@@ -117,7 +104,7 @@ function VisibilityMenu({
|
|
|
117
104
|
onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowScans(!showScans))}
|
|
118
105
|
>
|
|
119
106
|
<img alt="" className="h-4 w-4 object-contain" src="/icons/mesh.webp" />
|
|
120
|
-
<span>Scans</span>
|
|
107
|
+
<span>{i18n._({ id: 'viewer.visibility.scans', message: 'Scans' })}</span>
|
|
121
108
|
{showScans ? (
|
|
122
109
|
<Eye className="ml-auto h-4 w-4 text-foreground" />
|
|
123
110
|
) : (
|
|
@@ -130,7 +117,7 @@ function VisibilityMenu({
|
|
|
130
117
|
onSelect={(e) => keepOpen(e, () => useViewer.getState().setShowGuides(!showGuides))}
|
|
131
118
|
>
|
|
132
119
|
<img alt="" className="h-4 w-4 object-contain" src="/icons/floorplan.webp" />
|
|
133
|
-
<span>Guides</span>
|
|
120
|
+
<span>{i18n._({ id: 'viewer.visibility.guides', message: 'Guides' })}</span>
|
|
134
121
|
{showGuides ? (
|
|
135
122
|
<Eye className="ml-auto h-4 w-4 text-foreground" />
|
|
136
123
|
) : (
|
|
@@ -146,21 +133,18 @@ function VisibilityMenu({
|
|
|
146
133
|
// One "Display" button gathering shadows, camera projection, colors, render
|
|
147
134
|
// mode, scene theme and edges.
|
|
148
135
|
function DisplayMenu() {
|
|
136
|
+
const i18n = usePascalI18n()
|
|
149
137
|
const cameraMode = useViewer((s) => s.cameraMode)
|
|
150
|
-
const shading = useViewer((s) => s.shading)
|
|
151
138
|
const textures = useViewer((s) => s.textures)
|
|
152
139
|
const shadows = useViewer((s) => s.shadows)
|
|
153
140
|
const sceneTheme = useViewer((s) => s.sceneTheme)
|
|
154
|
-
const edges = useViewer((s) => s.edges)
|
|
155
|
-
const activeShading = SHADING_OPTIONS.find((o) => o.id === shading) ?? SHADING_OPTIONS[0]
|
|
156
141
|
const activeTheme = getSceneTheme(sceneTheme)
|
|
157
|
-
const activeEdges = EDGE_OPTIONS.find((o) => o.id === edges) ?? EDGE_OPTIONS[0]
|
|
158
142
|
return (
|
|
159
143
|
<DropdownMenu>
|
|
160
144
|
<DropdownMenuTrigger asChild>
|
|
161
145
|
<ActionButton
|
|
162
146
|
className="hover:bg-white/5 hover:text-foreground"
|
|
163
|
-
label=
|
|
147
|
+
label={i18n._({ id: 'viewer.display.title', message: 'Display settings' })}
|
|
164
148
|
size="icon"
|
|
165
149
|
tooltipClassName="dark"
|
|
166
150
|
tooltipSide="top"
|
|
@@ -174,8 +158,12 @@ function DisplayMenu() {
|
|
|
174
158
|
onSelect={(e) => keepOpen(e, () => useViewer.getState().setShadows(!shadows))}
|
|
175
159
|
>
|
|
176
160
|
<Contrast className="h-4 w-4" />
|
|
177
|
-
<span>Shadows</span>
|
|
178
|
-
<span className="ml-auto text-muted-foreground text-xs">
|
|
161
|
+
<span>{i18n._({ id: 'viewer.display.shadows', message: 'Shadows' })}</span>
|
|
162
|
+
<span className="ml-auto text-muted-foreground text-xs">
|
|
163
|
+
{shadows
|
|
164
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
165
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' })}
|
|
166
|
+
</span>
|
|
179
167
|
</DropdownMenuItem>
|
|
180
168
|
<DropdownMenuItem
|
|
181
169
|
onSelect={(e) =>
|
|
@@ -187,107 +175,70 @@ function DisplayMenu() {
|
|
|
187
175
|
}
|
|
188
176
|
>
|
|
189
177
|
<Camera className="h-4 w-4" />
|
|
190
|
-
<span>Camera</span>
|
|
178
|
+
<span>{i18n._({ id: 'viewer.display.camera', message: 'Camera' })}</span>
|
|
191
179
|
<span className="ml-auto text-muted-foreground text-xs">
|
|
192
|
-
{cameraMode === 'perspective'
|
|
180
|
+
{cameraMode === 'perspective'
|
|
181
|
+
? i18n._({ id: 'viewer.display.perspective', message: 'Perspective' })
|
|
182
|
+
: i18n._({ id: 'viewer.display.orthographic', message: 'Orthographic' })}
|
|
193
183
|
</span>
|
|
194
184
|
</DropdownMenuItem>
|
|
195
185
|
<DropdownMenuItem
|
|
196
186
|
onSelect={(e) => keepOpen(e, () => useViewer.getState().setTextures(!textures))}
|
|
197
187
|
>
|
|
198
188
|
{textures ? <Palette className="h-4 w-4" /> : <Square className="h-4 w-4" />}
|
|
199
|
-
<span>Colors</span>
|
|
189
|
+
<span>{i18n._({ id: 'viewer.display.colors', message: 'Colors' })}</span>
|
|
200
190
|
<span className="ml-auto text-muted-foreground text-xs">
|
|
201
|
-
{textures
|
|
191
|
+
{textures
|
|
192
|
+
? i18n._({ id: 'viewer.display.colored', message: 'Colored' })
|
|
193
|
+
: i18n._({ id: 'viewer.display.monochrome', message: 'Monochrome' })}
|
|
202
194
|
</span>
|
|
203
195
|
</DropdownMenuItem>
|
|
204
196
|
|
|
205
197
|
<DropdownMenuSeparator />
|
|
206
198
|
|
|
207
|
-
<
|
|
208
|
-
<DropdownMenuSubTrigger>
|
|
209
|
-
<activeShading.icon className="h-4 w-4" />
|
|
210
|
-
<span>Render</span>
|
|
211
|
-
<span className="ml-auto text-muted-foreground text-xs">{activeShading.name}</span>
|
|
212
|
-
</DropdownMenuSubTrigger>
|
|
213
|
-
<DropdownMenuSubContent className="dark min-w-56">
|
|
214
|
-
{SHADING_OPTIONS.map((option) => {
|
|
215
|
-
const OptionIcon = option.icon
|
|
216
|
-
return (
|
|
217
|
-
<DropdownMenuItem
|
|
218
|
-
key={option.id}
|
|
219
|
-
onSelect={() => useViewer.getState().setShading(option.id)}
|
|
220
|
-
>
|
|
221
|
-
<OptionIcon className="h-4 w-4" />
|
|
222
|
-
<div className="flex flex-col">
|
|
223
|
-
<span className="text-foreground">{option.name}</span>
|
|
224
|
-
<span className="text-muted-foreground text-xs">{option.detail}</span>
|
|
225
|
-
</div>
|
|
226
|
-
{shading === option.id ? (
|
|
227
|
-
<Check className="ml-auto h-4 w-4 text-foreground" />
|
|
228
|
-
) : null}
|
|
229
|
-
</DropdownMenuItem>
|
|
230
|
-
)
|
|
231
|
-
})}
|
|
232
|
-
</DropdownMenuSubContent>
|
|
233
|
-
</DropdownMenuSub>
|
|
199
|
+
<QualityMenu subContentClassName="dark min-w-56" />
|
|
234
200
|
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
<DropdownMenuSubContent className="dark min-w-56">
|
|
277
|
-
{EDGE_OPTIONS.map((option) => (
|
|
278
|
-
<DropdownMenuItem
|
|
279
|
-
key={option.id}
|
|
280
|
-
onSelect={() => useViewer.getState().setEdges(option.id)}
|
|
281
|
-
>
|
|
282
|
-
<div className="flex flex-col">
|
|
283
|
-
<span className="text-foreground">{option.name}</span>
|
|
284
|
-
<span className="text-muted-foreground text-xs">{option.detail}</span>
|
|
285
|
-
</div>
|
|
286
|
-
{edges === option.id ? <Check className="ml-auto h-4 w-4 text-foreground" /> : null}
|
|
287
|
-
</DropdownMenuItem>
|
|
288
|
-
))}
|
|
289
|
-
</DropdownMenuSubContent>
|
|
290
|
-
</DropdownMenuSub>
|
|
201
|
+
{textures ? (
|
|
202
|
+
<DaylightMenu subContentClassName="dark min-w-48" />
|
|
203
|
+
) : (
|
|
204
|
+
<>
|
|
205
|
+
<DropdownMenuSub>
|
|
206
|
+
<DropdownMenuSubTrigger>
|
|
207
|
+
<SwatchBook className="h-4 w-4" />
|
|
208
|
+
<span>{i18n._({ id: 'viewer.theme.title', message: 'Scene theme' })}</span>
|
|
209
|
+
<span className="ml-auto truncate text-muted-foreground text-xs">
|
|
210
|
+
{sceneThemeName(activeTheme.id, activeTheme.name, i18n)}
|
|
211
|
+
</span>
|
|
212
|
+
</DropdownMenuSubTrigger>
|
|
213
|
+
<DropdownMenuSubContent className="dark min-w-48">
|
|
214
|
+
{SCENE_THEMES.map((theme) => {
|
|
215
|
+
const swatches = (['wall', 'roof', 'floor', 'glazing'] as const).map(
|
|
216
|
+
(role) => theme.clayTints?.[role] ?? CLAY_PALETTE[role],
|
|
217
|
+
)
|
|
218
|
+
return (
|
|
219
|
+
<DropdownMenuItem
|
|
220
|
+
className="gap-2"
|
|
221
|
+
key={theme.id}
|
|
222
|
+
onSelect={() => useViewer.getState().setSceneTheme(theme.id)}
|
|
223
|
+
>
|
|
224
|
+
<span
|
|
225
|
+
className="grid h-5 w-5 shrink-0 grid-cols-2 overflow-hidden rounded-sm border border-black/10"
|
|
226
|
+
style={{ backgroundColor: theme.background }}
|
|
227
|
+
>
|
|
228
|
+
{swatches.map((color, index) => (
|
|
229
|
+
<span key={`${theme.id}-${index}`} style={{ backgroundColor: color }} />
|
|
230
|
+
))}
|
|
231
|
+
</span>
|
|
232
|
+
<span>{sceneThemeName(theme.id, theme.name, i18n)}</span>
|
|
233
|
+
{sceneTheme === theme.id ? <Check className="ml-auto h-4 w-4" /> : null}
|
|
234
|
+
</DropdownMenuItem>
|
|
235
|
+
)
|
|
236
|
+
})}
|
|
237
|
+
</DropdownMenuSubContent>
|
|
238
|
+
</DropdownMenuSub>
|
|
239
|
+
<EdgesToggleItem />
|
|
240
|
+
</>
|
|
241
|
+
)}
|
|
291
242
|
</DropdownMenuContent>
|
|
292
243
|
</DropdownMenu>
|
|
293
244
|
)
|
|
@@ -306,6 +257,9 @@ export type ViewerControlsBarProps = {
|
|
|
306
257
|
glbHasGuides?: boolean
|
|
307
258
|
walkthroughActive?: boolean
|
|
308
259
|
onWalkthroughToggle: () => void
|
|
260
|
+
/** The free-flying drone camera; its button shows only with a handler, and never on touch. */
|
|
261
|
+
droneActive?: boolean
|
|
262
|
+
onDroneToggle?: () => void
|
|
309
263
|
className?: string
|
|
310
264
|
}
|
|
311
265
|
|
|
@@ -317,162 +271,226 @@ export const ViewerControlsBar = ({
|
|
|
317
271
|
glbHasGuides = false,
|
|
318
272
|
walkthroughActive = false,
|
|
319
273
|
onWalkthroughToggle,
|
|
274
|
+
droneActive = false,
|
|
275
|
+
onDroneToggle,
|
|
320
276
|
className,
|
|
321
277
|
}: ViewerControlsBarProps) => {
|
|
278
|
+
const i18n = usePascalI18n()
|
|
322
279
|
const levelMode = useViewer((s) => s.levelMode)
|
|
323
280
|
const wallMode = useViewer((s) => s.wallMode)
|
|
281
|
+
const textures = useViewer((s) => s.textures)
|
|
282
|
+
const isMobile = useIsMobile()
|
|
324
283
|
// Sessions may carry a stale mode outside the cycle (e.g. the retired
|
|
325
284
|
// 'translucent'); render and cycle it as cutaway instead of crashing.
|
|
326
285
|
const safeWallMode = (
|
|
327
286
|
wallMode in wallModeConfig ? wallMode : 'cutaway'
|
|
328
287
|
) as keyof typeof wallModeConfig
|
|
329
|
-
const WallModeIcon = wallModeConfig[safeWallMode].icon
|
|
330
288
|
|
|
331
289
|
return (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<div className="mx-1 h-5 w-px bg-border/40" />
|
|
348
|
-
</>
|
|
349
|
-
)}
|
|
350
|
-
|
|
351
|
-
{/* Level mode */}
|
|
352
|
-
<ActionButton
|
|
353
|
-
className={
|
|
354
|
-
levelMode === 'stacked'
|
|
355
|
-
? 'hover:bg-white/5 hover:text-amber-400'
|
|
356
|
-
: 'bg-amber-500/20 text-amber-400'
|
|
357
|
-
}
|
|
358
|
-
label={`Levels: ${levelMode === 'manual' ? 'Manual' : levelModeLabels[levelMode as keyof typeof levelModeLabels]}`}
|
|
359
|
-
onClick={() => {
|
|
360
|
-
if (levelMode === 'manual') return useViewer.getState().setLevelMode('stacked')
|
|
361
|
-
const modes: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo']
|
|
362
|
-
const nextIndex = (modes.indexOf(levelMode as any) + 1) % modes.length
|
|
363
|
-
useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked')
|
|
364
|
-
}}
|
|
365
|
-
size="icon"
|
|
366
|
-
tooltipClassName="dark"
|
|
367
|
-
tooltipSide="top"
|
|
368
|
-
variant="ghost"
|
|
290
|
+
<>
|
|
291
|
+
{textures && !isMobile ? (
|
|
292
|
+
// The sky (time and weather) beside the compass in the scene's bottom-left corner.
|
|
293
|
+
<SkyControl dark height={SKY_DOCK_HEIGHT.viewer} style={{ left: 52 }} />
|
|
294
|
+
) : null}
|
|
295
|
+
<div
|
|
296
|
+
className={cn(
|
|
297
|
+
'dark absolute bottom-4 left-1/2 z-20 -translate-x-1/2 text-foreground sm:bottom-6',
|
|
298
|
+
className,
|
|
299
|
+
)}
|
|
300
|
+
>
|
|
301
|
+
<TooltipProvider delayDuration={0}>
|
|
302
|
+
<div
|
|
303
|
+
data-surface="viewer-dock"
|
|
304
|
+
className="corner-smooth pointer-events-auto flex h-12 max-w-[calc(100vw-1rem)] flex-row items-center justify-center gap-0.5 overflow-hidden rounded-2xl border border-border/40 bg-background/95 p-1 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out sm:h-14 sm:gap-1.5 sm:p-1.5"
|
|
369
305
|
>
|
|
370
|
-
{
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
306
|
+
{((canShowScans && (!glbActive || glbHasScans)) ||
|
|
307
|
+
(canShowGuides && (!glbActive || glbHasGuides))) && (
|
|
308
|
+
<>
|
|
309
|
+
<VisibilityMenu
|
|
310
|
+
canShowGuides={canShowGuides && (!glbActive || glbHasGuides)}
|
|
311
|
+
canShowScans={canShowScans && (!glbActive || glbHasScans)}
|
|
312
|
+
/>
|
|
313
|
+
<div className="mx-1 h-5 w-px bg-border/40" />
|
|
314
|
+
</>
|
|
315
|
+
)}
|
|
374
316
|
|
|
375
|
-
|
|
376
|
-
{!glbActive && (
|
|
317
|
+
{/* Level mode */}
|
|
377
318
|
<ActionButton
|
|
378
319
|
className={
|
|
379
|
-
|
|
380
|
-
? '
|
|
381
|
-
: 'bg-
|
|
320
|
+
levelMode === 'stacked'
|
|
321
|
+
? 'hover:bg-white/5 hover:text-amber-400'
|
|
322
|
+
: 'bg-amber-500/20 text-amber-400'
|
|
382
323
|
}
|
|
383
|
-
label={
|
|
324
|
+
label={i18n._({
|
|
325
|
+
id: 'viewer.levels.label',
|
|
326
|
+
message: 'Levels: {mode}',
|
|
327
|
+
values: { mode: levelModeLabel(levelMode, i18n) },
|
|
328
|
+
})}
|
|
384
329
|
onClick={() => {
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
330
|
+
if (levelMode === 'manual') return useViewer.getState().setLevelMode('stacked')
|
|
331
|
+
const modes: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo']
|
|
332
|
+
const nextIndex = (modes.indexOf(levelMode as any) + 1) % modes.length
|
|
333
|
+
useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked')
|
|
388
334
|
}}
|
|
389
335
|
size="icon"
|
|
390
336
|
tooltipClassName="dark"
|
|
391
337
|
tooltipSide="top"
|
|
392
338
|
variant="ghost"
|
|
393
339
|
>
|
|
394
|
-
<
|
|
340
|
+
{levelMode === 'solo' && <Diamond className="h-6 w-6" />}
|
|
341
|
+
{levelMode === 'exploded' && <Layers2 className="h-6 w-6" />}
|
|
342
|
+
{(levelMode === 'stacked' || levelMode === 'manual') && (
|
|
343
|
+
<Layers className="h-6 w-6" />
|
|
344
|
+
)}
|
|
395
345
|
</ActionButton>
|
|
396
|
-
)}
|
|
397
346
|
|
|
398
|
-
|
|
347
|
+
{/* Wall mode — parametric only; baked GLB walls are fixed-height. */}
|
|
348
|
+
{!glbActive && (
|
|
349
|
+
<ActionButton
|
|
350
|
+
className={
|
|
351
|
+
safeWallMode === 'cutaway'
|
|
352
|
+
? 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0'
|
|
353
|
+
: 'bg-white/10'
|
|
354
|
+
}
|
|
355
|
+
label={i18n._({
|
|
356
|
+
id: 'viewer.walls.label',
|
|
357
|
+
message: 'Walls: {mode}',
|
|
358
|
+
values: { mode: wallModeLabel(safeWallMode, i18n) },
|
|
359
|
+
})}
|
|
360
|
+
onClick={() => {
|
|
361
|
+
const modes: ('cutaway' | 'up' | 'down')[] = ['cutaway', 'up', 'down']
|
|
362
|
+
const nextIndex = (modes.indexOf(safeWallMode) + 1) % modes.length
|
|
363
|
+
useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway')
|
|
364
|
+
}}
|
|
365
|
+
size="icon"
|
|
366
|
+
tooltipClassName="dark"
|
|
367
|
+
tooltipSide="top"
|
|
368
|
+
variant="ghost"
|
|
369
|
+
>
|
|
370
|
+
<img
|
|
371
|
+
alt={wallModeLabel(safeWallMode, i18n)}
|
|
372
|
+
className="h-[28px] w-[28px]"
|
|
373
|
+
height={28}
|
|
374
|
+
src={wallModeConfig[safeWallMode].icon}
|
|
375
|
+
width={28}
|
|
376
|
+
/>
|
|
377
|
+
</ActionButton>
|
|
378
|
+
)}
|
|
399
379
|
|
|
400
|
-
|
|
380
|
+
<div className="mx-1 h-5 w-px bg-border/40" />
|
|
401
381
|
|
|
402
|
-
|
|
382
|
+
<DisplayMenu />
|
|
403
383
|
|
|
404
|
-
|
|
405
|
-
<ActionButton
|
|
406
|
-
className={
|
|
407
|
-
walkthroughActive
|
|
408
|
-
? 'bg-emerald-500/20 text-emerald-400'
|
|
409
|
-
: 'hover:bg-white/5 hover:text-emerald-400'
|
|
410
|
-
}
|
|
411
|
-
label={`Walkthrough: ${walkthroughActive ? 'On' : 'Off'}`}
|
|
412
|
-
onClick={onWalkthroughToggle}
|
|
413
|
-
size="icon"
|
|
414
|
-
tooltipClassName="dark"
|
|
415
|
-
tooltipSide="top"
|
|
416
|
-
variant="ghost"
|
|
417
|
-
>
|
|
418
|
-
<Footprints className="h-6 w-6" />
|
|
419
|
-
</ActionButton>
|
|
384
|
+
<div className="mx-1 h-5 w-px bg-border/40" />
|
|
420
385
|
|
|
421
|
-
|
|
386
|
+
{/* Walkthrough */}
|
|
387
|
+
<ActionButton
|
|
388
|
+
className={
|
|
389
|
+
walkthroughActive
|
|
390
|
+
? 'bg-emerald-500/20 text-emerald-400'
|
|
391
|
+
: 'hover:bg-white/5 hover:text-emerald-400'
|
|
392
|
+
}
|
|
393
|
+
label={i18n._({
|
|
394
|
+
id: 'viewer.walkthrough.label',
|
|
395
|
+
message: 'Walkthrough: {state}',
|
|
396
|
+
values: {
|
|
397
|
+
state: walkthroughActive
|
|
398
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
399
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' }),
|
|
400
|
+
},
|
|
401
|
+
})}
|
|
402
|
+
onClick={onWalkthroughToggle}
|
|
403
|
+
size="icon"
|
|
404
|
+
tooltipClassName="dark"
|
|
405
|
+
tooltipSide="top"
|
|
406
|
+
variant="ghost"
|
|
407
|
+
>
|
|
408
|
+
<Footprints className="h-6 w-6" />
|
|
409
|
+
</ActionButton>
|
|
422
410
|
|
|
423
|
-
|
|
424
|
-
|
|
411
|
+
{/* Drone — flying needs a keyboard. */}
|
|
412
|
+
{onDroneToggle && !isMobile && (
|
|
413
|
+
<ActionButton
|
|
414
|
+
className={
|
|
415
|
+
droneActive
|
|
416
|
+
? 'bg-emerald-500/20 text-emerald-400'
|
|
417
|
+
: 'hover:bg-white/5 hover:text-emerald-400'
|
|
418
|
+
}
|
|
419
|
+
label={i18n._({
|
|
420
|
+
id: 'viewer.drone.label',
|
|
421
|
+
message: 'Drone: {state}',
|
|
422
|
+
values: {
|
|
423
|
+
state: droneActive
|
|
424
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
425
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' }),
|
|
426
|
+
},
|
|
427
|
+
})}
|
|
428
|
+
onClick={onDroneToggle}
|
|
429
|
+
size="icon"
|
|
430
|
+
tooltipClassName="dark"
|
|
431
|
+
tooltipSide="top"
|
|
432
|
+
variant="ghost"
|
|
433
|
+
>
|
|
434
|
+
<Drone className="h-6 w-6" />
|
|
435
|
+
</ActionButton>
|
|
436
|
+
)}
|
|
425
437
|
|
|
426
|
-
|
|
427
|
-
<ActionButton
|
|
428
|
-
className="group hidden hover:bg-white/5 sm:inline-flex"
|
|
429
|
-
label="Orbit left"
|
|
430
|
-
onClick={() => emitter.emit('camera-controls:orbit-ccw')}
|
|
431
|
-
size="icon"
|
|
432
|
-
tooltipClassName="dark"
|
|
433
|
-
tooltipSide="top"
|
|
434
|
-
variant="ghost"
|
|
435
|
-
>
|
|
436
|
-
<img
|
|
437
|
-
alt="Orbit left"
|
|
438
|
-
className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
439
|
-
src="/icons/rotate.webp"
|
|
440
|
-
/>
|
|
441
|
-
</ActionButton>
|
|
438
|
+
<div className="mx-1 h-5 w-px bg-border/40" />
|
|
442
439
|
|
|
443
|
-
|
|
444
|
-
className="
|
|
445
|
-
label="Orbit right"
|
|
446
|
-
onClick={() => emitter.emit('camera-controls:orbit-cw')}
|
|
447
|
-
size="icon"
|
|
448
|
-
tooltipClassName="dark"
|
|
449
|
-
tooltipSide="top"
|
|
450
|
-
variant="ghost"
|
|
451
|
-
>
|
|
452
|
-
<img
|
|
453
|
-
alt="Orbit right"
|
|
454
|
-
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
455
|
-
src="/icons/rotate.webp"
|
|
456
|
-
/>
|
|
457
|
-
</ActionButton>
|
|
440
|
+
{/* Follow Pascal */}
|
|
441
|
+
<FollowPascalToggle className="hidden sm:inline-flex" tooltipClassName="dark" />
|
|
458
442
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
443
|
+
{/* Camera actions */}
|
|
444
|
+
<ActionButton
|
|
445
|
+
className="group hidden hover:bg-white/5 sm:inline-flex"
|
|
446
|
+
label={i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' })}
|
|
447
|
+
onClick={() => emitter.emit('camera-controls:orbit-ccw')}
|
|
448
|
+
size="icon"
|
|
449
|
+
tooltipClassName="dark"
|
|
450
|
+
tooltipSide="top"
|
|
451
|
+
variant="ghost"
|
|
452
|
+
>
|
|
453
|
+
<img
|
|
454
|
+
alt={i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' })}
|
|
455
|
+
className="h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
456
|
+
src="/icons/rotate.webp"
|
|
457
|
+
/>
|
|
458
|
+
</ActionButton>
|
|
459
|
+
|
|
460
|
+
<ActionButton
|
|
461
|
+
className="group hidden hover:bg-white/5 sm:inline-flex"
|
|
462
|
+
label={i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' })}
|
|
463
|
+
onClick={() => emitter.emit('camera-controls:orbit-cw')}
|
|
464
|
+
size="icon"
|
|
465
|
+
tooltipClassName="dark"
|
|
466
|
+
tooltipSide="top"
|
|
467
|
+
variant="ghost"
|
|
468
|
+
>
|
|
469
|
+
<img
|
|
470
|
+
alt={i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' })}
|
|
471
|
+
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
472
|
+
src="/icons/rotate.webp"
|
|
473
|
+
/>
|
|
474
|
+
</ActionButton>
|
|
475
|
+
|
|
476
|
+
<ActionButton
|
|
477
|
+
className="group hover:bg-white/5"
|
|
478
|
+
label={i18n._({ id: 'viewer.camera.topView', message: 'Top view' })}
|
|
479
|
+
onClick={() => emitter.emit('camera-controls:top-view')}
|
|
480
|
+
size="icon"
|
|
481
|
+
tooltipClassName="dark"
|
|
482
|
+
tooltipSide="top"
|
|
483
|
+
variant="ghost"
|
|
484
|
+
>
|
|
485
|
+
<img
|
|
486
|
+
alt={i18n._({ id: 'viewer.camera.topView', message: 'Top view' })}
|
|
487
|
+
className="h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100"
|
|
488
|
+
src="/icons/topview.webp"
|
|
489
|
+
/>
|
|
490
|
+
</ActionButton>
|
|
491
|
+
</div>
|
|
492
|
+
</TooltipProvider>
|
|
493
|
+
</div>
|
|
494
|
+
</>
|
|
477
495
|
)
|
|
478
496
|
}
|