2024-02-23 06:15:50 +03:00
|
|
|
import { useCallback, useEffect } from "react";
|
2024-03-03 19:32:35 +03:00
|
|
|
import scrollIntoView from "scroll-into-view-if-needed";
|
2024-02-21 02:22:59 +03:00
|
|
|
|
2024-03-01 18:36:13 +03:00
|
|
|
type DragHandlerProps = {
|
2024-02-21 02:22:59 +03:00
|
|
|
contentRef: React.RefObject<HTMLElement>;
|
|
|
|
|
timelineRef: React.RefObject<HTMLDivElement>;
|
|
|
|
|
scrollTimeRef: React.RefObject<HTMLDivElement>;
|
2024-02-28 16:18:08 +03:00
|
|
|
alignStartDateToTimeline: (time: number) => number;
|
|
|
|
|
alignEndDateToTimeline: (time: number) => number;
|
2024-02-21 02:22:59 +03:00
|
|
|
segmentDuration: number;
|
|
|
|
|
showHandlebar: boolean;
|
2024-03-01 18:36:13 +03:00
|
|
|
handlebarTime?: number;
|
|
|
|
|
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
|
|
|
|
handlebarTimeRef: React.MutableRefObject<HTMLDivElement | null>;
|
2024-02-21 02:22:59 +03:00
|
|
|
timelineDuration: number;
|
|
|
|
|
timelineStart: number;
|
|
|
|
|
isDragging: boolean;
|
|
|
|
|
setIsDragging: React.Dispatch<React.SetStateAction<boolean>>;
|
2024-03-01 18:36:13 +03:00
|
|
|
};
|
2024-02-21 02:22:59 +03:00
|
|
|
|
|
|
|
|
function useDraggableHandler({
|
|
|
|
|
contentRef,
|
|
|
|
|
timelineRef,
|
|
|
|
|
scrollTimeRef,
|
2024-02-28 16:18:08 +03:00
|
|
|
alignStartDateToTimeline,
|
2024-02-21 02:22:59 +03:00
|
|
|
segmentDuration,
|
|
|
|
|
showHandlebar,
|
2024-03-01 18:36:13 +03:00
|
|
|
handlebarTime,
|
|
|
|
|
setHandlebarTime,
|
|
|
|
|
handlebarTimeRef,
|
2024-02-21 02:22:59 +03:00
|
|
|
timelineDuration,
|
|
|
|
|
timelineStart,
|
|
|
|
|
isDragging,
|
|
|
|
|
setIsDragging,
|
|
|
|
|
}: DragHandlerProps) {
|
|
|
|
|
const handleMouseDown = useCallback(
|
|
|
|
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
setIsDragging(true);
|
|
|
|
|
},
|
2024-02-29 01:23:56 +03:00
|
|
|
[setIsDragging],
|
2024-02-21 02:22:59 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleMouseUp = useCallback(
|
|
|
|
|
(e: MouseEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
if (isDragging) {
|
|
|
|
|
setIsDragging(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-02-29 01:23:56 +03:00
|
|
|
[isDragging, setIsDragging],
|
2024-02-21 02:22:59 +03:00
|
|
|
);
|
|
|
|
|
|
2024-03-01 18:36:13 +03:00
|
|
|
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" }),
|
|
|
|
|
});
|
2024-03-03 19:32:35 +03:00
|
|
|
scrollIntoView(thumb, {
|
|
|
|
|
scrollMode: "if-needed",
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
});
|
2024-03-01 18:36:13 +03:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (setHandlebarTime) {
|
|
|
|
|
setHandlebarTime(segmentStartTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[segmentDuration, handlebarTimeRef, scrollTimeRef, setHandlebarTime],
|
|
|
|
|
);
|
|
|
|
|
|
2024-02-21 02:22:59 +03:00
|
|
|
const handleMouseMove = useCallback(
|
|
|
|
|
(e: MouseEvent) => {
|
2024-02-21 20:58:41 +03:00
|
|
|
if (
|
|
|
|
|
!contentRef.current ||
|
|
|
|
|
!timelineRef.current ||
|
|
|
|
|
!scrollTimeRef.current
|
|
|
|
|
) {
|
2024-02-21 02:22:59 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
2024-03-01 18:36:13 +03:00
|
|
|
if (showHandlebar && isDragging) {
|
2024-02-21 02:22:59 +03:00
|
|
|
const {
|
|
|
|
|
scrollHeight: timelineHeight,
|
|
|
|
|
clientHeight: visibleTimelineHeight,
|
|
|
|
|
scrollTop: scrolled,
|
|
|
|
|
offsetTop: timelineTop,
|
|
|
|
|
} = timelineRef.current;
|
|
|
|
|
|
|
|
|
|
const segmentHeight =
|
|
|
|
|
timelineHeight / (timelineDuration / segmentDuration);
|
|
|
|
|
|
|
|
|
|
const parentScrollTop = getCumulativeScrollTop(timelineRef.current);
|
|
|
|
|
|
|
|
|
|
const newHandlePosition = Math.min(
|
|
|
|
|
visibleTimelineHeight - timelineTop + parentScrollTop,
|
|
|
|
|
Math.max(
|
|
|
|
|
segmentHeight + scrolled,
|
2024-02-29 01:23:56 +03:00
|
|
|
e.clientY - timelineTop + parentScrollTop,
|
|
|
|
|
),
|
2024-02-21 02:22:59 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const segmentIndex = Math.floor(newHandlePosition / segmentHeight);
|
2024-02-28 16:18:08 +03:00
|
|
|
const segmentStartTime = alignStartDateToTimeline(
|
2024-02-29 01:23:56 +03:00
|
|
|
timelineStart - segmentIndex * segmentDuration,
|
2024-02-21 02:22:59 +03:00
|
|
|
);
|
|
|
|
|
|
2024-03-01 18:36:13 +03:00
|
|
|
updateHandlebarPosition(
|
|
|
|
|
newHandlePosition - segmentHeight,
|
|
|
|
|
segmentStartTime,
|
|
|
|
|
);
|
2024-02-21 02:22:59 +03:00
|
|
|
}
|
|
|
|
|
},
|
2024-02-29 01:23:56 +03:00
|
|
|
// we know that these deps are correct
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-02-21 02:22:59 +03:00
|
|
|
[
|
|
|
|
|
isDragging,
|
|
|
|
|
contentRef,
|
|
|
|
|
segmentDuration,
|
|
|
|
|
showHandlebar,
|
|
|
|
|
timelineDuration,
|
|
|
|
|
timelineStart,
|
2024-03-01 18:36:13 +03:00
|
|
|
updateHandlebarPosition,
|
|
|
|
|
alignStartDateToTimeline,
|
|
|
|
|
getCumulativeScrollTop,
|
2024-02-29 01:23:56 +03:00
|
|
|
],
|
2024-02-21 02:22:59 +03:00
|
|
|
);
|
|
|
|
|
|
2024-02-23 06:15:50 +03:00
|
|
|
useEffect(() => {
|
2024-03-01 18:36:13 +03:00
|
|
|
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);
|
2024-02-23 06:15:50 +03:00
|
|
|
}
|
2024-03-01 18:36:13 +03:00
|
|
|
// we know that these deps are correct
|
2024-02-29 01:23:56 +03:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
2024-03-01 18:36:13 +03:00
|
|
|
}, [handlebarTime, showHandlebar, scrollTimeRef, timelineStart]);
|
2024-02-23 06:15:50 +03:00
|
|
|
|
2024-02-21 02:22:59 +03:00
|
|
|
return { handleMouseDown, handleMouseUp, handleMouseMove };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default useDraggableHandler;
|