objects for zones

This commit is contained in:
Josh Hawkins 2024-04-10 12:03:46 -05:00
parent 96c44e65bd
commit cf96f7b706
5 changed files with 167 additions and 17 deletions

View File

@ -1,31 +1,145 @@
import { Button } from "../ui/button";
import { Polygon } from "@/types/canvas"; import { Polygon } from "@/types/canvas";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { useState } from "react"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import { useMemo, useState } from "react";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { GeneralFilterContent } from "../filter/ReviewFilterGroup";
import { FaObjectGroup } from "react-icons/fa";
import { Button } from "../ui/button";
import { ATTRIBUTES, FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr"; import useSWR from "swr";
import { isMobile } from "react-device-detect";
type PolygonCanvasProps = { type ZoneObjectSelectorProps = {
camera: string;
zoneName: string;
allLabels: string[];
updateLabelFilter: (labels: string[] | undefined) => void;
};
export function ZoneObjectSelector({
camera,
zoneName,
allLabels,
updateLabelFilter,
}: ZoneObjectSelectorProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const [open, setOpen] = useState(false);
const cameraConfig = useMemo(() => {
if (config && camera) {
return config.cameras[camera];
}
}, [config, camera]);
const zoneLabels = useMemo<string[]>(() => {
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)) {
labels.add(label);
}
});
cameraConfig.zones[zoneName].objects.forEach((label) => {
labels.add(label);
});
return [...labels].sort() || [];
}, [cameraConfig, zoneName]);
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
zoneLabels,
);
const trigger = (
<Button
className={`flex items-center gap-2 capitalize ${false ? "bg-selected hover:bg-selected" : ""}`}
size="sm"
>
<FaObjectGroup
className={`${false ? "text-background dark:text-primary" : "text-secondary-foreground"}`}
/>
</Button>
);
const content = (
<GeneralFilterContent
allLabels={allLabels}
selectedLabels={zoneLabels}
currentLabels={currentLabels}
updateLabelFilter={updateLabelFilter}
setCurrentLabels={setCurrentLabels}
onClose={() => setOpen(false)}
/>
);
if (isMobile) {
return (
<Drawer
open={open}
onOpenChange={(open) => {
if (!open) {
setCurrentLabels(zoneLabels);
}
setOpen(open);
}}
>
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[75dvh] overflow-hidden">
{content}
</DrawerContent>
</Drawer>
);
}
return (
<Popover
open={open}
onOpenChange={(open) => {
if (!open) {
setCurrentLabels(zoneLabels);
}
setOpen(open);
}}
>
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent>{content}</PopoverContent>
</Popover>
);
}
type ZoneControlsProps = {
camera: string; camera: string;
width: number;
height: number;
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>>; setActivePolygonIndex: React.Dispatch<React.SetStateAction<number | null>>;
}; };
export function PolygonControls({ export function ZoneControls({
camera,
polygons, polygons,
setPolygons, setPolygons,
activePolygonIndex, activePolygonIndex,
setActivePolygonIndex, setActivePolygonIndex,
}: PolygonCanvasProps) { }: ZoneControlsProps) {
const { data: config } = useSWR("config"); const { data: config } = useSWR("config");
const [zoneName, setZoneName] = useState<string | null>(); const [zoneName, setZoneName] = useState<string | null>();
const [invalidName, setInvalidName] = useState<boolean>(); const [invalidName, setInvalidName] = useState<boolean>();
@ -53,6 +167,7 @@ export function PolygonControls({
points: [], points: [],
isFinished: false, isFinished: false,
name: "new", name: "new",
camera: camera,
color: updatedPolygons[activePolygonIndex].color ?? [220, 0, 0], color: updatedPolygons[activePolygonIndex].color ?? [220, 0, 0],
}; };
setPolygons(updatedPolygons); setPolygons(updatedPolygons);
@ -66,6 +181,7 @@ export function PolygonControls({
points: [], points: [],
isFinished: false, isFinished: false,
name: zoneName, name: zoneName,
camera: camera,
color: [220, 0, 0], color: [220, 0, 0],
}, },
]); ]);
@ -95,6 +211,10 @@ export function PolygonControls({
> >
<DialogContent> <DialogContent>
<DialogTitle>New Zone</DialogTitle> <DialogTitle>New Zone</DialogTitle>
<DialogDescription>
Enter a label for your zone. Do not include spaces, and don't use
the name of a camera.
</DialogDescription>
<> <>
<Input <Input
className="mt-3" className="mt-3"
@ -102,7 +222,8 @@ export function PolygonControls({
value={zoneName ?? ""} value={zoneName ?? ""}
onChange={(e) => { onChange={(e) => {
setInvalidName( setInvalidName(
Object.keys(config.cameras).includes(e.target.value), Object.keys(config.cameras).includes(e.target.value) ||
e.target.value.includes(" "),
); );
setZoneName(e.target.value); setZoneName(e.target.value);
@ -110,7 +231,7 @@ export function PolygonControls({
/> />
{invalidName && ( {invalidName && (
<div className="text-danger text-sm"> <div className="text-danger text-sm">
Zone names must not be the name of a camera. Zone name is not valid.
</div> </div>
)} )}
<DialogFooter> <DialogFooter>
@ -137,4 +258,4 @@ export function PolygonControls({
); );
} }
export default PolygonControls; export default ZoneControls;

View File

@ -16,6 +16,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
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";
@ -24,7 +25,7 @@ import { PolygonCanvas } from "./PolygonCanvas";
import { Polygon } from "@/types/canvas"; import { Polygon } from "@/types/canvas";
import { interpolatePoints } from "@/utils/canvasUtil"; import { interpolatePoints } from "@/utils/canvasUtil";
import { isDesktop } from "react-device-detect"; import { isDesktop } from "react-device-detect";
import PolygonControls from "./PolygonControls"; import ZoneControls, { ZoneObjectSelector } from "./ZoneControls";
import { Skeleton } from "../ui/skeleton"; import { Skeleton } from "../ui/skeleton";
import { useResizeObserver } from "@/hooks/resize-observer"; import { useResizeObserver } from "@/hooks/resize-observer";
import { LuPencil } from "react-icons/lu"; import { LuPencil } from "react-icons/lu";
@ -68,6 +69,22 @@ export default function SettingsZones() {
} }
}, [config, selectedCamera]); }, [config, selectedCamera]);
const allLabels = useMemo<string[]>(() => {
if (!cameras) {
return [];
}
const labels = new Set<string>();
cameras.forEach((camera) => {
camera.objects.track.forEach((label) => {
labels.add(label);
});
});
return [...labels].sort();
}, [cameras]);
const grow = useMemo(() => { const grow = useMemo(() => {
if (!cameraConfig) { if (!cameraConfig) {
return; return;
@ -129,6 +146,7 @@ export default function SettingsZones() {
if (cameraConfig && containerRef.current) { if (cameraConfig && containerRef.current) {
setZonePolygons( setZonePolygons(
Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({ Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({
camera: cameraConfig.name,
name, name,
points: interpolatePoints( points: interpolatePoints(
parseCoordinates(zoneData.coordinates), parseCoordinates(zoneData.coordinates),
@ -232,6 +250,12 @@ export default function SettingsZones() {
> >
<LuPencil className="size-4 text-white" /> <LuPencil className="size-4 text-white" />
</div> </div>
<ZoneObjectSelector
camera={polygon.camera}
zoneName={polygon.name}
allLabels={allLabels}
updateLabelFilter={(objects) => console.log(objects)}
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -242,10 +266,8 @@ export default function SettingsZones() {
container width: {containerWidth}, container height: container width: {containerWidth}, container height:
{containerHeight} {containerHeight}
</div> </div>
<PolygonControls <ZoneControls
camera={cameraConfig.name} camera={cameraConfig.name}
width={scaledWidth}
height={scaledHeight}
polygons={zonePolygons} polygons={zonePolygons}
setPolygons={setZonePolygons} setPolygons={setZonePolygons}
activePolygonIndex={activePolygonIndex} activePolygonIndex={activePolygonIndex}

View File

@ -24,7 +24,7 @@ import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { DualThumbSlider } from "@/components/ui/slider"; import { DualThumbSlider } from "@/components/ui/slider";
import { Event } from "@/types/event"; import { Event } from "@/types/event";
import { FrigateConfig } from "@/types/frigateConfig"; import { ATTRIBUTES, FrigateConfig } from "@/types/frigateConfig";
import axios from "axios"; import axios from "axios";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
@ -199,8 +199,6 @@ export default function SubmitPlus() {
); );
} }
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
type PlusFilterGroupProps = { type PlusFilterGroupProps = {
selectedCameras: string[] | undefined; selectedCameras: string[] | undefined;
selectedLabels: string[] | undefined; selectedLabels: string[] | undefined;

View File

@ -1,4 +1,5 @@
export type Polygon = { export type Polygon = {
camera: string;
name: string; name: string;
points: number[][]; points: number[][];
isFinished: boolean; isFinished: boolean;

View File

@ -21,6 +21,14 @@ export interface BirdseyeConfig {
width: number; width: number;
} }
export const ATTRIBUTES = [
"amazon",
"face",
"fedex",
"license_plate",
"ups",
] as const;
export interface CameraConfig { export interface CameraConfig {
audio: { audio: {
enabled: boolean; enabled: boolean;