From 5be3cf81ea377d8bdb33e5adef39f48c7e6bcaa1 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:12:41 -0500 Subject: [PATCH] polygon item component, switch color, object form, hover cards --- web/src/components/settings/MasksAndZones.tsx | 362 +++++++----------- .../settings/MotionMaskEditPane.tsx | 2 +- .../settings/ObjectMaskEditPane.tsx | 86 +++-- web/src/components/settings/PolygonItem.tsx | 200 ++++++++++ web/src/components/settings/ZoneEditPane.tsx | 8 +- web/src/components/ui/switch.tsx | 1 + web/src/pages/Settings.tsx | 5 +- web/src/pages/SubmitPlus.tsx | 4 +- web/src/types/frigateConfig.ts | 8 +- web/tailwind.config.js | 1 + web/themes/theme-default.css | 10 +- 11 files changed, 405 insertions(+), 282 deletions(-) create mode 100644 web/src/components/settings/PolygonItem.tsx diff --git a/web/src/components/settings/MasksAndZones.tsx b/web/src/components/settings/MasksAndZones.tsx index 3dee2287d..be33560e4 100644 --- a/web/src/components/settings/MasksAndZones.tsx +++ b/web/src/components/settings/MasksAndZones.tsx @@ -4,33 +4,26 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PolygonCanvas } from "./PolygonCanvas"; import { Polygon, PolygonType } from "@/types/canvas"; -import { interpolatePoints, toRGBColorString } from "@/utils/canvasUtil"; -import { isMobile } from "react-device-detect"; +import { interpolatePoints } from "@/utils/canvasUtil"; import { Skeleton } from "../ui/skeleton"; import { useResizeObserver } from "@/hooks/resize-observer"; -import { LuCopy, LuPencil, LuPlus } from "react-icons/lu"; -import { FaDrawPolygon, FaObjectGroup } from "react-icons/fa"; -import { BsPersonBoundingBox } from "react-icons/bs"; -import { HiTrash } from "react-icons/hi"; +import { LuExternalLink, LuInfo, LuPlus } from "react-icons/lu"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hover-card"; import copy from "copy-to-clipboard"; import { toast } from "sonner"; import { Toaster } from "../ui/sonner"; import { Button } from "../ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "../ui/alert-dialog"; import Heading from "../ui/heading"; import ZoneEditPane from "./ZoneEditPane"; import MotionMaskEditPane from "./MotionMaskEditPane"; import ObjectMaskEditPane from "./ObjectMaskEditPane"; +import PolygonItem from "./PolygonItem"; +import { Link } from "react-router-dom"; const parseCoordinates = (coordinatesString: string) => { const coordinates = coordinatesString.split(","); @@ -45,25 +38,11 @@ const parseCoordinates = (coordinatesString: string) => { return points; }; -type PolygonItemProps = { - polygon: Polygon; - setAllPolygons: React.Dispatch>; - index: number; - activePolygonIndex: number | undefined; - hoveredPolygonIndex: number | null; - setHoveredPolygonIndex: (index: number | null) => void; - deleteDialogOpen: boolean; - setDeleteDialogOpen: (open: boolean) => void; - setActivePolygonIndex: (index: number | undefined) => void; - setEditPane: (type: PolygonType) => void; - handleCopyCoordinates: (index: number) => void; -}; - -export type ZoneObjects = { - camera: string; - zoneName: string; - objects: string[]; -}; +// export type ZoneObjects = { +// camera: string; +// zoneName: string; +// objects: string[]; +// }; type MasksAndZoneProps = { selectedCamera: string; @@ -85,14 +64,14 @@ export default function MasksAndZones({ const { data: config } = useSWR("config"); const [allPolygons, setAllPolygons] = useState([]); const [editingPolygons, setEditingPolygons] = useState([]); - const [zoneObjects, setZoneObjects] = useState([]); + // const [zoneObjects, setZoneObjects] = useState([]); const [activePolygonIndex, setActivePolygonIndex] = useState< number | undefined >(undefined); const [hoveredPolygonIndex, setHoveredPolygonIndex] = useState( null, ); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const containerRef = useRef(null); // const polygonTypes = [ // "zone", @@ -104,15 +83,15 @@ export default function MasksAndZones({ // type EditPaneType = (typeof polygonTypes)[number]; const [editPane, setEditPane] = useState(undefined); - const cameras = useMemo(() => { - if (!config) { - return []; - } + // const cameras = useMemo(() => { + // if (!config) { + // return []; + // } - return Object.values(config.cameras) - .filter((conf) => conf.ui.dashboard && conf.enabled) - .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order); - }, [config]); + // return Object.values(config.cameras) + // .filter((conf) => conf.ui.dashboard && conf.enabled) + // .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order); + // }, [config]); const cameraConfig = useMemo(() => { if (config && selectedCamera) { @@ -147,23 +126,23 @@ export default function MasksAndZones({ // [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 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 [{ width: containerWidth, height: containerHeight }] = useResizeObserver(containerRef); @@ -200,6 +179,7 @@ export default function MasksAndZones({ }, [config, selectedCamera]); const stretch = true; + // TODO: mobile / portrait cams const fitAspect = 16 / 9; const scaledHeight = useMemo(() => { @@ -272,18 +252,18 @@ export default function MasksAndZones({ }; const handleCancel = useCallback(() => { - console.log("handling cancel"); + // console.log("handling cancel"); setEditPane(undefined); - console.log("all", allPolygons); - console.log("editing", editingPolygons); + // console.log("all", allPolygons); + // console.log("editing", editingPolygons); // setAllPolygons(allPolygons.filter((poly) => !poly.isUnsaved)); setEditingPolygons([...allPolygons]); setActivePolygonIndex(undefined); setHoveredPolygonIndex(null); - }, [allPolygons, editingPolygons]); + }, [allPolygons]); const handleSave = useCallback(() => { - console.log("handling save"); + // console.log("handling save"); setAllPolygons([...(editingPolygons ?? [])]); setActivePolygonIndex(undefined); setEditPane(undefined); @@ -398,7 +378,7 @@ export default function MasksAndZones({ : [], ); - console.log("setting all and editing"); + // console.log("setting all and editing"); setAllPolygons([ ...zones, ...motionMasks, @@ -428,7 +408,7 @@ export default function MasksAndZones({ if (editPane === undefined) { setEditingPolygons([...allPolygons]); setIsEditing(false); - console.log("edit pane undefined, all", allPolygons); + // console.log("edit pane undefined, all", allPolygons); } else { setIsEditing(true); } @@ -463,7 +443,7 @@ export default function MasksAndZones({ {cameraConfig && editingPolygons && (
-
+
{editPane == "zone" && ( {(selectedZoneMask === undefined || selectedZoneMask.includes("zone" as PolygonType)) && ( -
+
-
Zones
+ + +
Zones
+
+ +
+

+ Zones allow you to define a specific area of the + frame so you can determine whether or not an + object is within a particular area. +

+
+ + Documentation{" "} + + +
+
+
+