mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 21:25:24 +03:00
progress
This commit is contained in:
parent
cf96f7b706
commit
8d0385c991
@ -67,8 +67,8 @@ export function PolygonCanvas({
|
||||
return distance < 15;
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||
if (!activePolygonIndex || !polygons) {
|
||||
const handleMouseDown = (e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||
if (activePolygonIndex == null || !polygons) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -100,50 +100,63 @@ export function PolygonCanvas({
|
||||
// }
|
||||
};
|
||||
|
||||
const handleMouseOverStartPoint = (e: KonvaEventObject<MouseEvent>) => {
|
||||
if (activePolygonIndex !== null && polygons) {
|
||||
const activePolygon = polygons[activePolygonIndex];
|
||||
if (!activePolygon.isFinished && activePolygon.points.length >= 3) {
|
||||
e.currentTarget.scale({ x: 2, y: 2 });
|
||||
}
|
||||
const handleMouseOverStartPoint = (
|
||||
e: KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => {
|
||||
if (activePolygonIndex == null || !polygons) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activePolygon = polygons[activePolygonIndex];
|
||||
if (!activePolygon.isFinished && activePolygon.points.length >= 3) {
|
||||
e.currentTarget.scale({ x: 2, y: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseOutStartPoint = (e: KonvaEventObject<MouseEvent>) => {
|
||||
const handleMouseOutStartPoint = (
|
||||
e: KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => {
|
||||
e.currentTarget.scale({ x: 1, y: 1 });
|
||||
if (activePolygonIndex !== null && polygons) {
|
||||
const activePolygon = polygons[activePolygonIndex];
|
||||
if (
|
||||
(!activePolygon.isFinished && activePolygon.points.length >= 3) ||
|
||||
activePolygon.isFinished
|
||||
) {
|
||||
e.currentTarget.scale({ x: 1, y: 1 });
|
||||
}
|
||||
|
||||
if (activePolygonIndex == null || !polygons) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activePolygon = polygons[activePolygonIndex];
|
||||
if (
|
||||
(!activePolygon.isFinished && activePolygon.points.length >= 3) ||
|
||||
activePolygon.isFinished
|
||||
) {
|
||||
e.currentTarget.scale({ x: 1, y: 1 });
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointDragMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||
if (activePolygonIndex !== null && polygons) {
|
||||
const updatedPolygons = [...polygons];
|
||||
const activePolygon = updatedPolygons[activePolygonIndex];
|
||||
const stage = e.target.getStage();
|
||||
if (stage) {
|
||||
const index = e.target.index - 1;
|
||||
const pos = [e.target._lastPos!.x, e.target._lastPos!.y];
|
||||
if (pos[0] < 0) pos[0] = 0;
|
||||
if (pos[1] < 0) pos[1] = 0;
|
||||
if (pos[0] > stage.width()) pos[0] = stage.width();
|
||||
if (pos[1] > stage.height()) pos[1] = stage.height();
|
||||
updatedPolygons[activePolygonIndex] = {
|
||||
...activePolygon,
|
||||
points: [
|
||||
...activePolygon.points.slice(0, index),
|
||||
pos,
|
||||
...activePolygon.points.slice(index + 1),
|
||||
],
|
||||
};
|
||||
setPolygons(updatedPolygons);
|
||||
}
|
||||
const handlePointDragMove = (
|
||||
e: KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => {
|
||||
if (activePolygonIndex == null || !polygons) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedPolygons = [...polygons];
|
||||
const activePolygon = updatedPolygons[activePolygonIndex];
|
||||
const stage = e.target.getStage();
|
||||
if (stage) {
|
||||
const index = e.target.index - 1;
|
||||
const pos = [e.target._lastPos!.x, e.target._lastPos!.y];
|
||||
if (pos[0] < 0) pos[0] = 0;
|
||||
if (pos[1] < 0) pos[1] = 0;
|
||||
if (pos[0] > stage.width()) pos[0] = stage.width();
|
||||
if (pos[1] > stage.height()) pos[1] = stage.height();
|
||||
updatedPolygons[activePolygonIndex] = {
|
||||
...activePolygon,
|
||||
points: [
|
||||
...activePolygon.points.slice(0, index),
|
||||
pos,
|
||||
...activePolygon.points.slice(index + 1),
|
||||
],
|
||||
};
|
||||
setPolygons(updatedPolygons);
|
||||
}
|
||||
};
|
||||
|
||||
@ -151,7 +164,7 @@ export function PolygonCanvas({
|
||||
return points.reduce((acc, point) => [...acc, ...point], []);
|
||||
};
|
||||
|
||||
const handleGroupDragEnd = (e: KonvaEventObject<MouseEvent>) => {
|
||||
const handleGroupDragEnd = (e: KonvaEventObject<MouseEvent | TouchEvent>) => {
|
||||
if (activePolygonIndex !== null && e.target.name() === "polygon") {
|
||||
const updatedPolygons = [...polygons];
|
||||
const activePolygon = updatedPolygons[activePolygonIndex];
|
||||
@ -174,6 +187,7 @@ export function PolygonCanvas({
|
||||
width={width}
|
||||
height={height}
|
||||
onMouseDown={handleMouseDown}
|
||||
onTouchStart={handleMouseDown}
|
||||
>
|
||||
<Layer>
|
||||
<Image
|
||||
|
||||
@ -11,10 +11,14 @@ type PolygonDrawerProps = {
|
||||
isActive: boolean;
|
||||
isFinished: boolean;
|
||||
color: number[];
|
||||
handlePointDragMove: (e: KonvaEventObject<MouseEvent>) => void;
|
||||
handleGroupDragEnd: (e: KonvaEventObject<MouseEvent>) => void;
|
||||
handleMouseOverStartPoint: (e: KonvaEventObject<MouseEvent>) => void;
|
||||
handleMouseOutStartPoint: (e: KonvaEventObject<MouseEvent>) => void;
|
||||
handlePointDragMove: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void;
|
||||
handleGroupDragEnd: (e: KonvaEventObject<MouseEvent | TouchEvent>) => void;
|
||||
handleMouseOverStartPoint: (
|
||||
e: KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => void;
|
||||
handleMouseOutStartPoint: (
|
||||
e: KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export default function PolygonDrawer({
|
||||
@ -33,13 +37,17 @@ export default function PolygonDrawer({
|
||||
const [minMaxX, setMinMaxX] = useState([0, 0]);
|
||||
const [minMaxY, setMinMaxY] = useState([0, 0]);
|
||||
|
||||
const handleGroupMouseOver = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||
const handleGroupMouseOver = (
|
||||
e: Konva.KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => {
|
||||
if (!isFinished) return;
|
||||
e.target.getStage()!.container().style.cursor = "move";
|
||||
setStage(e.target.getStage()!);
|
||||
};
|
||||
|
||||
const handleGroupMouseOut = (e: Konva.KonvaEventObject<MouseEvent>) => {
|
||||
const handleGroupMouseOut = (
|
||||
e: Konva.KonvaEventObject<MouseEvent | TouchEvent>,
|
||||
) => {
|
||||
if (!e.target) return;
|
||||
e.target.getStage()!.container().style.cursor = "default";
|
||||
};
|
||||
@ -87,6 +95,7 @@ export default function PolygonDrawer({
|
||||
onDragEnd={isActive ? handleGroupDragEnd : undefined}
|
||||
dragBoundFunc={isActive ? groupDragBound : undefined}
|
||||
onMouseOver={isActive ? handleGroupMouseOver : undefined}
|
||||
onTouchStart={isActive ? handleGroupMouseOver : undefined}
|
||||
onMouseOut={isActive ? handleGroupMouseOut : undefined}
|
||||
>
|
||||
<Line
|
||||
|
||||
@ -43,11 +43,8 @@ export function ZoneObjectSelector({
|
||||
if (!cameraConfig || !zoneName) {
|
||||
return [];
|
||||
}
|
||||
console.log(zoneName);
|
||||
|
||||
const labels = new Set<string>();
|
||||
// console.log("zone name", zoneName);
|
||||
// console.log(cameraConfig.zones[zoneName].objects);
|
||||
|
||||
cameraConfig.objects.track.forEach((label) => {
|
||||
if (!ATTRIBUTES.includes(label)) {
|
||||
@ -55,9 +52,13 @@ export function ZoneObjectSelector({
|
||||
}
|
||||
});
|
||||
|
||||
cameraConfig.zones[zoneName].objects.forEach((label) => {
|
||||
labels.add(label);
|
||||
});
|
||||
if (cameraConfig.zones[zoneName]) {
|
||||
cameraConfig.zones[zoneName].objects.forEach((label) => {
|
||||
if (!ATTRIBUTES.includes(label)) {
|
||||
labels.add(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return [...labels].sort() || [];
|
||||
}, [cameraConfig, zoneName]);
|
||||
@ -166,7 +167,7 @@ export function ZoneControls({
|
||||
updatedPolygons[activePolygonIndex] = {
|
||||
points: [],
|
||||
isFinished: false,
|
||||
name: "new",
|
||||
name: updatedPolygons[activePolygonIndex].name,
|
||||
camera: camera,
|
||||
color: updatedPolygons[activePolygonIndex].color ?? [220, 0, 0],
|
||||
};
|
||||
@ -210,29 +211,31 @@ export function ZoneControls({
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
{isMobile && <span tabIndex={0} className="sr-only" />}
|
||||
<DialogTitle>New Zone</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter a label for your zone. Do not include spaces, and don't use
|
||||
the name of a camera.
|
||||
Enter a unique label for your zone. Do not include spaces, and
|
||||
don't use the name of a camera.
|
||||
</DialogDescription>
|
||||
<>
|
||||
<Input
|
||||
className="mt-3"
|
||||
className={`mt-3 ${isMobile && "text-md"}`}
|
||||
type="search"
|
||||
value={zoneName ?? ""}
|
||||
onChange={(e) => {
|
||||
setInvalidName(
|
||||
Object.keys(config.cameras).includes(e.target.value) ||
|
||||
e.target.value.includes(" "),
|
||||
e.target.value.includes(" ") ||
|
||||
polygons
|
||||
.map((item) => item.name)
|
||||
.includes(e.target.value),
|
||||
);
|
||||
|
||||
setZoneName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{invalidName && (
|
||||
<div className="text-danger text-sm">
|
||||
Zone name is not valid.
|
||||
</div>
|
||||
<div className="text-danger text-sm">Invalid zone name.</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import useSWR from "swr";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { PolygonCanvas } from "./PolygonCanvas";
|
||||
import { Polygon } from "@/types/canvas";
|
||||
import { interpolatePoints } from "@/utils/canvasUtil";
|
||||
@ -43,9 +43,16 @@ const parseCoordinates = (coordinatesString: string) => {
|
||||
return points;
|
||||
};
|
||||
|
||||
export type ZoneObjects = {
|
||||
camera: string;
|
||||
zoneName: string;
|
||||
objects: string[];
|
||||
};
|
||||
|
||||
export default function SettingsZones() {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const [zonePolygons, setZonePolygons] = useState<Polygon[]>([]);
|
||||
const [zoneObjects, setZoneObjects] = useState<ZoneObjects[]>([]);
|
||||
const [activePolygonIndex, setActivePolygonIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
@ -85,6 +92,51 @@ export default function SettingsZones() {
|
||||
return [...labels].sort();
|
||||
}, [cameras]);
|
||||
|
||||
// const saveZoneObjects = useCallback(
|
||||
// (camera: string, zoneName: string, newObjects?: string[]) => {
|
||||
// setZoneObjects((prevZoneObjects) =>
|
||||
// prevZoneObjects.map((zoneObject) => {
|
||||
// if (
|
||||
// zoneObject.camera === camera &&
|
||||
// zoneObject.zoneName === zoneName
|
||||
// ) {
|
||||
// console.log("found", camera, "with", zoneName);
|
||||
// console.log("new objects", newObjects);
|
||||
// console.log("new zoneobject", {
|
||||
// ...zoneObject,
|
||||
// objects: newObjects ?? [],
|
||||
// });
|
||||
// // Replace objects with newObjects if provided
|
||||
// return {
|
||||
// ...zoneObject,
|
||||
// objects: newObjects ?? [],
|
||||
// };
|
||||
// }
|
||||
// return zoneObject; // Keep original object
|
||||
// }),
|
||||
// );
|
||||
// },
|
||||
// [setZoneObjects],
|
||||
// );
|
||||
|
||||
const saveZoneObjects = useCallback(
|
||||
(camera: string, zoneName: string, objects?: string[]) => {
|
||||
setZoneObjects((prevZoneObjects) => {
|
||||
const updatedZoneObjects = prevZoneObjects.map((zoneObject) => {
|
||||
if (
|
||||
zoneObject.camera === camera &&
|
||||
zoneObject.zoneName === zoneName
|
||||
) {
|
||||
return { ...zoneObject, objects: objects || [] };
|
||||
}
|
||||
return zoneObject;
|
||||
});
|
||||
return updatedZoneObjects;
|
||||
});
|
||||
},
|
||||
[setZoneObjects],
|
||||
);
|
||||
|
||||
const grow = useMemo(() => {
|
||||
if (!cameraConfig) {
|
||||
return;
|
||||
@ -105,6 +157,14 @@ export default function SettingsZones() {
|
||||
}
|
||||
}, [cameraConfig]);
|
||||
|
||||
const handleCameraChange = useCallback(
|
||||
(camera: string) => {
|
||||
setSelectedCamera(camera);
|
||||
setActivePolygonIndex(null);
|
||||
},
|
||||
[setSelectedCamera, setActivePolygonIndex],
|
||||
);
|
||||
|
||||
const [{ width: containerWidth, height: containerHeight }] =
|
||||
useResizeObserver(containerRef);
|
||||
|
||||
@ -159,20 +219,40 @@ export default function SettingsZones() {
|
||||
color: zoneData.color,
|
||||
})),
|
||||
);
|
||||
|
||||
setZoneObjects(
|
||||
Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({
|
||||
camera: cameraConfig.name,
|
||||
zoneName: name,
|
||||
objects: Object.keys(zoneData.filters),
|
||||
})),
|
||||
);
|
||||
}
|
||||
// we know that these deps are correct
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cameraConfig, containerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
"config zone objects",
|
||||
Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({
|
||||
camera: cameraConfig.name,
|
||||
zoneName: name,
|
||||
objects: Object.keys(zoneData.filters),
|
||||
})),
|
||||
);
|
||||
console.log("component zone objects", zoneObjects);
|
||||
}, [zoneObjects]);
|
||||
|
||||
if (!cameraConfig && !selectedCamera) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-auto">
|
||||
<Heading as="h2">Zones</Heading>
|
||||
<div className="flex items-center space-x-2 mt-5">
|
||||
<Select value={selectedCamera} onValueChange={setSelectedCamera}>
|
||||
<Select value={selectedCamera} onValueChange={handleCameraChange}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Camera" />
|
||||
</SelectTrigger>
|
||||
@ -194,9 +274,9 @@ export default function SettingsZones() {
|
||||
</div>
|
||||
|
||||
{cameraConfig && (
|
||||
<div className="flex flex-row justify-evenly">
|
||||
<div className="flex flex-col justify-evenly">
|
||||
<div
|
||||
className={`flex flex-col justify-center items-center w-[60%] ${grow}`}
|
||||
className={`flex flex-col justify-center items-center w-full md:w-[60%] ${grow}`}
|
||||
>
|
||||
<div ref={containerRef} className="size-full">
|
||||
{cameraConfig ? (
|
||||
@ -213,7 +293,7 @@ export default function SettingsZones() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[30%]">
|
||||
<div className="w-full md:w-[30%]">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@ -254,7 +334,9 @@ export default function SettingsZones() {
|
||||
camera={polygon.camera}
|
||||
zoneName={polygon.name}
|
||||
allLabels={allLabels}
|
||||
updateLabelFilter={(objects) => console.log(objects)}
|
||||
updateLabelFilter={(objects) =>
|
||||
saveZoneObjects(polygon.camera, polygon.name, objects)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@ -294,6 +376,6 @@ export default function SettingsZones() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,27 +46,31 @@ function General() {
|
||||
|
||||
export default function Settings() {
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue="general" className="w-auto">
|
||||
<TabsList>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="objects">Objects</TabsTrigger>
|
||||
<TabsTrigger value="zones">Zones</TabsTrigger>
|
||||
<TabsTrigger value="masks">Masks</TabsTrigger>
|
||||
<TabsTrigger value="motion">Motion</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="general">
|
||||
<General />
|
||||
</TabsContent>
|
||||
<TabsContent value="objects">Objects</TabsContent>
|
||||
<TabsContent value="zones">
|
||||
<SettingsZones />
|
||||
</TabsContent>
|
||||
<TabsContent value="masks">Masks</TabsContent>
|
||||
<TabsContent value="motion">
|
||||
<MotionTuner />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</>
|
||||
<div className="w-full h-full">
|
||||
<div className="flex h-full">
|
||||
<div className="flex-1 content-start gap-2 overflow-y-auto no-scrollbar mt-4 mr-5">
|
||||
<Tabs defaultValue="general" className="w-auto">
|
||||
<TabsList>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="objects">Objects</TabsTrigger>
|
||||
<TabsTrigger value="zones">Zones</TabsTrigger>
|
||||
<TabsTrigger value="masks">Masks</TabsTrigger>
|
||||
<TabsTrigger value="motion">Motion</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="general">
|
||||
<General />
|
||||
</TabsContent>
|
||||
<TabsContent value="objects">Objects</TabsContent>
|
||||
<TabsContent value="zones">
|
||||
<SettingsZones />
|
||||
</TabsContent>
|
||||
<TabsContent value="masks">Masks</TabsContent>
|
||||
<TabsContent value="motion">
|
||||
<MotionTuner />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,13 +21,7 @@ export interface BirdseyeConfig {
|
||||
width: number;
|
||||
}
|
||||
|
||||
export const ATTRIBUTES = [
|
||||
"amazon",
|
||||
"face",
|
||||
"fedex",
|
||||
"license_plate",
|
||||
"ups",
|
||||
] as const;
|
||||
export const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
|
||||
|
||||
export interface CameraConfig {
|
||||
audio: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user