mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-16 02:17:46 +01:00
Add zones friend name (#20761)
* feat: add zones friendly name * fix: fix the issue where the input field was empty when there was no friendly_name * chore: fix the issue where the friendly name would replace spaces with underscores * docs: update zones docs * Update web/src/components/settings/ZoneEditPane.tsx Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Add friendly_name option for zone configuration Added optional friendly name for zones in configuration. * fix: fix the logical error in the null/empty check for the polygons parameter * fix: remove the toast name for zones will use the friendly_name instead * docs: remove emoji tips * revert: revert zones doc ui tips * Update docs/docs/configuration/zones.md Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update docs/docs/configuration/zones.md Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Update docs/docs/configuration/zones.md Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * feat: add friendly zone names to tracking details and lifecycle item descriptions * chore: lint fix * refactor: add friendly zone names to timeline entries and clean up unused code * refactor: add formatList --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
@@ -262,13 +262,17 @@ export function PolygonCanvas({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activePolygonIndex === undefined || !polygons) {
|
||||
if (activePolygonIndex === undefined || !polygons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedPolygons = [...polygons];
|
||||
const activePolygon = updatedPolygons[activePolygonIndex];
|
||||
|
||||
if (!activePolygon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add default points order for already completed polygons
|
||||
if (!activePolygon.pointsOrder && activePolygon.isFinished) {
|
||||
updatedPolygons[activePolygonIndex] = {
|
||||
|
||||
@@ -179,7 +179,7 @@ export default function PolygonItem({
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("masksAndZones.form.polygonDrawing.delete.success", {
|
||||
name: polygon?.name,
|
||||
name: polygon?.friendly_name ?? polygon?.name,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
@@ -261,7 +261,9 @@ export default function PolygonItem({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<p className="cursor-default">{polygon.name}</p>
|
||||
<p className="cursor-default">
|
||||
{polygon.friendly_name ?? polygon.name}
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialog
|
||||
open={deleteDialogOpen}
|
||||
@@ -278,7 +280,7 @@ export default function PolygonItem({
|
||||
ns="views/settings"
|
||||
values={{
|
||||
type: polygon.type.replace("_", " "),
|
||||
name: polygon.name,
|
||||
name: polygon.friendly_name ?? polygon.name,
|
||||
}}
|
||||
>
|
||||
masksAndZones.form.polygonDrawing.delete.desc
|
||||
|
||||
@@ -34,6 +34,7 @@ import { Link } from "react-router-dom";
|
||||
import { LuExternalLink } from "react-icons/lu";
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { getTranslatedLabel } from "@/utils/i18n";
|
||||
import NameAndIdFields from "../input/NameAndIdFields";
|
||||
|
||||
type ZoneEditPaneProps = {
|
||||
polygons?: Polygon[];
|
||||
@@ -146,15 +147,37 @@ export default function ZoneEditPane({
|
||||
"masksAndZones.form.zoneName.error.mustNotContainPeriod",
|
||||
),
|
||||
},
|
||||
)
|
||||
.refine((value: string) => /^[a-zA-Z0-9_-]+$/.test(value), {
|
||||
message: t("masksAndZones.form.zoneName.error.hasIllegalCharacter"),
|
||||
})
|
||||
.refine((value: string) => /[a-zA-Z]/.test(value), {
|
||||
),
|
||||
friendly_name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: t(
|
||||
"masksAndZones.form.zoneName.error.mustHaveAtLeastOneLetter",
|
||||
"masksAndZones.form.zoneName.error.mustBeAtLeastTwoCharacters",
|
||||
),
|
||||
}),
|
||||
})
|
||||
.refine(
|
||||
(value: string) => {
|
||||
return !cameras.map((cam) => cam.name).includes(value);
|
||||
},
|
||||
{
|
||||
message: t(
|
||||
"masksAndZones.form.zoneName.error.mustNotBeSameWithCamera",
|
||||
),
|
||||
},
|
||||
)
|
||||
.refine(
|
||||
(value: string) => {
|
||||
const otherPolygonNames =
|
||||
polygons
|
||||
?.filter((_, index) => index !== activePolygonIndex)
|
||||
.map((polygon) => polygon.name) || [];
|
||||
|
||||
return !otherPolygonNames.includes(value);
|
||||
},
|
||||
{
|
||||
message: t("masksAndZones.form.zoneName.error.alreadyExists"),
|
||||
},
|
||||
),
|
||||
inertia: z.coerce
|
||||
.number()
|
||||
.min(1, {
|
||||
@@ -247,6 +270,7 @@ export default function ZoneEditPane({
|
||||
mode: "onBlur",
|
||||
defaultValues: {
|
||||
name: polygon?.name ?? "",
|
||||
friendly_name: polygon?.friendly_name ?? polygon?.name ?? "",
|
||||
inertia:
|
||||
polygon?.camera &&
|
||||
polygon?.name &&
|
||||
@@ -286,6 +310,7 @@ export default function ZoneEditPane({
|
||||
async (
|
||||
{
|
||||
name: zoneName,
|
||||
friendly_name,
|
||||
inertia,
|
||||
loitering_time,
|
||||
objects: form_objects,
|
||||
@@ -415,9 +440,14 @@ export default function ZoneEditPane({
|
||||
}
|
||||
}
|
||||
|
||||
let friendlyNameQuery = "";
|
||||
if (friendly_name) {
|
||||
friendlyNameQuery = `&cameras.${polygon?.camera}.zones.${zoneName}.friendly_name=${encodeURIComponent(friendly_name)}`;
|
||||
}
|
||||
|
||||
axios
|
||||
.put(
|
||||
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${speedThresholdQuery}${distancesQuery}${objectQueries}${alertQueries}${detectionQueries}`,
|
||||
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${speedThresholdQuery}${distancesQuery}${objectQueries}${friendlyNameQuery}${alertQueries}${detectionQueries}`,
|
||||
{
|
||||
requires_restart: 0,
|
||||
update_topic: `config/cameras/${polygon.camera}/zones`,
|
||||
@@ -427,7 +457,7 @@ export default function ZoneEditPane({
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("masksAndZones.zones.toast.success", {
|
||||
zoneName,
|
||||
zoneName: friendly_name || zoneName,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
@@ -541,26 +571,16 @@ export default function ZoneEditPane({
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="mt-2 space-y-6">
|
||||
<FormField
|
||||
<NameAndIdFields
|
||||
type="zone"
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("masksAndZones.zones.name.title")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
placeholder={t("masksAndZones.zones.name.inputPlaceHolder")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t("masksAndZones.zones.name.tips")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
nameField="friendly_name"
|
||||
idField="name"
|
||||
nameLabel={t("masksAndZones.zones.name.title")}
|
||||
nameDescription={t("masksAndZones.zones.name.tips")}
|
||||
placeholderName={t("masksAndZones.zones.name.inputPlaceHolder")}
|
||||
/>
|
||||
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
Reference in New Issue
Block a user