From c82ed43c137bc431bc2c22cc5bf1a4388dcb1186 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:36:28 -0500 Subject: [PATCH] Timeline tweaks (#10693) * make segment height static * fix timeline overscrolling * better alignment of motion timeline segments --- .../timeline/EventReviewTimeline.tsx | 2 +- web/src/components/timeline/EventSegment.tsx | 10 ++-- .../timeline/MotionReviewTimeline.tsx | 2 +- web/src/components/timeline/MotionSegment.tsx | 16 +++---- .../components/timeline/SummaryTimeline.tsx | 2 +- .../components/timeline/segment-metadata.tsx | 4 +- web/src/hooks/use-camera-activity.ts | 5 +- web/src/hooks/use-draggable-element.ts | 48 +++++++++---------- web/src/hooks/use-timeline-utils.ts | 8 +--- web/src/pages/UIPlayground.tsx | 6 +-- 10 files changed, 48 insertions(+), 55 deletions(-) diff --git a/web/src/components/timeline/EventReviewTimeline.tsx b/web/src/components/timeline/EventReviewTimeline.tsx index 56f2f6472..bccf43cd2 100644 --- a/web/src/components/timeline/EventReviewTimeline.tsx +++ b/web/src/components/timeline/EventReviewTimeline.tsx @@ -166,7 +166,7 @@ export function EventReviewTimeline({ // Generate segments for the timeline const generateSegments = useCallback(() => { - const segmentCount = timelineDuration / segmentDuration; + const segmentCount = Math.ceil(timelineDuration / segmentDuration); return Array.from({ length: segmentCount }, (_, index) => { const segmentTime = timelineStartAligned - index * segmentDuration; diff --git a/web/src/components/timeline/EventSegment.tsx b/web/src/components/timeline/EventSegment.tsx index 9fa2d025d..ab8f30157 100644 --- a/web/src/components/timeline/EventSegment.tsx +++ b/web/src/components/timeline/EventSegment.tsx @@ -139,7 +139,7 @@ export function EventSegment({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]); - const segmentClasses = `h-2 relative w-full ${ + const segmentClasses = `h-[8px] relative w-full ${ showMinimap ? isInMinimapRange ? "bg-secondary-highlight" @@ -149,7 +149,7 @@ export function EventSegment({ : "" } ${ isFirstSegmentInMinimap || isLastSegmentInMinimap - ? "relative h-2 border-b-2 border-neutral-600" + ? "relative h-[8px] border-b-2 border-neutral-600" : "" }`; @@ -230,16 +230,16 @@ export function EventSegment({ {severityValue === displaySeverityType && ( -
+
diff --git a/web/src/components/timeline/MotionReviewTimeline.tsx b/web/src/components/timeline/MotionReviewTimeline.tsx index 7d2d5371f..fa94355ca 100644 --- a/web/src/components/timeline/MotionReviewTimeline.tsx +++ b/web/src/components/timeline/MotionReviewTimeline.tsx @@ -169,7 +169,7 @@ export function MotionReviewTimeline({ // Generate segments for the timeline const generateSegments = useCallback(() => { - const segmentCount = timelineDuration / segmentDuration; + const segmentCount = Math.ceil(timelineDuration / segmentDuration); return Array.from({ length: segmentCount }, (_, index) => { const segmentTime = timelineStartAligned - index * segmentDuration; diff --git a/web/src/components/timeline/MotionSegment.tsx b/web/src/components/timeline/MotionSegment.tsx index 279f69a9a..c7b30b741 100644 --- a/web/src/components/timeline/MotionSegment.tsx +++ b/web/src/components/timeline/MotionSegment.tsx @@ -144,7 +144,7 @@ export function MotionSegment({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]); - const segmentClasses = `h-2 relative w-full ${ + const segmentClasses = `h-[8px] relative w-full ${ showMinimap ? isInMinimapRange ? "bg-secondary-highlight" @@ -154,7 +154,7 @@ export function MotionSegment({ : "" } ${ isFirstSegmentInMinimap || isLastSegmentInMinimap - ? "relative h-2 border-b-2 border-gray-500" + ? "relative h-[8px] border-b-2 border-gray-500" : "" }`; @@ -223,9 +223,9 @@ export function MotionSegment({ )} -
-
-
+
+
+
-
+
0) { return ( -
+
{ - const segmentCount = reviewTimelineDuration / segmentDuration; + const segmentCount = Math.ceil(reviewTimelineDuration / segmentDuration); if (segmentHeight) { return Array.from({ length: segmentCount }, (_, index) => { diff --git a/web/src/components/timeline/segment-metadata.tsx b/web/src/components/timeline/segment-metadata.tsx index a0f303175..349f56276 100644 --- a/web/src/components/timeline/segment-metadata.tsx +++ b/web/src/components/timeline/segment-metadata.tsx @@ -59,7 +59,7 @@ export function MinimapBounds({ export function Tick({ timestamp, timestampSpread }: TickSegmentProps) { return (
-
+
+
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
{ - if (!motionData || !reviewItems) { + if (!motionData || !reviewItems || !motionData) { return; } @@ -100,8 +100,7 @@ export function useCameraMotionNextTimestamp( alignStartDateToTimeline(timeRangeSegmentEnd)) % segmentDuration; - const startIndex = - offset > 0 ? Math.floor(offset / (segmentDuration / 15)) : 0; + const startIndex = Math.abs(Math.floor(offset / 15)); for ( let i = startIndex; diff --git a/web/src/hooks/use-draggable-element.ts b/web/src/hooks/use-draggable-element.ts index f68ea5ebe..eb32ce2bb 100644 --- a/web/src/hooks/use-draggable-element.ts +++ b/web/src/hooks/use-draggable-element.ts @@ -42,10 +42,12 @@ function useDraggableElement({ setIsDragging, setDraggableElementPosition, }: DraggableElementProps) { + const segmentHeight = 8; const [clientYPosition, setClientYPosition] = useState(null); const [initialClickAdjustment, setInitialClickAdjustment] = useState(0); const [elementScrollIntoView, setElementScrollIntoView] = useState(true); const [scrollEdgeSize, setScrollEdgeSize] = useState(); + const [fullTimelineHeight, setFullTimelineHeight] = useState(); const [segments, setSegments] = useState([]); const { alignStartDateToTimeline, getCumulativeScrollTop } = useTimelineUtils( { @@ -137,15 +139,9 @@ function useDraggableElement({ const timestampToPixels = useCallback( (time: number) => { - const { scrollHeight: timelineHeight } = - timelineRef.current as HTMLDivElement; - - const segmentHeight = - timelineHeight / (timelineDuration / segmentDuration); - return ((timelineStartAligned - time) / segmentDuration) * segmentHeight; }, - [segmentDuration, timelineRef, timelineStartAligned, timelineDuration], + [segmentDuration, timelineStartAligned], ); const updateDraggableElementPosition = useCallback( @@ -226,21 +222,17 @@ function useDraggableElement({ showDraggableElement && isDragging && clientYPosition && - segments + segments && + fullTimelineHeight ) { - const { scrollHeight: timelineHeight, scrollTop: scrolled } = - timelineRef.current; - - const segmentHeight = - timelineHeight / (timelineDuration / segmentDuration); + const { scrollTop: scrolled } = timelineRef.current; const parentScrollTop = getCumulativeScrollTop(timelineRef.current); // bottom of timeline const elementEarliest = draggableElementEarliestTime ? timestampToPixels(draggableElementEarliestTime) - : segmentHeight * (timelineDuration / segmentDuration) - - segmentHeight * 3.5; + : fullTimelineHeight - segmentHeight * 1.5; // top of timeline - default 2 segments added for draggableElement visibility const elementLatest = draggableElementLatestTime @@ -314,7 +306,11 @@ function useDraggableElement({ scrollEdgeSize)) / scrollEdgeSize, ); - timelineRef.current.scrollTop += segmentHeight * intensity; + const newScrollTop = Math.min( + fullTimelineHeight - segmentHeight, + timelineRef.current.scrollTop + segmentHeight * intensity, + ); + timelineRef.current.scrollTop = newScrollTop; } } @@ -374,11 +370,7 @@ function useDraggableElement({ !isDragging && segments.length > 0 ) { - const { scrollHeight: timelineHeight, scrollTop: scrolled } = - timelineRef.current; - - const segmentHeight = - timelineHeight / (timelineDuration / segmentDuration); + const { scrollTop: scrolled } = timelineRef.current; const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime); @@ -426,6 +418,7 @@ function useDraggableElement({ useEffect(() => { if (timelineRef.current && draggableElementTime && timelineCollapsed) { + setFullTimelineHeight(timelineRef.current.scrollHeight); const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime); let segmentElement = timelineRef.current.querySelector( @@ -435,8 +428,12 @@ function useDraggableElement({ if (!segmentElement) { // segment not found, maybe we collapsed over a collapsible segment let searchTime = alignedSegmentTime; - while (searchTime >= timelineStartAligned - timelineDuration) { - searchTime -= segmentDuration; + + while ( + searchTime < timelineStartAligned && + searchTime < timelineStartAligned + timelineDuration + ) { + searchTime += segmentDuration; segmentElement = timelineRef.current.querySelector( `[data-segment-id="${searchTime}"]`, ); @@ -456,10 +453,11 @@ function useDraggableElement({ }, [timelineCollapsed]); useEffect(() => { - if (timelineRef.current) { + if (timelineRef.current && segments) { setScrollEdgeSize(timelineRef.current.clientHeight * 0.03); + setFullTimelineHeight(timelineRef.current.scrollHeight); } - }, [timelineRef]); + }, [timelineRef, segments]); return { handleMouseDown, handleMouseUp, handleMouseMove }; } diff --git a/web/src/hooks/use-timeline-utils.ts b/web/src/hooks/use-timeline-utils.ts index c52213161..0bd35a39c 100644 --- a/web/src/hooks/use-timeline-utils.ts +++ b/web/src/hooks/use-timeline-utils.ts @@ -40,13 +40,9 @@ export function useTimelineUtils({ const getVisibleTimelineDuration = useCallback(() => { if (timelineRef?.current && timelineDuration) { - const { - scrollHeight: timelineHeight, - clientHeight: visibleTimelineHeight, - } = timelineRef.current; + const { clientHeight: visibleTimelineHeight } = timelineRef.current; - const segmentHeight = - timelineHeight / (timelineDuration / segmentDuration); + const segmentHeight = 8; const visibleTime = (visibleTimelineHeight / segmentHeight) * segmentDuration; diff --git a/web/src/pages/UIPlayground.tsx b/web/src/pages/UIPlayground.tsx index 62f47fb57..7107abade 100644 --- a/web/src/pages/UIPlayground.tsx +++ b/web/src/pages/UIPlayground.tsx @@ -367,7 +367,7 @@ function UIPlayground() { segmentDuration={zoomSettings.segmentDuration} // seconds per segment timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time - timelineEnd={Math.floor(Date.now() / 1000) - 24 * 60 * 60} // end of timeline - the later time + timelineEnd={Math.floor(Date.now() / 1000) - 4 * 60 * 60} // end of timeline - the later time showHandlebar // show / hide the handlebar handlebarTime={handlebarTime} // set the time of the handlebar setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time @@ -391,7 +391,7 @@ function UIPlayground() { segmentDuration={zoomSettings.segmentDuration} // seconds per segment timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time - timelineEnd={Math.floor(Date.now() / 1000) - 24 * 60 * 60} // end of timeline - the later time + timelineEnd={Math.floor(Date.now() / 1000) - 4 * 60 * 60} // end of timeline - the later time showHandlebar // show / hide the handlebar handlebarTime={handlebarTime} // set the time of the handlebar setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time @@ -416,7 +416,7 @@ function UIPlayground() {