black background

This commit is contained in:
Josh Hawkins 2025-10-28 21:04:50 -05:00
parent 576f692dae
commit 34d8c93325
5 changed files with 98 additions and 42 deletions

View File

@ -203,6 +203,7 @@ export function MotionReviewTimeline({
scrollToSegment={scrollToSegment} scrollToSegment={scrollToSegment}
isZooming={isZooming} isZooming={isZooming}
zoomDirection={zoomDirection} zoomDirection={zoomDirection}
getRecordingAvailability={getRecordingAvailability}
> >
<VirtualizedMotionSegments <VirtualizedMotionSegments
ref={virtualizedSegmentsRef} ref={virtualizedSegmentsRef}

View File

@ -16,6 +16,8 @@ type MotionSegmentProps = {
firstHalfMotionValue: number; firstHalfMotionValue: number;
secondHalfMotionValue: number; secondHalfMotionValue: number;
hasRecording?: boolean; hasRecording?: boolean;
prevIsNoRecording?: boolean;
nextIsNoRecording?: boolean;
motionOnly: boolean; motionOnly: boolean;
showMinimap: boolean; showMinimap: boolean;
minimapStartTime?: number; minimapStartTime?: number;
@ -33,6 +35,8 @@ export function MotionSegment({
firstHalfMotionValue, firstHalfMotionValue,
secondHalfMotionValue, secondHalfMotionValue,
hasRecording, hasRecording,
prevIsNoRecording,
nextIsNoRecording,
motionOnly, motionOnly,
showMinimap, showMinimap,
minimapStartTime, minimapStartTime,
@ -116,6 +120,16 @@ export function MotionSegment({
return showMinimap && segmentTime === alignedMinimapEndTime; return showMinimap && segmentTime === alignedMinimapEndTime;
}, [showMinimap, segmentTime, alignedMinimapEndTime]); }, [showMinimap, segmentTime, alignedMinimapEndTime]);
// Top border: current segment has no recording, but previous segment has recordings
const isFirstSegmentWithoutRecording = useMemo(() => {
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<HTMLDivElement>(null); const firstMinimapSegmentRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
@ -178,16 +192,17 @@ export function MotionSegment({
segmentClasses, segmentClasses,
severity[0] && "bg-gradient-to-r", severity[0] && "bg-gradient-to-r",
severity[0] && severityColorsBg[severity[0]], severity[0] && severityColorsBg[severity[0]],
// TODO: will update this for 0.17 hasRecording == false && "bg-background",
false &&
hasRecording == false &&
firstHalfMotionValue == 0 &&
secondHalfMotionValue == 0 &&
"bg-slashes",
)} )}
onClick={segmentClick} onClick={segmentClick}
onTouchEnd={(event) => handleTouchStart(event, segmentClick)} onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
> >
{isFirstSegmentWithoutRecording && (
<div className="absolute -top-[1px] left-0 right-0 h-[1px] bg-primary-variant/50" />
)}
{isLastSegmentWithoutRecording && (
<div className="absolute bottom-[0px] left-0 right-0 h-[1px] bg-primary-variant/50" />
)}
{!motionOnly && ( {!motionOnly && (
<> <>
{showMinimap && ( {showMinimap && (
@ -218,45 +233,47 @@ export function MotionSegment({
</> </>
)} )}
<div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]"> {hasRecording && (
<div className="mb-[1px] flex w-[20px] flex-row justify-center pt-[1px] md:w-[40px]"> <div className="absolute left-1/2 z-10 h-[8px] w-[20px] -translate-x-1/2 transform cursor-pointer md:w-[40px]">
<div className="mb-[1px] flex justify-center"> <div className="mb-[1px] flex w-[20px] flex-row justify-center pt-[1px] md:w-[40px]">
<div <div className="mb-[1px] flex justify-center">
key={`${segmentKey}_motion_data_1`} <div
data-motion-value={secondHalfSegmentWidth} key={`${segmentKey}_motion_data_1`}
className={cn( data-motion-value={secondHalfSegmentWidth}
"h-[2px]", className={cn(
"rounded-full", "h-[2px]",
secondHalfSegmentWidth "rounded-full",
? "bg-motion_review" secondHalfSegmentWidth
: "bg-muted-foreground", ? "bg-motion_review"
)} : "bg-muted-foreground",
style={{ )}
width: secondHalfSegmentWidth || 1, style={{
}} width: secondHalfSegmentWidth || 1,
></div> }}
></div>
</div>
</div> </div>
</div>
<div className="flex w-[20px] flex-row justify-center pb-[1px] md:w-[40px]"> <div className="flex w-[20px] flex-row justify-center pb-[1px] md:w-[40px]">
<div className="flex justify-center"> <div className="flex justify-center">
<div <div
key={`${segmentKey}_motion_data_2`} key={`${segmentKey}_motion_data_2`}
data-motion-value={firstHalfSegmentWidth} data-motion-value={firstHalfSegmentWidth}
className={cn( className={cn(
"h-[2px]", "h-[2px]",
"rounded-full", "rounded-full",
firstHalfSegmentWidth firstHalfSegmentWidth
? "bg-motion_review" ? "bg-motion_review"
: "bg-muted-foreground", : "bg-muted-foreground",
)} )}
style={{ style={{
width: firstHalfSegmentWidth || 1, width: firstHalfSegmentWidth || 1,
}} }}
></div> ></div>
</div>
</div> </div>
</div> </div>
</div> )}
</div> </div>
)} )}
</> </>

View File

@ -36,6 +36,7 @@ export type ReviewTimelineProps = {
scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void; scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void;
isZooming: boolean; isZooming: boolean;
zoomDirection: TimelineZoomDirection; zoomDirection: TimelineZoomDirection;
getRecordingAvailability?: (time: number) => boolean | undefined;
children: ReactNode; children: ReactNode;
}; };
@ -61,6 +62,7 @@ export function ReviewTimeline({
scrollToSegment, scrollToSegment,
isZooming, isZooming,
zoomDirection, zoomDirection,
getRecordingAvailability,
children, children,
}: ReviewTimelineProps) { }: ReviewTimelineProps) {
const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false); const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
@ -326,6 +328,25 @@ export function ReviewTimeline({
} }
}, [isDraggingHandlebar, onHandlebarDraggingChange]); }, [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 ( return (
<div <div
ref={timelineRef} ref={timelineRef}
@ -380,6 +401,12 @@ export function ReviewTimeline({
></div> ></div>
</div> </div>
</div> </div>
{/* TODO: determine if we should keep this tooltip */}
{isHandlebarInNoRecordingPeriod && (
<div className="absolute left-1/2 top-full z-50 mt-2 -translate-x-1/2 rounded-md bg-destructive/80 px-4 py-1 text-center text-xs text-white shadow-lg">
No recordings
</div>
)}
</div> </div>
)} )}
{showExportHandles && ( {showExportHandles && (

View File

@ -158,6 +158,15 @@ export const VirtualizedMotionSegments = forwardRef<
const hasRecording = getRecordingAvailability(segmentTime); 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) { if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
return null; // Skip rendering this segment in motion only mode return null; // Skip rendering this segment in motion only mode
} }
@ -177,6 +186,8 @@ export const VirtualizedMotionSegments = forwardRef<
firstHalfMotionValue={firstHalfMotionValue} firstHalfMotionValue={firstHalfMotionValue}
secondHalfMotionValue={secondHalfMotionValue} secondHalfMotionValue={secondHalfMotionValue}
hasRecording={hasRecording} hasRecording={hasRecording}
prevIsNoRecording={prevIsNoRecording}
nextIsNoRecording={nextIsNoRecording}
segmentDuration={segmentDuration} segmentDuration={segmentDuration}
segmentTime={segmentTime} segmentTime={segmentTime}
timestampSpread={timestampSpread} timestampSpread={timestampSpread}

View File

@ -44,7 +44,7 @@ module.exports = {
}, },
backgroundImage: { backgroundImage: {
slashes: 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: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border))",