mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-17 16:44:29 +03:00
timeline zooming hook
This commit is contained in:
parent
826d2333e8
commit
772da2533d
138
web/src/hooks/use-timeline-zoom.ts
Normal file
138
web/src/hooks/use-timeline-zoom.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
|
||||||
|
type ZoomSettings = {
|
||||||
|
segmentDuration: number;
|
||||||
|
timestampSpread: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseTimelineZoomProps = {
|
||||||
|
zoomSettings: ZoomSettings;
|
||||||
|
zoomLevels: ZoomSettings[];
|
||||||
|
onZoomChange: (newZoomLevel: number) => void;
|
||||||
|
pinchThresholdPercent?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTimelineZoom({
|
||||||
|
zoomSettings,
|
||||||
|
zoomLevels,
|
||||||
|
onZoomChange,
|
||||||
|
pinchThresholdPercent = 20,
|
||||||
|
}: UseTimelineZoomProps) {
|
||||||
|
const [zoomLevel, setZoomLevel] = useState(
|
||||||
|
zoomLevels.findIndex(
|
||||||
|
(level) =>
|
||||||
|
level.segmentDuration === zoomSettings.segmentDuration &&
|
||||||
|
level.timestampSpread === zoomSettings.timestampSpread,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const touchStartDistanceRef = useRef(0);
|
||||||
|
|
||||||
|
const getPinchThreshold = useCallback(() => {
|
||||||
|
return (window.innerHeight * pinchThresholdPercent) / 100;
|
||||||
|
}, [pinchThresholdPercent]);
|
||||||
|
|
||||||
|
const wheelDeltaRef = useRef(0);
|
||||||
|
const isZoomingRef = useRef(false);
|
||||||
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const handleZoom = useCallback(
|
||||||
|
(delta: number) => {
|
||||||
|
setZoomLevel((prevLevel) => {
|
||||||
|
const newLevel = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(zoomLevels.length - 1, prevLevel - delta),
|
||||||
|
);
|
||||||
|
if (newLevel !== prevLevel) {
|
||||||
|
onZoomChange(newLevel);
|
||||||
|
}
|
||||||
|
return newLevel;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[zoomLevels, onZoomChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedZoom = useCallback(() => {
|
||||||
|
if (Math.abs(wheelDeltaRef.current) >= 200) {
|
||||||
|
handleZoom(wheelDeltaRef.current > 0 ? 1 : -1);
|
||||||
|
wheelDeltaRef.current = 0;
|
||||||
|
isZoomingRef.current = false;
|
||||||
|
} else {
|
||||||
|
isZoomingRef.current = false;
|
||||||
|
}
|
||||||
|
}, [handleZoom]);
|
||||||
|
|
||||||
|
const handleWheel = useCallback(
|
||||||
|
(event: WheelEvent) => {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!isZoomingRef.current) {
|
||||||
|
wheelDeltaRef.current += event.deltaY;
|
||||||
|
|
||||||
|
if (Math.abs(wheelDeltaRef.current) >= 200) {
|
||||||
|
isZoomingRef.current = true;
|
||||||
|
|
||||||
|
if (debounceTimeoutRef.current) {
|
||||||
|
clearTimeout(debounceTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceTimeoutRef.current = setTimeout(() => {
|
||||||
|
debouncedZoom();
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[debouncedZoom],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTouchStart = useCallback((event: TouchEvent) => {
|
||||||
|
if (event.touches.length === 2) {
|
||||||
|
event.preventDefault();
|
||||||
|
const touch1 = event.touches[0];
|
||||||
|
const touch2 = event.touches[1];
|
||||||
|
const distance = Math.hypot(
|
||||||
|
touch1.clientX - touch2.clientX,
|
||||||
|
touch1.clientY - touch2.clientY,
|
||||||
|
);
|
||||||
|
touchStartDistanceRef.current = distance;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTouchMove = useCallback(
|
||||||
|
(event: TouchEvent) => {
|
||||||
|
if (event.touches.length === 2) {
|
||||||
|
event.preventDefault();
|
||||||
|
const touch1 = event.touches[0];
|
||||||
|
const touch2 = event.touches[1];
|
||||||
|
const currentDistance = Math.hypot(
|
||||||
|
touch1.clientX - touch2.clientX,
|
||||||
|
touch1.clientY - touch2.clientY,
|
||||||
|
);
|
||||||
|
|
||||||
|
const distanceDelta = currentDistance - touchStartDistanceRef.current;
|
||||||
|
const pinchThreshold = getPinchThreshold();
|
||||||
|
|
||||||
|
if (Math.abs(distanceDelta) > pinchThreshold) {
|
||||||
|
handleZoom(distanceDelta > 0 ? -1 : 1);
|
||||||
|
touchStartDistanceRef.current = currentDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handleZoom, getPinchThreshold],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("wheel", handleWheel, { passive: false });
|
||||||
|
window.addEventListener("touchstart", handleTouchStart, { passive: false });
|
||||||
|
window.addEventListener("touchmove", handleTouchMove, { passive: false });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("wheel", handleWheel);
|
||||||
|
window.removeEventListener("touchstart", handleTouchStart);
|
||||||
|
window.removeEventListener("touchmove", handleTouchMove);
|
||||||
|
};
|
||||||
|
}, [handleWheel, handleTouchStart, handleTouchMove]);
|
||||||
|
|
||||||
|
return { zoomLevel, handleZoom };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user