diff --git a/web/src/hooks/use-draggable-element.ts b/web/src/hooks/use-draggable-element.ts index 51b6a6a52..0168cd5a2 100644 --- a/web/src/hooks/use-draggable-element.ts +++ b/web/src/hooks/use-draggable-element.ts @@ -1,4 +1,11 @@ -import { ReactNode, useCallback, useEffect, useMemo, useState } from "react"; +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import scrollIntoView from "scroll-into-view-if-needed"; import { useTimelineUtils } from "./use-timeline-utils"; import { FrigateConfig } from "@/types/frigateConfig"; @@ -66,6 +73,12 @@ function useDraggableElement({ timelineRef, }); + // track user interaction and adjust scrolling behavior + + const [userInteracting, setUserInteracting] = useState(false); + const interactionTimeout = useRef(); + const isProgrammaticScroll = useRef(false); + const draggingAtTopEdge = useMemo(() => { if (clientYPosition && timelineRef.current && scrollEdgeSize) { const timelineRect = timelineRef.current.getBoundingClientRect(); @@ -179,7 +192,7 @@ function useDraggableElement({ minute: "2-digit", ...(segmentDuration < 60 && !dense && { second: "2-digit" }), }); - if (scrollTimeline) { + if (scrollTimeline && !userInteracting) { scrollIntoView(thumb, { block: "center", behavior: "smooth", @@ -202,6 +215,7 @@ function useDraggableElement({ setDraggableElementPosition, dense, config, + userInteracting, ], ); @@ -510,6 +524,47 @@ function useDraggableElement({ } }, [timelineRef, segmentsRef, segments]); + useEffect(() => { + const handleUserInteraction = () => { + if (!isProgrammaticScroll.current) { + setUserInteracting(true); + + if (interactionTimeout.current) { + clearTimeout(interactionTimeout.current); + } + + interactionTimeout.current = setTimeout(() => { + setUserInteracting(false); + }, 3000); + } else { + isProgrammaticScroll.current = false; + } + }; + + const timelineElement = timelineRef.current; + + if (timelineElement) { + timelineElement.addEventListener("scroll", handleUserInteraction); + timelineElement.addEventListener("mousedown", handleUserInteraction); + timelineElement.addEventListener("mouseup", handleUserInteraction); + timelineElement.addEventListener("touchstart", handleUserInteraction); + timelineElement.addEventListener("touchmove", handleUserInteraction); + timelineElement.addEventListener("touchend", handleUserInteraction); + + return () => { + timelineElement.removeEventListener("scroll", handleUserInteraction); + timelineElement.removeEventListener("mousedown", handleUserInteraction); + timelineElement.removeEventListener("mouseup", handleUserInteraction); + timelineElement.removeEventListener( + "touchstart", + handleUserInteraction, + ); + timelineElement.removeEventListener("touchmove", handleUserInteraction); + timelineElement.removeEventListener("touchend", handleUserInteraction); + }; + } + }, [timelineRef]); + return { handleMouseDown, handleMouseUp, handleMouseMove }; } diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index f01da9578..0c59cef38 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -702,7 +702,6 @@ function Timeline({ setExportEndTime={setExportEndTime} handlebarTime={currentTime} setHandlebarTime={setCurrentTime} - onlyInitialHandlebarScroll={true} events={mainCameraReviewItems} motion_events={motionData ?? []} severityType="significant_motion"