mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
working motion masks
This commit is contained in:
parent
1982fa3461
commit
93b206d6b4
@ -4,7 +4,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { PolygonCanvas } from "./PolygonCanvas";
|
import { PolygonCanvas } from "./PolygonCanvas";
|
||||||
import { Polygon, PolygonType } from "@/types/canvas";
|
import { Polygon, PolygonType } from "@/types/canvas";
|
||||||
import { interpolatePoints } from "@/utils/canvasUtil";
|
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { Skeleton } from "../ui/skeleton";
|
||||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||||
import { LuExternalLink, LuInfo, LuPlus } from "react-icons/lu";
|
import { LuExternalLink, LuInfo, LuPlus } from "react-icons/lu";
|
||||||
@ -25,19 +25,6 @@ import ObjectMaskEditPane from "./ObjectMaskEditPane";
|
|||||||
import PolygonItem from "./PolygonItem";
|
import PolygonItem from "./PolygonItem";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const parseCoordinates = (coordinatesString: string) => {
|
|
||||||
const coordinates = coordinatesString.split(",");
|
|
||||||
const points = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < coordinates.length; i += 2) {
|
|
||||||
const x = parseFloat(coordinates[i]);
|
|
||||||
const y = parseFloat(coordinates[i + 1]);
|
|
||||||
points.push([x, y]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return points;
|
|
||||||
};
|
|
||||||
|
|
||||||
// export type ZoneObjects = {
|
// export type ZoneObjects = {
|
||||||
// camera: string;
|
// camera: string;
|
||||||
// zoneName: string;
|
// zoneName: string;
|
||||||
@ -299,8 +286,9 @@ export default function MasksAndZones({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cameraConfig && containerRef.current && scaledWidth && scaledHeight) {
|
if (cameraConfig && containerRef.current && scaledWidth && scaledHeight) {
|
||||||
const zones = Object.entries(cameraConfig.zones).map(
|
const zones = Object.entries(cameraConfig.zones).map(
|
||||||
([name, zoneData]) => ({
|
([name, zoneData], index) => ({
|
||||||
type: "zone" as PolygonType,
|
type: "zone" as PolygonType,
|
||||||
|
typeIndex: index,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name,
|
name,
|
||||||
objects: zoneData.objects,
|
objects: zoneData.objects,
|
||||||
@ -317,28 +305,32 @@ export default function MasksAndZones({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const motionMasks = Object.entries(cameraConfig.motion.mask).map(
|
// this can be an array or a string
|
||||||
([, maskData], index) => ({
|
const motionMasks = Object.entries(
|
||||||
type: "motion_mask" as PolygonType,
|
Array.isArray(cameraConfig.motion.mask)
|
||||||
camera: cameraConfig.name,
|
? cameraConfig.motion.mask
|
||||||
name: `Motion Mask ${index + 1}`,
|
: [cameraConfig.motion.mask],
|
||||||
objects: [],
|
).map(([, maskData], index) => ({
|
||||||
points: interpolatePoints(
|
type: "motion_mask" as PolygonType,
|
||||||
parseCoordinates(maskData),
|
typeIndex: index,
|
||||||
1,
|
camera: cameraConfig.name,
|
||||||
1,
|
name: `Motion Mask ${index + 1}`,
|
||||||
scaledWidth,
|
objects: [],
|
||||||
scaledHeight,
|
points: interpolatePoints(
|
||||||
),
|
parseCoordinates(maskData),
|
||||||
isFinished: true,
|
1,
|
||||||
// isUnsaved: false,
|
1,
|
||||||
color: [0, 0, 255],
|
scaledWidth,
|
||||||
}),
|
scaledHeight,
|
||||||
);
|
),
|
||||||
|
isFinished: true,
|
||||||
|
color: [0, 0, 255],
|
||||||
|
}));
|
||||||
|
|
||||||
const globalObjectMasks = Object.entries(cameraConfig.objects.mask).map(
|
const globalObjectMasks = Object.entries(cameraConfig.objects.mask).map(
|
||||||
([, maskData], index) => ({
|
([, maskData], index) => ({
|
||||||
type: "object_mask" as PolygonType,
|
type: "object_mask" as PolygonType,
|
||||||
|
typeIndex: index,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name: `Object Mask ${index + 1} (all objects)`,
|
name: `Object Mask ${index + 1} (all objects)`,
|
||||||
objects: [],
|
objects: [],
|
||||||
@ -365,6 +357,7 @@ export default function MasksAndZones({
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
type: "object_mask" as PolygonType,
|
type: "object_mask" as PolygonType,
|
||||||
|
typeIndex: subIndex,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name: `Object Mask ${globalObjectMasksCount + subIndex + 1} (${objectName})`,
|
name: `Object Mask ${globalObjectMasksCount + subIndex + 1} (${objectName})`,
|
||||||
objects: [objectName],
|
objects: [objectName],
|
||||||
@ -411,6 +404,10 @@ export default function MasksAndZones({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [cameraConfig, containerRef, scaledHeight, scaledWidth]);
|
}, [cameraConfig, containerRef, scaledHeight, scaledWidth]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("editing polygons changed:", editingPolygons);
|
||||||
|
}, [editingPolygons]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editPane === undefined) {
|
if (editPane === undefined) {
|
||||||
setEditingPolygons([...allPolygons]);
|
setEditingPolygons([...allPolygons]);
|
||||||
@ -469,6 +466,10 @@ export default function MasksAndZones({
|
|||||||
polygons={editingPolygons}
|
polygons={editingPolygons}
|
||||||
setPolygons={setEditingPolygons}
|
setPolygons={setEditingPolygons}
|
||||||
activePolygonIndex={activePolygonIndex}
|
activePolygonIndex={activePolygonIndex}
|
||||||
|
scaledWidth={scaledWidth}
|
||||||
|
scaledHeight={scaledHeight}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
@ -478,6 +479,10 @@ export default function MasksAndZones({
|
|||||||
polygons={editingPolygons}
|
polygons={editingPolygons}
|
||||||
setPolygons={setEditingPolygons}
|
setPolygons={setEditingPolygons}
|
||||||
activePolygonIndex={activePolygonIndex}
|
activePolygonIndex={activePolygonIndex}
|
||||||
|
scaledWidth={scaledWidth}
|
||||||
|
scaledHeight={scaledHeight}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,18 +2,32 @@ import Heading from "../ui/heading";
|
|||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
import { Form, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
||||||
import { useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Polygon } from "@/types/canvas";
|
|
||||||
import PolygonEditControls from "./PolygonEditControls";
|
import PolygonEditControls from "./PolygonEditControls";
|
||||||
import { FaCheckCircle } from "react-icons/fa";
|
import { FaCheckCircle } from "react-icons/fa";
|
||||||
|
import { Polygon } from "@/types/canvas";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import {
|
||||||
|
flattenPoints,
|
||||||
|
interpolatePoints,
|
||||||
|
parseCoordinates,
|
||||||
|
} from "@/utils/canvasUtil";
|
||||||
|
import axios from "axios";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Toaster } from "../ui/sonner";
|
||||||
|
|
||||||
type MotionMaskEditPaneProps = {
|
type MotionMaskEditPaneProps = {
|
||||||
polygons?: Polygon[];
|
polygons?: Polygon[];
|
||||||
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||||
activePolygonIndex?: number;
|
activePolygonIndex?: number;
|
||||||
|
scaledWidth?: number;
|
||||||
|
scaledHeight?: number;
|
||||||
|
isLoading: boolean;
|
||||||
|
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
onSave?: () => void;
|
onSave?: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
@ -22,9 +36,16 @@ export default function MotionMaskEditPane({
|
|||||||
polygons,
|
polygons,
|
||||||
setPolygons,
|
setPolygons,
|
||||||
activePolygonIndex,
|
activePolygonIndex,
|
||||||
|
scaledWidth,
|
||||||
|
scaledHeight,
|
||||||
|
isLoading,
|
||||||
|
setIsLoading,
|
||||||
onSave,
|
onSave,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: MotionMaskEditPaneProps) {
|
}: MotionMaskEditPaneProps) {
|
||||||
|
const { data: config, mutate: updateConfig } =
|
||||||
|
useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const polygon = useMemo(() => {
|
const polygon = useMemo(() => {
|
||||||
if (polygons && activePolygonIndex !== undefined) {
|
if (polygons && activePolygonIndex !== undefined) {
|
||||||
return polygons[activePolygonIndex];
|
return polygons[activePolygonIndex];
|
||||||
@ -33,6 +54,12 @@ export default function MotionMaskEditPane({
|
|||||||
}
|
}
|
||||||
}, [polygons, activePolygonIndex]);
|
}, [polygons, activePolygonIndex]);
|
||||||
|
|
||||||
|
const cameraConfig = useMemo(() => {
|
||||||
|
if (polygon?.camera && config) {
|
||||||
|
return config.cameras[polygon.camera];
|
||||||
|
}
|
||||||
|
}, [polygon, config]);
|
||||||
|
|
||||||
const defaultName = useMemo(() => {
|
const defaultName = useMemo(() => {
|
||||||
if (!polygons) {
|
if (!polygons) {
|
||||||
return;
|
return;
|
||||||
@ -60,20 +87,121 @@ export default function MotionMaskEditPane({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
const saveToConfig = useCallback(async () => {
|
||||||
// console.log("form values", values);
|
if (!scaledWidth || !scaledHeight || !polygon || !cameraConfig) {
|
||||||
// if (activePolygonIndex === undefined || !polygons) {
|
return;
|
||||||
// return;
|
}
|
||||||
// }
|
// console.log("loitering time", loitering_time);
|
||||||
|
// const alertsZones = config?.cameras[camera]?.review.alerts.required_zones;
|
||||||
|
|
||||||
// const updatedPolygons = [...polygons];
|
// const detectionsZones =
|
||||||
// const activePolygon = updatedPolygons[activePolygonIndex];
|
// config?.cameras[camera]?.review.detections.required_zones;
|
||||||
// updatedPolygons[activePolygonIndex] = {
|
|
||||||
// ...activePolygon,
|
// console.log("out of try except", mutatedConfig);
|
||||||
// name: defaultName ?? "foo",
|
|
||||||
// };
|
const coordinates = flattenPoints(
|
||||||
// setPolygons(updatedPolygons);
|
interpolatePoints(polygon.points, scaledWidth, scaledHeight, 1, 1),
|
||||||
|
).join(",");
|
||||||
|
|
||||||
|
let index = Array.isArray(cameraConfig.motion.mask)
|
||||||
|
? cameraConfig.motion.mask.length
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
console.log("are we an array?", Array.isArray(cameraConfig.motion.mask));
|
||||||
|
console.log("index", index);
|
||||||
|
const editingMask = polygon.name.length > 0;
|
||||||
|
|
||||||
|
// editing existing mask, not creating a new one
|
||||||
|
if (editingMask) {
|
||||||
|
index = polygon.typeIndex;
|
||||||
|
if (polygon.name) {
|
||||||
|
const match = polygon.name.match(/\d+/);
|
||||||
|
if (match) {
|
||||||
|
// index = parseInt(match[0]) - 1;
|
||||||
|
console.log("editing, index", index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredMask = Array.isArray(cameraConfig.motion.mask)
|
||||||
|
? cameraConfig.motion.mask
|
||||||
|
: [cameraConfig.motion.mask].filter(
|
||||||
|
(_, currentIndex) => currentIndex !== index,
|
||||||
|
);
|
||||||
|
console.log("filtered", filteredMask);
|
||||||
|
|
||||||
|
// if (editingMask) {
|
||||||
|
// if (index != null) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
filteredMask.splice(index, 0, coordinates);
|
||||||
|
console.log("filtered after splice", filteredMask);
|
||||||
|
|
||||||
|
const queryString = filteredMask
|
||||||
|
.map((pointsArray) => {
|
||||||
|
const coordinates = flattenPoints(parseCoordinates(pointsArray)).join(
|
||||||
|
",",
|
||||||
|
);
|
||||||
|
return `cameras.${polygon?.camera}.motion.mask=${coordinates}&`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
console.log("polygon", polygon);
|
||||||
|
console.log(queryString);
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// `config/set?cameras.${polygon?.camera}.motion.mask=${coordinates}&${queryString}`,
|
||||||
|
// );
|
||||||
|
console.log("motion masks", cameraConfig.motion.mask);
|
||||||
|
console.log("new coords", coordinates);
|
||||||
|
// return;
|
||||||
|
|
||||||
|
axios
|
||||||
|
.put(`config/set?${queryString}`, {
|
||||||
|
requires_restart: 0,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success(`Zone ${name} saved.`, {
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
// setChangedValue(false);
|
||||||
|
updateConfig();
|
||||||
|
} else {
|
||||||
|
toast.error(`Failed to save config changes: ${res.statusText}`, {
|
||||||
|
position: "top-center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(
|
||||||
|
`Failed to save config changes: ${error.response.data.message}`,
|
||||||
|
{ position: "top-center" },
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, [updateConfig, polygon, scaledWidth, scaledHeight, setIsLoading]);
|
||||||
|
|
||||||
|
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
|
if (activePolygonIndex === undefined || !values || !polygons) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
// polygons[activePolygonIndex].name = values.name;
|
||||||
|
// console.log("form values", values);
|
||||||
|
// console.log(
|
||||||
|
// "string",
|
||||||
|
|
||||||
|
// flattenPoints(
|
||||||
|
// interpolatePoints(polygon.points, scaledWidth, scaledHeight, 1, 1),
|
||||||
|
// ).join(","),
|
||||||
|
// );
|
||||||
// console.log("active polygon", polygons[activePolygonIndex]);
|
// console.log("active polygon", polygons[activePolygonIndex]);
|
||||||
|
|
||||||
|
saveToConfig();
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
@ -85,6 +213,7 @@ export default function MotionMaskEditPane({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Toaster position="top-center" />
|
||||||
<Heading as="h3" className="my-2">
|
<Heading as="h3" className="my-2">
|
||||||
{polygon.name.length ? "Edit" : "New"} Motion Mask
|
{polygon.name.length ? "Edit" : "New"} Motion Mask
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|||||||
@ -20,7 +20,11 @@ import { FaDrawPolygon, FaObjectGroup } from "react-icons/fa";
|
|||||||
import { BsPersonBoundingBox } from "react-icons/bs";
|
import { BsPersonBoundingBox } from "react-icons/bs";
|
||||||
import { HiOutlineDotsVertical, HiTrash } from "react-icons/hi";
|
import { HiOutlineDotsVertical, HiTrash } from "react-icons/hi";
|
||||||
import { isMobile } from "react-device-detect";
|
import { isMobile } from "react-device-detect";
|
||||||
import { toRGBColorString } from "@/utils/canvasUtil";
|
import {
|
||||||
|
flattenPoints,
|
||||||
|
parseCoordinates,
|
||||||
|
toRGBColorString,
|
||||||
|
} from "@/utils/canvasUtil";
|
||||||
import { Polygon, PolygonType } from "@/types/canvas";
|
import { Polygon, PolygonType } from "@/types/canvas";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@ -33,6 +37,7 @@ import { reviewQueries } from "@/utils/zoneEdutUtil";
|
|||||||
type PolygonItemProps = {
|
type PolygonItemProps = {
|
||||||
polygon: Polygon;
|
polygon: Polygon;
|
||||||
setAllPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
setAllPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||||
|
setReindexPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||||
index: number;
|
index: number;
|
||||||
activePolygonIndex: number | undefined;
|
activePolygonIndex: number | undefined;
|
||||||
hoveredPolygonIndex: number | null;
|
hoveredPolygonIndex: number | null;
|
||||||
@ -86,13 +91,40 @@ export default function PolygonItem({
|
|||||||
cameraConfig?.review.alerts.required_zones || [],
|
cameraConfig?.review.alerts.required_zones || [],
|
||||||
cameraConfig?.review.detections.required_zones || [],
|
cameraConfig?.review.detections.required_zones || [],
|
||||||
);
|
);
|
||||||
url = `config/set?cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}`;
|
url = `cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}`;
|
||||||
}
|
}
|
||||||
if (polygon.type == "motion_mask") {
|
if (polygon.type == "motion_mask") {
|
||||||
url = `config/set?cameras.${polygon.camera}.motion.mask`;
|
console.log("deleting", polygon.typeIndex);
|
||||||
|
if (polygon.name) {
|
||||||
|
const match = polygon.name.match(/\d+/);
|
||||||
|
if (match) {
|
||||||
|
// index = parseInt(match[0]) - 1;
|
||||||
|
console.log("deleting, index", polygon.typeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredMask = (
|
||||||
|
Array.isArray(cameraConfig.motion.mask)
|
||||||
|
? cameraConfig.motion.mask
|
||||||
|
: [cameraConfig.motion.mask]
|
||||||
|
).filter((_, currentIndex) => currentIndex !== polygon.typeIndex);
|
||||||
|
console.log(filteredMask);
|
||||||
|
|
||||||
|
url = filteredMask
|
||||||
|
.map((pointsArray) => {
|
||||||
|
const coordinates = flattenPoints(
|
||||||
|
parseCoordinates(pointsArray),
|
||||||
|
).join(",");
|
||||||
|
return `cameras.${polygon?.camera}.motion.mask=${coordinates}&`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
// return;
|
||||||
|
// url = `config/set?cameras.${polygon.camera}.motion.mask`;
|
||||||
}
|
}
|
||||||
axios
|
await axios
|
||||||
.put(url, { requires_restart: 0 })
|
.put(`config/set?${url}`, { requires_restart: 0 })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
toast.success(`${polygon?.name} has been deleted.`, {
|
toast.success(`${polygon?.name} has been deleted.`, {
|
||||||
@ -116,12 +148,35 @@ export default function PolygonItem({
|
|||||||
// setIsLoading(false);
|
// setIsLoading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[updateConfig],
|
[updateConfig, cameraConfig],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = (index: number) => {
|
const reindexPolygons = (arr: Polygon[]): Polygon[] => {
|
||||||
|
const typeCounters: { [type: string]: number } = {};
|
||||||
|
|
||||||
|
return arr.map((obj) => {
|
||||||
|
if (!typeCounters[obj.type]) {
|
||||||
|
typeCounters[obj.type] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newObj: Polygon = {
|
||||||
|
...obj,
|
||||||
|
typeIndex: typeCounters[obj.type],
|
||||||
|
};
|
||||||
|
typeCounters[obj.type]++;
|
||||||
|
return newObj;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (type: string, typeIndex: number) => {
|
||||||
setAllPolygons((oldPolygons) => {
|
setAllPolygons((oldPolygons) => {
|
||||||
return oldPolygons.filter((_, i) => i !== index);
|
const filteredPolygons = oldPolygons.filter(
|
||||||
|
(polygon) =>
|
||||||
|
!(polygon.type === type && polygon.typeIndex === typeIndex),
|
||||||
|
);
|
||||||
|
console.log("filtered", filteredPolygons);
|
||||||
|
// console.log("reindexed", reindexPolygons(filteredPolygons));
|
||||||
|
return filteredPolygons;
|
||||||
});
|
});
|
||||||
setActivePolygonIndex(undefined);
|
setActivePolygonIndex(undefined);
|
||||||
saveToConfig(polygon);
|
saveToConfig(polygon);
|
||||||
@ -176,7 +231,9 @@ export default function PolygonItem({
|
|||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
<AlertDialogAction onClick={() => handleDelete(index)}>
|
<AlertDialogAction
|
||||||
|
onClick={() => handleDelete(polygon.type, polygon.typeIndex)}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { isMobile } from "react-device-detect";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Polygon } from "@/types/canvas";
|
import { FormValuesType, Polygon } from "@/types/canvas";
|
||||||
import { reviewQueries } from "@/utils/zoneEdutUtil";
|
import { reviewQueries } from "@/utils/zoneEdutUtil";
|
||||||
import { Switch } from "../ui/switch";
|
import { Switch } from "../ui/switch";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
@ -158,15 +158,6 @@ export default function ZoneEditPane({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// const [changedValue, setChangedValue] = useState(false);
|
// const [changedValue, setChangedValue] = useState(false);
|
||||||
type FormValuesType = {
|
|
||||||
name: string;
|
|
||||||
inertia: number;
|
|
||||||
loitering_time: number;
|
|
||||||
isFinished: boolean;
|
|
||||||
objects: string[];
|
|
||||||
review_alerts: boolean;
|
|
||||||
review_detections: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const requiredDetectionZones = useMemo(
|
// const requiredDetectionZones = useMemo(
|
||||||
// () => cameraConfig?.review.detections.required_zones,
|
// () => cameraConfig?.review.detections.required_zones,
|
||||||
|
|||||||
@ -56,9 +56,11 @@ export default function Settings() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cameras) {
|
if (cameras) {
|
||||||
|
// TODO: fixme
|
||||||
setSelectedCamera(cameras[0].name);
|
setSelectedCamera(cameras[0].name);
|
||||||
|
console.log("setting selected cam");
|
||||||
}
|
}
|
||||||
}, [cameras]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-full p-2 flex flex-col">
|
<div className="size-full p-2 flex flex-col">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export type PolygonType = "zone" | "motion_mask" | "object_mask";
|
export type PolygonType = "zone" | "motion_mask" | "object_mask";
|
||||||
|
|
||||||
export type Polygon = {
|
export type Polygon = {
|
||||||
|
typeIndex: number;
|
||||||
camera: string;
|
camera: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: PolygonType;
|
type: PolygonType;
|
||||||
@ -10,3 +11,13 @@ export type Polygon = {
|
|||||||
// isUnsaved: boolean;
|
// isUnsaved: boolean;
|
||||||
color: number[];
|
color: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FormValuesType = {
|
||||||
|
name: string;
|
||||||
|
inertia: number;
|
||||||
|
loitering_time: number;
|
||||||
|
isFinished: boolean;
|
||||||
|
objects: string[];
|
||||||
|
review_alerts: boolean;
|
||||||
|
review_detections: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@ -64,6 +64,19 @@ export const interpolatePoints = (
|
|||||||
return newPoints;
|
return newPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseCoordinates = (coordinatesString: string) => {
|
||||||
|
const coordinates = coordinatesString.split(",");
|
||||||
|
const points = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < coordinates.length; i += 2) {
|
||||||
|
const x = parseFloat(coordinates[i]);
|
||||||
|
const y = parseFloat(coordinates[i + 1]);
|
||||||
|
points.push([x, y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
export const flattenPoints = (points: number[][]): number[] => {
|
export const flattenPoints = (points: number[][]): number[] => {
|
||||||
return points.reduce((acc, point) => [...acc, ...point], []);
|
return points.reduce((acc, point) => [...acc, ...point], []);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user