mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 14:18:21 +03:00
Harden camera zoom helper restore and key namespace
This commit is contained in:
parent
1848798da8
commit
046568ea6b
162
web/src/utils/cameraZoom.ts
Normal file
162
web/src/utils/cameraZoom.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
export const CAMERA_ZOOM_MIN_SCALE = 1;
|
||||||
|
export const CAMERA_ZOOM_MAX_SCALE = 8;
|
||||||
|
// Tuning constant for discrete shift+wheel zoom on grid cards.
|
||||||
|
export const CAMERA_ZOOM_SHIFT_WHEEL_STEP = 0.1;
|
||||||
|
|
||||||
|
export type CameraZoomRuntimeTransform = {
|
||||||
|
scale: number;
|
||||||
|
positionX: number;
|
||||||
|
positionY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CameraZoomDimensions = {
|
||||||
|
viewportWidth: number;
|
||||||
|
viewportHeight: number;
|
||||||
|
contentWidth: number;
|
||||||
|
contentHeight: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CameraZoomPersistedState = {
|
||||||
|
/**
|
||||||
|
* Scale normalized to a [0, 1] range between min and max zoom.
|
||||||
|
*/
|
||||||
|
normalizedScale: number;
|
||||||
|
/**
|
||||||
|
* Relative content x-coordinate (0..1) that should map to the viewport center.
|
||||||
|
*/
|
||||||
|
focusX: number;
|
||||||
|
/**
|
||||||
|
* Relative content y-coordinate (0..1) that should map to the viewport center.
|
||||||
|
*/
|
||||||
|
focusY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function clamp(value: number, min: number, max: number): number {
|
||||||
|
return Math.min(max, Math.max(min, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clampScale(
|
||||||
|
scale: number,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): number {
|
||||||
|
return clamp(scale, minScale, maxScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeScale(
|
||||||
|
scale: number,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): number {
|
||||||
|
if (maxScale <= minScale) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clamp(
|
||||||
|
(clampScale(scale, minScale, maxScale) - minScale) / (maxScale - minScale),
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function denormalizeScale(
|
||||||
|
normalizedScale: number,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): number {
|
||||||
|
if (maxScale <= minScale) {
|
||||||
|
return minScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = clamp(normalizedScale, 0, 1);
|
||||||
|
return minScale + normalized * (maxScale - minScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a new pan position to keep the content under the cursor fixed
|
||||||
|
* while changing scale.
|
||||||
|
*/
|
||||||
|
export function getCursorRelativeZoomTransform(
|
||||||
|
current: CameraZoomRuntimeTransform,
|
||||||
|
targetScale: number,
|
||||||
|
cursorX: number,
|
||||||
|
cursorY: number,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): CameraZoomRuntimeTransform {
|
||||||
|
const nextScale = clampScale(targetScale, minScale, maxScale);
|
||||||
|
const safeCurrentScale =
|
||||||
|
Number.isFinite(current.scale) && current.scale > 0
|
||||||
|
? current.scale
|
||||||
|
: CAMERA_ZOOM_MIN_SCALE;
|
||||||
|
const contentX = (cursorX - current.positionX) / safeCurrentScale;
|
||||||
|
const contentY = (cursorY - current.positionY) / safeCurrentScale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
scale: nextScale,
|
||||||
|
positionX: cursorX - contentX * nextScale,
|
||||||
|
positionY: cursorY - contentY * nextScale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPersistedCameraZoomState(
|
||||||
|
runtime: CameraZoomRuntimeTransform,
|
||||||
|
dimensions: CameraZoomDimensions,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): CameraZoomPersistedState {
|
||||||
|
const safeContentWidth = dimensions.contentWidth || 1;
|
||||||
|
const safeContentHeight = dimensions.contentHeight || 1;
|
||||||
|
const safeScale =
|
||||||
|
Number.isFinite(runtime.scale) && runtime.scale > 0
|
||||||
|
? runtime.scale
|
||||||
|
: CAMERA_ZOOM_MIN_SCALE;
|
||||||
|
const centerContentX =
|
||||||
|
(dimensions.viewportWidth / 2 - runtime.positionX) / safeScale;
|
||||||
|
const centerContentY =
|
||||||
|
(dimensions.viewportHeight / 2 - runtime.positionY) / safeScale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
normalizedScale: normalizeScale(runtime.scale, minScale, maxScale),
|
||||||
|
focusX: clamp(centerContentX / safeContentWidth, 0, 1),
|
||||||
|
focusY: clamp(centerContentY / safeContentHeight, 0, 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromPersistedCameraZoomState(
|
||||||
|
persisted: CameraZoomPersistedState,
|
||||||
|
dimensions: CameraZoomDimensions,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): CameraZoomRuntimeTransform {
|
||||||
|
const scale = denormalizeScale(persisted.normalizedScale, minScale, maxScale);
|
||||||
|
const safeContentWidth = dimensions.contentWidth || 1;
|
||||||
|
const safeContentHeight = dimensions.contentHeight || 1;
|
||||||
|
const contentX = clamp(persisted.focusX, 0, 1) * safeContentWidth;
|
||||||
|
const contentY = clamp(persisted.focusY, 0, 1) * safeContentHeight;
|
||||||
|
|
||||||
|
return {
|
||||||
|
scale,
|
||||||
|
positionX: dimensions.viewportWidth / 2 - contentX * scale,
|
||||||
|
positionY: dimensions.viewportHeight / 2 - contentY * scale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextScaleFromWheelDelta(
|
||||||
|
currentScale: number,
|
||||||
|
wheelDeltaY: number,
|
||||||
|
step: number = CAMERA_ZOOM_SHIFT_WHEEL_STEP,
|
||||||
|
minScale: number = CAMERA_ZOOM_MIN_SCALE,
|
||||||
|
maxScale: number = CAMERA_ZOOM_MAX_SCALE,
|
||||||
|
): number {
|
||||||
|
if (wheelDeltaY === 0) {
|
||||||
|
return clampScale(currentScale, minScale, maxScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
const direction = wheelDeltaY > 0 ? -1 : 1;
|
||||||
|
return clampScale(currentScale + direction * step, minScale, maxScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCameraZoomStorageKey(cameraName: string): string {
|
||||||
|
return `live:grid-card:zoom:${cameraName}`;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user