mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-28 23:06:13 +02:00
feat: Add camera nickname (#19567)
* refactor: Refactor camera nickname * fix: fix cameraNameLabel visually * chore: The Explore search function also displays the Camera's nickname in English * chore: add mobile page camera nickname * feat: webpush support camera nickname * fix: fix storage camera name is null * chore: fix review detail and context menu camera nickname * chore: fix use-stats and notification setting camera nickname * fix: fix stats camera if not nickname need capitalize * fix: fix debug page open camera web ui i18n and camera nickname support * fix: fix camera metrics not use nickname * refactor: refactor use-camera-nickname hook.
This commit is contained in:
@@ -64,6 +64,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
|
||||
|
||||
type RecordingViewProps = {
|
||||
startCamera: string;
|
||||
@@ -719,7 +720,7 @@ export function RecordingView({
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="smart-capitalize">
|
||||
{cam.replaceAll("_", " ")}
|
||||
<CameraNameLabel camera={cam} />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -49,6 +49,8 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { IoMdArrowRoundBack } from "react-icons/io";
|
||||
import { isDesktop } from "react-device-detect";
|
||||
import { useCameraNickname } from "@/hooks/use-camera-nickname";
|
||||
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
|
||||
|
||||
type CameraSettingsViewProps = {
|
||||
selectedCamera: string;
|
||||
@@ -96,6 +98,8 @@ export default function CameraSettingsView({
|
||||
return [];
|
||||
}, [config]);
|
||||
|
||||
const selectCameraName = useCameraNickname(selectedCamera);
|
||||
|
||||
// zones and labels
|
||||
|
||||
const zones = useMemo(() => {
|
||||
@@ -337,11 +341,13 @@ export default function CameraSettingsView({
|
||||
<SelectValue placeholder={t("camera.selectCamera")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cameras.map((camera) => (
|
||||
<SelectItem key={camera} value={camera}>
|
||||
{capitalizeFirstLetter(camera.replaceAll("_", " "))}
|
||||
</SelectItem>
|
||||
))}
|
||||
{cameras.map((camera) => {
|
||||
return (
|
||||
<SelectItem key={camera} value={camera}>
|
||||
<CameraNameLabel camera={camera} />
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -612,18 +618,14 @@ export default function CameraSettingsView({
|
||||
),
|
||||
)
|
||||
.join(", "),
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
cameraName: selectCameraName,
|
||||
},
|
||||
)
|
||||
: t(
|
||||
"camera.reviewClassification.objectAlertsTips",
|
||||
{
|
||||
alertsLabels,
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
cameraName: selectCameraName,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
@@ -735,9 +737,7 @@ export default function CameraSettingsView({
|
||||
),
|
||||
)
|
||||
.join(", "),
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
cameraName: selectCameraName,
|
||||
}}
|
||||
ns="views/settings"
|
||||
/>
|
||||
@@ -754,9 +754,7 @@ export default function CameraSettingsView({
|
||||
),
|
||||
)
|
||||
.join(", "),
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
cameraName: selectCameraName,
|
||||
}}
|
||||
ns="views/settings"
|
||||
/>
|
||||
@@ -766,9 +764,7 @@ export default function CameraSettingsView({
|
||||
i18nKey="camera.reviewClassification.objectDetectionsTips"
|
||||
values={{
|
||||
detectionsLabels,
|
||||
cameraName: capitalizeFirstLetter(
|
||||
cameraConfig?.name ?? "",
|
||||
).replaceAll("_", " "),
|
||||
cameraName: selectCameraName,
|
||||
}}
|
||||
ns="views/settings"
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
SelectTrigger,
|
||||
} from "@/components/ui/select";
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
|
||||
|
||||
type FrigatePlusModel = {
|
||||
id: string;
|
||||
@@ -488,7 +489,9 @@ export default function FrigatePlusSettingsView({
|
||||
key={name}
|
||||
className="border-b border-secondary"
|
||||
>
|
||||
<td className="px-4 py-2">{name}</td>
|
||||
<td className="px-4 py-2">
|
||||
<CameraNameLabel camera={name} />
|
||||
</td>
|
||||
<td className="px-4 py-2 text-center">
|
||||
{camera.snapshots.enabled ? (
|
||||
<CheckCircle2 className="mx-auto size-5 text-green-500" />
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import Heading from "@/components/ui/heading";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
||||
@@ -46,6 +45,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useDateLocale } from "@/hooks/use-date-locale";
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
|
||||
|
||||
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.js";
|
||||
|
||||
@@ -464,7 +464,8 @@ export default function NotificationView({
|
||||
{allCameras?.map((camera) => (
|
||||
<FilterSwitch
|
||||
key={camera.name}
|
||||
label={camera.name.replaceAll("_", " ")}
|
||||
label={camera.name}
|
||||
isCameraName={true}
|
||||
isChecked={field.value?.includes(camera.name)}
|
||||
onCheckedChange={(checked) => {
|
||||
setChangedValue(true);
|
||||
@@ -697,12 +698,11 @@ export function CameraNotificationSwitch({
|
||||
<LuX className="size-6 text-danger" />
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<Label
|
||||
<CameraNameLabel
|
||||
className="text-md cursor-pointer text-primary smart-capitalize"
|
||||
htmlFor="camera"
|
||||
>
|
||||
{camera.replaceAll("_", " ")}
|
||||
</Label>
|
||||
camera={camera}
|
||||
/>
|
||||
|
||||
{!isSuspended ? (
|
||||
<div className="flex flex-row items-center gap-2 text-sm text-success">
|
||||
|
||||
@@ -30,6 +30,7 @@ import { isDesktop } from "react-device-detect";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||
import { getTranslatedLabel } from "@/utils/i18n";
|
||||
import { useCameraNickname } from "@/hooks/use-camera-nickname";
|
||||
import { AudioLevelGraph } from "@/components/audio/AudioLevelGraph";
|
||||
import { useWs } from "@/api/ws";
|
||||
|
||||
@@ -128,6 +129,8 @@ export default function ObjectSettingsView({
|
||||
}
|
||||
}, [config, selectedCamera]);
|
||||
|
||||
const cameraName = useCameraNickname(cameraConfig);
|
||||
|
||||
const { objects, audio_detections } = useCameraActivity(
|
||||
cameraConfig ?? ({} as CameraConfig),
|
||||
);
|
||||
@@ -186,7 +189,9 @@ export default function ObjectSettingsView({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
Open {capitalizeFirstLetter(cameraConfig.name)}'s Web UI
|
||||
{t("debug.openCameraWebUI", {
|
||||
camera: cameraName,
|
||||
})}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { cn } from "@/lib/utils";
|
||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTriggers } from "@/api/ws";
|
||||
import { useCameraNickname } from "@/hooks/use-camera-nickname";
|
||||
|
||||
type ConfigSetBody = {
|
||||
requires_restart: number;
|
||||
@@ -78,6 +79,7 @@ export default function TriggerView({
|
||||
const [triggeredTrigger, setTriggeredTrigger] = useState<string>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const cameraName = useCameraNickname(selectedCamera);
|
||||
const triggers = useMemo(() => {
|
||||
if (
|
||||
!config ||
|
||||
@@ -390,7 +392,9 @@ export default function TriggerView({
|
||||
{t("triggers.management.title")}
|
||||
</Heading>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("triggers.management.desc", { camera: selectedCamera })}
|
||||
{t("triggers.management.desc", {
|
||||
camera: cameraName,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
@@ -4,7 +4,7 @@ import CameraInfoDialog from "@/components/overlay/CameraInfoDialog";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { FrigateStats } from "@/types/stats";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { MdInfo } from "react-icons/md";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import useSWR from "swr";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
|
||||
import { resolveCameraName } from "@/hooks/use-camera-nickname";
|
||||
|
||||
type CameraMetricsProps = {
|
||||
lastUpdated: number;
|
||||
@@ -26,6 +28,11 @@ export default function CameraMetrics({
|
||||
const { t } = useTranslation(["views/system"]);
|
||||
// camera info dialog
|
||||
|
||||
const getCameraName = useCallback(
|
||||
(cameraId: string) => resolveCameraName(config, cameraId),
|
||||
[config],
|
||||
);
|
||||
|
||||
const [showCameraInfoDialog, setShowCameraInfoDialog] = useState(false);
|
||||
const [probeCameraName, setProbeCameraName] = useState<string>();
|
||||
|
||||
@@ -142,7 +149,7 @@ export default function CameraMetrics({
|
||||
}
|
||||
|
||||
if (!(key in series)) {
|
||||
const camName = key.replaceAll("_", " ");
|
||||
const camName = getCameraName(key);
|
||||
series[key] = {};
|
||||
series[key]["ffmpeg"] = {
|
||||
name: t("cameras.label.cameraFfmpeg", { camName: camName }),
|
||||
@@ -173,7 +180,7 @@ export default function CameraMetrics({
|
||||
});
|
||||
});
|
||||
return series;
|
||||
}, [config, statsHistory, t]);
|
||||
}, [config, getCameraName, statsHistory, t]);
|
||||
|
||||
const cameraFpsSeries = useMemo(() => {
|
||||
if (!statsHistory) {
|
||||
@@ -193,7 +200,7 @@ export default function CameraMetrics({
|
||||
|
||||
Object.entries(stats.cameras).forEach(([key, camStats]) => {
|
||||
if (!(key in series)) {
|
||||
const camName = key.replaceAll("_", " ");
|
||||
const camName = getCameraName(key);
|
||||
series[key] = {};
|
||||
series[key]["fps"] = {
|
||||
name: t("cameras.label.cameraFramesPerSecond", {
|
||||
@@ -230,7 +237,7 @@ export default function CameraMetrics({
|
||||
});
|
||||
});
|
||||
return series;
|
||||
}, [statsHistory, t]);
|
||||
}, [getCameraName, statsHistory, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showCameraInfoDialog) {
|
||||
@@ -276,7 +283,7 @@ export default function CameraMetrics({
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="text-sm font-medium text-muted-foreground smart-capitalize">
|
||||
{camera.name.replaceAll("_", " ")}
|
||||
<CameraNameLabel camera={camera} />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
|
||||
Reference in New Issue
Block a user