From e09f0a6b11cd5c57b2b20dc05bda751574e77c95 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:08:47 -0500 Subject: [PATCH] UI tweaks (#17685) * Fix i18n key * ensure license_plate is added to filter list for dedicated lpr cameras * add ability to use browser back button to close MobilePage * add license_plate to review filter * remove overlay state hook in review * don't zoom text on iOS --- .../components/filter/ReviewFilterGroup.tsx | 4 ++ .../components/filter/SearchFilterGroup.tsx | 4 ++ web/src/components/mobile/MobilePage.tsx | 53 ++++++++++++++++--- .../overlay/detail/AnnotationSettingsPane.tsx | 6 +-- .../overlay/detail/ReviewDetailDialog.tsx | 6 +-- .../overlay/detail/SearchDetailDialog.tsx | 2 +- web/src/types/frigateConfig.ts | 1 + 7 files changed, 60 insertions(+), 16 deletions(-) diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index dfda25046..eaee3ccf5 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -93,6 +93,10 @@ export default function ReviewFilterGroup({ labels.add(label); }); + if (cameraConfig.type == "lpr") { + labels.add("license_plate"); + } + if (cameraConfig.audio.enabled_in_config) { cameraConfig.audio.listen.forEach((label) => { labels.add(label); diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index e6a8214c3..77cd95db0 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -69,6 +69,10 @@ export default function SearchFilterGroup({ } }); + if (cameraConfig.type == "lpr") { + labels.add("license_plate"); + } + if (cameraConfig.audio.enabled_in_config) { cameraConfig.audio.listen.forEach((label) => { labels.add(label); diff --git a/web/src/components/mobile/MobilePage.tsx b/web/src/components/mobile/MobilePage.tsx index 8330f0a64..524e0839c 100644 --- a/web/src/components/mobile/MobilePage.tsx +++ b/web/src/components/mobile/MobilePage.tsx @@ -1,4 +1,10 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import { + createContext, + useContext, + useEffect, + useState, + useCallback, +} from "react"; import { createPortal } from "react-dom"; import { motion, AnimatePresence } from "framer-motion"; import { IoMdArrowRoundBack } from "react-icons/io"; @@ -6,6 +12,7 @@ import { cn } from "@/lib/utils"; import { isPWA } from "@/utils/isPWA"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; +import { useLocation } from "react-router-dom"; const MobilePageContext = createContext<{ open: boolean; @@ -24,15 +31,47 @@ export function MobilePage({ onOpenChange, }: MobilePageProps) { const [uncontrolledOpen, setUncontrolledOpen] = useState(false); + const location = useLocation(); const open = controlledOpen ?? uncontrolledOpen; - const setOpen = (value: boolean) => { - if (onOpenChange) { - onOpenChange(value); - } else { - setUncontrolledOpen(value); + const setOpen = useCallback( + (value: boolean) => { + if (onOpenChange) { + onOpenChange(value); + } else { + setUncontrolledOpen(value); + } + }, + [onOpenChange, setUncontrolledOpen], + ); + + useEffect(() => { + let isActive = true; + + if (open && isActive) { + window.history.pushState({ isMobilePage: true }, "", location.pathname); } - }; + + const handlePopState = (event: PopStateEvent) => { + if (open && isActive) { + event.preventDefault(); + setOpen(false); + // Delay replaceState to ensure state updates are processed + setTimeout(() => { + if (isActive) { + window.history.replaceState(null, "", location.pathname); + } + }, 0); + } + }; + + window.addEventListener("popstate", handlePopState); + + return () => { + isActive = false; + window.removeEventListener("popstate", handlePopState); + }; + }, [open, setOpen, location.pathname]); return ( diff --git a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx index a3859a618..55680d405 100644 --- a/web/src/components/overlay/detail/AnnotationSettingsPane.tsx +++ b/web/src/components/overlay/detail/AnnotationSettingsPane.tsx @@ -200,9 +200,9 @@ export function AnnotationSettingsPane({ /> - {t( - "objectLifecycle.annotationSettings.offset.millisecondsToOffset", - )} + + objectLifecycle.annotationSettings.offset.millisecondsToOffset +
{t("objectLifecycle.annotationSettings.offset.tips")}
diff --git a/web/src/components/overlay/detail/ReviewDetailDialog.tsx b/web/src/components/overlay/detail/ReviewDetailDialog.tsx index ab2bc6a8e..2f6dfa2f6 100644 --- a/web/src/components/overlay/detail/ReviewDetailDialog.tsx +++ b/web/src/components/overlay/detail/ReviewDetailDialog.tsx @@ -37,7 +37,6 @@ import { MobilePageHeader, MobilePageTitle, } from "@/components/mobile/MobilePage"; -import { useOverlayState } from "@/hooks/use-overlay-state"; import { DownloadVideoButton } from "@/components/button/DownloadVideoButton"; import { TooltipPortal } from "@radix-ui/react-tooltip"; import { LuSearch } from "react-icons/lu"; @@ -109,10 +108,7 @@ export default function ReviewDetailDialog({ // dialog and mobile page - const [isOpen, setIsOpen] = useOverlayState( - "reviewPane", - review != undefined, - ); + const [isOpen, setIsOpen] = useState(review != undefined); const handleOpenChange = useCallback( (open: boolean) => { diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index afd52b629..abebe1851 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -884,7 +884,7 @@ function ObjectDetailsTab({ <>