fix: preserve lidar review scroll

This commit is contained in:
DCCONSTRUCTIONS 2026-07-26 00:52:50 +03:00
parent 796b9306f9
commit 4c83e8a4e7
3 changed files with 50 additions and 3 deletions

View File

@ -2825,6 +2825,7 @@
display: grid; display: grid;
gap: 0.72rem; gap: 0.72rem;
margin-top: 1.2rem; margin-top: 1.2rem;
overflow-anchor: none;
padding-top: 1rem; padding-top: 1rem;
} }

View File

@ -1,4 +1,11 @@
import { useEffect, useMemo, useState } from "react"; import {
useCallback,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { StatusBadge } from "@nodedc/ui-react"; import { StatusBadge } from "@nodedc/ui-react";
import { import {
@ -35,6 +42,8 @@ export function LidarLocalSurfacePanel({
selectedWindowKey: string | null; selectedWindowKey: string | null;
reloadGeneration: number; reloadGeneration: number;
}) { }) {
const panelRef = useRef<HTMLElement | null>(null);
const pendingScrollTopRef = useRef<number | null>(null);
const [model, setModel] = useState<LidarLocalSurfaceModel | null>(null); const [model, setModel] = useState<LidarLocalSurfaceModel | null>(null);
const [frame, setFrame] = useState<LidarLocalSurfaceFrame | null>(null); const [frame, setFrame] = useState<LidarLocalSurfaceFrame | null>(null);
const [timeline, setTimeline] = useState<Timeline | null>(null); const [timeline, setTimeline] = useState<Timeline | null>(null);
@ -47,6 +56,27 @@ export function LidarLocalSurfacePanel({
>("local-surface"); >("local-surface");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const captureFrameSelectionScroll = useCallback(() => {
const scrollContainer = panelRef.current?.closest<HTMLElement>(
".nodedc-application-panel__body",
);
pendingScrollTopRef.current = scrollContainer?.scrollTop ?? null;
}, []);
const selectFrame = useCallback((frameIndex: number) => {
if (pendingScrollTopRef.current === null) {
captureFrameSelectionScroll();
}
setSelectedFrameIndex(frameIndex);
}, [captureFrameSelectionScroll]);
useLayoutEffect(() => {
const scrollTop = pendingScrollTopRef.current;
if (scrollTop === null) return;
const scrollContainer = panelRef.current?.closest<HTMLElement>(
".nodedc-application-panel__body",
);
scrollContainer?.scrollTo({ top: scrollTop, behavior: "auto" });
pendingScrollTopRef.current = null;
}, [selectedFrameIndex]);
const anchor = useMemo(() => { const anchor = useMemo(() => {
if (!model) return null; if (!model) return null;
@ -205,6 +235,7 @@ export function LidarLocalSurfacePanel({
return ( return (
<section <section
ref={panelRef}
className="lidar-local-surface" className="lidar-local-surface"
aria-label="Динамическая локальная поверхность LiDAR" aria-label="Динамическая локальная поверхность LiDAR"
> >
@ -289,12 +320,14 @@ export function LidarLocalSurfacePanel({
timeline={timeline} timeline={timeline}
review={review} review={review}
selectedFrameIndex={selectedFrameIndex} selectedFrameIndex={selectedFrameIndex}
onSelectFrame={setSelectedFrameIndex} onSelectionStart={captureFrameSelectionScroll}
onSelectFrame={selectFrame}
/> />
<LidarLocalSurfaceReviewQueue <LidarLocalSurfaceReviewQueue
review={review} review={review}
selectedFrameIndex={selectedFrameIndex} selectedFrameIndex={selectedFrameIndex}
onSelectFrame={setSelectedFrameIndex} onSelectionStart={captureFrameSelectionScroll}
onSelectFrame={selectFrame}
/> />
</> </>
) : null} ) : null}

View File

@ -42,11 +42,13 @@ export function LidarLocalSurfaceTimeline({
timeline, timeline,
review, review,
selectedFrameIndex, selectedFrameIndex,
onSelectionStart,
onSelectFrame, onSelectFrame,
}: { }: {
timeline: Timeline; timeline: Timeline;
review: LidarLocalSurfaceReview; review: LidarLocalSurfaceReview;
selectedFrameIndex: number; selectedFrameIndex: number;
onSelectionStart: () => void;
onSelectFrame: (frameIndex: number) => void; onSelectFrame: (frameIndex: number) => void;
}) { }) {
const plot = useMemo(() => { const plot = useMemo(() => {
@ -131,6 +133,10 @@ export function LidarLocalSurfaceTimeline({
role="img" role="img"
tabIndex={0} tabIndex={0}
aria-label="Ошибка предсказания локальной поверхности по всей записи" aria-label="Ошибка предсказания локальной поверхности по всей записи"
onMouseDown={(event) => {
onSelectionStart();
event.preventDefault();
}}
onClick={(event) => selectAtPointer(event.clientX, event.currentTarget)} onClick={(event) => selectAtPointer(event.clientX, event.currentTarget)}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === "ArrowLeft") { if (event.key === "ArrowLeft") {
@ -221,10 +227,12 @@ function hasReasonKind(
export function LidarLocalSurfaceReviewQueue({ export function LidarLocalSurfaceReviewQueue({
review, review,
selectedFrameIndex, selectedFrameIndex,
onSelectionStart,
onSelectFrame, onSelectFrame,
}: { }: {
review: LidarLocalSurfaceReview; review: LidarLocalSurfaceReview;
selectedFrameIndex: number; selectedFrameIndex: number;
onSelectionStart: () => void;
onSelectFrame: (frameIndex: number) => void; onSelectFrame: (frameIndex: number) => void;
}) { }) {
const [filter, setFilter] = useState<ReviewFilter>("high"); const [filter, setFilter] = useState<ReviewFilter>("high");
@ -281,6 +289,7 @@ export function LidarLocalSurfaceReviewQueue({
type="button" type="button"
key={item.key} key={item.key}
data-active={filter === item.key ? "true" : undefined} data-active={filter === item.key ? "true" : undefined}
onMouseDown={(event) => event.preventDefault()}
onClick={() => setFilter(item.key)} onClick={() => setFilter(item.key)}
> >
{item.label} <span>{item.count}</span> {item.label} <span>{item.count}</span>
@ -297,6 +306,10 @@ export function LidarLocalSurfaceReviewQueue({
item.frameIndex === selectedFrameIndex ? "true" : undefined item.frameIndex === selectedFrameIndex ? "true" : undefined
} }
data-attention={item.attention} data-attention={item.attention}
onMouseDown={(event) => {
onSelectionStart();
event.preventDefault();
}}
onClick={() => onSelectFrame(item.frameIndex)} onClick={() => onSelectFrame(item.frameIndex)}
> >
<span> <span>