mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
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
This commit is contained in:
parent
49530dc2e4
commit
a49e1bbc64
@ -33,7 +33,7 @@ import { useSwipeable } from "react-swipeable";
|
||||
type PreviewPlayerProps = {
|
||||
review: ReviewSegment;
|
||||
allPreviews?: Preview[];
|
||||
onTimeUpdate?: (time: number | undefined) => void;
|
||||
onTimeUpdate?: React.Dispatch<React.SetStateAction<number | undefined>>;
|
||||
setReviewed: (reviewId: string) => void;
|
||||
markAboveReviewed: () => void;
|
||||
onClick: (reviewId: string, ctrl: boolean) => void;
|
||||
|
@ -45,10 +45,9 @@ export function EventReviewTimeline({
|
||||
onHandlebarDraggingChange,
|
||||
}: EventReviewTimelineProps) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [currentTimeSegment, setCurrentTimeSegment] = useState<number>(0);
|
||||
const scrollTimeRef = useRef<HTMLDivElement>(null);
|
||||
const timelineRef = useRef<HTMLDivElement>(null);
|
||||
const currentTimeRef = useRef<HTMLDivElement>(null);
|
||||
const handlebarTimeRef = useRef<HTMLDivElement>(null);
|
||||
const observer = useRef<ResizeObserver | null>(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 (
|
||||
<div
|
||||
@ -248,12 +191,12 @@ export function EventReviewTimeline({
|
||||
>
|
||||
<div
|
||||
className={`bg-destructive rounded-full mx-auto ${
|
||||
segmentDuration < 60 ? "w-20" : "w-16"
|
||||
segmentDuration < 60 ? "w-14 md:w-20" : "w-12 md:w-16"
|
||||
} h-5 flex items-center justify-center`}
|
||||
>
|
||||
<div
|
||||
ref={currentTimeRef}
|
||||
className="text-white text-xs z-10"
|
||||
ref={handlebarTimeRef}
|
||||
className="text-white text-[8px] md:text-xs z-10"
|
||||
></div>
|
||||
</div>
|
||||
<div className="absolute h-1 w-full bg-destructive top-1/2 transform -translate-y-1/2"></div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
interface DragHandlerProps {
|
||||
type DragHandlerProps = {
|
||||
contentRef: React.RefObject<HTMLElement>;
|
||||
timelineRef: React.RefObject<HTMLDivElement>;
|
||||
scrollTimeRef: React.RefObject<HTMLDivElement>;
|
||||
@ -8,15 +8,15 @@ interface DragHandlerProps {
|
||||
alignEndDateToTimeline: (time: number) => number;
|
||||
segmentDuration: number;
|
||||
showHandlebar: boolean;
|
||||
handlebarTime?: number;
|
||||
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
handlebarTimeRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
timelineDuration: number;
|
||||
timelineStart: number;
|
||||
isDragging: boolean;
|
||||
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
currentTimeRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
};
|
||||
|
||||
// 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<HTMLDivElement>) => {
|
||||
@ -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,28 +124,11 @@ 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
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -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 };
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ export default function EventView({
|
||||
)}
|
||||
|
||||
<div
|
||||
className="w-full m-2 grid md:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4"
|
||||
className="w-full m-2 grid sm:grid-cols-2 md:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4"
|
||||
ref={contentRef}
|
||||
>
|
||||
{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}
|
||||
|
Loading…
Reference in New Issue
Block a user