From bd64e6f873286ec593a598b66ea4fad5dc6aa319 Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Thu, 11 Apr 2024 15:48:35 -0500
Subject: [PATCH] merge dev
---
web/package-lock.json | 29 ++-
web/package.json | 1 +
.../{settings => menu}/AccountSettings.tsx | 0
.../{settings => menu}/GeneralSettings.tsx | 0
web/src/components/navigation/Bottombar.tsx | 4 +-
web/src/components/navigation/Sidebar.tsx | 4 +-
web/src/components/settings/General.tsx | 40 +++
.../settings/{Zones.tsx => MasksAndZones.tsx} | 229 +++++++++++------
.../{ZoneControls.tsx => NewZoneButton.tsx} | 132 +++++-----
web/src/components/settings/PolygonDrawer.tsx | 8 +-
web/src/components/ui/separator.tsx | 29 +++
web/src/pages/Settings.tsx | 238 +++++++++++++-----
web/src/utils/canvasUtil.ts | 8 +
13 files changed, 511 insertions(+), 211 deletions(-)
rename web/src/components/{settings => menu}/AccountSettings.tsx (100%)
rename web/src/components/{settings => menu}/GeneralSettings.tsx (100%)
create mode 100644 web/src/components/settings/General.tsx
rename web/src/components/settings/{Zones.tsx => MasksAndZones.tsx} (66%)
rename web/src/components/settings/{ZoneControls.tsx => NewZoneButton.tsx} (68%)
create mode 100644 web/src/components/ui/separator.tsx
diff --git a/web/package-lock.json b/web/package-lock.json
index a09f066ea..ab9283144 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -21,6 +21,7 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
@@ -1650,6 +1651,29 @@
}
}
},
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz",
+ "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/react-primitive": "1.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slider": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
@@ -2568,11 +2592,6 @@
"@types/react": "*"
}
},
- "node_modules/@types/scheduler": {
- "version": "0.16.8",
- "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
- "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A=="
- },
"node_modules/@types/semver": {
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz",
diff --git a/web/package.json b/web/package.json
index c39567e4c..a2144bdce 100644
--- a/web/package.json
+++ b/web/package.json
@@ -26,6 +26,7 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
+ "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
diff --git a/web/src/components/settings/AccountSettings.tsx b/web/src/components/menu/AccountSettings.tsx
similarity index 100%
rename from web/src/components/settings/AccountSettings.tsx
rename to web/src/components/menu/AccountSettings.tsx
diff --git a/web/src/components/settings/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx
similarity index 100%
rename from web/src/components/settings/GeneralSettings.tsx
rename to web/src/components/menu/GeneralSettings.tsx
diff --git a/web/src/components/navigation/Bottombar.tsx b/web/src/components/navigation/Bottombar.tsx
index 2ef8c2e8d..e21556aaa 100644
--- a/web/src/components/navigation/Bottombar.tsx
+++ b/web/src/components/navigation/Bottombar.tsx
@@ -6,8 +6,8 @@ import { FrigateStats } from "@/types/stats";
import { useFrigateStats } from "@/api/ws";
import { useMemo } from "react";
import useStats from "@/hooks/use-stats";
-import GeneralSettings from "../settings/GeneralSettings";
-import AccountSettings from "../settings/AccountSettings";
+import GeneralSettings from "../menu/GeneralSettings";
+import AccountSettings from "../menu/AccountSettings";
import useNavigation from "@/hooks/use-navigation";
function Bottombar() {
diff --git a/web/src/components/navigation/Sidebar.tsx b/web/src/components/navigation/Sidebar.tsx
index e4b4d9c81..ea895a12f 100644
--- a/web/src/components/navigation/Sidebar.tsx
+++ b/web/src/components/navigation/Sidebar.tsx
@@ -2,8 +2,8 @@ import Logo from "../Logo";
import NavItem from "./NavItem";
import { CameraGroupSelector } from "../filter/CameraGroupSelector";
import { useLocation } from "react-router-dom";
-import GeneralSettings from "../settings/GeneralSettings";
-import AccountSettings from "../settings/AccountSettings";
+import GeneralSettings from "../menu/GeneralSettings";
+import AccountSettings from "../menu/AccountSettings";
import useNavigation from "@/hooks/use-navigation";
function Sidebar() {
diff --git a/web/src/components/settings/General.tsx b/web/src/components/settings/General.tsx
new file mode 100644
index 000000000..4d8465299
--- /dev/null
+++ b/web/src/components/settings/General.tsx
@@ -0,0 +1,40 @@
+import Heading from "@/components/ui/heading";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Switch } from "@/components/ui/switch";
+
+export default function General() {
+ return (
+ <>
+ Settings
+
+ {}} />
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/components/settings/Zones.tsx b/web/src/components/settings/MasksAndZones.tsx
similarity index 66%
rename from web/src/components/settings/Zones.tsx
rename to web/src/components/settings/MasksAndZones.tsx
index 955149075..e038eed01 100644
--- a/web/src/components/settings/Zones.tsx
+++ b/web/src/components/settings/MasksAndZones.tsx
@@ -1,13 +1,4 @@
-import Heading from "@/components/ui/heading";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
+import { Separator } from "@/components/ui/separator";
import {
Table,
TableBody,
@@ -23,12 +14,16 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { PolygonCanvas } from "./PolygonCanvas";
import { Polygon } from "@/types/canvas";
-import { interpolatePoints } from "@/utils/canvasUtil";
+import { interpolatePoints, toRGBColorString } from "@/utils/canvasUtil";
import { isDesktop } from "react-device-detect";
-import ZoneControls, { ZoneObjectSelector } from "./ZoneControls";
+import ZoneControls, {
+ NewZoneButton,
+ ZoneObjectSelector,
+} from "./NewZoneButton";
import { Skeleton } from "../ui/skeleton";
import { useResizeObserver } from "@/hooks/resize-observer";
-import { LuPencil } from "react-icons/lu";
+import { LuCopy, LuPencil, LuPlusSquare, LuTrash } from "react-icons/lu";
+import { FaDrawPolygon } from "react-icons/fa";
const parseCoordinates = (coordinatesString: string) => {
const coordinates = coordinatesString.split(",");
@@ -49,7 +44,15 @@ export type ZoneObjects = {
objects: string[];
};
-export default function SettingsZones() {
+type MasksAndZoneProps = {
+ selectedCamera: string;
+ setSelectedCamera: React.Dispatch>;
+};
+
+export default function MasksAndZones({
+ selectedCamera,
+ setSelectedCamera,
+}: MasksAndZoneProps) {
const { data: config } = useSWR("config");
const [zonePolygons, setZonePolygons] = useState([]);
const [zoneObjects, setZoneObjects] = useState([]);
@@ -68,8 +71,6 @@ export default function SettingsZones() {
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
}, [config]);
- const [selectedCamera, setSelectedCamera] = useState(cameras[0].name);
-
const cameraConfig = useMemo(() => {
if (config && selectedCamera) {
return config.cameras[selectedCamera];
@@ -137,7 +138,7 @@ export default function SettingsZones() {
[setZoneObjects],
);
- const grow = useMemo(() => {
+ const growe = useMemo(() => {
if (!cameraConfig) {
return;
}
@@ -157,14 +158,51 @@ export default function SettingsZones() {
}
}, [cameraConfig]);
- const handleCameraChange = useCallback(
- (camera: string) => {
- setSelectedCamera(camera);
- setActivePolygonIndex(null);
+ const getCameraAspect = useCallback(
+ (cam: string) => {
+ if (!config) {
+ return undefined;
+ }
+
+ const camera = config.cameras[cam];
+
+ if (!camera) {
+ return undefined;
+ }
+
+ return camera.detect.width / camera.detect.height;
},
- [setSelectedCamera, setActivePolygonIndex],
+ [config],
);
+ const mainCameraAspect = useMemo(() => {
+ const aspectRatio = getCameraAspect(selectedCamera);
+
+ if (!aspectRatio) {
+ return "normal";
+ } else if (aspectRatio > 2) {
+ return "wide";
+ } else if (aspectRatio < 16 / 9) {
+ return "tall";
+ } else {
+ return "normal";
+ }
+ }, [getCameraAspect, selectedCamera]);
+
+ const grow = useMemo(() => {
+ if (mainCameraAspect == "wide") {
+ return "w-full aspect-wide";
+ } else if (mainCameraAspect == "tall") {
+ if (isDesktop) {
+ return "size-full aspect-tall flex flex-col justify-center";
+ } else {
+ return "size-full";
+ }
+ } else {
+ return "w-full aspect-video";
+ }
+ }, [mainCameraAspect]);
+
const [{ width: containerWidth, height: containerHeight }] =
useResizeObserver(containerRef);
@@ -173,13 +211,16 @@ export default function SettingsZones() {
: { width: 1, height: 1 };
const aspectRatio = width / height;
- const stretch = false;
- const fitAspect = 0.75;
+ const stretch = true;
+ const fitAspect = 16 / 9;
+ // console.log(containerRef.current?.clientHeight);
const scaledHeight = useMemo(() => {
const scaledHeight =
aspectRatio < (fitAspect ?? 0)
- ? Math.floor(containerHeight)
+ ? Math.floor(
+ Math.min(containerHeight, containerRef.current?.clientHeight),
+ )
: Math.floor(containerWidth / aspectRatio);
const finalHeight = stretch ? scaledHeight : Math.min(scaledHeight, height);
@@ -244,57 +285,86 @@ export default function SettingsZones() {
console.log("component zone objects", zoneObjects);
}, [zoneObjects]);
+ useEffect(() => {
+ if (selectedCamera) {
+ setActivePolygonIndex(null);
+ }
+ }, [selectedCamera]);
+
if (!cameraConfig && !selectedCamera) {
return ;
}
return (
-
-
Zones
-
-
-
-
+ <>
{cameraConfig && (
-
-
-
- {cameraConfig ? (
-
- ) : (
-
- )}
+
+
-
-
+
+ {zonePolygons.map((polygon, index) => (
+
+
+
+ {polygon.name}
+
+
+
setActivePolygonIndex(index)}
+ >
+
+
+
+
{
+ setZonePolygons((oldPolygons) => {
+ return oldPolygons.filter((_, i) => i !== index);
+ });
+ setActivePolygonIndex(null);
+ }}
+ >
+
+
+
+
+ ))}
+ {/*
Name
@@ -372,10 +442,29 @@ export default function SettingsZones() {
0,
)}
+ */}
+
+
+
+ {cameraConfig ? (
+
+ ) : (
+
+ )}
)}
-
+ >
);
}
diff --git a/web/src/components/settings/ZoneControls.tsx b/web/src/components/settings/NewZoneButton.tsx
similarity index 68%
rename from web/src/components/settings/ZoneControls.tsx
rename to web/src/components/settings/NewZoneButton.tsx
index a8f762fec..6c4ce3625 100644
--- a/web/src/components/settings/ZoneControls.tsx
+++ b/web/src/components/settings/NewZoneButton.tsx
@@ -16,6 +16,7 @@ import { Button } from "../ui/button";
import { ATTRIBUTES, FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr";
import { isMobile } from "react-device-detect";
+import { LuPlusSquare } from "react-icons/lu";
type ZoneObjectSelectorProps = {
camera: string;
@@ -126,7 +127,7 @@ export function ZoneObjectSelector({
);
}
-type ZoneControlsProps = {
+type NewZoneButtonProps = {
camera: string;
polygons: Polygon[];
setPolygons: React.Dispatch>;
@@ -134,13 +135,13 @@ type ZoneControlsProps = {
setActivePolygonIndex: React.Dispatch>;
};
-export function ZoneControls({
+export function NewZoneButton({
camera,
polygons,
setPolygons,
activePolygonIndex,
setActivePolygonIndex,
-}: ZoneControlsProps) {
+}: NewZoneButtonProps) {
const { data: config } = useSWR("config");
const [zoneName, setZoneName] = useState();
const [invalidName, setInvalidName] = useState();
@@ -190,75 +191,76 @@ export function ZoneControls({
};
return (
-
-
-
+
+ >
+
+
);
}
-export default ZoneControls;
+export default NewZoneButton;
diff --git a/web/src/components/settings/PolygonDrawer.tsx b/web/src/components/settings/PolygonDrawer.tsx
index bb3a52a27..87adf112a 100644
--- a/web/src/components/settings/PolygonDrawer.tsx
+++ b/web/src/components/settings/PolygonDrawer.tsx
@@ -1,6 +1,6 @@
import { useCallback, useState } from "react";
import { Line, Circle, Group } from "react-konva";
-import { minMax, dragBoundFunc } from "@/utils/canvasUtil";
+import { minMax, toRGBColorString, dragBoundFunc } from "@/utils/canvasUtil";
import type { KonvaEventObject } from "konva/lib/Node";
import Konva from "konva";
import { Vector2d } from "konva/lib/types";
@@ -78,11 +78,7 @@ export default function PolygonDrawer({
const colorString = useCallback(
(darkened: boolean) => {
- if (color.length !== 3) {
- return "rgb(220,0,0,0.5)";
- }
-
- return `rgba(${color[2]},${color[1]},${color[0]},${darkened ? "0.8" : "0.5"})`;
+ return toRGBColorString(color, darkened);
},
[color],
);
diff --git a/web/src/components/ui/separator.tsx b/web/src/components/ui/separator.tsx
new file mode 100644
index 000000000..6d7f12265
--- /dev/null
+++ b/web/src/components/ui/separator.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx
index 9838cd9e5..40c1ef5cb 100644
--- a/web/src/pages/Settings.tsx
+++ b/web/src/pages/Settings.tsx
@@ -1,75 +1,191 @@
-import Heading from "@/components/ui/heading";
-import { Label } from "@/components/ui/label";
import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { Switch } from "@/components/ui/switch";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+// import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
+import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import MotionTuner from "@/components/settings/MotionTuner";
-import SettingsZones from "@/components/settings/Zones";
+import MasksAndZones from "@/components/settings/MasksAndZones";
+import { Button } from "@/components/ui/button";
+import { useMemo, useState } from "react";
+import useOptimisticState from "@/hooks/use-optimistic-state";
+import Logo from "@/components/Logo";
+import { isMobile } from "react-device-detect";
+import { FaVideo } from "react-icons/fa";
+import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
+import useSWR from "swr";
+import General from "@/components/settings/General";
+import FilterCheckBox from "@/components/filter/FilterCheckBox";
-function General() {
- return (
- <>
- Settings
-
-
{}} />
-
+type CameraSelectButtonProps = {
+ allCameras: CameraConfig[];
+ selectedCamera: string;
+ setSelectedCamera: React.Dispatch>;
+};
+
+function CameraSelectButton({
+ allCameras,
+ selectedCamera,
+ setSelectedCamera,
+}: CameraSelectButtonProps) {
+ const [open, setOpen] = useState(false);
+
+ const trigger = (
+