@pascal-app/viewer 1.1.0 → 1.1.2
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/README.md +157 -0
- package/dist/components/viewer/drone-controller.d.ts.map +1 -1
- package/dist/components/viewer/drone-controller.js +1 -5
- package/dist/components/viewer/first-person/build-collider-world.d.ts +14 -0
- package/dist/components/viewer/first-person/build-collider-world.d.ts.map +1 -0
- package/dist/components/viewer/first-person/build-collider-world.js +426 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts +22 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts.map +1 -0
- package/dist/components/viewer/first-person/ground-holes.js +116 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts +27 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts.map +1 -0
- package/dist/components/viewer/first-person/mechanism-targets.js +79 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts +32 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts.map +1 -0
- package/dist/components/viewer/first-person/terrain-collider.js +175 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +3 -4
- package/dist/components/viewer/live-walkthrough-controller.d.ts +17 -0
- package/dist/components/viewer/live-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/live-walkthrough-controller.js +1449 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +22 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/lib/daylight/room-map.d.ts +29 -4
- package/dist/lib/daylight/room-map.d.ts.map +1 -1
- package/dist/lib/daylight/room-map.js +219 -36
- package/dist/lib/daylight/room-scene.d.ts.map +1 -1
- package/dist/lib/daylight/room-scene.js +9 -3
- package/dist/lib/daylight/rooms.d.ts +15 -6
- package/dist/lib/daylight/rooms.d.ts.map +1 -1
- package/dist/lib/daylight/rooms.js +49 -9
- package/dist/lib/measurement-presentation.d.ts +2 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -1
- package/dist/lib/measurement-presentation.js +6 -0
- package/dist/lib/typing-target.d.ts +2 -0
- package/dist/lib/typing-target.d.ts.map +1 -0
- package/dist/lib/typing-target.js +7 -0
- package/dist/lib/viewer-config.d.ts +6 -0
- package/dist/lib/viewer-config.d.ts.map +1 -1
- package/dist/lib/viewer-config.js +2 -0
- package/dist/sfx/audio-settings.d.ts +81 -0
- package/dist/sfx/audio-settings.d.ts.map +1 -0
- package/dist/sfx/audio-settings.js +166 -0
- package/dist/sfx/index.d.ts +6 -0
- package/dist/sfx/index.d.ts.map +1 -0
- package/dist/sfx/index.js +5 -0
- package/dist/sfx/interaction-sounds.d.ts +7 -0
- package/dist/sfx/interaction-sounds.d.ts.map +1 -0
- package/dist/sfx/interaction-sounds.js +39 -0
- package/dist/sfx/player.d.ts +31 -0
- package/dist/sfx/player.d.ts.map +1 -0
- package/dist/sfx/player.js +255 -0
- package/dist/sfx/sounds.d.ts +60 -0
- package/dist/sfx/sounds.d.ts.map +1 -0
- package/dist/sfx/sounds.js +25 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts +8 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts.map +1 -0
- package/dist/sfx/use-viewer-ui-sounds.js +42 -0
- package/dist/store/use-first-person-hud.d.ts +13 -0
- package/dist/store/use-first-person-hud.d.ts.map +1 -0
- package/dist/store/use-first-person-hud.js +21 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +27 -13
- package/dist/ui/components/action-button.d.ts +14 -0
- package/dist/ui/components/action-button.d.ts.map +1 -0
- package/dist/ui/components/action-button.js +21 -0
- package/dist/ui/components/action-menu-placement.d.ts +7 -0
- package/dist/ui/components/action-menu-placement.d.ts.map +1 -0
- package/dist/ui/components/action-menu-placement.js +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts.map +1 -0
- package/dist/ui/components/floorplan-compass-button.js +22 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts +6 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts.map +1 -0
- package/dist/ui/components/follow-pascal-toggle.js +21 -0
- package/dist/ui/components/localized-html.d.ts +9 -0
- package/dist/ui/components/localized-html.d.ts.map +1 -0
- package/dist/ui/components/localized-html.js +16 -0
- package/dist/ui/components/render-settings-menu.d.ts +9 -0
- package/dist/ui/components/render-settings-menu.d.ts.map +1 -0
- package/dist/ui/components/render-settings-menu.js +145 -0
- package/dist/ui/components/rooms/room-highlight.d.ts +10 -0
- package/dist/ui/components/rooms/room-highlight.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-highlight.js +56 -0
- package/dist/ui/components/rooms/room-labels.d.ts +278 -0
- package/dist/ui/components/rooms/room-labels.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-labels.js +1295 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts +14 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts.map +1 -0
- package/dist/ui/components/rooms/viewer-rooms.js +123 -0
- package/dist/ui/components/scene-loader.d.ts +18 -0
- package/dist/ui/components/scene-loader.d.ts.map +1 -0
- package/dist/ui/components/scene-loader.js +29 -0
- package/dist/ui/components/sky-control.d.ts +37 -0
- package/dist/ui/components/sky-control.d.ts.map +1 -0
- package/dist/ui/components/sky-control.js +382 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts +4 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts.map +1 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.js +117 -0
- package/dist/ui/components/viewer-controls-bar.d.ts +20 -0
- package/dist/ui/components/viewer-controls-bar.d.ts.map +1 -0
- package/dist/ui/components/viewer-controls-bar.js +130 -0
- package/dist/ui/components/viewer-hover-label.d.ts +14 -0
- package/dist/ui/components/viewer-hover-label.d.ts.map +1 -0
- package/dist/ui/components/viewer-hover-label.js +28 -0
- package/dist/ui/components/viewer-scene-header.d.ts +34 -0
- package/dist/ui/components/viewer-scene-header.d.ts.map +1 -0
- package/dist/ui/components/viewer-scene-header.js +92 -0
- package/dist/ui/components/viewer-selection-manager.d.ts +7 -0
- package/dist/ui/components/viewer-selection-manager.d.ts.map +1 -0
- package/dist/ui/components/viewer-selection-manager.js +116 -0
- package/dist/ui/components/viewer-stage-modes.d.ts +7 -0
- package/dist/ui/components/viewer-stage-modes.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-modes.js +42 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts +10 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-switcher.js +16 -0
- package/dist/ui/components/viewer-stage.d.ts +25 -0
- package/dist/ui/components/viewer-stage.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage.js +107 -0
- package/dist/ui/components/walkthrough-hud.d.ts +29 -0
- package/dist/ui/components/walkthrough-hud.d.ts.map +1 -0
- package/dist/ui/components/walkthrough-hud.js +55 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-camera-sync.js +157 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts +61 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.js +280 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts +54 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.js +317 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts +24 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-label-angle.js +60 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts +18 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts +26 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.js +173 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts +8 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.js +21 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts +19 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview.js +700 -0
- package/dist/ui/hooks/use-mobile.d.ts +2 -0
- package/dist/ui/hooks/use-mobile.d.ts.map +1 -0
- package/dist/ui/hooks/use-mobile.js +14 -0
- package/dist/ui/index.d.ts +49 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +48 -0
- package/dist/ui/lib/below-grade.d.ts +38 -0
- package/dist/ui/lib/below-grade.d.ts.map +1 -0
- package/dist/ui/lib/below-grade.js +102 -0
- package/dist/ui/lib/camera-pose.d.ts +19 -0
- package/dist/ui/lib/camera-pose.d.ts.map +1 -0
- package/dist/ui/lib/camera-pose.js +113 -0
- package/dist/ui/lib/cn.d.ts +3 -0
- package/dist/ui/lib/cn.d.ts.map +1 -0
- package/dist/ui/lib/cn.js +5 -0
- package/dist/ui/lib/level-name.d.ts +10 -0
- package/dist/ui/lib/level-name.d.ts.map +1 -0
- package/dist/ui/lib/level-name.js +33 -0
- package/dist/ui/lib/node-display-label.d.ts +8 -0
- package/dist/ui/lib/node-display-label.d.ts.map +1 -0
- package/dist/ui/lib/node-display-label.js +36 -0
- package/dist/ui/lib/room-area.d.ts +3 -0
- package/dist/ui/lib/room-area.d.ts.map +1 -0
- package/dist/ui/lib/room-area.js +8 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts +46 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts.map +1 -0
- package/dist/ui/lib/room-assembly-overlay.js +222 -0
- package/dist/ui/lib/room-name-messages.d.ts +28 -0
- package/dist/ui/lib/room-name-messages.d.ts.map +1 -0
- package/dist/ui/lib/room-name-messages.js +112 -0
- package/dist/ui/lib/room-selection.d.ts +58 -0
- package/dist/ui/lib/room-selection.d.ts.map +1 -0
- package/dist/ui/lib/room-selection.js +297 -0
- package/dist/ui/lib/scene-theme-name.d.ts +4 -0
- package/dist/ui/lib/scene-theme-name.d.ts.map +1 -0
- package/dist/ui/lib/scene-theme-name.js +22 -0
- package/dist/ui/lib/selection-modifiers.d.ts +20 -0
- package/dist/ui/lib/selection-modifiers.d.ts.map +1 -0
- package/dist/ui/lib/selection-modifiers.js +15 -0
- package/dist/ui/lib/viewer-selection.d.ts +132 -0
- package/dist/ui/lib/viewer-selection.d.ts.map +1 -0
- package/dist/ui/lib/viewer-selection.js +462 -0
- package/dist/ui/lib/zone-content.d.ts +6 -0
- package/dist/ui/lib/zone-content.d.ts.map +1 -0
- package/dist/ui/lib/zone-content.js +96 -0
- package/dist/ui/primitives/button.d.ts +11 -0
- package/dist/ui/primitives/button.d.ts.map +1 -0
- package/dist/ui/primitives/button.js +35 -0
- package/dist/ui/primitives/dropdown-menu.d.ts +26 -0
- package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/ui/primitives/dropdown-menu.js +53 -0
- package/dist/ui/primitives/popover.d.ts +8 -0
- package/dist/ui/primitives/popover.d.ts.map +1 -0
- package/dist/ui/primitives/popover.js +17 -0
- package/dist/ui/primitives/switch.d.ts +5 -0
- package/dist/ui/primitives/switch.d.ts.map +1 -0
- package/dist/ui/primitives/switch.js +8 -0
- package/dist/ui/primitives/tooltip.d.ts +8 -0
- package/dist/ui/primitives/tooltip.d.ts.map +1 -0
- package/dist/ui/primitives/tooltip.js +17 -0
- package/dist/ui/store/camera-pose-store.d.ts +17 -0
- package/dist/ui/store/camera-pose-store.d.ts.map +1 -0
- package/dist/ui/store/camera-pose-store.js +15 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts +26 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts.map +1 -0
- package/dist/ui/store/navigation-sync-pose-store.js +16 -0
- package/dist/ui/store/use-follow-pascal.d.ts +30 -0
- package/dist/ui/store/use-follow-pascal.d.ts.map +1 -0
- package/dist/ui/store/use-follow-pascal.js +28 -0
- package/dist/ui/theme.css +339 -0
- package/package.json +34 -7
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { emitter } from '@pascal-app/core';
|
|
3
|
+
import { useThree } from '@react-three/fiber';
|
|
4
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import { Vector3 } from 'three';
|
|
6
|
+
import useViewer from '../../store/use-viewer';
|
|
7
|
+
import { normalizeCameraPose } from '../lib/camera-pose';
|
|
8
|
+
import { publishCameraPose } from '../store/camera-pose-store';
|
|
9
|
+
const position = new Vector3();
|
|
10
|
+
const target = new Vector3();
|
|
11
|
+
function isPerspectiveCamera(camera) {
|
|
12
|
+
return camera.isPerspectiveCamera === true;
|
|
13
|
+
}
|
|
14
|
+
function isOrthographicCamera(camera) {
|
|
15
|
+
return camera.isOrthographicCamera === true;
|
|
16
|
+
}
|
|
17
|
+
function cameraViewWidth(camera, distance, width, height) {
|
|
18
|
+
if (isPerspectiveCamera(camera)) {
|
|
19
|
+
const fov = (camera.getEffectiveFOV() * Math.PI) / 180;
|
|
20
|
+
return Math.max(0.001, 2 * distance * Math.tan(fov / 2) * (width / Math.max(height, 1)));
|
|
21
|
+
}
|
|
22
|
+
if (isOrthographicCamera(camera)) {
|
|
23
|
+
return Math.max(0.001, (camera.right - camera.left) / camera.zoom);
|
|
24
|
+
}
|
|
25
|
+
return Math.max(0.001, distance);
|
|
26
|
+
}
|
|
27
|
+
function applyViewWidth(controls, camera, viewWidth, width, height) {
|
|
28
|
+
if (isPerspectiveCamera(camera)) {
|
|
29
|
+
const fov = (camera.getEffectiveFOV() * Math.PI) / 180;
|
|
30
|
+
const denominator = 2 * Math.tan(fov / 2) * (width / Math.max(height, 1));
|
|
31
|
+
if (denominator > 0)
|
|
32
|
+
controls.dollyTo(Math.max(0.001, viewWidth / denominator), false);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (isOrthographicCamera(camera) && viewWidth > 0) {
|
|
36
|
+
controls.zoomTo(Math.max(0.001, (camera.right - camera.left) / viewWidth), false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function useViewerCameraNavigationSync(controls) {
|
|
40
|
+
const camera = useThree((state) => state.camera);
|
|
41
|
+
const size = useThree((state) => state.size);
|
|
42
|
+
const suppressPublish = useRef(false);
|
|
43
|
+
const pendingPose = useRef(null);
|
|
44
|
+
const publishCurrentPose = useCallback(() => {
|
|
45
|
+
const control = controls.current;
|
|
46
|
+
if (!control || suppressPublish.current)
|
|
47
|
+
return;
|
|
48
|
+
control.getPosition(position, false);
|
|
49
|
+
control.getTarget(target, false);
|
|
50
|
+
const projection = isPerspectiveCamera(camera)
|
|
51
|
+
? 'perspective'
|
|
52
|
+
: isOrthographicCamera(camera)
|
|
53
|
+
? 'orthographic'
|
|
54
|
+
: null;
|
|
55
|
+
if (!projection)
|
|
56
|
+
return;
|
|
57
|
+
const pose = normalizeCameraPose({
|
|
58
|
+
position: [position.x, position.y, position.z],
|
|
59
|
+
target: [target.x, target.y, target.z],
|
|
60
|
+
projection,
|
|
61
|
+
viewWidth: cameraViewWidth(camera, position.distanceTo(target), size.width, size.height),
|
|
62
|
+
...(isPerspectiveCamera(camera) ? { fov: camera.fov } : {}),
|
|
63
|
+
});
|
|
64
|
+
if (pose)
|
|
65
|
+
publishCameraPose(pose);
|
|
66
|
+
}, [camera, controls, size.height, size.width]);
|
|
67
|
+
const applyPendingPose = useCallback(() => {
|
|
68
|
+
const control = controls.current;
|
|
69
|
+
const pose = pendingPose.current;
|
|
70
|
+
if (!(control && pose))
|
|
71
|
+
return;
|
|
72
|
+
const projectionMatches = (pose.projection === 'perspective' && isPerspectiveCamera(camera)) ||
|
|
73
|
+
(pose.projection === 'orthographic' && isOrthographicCamera(camera));
|
|
74
|
+
if (!projectionMatches)
|
|
75
|
+
return;
|
|
76
|
+
pendingPose.current = null;
|
|
77
|
+
suppressPublish.current = true;
|
|
78
|
+
try {
|
|
79
|
+
if (pose.fov !== undefined && isPerspectiveCamera(camera)) {
|
|
80
|
+
camera.fov = pose.fov;
|
|
81
|
+
camera.updateProjectionMatrix();
|
|
82
|
+
}
|
|
83
|
+
control.setLookAt(pose.position[0], pose.position[1], pose.position[2], pose.target[0], pose.target[1], pose.target[2], false);
|
|
84
|
+
if (pose.viewWidth !== undefined) {
|
|
85
|
+
applyViewWidth(control, camera, pose.viewWidth, size.width, size.height);
|
|
86
|
+
}
|
|
87
|
+
control.update(0);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
suppressPublish.current = false;
|
|
91
|
+
publishCurrentPose();
|
|
92
|
+
}
|
|
93
|
+
}, [camera, controls, publishCurrentPose, size.height, size.width]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
applyPendingPose();
|
|
96
|
+
}, [applyPendingPose]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const applyPose = (value) => {
|
|
99
|
+
const pose = normalizeCameraPose(value);
|
|
100
|
+
if (!pose)
|
|
101
|
+
return;
|
|
102
|
+
pendingPose.current = pose;
|
|
103
|
+
if (useViewer.getState().cameraMode !== pose.projection) {
|
|
104
|
+
useViewer.getState().setCameraMode(pose.projection);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
applyPendingPose();
|
|
108
|
+
};
|
|
109
|
+
emitter.on('camera-controls:apply-pose', applyPose);
|
|
110
|
+
return () => emitter.off('camera-controls:apply-pose', applyPose);
|
|
111
|
+
}, [applyPendingPose]);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const frame = requestAnimationFrame(publishCurrentPose);
|
|
114
|
+
return () => cancelAnimationFrame(frame);
|
|
115
|
+
}, [publishCurrentPose]);
|
|
116
|
+
return publishCurrentPose;
|
|
117
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ViewerControlsBarProps = {
|
|
2
|
+
canShowScans?: boolean;
|
|
3
|
+
canShowGuides?: boolean;
|
|
4
|
+
/** A baked GLB is the active artifact: hide controls it can't honor (wall
|
|
5
|
+
* modes aren't baked into the GLB). */
|
|
6
|
+
glbActive?: boolean;
|
|
7
|
+
/** In GLB mode, whether scans/guides were re-added from scene data — so the
|
|
8
|
+
* visibility control surfaces the matching toggle even though the artifact
|
|
9
|
+
* itself carries none. */
|
|
10
|
+
glbHasScans?: boolean;
|
|
11
|
+
glbHasGuides?: boolean;
|
|
12
|
+
walkthroughActive?: boolean;
|
|
13
|
+
onWalkthroughToggle: () => void;
|
|
14
|
+
/** The free-flying drone camera; its button shows only with a handler, and never on touch. */
|
|
15
|
+
droneActive?: boolean;
|
|
16
|
+
onDroneToggle?: () => void;
|
|
17
|
+
className?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const ViewerControlsBar: ({ canShowScans, canShowGuides, glbActive, glbHasScans, glbHasGuides, walkthroughActive, onWalkthroughToggle, droneActive, onDroneToggle, className, }: ViewerControlsBarProps) => import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=viewer-controls-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-controls-bar.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-controls-bar.tsx"],"names":[],"mappings":"AAsPA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;4CACwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;+BAE2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,mBAAmB,EAAE,MAAM,IAAI,CAAA;IAC/B,8FAA8F;IAC9F,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,iBAAiB,0JAW3B,sBAAsB,gCA2NxB,CAAA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { emitter, usePascalI18n } from '@pascal-app/core';
|
|
4
|
+
import { Camera, Check, Contrast, Diamond, Drone, Eye, EyeOff, Footprints, Layers, Layers2, Palette, SlidersHorizontal, Square, SwatchBook, } from 'lucide-react';
|
|
5
|
+
import { CLAY_PALETTE } from '../../lib/materials';
|
|
6
|
+
import { getSceneTheme, SCENE_THEMES } from '../../lib/scene-themes';
|
|
7
|
+
import { getViewerConfig } from '../../lib/viewer-config';
|
|
8
|
+
import useViewer from '../../store/use-viewer';
|
|
9
|
+
import { useIsMobile } from '../hooks/use-mobile';
|
|
10
|
+
import { cn } from '../lib/cn';
|
|
11
|
+
import { sceneThemeName } from '../lib/scene-theme-name';
|
|
12
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '../primitives/dropdown-menu';
|
|
13
|
+
import { TooltipProvider } from '../primitives/tooltip';
|
|
14
|
+
import { ActionButton } from './action-button';
|
|
15
|
+
import { FollowPascalToggle } from './follow-pascal-toggle';
|
|
16
|
+
import { DaylightMenu, EdgesToggleItem, QualityMenu } from './render-settings-menu';
|
|
17
|
+
import { SKY_DOCK_HEIGHT, SkyControl } from './sky-control';
|
|
18
|
+
// Resolved per render from `configureViewer({ iconsBaseUrl })`: app icons, not item assets.
|
|
19
|
+
const iconUrl = (name) => `${getViewerConfig().iconsBaseUrl}/${name}.webp`;
|
|
20
|
+
const wallModeConfig = {
|
|
21
|
+
up: { icon: 'room' },
|
|
22
|
+
cutaway: { icon: 'wallcut' },
|
|
23
|
+
down: { icon: 'walllow' },
|
|
24
|
+
};
|
|
25
|
+
function levelModeLabel(mode, i18n) {
|
|
26
|
+
if (mode === 'stacked')
|
|
27
|
+
return i18n._({ id: 'viewer.levels.stacked', message: 'Stacked' });
|
|
28
|
+
if (mode === 'exploded')
|
|
29
|
+
return i18n._({ id: 'viewer.levels.exploded', message: 'Exploded' });
|
|
30
|
+
if (mode === 'solo')
|
|
31
|
+
return i18n._({ id: 'viewer.levels.solo', message: 'Solo' });
|
|
32
|
+
return i18n._({ id: 'viewer.levels.manual', message: 'Manual' });
|
|
33
|
+
}
|
|
34
|
+
function wallModeLabel(mode, i18n) {
|
|
35
|
+
if (mode === 'up')
|
|
36
|
+
return i18n._({ id: 'viewer.walls.fullHeight', message: 'Full height' });
|
|
37
|
+
if (mode === 'down')
|
|
38
|
+
return i18n._({ id: 'viewer.walls.low', message: 'Low' });
|
|
39
|
+
return i18n._({ id: 'viewer.walls.cutaway', message: 'Cutaway' });
|
|
40
|
+
}
|
|
41
|
+
// Keep the dropdown open when flipping an in-place toggle row.
|
|
42
|
+
const keepOpen = (event, fn) => {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
fn();
|
|
45
|
+
};
|
|
46
|
+
// Scans + guides folded into one control. A baked GLB carries none of its own,
|
|
47
|
+
// but the GLB viewer re-adds them from scene data when the privacy flags allow,
|
|
48
|
+
// so the toggle shows for whichever exist.
|
|
49
|
+
function VisibilityMenu({ canShowScans, canShowGuides, }) {
|
|
50
|
+
const i18n = usePascalI18n();
|
|
51
|
+
const showScans = useViewer((s) => s.showScans);
|
|
52
|
+
const showGuides = useViewer((s) => s.showGuides);
|
|
53
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(ActionButton, { className: "hover:bg-white/5 hover:text-foreground", label: i18n._({ id: 'viewer.visibility.title', message: 'Visibility' }), size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx(Eye, { className: "h-5 w-5" }) }) }), _jsxs(DropdownMenuContent, { align: "center", className: "dark min-w-44", side: "top", children: [canShowScans && (_jsxs(DropdownMenuItem, { onSelect: (e) => keepOpen(e, () => useViewer.getState().setShowScans(!showScans)), children: [_jsx("img", { alt: "", className: "h-4 w-4 object-contain", src: iconUrl('mesh') }), _jsx("span", { children: i18n._({ id: 'viewer.visibility.scans', message: 'Scans' }) }), showScans ? (_jsx(Eye, { className: "ml-auto h-4 w-4 text-foreground" })) : (_jsx(EyeOff, { className: "ml-auto h-4 w-4 text-muted-foreground" }))] })), canShowGuides && (_jsxs(DropdownMenuItem, { onSelect: (e) => keepOpen(e, () => useViewer.getState().setShowGuides(!showGuides)), children: [_jsx("img", { alt: "", className: "h-4 w-4 object-contain", src: iconUrl('floorplan') }), _jsx("span", { children: i18n._({ id: 'viewer.visibility.guides', message: 'Guides' }) }), showGuides ? (_jsx(Eye, { className: "ml-auto h-4 w-4 text-foreground" })) : (_jsx(EyeOff, { className: "ml-auto h-4 w-4 text-muted-foreground" }))] }))] })] }));
|
|
54
|
+
}
|
|
55
|
+
// One "Display" button gathering shadows, camera projection, colors, render
|
|
56
|
+
// mode, scene theme and edges.
|
|
57
|
+
function DisplayMenu() {
|
|
58
|
+
const i18n = usePascalI18n();
|
|
59
|
+
const cameraMode = useViewer((s) => s.cameraMode);
|
|
60
|
+
const textures = useViewer((s) => s.textures);
|
|
61
|
+
const shadows = useViewer((s) => s.shadows);
|
|
62
|
+
const sceneTheme = useViewer((s) => s.sceneTheme);
|
|
63
|
+
const activeTheme = getSceneTheme(sceneTheme);
|
|
64
|
+
return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(ActionButton, { className: "hover:bg-white/5 hover:text-foreground", label: i18n._({ id: 'viewer.display.title', message: 'Display settings' }), size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx(SlidersHorizontal, { className: "h-5 w-5" }) }) }), _jsxs(DropdownMenuContent, { align: "center", className: "dark min-w-56", side: "top", children: [_jsxs(DropdownMenuItem, { onSelect: (e) => keepOpen(e, () => useViewer.getState().setShadows(!shadows)), children: [_jsx(Contrast, { className: "h-4 w-4" }), _jsx("span", { children: i18n._({ id: 'viewer.display.shadows', message: 'Shadows' }) }), _jsx("span", { className: "ml-auto text-muted-foreground text-xs", children: shadows
|
|
65
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
66
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' }) })] }), _jsxs(DropdownMenuItem, { onSelect: (e) => keepOpen(e, () => useViewer
|
|
67
|
+
.getState()
|
|
68
|
+
.setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective')), children: [_jsx(Camera, { className: "h-4 w-4" }), _jsx("span", { children: i18n._({ id: 'viewer.display.camera', message: 'Camera' }) }), _jsx("span", { className: "ml-auto text-muted-foreground text-xs", children: cameraMode === 'perspective'
|
|
69
|
+
? i18n._({ id: 'viewer.display.perspective', message: 'Perspective' })
|
|
70
|
+
: i18n._({ id: 'viewer.display.orthographic', message: 'Orthographic' }) })] }), _jsxs(DropdownMenuItem, { onSelect: (e) => keepOpen(e, () => useViewer.getState().setTextures(!textures)), children: [textures ? _jsx(Palette, { className: "h-4 w-4" }) : _jsx(Square, { className: "h-4 w-4" }), _jsx("span", { children: i18n._({ id: 'viewer.display.colors', message: 'Colors' }) }), _jsx("span", { className: "ml-auto text-muted-foreground text-xs", children: textures
|
|
71
|
+
? i18n._({ id: 'viewer.display.colored', message: 'Colored' })
|
|
72
|
+
: i18n._({ id: 'viewer.display.monochrome', message: 'Monochrome' }) })] }), _jsx(DropdownMenuSeparator, {}), _jsx(QualityMenu, { subContentClassName: "dark min-w-56" }), textures ? (_jsx(DaylightMenu, { subContentClassName: "dark min-w-48" })) : (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { children: [_jsx(SwatchBook, { className: "h-4 w-4" }), _jsx("span", { children: i18n._({ id: 'viewer.theme.title', message: 'Scene theme' }) }), _jsx("span", { className: "ml-auto truncate text-muted-foreground text-xs", children: sceneThemeName(activeTheme.id, activeTheme.name, i18n) })] }), _jsx(DropdownMenuSubContent, { className: "dark min-w-48", children: SCENE_THEMES.map((theme) => {
|
|
73
|
+
const swatches = ['wall', 'roof', 'floor', 'glazing'].map((role) => theme.clayTints?.[role] ?? CLAY_PALETTE[role]);
|
|
74
|
+
return (_jsxs(DropdownMenuItem, { className: "gap-2", onSelect: () => useViewer.getState().setSceneTheme(theme.id), children: [_jsx("span", { className: "grid h-5 w-5 shrink-0 grid-cols-2 overflow-hidden rounded-sm border border-black/10", style: { backgroundColor: theme.background }, children: swatches.map((color, index) => (_jsx("span", { style: { backgroundColor: color } }, `${theme.id}-${index}`))) }), _jsx("span", { children: sceneThemeName(theme.id, theme.name, i18n) }), sceneTheme === theme.id ? _jsx(Check, { className: "ml-auto h-4 w-4" }) : null] }, theme.id));
|
|
75
|
+
}) })] }), _jsx(EdgesToggleItem, {})] }))] })] }));
|
|
76
|
+
}
|
|
77
|
+
export const ViewerControlsBar = ({ canShowScans = true, canShowGuides = true, glbActive = false, glbHasScans = false, glbHasGuides = false, walkthroughActive = false, onWalkthroughToggle, droneActive = false, onDroneToggle, className, }) => {
|
|
78
|
+
const i18n = usePascalI18n();
|
|
79
|
+
const levelMode = useViewer((s) => s.levelMode);
|
|
80
|
+
const wallMode = useViewer((s) => s.wallMode);
|
|
81
|
+
const textures = useViewer((s) => s.textures);
|
|
82
|
+
const isMobile = useIsMobile();
|
|
83
|
+
// Sessions may carry a stale mode outside the cycle (e.g. the retired
|
|
84
|
+
// 'translucent'); render and cycle it as cutaway instead of crashing.
|
|
85
|
+
const safeWallMode = (wallMode in wallModeConfig ? wallMode : 'cutaway');
|
|
86
|
+
return (_jsxs(_Fragment, { children: [textures && !isMobile ? (_jsx(SkyControl, { dark: true, height: SKY_DOCK_HEIGHT.viewer, style: { left: 52 } })) : null, _jsx("div", { className: cn('dark absolute bottom-4 left-1/2 z-20 -translate-x-1/2 text-foreground sm:bottom-6', className), children: _jsx(TooltipProvider, { delayDuration: 0, children: _jsxs("div", { "data-surface": "viewer-dock", 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", children: [((canShowScans && (!glbActive || glbHasScans)) ||
|
|
87
|
+
(canShowGuides && (!glbActive || glbHasGuides))) && (_jsxs(_Fragment, { children: [_jsx(VisibilityMenu, { canShowGuides: canShowGuides && (!glbActive || glbHasGuides), canShowScans: canShowScans && (!glbActive || glbHasScans) }), _jsx("div", { className: "mx-1 h-5 w-px bg-border/40" })] })), _jsxs(ActionButton, { className: levelMode === 'stacked'
|
|
88
|
+
? 'hover:bg-white/5 hover:text-amber-400'
|
|
89
|
+
: 'bg-amber-500/20 text-amber-400', label: i18n._({
|
|
90
|
+
id: 'viewer.levels.label',
|
|
91
|
+
message: 'Levels: {mode}',
|
|
92
|
+
values: { mode: levelModeLabel(levelMode, i18n) },
|
|
93
|
+
}), onClick: () => {
|
|
94
|
+
if (levelMode === 'manual')
|
|
95
|
+
return useViewer.getState().setLevelMode('stacked');
|
|
96
|
+
const modes = ['stacked', 'exploded', 'solo'];
|
|
97
|
+
const nextIndex = (modes.indexOf(levelMode) + 1) % modes.length;
|
|
98
|
+
useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked');
|
|
99
|
+
}, size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: [levelMode === 'solo' && _jsx(Diamond, { className: "h-6 w-6" }), levelMode === 'exploded' && _jsx(Layers2, { className: "h-6 w-6" }), (levelMode === 'stacked' || levelMode === 'manual') && (_jsx(Layers, { className: "h-6 w-6" }))] }), !glbActive && (_jsx(ActionButton, { className: safeWallMode === 'cutaway'
|
|
100
|
+
? 'opacity-60 grayscale hover:bg-white/5 hover:opacity-100 hover:grayscale-0'
|
|
101
|
+
: 'bg-white/10', label: i18n._({
|
|
102
|
+
id: 'viewer.walls.label',
|
|
103
|
+
message: 'Walls: {mode}',
|
|
104
|
+
values: { mode: wallModeLabel(safeWallMode, i18n) },
|
|
105
|
+
}), onClick: () => {
|
|
106
|
+
const modes = ['cutaway', 'up', 'down'];
|
|
107
|
+
const nextIndex = (modes.indexOf(safeWallMode) + 1) % modes.length;
|
|
108
|
+
useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway');
|
|
109
|
+
}, size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx("img", { alt: wallModeLabel(safeWallMode, i18n), className: "h-[28px] w-[28px]", height: 28, src: iconUrl(wallModeConfig[safeWallMode].icon), width: 28 }) })), _jsx("div", { className: "mx-1 h-5 w-px bg-border/40" }), _jsx(DisplayMenu, {}), _jsx("div", { className: "mx-1 h-5 w-px bg-border/40" }), _jsx(ActionButton, { className: walkthroughActive
|
|
110
|
+
? 'bg-emerald-500/20 text-emerald-400'
|
|
111
|
+
: 'hover:bg-white/5 hover:text-emerald-400', label: i18n._({
|
|
112
|
+
id: 'viewer.walkthrough.label',
|
|
113
|
+
message: 'Walkthrough: {state}',
|
|
114
|
+
values: {
|
|
115
|
+
state: walkthroughActive
|
|
116
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
117
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' }),
|
|
118
|
+
},
|
|
119
|
+
}), onClick: onWalkthroughToggle, size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx(Footprints, { className: "h-6 w-6" }) }), onDroneToggle && !isMobile && (_jsx(ActionButton, { className: droneActive
|
|
120
|
+
? 'bg-emerald-500/20 text-emerald-400'
|
|
121
|
+
: 'hover:bg-white/5 hover:text-emerald-400', label: i18n._({
|
|
122
|
+
id: 'viewer.drone.label',
|
|
123
|
+
message: 'Drone: {state}',
|
|
124
|
+
values: {
|
|
125
|
+
state: droneActive
|
|
126
|
+
? i18n._({ id: 'viewer.common.on', message: 'On' })
|
|
127
|
+
: i18n._({ id: 'viewer.common.off', message: 'Off' }),
|
|
128
|
+
},
|
|
129
|
+
}), onClick: onDroneToggle, size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx(Drone, { className: "h-6 w-6" }) })), _jsx("div", { className: "mx-1 h-5 w-px bg-border/40" }), _jsx(FollowPascalToggle, { className: "hidden sm:inline-flex", tooltipClassName: "dark" }), _jsx(ActionButton, { className: "group hidden hover:bg-white/5 sm:inline-flex", label: i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' }), onClick: () => emitter.emit('camera-controls:orbit-ccw'), size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx("img", { alt: i18n._({ id: 'viewer.camera.orbitLeft', message: 'Orbit left' }), className: "h-[28px] w-[28px] -scale-x-100 object-contain opacity-70 transition-opacity group-hover:opacity-100", src: iconUrl('rotate') }) }), _jsx(ActionButton, { className: "group hidden hover:bg-white/5 sm:inline-flex", label: i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' }), onClick: () => emitter.emit('camera-controls:orbit-cw'), size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx("img", { alt: i18n._({ id: 'viewer.camera.orbitRight', message: 'Orbit right' }), className: "h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100", src: iconUrl('rotate') }) }), _jsx(ActionButton, { className: "group hover:bg-white/5", label: i18n._({ id: 'viewer.camera.topView', message: 'Top view' }), onClick: () => emitter.emit('camera-controls:top-view'), size: "icon", tooltipClassName: "dark", tooltipSide: "top", variant: "ghost", children: _jsx("img", { alt: i18n._({ id: 'viewer.camera.topView', message: 'Top view' }), className: "h-[28px] w-[28px] object-contain opacity-70 transition-opacity group-hover:opacity-100", src: iconUrl('topview') }) })] }) }) })] }));
|
|
130
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DescriptorTranslator } from '../lib/room-name-messages';
|
|
2
|
+
import { type ViewerNodes } from '../lib/viewer-selection';
|
|
3
|
+
/** The hovered node's name, for the bottom pill. A room reads as the room. */
|
|
4
|
+
export declare function viewerHoverLabel(hoveredId: string | null, nodes: ViewerNodes, i18n: DescriptorTranslator): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* What a click would select, named in a pill above the controls bar. Every
|
|
7
|
+
* viewer surface's hit source sets the hover (`applyViewerHover`); hidden in
|
|
8
|
+
* the walkthrough and on devices without a hovering pointer. Names come from
|
|
9
|
+
* the parametric scene store unless the surface passes its own graph.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ViewerHoverLabel({ nodes: nodesProp }: {
|
|
12
|
+
nodes?: ViewerNodes;
|
|
13
|
+
}): import("react").JSX.Element | null;
|
|
14
|
+
//# sourceMappingURL=viewer-hover-label.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-hover-label.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-hover-label.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,KAAK,WAAW,EAAa,MAAM,yBAAyB,CAAA;AAErE,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,oBAAoB,GACzB,MAAM,GAAG,IAAI,CAIf;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,sCAgB7E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { usePascalI18n, useScene } from '@pascal-app/core';
|
|
4
|
+
import useViewer from '../../store/use-viewer';
|
|
5
|
+
import { nodeDisplayLabel } from '../lib/node-display-label';
|
|
6
|
+
import { zoneLabel } from '../lib/viewer-selection';
|
|
7
|
+
/** The hovered node's name, for the bottom pill. A room reads as the room. */
|
|
8
|
+
export function viewerHoverLabel(hoveredId, nodes, i18n) {
|
|
9
|
+
const node = hoveredId ? nodes[hoveredId] : undefined;
|
|
10
|
+
if (!node)
|
|
11
|
+
return null;
|
|
12
|
+
return node.type === 'zone' ? zoneLabel(node, i18n) : nodeDisplayLabel(node);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What a click would select, named in a pill above the controls bar. Every
|
|
16
|
+
* viewer surface's hit source sets the hover (`applyViewerHover`); hidden in
|
|
17
|
+
* the walkthrough and on devices without a hovering pointer. Names come from
|
|
18
|
+
* the parametric scene store unless the surface passes its own graph.
|
|
19
|
+
*/
|
|
20
|
+
export function ViewerHoverLabel({ nodes: nodesProp }) {
|
|
21
|
+
const i18n = usePascalI18n();
|
|
22
|
+
const sceneNodes = useScene((s) => s.nodes);
|
|
23
|
+
const nodes = nodesProp ?? sceneNodes;
|
|
24
|
+
const label = useViewer((s) => s.walkthroughMode ? null : viewerHoverLabel(s.hoveredId, nodes, i18n));
|
|
25
|
+
if (!label)
|
|
26
|
+
return null;
|
|
27
|
+
return (_jsx("div", { className: "pointer-events-none absolute bottom-24 left-1/2 z-20 -translate-x-1/2 rounded-full border border-border/60 bg-background/90 px-3 py-1 text-foreground text-sm shadow-elevation-3 backdrop-blur [@media(hover:none)]:hidden", "data-testid": "viewer-hover-label", children: label }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import { type ViewerSource } from '../lib/viewer-selection';
|
|
3
|
+
/** What a header link receives; a host passes its router's link component (e.g. Next's `Link`). */
|
|
4
|
+
export type ViewerLinkProps = {
|
|
5
|
+
href: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
'aria-label'?: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
export type ViewerSceneHeaderProps = {
|
|
11
|
+
projectName?: string | null;
|
|
12
|
+
owner?: {
|
|
13
|
+
username?: string | null;
|
|
14
|
+
} | null;
|
|
15
|
+
onBack?: () => void;
|
|
16
|
+
/** Fallback destination when no `onBack` handler is supplied. Must already be
|
|
17
|
+
* sanitized by the caller. `null` shows no back arrow (an embedded viewer with
|
|
18
|
+
* nowhere to go back to). */
|
|
19
|
+
backHref?: string | null;
|
|
20
|
+
/** Extra row under the project info (e.g. like and duplicate actions). */
|
|
21
|
+
stats?: ReactNode;
|
|
22
|
+
/** The scene the header navigates; the parametric scene store by default. */
|
|
23
|
+
source?: ViewerSource;
|
|
24
|
+
/** Renders the back and owner links; a plain `<a>` by default. */
|
|
25
|
+
linkComponent?: ComponentType<ViewerLinkProps>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The navigation of every viewer surface: the project card, the bar (building
|
|
29
|
+
* › floor › room › element) and the floor list with, under the current floor,
|
|
30
|
+
* its units, rooms and zones. A row or a crumb selects and frames; the canvas
|
|
31
|
+
* selects in place (`ui/lib/viewer-selection.ts`).
|
|
32
|
+
*/
|
|
33
|
+
export declare const ViewerSceneHeader: ({ projectName, owner, onBack, backHref, stats, source: sourceProp, linkComponent: Link, }: ViewerSceneHeaderProps) => import("react").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=viewer-scene-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-scene-header.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-scene-header.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAA;AAMnE,OAAO,EAUL,KAAK,YAAY,EAGlB,MAAM,yBAAyB,CAAA;AAEhC,mGAAmG;AACnG,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAMD,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB;;kCAE8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAA;CAC/C,CAAA;AAUD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,8FAQ3B,sBAAsB,gCAyNxB,CAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { sceneRegistry, useLocale, usePascalI18n, useScene, } from '@pascal-app/core';
|
|
4
|
+
import { ArrowLeft, ChevronRight, Layers } from 'lucide-react';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { markPerfAction } from '../../lib/perf-actions';
|
|
7
|
+
import useViewer from '../../store/use-viewer';
|
|
8
|
+
import { cn } from '../lib/cn';
|
|
9
|
+
import { localizedLevelName } from '../lib/level-name';
|
|
10
|
+
import { formatRoomArea } from '../lib/room-area';
|
|
11
|
+
import { applyViewerClick, frameViewerNode, roomOfObject, selectViewerBuilding, selectViewerLevel, selectViewerRoom, selectViewerUnit, viewerBreadcrumb, viewerSidebarSections, } from '../lib/viewer-selection';
|
|
12
|
+
function PlainLink(props) {
|
|
13
|
+
return _jsx("a", { ...props });
|
|
14
|
+
}
|
|
15
|
+
function sectionTitle(kind, i18n) {
|
|
16
|
+
if (kind === 'units')
|
|
17
|
+
return i18n._({ id: 'viewer.header.units', message: 'Units' });
|
|
18
|
+
if (kind === 'rooms')
|
|
19
|
+
return i18n._({ id: 'viewer.header.rooms', message: 'Rooms' });
|
|
20
|
+
return i18n._({ id: 'viewer.header.zones', message: 'Zones' });
|
|
21
|
+
}
|
|
22
|
+
const parametricObject = (id) => sceneRegistry.nodes.get(id);
|
|
23
|
+
/**
|
|
24
|
+
* The navigation of every viewer surface: the project card, the bar (building
|
|
25
|
+
* › floor › room › element) and the floor list with, under the current floor,
|
|
26
|
+
* its units, rooms and zones. A row or a crumb selects and frames; the canvas
|
|
27
|
+
* selects in place (`ui/lib/viewer-selection.ts`).
|
|
28
|
+
*/
|
|
29
|
+
export const ViewerSceneHeader = ({ projectName, owner, onBack, backHref = '/', stats, source: sourceProp, linkComponent: Link = PlainLink, }) => {
|
|
30
|
+
const i18n = usePascalI18n();
|
|
31
|
+
const catalogVersion = i18n.getVersion();
|
|
32
|
+
const locale = useLocale();
|
|
33
|
+
const sceneNodes = useScene((s) => s.nodes);
|
|
34
|
+
const source = useMemo(() => sourceProp ?? { nodes: sceneNodes, objectFor: parametricObject }, [sourceProp, sceneNodes]);
|
|
35
|
+
const { nodes } = source;
|
|
36
|
+
const selection = useViewer((s) => s.selection);
|
|
37
|
+
const focusedUnitId = useViewer((s) => s.focusedUnitId);
|
|
38
|
+
const unit = useViewer((s) => s.unit);
|
|
39
|
+
const crumbs = useMemo(() => viewerBreadcrumb(selection, focusedUnitId, nodes, i18n, (nodeId, levelId) => roomOfObject(nodeId, levelId, source)), [selection, focusedUnitId, nodes, source, i18n, locale, catalogVersion]);
|
|
40
|
+
const buildingId = crumbs.find((crumb) => crumb.kind === 'building')?.id ?? null;
|
|
41
|
+
// Highest first so the list reads top-down like a building section.
|
|
42
|
+
const levels = useMemo(() => {
|
|
43
|
+
const building = buildingId ? nodes[buildingId] : undefined;
|
|
44
|
+
if (building?.type !== 'building')
|
|
45
|
+
return [];
|
|
46
|
+
return building.children
|
|
47
|
+
.map((id) => nodes[id])
|
|
48
|
+
.filter((node) => node?.type === 'level')
|
|
49
|
+
.sort((a, b) => b.level - a.level);
|
|
50
|
+
}, [buildingId, nodes]);
|
|
51
|
+
const sections = useMemo(() => (selection.levelId ? viewerSidebarSections(selection.levelId, nodes, i18n) : []), [selection.levelId, nodes, i18n, locale, catalogVersion]);
|
|
52
|
+
const handleCrumb = (crumb) => {
|
|
53
|
+
switch (crumb.kind) {
|
|
54
|
+
case 'building':
|
|
55
|
+
return selectViewerBuilding(source);
|
|
56
|
+
case 'level':
|
|
57
|
+
return selectViewerLevel(crumb.id, source);
|
|
58
|
+
case 'unit':
|
|
59
|
+
return selectViewerUnit(crumb.id, source);
|
|
60
|
+
case 'room':
|
|
61
|
+
return selectViewerRoom(crumb.id, source);
|
|
62
|
+
case 'node':
|
|
63
|
+
applyViewerClick({ nodeId: crumb.id, point: null }, nodes);
|
|
64
|
+
return frameViewerNode(crumb.id, source);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return (_jsxs("div", { className: "dark absolute top-4 left-4 z-20 flex max-h-[calc(100%-7rem)] flex-col gap-3 text-foreground", children: [_jsxs("div", { className: "corner-smooth pointer-events-auto flex min-w-[200px] max-w-[min(28rem,calc(100vw-2rem))] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out", children: [_jsxs("div", { className: "flex items-center gap-3 px-3 py-2.5", children: [onBack ? (_jsx("button", { "aria-label": i18n._({ id: 'viewer.header.back', message: 'Back' }), className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10", onClick: onBack, type: "button", children: _jsx(ArrowLeft, { className: "h-4 w-4 text-muted-foreground" }) })) : backHref === null ? null : (_jsx(Link, { "aria-label": i18n._({ id: 'viewer.header.back', message: 'Back' }), className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10", href: backHref, children: _jsx(ArrowLeft, { className: "h-4 w-4 text-muted-foreground" }) })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate font-medium text-foreground text-sm", children: projectName || i18n._({ id: 'viewer.header.untitled', message: 'Untitled' }) }), owner?.username && (_jsxs(Link, { className: "text-muted-foreground text-xs transition-colors hover:text-foreground", href: `/u/${owner.username}`, children: ["@", owner.username] }))] })] }), stats && (_jsx("div", { className: "flex items-center gap-1 border-border/40 border-t px-3 py-2", children: stats })), crumbs.length > 0 && (_jsx("nav", { "aria-label": i18n._({ id: 'viewer.header.selection', message: 'Selection' }), className: "flex flex-wrap items-center gap-x-1.5 gap-y-1 border-border/40 border-t px-3 py-2 text-xs", "data-testid": "viewer-breadcrumb", children: crumbs.map((crumb, index) => {
|
|
68
|
+
const isLast = index === crumbs.length - 1;
|
|
69
|
+
return (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [index > 0 && _jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground/50" }), _jsx("button", { className: cn('truncate transition-colors', isLast
|
|
70
|
+
? 'font-medium text-foreground'
|
|
71
|
+
: 'text-muted-foreground hover:text-foreground'), "data-crumb": crumb.kind, onClick: () => handleCrumb(crumb), type: "button", children: crumb.label })] }, `${crumb.kind}:${crumb.id}`));
|
|
72
|
+
}) }))] }), levels.length > 0 && (_jsxs("div", { className: "corner-smooth pointer-events-auto flex min-h-0 w-56 flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 py-1 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out", children: [_jsx("span", { className: "px-3 py-2 font-medium text-[10px] text-muted-foreground uppercase tracking-wider", children: i18n._({ id: 'viewer.header.levels', message: 'Levels' }) }), _jsx("div", { className: "flex min-h-0 flex-col overflow-y-auto", children: levels.map((lvl) => {
|
|
73
|
+
const isSelected = lvl.id === selection.levelId;
|
|
74
|
+
return (_jsxs("div", { children: [_jsx("button", { className: cn('group/row relative flex h-8 w-full cursor-pointer select-none items-center border-border/50 border-r border-r-transparent border-b px-3 text-sm transition-all duration-200', isSelected
|
|
75
|
+
? 'border-r-3 border-r-white bg-accent/50 text-foreground'
|
|
76
|
+
: 'text-muted-foreground hover:bg-accent/30 hover:text-foreground'), onClick: () => {
|
|
77
|
+
if (!isSelected)
|
|
78
|
+
markPerfAction('level-switch', lvl.id);
|
|
79
|
+
selectViewerLevel(lvl.id, source);
|
|
80
|
+
}, type: "button", children: _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: cn('flex h-4 w-4 shrink-0 items-center justify-center transition-all duration-200', !isSelected && 'opacity-60 grayscale'), children: _jsx(Layers, { className: "h-3.5 w-3.5" }) }), _jsx("div", { className: "min-w-0 flex-1 truncate text-left", children: localizedLevelName(lvl, i18n, locale) })] }) }), isSelected &&
|
|
81
|
+
sections.map((section) => (_jsxs("div", { className: "border-border/50 border-b pb-1", "data-viewer-section": section.kind, children: [_jsx("span", { className: "block px-3 pt-2 pb-1 pl-9 font-medium text-[10px] text-muted-foreground uppercase tracking-wider", children: sectionTitle(section.kind, i18n) }), section.rows.map((row) => {
|
|
82
|
+
const active = section.kind === 'units'
|
|
83
|
+
? row.id === focusedUnitId
|
|
84
|
+
: row.id === selection.zoneId;
|
|
85
|
+
return (_jsxs("button", { className: cn('flex h-7 w-full cursor-pointer select-none items-center gap-2 pr-3 pl-9 text-left text-sm transition-colors', active
|
|
86
|
+
? 'bg-accent/50 text-foreground'
|
|
87
|
+
: 'text-muted-foreground hover:bg-accent/30 hover:text-foreground'), onClick: () => section.kind === 'units'
|
|
88
|
+
? selectViewerUnit(row.id, source)
|
|
89
|
+
: selectViewerRoom(row.id, source), type: "button", children: [_jsx("span", { className: "h-2 w-2 shrink-0 rounded-full", style: { backgroundColor: row.color } }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: row.label }), _jsx("span", { className: "shrink-0 font-mono text-[11px] text-muted-foreground tabular-nums", children: formatRoomArea(row.areaM2, unit, locale) })] }, row.id));
|
|
90
|
+
})] }, section.kind)))] }, lvl.id));
|
|
91
|
+
}) })] }))] }));
|
|
92
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection for a parametric viewer surface (the published viewer's fallback,
|
|
3
|
+
* its embed, the editor's Preview): free-form and room-first, through the same
|
|
4
|
+
* rules as the baked viewer. Mount it with `<Viewer selectionManager="custom">`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ViewerSelectionManager(): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=viewer-selection-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-selection-manager.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-selection-manager.tsx"],"names":[],"mappings":"AAaA;;;;GAIG;AACH,wBAAgB,sBAAsB,gCAoErC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { emitter, sceneRegistry, useScene } from '@pascal-app/core';
|
|
4
|
+
import { useThree } from '@react-three/fiber';
|
|
5
|
+
import { useEffect } from 'react';
|
|
6
|
+
import useViewer from '../../store/use-viewer';
|
|
7
|
+
import { selectionModifiersFromEvent } from '../lib/selection-modifiers';
|
|
8
|
+
import { applyViewerClick, applyViewerHover, viewerPickFromNodeEvent, } from '../lib/viewer-selection';
|
|
9
|
+
/**
|
|
10
|
+
* Selection for a parametric viewer surface (the published viewer's fallback,
|
|
11
|
+
* its embed, the editor's Preview): free-form and room-first, through the same
|
|
12
|
+
* rules as the baked viewer. Mount it with `<Viewer selectionManager="custom">`.
|
|
13
|
+
*/
|
|
14
|
+
export function ViewerSelectionManager() {
|
|
15
|
+
const gl = useThree((state) => state.gl);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
let clickHandled = false;
|
|
18
|
+
let hoveredFrom = null;
|
|
19
|
+
const pendingClicks = new Set();
|
|
20
|
+
const onMove = (event) => {
|
|
21
|
+
if (useViewer.getState().walkthroughMode)
|
|
22
|
+
return;
|
|
23
|
+
const pick = viewerPickFromNodeEvent(event, useScene.getState().nodes);
|
|
24
|
+
if (!pick)
|
|
25
|
+
return;
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
hoveredFrom = event.node.id;
|
|
28
|
+
applyViewerHover(pick, useScene.getState().nodes);
|
|
29
|
+
};
|
|
30
|
+
const onLeave = (event) => {
|
|
31
|
+
if (event.node.id !== hoveredFrom)
|
|
32
|
+
return;
|
|
33
|
+
hoveredFrom = null;
|
|
34
|
+
useViewer.getState().setHoveredId(null);
|
|
35
|
+
};
|
|
36
|
+
const onClick = (event) => {
|
|
37
|
+
if (useViewer.getState().walkthroughMode)
|
|
38
|
+
return;
|
|
39
|
+
const pick = viewerPickFromNodeEvent(event, useScene.getState().nodes);
|
|
40
|
+
if (!pick)
|
|
41
|
+
return;
|
|
42
|
+
event.stopPropagation();
|
|
43
|
+
clickHandled = true;
|
|
44
|
+
applyViewerClick(pick, useScene.getState().nodes, selectionModifiersFromEvent(event.nativeEvent));
|
|
45
|
+
};
|
|
46
|
+
// A click on nothing selectable clears the room and the element.
|
|
47
|
+
const onCanvasClick = (event) => {
|
|
48
|
+
const viewer = useViewer.getState();
|
|
49
|
+
if (event.button !== 0 || viewer.walkthroughMode)
|
|
50
|
+
return;
|
|
51
|
+
if (viewer.cameraDragging || viewer.inputDragging)
|
|
52
|
+
return;
|
|
53
|
+
const frame = requestAnimationFrame(() => {
|
|
54
|
+
pendingClicks.delete(frame);
|
|
55
|
+
if (useViewer.getState().walkthroughMode)
|
|
56
|
+
return;
|
|
57
|
+
if (clickHandled) {
|
|
58
|
+
clickHandled = false;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
applyViewerClick(null, useScene.getState().nodes);
|
|
62
|
+
});
|
|
63
|
+
pendingClicks.add(frame);
|
|
64
|
+
};
|
|
65
|
+
emitter.on('node:move', onMove);
|
|
66
|
+
emitter.on('node:enter', onMove);
|
|
67
|
+
emitter.on('node:leave', onLeave);
|
|
68
|
+
emitter.on('node:click', onClick);
|
|
69
|
+
const canvas = gl.domElement;
|
|
70
|
+
canvas.addEventListener('click', onCanvasClick);
|
|
71
|
+
return () => {
|
|
72
|
+
emitter.off('node:move', onMove);
|
|
73
|
+
emitter.off('node:enter', onMove);
|
|
74
|
+
emitter.off('node:leave', onLeave);
|
|
75
|
+
emitter.off('node:click', onClick);
|
|
76
|
+
canvas.removeEventListener('click', onCanvasClick);
|
|
77
|
+
for (const frame of pendingClicks)
|
|
78
|
+
cancelAnimationFrame(frame);
|
|
79
|
+
useViewer.getState().setHoveredId(null);
|
|
80
|
+
};
|
|
81
|
+
}, [gl]);
|
|
82
|
+
return _jsx(ViewerOutlineSync, {});
|
|
83
|
+
}
|
|
84
|
+
/** The selected elements and the hovered one, outlined (rooms highlight instead). */
|
|
85
|
+
function ViewerOutlineSync() {
|
|
86
|
+
const selectedIds = useViewer((s) => s.selection.selectedIds);
|
|
87
|
+
const externalSelectedIds = useViewer((s) => s.externalSelectedIds);
|
|
88
|
+
const hoveredId = useViewer((s) => s.hoveredId);
|
|
89
|
+
const outliner = useViewer((s) => s.outliner);
|
|
90
|
+
const geometryRevision = useViewer((s) => s.geometryRevision);
|
|
91
|
+
const walkthrough = useViewer((s) => s.walkthroughMode);
|
|
92
|
+
const nodes = useScene((s) => s.nodes);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
void geometryRevision;
|
|
95
|
+
outliner.selectedObjects.length = 0;
|
|
96
|
+
outliner.hoveredObjects.length = 0;
|
|
97
|
+
if (walkthrough)
|
|
98
|
+
return;
|
|
99
|
+
for (const id of new Set([...selectedIds, ...externalSelectedIds])) {
|
|
100
|
+
const object = sceneRegistry.nodes.get(id);
|
|
101
|
+
if (object)
|
|
102
|
+
outliner.selectedObjects.push(object);
|
|
103
|
+
}
|
|
104
|
+
const hovered = hoveredId ? nodes[hoveredId] : undefined;
|
|
105
|
+
if (hovered && hovered.type !== 'zone') {
|
|
106
|
+
const object = sceneRegistry.nodes.get(hovered.id);
|
|
107
|
+
if (object)
|
|
108
|
+
outliner.hoveredObjects.push(object);
|
|
109
|
+
}
|
|
110
|
+
}, [selectedIds, externalSelectedIds, hoveredId, outliner, nodes, geometryRevision, walkthrough]);
|
|
111
|
+
useEffect(() => () => {
|
|
112
|
+
outliner.selectedObjects.length = 0;
|
|
113
|
+
outliner.hoveredObjects.length = 0;
|
|
114
|
+
}, [outliner]);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const VIEWER_STAGE_MODES: readonly ['3d', '2d', 'split'];
|
|
2
|
+
export type ViewerStageMode = (typeof VIEWER_STAGE_MODES)[number];
|
|
3
|
+
export declare function normalizeViewerStageModes(modes: readonly ViewerStageMode[] | undefined): readonly ViewerStageMode[];
|
|
4
|
+
export declare function resolveViewerStageMode(mode: ViewerStageMode | undefined, modes: readonly ViewerStageMode[]): ViewerStageMode;
|
|
5
|
+
export declare function resolveMobileViewerStageMode(mode: ViewerStageMode, modes: readonly ViewerStageMode[]): ViewerStageMode;
|
|
6
|
+
export declare function viewerStageIncludes3D(modes: readonly ViewerStageMode[]): boolean;
|
|
7
|
+
//# sourceMappingURL=viewer-stage-modes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-stage-modes.d.ts","sourceRoot":"","sources":["../../../src/ui/components/viewer-stage-modes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,YAAI,IAAI,EAAE,IAAI,EAAE,OAAO,CAAU,CAAA;AAEhE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AASjE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,GAC5C,SAAS,eAAe,EAAE,CAc5B;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,eAAe,GAAG,SAAS,EACjC,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,eAAe,CAEjB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,eAAe,CAKjB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,WAEtE"}
|