mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-21 15:48: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,
|
MdPhotoCamera,
|
||||||
} from "react-icons/md";
|
} from "react-icons/md";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
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 useSWR from "swr";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useSessionPersistence } from "@/hooks/use-session-persistence";
|
import { useSessionPersistence } from "@/hooks/use-session-persistence";
|
||||||
@ -124,7 +128,11 @@ import {
|
|||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import {
|
import {
|
||||||
createLiveZoomWrapperProps,
|
createLiveZoomWrapperProps,
|
||||||
|
getCursorRelativeZoomTransform,
|
||||||
getLiveZoomTransformStyles,
|
getLiveZoomTransformStyles,
|
||||||
|
LIVE_ZOOM_MAX_SCALE,
|
||||||
|
LIVE_ZOOM_MIN_SCALE,
|
||||||
|
LIVE_ZOOM_SHIFT_WHEEL_STEP,
|
||||||
} from "@/views/live/liveZoom";
|
} from "@/views/live/liveZoom";
|
||||||
|
|
||||||
type LiveCameraViewProps = {
|
type LiveCameraViewProps = {
|
||||||
@ -146,6 +154,7 @@ export default function LiveCameraView({
|
|||||||
const { isPortrait } = useMobileOrientation();
|
const { isPortrait } = useMobileOrientation();
|
||||||
const mainRef = useRef<HTMLDivElement | null>(null);
|
const mainRef = useRef<HTMLDivElement | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const zoomRef = useRef<ReactZoomPanPinchRef | null>(null);
|
||||||
const [{ width: windowWidth, height: windowHeight }] =
|
const [{ width: windowWidth, height: windowHeight }] =
|
||||||
useResizeObserver(window);
|
useResizeObserver(window);
|
||||||
|
|
||||||
@ -440,8 +449,47 @@ export default function LiveCameraView({
|
|||||||
[config, webRTC],
|
[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 (
|
return (
|
||||||
<TransformWrapper {...createLiveZoomWrapperProps(debug)}>
|
<TransformWrapper ref={zoomRef} {...createLiveZoomWrapperProps(debug)}>
|
||||||
<Toaster position="top-center" closeButton={true} />
|
<Toaster position="top-center" closeButton={true} />
|
||||||
<div
|
<div
|
||||||
ref={mainRef}
|
ref={mainRef}
|
||||||
@ -621,7 +669,12 @@ export default function LiveCameraView({
|
|||||||
</div>
|
</div>
|
||||||
{!debug ? (
|
{!debug ? (
|
||||||
<div id="player-container" className="size-full" ref={containerRef}>
|
<div id="player-container" className="size-full" ref={containerRef}>
|
||||||
<TransformComponent {...getLiveZoomTransformStyles("player")}>
|
<TransformComponent
|
||||||
|
{...getLiveZoomTransformStyles("player")}
|
||||||
|
wrapperProps={{
|
||||||
|
onWheel: handlePlayerWheel,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-center ${growClassName}`}
|
className={`flex flex-col items-center justify-center ${growClassName}`}
|
||||||
ref={clickOverlayRef}
|
ref={clickOverlayRef}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export type LiveZoomMode = "player" | "debug";
|
|||||||
|
|
||||||
export type LiveZoomWrapperProps = Pick<
|
export type LiveZoomWrapperProps = Pick<
|
||||||
ReactZoomPanPinchProps,
|
ReactZoomPanPinchProps,
|
||||||
"minScale" | "wheel" | "disabled"
|
"minScale" | "maxScale" | "wheel" | "disabled"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type LiveZoomTransformStyles = {
|
export type LiveZoomTransformStyles = {
|
||||||
@ -19,37 +19,76 @@ export const LIVE_ZOOM_WHEEL_CONFIG: NonNullable<
|
|||||||
smoothStep: 0.005,
|
smoothStep: 0.005,
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIVE_ZOOM_TRANSFORM_STYLES: Record<LiveZoomMode, LiveZoomTransformStyles> =
|
export const LIVE_ZOOM_SHIFT_WHEEL_STEP = 0.1;
|
||||||
{
|
export const LIVE_ZOOM_MIN_SCALE = 1;
|
||||||
player: {
|
export const LIVE_ZOOM_MAX_SCALE = 5;
|
||||||
wrapperStyle: {
|
|
||||||
width: "100%",
|
type LiveZoomTransformState = {
|
||||||
height: "100%",
|
positionX: number;
|
||||||
},
|
positionY: number;
|
||||||
contentStyle: {
|
scale: number;
|
||||||
position: "relative",
|
};
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
type LiveZoomPoint = {
|
||||||
padding: "8px",
|
x: number;
|
||||||
},
|
y: number;
|
||||||
},
|
};
|
||||||
debug: {
|
|
||||||
wrapperStyle: {
|
export function getCursorRelativeZoomTransform(
|
||||||
width: "100%",
|
transformState: LiveZoomTransformState,
|
||||||
height: "100%",
|
cursorPoint: LiveZoomPoint,
|
||||||
},
|
nextScale: number,
|
||||||
contentStyle: {
|
): LiveZoomTransformState {
|
||||||
position: "relative",
|
const scaleRatio = nextScale / transformState.scale;
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createLiveZoomWrapperProps(disabled: boolean): LiveZoomWrapperProps {
|
|
||||||
return {
|
return {
|
||||||
minScale: 1,
|
scale: nextScale,
|
||||||
wheel: LIVE_ZOOM_WHEEL_CONFIG,
|
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,
|
disabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user