diff --git a/web/src/components/settings/PolygonControls.tsx b/web/src/components/settings/ZoneControls.tsx similarity index 51% rename from web/src/components/settings/PolygonControls.tsx rename to web/src/components/settings/ZoneControls.tsx index fe62cd343..f6ba06f30 100644 --- a/web/src/components/settings/PolygonControls.tsx +++ b/web/src/components/settings/ZoneControls.tsx @@ -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("config"); + const [open, setOpen] = useState(false); + + const cameraConfig = useMemo(() => { + if (config && camera) { + return config.cameras[camera]; + } + }, [config, camera]); + + const zoneLabels = useMemo(() => { + if (!cameraConfig || !zoneName) { + return []; + } + console.log(zoneName); + + const labels = new Set(); + // 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( + zoneLabels, + ); + + const trigger = ( + + ); + + const content = ( + setOpen(false)} + /> + ); + + if (isMobile) { + return ( + { + if (!open) { + setCurrentLabels(zoneLabels); + } + + setOpen(open); + }} + > + {trigger} + + {content} + + + ); + } + + return ( + { + if (!open) { + setCurrentLabels(zoneLabels); + } + + setOpen(open); + }} + > + {trigger} + {content} + + ); +} + +type ZoneControlsProps = { camera: string; - width: number; - height: number; polygons: Polygon[]; setPolygons: React.Dispatch>; activePolygonIndex: number | null; setActivePolygonIndex: React.Dispatch>; }; -export function PolygonControls({ +export function ZoneControls({ + camera, polygons, setPolygons, activePolygonIndex, setActivePolygonIndex, -}: PolygonCanvasProps) { +}: ZoneControlsProps) { const { data: config } = useSWR("config"); const [zoneName, setZoneName] = useState(); const [invalidName, setInvalidName] = useState(); @@ -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({ > New Zone + + Enter a label for your zone. Do not include spaces, and don't use + the name of a camera. + <> { 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 && (
- Zone names must not be the name of a camera. + Zone name is not valid.
)} @@ -137,4 +258,4 @@ export function PolygonControls({ ); } -export default PolygonControls; +export default ZoneControls; diff --git a/web/src/components/settings/Zones.tsx b/web/src/components/settings/Zones.tsx index 15ad7a29b..e9fc89b7d 100644 --- a/web/src/components/settings/Zones.tsx +++ b/web/src/components/settings/Zones.tsx @@ -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(() => { + if (!cameras) { + return []; + } + + const labels = new Set(); + + 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() { > + console.log(objects)} + /> ))} @@ -242,10 +266,8 @@ export default function SettingsZones() { container width: {containerWidth}, container height: {containerHeight} -