feat(planning): consolidate recorded-route localization and spatial scene
Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
This commit is contained in:
@@ -7,22 +7,26 @@ export function SpatialScene({viewportRef, focused, primaryFocused, mediaMaximiz
|
||||
navigationReady=false}: {
|
||||
viewportRef: RefObject<HTMLDivElement|null>; focused?:boolean; primaryFocused?:boolean;
|
||||
mediaMaximized?:boolean; toolbar:ReactNode; renderer:ReactNode; deviceControls?:ReactNode;
|
||||
sourceControls?:ReactNode; status:{label:string;tone:'neutral'|'success'|'warning'|'danger';message?:string};
|
||||
sourceControls?:ReactNode; status:{label:string;tone:'neutral'|'success'|'warning'|'danger';message?:string;pulse?:boolean};
|
||||
metrics:ReactNode; timeline?:ReactNode; media?:ReactNode; overlays?:ReactNode; footer?:ReactNode;
|
||||
navigationReady?:boolean;
|
||||
}) {
|
||||
const detailsHidden = primaryFocused || mediaMaximized;
|
||||
return <div className="spatial-workspace" data-focused={focused?'true':undefined}>
|
||||
<div className="spatial-toolbar" data-viewer-controls="v1"><div className="spatial-toolbar__actions">{toolbar}</div></div>
|
||||
<div ref={viewportRef} className="spatial-viewport-shell" data-primary-focused={primaryFocused?'true':undefined} data-media-maximized={mediaMaximized?'true':undefined}>
|
||||
{renderer}
|
||||
{deviceControls&&<div className="scene-device-controls">{deviceControls}</div>}
|
||||
{sourceControls}
|
||||
<div className="scene-status scene-status--top-left" aria-hidden={primaryFocused||mediaMaximized}>
|
||||
<span className="section-eyebrow">ВИЗУАЛЬНЫЙ ДВИЖОК</span>
|
||||
<StatusBadge tone={status.tone}>{status.label}</StatusBadge>
|
||||
{status.message?<small>{status.message}</small>:null}
|
||||
{detailsHidden ? sourceControls : null}
|
||||
<div className="scene-information" aria-hidden={detailsHidden}>
|
||||
{!detailsHidden ? sourceControls : null}
|
||||
<div className="scene-status">
|
||||
<span className="section-eyebrow">ВИЗУАЛЬНЫЙ ДВИЖОК</span>
|
||||
<StatusBadge tone={status.tone} pulse={status.pulse}>{status.label}</StatusBadge>
|
||||
{status.message?<small>{status.message}</small>:null}
|
||||
</div>
|
||||
<div className="scene-metrics" aria-label="Метрики пространственной сцены">{metrics}</div>
|
||||
</div>
|
||||
<div className="scene-metrics" aria-label="Метрики пространственной сцены" aria-hidden={primaryFocused||mediaMaximized}>{metrics}</div>
|
||||
{overlays}
|
||||
{navigationReady&&!mediaMaximized&&<div className="scene-navigation-hint" aria-label="Навигация по 3D-сцене"><span>Колесо · зум к курсору</span><span>WASD · свободный проход</span></div>}
|
||||
{timeline}{media}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {Button,Icon} from '@nodedc/ui-react';
|
||||
export function SpatialToolbarActions({openSource,openLayers,openDisplay}:{openSource:()=>void;openLayers:()=>void;openDisplay:()=>void}) {
|
||||
export function SpatialToolbarActions({openSource,openLayers,openDisplay,activeTool}:{openSource?:()=>void;openLayers:()=>void;openDisplay:()=>void;activeTool?:'layers'|'display'|null}) {
|
||||
return <>
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="network"/>} onClick={openSource}>Движок</Button>
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="list"/>} onClick={openLayers}>Слои</Button>
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="sliders"/>} onClick={openDisplay}>Отображение</Button>
|
||||
{openSource&&<Button size="compact" variant="secondary" icon={<Icon name="network"/>} onClick={openSource}>Движок</Button>}
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="list"/>} aria-pressed={activeTool==='layers'} onClick={openLayers}>Слои</Button>
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="sliders"/>} aria-pressed={activeTool==='display'} onClick={openDisplay}>Отображение</Button>
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -86,15 +86,6 @@
|
||||
right: 0.85rem;
|
||||
}
|
||||
|
||||
.scene-status--top-left {
|
||||
left: 7.1rem;
|
||||
}
|
||||
|
||||
.scene-status[aria-hidden="true"],
|
||||
.scene-metrics[aria-hidden="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.observation-source-menu {
|
||||
overflow: hidden;
|
||||
font-family: var(--nodedc-font-family);
|
||||
@@ -634,11 +625,6 @@ i[data-availability="error"] {
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.scene-status--top-left {
|
||||
top: 4.2rem;
|
||||
left: 0.6rem;
|
||||
}
|
||||
|
||||
.observation-timeline[data-accumulation="true"] {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
@@ -261,8 +261,6 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.scene-status,
|
||||
.scene-metrics,
|
||||
.scene-adapter-note,
|
||||
.scene-selection,
|
||||
.scene-timeline {
|
||||
@@ -273,13 +271,34 @@
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.scene-status--top-left {
|
||||
.scene-information {
|
||||
position: absolute;
|
||||
z-index: 12;
|
||||
top: 0.85rem;
|
||||
left: 0.85rem;
|
||||
max-width: min(20rem, calc(100% - 1.7rem));
|
||||
display: grid;
|
||||
justify-items: start;
|
||||
gap: 0.65rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scene-information[aria-hidden="true"] { display: none; }
|
||||
.scene-information > .scene-source-controls { position: static; pointer-events: auto; }
|
||||
|
||||
.scene-status,
|
||||
.scene-metrics {
|
||||
border: 0;
|
||||
background: rgb(9 10 13 / 0.74);
|
||||
backdrop-filter: blur(16px);
|
||||
border-radius: 0.9rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.scene-status {
|
||||
display: grid;
|
||||
justify-items: start;
|
||||
gap: 0.42rem;
|
||||
border-radius: 0.9rem;
|
||||
padding: 0.7rem;
|
||||
}
|
||||
|
||||
@@ -291,11 +310,8 @@
|
||||
}
|
||||
|
||||
.scene-metrics {
|
||||
top: 0.85rem;
|
||||
right: 0.85rem;
|
||||
display: grid;
|
||||
min-width: 10rem;
|
||||
border-radius: 0.9rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
}
|
||||
|
||||
@@ -313,6 +329,10 @@
|
||||
.scene-metrics strong { color: var(--nodedc-text-primary); font-size: 0.72rem; }
|
||||
.scene-metrics small { color: var(--nodedc-text-muted); font-size: 0.52rem; font-weight: 500; }
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.scene-information { top: 0.6rem; left: 0.6rem; max-width: min(20rem, calc(100% - 1.2rem)); }
|
||||
}
|
||||
|
||||
.scene-adapter-note {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
Reference in New Issue
Block a user