diff --git a/web/src/components/timeline/MotionReviewTimeline.tsx b/web/src/components/timeline/MotionReviewTimeline.tsx index 552fc6976..2aa1bbbc6 100644 --- a/web/src/components/timeline/MotionReviewTimeline.tsx +++ b/web/src/components/timeline/MotionReviewTimeline.tsx @@ -83,52 +83,17 @@ export function MotionReviewTimeline({ motion_events, ); - // Generate segment times for the timeline const segmentTimes = useMemo(() => { const segments = []; let segmentTime = timelineStartAligned; - while (segmentTime >= timelineStartAligned - timelineDuration) { - const motionStart = segmentTime; - const motionEnd = motionStart + segmentDuration; - - const firstHalfMotionValue = getMotionSegmentValue(motionStart); - const secondHalfMotionValue = getMotionSegmentValue( - motionStart + segmentDuration / 2, - ); - - const segmentMotion = - firstHalfMotionValue > 0 || secondHalfMotionValue > 0; - const overlappingReviewItems = events.some( - (item) => - (item.start_time >= motionStart && item.start_time < motionEnd) || - ((item.end_time ?? timelineStart) > motionStart && - (item.end_time ?? timelineStart) <= motionEnd) || - (item.start_time <= motionStart && - (item.end_time ?? timelineStart) >= motionEnd), - ); - - if ((!segmentMotion || overlappingReviewItems) && motionOnly) { - // exclude segment if necessary when in motion only mode - segmentTime -= segmentDuration; - continue; - } - + for (let i = 0; i < Math.ceil(timelineDuration / segmentDuration); i++) { segments.push(segmentTime); segmentTime -= segmentDuration; } + return segments; - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - segmentDuration, - timelineStartAligned, - timelineDuration, - events, - getMotionSegmentValue, - motion_events, - motionOnly, - ]); + }, [timelineStartAligned, segmentDuration, timelineDuration]); const scrollToSegment = useCallback( (segmentTime: number, ifNeeded?: boolean) => { diff --git a/web/src/components/timeline/VirtualizedMotionSegments.tsx b/web/src/components/timeline/VirtualizedMotionSegments.tsx index bfe9362cf..c0db7d043 100644 --- a/web/src/components/timeline/VirtualizedMotionSegments.tsx +++ b/web/src/components/timeline/VirtualizedMotionSegments.tsx @@ -9,7 +9,7 @@ import React, { import MotionSegment from "./MotionSegment"; import { ReviewSegment, MotionData } from "@/types/review"; -interface VirtualizedMotionSegmentsProps { +type VirtualizedMotionSegmentsProps = { timelineRef: React.RefObject; segments: number[]; events: ReviewSegment[]; @@ -24,7 +24,7 @@ interface VirtualizedMotionSegmentsProps { dense: boolean; motionOnly: boolean; getMotionSegmentValue: (timestamp: number) => number; -} +}; export interface VirtualizedMotionSegmentsRef { scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void; @@ -125,6 +125,75 @@ export const VirtualizedMotionSegments = forwardRef< scrollToSegment, })); + const renderSegment = useCallback( + (segmentTime: number, index: number) => { + const motionStart = segmentTime; + const motionEnd = motionStart + segmentDuration; + + const firstHalfMotionValue = getMotionSegmentValue(motionStart); + const secondHalfMotionValue = getMotionSegmentValue( + motionStart + segmentDuration / 2, + ); + + const segmentMotion = + firstHalfMotionValue > 0 || secondHalfMotionValue > 0; + const overlappingReviewItems = events.some( + (item) => + (item.start_time >= motionStart && item.start_time < motionEnd) || + ((item.end_time ?? segmentTime) > motionStart && + (item.end_time ?? segmentTime) <= motionEnd) || + (item.start_time <= motionStart && + (item.end_time ?? segmentTime) >= motionEnd), + ); + + if ((!segmentMotion || overlappingReviewItems) && motionOnly) { + return null; // Skip rendering this segment in motion only mode + } + + return ( +
+ +
+ ); + }, + [ + events, + getMotionSegmentValue, + motionOnly, + segmentDuration, + showMinimap, + minimapStartTime, + minimapEndTime, + setHandlebarTime, + scrollToSegment, + dense, + timestampSpread, + visibleRange.start, + ], + ); + const totalHeight = segments.length * SEGMENT_HEIGHT; const visibleSegments = segments.slice( visibleRange.start, @@ -149,46 +218,17 @@ export const VirtualizedMotionSegments = forwardRef< aria-hidden="true" /> )} - {visibleSegments.map((segmentTime, index) => { - const firstHalfMotionValue = getMotionSegmentValue(segmentTime); - const secondHalfMotionValue = getMotionSegmentValue( - segmentTime + segmentDuration / 2, - ); - - return ( -
- -
- ); - })} + {visibleSegments.map((segmentTime, index) => + renderSegment(segmentTime, index), + )} {visibleRange.end < segments.length && (