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:
GuoQing Liu
2025-08-26 11:15:01 -06:00
committed by GitHub
parent 195f705616
commit d3af748366
31 changed files with 276 additions and 99 deletions
@@ -16,6 +16,7 @@ import axios from "axios";
import { toast } from "sonner";
import { Toaster } from "../ui/sonner";
import { Trans, useTranslation } from "react-i18next";
import { useCameraNickname } from "@/hooks/use-camera-nickname";
type CameraInfoDialogProps = {
camera: CameraConfig;
@@ -74,6 +75,8 @@ export default function CameraInfoDialog({
return b === 0 ? a : gcd(b, a % b);
}
const cameraName = useCameraNickname(camera);
return (
<>
<Toaster position="top-center" />
@@ -85,7 +88,7 @@ export default function CameraInfoDialog({
<DialogHeader>
<DialogTitle className="smart-capitalize">
{t("cameras.info.cameraProbeInfo", {
camera: camera.name.replaceAll("_", " "),
camera: cameraName,
})}
</DialogTitle>
</DialogHeader>
@@ -37,6 +37,7 @@ import ImagePicker from "@/components/overlay/ImagePicker";
import { Trigger, TriggerAction, TriggerType } from "@/types/trigger";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "../ui/textarea";
import { useCameraNickname } from "@/hooks/use-camera-nickname";
type CreateTriggerDialogProps = {
show: boolean;
@@ -161,6 +162,8 @@ export default function CreateTriggerDialog({
onCancel();
};
const cameraName = useCameraNickname(selectedCamera);
return (
<Dialog open={show} onOpenChange={onCancel}>
<DialogContent className="sm:max-w-[425px]">
@@ -177,7 +180,9 @@ export default function CreateTriggerDialog({
trigger
? "triggers.dialog.editTrigger.desc"
: "triggers.dialog.createTrigger.desc",
{ camera: selectedCamera },
{
camera: cameraName,
},
)}
</DialogDescription>
</DialogHeader>
@@ -4,6 +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";
type MobileCameraDrawerProps = {
allCameras: string[];
@@ -44,7 +45,7 @@ export default function MobileCameraDrawer({
setCameraDrawer(false);
}}
>
{cam.replaceAll("_", " ")}
<CameraNameLabel camera={cam} />
</div>
))}
</div>
@@ -47,6 +47,7 @@ import { LuSearch } from "react-icons/lu";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { Trans, useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
type ReviewDetailDialogProps = {
review?: ReviewSegment;
@@ -292,7 +293,7 @@ export default function ReviewDetailDialog({
{t("details.camera")}
</div>
<div className="text-sm smart-capitalize">
{review.camera.replaceAll("_", " ")}
<CameraNameLabel camera={review.camera} />
</div>
</div>
<div className="flex flex-col gap-1.5">
@@ -79,6 +79,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";
const SEARCH_TABS = [
"details",
@@ -864,7 +865,7 @@ function ObjectDetailsTab({
<div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">{t("details.camera")}</div>
<div className="text-sm smart-capitalize">
{search.camera.replaceAll("_", " ")}
<CameraNameLabel camera={search.camera} />
</div>
</div>
<div className="flex flex-col gap-1.5">