mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 05:35: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 = {
|
||||
selectedCamera: string;
|
||||
selectedZoneMask: PolygonType;
|
||||
};
|
||||
|
||||
export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
export default function MasksAndZones({
|
||||
selectedCamera,
|
||||
selectedZoneMask,
|
||||
}: MasksAndZoneProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
|
||||
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>();
|
||||
@ -266,23 +270,6 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
[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 }] =
|
||||
useResizeObserver(containerRef);
|
||||
|
||||
@ -322,7 +309,6 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
|
||||
const scaledHeight = useMemo(() => {
|
||||
if (containerRef.current && aspectRatio && detectHeight) {
|
||||
console.log("recalc", Date.now());
|
||||
const scaledHeight =
|
||||
aspectRatio < (fitAspect ?? 0)
|
||||
? Math.floor(
|
||||
@ -405,9 +391,9 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (cameraConfig && containerRef.current && scaledWidth) {
|
||||
setAllPolygons([
|
||||
...Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({
|
||||
type: "zone" as PolygonType, // Add the type property here
|
||||
const zones = Object.entries(cameraConfig.zones).map(
|
||||
([name, zoneData]) => ({
|
||||
type: "zone" as PolygonType,
|
||||
camera: cameraConfig.name,
|
||||
name,
|
||||
points: interpolatePoints(
|
||||
@ -419,11 +405,14 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
),
|
||||
isFinished: true,
|
||||
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,
|
||||
camera: cameraConfig.name,
|
||||
name: "motion_mask",
|
||||
name: `Motion Mask ${index + 1}`,
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskData),
|
||||
1,
|
||||
@ -433,32 +422,59 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
),
|
||||
isFinished: true,
|
||||
color: [0, 0, 255],
|
||||
})),
|
||||
...Object.entries(cameraConfig.objects.filters).flatMap(
|
||||
([objectName, { mask }]): Polygon[] =>
|
||||
mask !== null && mask !== undefined
|
||||
? mask.flatMap((maskItem) =>
|
||||
maskItem !== null && maskItem !== undefined
|
||||
? [
|
||||
{
|
||||
type: "object_mask" as PolygonType,
|
||||
camera: cameraConfig.name,
|
||||
name: objectName,
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskItem),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
color: [128, 128, 128],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
: [],
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
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[] =>
|
||||
mask !== null && mask !== undefined
|
||||
? mask.flatMap((maskItem, subIndex) =>
|
||||
maskItem !== null && maskItem !== undefined
|
||||
? [
|
||||
{
|
||||
type: "object_mask" as PolygonType,
|
||||
camera: cameraConfig.name,
|
||||
name: `${objectName.charAt(0).toUpperCase() + objectName.slice(1)} Object Mask ${globalObjectMasksCount + subIndex + 1}`,
|
||||
points: interpolatePoints(
|
||||
parseCoordinates(maskItem),
|
||||
1,
|
||||
1,
|
||||
scaledWidth,
|
||||
scaledHeight,
|
||||
),
|
||||
isFinished: true,
|
||||
color: [128, 128, 128],
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
: [],
|
||||
);
|
||||
|
||||
setAllPolygons([
|
||||
...zones,
|
||||
...motionMasks,
|
||||
...globalObjectMasks,
|
||||
...objectMasks,
|
||||
]);
|
||||
|
||||
setZoneObjects(
|
||||
@ -502,12 +518,14 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
// console.log(selectedZoneMask);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cameraConfig && allPolygons && (
|
||||
<div className="flex flex-col md:flex-row size-full">
|
||||
<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" && (
|
||||
<ZoneEditPane
|
||||
polygons={allPolygons}
|
||||
@ -530,107 +548,126 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{editPane == undefined && (
|
||||
{editPane === undefined && (
|
||||
<>
|
||||
<div className="flex flex-row justify-between items-center mb-3">
|
||||
<div className="text-md">Zones</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("zone");
|
||||
handleNewPolygon("zone");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "zone" ? [{ polygon, index }] : [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
<div className="flex flex-row justify-between items-center my-3">
|
||||
<div className="text-md">Motion Masks</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("motion_mask");
|
||||
handleNewPolygon("motion_mask");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "motion_mask" ? [{ polygon, index }] : [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
<div className="flex flex-row justify-between items-center my-3">
|
||||
<div className="text-md">Object Masks</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("motion_mask");
|
||||
handleNewPolygon("motion_mask");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "object_mask" ? [{ polygon, index }] : [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
{(selectedZoneMask === undefined ||
|
||||
selectedZoneMask.includes("zone" as PolygonType)) && (
|
||||
<>
|
||||
<div className="flex flex-row justify-between items-center mb-3">
|
||||
<div className="text-md">Zones</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("zone");
|
||||
handleNewPolygon("zone");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "zone" ? [{ polygon, index }] : [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{(selectedZoneMask === undefined ||
|
||||
selectedZoneMask.includes("motion_mask" as PolygonType)) && (
|
||||
<>
|
||||
<div className="flex flex-row justify-between items-center my-3">
|
||||
<div className="text-md">Motion Masks</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("motion_mask");
|
||||
handleNewPolygon("motion_mask");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "motion_mask"
|
||||
? [{ polygon, index }]
|
||||
: [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{(selectedZoneMask === undefined ||
|
||||
selectedZoneMask.includes("object_mask" as PolygonType)) && (
|
||||
<>
|
||||
<div className="flex flex-row justify-between items-center my-3">
|
||||
<div className="text-md">Object Masks</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 px-0"
|
||||
onClick={() => {
|
||||
setEditPane("motion_mask");
|
||||
handleNewPolygon("motion_mask");
|
||||
}}
|
||||
>
|
||||
<LuPlusSquare />
|
||||
</Button>
|
||||
</div>
|
||||
{allPolygons
|
||||
.flatMap((polygon, index) =>
|
||||
polygon.type === "object_mask"
|
||||
? [{ polygon, index }]
|
||||
: [],
|
||||
)
|
||||
.map(({ polygon, index }) => (
|
||||
<PolygonItem
|
||||
key={index}
|
||||
polygon={polygon}
|
||||
index={index}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
setHoveredPolygonIndex={setHoveredPolygonIndex}
|
||||
deleteDialogOpen={deleteDialogOpen}
|
||||
setDeleteDialogOpen={setDeleteDialogOpen}
|
||||
setActivePolygonIndex={setActivePolygonIndex}
|
||||
setEditPane={setEditPane}
|
||||
setAllPolygons={setAllPolygons}
|
||||
handleCopyCoordinates={handleCopyCoordinates}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* <Table>
|
||||
@ -728,6 +765,7 @@ export default function MasksAndZones({ selectedCamera }: MasksAndZoneProps) {
|
||||
setPolygons={setEditingPolygons}
|
||||
activePolygonIndex={activePolygonIndex}
|
||||
hoveredPolygonIndex={hoveredPolygonIndex}
|
||||
selectedZoneMask={selectedZoneMask}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="w-full h-full" />
|
||||
|
||||
@ -3,7 +3,7 @@ import PolygonDrawer from "./PolygonDrawer";
|
||||
import { Stage, Layer, Image, Text } from "react-konva";
|
||||
import Konva from "konva";
|
||||
import type { KonvaEventObject } from "konva/lib/Node";
|
||||
import { Polygon } from "@/types/canvas";
|
||||
import { Polygon, PolygonType } from "@/types/canvas";
|
||||
import { useApiHost } from "@/api";
|
||||
import { getAveragePoint } from "@/utils/canvasUtil";
|
||||
|
||||
@ -16,6 +16,7 @@ type PolygonCanvasProps = {
|
||||
setPolygons: React.Dispatch<React.SetStateAction<Polygon[]>>;
|
||||
activePolygonIndex: number | undefined;
|
||||
hoveredPolygonIndex: number | null;
|
||||
selectedZoneMask: PolygonType;
|
||||
};
|
||||
|
||||
export function PolygonCanvas({
|
||||
@ -27,6 +28,7 @@ export function PolygonCanvas({
|
||||
setPolygons,
|
||||
activePolygonIndex,
|
||||
hoveredPolygonIndex,
|
||||
selectedZoneMask,
|
||||
}: PolygonCanvasProps) {
|
||||
const [image, setImage] = useState<HTMLImageElement | undefined>();
|
||||
const imageRef = useRef<Konva.Image | null>(null);
|
||||
@ -205,36 +207,40 @@ export function PolygonCanvas({
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
{polygons?.map((polygon, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<PolygonDrawer
|
||||
key={index}
|
||||
points={polygon.points}
|
||||
flattenedPoints={flattenPoints(polygon.points)}
|
||||
isActive={index === activePolygonIndex}
|
||||
isHovered={index === hoveredPolygonIndex}
|
||||
isFinished={polygon.isFinished}
|
||||
color={polygon.color}
|
||||
handlePointDragMove={handlePointDragMove}
|
||||
handleGroupDragEnd={handleGroupDragEnd}
|
||||
handleMouseOverStartPoint={handleMouseOverStartPoint}
|
||||
handleMouseOutStartPoint={handleMouseOutStartPoint}
|
||||
/>
|
||||
{index === hoveredPolygonIndex && (
|
||||
<Text
|
||||
text={polygon.name}
|
||||
align="left"
|
||||
verticalAlign="top"
|
||||
x={
|
||||
getAveragePoint(flattenPoints(polygon.points)).x
|
||||
// - (polygon.name.length * 16 * 0.6) / 2
|
||||
}
|
||||
y={getAveragePoint(flattenPoints(polygon.points)).y} //- 16 / 2}
|
||||
fontSize={16}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
{polygons?.map(
|
||||
(polygon, index) =>
|
||||
(selectedZoneMask === undefined ||
|
||||
selectedZoneMask.includes(polygon.type)) && (
|
||||
<React.Fragment key={index}>
|
||||
<PolygonDrawer
|
||||
key={index}
|
||||
points={polygon.points}
|
||||
flattenedPoints={flattenPoints(polygon.points)}
|
||||
isActive={index === activePolygonIndex}
|
||||
isHovered={index === hoveredPolygonIndex}
|
||||
isFinished={polygon.isFinished}
|
||||
color={polygon.color}
|
||||
handlePointDragMove={handlePointDragMove}
|
||||
handleGroupDragEnd={handleGroupDragEnd}
|
||||
handleMouseOverStartPoint={handleMouseOverStartPoint}
|
||||
handleMouseOutStartPoint={handleMouseOutStartPoint}
|
||||
/>
|
||||
{index === hoveredPolygonIndex && (
|
||||
<Text
|
||||
text={polygon.name}
|
||||
align="left"
|
||||
verticalAlign="top"
|
||||
x={
|
||||
getAveragePoint(flattenPoints(polygon.points)).x
|
||||
// - (polygon.name.length * 16 * 0.6) / 2
|
||||
}
|
||||
y={getAveragePoint(flattenPoints(polygon.points)).y} //- 16 / 2}
|
||||
fontSize={16}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
),
|
||||
)}
|
||||
</Layer>
|
||||
</Stage>
|
||||
);
|
||||
|
||||
@ -20,6 +20,8 @@ import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
|
||||
import useSWR from "swr";
|
||||
import General from "@/components/settings/General";
|
||||
import FilterCheckBox from "@/components/filter/FilterCheckBox";
|
||||
import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter";
|
||||
import { PolygonType } from "@/types/canvas";
|
||||
|
||||
type CameraSelectButtonProps = {
|
||||
allCameras: CameraConfig[];
|
||||
@ -136,6 +138,8 @@ export default function Settings() {
|
||||
|
||||
const [selectedCamera, setSelectedCamera] = useState(cameras[0].name);
|
||||
|
||||
const [filterZoneMask, setFilterZoneMask] = useState<PolygonType[]>();
|
||||
|
||||
return (
|
||||
<div className="size-full p-2 flex flex-col">
|
||||
<div className="w-full h-11 relative flex justify-between items-center">
|
||||
@ -168,6 +172,10 @@ export default function Settings() {
|
||||
page == "masks / zones" ||
|
||||
page == "motion tuner") && (
|
||||
<div className="flex items-center gap-2">
|
||||
<ZoneMaskFilterButton
|
||||
selectedZoneMask={filterZoneMask}
|
||||
updateZoneMaskFilter={setFilterZoneMask}
|
||||
/>
|
||||
<CameraSelectButton
|
||||
allCameras={cameras}
|
||||
selectedCamera={selectedCamera}
|
||||
@ -182,7 +190,7 @@ export default function Settings() {
|
||||
{page == "masks / zones" && (
|
||||
<MasksAndZones
|
||||
selectedCamera={selectedCamera}
|
||||
setSelectedCamera={setSelectedCamera}
|
||||
selectedZoneMask={filterZoneMask}
|
||||
/>
|
||||
)}
|
||||
{page == "motion tuner" && <MotionTuner />}
|
||||
|
||||
@ -340,7 +340,7 @@ export interface FrigateConfig {
|
||||
threshold: number;
|
||||
};
|
||||
};
|
||||
mask: string;
|
||||
mask: string[];
|
||||
track: string[];
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user