Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frigate/config/camera/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@


class ZoneConfig(BaseModel):
friendly_name: Optional[str] = Field(
None, title="Zone friendly name used in the Frigate UI."
)
filters: dict[str, FilterConfig] = Field(
default_factory=dict, title="Zone filters."
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
import { CameraConfig } from "@/types/frigateConfig";
import { useZoneFriendlyName } from "@/hooks/use-zone-friendly-name";

interface CameraNameLabelProps
extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {
camera?: string | CameraConfig;
}

interface ZoneNameLabelProps
extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {
zone: string;
camera?: string;
}

const CameraNameLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
CameraNameLabelProps
Expand All @@ -21,4 +28,17 @@ const CameraNameLabel = React.forwardRef<
});
CameraNameLabel.displayName = LabelPrimitive.Root.displayName;

export { CameraNameLabel };
const ZoneNameLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
ZoneNameLabelProps
>(({ className, zone, camera, ...props }, ref) => {
const displayName = useZoneFriendlyName(zone, camera);
return (
<LabelPrimitive.Root ref={ref} className={className} {...props}>
{displayName}
</LabelPrimitive.Root>
);
});
ZoneNameLabel.displayName = LabelPrimitive.Root.displayName;

export { CameraNameLabel, ZoneNameLabel };
2 changes: 1 addition & 1 deletion web/src/components/filter/CameraGroupSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import { CameraStreamingDialog } from "../settings/CameraStreamingDialog";
import { DialogTrigger } from "@radix-ui/react-dialog";
import { useStreamingSettings } from "@/context/streaming-settings-provider";
import { Trans, useTranslation } from "react-i18next";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";
import { useAllowedCameras } from "@/hooks/use-allowed-cameras";
import { useIsCustomRole } from "@/hooks/use-is-custom-role";

Expand Down
2 changes: 1 addition & 1 deletion web/src/components/filter/CamerasFilterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export function CamerasFilterContent({
key={item}
isChecked={currentCameras?.includes(item) ?? false}
label={item}
isCameraName={true}
type={"camera"}
disabled={
mainCamera !== undefined &&
currentCameras !== undefined &&
Expand Down
16 changes: 13 additions & 3 deletions web/src/components/filter/FilterSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
import { Switch } from "../ui/switch";
import { Label } from "../ui/label";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel, ZoneNameLabel } from "../camera/FriendlyNameLabel";

type FilterSwitchProps = {
label: string;
disabled?: boolean;
isChecked: boolean;
isCameraName?: boolean;
type?: string;
extraValue?: string;
onCheckedChange: (checked: boolean) => void;
};
export default function FilterSwitch({
label,
disabled = false,
isChecked,
isCameraName = false,
type = "",
extraValue = "",
onCheckedChange,
}: FilterSwitchProps) {
return (
<div className="flex items-center justify-between gap-1">
{isCameraName ? (
{type === "camera" ? (
<CameraNameLabel
className={`mx-2 w-full cursor-pointer text-sm font-medium leading-none text-primary smart-capitalize peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${disabled ? "text-secondary-foreground" : ""}`}
htmlFor={label}
camera={label}
/>
) : type === "zone" ? (
<ZoneNameLabel
className={`mx-2 w-full cursor-pointer text-sm font-medium leading-none text-primary smart-capitalize peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${disabled ? "text-secondary-foreground" : ""}`}
htmlFor={label}
camera={extraValue}
zone={label}
/>
) : (
<Label
className={`mx-2 w-full cursor-pointer text-primary smart-capitalize ${disabled ? "text-secondary-foreground" : ""}`}
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/filter/ReviewFilterGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ export function GeneralFilterContent({
{allZones.map((item) => (
<FilterSwitch
key={item}
label={item.replaceAll("_", " ")}
label={item}
type={"zone"}
isChecked={filter.zones?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {
Expand Down
11 changes: 10 additions & 1 deletion web/src/components/input/InputWithTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { FrigateConfig } from "@/types/frigateConfig";
import { MdImageSearch } from "react-icons/md";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel, ZoneNameLabel } from "../camera/FriendlyNameLabel";

type InputWithTagsProps = {
inputFocused: boolean;
Expand Down Expand Up @@ -831,6 +831,8 @@ export default function InputWithTags({
getTranslatedLabel(value)
) : filterType === "cameras" ? (
<CameraNameLabel camera={value} />
) : filterType === "zones" ? (
<ZoneNameLabel zone={value} />
) : (
value.replaceAll("_", " ")
)}
Expand Down Expand Up @@ -934,6 +936,11 @@ export default function InputWithTags({
<CameraNameLabel camera={suggestion} />
{")"}
</>
) : currentFilterType === "zones" ? (
<>
{suggestion} {" ("} <ZoneNameLabel zone={suggestion} />
{")"}
</>
) : (
suggestion
)
Expand All @@ -943,6 +950,8 @@ export default function InputWithTags({
{currentFilterType ? (
currentFilterType === "cameras" ? (
<CameraNameLabel camera={suggestion} />
) : currentFilterType === "zones" ? (
<ZoneNameLabel zone={suggestion} />
) : (
formatFilterValues(currentFilterType, suggestion)
)
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/menu/LiveContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
import { useTranslation } from "react-i18next";
import { useDateLocale } from "@/hooks/use-date-locale";
import { useIsAdmin } from "@/hooks/use-is-admin";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";

type LiveContextMenuProps = {
className?: string;
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/overlay/CreateRoleDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from "@/components/ui/dialog";
import { useTranslation } from "react-i18next";
import { FrigateConfig } from "@/types/frigateConfig";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";
import { isDesktop, isMobile } from "react-device-detect";
import { cn } from "@/lib/utils";
import {
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/overlay/EditRoleCamerasDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from "@/components/ui/dialog";
import { Trans, useTranslation } from "react-i18next";
import { FrigateConfig } from "@/types/frigateConfig";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";

type EditRoleCamerasOverlayProps = {
show: boolean;
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/overlay/MobileCameraDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from "../ui/button";
import { FaVideo } from "react-icons/fa";
import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next";
import { CameraNameLabel } from "../camera/CameraNameLabel";
import { CameraNameLabel } from "../camera/FriendlyNameLabel";

type MobileCameraDrawerProps = {
allCameras: string[];
Expand Down
20 changes: 18 additions & 2 deletions web/src/components/overlay/ObjectTrackOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TooltipPortal } from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { Event } from "@/types/event";
import { resolveZoneName } from "@/hooks/use-zone-friendly-name";

// Use a small tolerance (10ms) for browsers with seek precision by-design issues
const TOLERANCE = 0.01;
Expand Down Expand Up @@ -73,6 +74,10 @@ export default function ObjectTrackOverlay({
{ revalidateOnFocus: false },
);

const getZonesFriendlyNames = (zones: string[], config: FrigateConfig) => {
return zones?.map((zone) => resolveZoneName(config, zone)) ?? [];
};

const timelineResults = useMemo(() => {
// Group timeline entries by source_id
if (!timelineData) return selectedObjectIds.map(() => []);
Expand All @@ -86,8 +91,19 @@ export default function ObjectTrackOverlay({
}

// Return timeline arrays in the same order as selectedObjectIds
return selectedObjectIds.map((id) => grouped[id] || []);
}, [selectedObjectIds, timelineData]);
return selectedObjectIds.map((id) => {
const entries = grouped[id] || [];
return entries.map((event) => ({
...event,
data: {
...event.data,
zones_friendly_names: config
? getZonesFriendlyNames(event.data?.zones, config)
: [],
},
}));
});
}, [selectedObjectIds, timelineData, config]);

const typeColorMap = useMemo(
() => ({
Expand Down
31 changes: 24 additions & 7 deletions web/src/components/overlay/detail/ObjectPath.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
import { useTranslation } from "react-i18next";
import { resolveZoneName } from "@/hooks/use-zone-friendly-name";
import { FrigateConfig } from "@/types/frigateConfig";
import useSWR from "swr";

type ObjectPathProps = {
positions?: Position[];
Expand Down Expand Up @@ -42,16 +45,30 @@ export function ObjectPath({
visible = true,
}: ObjectPathProps) {
const { t } = useTranslation(["views/explore"]);
const { data: config } = useSWR<FrigateConfig>("config");
const getAbsolutePositions = useCallback(() => {
if (!imgRef.current || !positions) return [];
const imgRect = imgRef.current.getBoundingClientRect();
return positions.map((pos) => ({
x: pos.x * imgRect.width,
y: pos.y * imgRect.height,
timestamp: pos.timestamp,
lifecycle_item: pos.lifecycle_item,
}));
}, [positions, imgRef]);
return positions.map((pos) => {
if (config && pos.lifecycle_item?.data?.zones) {
pos.lifecycle_item = {
...pos.lifecycle_item,
data: {
...pos.lifecycle_item.data,
zones_friendly_names: pos.lifecycle_item.data.zones.map((zone) => {
return resolveZoneName(config, zone);
}),
},
};
}
return {
x: pos.x * imgRect.width,
y: pos.y * imgRect.height,
timestamp: pos.timestamp,
lifecycle_item: pos.lifecycle_item,
};
});
}, [imgRef, positions, config]);

const generateStraightPath = useCallback((points: Position[]) => {
if (!points || points.length < 2) return "";
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/overlay/detail/SearchDetailDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import { useIsAdmin } from "@/hooks/use-is-admin";
import FaceSelectionDialog from "../FaceSelectionDialog";
import { getTranslatedLabel } from "@/utils/i18n";
import { CgTranscript } from "react-icons/cg";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { CameraNameLabel } from "@/components/camera/FriendlyNameLabel";
import { PiPath } from "react-icons/pi";
import Heading from "@/components/ui/heading";

Expand Down
3 changes: 2 additions & 1 deletion web/src/components/overlay/dialog/SearchFilterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,8 @@ export function ZoneFilterContent({
{allZones.map((item) => (
<FilterSwitch
key={item}
label={item.replaceAll("_", " ")}
label={item}
type={"zone"}
isChecked={zones?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/settings/PolygonCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export function PolygonCanvas({
const activePolygon = updatedPolygons[activePolygonIndex];

// add default points order for already completed polygons
if (!activePolygon.pointsOrder && activePolygon.isFinished) {
if (!activePolygon?.pointsOrder && activePolygon?.isFinished) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you seeing crashes here? Why was this change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you seeing crashes here? Why was this change necessary?

yup, crashes may occur after saving, but the cause remains unknown.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've never had reports of crashes here. It would be good to make sure your changes are not causing an issue.

updatedPolygons[activePolygonIndex] = {
...activePolygon,
pointsOrder: activePolygon.points.map((_, index) => index),
Expand Down
6 changes: 4 additions & 2 deletions web/src/components/settings/PolygonItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down
Loading