mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									ea98785097
								
							
						
					
					
						commit
						e09f0a6b11
					
				| @ -93,6 +93,10 @@ export default function ReviewFilterGroup({ | |||||||
|         labels.add(label); |         labels.add(label); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       if (cameraConfig.type == "lpr") { | ||||||
|  |         labels.add("license_plate"); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if (cameraConfig.audio.enabled_in_config) { |       if (cameraConfig.audio.enabled_in_config) { | ||||||
|         cameraConfig.audio.listen.forEach((label) => { |         cameraConfig.audio.listen.forEach((label) => { | ||||||
|           labels.add(label); |           labels.add(label); | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ export default function SearchFilterGroup({ | |||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |       if (cameraConfig.type == "lpr") { | ||||||
|  |         labels.add("license_plate"); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if (cameraConfig.audio.enabled_in_config) { |       if (cameraConfig.audio.enabled_in_config) { | ||||||
|         cameraConfig.audio.listen.forEach((label) => { |         cameraConfig.audio.listen.forEach((label) => { | ||||||
|           labels.add(label); |           labels.add(label); | ||||||
|  | |||||||
| @ -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 { createPortal } from "react-dom"; | ||||||
| import { motion, AnimatePresence } from "framer-motion"; | import { motion, AnimatePresence } from "framer-motion"; | ||||||
| import { IoMdArrowRoundBack } from "react-icons/io"; | import { IoMdArrowRoundBack } from "react-icons/io"; | ||||||
| @ -6,6 +12,7 @@ import { cn } from "@/lib/utils"; | |||||||
| import { isPWA } from "@/utils/isPWA"; | import { isPWA } from "@/utils/isPWA"; | ||||||
| import { Button } from "@/components/ui/button"; | import { Button } from "@/components/ui/button"; | ||||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||||
|  | import { useLocation } from "react-router-dom"; | ||||||
| 
 | 
 | ||||||
| const MobilePageContext = createContext<{ | const MobilePageContext = createContext<{ | ||||||
|   open: boolean; |   open: boolean; | ||||||
| @ -24,15 +31,47 @@ export function MobilePage({ | |||||||
|   onOpenChange, |   onOpenChange, | ||||||
| }: MobilePageProps) { | }: MobilePageProps) { | ||||||
|   const [uncontrolledOpen, setUncontrolledOpen] = useState(false); |   const [uncontrolledOpen, setUncontrolledOpen] = useState(false); | ||||||
|  |   const location = useLocation(); | ||||||
| 
 | 
 | ||||||
|   const open = controlledOpen ?? uncontrolledOpen; |   const open = controlledOpen ?? uncontrolledOpen; | ||||||
|   const setOpen = (value: boolean) => { |   const setOpen = useCallback( | ||||||
|     if (onOpenChange) { |     (value: boolean) => { | ||||||
|       onOpenChange(value); |       if (onOpenChange) { | ||||||
|     } else { |         onOpenChange(value); | ||||||
|       setUncontrolledOpen(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 ( |   return ( | ||||||
|     <MobilePageContext.Provider value={{ open, onOpenChange: setOpen }}> |     <MobilePageContext.Provider value={{ open, onOpenChange: setOpen }}> | ||||||
|  | |||||||
| @ -200,9 +200,9 @@ export function AnnotationSettingsPane({ | |||||||
|                       /> |                       /> | ||||||
|                     </FormControl> |                     </FormControl> | ||||||
|                     <FormDescription> |                     <FormDescription> | ||||||
|                       {t( |                       <Trans ns="views/explore"> | ||||||
|                         "objectLifecycle.annotationSettings.offset.millisecondsToOffset", |                         objectLifecycle.annotationSettings.offset.millisecondsToOffset | ||||||
|                       )} |                       </Trans> | ||||||
|                       <div className="mt-2"> |                       <div className="mt-2"> | ||||||
|                         {t("objectLifecycle.annotationSettings.offset.tips")} |                         {t("objectLifecycle.annotationSettings.offset.tips")} | ||||||
|                       </div> |                       </div> | ||||||
|  | |||||||
| @ -37,7 +37,6 @@ import { | |||||||
|   MobilePageHeader, |   MobilePageHeader, | ||||||
|   MobilePageTitle, |   MobilePageTitle, | ||||||
| } from "@/components/mobile/MobilePage"; | } from "@/components/mobile/MobilePage"; | ||||||
| import { useOverlayState } from "@/hooks/use-overlay-state"; |  | ||||||
| import { DownloadVideoButton } from "@/components/button/DownloadVideoButton"; | import { DownloadVideoButton } from "@/components/button/DownloadVideoButton"; | ||||||
| import { TooltipPortal } from "@radix-ui/react-tooltip"; | import { TooltipPortal } from "@radix-ui/react-tooltip"; | ||||||
| import { LuSearch } from "react-icons/lu"; | import { LuSearch } from "react-icons/lu"; | ||||||
| @ -109,10 +108,7 @@ export default function ReviewDetailDialog({ | |||||||
| 
 | 
 | ||||||
|   // dialog and mobile page
 |   // dialog and mobile page
 | ||||||
| 
 | 
 | ||||||
|   const [isOpen, setIsOpen] = useOverlayState( |   const [isOpen, setIsOpen] = useState(review != undefined); | ||||||
|     "reviewPane", |  | ||||||
|     review != undefined, |  | ||||||
|   ); |  | ||||||
| 
 | 
 | ||||||
|   const handleOpenChange = useCallback( |   const handleOpenChange = useCallback( | ||||||
|     (open: boolean) => { |     (open: boolean) => { | ||||||
|  | |||||||
| @ -884,7 +884,7 @@ function ObjectDetailsTab({ | |||||||
|           <> |           <> | ||||||
|             <div className="text-sm text-primary/40"></div> |             <div className="text-sm text-primary/40"></div> | ||||||
|             <Textarea |             <Textarea | ||||||
|               className="h-64" |               className="text-md h-64" | ||||||
|               placeholder={t("details.description.placeholder")} |               placeholder={t("details.description.placeholder")} | ||||||
|               value={desc} |               value={desc} | ||||||
|               onChange={(e) => setDesc(e.target.value)} |               onChange={(e) => setDesc(e.target.value)} | ||||||
|  | |||||||
| @ -241,6 +241,7 @@ export interface CameraConfig { | |||||||
|     position: string; |     position: string; | ||||||
|     thickness: number; |     thickness: number; | ||||||
|   }; |   }; | ||||||
|  |   type: string; | ||||||
|   ui: UiConfig; |   ui: UiConfig; | ||||||
|   webui_url: string | null; |   webui_url: string | null; | ||||||
|   zones: { |   zones: { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user