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 {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogTitle,
} 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 { 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 { 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;
width: number;
height: number;
polygons: Polygon[];
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
activePolygonIndex: number | null;
setActivePolygonIndex: React.Dispatch<React.SetStateAction<number | null>>;
};
export function PolygonControls({
export function ZoneControls({
camera,
polygons,
setPolygons,
activePolygonIndex,
setActivePolygonIndex,
}: PolygonCanvasProps) {
}: ZoneControlsProps) {
const { data: config } = useSWR("config");
const [zoneName, setZoneName] = useState<string | null>();
const [invalidName, setInvalidName] = useState<boolean>();
@ -53,6 +167,7 @@ export function PolygonControls({
points: [],
isFinished: false,
name: "new",
camera: camera,
color: updatedPolygons[activePolygonIndex].color ?? [220, 0, 0],
};
setPolygons(updatedPolygons);
@ -66,6 +181,7 @@ export function PolygonControls({
points: [],
isFinished: false,
name: zoneName,
camera: camera,
color: [220, 0, 0],
},
]);
@ -95,6 +211,10 @@ export function PolygonControls({
>
<DialogContent>
<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
className="mt-3"
@ -102,7 +222,8 @@ export function PolygonControls({
value={zoneName ?? ""}
onChange={(e) => {
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);
@ -110,7 +231,7 @@ export function PolygonControls({
/>
{invalidName && (
<div className="text-danger text-sm">
Zone names must not be the name of a camera.
Zone name is not valid.
</div>
)}
<DialogFooter>
@ -137,4 +258,4 @@ export function PolygonControls({
);
}
export default PolygonControls;
export default ZoneControls;

View File

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

View File

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

View File

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

View File

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