From 34d8c93325c5707007ed8de9ede5e8af21ffac48 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:04:50 -0500 Subject: [PATCH] black background --- .../timeline/MotionReviewTimeline.tsx | 1 + web/src/components/timeline/MotionSegment.tsx | 99 +++++++++++-------- .../components/timeline/ReviewTimeline.tsx | 27 +++++ .../timeline/VirtualizedMotionSegments.tsx | 11 +++ web/tailwind.config.cjs | 2 +- 5 files changed, 98 insertions(+), 42 deletions(-) diff --git a/web/src/components/timeline/MotionReviewTimeline.tsx b/web/src/components/timeline/MotionReviewTimeline.tsx index 662ccf150..9fb8dbdc0 100644 --- a/web/src/components/timeline/MotionReviewTimeline.tsx +++ b/web/src/components/timeline/MotionReviewTimeline.tsx @@ -203,6 +203,7 @@ export function MotionReviewTimeline({ scrollToSegment={scrollToSegment} isZooming={isZooming} zoomDirection={zoomDirection} + getRecordingAvailability={getRecordingAvailability} > { + return hasRecording === false && prevIsNoRecording === false; + }, [hasRecording, prevIsNoRecording]); + + // Bottom border: current segment has no recording, but next segment has recordings + const isLastSegmentWithoutRecording = useMemo(() => { + return hasRecording === false && nextIsNoRecording === false; + }, [hasRecording, nextIsNoRecording]); + const firstMinimapSegmentRef = useRef(null); useEffect(() => { @@ -178,16 +192,17 @@ export function MotionSegment({ segmentClasses, severity[0] && "bg-gradient-to-r", severity[0] && severityColorsBg[severity[0]], - // TODO: will update this for 0.17 - false && - hasRecording == false && - firstHalfMotionValue == 0 && - secondHalfMotionValue == 0 && - "bg-slashes", + hasRecording == false && "bg-background", )} onClick={segmentClick} onTouchEnd={(event) => handleTouchStart(event, segmentClick)} > + {isFirstSegmentWithoutRecording && ( +
+ )} + {isLastSegmentWithoutRecording && ( +
+ )} {!motionOnly && ( <> {showMinimap && ( @@ -218,45 +233,47 @@ export function MotionSegment({ )} -
-
-
-
+ {hasRecording && ( +
+
+
+
+
-
-
-
-
+
+
+
+
-
+ )}
)} diff --git a/web/src/components/timeline/ReviewTimeline.tsx b/web/src/components/timeline/ReviewTimeline.tsx index d2238a718..6d1d3a0b5 100644 --- a/web/src/components/timeline/ReviewTimeline.tsx +++ b/web/src/components/timeline/ReviewTimeline.tsx @@ -36,6 +36,7 @@ export type ReviewTimelineProps = { scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void; isZooming: boolean; zoomDirection: TimelineZoomDirection; + getRecordingAvailability?: (time: number) => boolean | undefined; children: ReactNode; }; @@ -61,6 +62,7 @@ export function ReviewTimeline({ scrollToSegment, isZooming, zoomDirection, + getRecordingAvailability, children, }: ReviewTimelineProps) { const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false); @@ -326,6 +328,25 @@ export function ReviewTimeline({ } }, [isDraggingHandlebar, onHandlebarDraggingChange]); + const isHandlebarInNoRecordingPeriod = useMemo(() => { + if (!getRecordingAvailability || handlebarTime === undefined) return false; + + // Check current segment + const currentAvailability = getRecordingAvailability(handlebarTime); + if (currentAvailability !== false) return false; + + // Check if at least one adjacent segment also has no recordings + const beforeAvailability = getRecordingAvailability( + handlebarTime - segmentDuration, + ); + const afterAvailability = getRecordingAvailability( + handlebarTime + segmentDuration, + ); + + // If current segment has no recordings AND at least one adjacent segment also has no recordings + return beforeAvailability === false || afterAvailability === false; + }, [getRecordingAvailability, handlebarTime, segmentDuration]); + return (
+ {/* TODO: determine if we should keep this tooltip */} + {isHandlebarInNoRecordingPeriod && ( +
+ No recordings +
+ )}
)} {showExportHandles && ( diff --git a/web/src/components/timeline/VirtualizedMotionSegments.tsx b/web/src/components/timeline/VirtualizedMotionSegments.tsx index fc7a8224f..15641896d 100644 --- a/web/src/components/timeline/VirtualizedMotionSegments.tsx +++ b/web/src/components/timeline/VirtualizedMotionSegments.tsx @@ -158,6 +158,15 @@ export const VirtualizedMotionSegments = forwardRef< const hasRecording = getRecordingAvailability(segmentTime); + const prevSegmentTime = segmentTime + segmentDuration; + const nextSegmentTime = segmentTime - segmentDuration; + + const prevHasRecording = getRecordingAvailability(prevSegmentTime); + const nextHasRecording = getRecordingAvailability(nextSegmentTime); + + const prevIsNoRecording = prevHasRecording === false; + const nextIsNoRecording = nextHasRecording === false; + if ((!segmentMotion || overlappingReviewItems) && motionOnly) { return null; // Skip rendering this segment in motion only mode } @@ -177,6 +186,8 @@ export const VirtualizedMotionSegments = forwardRef< firstHalfMotionValue={firstHalfMotionValue} secondHalfMotionValue={secondHalfMotionValue} hasRecording={hasRecording} + prevIsNoRecording={prevIsNoRecording} + nextIsNoRecording={nextIsNoRecording} segmentDuration={segmentDuration} segmentTime={segmentTime} timestampSpread={timestampSpread} diff --git a/web/tailwind.config.cjs b/web/tailwind.config.cjs index 33d403e4f..eb1195bda 100644 --- a/web/tailwind.config.cjs +++ b/web/tailwind.config.cjs @@ -44,7 +44,7 @@ module.exports = { }, backgroundImage: { slashes: - "repeating-linear-gradient(45deg, hsl(var(--primary-variant) / 0.2), hsl(var(--primary-variant) / 0.2) 2px, transparent 2px, transparent 8px)", + "repeating-linear-gradient(135deg, hsl(var(--primary-variant) / 0.3), hsl(var(--primary-variant) / 0.3) 2px, transparent 2px, transparent 8px), linear-gradient(to right, hsl(var(--background)), hsl(var(--background)))", }, colors: { border: "hsl(var(--border))",