This commit is contained in:
Josh Hawkins 2024-04-08 21:35:53 -05:00
parent bbca5ffdfa
commit 0863d43e04
3 changed files with 23 additions and 152 deletions

View File

@ -5,7 +5,6 @@ import Konva from "konva";
import type { KonvaEventObject } from "konva/lib/Node"; import type { KonvaEventObject } from "konva/lib/Node";
import { Polygon } from "@/types/canvas"; import { Polygon } from "@/types/canvas";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { useResizeObserver } from "@/hooks/resize-observer";
type PolygonCanvasProps = { type PolygonCanvasProps = {
camera: string; camera: string;
@ -14,7 +13,6 @@ type PolygonCanvasProps = {
polygons: Polygon[]; polygons: Polygon[];
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>; setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
activePolygonIndex: number | null; activePolygonIndex: number | null;
setActivePolygonIndex: React.Dispatch<React.SetStateAction<number | null>>;
}; };
export function PolygonCanvas({ export function PolygonCanvas({
@ -24,77 +22,35 @@ export function PolygonCanvas({
polygons, polygons,
setPolygons, setPolygons,
activePolygonIndex, activePolygonIndex,
setActivePolygonIndex,
}: PolygonCanvasProps) { }: PolygonCanvasProps) {
const [image, setImage] = useState<HTMLImageElement | undefined>(); const [image, setImage] = useState<HTMLImageElement | undefined>();
const imageRef = useRef<Konva.Image | null>(null); const imageRef = useRef<Konva.Image | null>(null);
// const containerRef = useRef<HTMLDivElement | null>(null);
const stageRef = useRef<Konva.Stage>(null); const stageRef = useRef<Konva.Stage>(null);
// const [points, setPoints] = useState<number[][]>([]);
// const [activePolygonIndex, setActivePolygonIndex] = useState<number | null>(
// null,
// );
// const [size, setSize] = useState<{ width: number; height: number }>({
// width: width,
// height: height,
// });
const apiHost = useApiHost(); const apiHost = useApiHost();
// const [position, setPosition] = useState([0, 0]);
// const [{ width: windowWidth }] = useResizeObserver(window);
const videoElement = useMemo(() => { const videoElement = useMemo(() => {
if (camera && width && height) { if (camera && width && height) {
// console.log("width:", containerRef.current.clientWidth);
// console.log("width:", containerRef.current.clientHeight);
const element = new window.Image(); const element = new window.Image();
element.width = width; //containerRef.current.clientWidth; element.width = width;
element.height = height; //containerRef.current.clientHeight; element.height = height;
element.src = `${apiHost}api/${camera}/latest.jpg`; element.src = `${apiHost}api/${camera}/latest.jpg`;
// setSize({
// width: width,
// height: height,
// });
return element; return element;
} }
}, [camera, width, height, apiHost]); }, [camera, width, height, apiHost]);
// const imageScale = scaledWidth / 720;
// console.log("window width", windowWidth);
useEffect(() => { useEffect(() => {
if (!videoElement) { if (!videoElement) {
return; return;
} }
const onload = function () { const onload = function () {
setImage(videoElement); setImage(videoElement);
// if (!imageRef.current) imageRef.current = videoElement;
console.log(videoElement, Date.now());
}; };
videoElement.addEventListener("load", onload); videoElement.addEventListener("load", onload);
return () => { return () => {
console.log("unloading");
videoElement.removeEventListener("load", onload); videoElement.removeEventListener("load", onload);
}; };
}, [videoElement]); }, [videoElement]);
// use Konva.Animation to redraw a layer
// useEffect(() => {
// //videoElement.play();
// if (!videoElement && !imageRef && !imageRef.current) {
// return;
// }
// const layer = imageRef.current?.getLayer();
// console.log("layer", layer);
// const anim = new Konva.Animation(() => {}, layer);
// anim.start();
// return () => {
// anim.stop();
// };
// }, [videoElement]);
const getMousePos = (stage: Konva.Stage) => { const getMousePos = (stage: Konva.Stage) => {
return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y]; return [stage.getPointerPosition()!.x, stage.getPointerPosition()!.y];
}; };
@ -104,7 +60,6 @@ export function PolygonCanvas({
return false; return false;
} }
const [firstPoint] = polygon.points; const [firstPoint] = polygon.points;
console.log("first", firstPoint);
const distance = Math.hypot( const distance = Math.hypot(
mousePos[0] - firstPoint[0], mousePos[0] - firstPoint[0],
mousePos[1] - firstPoint[1], mousePos[1] - firstPoint[1],
@ -116,23 +71,7 @@ export function PolygonCanvas({
if (!activePolygonIndex || !polygons) { if (!activePolygonIndex || !polygons) {
return; return;
} }
console.log("mouse down polygons", polygons);
console.log(activePolygonIndex);
// if (!polygons[activePolygonIndex].points.length) {
// // Start a new polygon
// const stage = e.target.getStage()!;
// const mousePos = getMousePos(stage);
// setPolygons([
// ...polygons,
// {
// name: "foo",
// points: [mousePos],
// isFinished: false,
// },
// ]);
// setActivePolygonIndex(polygons.length);
// } else {
const updatedPolygons = [...polygons]; const updatedPolygons = [...polygons];
const activePolygon = updatedPolygons[activePolygonIndex]; const activePolygon = updatedPolygons[activePolygonIndex];
const stage = e.target.getStage()!; const stage = e.target.getStage()!;
@ -148,7 +87,6 @@ export function PolygonCanvas({
isFinished: true, isFinished: true,
}; };
setPolygons(updatedPolygons); setPolygons(updatedPolygons);
// setActivePolygonIndex(null);
} else { } else {
if (!activePolygon.isFinished) { if (!activePolygon.isFinished) {
// Add a new point to the active polygon // Add a new point to the active polygon
@ -162,12 +100,6 @@ export function PolygonCanvas({
// } // }
}; };
const handleMouseMove = (e: KonvaEventObject<MouseEvent>) => {
const stage = e.target.getStage()!;
const mousePos = getMousePos(stage);
// setPosition(mousePos);
};
const handleMouseOverStartPoint = (e: KonvaEventObject<MouseEvent>) => { const handleMouseOverStartPoint = (e: KonvaEventObject<MouseEvent>) => {
if (activePolygonIndex !== null && polygons) { if (activePolygonIndex !== null && polygons) {
const activePolygon = polygons[activePolygonIndex]; const activePolygon = polygons[activePolygonIndex];
@ -178,16 +110,13 @@ export function PolygonCanvas({
}; };
const handleMouseOutStartPoint = (e: KonvaEventObject<MouseEvent>) => { const handleMouseOutStartPoint = (e: KonvaEventObject<MouseEvent>) => {
// console.log("active index:", activePolygonIndex);
e.currentTarget.scale({ x: 1, y: 1 }); e.currentTarget.scale({ x: 1, y: 1 });
if (activePolygonIndex !== null && polygons) { if (activePolygonIndex !== null && polygons) {
const activePolygon = polygons[activePolygonIndex]; const activePolygon = polygons[activePolygonIndex];
// console.log(activePolygon);
if ( if (
(!activePolygon.isFinished && activePolygon.points.length >= 3) || (!activePolygon.isFinished && activePolygon.points.length >= 3) ||
activePolygon.isFinished activePolygon.isFinished
) { ) {
// console.log(e.currentTarget);
e.currentTarget.scale({ x: 1, y: 1 }); e.currentTarget.scale({ x: 1, y: 1 });
} }
} }
@ -238,14 +167,12 @@ export function PolygonCanvas({
setPolygons(updatedPolygons); setPolygons(updatedPolygons);
} }
}; };
// console.log("rendering canvas", Date.now());
return ( return (
<Stage <Stage
ref={stageRef} ref={stageRef}
width={width} width={width}
height={height} height={height}
// onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
> >
<Layer> <Layer>

View File

@ -53,9 +53,6 @@ export function PolygonControls({
name: "new", name: "new",
}, },
]); ]);
console.log(polygons.length);
console.log(polygons);
console.log("active index", polygons.length);
setActivePolygonIndex(polygons.length); setActivePolygonIndex(polygons.length);
}; };

View File

@ -19,12 +19,10 @@ import {
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { PolygonCanvas } from "./PolygonCanvas"; import { PolygonCanvas } from "./PolygonCanvas";
import { useApiHost } from "@/api";
import { Polygon } from "@/types/canvas"; import { Polygon } from "@/types/canvas";
import { interpolatePoints } from "@/utils/canvasUtil"; import { interpolatePoints } from "@/utils/canvasUtil";
import AutoUpdatingCameraImage from "../camera/AutoUpdatingCameraImage";
import { isDesktop } from "react-device-detect"; import { isDesktop } from "react-device-detect";
import PolygonControls from "./PolygonControls"; import PolygonControls from "./PolygonControls";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
@ -50,10 +48,7 @@ export default function SettingsZones() {
const [activePolygonIndex, setActivePolygonIndex] = useState<number | null>( const [activePolygonIndex, setActivePolygonIndex] = useState<number | null>(
null, null,
); );
const imgRef = useRef<HTMLImageElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const apiHost = useApiHost();
// const videoSource = `${apiHost}api/ptzcam/latest.jpg`;
const cameras = useMemo(() => { const cameras = useMemo(() => {
if (!config) { if (!config) {
@ -73,55 +68,28 @@ export default function SettingsZones() {
} }
}, [config, selectedCamera]); }, [config, selectedCamera]);
const cameraAspect = useMemo(() => { const grow = useMemo(() => {
if (!cameraConfig) { if (!cameraConfig) {
return; return;
} }
const aspectRatio = cameraConfig.detect.width / cameraConfig.detect.height; const aspectRatio = cameraConfig.detect.width / cameraConfig.detect.height;
console.log("aspect", aspectRatio);
if (!aspectRatio) { if (aspectRatio > 2) {
return "normal";
} else if (aspectRatio > 2) {
return "wide";
} else if (aspectRatio < 16 / 9) {
return "tall";
} else {
return "normal";
}
}, [cameraConfig]);
const grow = useMemo(() => {
if (cameraAspect == "wide") {
return "aspect-wide"; return "aspect-wide";
} else if (cameraAspect == "tall") { } else if (aspectRatio < 16 / 9) {
if (isDesktop) { if (isDesktop) {
return "size-full aspect-tall"; return "size-full aspect-tall";
} else { } else {
return "size-full"; return "size-full";
} }
} else { } else {
return "aspect-video"; return "size-full aspect-video";
} }
}, [cameraAspect]); }, [cameraConfig]);
// const [{ width: containerWidth, height: containerHeight }] = const [{ width: containerWidth, height: containerHeight }] =
// useResizeObserver(containerRef); useResizeObserver(containerRef);
const containerWidth = containerRef.current?.clientWidth;
const containerHeight = containerRef.current?.clientHeight;
// Add scrollbar width (when visible) to the available observer width to eliminate screen juddering.
// https://github.com/blakeblackshear/frigate/issues/1657
let scrollBarWidth = 0;
// if (window.innerWidth && document.body.offsetWidth) {
// scrollBarWidth = window.innerWidth - document.body.offsetWidth;
// }
// const availableWidth = scrollBarWidth
// ? containerWidth + scrollBarWidth
// : containerWidth;
const availableWidth = containerWidth;
const { width, height } = cameraConfig const { width, height } = cameraConfig
? cameraConfig.detect ? cameraConfig.detect
@ -129,12 +97,13 @@ export default function SettingsZones() {
const aspectRatio = width / height; const aspectRatio = width / height;
const stretch = false; const stretch = false;
const fitAspect = 1; const fitAspect = 0.75;
const scaledHeight = useMemo(() => { const scaledHeight = useMemo(() => {
const scaledHeight = const scaledHeight =
aspectRatio < (fitAspect ?? 0) aspectRatio < (fitAspect ?? 0)
? Math.floor(containerHeight) ? Math.floor(containerHeight)
: Math.floor(availableWidth / aspectRatio); : Math.floor(containerWidth / aspectRatio);
const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height); const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height);
if (finalHeight > 0) { if (finalHeight > 0) {
@ -143,16 +112,17 @@ export default function SettingsZones() {
return 100; return 100;
}, [ }, [
availableWidth,
aspectRatio, aspectRatio,
containerWidth,
containerHeight, containerHeight,
fitAspect, fitAspect,
height, height,
stretch, stretch,
]); ]);
const scaledWidth = useMemo( const scaledWidth = useMemo(
() => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth), () => Math.ceil(scaledHeight * aspectRatio),
[scaledHeight, aspectRatio, scrollBarWidth], [scaledHeight, aspectRatio],
); );
useEffect(() => { useEffect(() => {
@ -175,37 +145,10 @@ export default function SettingsZones() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [cameraConfig, containerRef]); }, [cameraConfig, containerRef]);
// const image = useMemo(() => {
// if (cameraConfig && containerRef && containerRef.current) {
// console.log("width:", containerRef.current.clientWidth);
// const element = new window.Image();
// element.width = containerRef.current.clientWidth;
// element.height = containerRef.current.clientHeight;
// element.src = `${apiHost}api/${cameraConfig.name}/latest.jpg`;
// return element;
// }
// }, [cameraConfig, apiHost, containerRef]);
// useEffect(() => {
// if (image) {
// imgRef.current = image;
// }
// }, [image]);
if (!cameraConfig && !selectedCamera) { if (!cameraConfig && !selectedCamera) {
return <ActivityIndicator />; return <ActivityIndicator />;
} }
// console.log("selected camera", selectedCamera);
// console.log("threshold", motionThreshold);
// console.log("contour area", motionContourArea);
// console.log("zone polygons", zonePolygons);
// console.log("width:", containerRef.current.clientWidth);
// const element = new window.Image();
// element.width = containerRef.current.clientWidth;
// element.height = containerRef.current.clientHeight;
return ( return (
<> <>
<Heading as="h2">Zones</Heading> <Heading as="h2">Zones</Heading>
@ -234,7 +177,7 @@ export default function SettingsZones() {
{cameraConfig && ( {cameraConfig && (
<div className="flex flex-row justify-evenly"> <div className="flex flex-row justify-evenly">
<div <div
className={`flex flex-col justify-center items-center w-[50%] ${grow}`} className={`flex flex-col justify-center items-center w-[60%] ${grow}`}
> >
<div ref={containerRef} className="size-full"> <div ref={containerRef} className="size-full">
{cameraConfig ? ( {cameraConfig ? (
@ -245,7 +188,6 @@ export default function SettingsZones() {
polygons={zonePolygons} polygons={zonePolygons}
setPolygons={setZonePolygons} setPolygons={setZonePolygons}
activePolygonIndex={activePolygonIndex} activePolygonIndex={activePolygonIndex}
setActivePolygonIndex={setActivePolygonIndex}
/> />
) : ( ) : (
<Skeleton className="w-full h-full" /> <Skeleton className="w-full h-full" />
@ -295,6 +237,11 @@ export default function SettingsZones() {
))} ))}
</TableBody> </TableBody>
</Table> </Table>
<div>
scaled width: {scaledWidth}, scaled height: {scaledHeight},
container width: {containerWidth}, container height:
{containerHeight}
</div>
<PolygonControls <PolygonControls
camera={cameraConfig.name} camera={cameraConfig.name}
width={scaledWidth} width={scaledWidth}