mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-02 17:12:16 +03:00
Mask/zone editor fixes (#22732)
* add guards to reject missing sub commands * mask/zone bugfixes - fix websocket crash when creating a new mask or zone before a name is assigned - fix deleted masks and zones not disappearing from the list until navigating away - fix deleting profile override not reverting to the base mask in the list - fix inertia defaulting to nan * disable save button on invalid form state * fix validation for speed estimation * ensure polygon is closed before allowing save * require all masks and zones to be on the base config * clarify dialog message and tooltip when removing an override * clarify docs
This commit is contained in:
@@ -30,7 +30,6 @@ import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { reviewQueries } from "@/utils/zoneEdutUtil";
|
||||
import IconWrapper from "../ui/icon-wrapper";
|
||||
import { buttonVariants } from "../ui/button";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import ActivityIndicator from "../indicators/activity-indicator";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -51,6 +50,7 @@ type PolygonItemProps = {
|
||||
setLoadingPolygonIndex: (index: number | undefined) => void;
|
||||
editingProfile?: string | null;
|
||||
allProfileNames?: string[];
|
||||
onDeleted?: () => void;
|
||||
};
|
||||
|
||||
export default function PolygonItem({
|
||||
@@ -67,6 +67,7 @@ export default function PolygonItem({
|
||||
setLoadingPolygonIndex,
|
||||
editingProfile,
|
||||
allProfileNames,
|
||||
onDeleted,
|
||||
}: PolygonItemProps) {
|
||||
const { t } = useTranslation("views/settings");
|
||||
const { data: config, mutate: updateConfig } =
|
||||
@@ -152,7 +153,19 @@ export default function PolygonItem({
|
||||
cameraConfig?.review.alerts.required_zones || [],
|
||||
cameraConfig?.review.detections.required_zones || [],
|
||||
);
|
||||
url = `cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}`;
|
||||
// Also delete from profiles that have overrides for this zone
|
||||
let profileQueries = "";
|
||||
if (allProfileNames && cameraConfig) {
|
||||
for (const profileName of allProfileNames) {
|
||||
if (
|
||||
cameraConfig.profiles?.[profileName]?.zones?.[polygon.name] !==
|
||||
undefined
|
||||
) {
|
||||
profileQueries += `&cameras.${polygon.camera}.profiles.${profileName}.zones.${polygon.name}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
url = `cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}${profileQueries}`;
|
||||
}
|
||||
|
||||
await axios
|
||||
@@ -169,6 +182,7 @@ export default function PolygonItem({
|
||||
{ position: "top-center" },
|
||||
);
|
||||
updateConfig();
|
||||
onDeleted?.();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error.title", {
|
||||
@@ -211,11 +225,41 @@ export default function PolygonItem({
|
||||
},
|
||||
};
|
||||
|
||||
let cameraUpdate: Record<string, unknown>;
|
||||
if (editingProfile) {
|
||||
cameraUpdate = { profiles: { [editingProfile]: deleteSection } };
|
||||
} else {
|
||||
// Base mode: also delete from profiles that have overrides for this mask
|
||||
const profileDeletes: Record<string, unknown> = {};
|
||||
if (allProfileNames && cameraConfig) {
|
||||
for (const profileName of allProfileNames) {
|
||||
const profileData = cameraConfig.profiles?.[profileName];
|
||||
if (!profileData) continue;
|
||||
|
||||
const hasMask =
|
||||
polygon.type === "motion_mask"
|
||||
? profileData.motion?.mask?.[polygon.name] !== undefined
|
||||
: polygon.type === "object_mask"
|
||||
? profileData.objects?.mask?.[polygon.name] !== undefined ||
|
||||
Object.values(profileData.objects?.filters || {}).some(
|
||||
(f) => f?.mask?.[polygon.name] !== undefined,
|
||||
)
|
||||
: false;
|
||||
|
||||
if (hasMask) {
|
||||
profileDeletes[profileName] = deleteSection;
|
||||
}
|
||||
}
|
||||
}
|
||||
cameraUpdate =
|
||||
Object.keys(profileDeletes).length > 0
|
||||
? { ...deleteSection, profiles: profileDeletes }
|
||||
: deleteSection;
|
||||
}
|
||||
|
||||
const configUpdate = {
|
||||
cameras: {
|
||||
[polygon.camera]: editingProfile
|
||||
? { profiles: { [editingProfile]: deleteSection } }
|
||||
: deleteSection,
|
||||
[polygon.camera]: cameraUpdate,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -234,6 +278,7 @@ export default function PolygonItem({
|
||||
{ position: "top-center" },
|
||||
);
|
||||
updateConfig();
|
||||
onDeleted?.();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error.title", {
|
||||
@@ -267,6 +312,8 @@ export default function PolygonItem({
|
||||
index,
|
||||
setLoadingPolygonIndex,
|
||||
editingProfile,
|
||||
allProfileNames,
|
||||
onDeleted,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -425,32 +472,51 @@ export default function PolygonItem({
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("masksAndZones.form.polygonDrawing.delete.title")}
|
||||
{polygon.polygonSource === "override"
|
||||
? t("masksAndZones.form.polygonDrawing.revertOverride.title")
|
||||
: t("masksAndZones.form.polygonDrawing.delete.title")}
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans
|
||||
ns="views/settings"
|
||||
values={{
|
||||
type: t(
|
||||
`masksAndZones.form.polygonDrawing.type.${polygon.type}`,
|
||||
{ ns: "views/settings" },
|
||||
),
|
||||
name: polygon.friendly_name ?? polygon.name,
|
||||
}}
|
||||
>
|
||||
masksAndZones.form.polygonDrawing.delete.desc
|
||||
</Trans>
|
||||
{polygon.polygonSource === "override" ? (
|
||||
<Trans
|
||||
ns="views/settings"
|
||||
values={{
|
||||
type: t(
|
||||
`masksAndZones.form.polygonDrawing.type.${polygon.type}`,
|
||||
{ ns: "views/settings" },
|
||||
),
|
||||
name: polygon.friendly_name ?? polygon.name,
|
||||
}}
|
||||
>
|
||||
masksAndZones.form.polygonDrawing.revertOverride.desc
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans
|
||||
ns="views/settings"
|
||||
values={{
|
||||
type: t(
|
||||
`masksAndZones.form.polygonDrawing.type.${polygon.type}`,
|
||||
{ ns: "views/settings" },
|
||||
),
|
||||
name: polygon.friendly_name ?? polygon.name,
|
||||
}}
|
||||
>
|
||||
masksAndZones.form.polygonDrawing.delete.desc
|
||||
</Trans>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
{t("button.cancel", { ns: "common" })}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className={buttonVariants({ variant: "destructive" })}
|
||||
className="bg-destructive text-white hover:bg-destructive/90"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{t("button.delete", { ns: "common" })}
|
||||
{polygon.polygonSource === "override"
|
||||
? t("masksAndZones.form.polygonDrawing.revertOverride.title")
|
||||
: t("button.delete", { ns: "common" })}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -563,7 +629,9 @@ export default function PolygonItem({
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t("button.delete", { ns: "common" })}
|
||||
{polygon.polygonSource === "override"
|
||||
? t("masksAndZones.form.polygonDrawing.revertOverride.title")
|
||||
: t("button.delete", { ns: "common" })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user