mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 13:45:25 +03:00
filtering
This commit is contained in:
parent
52af3cef9b
commit
72e7e67b29
130
web/src/components/filter/ZoneMaskFilter.tsx
Normal file
130
web/src/components/filter/ZoneMaskFilter.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { FaFilter } from "react-icons/fa";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
|
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
import { PolygonType } from "@/types/canvas";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
||||||
|
|
||||||
|
type ZoneMaskFilterButtonProps = {
|
||||||
|
selectedZoneMask?: PolygonType[];
|
||||||
|
updateZoneMaskFilter: (labels: PolygonType[] | undefined) => void;
|
||||||
|
};
|
||||||
|
export function ZoneMaskFilterButton({
|
||||||
|
selectedZoneMask,
|
||||||
|
updateZoneMaskFilter,
|
||||||
|
}: ZoneMaskFilterButtonProps) {
|
||||||
|
const trigger = (
|
||||||
|
<Button size="sm" className="flex items-center gap-2">
|
||||||
|
<FaFilter className="text-secondary-foreground" />
|
||||||
|
<div className="hidden md:block text-primary">Filter</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
const content = (
|
||||||
|
<GeneralFilterContent
|
||||||
|
selectedZoneMask={selectedZoneMask}
|
||||||
|
updateZoneMaskFilter={updateZoneMaskFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger asChild>{trigger}</DrawerTrigger>
|
||||||
|
<DrawerContent className="max-h-[75dvh] p-3 mx-1 overflow-hidden">
|
||||||
|
{content}
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
||||||
|
<PopoverContent>{content}</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeneralFilterContentProps = {
|
||||||
|
selectedZoneMask: PolygonType[] | undefined;
|
||||||
|
updateZoneMaskFilter: (labels: PolygonType[] | undefined) => void;
|
||||||
|
};
|
||||||
|
export function GeneralFilterContent({
|
||||||
|
selectedZoneMask,
|
||||||
|
updateZoneMaskFilter,
|
||||||
|
}: GeneralFilterContentProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="h-auto overflow-y-auto overflow-x-hidden">
|
||||||
|
<div className="flex justify-between items-center my-2.5">
|
||||||
|
<Label
|
||||||
|
className="mx-2 text-primary cursor-pointer"
|
||||||
|
htmlFor="allLabels"
|
||||||
|
>
|
||||||
|
All Masks and Zones
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
className="ml-1"
|
||||||
|
id="allLabels"
|
||||||
|
checked={selectedZoneMask == undefined}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
if (isChecked) {
|
||||||
|
updateZoneMaskFilter(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="my-2.5 flex flex-col gap-2.5">
|
||||||
|
{["zone", "motion_mask", "object_mask"].map((item) => (
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label
|
||||||
|
className="w-full mx-2 text-primary capitalize cursor-pointer"
|
||||||
|
htmlFor={item}
|
||||||
|
>
|
||||||
|
{item
|
||||||
|
.replace(/_/g, " ")
|
||||||
|
.replace(/\b\w/g, (char) => char.toUpperCase()) + "s"}
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
key={item}
|
||||||
|
className="ml-1"
|
||||||
|
id={item}
|
||||||
|
checked={
|
||||||
|
selectedZoneMask?.includes(item as PolygonType) ?? false
|
||||||
|
}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
if (isChecked) {
|
||||||
|
const updatedLabels = selectedZoneMask
|
||||||
|
? [...selectedZoneMask]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
updatedLabels.push(item as PolygonType);
|
||||||
|
updateZoneMaskFilter(updatedLabels);
|
||||||
|
} else {
|
||||||
|
const updatedLabels = selectedZoneMask
|
||||||
|
? [...selectedZoneMask]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// can not deselect the last item
|
||||||
|
if (updatedLabels.length > 1) {
|
||||||
|
updatedLabels.splice(
|
||||||
|
updatedLabels.indexOf(item as PolygonType),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
updateZoneMaskFilter(updatedLabels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -180,9 +180,13 @@ export type ZoneObjects = {
|
|||||||
|
|
||||||
type MasksAndZoneProps = {
|
type MasksAndZoneProps = {
|
||||||
selectedCamera: string;
|
selectedCamera: string;
|
||||||
|
selectedZoneMask: PolygonType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
export default function MasksAndZones({
|
||||||
|
selectedCamera,
|
||||||
|
selectedZoneMask,
|
||||||
|
}: MasksAndZoneProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
||||||
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>();
|
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>();
|
||||||
@ -266,23 +270,6 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
[setZoneObjects],
|
[setZoneObjects],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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;
|
|
||||||
// },
|
|
||||||
// [config],
|
|
||||||
// );
|
|
||||||
|
|
||||||
const [{ width: containerWidth, height: containerHeight }] =
|
const [{ width: containerWidth, height: containerHeight }] =
|
||||||
useResizeObserver(containerRef);
|
useResizeObserver(containerRef);
|
||||||
|
|
||||||
@ -322,7 +309,6 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
|
|
||||||
const scaledHeight = useMemo(() => {
|
const scaledHeight = useMemo(() => {
|
||||||
if (containerRef.current && aspectRatio && detectHeight) {
|
if (containerRef.current && aspectRatio && detectHeight) {
|
||||||
console.log("recalc", Date.now());
|
|
||||||
const scaledHeight =
|
const scaledHeight =
|
||||||
aspectRatio < (fitAspect ?? 0)
|
aspectRatio < (fitAspect ?? 0)
|
||||||
? Math.floor(
|
? Math.floor(
|
||||||
@ -405,9 +391,9 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cameraConfig && containerRef.current && scaledWidth) {
|
if (cameraConfig && containerRef.current && scaledWidth) {
|
||||||
setAllPolygons([
|
const zones = Object.entries(cameraConfig.zones).map(
|
||||||
...Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({
|
([name, zoneData]) => ({
|
||||||
type: "zone" as PolygonType, // Add the type property here
|
type: "zone" as PolygonType,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name,
|
name,
|
||||||
points: interpolatePoints(
|
points: interpolatePoints(
|
||||||
@ -419,11 +405,14 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
),
|
),
|
||||||
isFinished: true,
|
isFinished: true,
|
||||||
color: zoneData.color,
|
color: zoneData.color,
|
||||||
})),
|
}),
|
||||||
...Object.entries(cameraConfig.motion.mask).map(([, maskData]) => ({
|
);
|
||||||
|
|
||||||
|
const motionMasks = Object.entries(cameraConfig.motion.mask).map(
|
||||||
|
([, maskData], index) => ({
|
||||||
type: "motion_mask" as PolygonType,
|
type: "motion_mask" as PolygonType,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name: "motion_mask",
|
name: `Motion Mask ${index + 1}`,
|
||||||
points: interpolatePoints(
|
points: interpolatePoints(
|
||||||
parseCoordinates(maskData),
|
parseCoordinates(maskData),
|
||||||
1,
|
1,
|
||||||
@ -433,17 +422,38 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
),
|
),
|
||||||
isFinished: true,
|
isFinished: true,
|
||||||
color: [0, 0, 255],
|
color: [0, 0, 255],
|
||||||
})),
|
}),
|
||||||
...Object.entries(cameraConfig.objects.filters).flatMap(
|
);
|
||||||
|
|
||||||
|
const globalObjectMasks = Object.entries(cameraConfig.objects.mask).map(
|
||||||
|
([, maskData], index) => ({
|
||||||
|
type: "object_mask" as PolygonType,
|
||||||
|
camera: cameraConfig.name,
|
||||||
|
name: `All Objects Object Mask ${index + 1}`,
|
||||||
|
points: interpolatePoints(
|
||||||
|
parseCoordinates(maskData),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
scaledWidth,
|
||||||
|
scaledHeight,
|
||||||
|
),
|
||||||
|
isFinished: true,
|
||||||
|
color: [0, 0, 255],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const globalObjectMasksCount = globalObjectMasks.length;
|
||||||
|
|
||||||
|
const objectMasks = Object.entries(cameraConfig.objects.filters).flatMap(
|
||||||
([objectName, { mask }]): Polygon[] =>
|
([objectName, { mask }]): Polygon[] =>
|
||||||
mask !== null && mask !== undefined
|
mask !== null && mask !== undefined
|
||||||
? mask.flatMap((maskItem) =>
|
? mask.flatMap((maskItem, subIndex) =>
|
||||||
maskItem !== null && maskItem !== undefined
|
maskItem !== null && maskItem !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
type: "object_mask" as PolygonType,
|
type: "object_mask" as PolygonType,
|
||||||
camera: cameraConfig.name,
|
camera: cameraConfig.name,
|
||||||
name: objectName,
|
name: `${objectName.charAt(0).toUpperCase() + objectName.slice(1)} Object Mask ${globalObjectMasksCount + subIndex + 1}`,
|
||||||
points: interpolatePoints(
|
points: interpolatePoints(
|
||||||
parseCoordinates(maskItem),
|
parseCoordinates(maskItem),
|
||||||
1,
|
1,
|
||||||
@ -458,7 +468,13 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
: [],
|
: [],
|
||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
),
|
);
|
||||||
|
|
||||||
|
setAllPolygons([
|
||||||
|
...zones,
|
||||||
|
...motionMasks,
|
||||||
|
...globalObjectMasks,
|
||||||
|
...objectMasks,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setZoneObjects(
|
setZoneObjects(
|
||||||
@ -502,12 +518,14 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log(selectedZoneMask);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{cameraConfig && allPolygons && (
|
{cameraConfig && allPolygons && (
|
||||||
<div className="flex flex-col md:flex-row size-full">
|
<div className="flex flex-col md:flex-row size-full">
|
||||||
<Toaster position="top-center" />
|
<Toaster position="top-center" />
|
||||||
<div className="flex flex-col order-last w-full overflow-y-auto md:w-3/12 md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt">
|
<div className="flex flex-col w-full overflow-y-auto md:w-3/12 order-last md:order-none md:mr-2 rounded-lg border-secondary-foreground border-[1px] p-2 bg-background_alt">
|
||||||
{editPane == "zone" && (
|
{editPane == "zone" && (
|
||||||
<ZoneEditPane
|
<ZoneEditPane
|
||||||
polygons={allPolygons}
|
polygons={allPolygons}
|
||||||
@ -530,7 +548,10 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{editPane == undefined && (
|
{editPane === undefined && (
|
||||||
|
<>
|
||||||
|
{(selectedZoneMask === undefined ||
|
||||||
|
selectedZoneMask.includes("zone" as PolygonType)) && (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row justify-between items-center mb-3">
|
<div className="flex flex-row justify-between items-center mb-3">
|
||||||
<div className="text-md">Zones</div>
|
<div className="text-md">Zones</div>
|
||||||
@ -565,6 +586,11 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
handleCopyCoordinates={handleCopyCoordinates}
|
handleCopyCoordinates={handleCopyCoordinates}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(selectedZoneMask === undefined ||
|
||||||
|
selectedZoneMask.includes("motion_mask" as PolygonType)) && (
|
||||||
|
<>
|
||||||
<div className="flex flex-row justify-between items-center my-3">
|
<div className="flex flex-row justify-between items-center my-3">
|
||||||
<div className="text-md">Motion Masks</div>
|
<div className="text-md">Motion Masks</div>
|
||||||
<Button
|
<Button
|
||||||
@ -580,7 +606,9 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
</div>
|
</div>
|
||||||
{allPolygons
|
{allPolygons
|
||||||
.flatMap((polygon, index) =>
|
.flatMap((polygon, index) =>
|
||||||
polygon.type === "motion_mask" ? [{ polygon, index }] : [],
|
polygon.type === "motion_mask"
|
||||||
|
? [{ polygon, index }]
|
||||||
|
: [],
|
||||||
)
|
)
|
||||||
.map(({ polygon, index }) => (
|
.map(({ polygon, index }) => (
|
||||||
<PolygonItem
|
<PolygonItem
|
||||||
@ -598,6 +626,11 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
handleCopyCoordinates={handleCopyCoordinates}
|
handleCopyCoordinates={handleCopyCoordinates}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(selectedZoneMask === undefined ||
|
||||||
|
selectedZoneMask.includes("object_mask" as PolygonType)) && (
|
||||||
|
<>
|
||||||
<div className="flex flex-row justify-between items-center my-3">
|
<div className="flex flex-row justify-between items-center my-3">
|
||||||
<div className="text-md">Object Masks</div>
|
<div className="text-md">Object Masks</div>
|
||||||
<Button
|
<Button
|
||||||
@ -613,7 +646,9 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
</div>
|
</div>
|
||||||
{allPolygons
|
{allPolygons
|
||||||
.flatMap((polygon, index) =>
|
.flatMap((polygon, index) =>
|
||||||
polygon.type === "object_mask" ? [{ polygon, index }] : [],
|
polygon.type === "object_mask"
|
||||||
|
? [{ polygon, index }]
|
||||||
|
: [],
|
||||||
)
|
)
|
||||||
.map(({ polygon, index }) => (
|
.map(({ polygon, index }) => (
|
||||||
<PolygonItem
|
<PolygonItem
|
||||||
@ -633,6 +668,8 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{/* <Table>
|
{/* <Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@ -728,6 +765,7 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
|||||||
setPolygons={setEditingPolygons}
|
setPolygons={setEditingPolygons}
|
||||||
activePolygonIndex={activePolygonIndex}
|
activePolygonIndex={activePolygonIndex}
|
||||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||||
|
selectedZoneMask={selectedZoneMask}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton className="w-full h-full" />
|
<Skeleton className="w-full h-full" />
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import PolygonDrawer from "./PolygonDrawer";
|
|||||||
import { Stage, Layer, Image, Text } from "react-konva";
|
import { Stage, Layer, Image, Text } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { KonvaEventObject } from "konva/lib/Node";
|
import type { KonvaEventObject } from "konva/lib/Node";
|
||||||
import { Polygon } from "@/types/canvas";
|
import { Polygon, PolygonType } from "@/types/canvas";
|
||||||
import { useApiHost } from "@/api";
|
import { useApiHost } from "@/api";
|
||||||
import { getAveragePoint } from "@/utils/canvasUtil";
|
import { getAveragePoint } from "@/utils/canvasUtil";
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ type PolygonCanvasProps = {
|
|||||||
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||||
activePolygonIndex: number | undefined;
|
activePolygonIndex: number | undefined;
|
||||||
hoveredPolygonIndex: number | null;
|
hoveredPolygonIndex: number | null;
|
||||||
|
selectedZoneMask: PolygonType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PolygonCanvas({
|
export function PolygonCanvas({
|
||||||
@ -27,6 +28,7 @@ export function PolygonCanvas({
|
|||||||
setPolygons,
|
setPolygons,
|
||||||
activePolygonIndex,
|
activePolygonIndex,
|
||||||
hoveredPolygonIndex,
|
hoveredPolygonIndex,
|
||||||
|
selectedZoneMask,
|
||||||
}: PolygonCanvasProps) {
|
}: PolygonCanvasProps) {
|
||||||
const [image, setImage] = useState<HTMLImageElement | undefined>();
|
const [image, setImage] = useState<HTMLImageElement | undefined>();
|
||||||
const imageRef = useRef<Konva.Image | null>(null);
|
const imageRef = useRef<Konva.Image | null>(null);
|
||||||
@ -205,7 +207,10 @@ export function PolygonCanvas({
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
{polygons?.map((polygon, index) => (
|
{polygons?.map(
|
||||||
|
(polygon, index) =>
|
||||||
|
(selectedZoneMask === undefined ||
|
||||||
|
selectedZoneMask.includes(polygon.type)) && (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<PolygonDrawer
|
<PolygonDrawer
|
||||||
key={index}
|
key={index}
|
||||||
@ -234,7 +239,8 @@ export function PolygonCanvas({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
),
|
||||||
|
)}
|
||||||
</Layer>
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import General from "@/components/settings/General";
|
import General from "@/components/settings/General";
|
||||||
import FilterCheckBox from "@/components/filter/FilterCheckBox";
|
import FilterCheckBox from "@/components/filter/FilterCheckBox";
|
||||||
|
import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter";
|
||||||
|
import { PolygonType } from "@/types/canvas";
|
||||||
|
|
||||||
type CameraSelectButtonProps = {
|
type CameraSelectButtonProps = {
|
||||||
allCameras: CameraConfig[];
|
allCameras: CameraConfig[];
|
||||||
@ -136,6 +138,8 @@ export default function Settings() {
|
|||||||
|
|
||||||
const [selectedCamera, setSelectedCamera] = useState(cameras[0].name);
|
const [selectedCamera, setSelectedCamera] = useState(cameras[0].name);
|
||||||
|
|
||||||
|
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-full p-2 flex flex-col">
|
<div className="size-full p-2 flex flex-col">
|
||||||
<div className="w-full h-11 relative flex justify-between items-center">
|
<div className="w-full h-11 relative flex justify-between items-center">
|
||||||
@ -168,6 +172,10 @@ export default function Settings() {
|
|||||||
page == "masks / zones" ||
|
page == "masks / zones" ||
|
||||||
page == "motion tuner") && (
|
page == "motion tuner") && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<ZoneMaskFilterButton
|
||||||
|
selectedZoneMask={filterZoneMask}
|
||||||
|
updateZoneMaskFilter={setFilterZoneMask}
|
||||||
|
/>
|
||||||
<CameraSelectButton
|
<CameraSelectButton
|
||||||
allCameras={cameras}
|
allCameras={cameras}
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
@ -182,7 +190,7 @@ export default function Settings() {
|
|||||||
{page == "masks / zones" && (
|
{page == "masks / zones" && (
|
||||||
<MasksAndZones
|
<MasksAndZones
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
setSelectedCamera={setSelectedCamera}
|
selectedZoneMask={filterZoneMask}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{page == "motion tuner" && <MotionTuner />}
|
{page == "motion tuner" && <MotionTuner />}
|
||||||
|
|||||||
@ -340,7 +340,7 @@ export interface FrigateConfig {
|
|||||||
threshold: number;
|
threshold: number;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
mask: string;
|
mask: string[];
|
||||||
track: string[];
|
track: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user