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;
gap: 0.72rem;
margin-top: 1.2rem;
overflow-anchor: none;
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 {
@ -35,6 +42,8 @@ export function LidarLocalSurfacePanel({
selectedWindowKey: string | null;
reloadGeneration: number;
}) {
const panelRef = useRef<HTMLElement | null>(null);
const pendingScrollTopRef = useRef<number | null>(null);
const [model, setModel] = useState<LidarLocalSurfaceModel | null>(null);
const [frame, setFrame] = useState<LidarLocalSurfaceFrame | null>(null);
const [timeline, setTimeline] = useState<Timeline | null>(null);
@ -47,6 +56,27 @@ export function LidarLocalSurfacePanel({
>("local-surface");
const [loading, setLoading] = useState(true);
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(() => {
if (!model) return null;
@ -205,6 +235,7 @@ export function LidarLocalSurfacePanel({
return (
<section
ref={panelRef}
className="lidar-local-surface"
aria-label="Динамическая локальная поверхность LiDAR"
>
@ -289,12 +320,14 @@ export function LidarLocalSurfacePanel({
timeline={timeline}
review={review}
selectedFrameIndex={selectedFrameIndex}
onSelectFrame={setSelectedFrameIndex}
onSelectionStart={captureFrameSelectionScroll}
onSelectFrame={selectFrame}
/>
<LidarLocalSurfaceReviewQueue
review={review}
selectedFrameIndex={selectedFrameIndex}
onSelectFrame={setSelectedFrameIndex}
onSelectionStart={captureFrameSelectionScroll}
onSelectFrame={selectFrame}
/>
</>
) : null}

View File

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