Timeline tweaks (#10693)

* make segment height static

* fix timeline overscrolling

* better alignment of motion timeline segments
This commit is contained in:
Josh Hawkins 2024-03-26 16:36:28 -05:00 committed by GitHub
parent 1377d33e25
commit c82ed43c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 48 additions and 55 deletions

View File

@ -166,7 +166,7 @@ export function EventReviewTimeline({
// Generate segments for the timeline // Generate segments for the timeline
const generateSegments = useCallback(() => { const generateSegments = useCallback(() => {
const segmentCount = timelineDuration / segmentDuration; const segmentCount = Math.ceil(timelineDuration / segmentDuration);
return Array.from({ length: segmentCount }, (_, index) => { return Array.from({ length: segmentCount }, (_, index) => {
const segmentTime = timelineStartAligned - index * segmentDuration; const segmentTime = timelineStartAligned - index * segmentDuration;

View File

@ -139,7 +139,7 @@ export function EventSegment({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]); }, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]);
const segmentClasses = `h-2 relative w-full ${ const segmentClasses = `h-[8px] relative w-full ${
showMinimap showMinimap
? isInMinimapRange ? isInMinimapRange
? "bg-secondary-highlight" ? "bg-secondary-highlight"
@ -149,7 +149,7 @@ export function EventSegment({
: "" : ""
} ${ } ${
isFirstSegmentInMinimap || isLastSegmentInMinimap 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 && ( {severityValue === displaySeverityType && (
<HoverCard openDelay={200} closeDelay={100}> <HoverCard openDelay={200} closeDelay={100}>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-2 z-10 cursor-pointer"> <div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-[8px] z-10 cursor-pointer">
<div className="flex flex-row justify-center w-[20px] md:w-[40px]"> <div className="flex flex-row justify-center w-[20px] md:w-[40px]">
<div className="flex justify-center"> <div className="flex justify-center">
<div <div
className="absolute left-1/2 transform -translate-x-1/2 w-[8px] h-2 ml-[2px] z-10 cursor-pointer" className="absolute left-1/2 transform -translate-x-1/2 w-[8px] h-[8px] ml-[2px] z-10 cursor-pointer"
data-severity={severityValue} data-severity={severityValue}
> >
<div <div
key={`${segmentKey}_${index}_primary_data`} key={`${segmentKey}_${index}_primary_data`}
className={`w-full h-2 bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`} className={`w-full h-[8px] bg-gradient-to-r ${roundBottomPrimary ? "rounded-bl-full rounded-br-full" : ""} ${roundTopPrimary ? "rounded-tl-full rounded-tr-full" : ""} ${severityColors[severityValue]}`}
></div> ></div>
</div> </div>
</div> </div>

View File

@ -169,7 +169,7 @@ export function MotionReviewTimeline({
// Generate segments for the timeline // Generate segments for the timeline
const generateSegments = useCallback(() => { const generateSegments = useCallback(() => {
const segmentCount = timelineDuration / segmentDuration; const segmentCount = Math.ceil(timelineDuration / segmentDuration);
return Array.from({ length: segmentCount }, (_, index) => { return Array.from({ length: segmentCount }, (_, index) => {
const segmentTime = timelineStartAligned - index * segmentDuration; const segmentTime = timelineStartAligned - index * segmentDuration;

View File

@ -144,7 +144,7 @@ export function MotionSegment({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]); }, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]);
const segmentClasses = `h-2 relative w-full ${ const segmentClasses = `h-[8px] relative w-full ${
showMinimap showMinimap
? isInMinimapRange ? isInMinimapRange
? "bg-secondary-highlight" ? "bg-secondary-highlight"
@ -154,7 +154,7 @@ export function MotionSegment({
: "" : ""
} ${ } ${
isFirstSegmentInMinimap || isLastSegmentInMinimap 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({
</> </>
)} )}
<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-2 z-10 cursor-pointer"> <div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-[8px] z-10 cursor-pointer">
<div className="flex flex-row justify-center w-[20px] md:w-[40px] mb-[1px]"> <div className="flex flex-row justify-center w-[20px] md:w-[40px] pt-[1px] mb-[1px]">
<div className="flex justify-center"> <div className="flex justify-center mb-[1px]">
<div <div
key={`${segmentKey}_motion_data_1`} key={`${segmentKey}_motion_data_1`}
data-motion-value={secondHalfSegmentWidth} data-motion-value={secondHalfSegmentWidth}
@ -237,7 +237,7 @@ export function MotionSegment({
</div> </div>
</div> </div>
<div className="flex flex-row justify-center w-[20px] md:w-[40px]"> <div className="flex flex-row justify-center pb-[1px] w-[20px] 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`}
@ -256,11 +256,11 @@ export function MotionSegment({
if (severityValue > 0) { if (severityValue > 0) {
return ( return (
<React.Fragment key={index}> <React.Fragment key={index}>
<div className="absolute right-0 h-2 z-10"> <div className="absolute right-0 h-[8px] z-10">
<div <div
key={`${segmentKey}_${index}_secondary_data`} key={`${segmentKey}_${index}_secondary_data`}
className={` className={`
w-1 h-2 bg-gradient-to-r w-1 h-[8px] bg-gradient-to-r
${roundBottomSecondary ? "rounded-bl-full rounded-br-full" : ""} ${roundBottomSecondary ? "rounded-bl-full rounded-br-full" : ""}
${roundTopSecondary ? "rounded-tl-full rounded-tr-full" : ""} ${roundTopSecondary ? "rounded-tl-full rounded-tr-full" : ""}
${severityColors[severityValue]} ${severityColors[severityValue]}

View File

@ -57,7 +57,7 @@ export function SummaryTimeline({
// Generate segments for the timeline // Generate segments for the timeline
const generateSegments = useCallback(() => { const generateSegments = useCallback(() => {
const segmentCount = reviewTimelineDuration / segmentDuration; const segmentCount = Math.ceil(reviewTimelineDuration / segmentDuration);
if (segmentHeight) { if (segmentHeight) {
return Array.from({ length: segmentCount }, (_, index) => { return Array.from({ length: segmentCount }, (_, index) => {

View File

@ -59,7 +59,7 @@ export function MinimapBounds({
export function Tick({ timestamp, timestampSpread }: TickSegmentProps) { export function Tick({ timestamp, timestampSpread }: TickSegmentProps) {
return ( return (
<div className="absolute"> <div className="absolute">
<div className="flex items-end content-end w-[12px] h-2"> <div className="flex items-end content-end w-[12px] h-[8px]">
<div <div
className={`pointer-events-none select-none h-0.5 ${ className={`pointer-events-none select-none h-0.5 ${
timestamp.getMinutes() % timestampSpread === 0 && timestamp.getMinutes() % timestampSpread === 0 &&
@ -84,7 +84,7 @@ export function Timestamp({
segmentKey, segmentKey,
}: TimestampSegmentProps) { }: TimestampSegmentProps) {
return ( return (
<div className="absolute left-[15px] h-2 z-10"> <div className="absolute left-[15px] h-[8px] z-10">
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && ( {!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
<div <div
key={`${segmentKey}_timestamp`} key={`${segmentKey}_timestamp`}

View File

@ -82,7 +82,7 @@ export function useCameraMotionNextTimestamp(
}); });
const noMotionRanges = useMemo(() => { const noMotionRanges = useMemo(() => {
if (!motionData || !reviewItems) { if (!motionData || !reviewItems || !motionData) {
return; return;
} }
@ -100,8 +100,7 @@ export function useCameraMotionNextTimestamp(
alignStartDateToTimeline(timeRangeSegmentEnd)) % alignStartDateToTimeline(timeRangeSegmentEnd)) %
segmentDuration; segmentDuration;
const startIndex = const startIndex = Math.abs(Math.floor(offset / 15));
offset > 0 ? Math.floor(offset / (segmentDuration / 15)) : 0;
for ( for (
let i = startIndex; let i = startIndex;

View File

@ -42,10 +42,12 @@ function useDraggableElement({
setIsDragging, setIsDragging,
setDraggableElementPosition, setDraggableElementPosition,
}: DraggableElementProps) { }: DraggableElementProps) {
const segmentHeight = 8;
const [clientYPosition, setClientYPosition] = useState<number | null>(null); const [clientYPosition, setClientYPosition] = useState<number | null>(null);
const [initialClickAdjustment, setInitialClickAdjustment] = useState(0); const [initialClickAdjustment, setInitialClickAdjustment] = useState(0);
const [elementScrollIntoView, setElementScrollIntoView] = useState(true); const [elementScrollIntoView, setElementScrollIntoView] = useState(true);
const [scrollEdgeSize, setScrollEdgeSize] = useState<number>(); const [scrollEdgeSize, setScrollEdgeSize] = useState<number>();
const [fullTimelineHeight, setFullTimelineHeight] = useState<number>();
const [segments, setSegments] = useState<HTMLDivElement[]>([]); const [segments, setSegments] = useState<HTMLDivElement[]>([]);
const { alignStartDateToTimeline, getCumulativeScrollTop } = useTimelineUtils( const { alignStartDateToTimeline, getCumulativeScrollTop } = useTimelineUtils(
{ {
@ -137,15 +139,9 @@ function useDraggableElement({
const timestampToPixels = useCallback( const timestampToPixels = useCallback(
(time: number) => { (time: number) => {
const { scrollHeight: timelineHeight } =
timelineRef.current as HTMLDivElement;
const segmentHeight =
timelineHeight / (timelineDuration / segmentDuration);
return ((timelineStartAligned - time) / segmentDuration) * segmentHeight; return ((timelineStartAligned - time) / segmentDuration) * segmentHeight;
}, },
[segmentDuration, timelineRef, timelineStartAligned, timelineDuration], [segmentDuration, timelineStartAligned],
); );
const updateDraggableElementPosition = useCallback( const updateDraggableElementPosition = useCallback(
@ -226,21 +222,17 @@ function useDraggableElement({
showDraggableElement && showDraggableElement &&
isDragging && isDragging &&
clientYPosition && clientYPosition &&
segments segments &&
fullTimelineHeight
) { ) {
const { scrollHeight: timelineHeight, scrollTop: scrolled } = const { scrollTop: scrolled } = timelineRef.current;
timelineRef.current;
const segmentHeight =
timelineHeight / (timelineDuration / segmentDuration);
const parentScrollTop = getCumulativeScrollTop(timelineRef.current); const parentScrollTop = getCumulativeScrollTop(timelineRef.current);
// bottom of timeline // bottom of timeline
const elementEarliest = draggableElementEarliestTime const elementEarliest = draggableElementEarliestTime
? timestampToPixels(draggableElementEarliestTime) ? timestampToPixels(draggableElementEarliestTime)
: segmentHeight * (timelineDuration / segmentDuration) - : fullTimelineHeight - segmentHeight * 1.5;
segmentHeight * 3.5;
// top of timeline - default 2 segments added for draggableElement visibility // top of timeline - default 2 segments added for draggableElement visibility
const elementLatest = draggableElementLatestTime const elementLatest = draggableElementLatestTime
@ -314,7 +306,11 @@ function useDraggableElement({
scrollEdgeSize)) / scrollEdgeSize)) /
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 && !isDragging &&
segments.length > 0 segments.length > 0
) { ) {
const { scrollHeight: timelineHeight, scrollTop: scrolled } = const { scrollTop: scrolled } = timelineRef.current;
timelineRef.current;
const segmentHeight =
timelineHeight / (timelineDuration / segmentDuration);
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime); const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
@ -426,6 +418,7 @@ function useDraggableElement({
useEffect(() => { useEffect(() => {
if (timelineRef.current && draggableElementTime && timelineCollapsed) { if (timelineRef.current && draggableElementTime && timelineCollapsed) {
setFullTimelineHeight(timelineRef.current.scrollHeight);
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime); const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
let segmentElement = timelineRef.current.querySelector( let segmentElement = timelineRef.current.querySelector(
@ -435,8 +428,12 @@ function useDraggableElement({
if (!segmentElement) { if (!segmentElement) {
// segment not found, maybe we collapsed over a collapsible segment // segment not found, maybe we collapsed over a collapsible segment
let searchTime = alignedSegmentTime; let searchTime = alignedSegmentTime;
while (searchTime >= timelineStartAligned - timelineDuration) {
searchTime -= segmentDuration; while (
searchTime < timelineStartAligned &&
searchTime < timelineStartAligned + timelineDuration
) {
searchTime += segmentDuration;
segmentElement = timelineRef.current.querySelector( segmentElement = timelineRef.current.querySelector(
`[data-segment-id="${searchTime}"]`, `[data-segment-id="${searchTime}"]`,
); );
@ -456,10 +453,11 @@ function useDraggableElement({
}, [timelineCollapsed]); }, [timelineCollapsed]);
useEffect(() => { useEffect(() => {
if (timelineRef.current) { if (timelineRef.current && segments) {
setScrollEdgeSize(timelineRef.current.clientHeight * 0.03); setScrollEdgeSize(timelineRef.current.clientHeight * 0.03);
setFullTimelineHeight(timelineRef.current.scrollHeight);
} }
}, [timelineRef]); }, [timelineRef, segments]);
return { handleMouseDown, handleMouseUp, handleMouseMove }; return { handleMouseDown, handleMouseUp, handleMouseMove };
} }

View File

@ -40,13 +40,9 @@ export function useTimelineUtils({
const getVisibleTimelineDuration = useCallback(() => { const getVisibleTimelineDuration = useCallback(() => {
if (timelineRef?.current && timelineDuration) { if (timelineRef?.current && timelineDuration) {
const { const { clientHeight: visibleTimelineHeight } = timelineRef.current;
scrollHeight: timelineHeight,
clientHeight: visibleTimelineHeight,
} = timelineRef.current;
const segmentHeight = const segmentHeight = 8;
timelineHeight / (timelineDuration / segmentDuration);
const visibleTime = const visibleTime =
(visibleTimelineHeight / segmentHeight) * segmentDuration; (visibleTimelineHeight / segmentHeight) * segmentDuration;

View File

@ -367,7 +367,7 @@ function UIPlayground() {
segmentDuration={zoomSettings.segmentDuration} // seconds per segment segmentDuration={zoomSettings.segmentDuration} // seconds per segment
timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp
timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time 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 showHandlebar // show / hide the handlebar
handlebarTime={handlebarTime} // set the time of the handlebar handlebarTime={handlebarTime} // set the time of the handlebar
setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time
@ -391,7 +391,7 @@ function UIPlayground() {
segmentDuration={zoomSettings.segmentDuration} // seconds per segment segmentDuration={zoomSettings.segmentDuration} // seconds per segment
timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp timestampSpread={zoomSettings.timestampSpread} // minutes between each major timestamp
timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time 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 showHandlebar // show / hide the handlebar
handlebarTime={handlebarTime} // set the time of the handlebar handlebarTime={handlebarTime} // set the time of the handlebar
setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time setHandlebarTime={setHandlebarTime} // expose handler to set the handlebar time
@ -416,7 +416,7 @@ function UIPlayground() {
<SummaryTimeline <SummaryTimeline
reviewTimelineRef={reviewTimelineRef} // the ref to the review timeline reviewTimelineRef={reviewTimelineRef} // the ref to the review timeline
timelineStart={Math.floor(Date.now() / 1000)} // timestamp start of the timeline - the earlier time 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
segmentDuration={zoomSettings.segmentDuration} segmentDuration={zoomSettings.segmentDuration}
events={mockEvents} events={mockEvents}
severityType={"alert"} // show only events of this severity on the summary timeline severityType={"alert"} // show only events of this severity on the summary timeline