diff --git a/apps/control-station/src/styles/workspaces.css b/apps/control-station/src/styles/workspaces.css index 0880f6b..91b7124 100644 --- a/apps/control-station/src/styles/workspaces.css +++ b/apps/control-station/src/styles/workspaces.css @@ -2825,6 +2825,7 @@ display: grid; gap: 0.72rem; margin-top: 1.2rem; + overflow-anchor: none; padding-top: 1rem; } diff --git a/apps/control-station/src/workspaces/LidarLocalSurfacePanel.tsx b/apps/control-station/src/workspaces/LidarLocalSurfacePanel.tsx index 4359ad2..61a72bc 100644 --- a/apps/control-station/src/workspaces/LidarLocalSurfacePanel.tsx +++ b/apps/control-station/src/workspaces/LidarLocalSurfacePanel.tsx @@ -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(null); + const pendingScrollTopRef = useRef(null); const [model, setModel] = useState(null); const [frame, setFrame] = useState(null); const [timeline, setTimeline] = useState(null); @@ -47,6 +56,27 @@ export function LidarLocalSurfacePanel({ >("local-surface"); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const captureFrameSelectionScroll = useCallback(() => { + const scrollContainer = panelRef.current?.closest( + ".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( + ".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 (
@@ -289,12 +320,14 @@ export function LidarLocalSurfacePanel({ timeline={timeline} review={review} selectedFrameIndex={selectedFrameIndex} - onSelectFrame={setSelectedFrameIndex} + onSelectionStart={captureFrameSelectionScroll} + onSelectFrame={selectFrame} /> ) : null} diff --git a/apps/control-station/src/workspaces/LidarLocalSurfaceTimeline.tsx b/apps/control-station/src/workspaces/LidarLocalSurfaceTimeline.tsx index 4941cf8..515a815 100644 --- a/apps/control-station/src/workspaces/LidarLocalSurfaceTimeline.tsx +++ b/apps/control-station/src/workspaces/LidarLocalSurfaceTimeline.tsx @@ -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("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} {item.count} @@ -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)} >