mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
update draggable element hook to use only math
This commit is contained in:
parent
e4dbfdae9f
commit
826d2333e8
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
|
||||||
import { useTimelineUtils } from "./use-timeline-utils";
|
import { useTimelineUtils } from "./use-timeline-utils";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -33,7 +25,8 @@ type DraggableElementProps = {
|
|||||||
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
|
setDraggableElementPosition?: React.Dispatch<React.SetStateAction<number>>;
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
timelineSegments: ReactNode[];
|
segments: number[];
|
||||||
|
scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function useDraggableElement({
|
function useDraggableElement({
|
||||||
@ -57,7 +50,8 @@ function useDraggableElement({
|
|||||||
setIsDragging,
|
setIsDragging,
|
||||||
setDraggableElementPosition,
|
setDraggableElementPosition,
|
||||||
dense,
|
dense,
|
||||||
timelineSegments,
|
segments,
|
||||||
|
scrollToSegment,
|
||||||
}: DraggableElementProps) {
|
}: DraggableElementProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
@ -66,7 +60,6 @@ function useDraggableElement({
|
|||||||
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 [fullTimelineHeight, setFullTimelineHeight] = useState<number>();
|
||||||
const [segments, setSegments] = useState<HTMLDivElement[]>([]);
|
|
||||||
const { alignStartDateToTimeline, getCumulativeScrollTop, segmentHeight } =
|
const { alignStartDateToTimeline, getCumulativeScrollTop, segmentHeight } =
|
||||||
useTimelineUtils({
|
useTimelineUtils({
|
||||||
segmentDuration: segmentDuration,
|
segmentDuration: segmentDuration,
|
||||||
@ -201,11 +194,7 @@ function useDraggableElement({
|
|||||||
draggableElementTimeRef.current.textContent =
|
draggableElementTimeRef.current.textContent =
|
||||||
getFormattedTimestamp(segmentStartTime);
|
getFormattedTimestamp(segmentStartTime);
|
||||||
if (scrollTimeline && !userInteracting) {
|
if (scrollTimeline && !userInteracting) {
|
||||||
scrollIntoView(thumb, {
|
scrollToSegment(segmentStartTime);
|
||||||
block: "center",
|
|
||||||
behavior: "smooth",
|
|
||||||
scrollMode: "if-needed",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -222,6 +211,7 @@ function useDraggableElement({
|
|||||||
setDraggableElementPosition,
|
setDraggableElementPosition,
|
||||||
getFormattedTimestamp,
|
getFormattedTimestamp,
|
||||||
userInteracting,
|
userInteracting,
|
||||||
|
scrollToSegment,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -241,12 +231,6 @@ function useDraggableElement({
|
|||||||
[contentRef, draggableElementRef, timelineRef, getClientYPosition],
|
[contentRef, draggableElementRef, timelineRef, getClientYPosition],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (timelineRef.current && timelineSegments.length) {
|
|
||||||
setSegments(Array.from(timelineRef.current.querySelectorAll(".segment")));
|
|
||||||
}
|
|
||||||
}, [timelineRef, timelineCollapsed, timelineSegments]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let animationFrameId: number | null = null;
|
let animationFrameId: number | null = null;
|
||||||
|
|
||||||
@ -256,9 +240,11 @@ function useDraggableElement({
|
|||||||
showDraggableElement &&
|
showDraggableElement &&
|
||||||
isDragging &&
|
isDragging &&
|
||||||
clientYPosition &&
|
clientYPosition &&
|
||||||
segments &&
|
segments.length > 0 &&
|
||||||
fullTimelineHeight
|
fullTimelineHeight
|
||||||
) {
|
) {
|
||||||
|
console.log("triggering scroll");
|
||||||
|
|
||||||
const { scrollTop: scrolled } = timelineRef.current;
|
const { scrollTop: scrolled } = timelineRef.current;
|
||||||
|
|
||||||
const parentScrollTop = getCumulativeScrollTop(timelineRef.current);
|
const parentScrollTop = getCumulativeScrollTop(timelineRef.current);
|
||||||
@ -295,31 +281,78 @@ function useDraggableElement({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetSegmentId = 0;
|
const start = Math.max(0, Math.floor(scrolled / segmentHeight));
|
||||||
let offset = 0;
|
|
||||||
|
|
||||||
segments.forEach((segmentElement: HTMLDivElement) => {
|
console.log("newElementposition", newElementPosition);
|
||||||
const rect = segmentElement.getBoundingClientRect();
|
const relativePosition = newElementPosition - scrolled;
|
||||||
const segmentTop =
|
const segmentIndex =
|
||||||
rect.top + scrolled - timelineTopAbsolute - segmentHeight;
|
Math.floor(relativePosition / segmentHeight) + start + 1;
|
||||||
const segmentBottom =
|
console.log(
|
||||||
rect.bottom + scrolled - timelineTopAbsolute - segmentHeight;
|
"segments",
|
||||||
|
segments,
|
||||||
|
"segment index",
|
||||||
|
segmentIndex,
|
||||||
|
"start",
|
||||||
|
start,
|
||||||
|
);
|
||||||
|
const targetSegmentTime = segments[segmentIndex];
|
||||||
|
console.log("segment time:", new Date(targetSegmentTime * 1000));
|
||||||
|
if (targetSegmentTime === undefined) return;
|
||||||
|
|
||||||
// Check if handlebar position falls within the segment bounds
|
const segmentEnd = (segmentIndex - 1) * segmentHeight - scrolled;
|
||||||
if (
|
const segmentStart = segmentIndex * segmentHeight - scrolled;
|
||||||
newElementPosition >= segmentTop &&
|
console.log(
|
||||||
newElementPosition <= segmentBottom
|
"relative position",
|
||||||
) {
|
relativePosition,
|
||||||
targetSegmentId = parseFloat(
|
"segment end ",
|
||||||
segmentElement.getAttribute("data-segment-id") || "0",
|
segmentEnd,
|
||||||
|
"segment start",
|
||||||
|
segmentStart,
|
||||||
);
|
);
|
||||||
offset = Math.min(
|
|
||||||
segmentBottom - newElementPosition,
|
const offset = Math.min(segmentStart - relativePosition, segmentHeight);
|
||||||
segmentHeight,
|
|
||||||
);
|
// targetSegmentId =
|
||||||
return;
|
// targetSegmentTime + (offset / segmentHeight) * segmentDuration;
|
||||||
}
|
// console.log(
|
||||||
});
|
// "target segment time",
|
||||||
|
// new Date(targetSegmentTime * 1000),
|
||||||
|
// "offset",
|
||||||
|
// offset,
|
||||||
|
// "final calc:",
|
||||||
|
// new Date(targetSegmentId * 1000),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// updateDraggableElementPosition(
|
||||||
|
// newElementPosition,
|
||||||
|
// targetSegmentId,
|
||||||
|
// false,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// segments.forEach((segmentElement: HTMLDivElement) => {
|
||||||
|
// const rect = segmentElement.getBoundingClientRect();
|
||||||
|
// const segmentTop =
|
||||||
|
// rect.top + scrolled - timelineTopAbsolute - segmentHeight;
|
||||||
|
// const segmentBottom =
|
||||||
|
// rect.bottom + scrolled - timelineTopAbsolute - segmentHeight;
|
||||||
|
|
||||||
|
// // Check if handlebar position falls within the segment bounds
|
||||||
|
// if (
|
||||||
|
// newElementPosition >= segmentTop &&
|
||||||
|
// newElementPosition <= segmentBottom
|
||||||
|
// ) {
|
||||||
|
// targetSegmentId = parseFloat(
|
||||||
|
// segmentElement.getAttribute("data-segment-id") || "0",
|
||||||
|
// );
|
||||||
|
// offset = Math.min(
|
||||||
|
// segmentBottom - newElementPosition,
|
||||||
|
// segmentHeight,
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
if ((draggingAtTopEdge || draggingAtBottomEdge) && scrollEdgeSize) {
|
if ((draggingAtTopEdge || draggingAtBottomEdge) && scrollEdgeSize) {
|
||||||
if (draggingAtTopEdge) {
|
if (draggingAtTopEdge) {
|
||||||
@ -349,8 +382,10 @@ function useDraggableElement({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setTime = alignSetTimeToSegment
|
const setTime = alignSetTimeToSegment
|
||||||
? targetSegmentId
|
? targetSegmentTime
|
||||||
: targetSegmentId + segmentDuration * (offset / segmentHeight);
|
: targetSegmentTime + segmentDuration * (offset / segmentHeight);
|
||||||
|
|
||||||
|
console.log("set time", new Date(setTime * 1000));
|
||||||
|
|
||||||
updateDraggableElementPosition(
|
updateDraggableElementPosition(
|
||||||
newElementPosition,
|
newElementPosition,
|
||||||
@ -361,7 +396,7 @@ function useDraggableElement({
|
|||||||
|
|
||||||
if (setDraggableElementTime) {
|
if (setDraggableElementTime) {
|
||||||
setDraggableElementTime(
|
setDraggableElementTime(
|
||||||
targetSegmentId + segmentDuration * (offset / segmentHeight),
|
targetSegmentTime + segmentDuration * (offset / segmentHeight),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,6 +432,7 @@ function useDraggableElement({
|
|||||||
draggingAtTopEdge,
|
draggingAtTopEdge,
|
||||||
draggingAtBottomEdge,
|
draggingAtBottomEdge,
|
||||||
showDraggableElement,
|
showDraggableElement,
|
||||||
|
segments,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -408,24 +444,99 @@ function useDraggableElement({
|
|||||||
!isDragging &&
|
!isDragging &&
|
||||||
segments.length > 0
|
segments.length > 0
|
||||||
) {
|
) {
|
||||||
|
console.log(
|
||||||
|
"Firing to find segment",
|
||||||
|
new Date(draggableElementTime * 1000),
|
||||||
|
);
|
||||||
|
// const segments = Array.from(
|
||||||
|
// timelineRef.current.querySelectorAll(".segment"),
|
||||||
|
// ) as HTMLDivElement[];
|
||||||
|
|
||||||
|
// if (segments.length == 0) {
|
||||||
|
// // still set initial time on handlebar
|
||||||
|
// updateDraggableElementPosition(
|
||||||
|
// 0,
|
||||||
|
// draggableElementTime,
|
||||||
|
// elementScrollIntoView,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
const { scrollTop: scrolled } = timelineRef.current;
|
const { scrollTop: scrolled } = timelineRef.current;
|
||||||
|
|
||||||
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
||||||
|
if (!userInteracting) {
|
||||||
|
scrollToSegment(alignedSegmentTime);
|
||||||
|
}
|
||||||
|
|
||||||
const segmentElement = timelineRef.current.querySelector(
|
|
||||||
`[data-segment-id="${alignedSegmentTime}"]`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (segmentElement) {
|
|
||||||
const timelineRect = timelineRef.current.getBoundingClientRect();
|
const timelineRect = timelineRef.current.getBoundingClientRect();
|
||||||
const timelineTopAbsolute = timelineRect.top;
|
const timelineTopAbsolute = timelineRect.top;
|
||||||
const rect = segmentElement.getBoundingClientRect();
|
|
||||||
const segmentTop = rect.top + scrolled - timelineTopAbsolute;
|
const segmentIndex = segments.findIndex(
|
||||||
|
(time) => time === alignedSegmentTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"aligned segment time",
|
||||||
|
new Date(alignedSegmentTime * 1000),
|
||||||
|
"segment index",
|
||||||
|
segmentIndex,
|
||||||
|
"segment duration",
|
||||||
|
segmentDuration,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (segmentIndex !== 0) {
|
||||||
|
// const segmentTop = (segmentIndex - 1) * segmentHeight;
|
||||||
|
// const segmentBottom = segmentIndex * segmentHeight;
|
||||||
|
// console.log(
|
||||||
|
// "relative position",
|
||||||
|
// relativePosition,
|
||||||
|
// "segment top",
|
||||||
|
// segmentTop,
|
||||||
|
// "segment bottom",
|
||||||
|
// segmentBottom,
|
||||||
|
// );
|
||||||
|
// offset = Math.min(segmentBottom - relativePosition, segmentHeight);
|
||||||
|
|
||||||
|
// const segmentStartTime = targetSegmentTime;
|
||||||
|
// targetSegmentId =
|
||||||
|
// segmentStartTime + (offset / segmentHeight) * segmentDuration;
|
||||||
|
// console.log(
|
||||||
|
// "target segment time",
|
||||||
|
// new Date(targetSegmentTime * 1000),
|
||||||
|
// "offset",
|
||||||
|
// offset,
|
||||||
|
// "final calc:",
|
||||||
|
// new Date(targetSegmentId * 1000),
|
||||||
|
// );
|
||||||
|
|
||||||
|
const segmentEnd = (segmentIndex - 1) * segmentHeight;
|
||||||
|
const segmentStart = segmentIndex * segmentHeight;
|
||||||
|
console.log(
|
||||||
|
"***** segment index",
|
||||||
|
segmentIndex,
|
||||||
|
"segment start",
|
||||||
|
segmentStart,
|
||||||
|
"segment end",
|
||||||
|
segmentEnd,
|
||||||
|
"scrolled",
|
||||||
|
scrolled,
|
||||||
|
"timeline top abs",
|
||||||
|
timelineTopAbsolute,
|
||||||
|
);
|
||||||
|
|
||||||
|
// const relativePosition =
|
||||||
|
// ((draggableElementTime - alignedSegmentTime) / segmentDuration) *
|
||||||
|
// segmentHeight;
|
||||||
|
// console.log("relative pos", relativePosition);
|
||||||
|
// const offset = Math.min(segmentStart - relativePosition, segmentHeight);
|
||||||
const offset =
|
const offset =
|
||||||
((draggableElementTime - alignedSegmentTime) / segmentDuration) *
|
((draggableElementTime - alignedSegmentTime) / segmentDuration) *
|
||||||
segmentHeight;
|
segmentHeight;
|
||||||
// subtract half the height of the handlebar cross bar (4px) for pixel perfection
|
// subtract half the height of the handlebar cross bar (4px) for pixel perfection
|
||||||
const newElementPosition = segmentTop - offset - 2;
|
const newElementPosition = segmentStart - offset - 2;
|
||||||
|
console.log("offset", offset, "new pos", newElementPosition);
|
||||||
|
|
||||||
updateDraggableElementPosition(
|
updateDraggableElementPosition(
|
||||||
newElementPosition,
|
newElementPosition,
|
||||||
@ -438,6 +549,33 @@ function useDraggableElement({
|
|||||||
setElementScrollIntoView(false);
|
setElementScrollIntoView(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const segmentElement = timelineRef.current.querySelector(
|
||||||
|
// `[data-segment-id="${alignedSegmentTime}"]`,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (segmentElement) {
|
||||||
|
// const timelineRect = timelineRef.current.getBoundingClientRect();
|
||||||
|
// const timelineTopAbsolute = timelineRect.top;
|
||||||
|
// const rect = segmentElement.getBoundingClientRect();
|
||||||
|
// const segmentTop = rect.top + scrolled - timelineTopAbsolute;
|
||||||
|
// const offset =
|
||||||
|
// ((draggableElementTime - alignedSegmentTime) / segmentDuration) *
|
||||||
|
// segmentHeight;
|
||||||
|
// // subtract half the height of the handlebar cross bar (4px) for pixel perfection
|
||||||
|
// const newElementPosition = segmentTop - offset - 2;
|
||||||
|
|
||||||
|
// updateDraggableElementPosition(
|
||||||
|
// newElementPosition,
|
||||||
|
// draggableElementTime,
|
||||||
|
// elementScrollIntoView,
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (initialScrollIntoViewOnly) {
|
||||||
|
// setElementScrollIntoView(false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -454,14 +592,27 @@ function useDraggableElement({
|
|||||||
segments,
|
segments,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const findNextAvailableSegment = useCallback(
|
||||||
|
(startTime: number) => {
|
||||||
|
let searchTime = startTime;
|
||||||
|
while (searchTime < timelineStartAligned + timelineDuration) {
|
||||||
|
if (segments.includes(searchTime)) {
|
||||||
|
return searchTime;
|
||||||
|
}
|
||||||
|
searchTime += segmentDuration;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[segments, timelineStartAligned, timelineDuration, segmentDuration],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
timelineRef.current &&
|
timelineRef.current &&
|
||||||
segmentsRef.current &&
|
segmentsRef.current &&
|
||||||
draggableElementTime &&
|
draggableElementTime &&
|
||||||
timelineCollapsed &&
|
timelineCollapsed &&
|
||||||
timelineSegments &&
|
segments.length > 0
|
||||||
segments
|
|
||||||
) {
|
) {
|
||||||
setFullTimelineHeight(
|
setFullTimelineHeight(
|
||||||
Math.min(
|
Math.min(
|
||||||
@ -469,47 +620,32 @@ function useDraggableElement({
|
|||||||
segmentsRef.current.scrollHeight,
|
segmentsRef.current.scrollHeight,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Firing for collapsed",
|
||||||
|
new Date(draggableElementTime * 1000),
|
||||||
|
);
|
||||||
|
|
||||||
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
const alignedSegmentTime = alignStartDateToTimeline(draggableElementTime);
|
||||||
|
|
||||||
let segmentElement = timelineRef.current.querySelector(
|
if (segments.includes(alignedSegmentTime)) {
|
||||||
`[data-segment-id="${alignedSegmentTime}"]`,
|
scrollToSegment(alignedSegmentTime);
|
||||||
);
|
} else {
|
||||||
|
|
||||||
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;
|
const nextAvailableSegment =
|
||||||
|
findNextAvailableSegment(alignedSegmentTime);
|
||||||
|
|
||||||
while (
|
if (nextAvailableSegment !== null) {
|
||||||
searchTime < timelineStartAligned &&
|
scrollToSegment(nextAvailableSegment);
|
||||||
searchTime < timelineStartAligned + timelineDuration
|
|
||||||
) {
|
|
||||||
searchTime += segmentDuration;
|
|
||||||
segmentElement = timelineRef.current.querySelector(
|
|
||||||
`[data-segment-id="${searchTime}"]`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (segmentElement) {
|
|
||||||
// found, set time
|
|
||||||
if (setDraggableElementTime) {
|
if (setDraggableElementTime) {
|
||||||
setDraggableElementTime(searchTime);
|
setDraggableElementTime(nextAvailableSegment);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!segmentElement) {
|
|
||||||
// segment still not found, just start at the beginning of the timeline or at now()
|
|
||||||
if (segments?.length) {
|
|
||||||
const searchTime = parseInt(
|
|
||||||
segments[0].getAttribute("data-segment-id") || "0",
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
if (setDraggableElementTime) {
|
|
||||||
setDraggableElementTime(searchTime);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// segment still not found, just start at the beginning of the timeline or at now()
|
||||||
|
const firstAvailableSegment = segments[0] || timelineStartAligned;
|
||||||
|
scrollToSegment(firstAvailableSegment);
|
||||||
if (setDraggableElementTime) {
|
if (setDraggableElementTime) {
|
||||||
setDraggableElementTime(timelineStartAligned);
|
setDraggableElementTime(firstAvailableSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user