From a49e1bbc64dd1964837d702208a45c0a7a787aec Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:36:13 -0600 Subject: [PATCH] Timeline handlebar changes (#10170) * auto scrolling handlebar with preview time * tablets can show 2 columns on the event view grid * font sizes * hide minimap when previewing --- .../player/PreviewThumbnailPlayer.tsx | 2 +- .../timeline/EventReviewTimeline.tsx | 75 ++--------- web/src/hooks/use-handle-dragging.ts | 118 +++++++++++------- web/src/views/events/EventView.tsx | 4 +- 4 files changed, 86 insertions(+), 113 deletions(-) diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 5e7c01ddab..d9e6b29dcf 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -33,7 +33,7 @@ import { useSwipeable } from "react-swipeable"; type PreviewPlayerProps = { review: ReviewSegment; allPreviews?: Preview[]; - onTimeUpdate?: (time: number | undefined) => void; + onTimeUpdate?: React.Dispatch>; setReviewed: (reviewId: string) => void; markAboveReviewed: () => void; onClick: (reviewId: string, ctrl: boolean) => void; diff --git a/web/src/components/timeline/EventReviewTimeline.tsx b/web/src/components/timeline/EventReviewTimeline.tsx index b5871283a5..e78f9ff21c 100644 --- a/web/src/components/timeline/EventReviewTimeline.tsx +++ b/web/src/components/timeline/EventReviewTimeline.tsx @@ -45,10 +45,9 @@ export function EventReviewTimeline({ onHandlebarDraggingChange, }: EventReviewTimelineProps) { const [isDragging, setIsDragging] = useState(false); - const [currentTimeSegment, setCurrentTimeSegment] = useState(0); const scrollTimeRef = useRef(null); const timelineRef = useRef(null); - const currentTimeRef = useRef(null); + const handlebarTimeRef = useRef(null); const observer = useRef(null); const timelineDuration = useMemo( () => timelineStart - timelineEnd, @@ -69,12 +68,13 @@ export function EventReviewTimeline({ alignEndDateToTimeline, segmentDuration, showHandlebar, + handlebarTime, + setHandlebarTime, timelineDuration, timelineStart, isDragging, setIsDragging, - currentTimeRef, - setHandlebarTime, + handlebarTimeRef, }); function handleResize() { @@ -151,66 +151,15 @@ export function EventReviewTimeline({ ], ); - useEffect(() => { - if (showHandlebar) { - requestAnimationFrame(() => { - if (currentTimeRef.current && currentTimeSegment) { - currentTimeRef.current.textContent = new Date( - currentTimeSegment * 1000, - ).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - ...(segmentDuration < 60 && { second: "2-digit" }), - }); - } - }); - } - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentTimeSegment, showHandlebar]); - useEffect(() => { if (onHandlebarDraggingChange) { onHandlebarDraggingChange(isDragging); } }, [isDragging, onHandlebarDraggingChange]); - useEffect(() => { - if (timelineRef.current && handlebarTime && showHandlebar) { - const { scrollHeight: timelineHeight } = timelineRef.current; - - // Calculate the height of an individual segment - const segmentHeight = - timelineHeight / (timelineDuration / segmentDuration); - - // Calculate the segment index corresponding to the target time - const alignedHandlebarTime = alignStartDateToTimeline(handlebarTime); - const segmentIndex = Math.ceil( - (timelineStart - alignedHandlebarTime) / segmentDuration, - ); - - // Calculate the top position based on the segment index - const newTopPosition = Math.max(0, segmentIndex * segmentHeight); - - // Set the top position of the handle - const thumb = scrollTimeRef.current; - if (thumb) { - requestAnimationFrame(() => { - thumb.style.top = `${newTopPosition}px`; - }); - } - - setCurrentTimeSegment(alignedHandlebarTime); - } - // should only be run once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useEffect(() => { generateSegments(); - if (!currentTimeSegment && !handlebarTime) { - setCurrentTimeSegment(timelineStart); - } + // TODO: touch events for mobile document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); @@ -220,13 +169,7 @@ export function EventReviewTimeline({ }; // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - currentTimeSegment, - generateSegments, - timelineStart, - handleMouseUp, - handleMouseMove, - ]); + }, [generateSegments, timelineStart, handleMouseUp, handleMouseMove]); return (
diff --git a/web/src/hooks/use-handle-dragging.ts b/web/src/hooks/use-handle-dragging.ts index 889709441a..b208526d0c 100644 --- a/web/src/hooks/use-handle-dragging.ts +++ b/web/src/hooks/use-handle-dragging.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -interface DragHandlerProps { +type DragHandlerProps = { contentRef: React.RefObject; timelineRef: React.RefObject; scrollTimeRef: React.RefObject; @@ -8,15 +8,15 @@ interface DragHandlerProps { alignEndDateToTimeline: (time: number) => number; segmentDuration: number; showHandlebar: boolean; + handlebarTime?: number; + setHandlebarTime?: React.Dispatch>; + handlebarTimeRef: React.MutableRefObject; timelineDuration: number; timelineStart: number; isDragging: boolean; setIsDragging: React.Dispatch>; - currentTimeRef: React.MutableRefObject; - setHandlebarTime?: React.Dispatch>; -} +}; -// TODO: handle mobile touch events function useDraggableHandler({ contentRef, timelineRef, @@ -24,12 +24,13 @@ function useDraggableHandler({ alignStartDateToTimeline, segmentDuration, showHandlebar, + handlebarTime, + setHandlebarTime, + handlebarTimeRef, timelineDuration, timelineStart, isDragging, setIsDragging, - currentTimeRef, - setHandlebarTime, }: DragHandlerProps) { const handleMouseDown = useCallback( (e: React.MouseEvent) => { @@ -51,6 +52,39 @@ function useDraggableHandler({ [isDragging, setIsDragging], ); + const getCumulativeScrollTop = useCallback((element: HTMLElement | null) => { + let scrollTop = 0; + while (element) { + scrollTop += element.scrollTop; + element = element.parentElement; + } + return scrollTop; + }, []); + + const updateHandlebarPosition = useCallback( + (newHandlePosition: number, segmentStartTime: number) => { + const thumb = scrollTimeRef.current; + if (thumb) { + requestAnimationFrame(() => { + thumb.style.top = `${newHandlePosition}px`; + if (handlebarTimeRef.current) { + handlebarTimeRef.current.textContent = new Date( + segmentStartTime * 1000, + ).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + ...(segmentDuration < 60 && { second: "2-digit" }), + }); + } + }); + if (setHandlebarTime) { + setHandlebarTime(segmentStartTime); + } + } + }, + [segmentDuration, handlebarTimeRef, scrollTimeRef, setHandlebarTime], + ); + const handleMouseMove = useCallback( (e: MouseEvent) => { if ( @@ -64,7 +98,7 @@ function useDraggableHandler({ e.preventDefault(); e.stopPropagation(); - if (isDragging) { + if (showHandlebar && isDragging) { const { scrollHeight: timelineHeight, clientHeight: visibleTimelineHeight, @@ -75,15 +109,6 @@ function useDraggableHandler({ const segmentHeight = timelineHeight / (timelineDuration / segmentDuration); - const getCumulativeScrollTop = (element: HTMLElement | null) => { - let scrollTop = 0; - while (element) { - scrollTop += element.scrollTop; - element = element.parentElement; - } - return scrollTop; - }; - const parentScrollTop = getCumulativeScrollTop(timelineRef.current); const newHandlePosition = Math.min( @@ -99,27 +124,10 @@ function useDraggableHandler({ timelineStart - segmentIndex * segmentDuration, ); - if (showHandlebar) { - const thumb = scrollTimeRef.current; - requestAnimationFrame(() => { - thumb.style.top = `${newHandlePosition - segmentHeight}px`; - if (currentTimeRef.current) { - currentTimeRef.current.textContent = new Date( - segmentStartTime * 1000, - ).toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - ...(segmentDuration < 60 && { second: "2-digit" }), - }); - } - }); - if (setHandlebarTime) { - setHandlebarTime( - timelineStart - - (newHandlePosition / segmentHeight) * segmentDuration, - ); - } - } + updateHandlebarPosition( + newHandlePosition - segmentHeight, + segmentStartTime, + ); } }, // we know that these deps are correct @@ -131,21 +139,43 @@ function useDraggableHandler({ showHandlebar, timelineDuration, timelineStart, + updateHandlebarPosition, + alignStartDateToTimeline, + getCumulativeScrollTop, ], ); useEffect(() => { - // TODO: determine when we want to do this - const handlebar = scrollTimeRef.current; - if (handlebar && showHandlebar) { - handlebar.scrollIntoView({ + if ( + timelineRef.current && + scrollTimeRef.current && + showHandlebar && + handlebarTime && + !isDragging + ) { + const { scrollHeight: timelineHeight, scrollTop: scrolled } = + timelineRef.current; + + const segmentHeight = + timelineHeight / (timelineDuration / segmentDuration); + + const parentScrollTop = getCumulativeScrollTop(timelineRef.current); + + const newHandlePosition = + ((timelineStart - handlebarTime) / segmentDuration) * segmentHeight + + parentScrollTop - + scrolled; + + updateHandlebarPosition(newHandlePosition - segmentHeight, handlebarTime); + + scrollTimeRef.current.scrollIntoView({ behavior: "smooth", block: "center", }); } - // temporary until behavior is decided + // we know that these deps are correct // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [handlebarTime, showHandlebar, scrollTimeRef, timelineStart]); return { handleMouseDown, handleMouseUp, handleMouseMove }; } diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 866639b674..d194e156ef 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -322,7 +322,7 @@ export default function EventView({ )}
{currentItems ? ( @@ -366,7 +366,7 @@ export default function EventView({ timestampSpread={15} timelineStart={timeRange.before} timelineEnd={timeRange.after} - showMinimap={showMinimap} + showMinimap={showMinimap && !previewTime} minimapStartTime={minimapBounds.start} minimapEndTime={minimapBounds.end} showHandlebar={previewTime != undefined}