mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 06:08:22 +03:00
Clamp live zoom scale and align wheel coordinates to transform viewport
This commit is contained in:
parent
f914a0c81c
commit
de9878f5fd
@ -88,7 +88,11 @@ import {
|
||||
MdPhotoCamera,
|
||||
} from "react-icons/md";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
||||
import {
|
||||
TransformWrapper,
|
||||
TransformComponent,
|
||||
ReactZoomPanPinchRef,
|
||||
} from "react-zoom-pan-pinch";
|
||||
import useSWR from "swr";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useSessionPersistence } from "@/hooks/use-session-persistence";
|
||||
@ -124,7 +128,11 @@ import {
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import {
|
||||
createLiveZoomWrapperProps,
|
||||
getCursorRelativeZoomTransform,
|
||||
getLiveZoomTransformStyles,
|
||||
LIVE_ZOOM_MAX_SCALE,
|
||||
LIVE_ZOOM_MIN_SCALE,
|
||||
LIVE_ZOOM_SHIFT_WHEEL_STEP,
|
||||
} from "@/views/live/liveZoom";
|
||||
|
||||
type LiveCameraViewProps = {
|
||||
@ -146,6 +154,7 @@ export default function LiveCameraView({
|
||||
const { isPortrait } = useMobileOrientation();
|
||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const zoomRef = useRef<ReactZoomPanPinchRef | null>(null);
|
||||
const [{ width: windowWidth, height: windowHeight }] =
|
||||
useResizeObserver(window);
|
||||
|
||||
@ -440,8 +449,47 @@ export default function LiveCameraView({
|
||||
[config, webRTC],
|
||||
);
|
||||
|
||||
const handlePlayerWheel = useCallback(
|
||||
(e: React.WheelEvent<HTMLDivElement>) => {
|
||||
if (!e.shiftKey || !zoomRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transformState = zoomRef.current.instance.transformState;
|
||||
const zoomStep =
|
||||
e.deltaY < 0 ? LIVE_ZOOM_SHIFT_WHEEL_STEP : -LIVE_ZOOM_SHIFT_WHEEL_STEP;
|
||||
const nextScale = Math.min(
|
||||
LIVE_ZOOM_MAX_SCALE,
|
||||
Math.max(LIVE_ZOOM_MIN_SCALE, transformState.scale + zoomStep),
|
||||
);
|
||||
|
||||
if (nextScale === transformState.scale) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const nextTransform = getCursorRelativeZoomTransform(
|
||||
transformState,
|
||||
{
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top,
|
||||
},
|
||||
nextScale,
|
||||
);
|
||||
|
||||
zoomRef.current.setTransform(
|
||||
nextTransform.positionX,
|
||||
nextTransform.positionY,
|
||||
nextTransform.scale,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<TransformWrapper {...createLiveZoomWrapperProps(debug)}>
|
||||
<TransformWrapper ref={zoomRef} {...createLiveZoomWrapperProps(debug)}>
|
||||
<Toaster position="top-center" closeButton={true} />
|
||||
<div
|
||||
ref={mainRef}
|
||||
@ -621,7 +669,12 @@ export default function LiveCameraView({
|
||||
</div>
|
||||
{!debug ? (
|
||||
<div id="player-container" className="size-full" ref={containerRef}>
|
||||
<TransformComponent {...getLiveZoomTransformStyles("player")}>
|
||||
<TransformComponent
|
||||
{...getLiveZoomTransformStyles("player")}
|
||||
wrapperProps={{
|
||||
onWheel: handlePlayerWheel,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`flex flex-col items-center justify-center ${growClassName}`}
|
||||
ref={clickOverlayRef}
|
||||
|
||||
@ -5,7 +5,7 @@ export type LiveZoomMode = "player" | "debug";
|
||||
|
||||
export type LiveZoomWrapperProps = Pick<
|
||||
ReactZoomPanPinchProps,
|
||||
"minScale" | "wheel" | "disabled"
|
||||
"minScale" | "maxScale" | "wheel" | "disabled"
|
||||
>;
|
||||
|
||||
export type LiveZoomTransformStyles = {
|
||||
@ -19,37 +19,76 @@ export const LIVE_ZOOM_WHEEL_CONFIG: NonNullable<
|
||||
smoothStep: 0.005,
|
||||
};
|
||||
|
||||
const LIVE_ZOOM_TRANSFORM_STYLES: Record<LiveZoomMode, LiveZoomTransformStyles> =
|
||||
{
|
||||
player: {
|
||||
wrapperStyle: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
contentStyle: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "8px",
|
||||
},
|
||||
},
|
||||
debug: {
|
||||
wrapperStyle: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
contentStyle: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
},
|
||||
};
|
||||
export const LIVE_ZOOM_SHIFT_WHEEL_STEP = 0.1;
|
||||
export const LIVE_ZOOM_MIN_SCALE = 1;
|
||||
export const LIVE_ZOOM_MAX_SCALE = 5;
|
||||
|
||||
type LiveZoomTransformState = {
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
scale: number;
|
||||
};
|
||||
|
||||
type LiveZoomPoint = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export function getCursorRelativeZoomTransform(
|
||||
transformState: LiveZoomTransformState,
|
||||
cursorPoint: LiveZoomPoint,
|
||||
nextScale: number,
|
||||
): LiveZoomTransformState {
|
||||
const scaleRatio = nextScale / transformState.scale;
|
||||
|
||||
export function createLiveZoomWrapperProps(disabled: boolean): LiveZoomWrapperProps {
|
||||
return {
|
||||
minScale: 1,
|
||||
wheel: LIVE_ZOOM_WHEEL_CONFIG,
|
||||
scale: nextScale,
|
||||
positionX:
|
||||
cursorPoint.x - (cursorPoint.x - transformState.positionX) * scaleRatio,
|
||||
positionY:
|
||||
cursorPoint.y - (cursorPoint.y - transformState.positionY) * scaleRatio,
|
||||
};
|
||||
}
|
||||
|
||||
const LIVE_ZOOM_TRANSFORM_STYLES: Record<
|
||||
LiveZoomMode,
|
||||
LiveZoomTransformStyles
|
||||
> = {
|
||||
player: {
|
||||
wrapperStyle: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
contentStyle: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: "8px",
|
||||
},
|
||||
},
|
||||
debug: {
|
||||
wrapperStyle: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
contentStyle: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function createLiveZoomWrapperProps(
|
||||
disabled: boolean,
|
||||
): LiveZoomWrapperProps {
|
||||
return {
|
||||
minScale: LIVE_ZOOM_MIN_SCALE,
|
||||
maxScale: LIVE_ZOOM_MAX_SCALE,
|
||||
wheel: {
|
||||
...LIVE_ZOOM_WHEEL_CONFIG,
|
||||
disabled: true,
|
||||
},
|
||||
disabled,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user