fix(lab): align spatial replay controls

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 09:14:20 +03:00
parent e38f2fb1da
commit b8fb4ebcba
7 changed files with 119 additions and 86 deletions
@@ -14,6 +14,7 @@ export function ObservationTimeline({
onSeek,
onPlayingChange,
onJumpToEnd,
showJumpToEnd = true,
playbackRate,
onPlaybackRateChange,
accumulationSeconds,
@@ -32,6 +33,7 @@ export function ObservationTimeline({
onSeek?: (timeNs: number) => void;
onPlayingChange?: (playing: boolean) => void;
onJumpToEnd?: () => void;
showJumpToEnd?: boolean;
playbackRate?: number;
onPlaybackRateChange?: (rate: number) => void;
accumulationSeconds?: number;
@@ -144,15 +146,17 @@ export function ObservationTimeline({
{buffered ? `${sourceCount} каналов` : `${synchronizationLabel} · буфер не включён`}
</small>
</div>
<button
type="button"
className="observation-timeline__follow"
data-active={!buffered && active ? "true" : undefined}
disabled={buffered ? !onJumpToEnd : true}
onClick={onJumpToEnd}
>
{buffered ? "К КОНЦУ" : "ЭФИР"}
</button>
{showJumpToEnd ? (
<button
type="button"
className="observation-timeline__follow"
data-active={!buffered && active ? "true" : undefined}
disabled={buffered ? !onJumpToEnd : true}
onClick={onJumpToEnd}
>
{buffered ? "К КОНЦУ" : "ЭФИР"}
</button>
) : null}
</div>
</div>
);
@@ -30,6 +30,7 @@ export function LaboratoryEvidenceViewer<
secondaryMode,
overlay,
transport,
trailingActions,
children,
}: {
label: string;
@@ -48,6 +49,7 @@ export function LaboratoryEvidenceViewer<
};
overlay?: ReactNode;
transport?: ReactNode;
trailingActions?: ReactNode;
children: ReactNode;
}) {
const expandButtonRef = useRef<HTMLButtonElement | null>(null);
@@ -98,6 +100,7 @@ export function LaboratoryEvidenceViewer<
label={`${label}: режим представления`}
onChange={onModeChange}
/>
{trailingActions}
<IconButton
ref={expandButtonRef}
label={expanded ? `Свернуть ${label}` : `Развернуть ${label}`}
@@ -1,5 +1,10 @@
import { useEffect, useRef, useState } from "react";
import { Button, Icon } from "@nodedc/ui-react";
import {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@@ -91,15 +96,13 @@ function decisionColor(
return tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92]);
}
export function LaboratoryMetricEvidenceScene({
pointCloudBodyXyzM,
obstacles,
rig,
corridor,
occupiedVoxelSizeM,
mode,
label,
}: {
export interface LaboratoryMetricEvidenceSceneHandle {
resetView: () => void;
}
export const LaboratoryMetricEvidenceScene = forwardRef<
LaboratoryMetricEvidenceSceneHandle,
{
pointCloudBodyXyzM: readonly LaboratoryMetricPoint3[];
obstacles: readonly LaboratoryMetricObstacleVisual[];
rig: LaboratoryMetricRigVisual;
@@ -107,7 +110,20 @@ export function LaboratoryMetricEvidenceScene({
occupiedVoxelSizeM: number;
mode: LaboratoryMetricSceneMode;
label: string;
}) {
showCurrentIncrement: boolean;
showRollingMap: boolean;
}
>(function LaboratoryMetricEvidenceScene({
pointCloudBodyXyzM,
obstacles,
rig,
corridor,
occupiedVoxelSizeM,
mode,
label,
showCurrentIncrement,
showRollingMap,
}, ref) {
const hostRef = useRef<HTMLDivElement | null>(null);
const sceneRef = useRef<THREE.Scene | null>(null);
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
@@ -115,8 +131,6 @@ export function LaboratoryMetricEvidenceScene({
const staticContentRef = useRef<THREE.Group | null>(null);
const dynamicContentRef = useRef<THREE.Group | null>(null);
const [renderError, setRenderError] = useState<string | null>(null);
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
useEffect(() => {
const host = hostRef.current;
@@ -370,39 +384,13 @@ export function LaboratoryMetricEvidenceScene({
};
useEffect(resetView, [corridor.forwardLengthM, mode]);
useImperativeHandle(ref, () => ({ resetView }));
return (
<div className="laboratory-metric-evidence-scene">
<div ref={hostRef} className="laboratory-metric-evidence-scene__viewport">
{renderError ? <p>{renderError}</p> : null}
</div>
<div className="laboratory-metric-evidence-scene__toolbar">
<Button
variant="secondary"
size="compact"
icon={<Icon name="refresh" size={14} />}
onClick={resetView}
>
Сбросить ракурс
</Button>
<Button
variant={showCurrentIncrement ? "primary" : "secondary"}
size="compact"
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
CURRENT INCREMENT
</Button>
<Button
variant={showRollingMap ? "primary" : "secondary"}
size="compact"
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
ROLLING MAP
</Button>
<span>ЛКМ · вращение · колесо · масштаб · ПКМ · панорама</span>
</div>
<div className="laboratory-metric-evidence-scene__legend">
<span data-decision="threat">Угроза</span>
<span data-decision="not-threat">Вне коридора</span>
@@ -412,4 +400,4 @@ export function LaboratoryMetricEvidenceScene({
</div>
</div>
);
}
});