From 9be595107673d45792d8dba2eae261f80dfcc2e0 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 16 Apr 2024 14:55:24 -0600 Subject: [PATCH] UI tweaks (#10998) * Make buttons consistent and have hover state * Use switch for camera to be consistent * Use everywhere and remove unused * Use green for normal stats color * Fix logs copy icon * Remove warnings from pydantic serialization * Ignore warnings * Fix wsdl resolution * Fix loading on switch --- frigate/api/app.py | 2 +- frigate/config.py | 17 +++-- frigate/ptz/onvif.py | 4 +- .../components/filter/CameraGroupSelector.tsx | 4 +- web/src/components/filter/FilterCheckBox.tsx | 34 --------- web/src/components/filter/FilterSwitch.tsx | 29 +++++++ .../components/filter/ReviewFilterGroup.tsx | 75 ++++++++++--------- web/src/components/graph/SystemGraph.tsx | 2 +- web/src/components/navigation/NavItem.tsx | 4 +- web/src/components/navigation/Sidebar.tsx | 2 +- .../player/dynamic/DynamicVideoPlayer.tsx | 6 +- .../components/settings/AccountSettings.tsx | 10 ++- .../components/settings/GeneralSettings.tsx | 8 +- web/src/pages/Logs.tsx | 4 +- 14 files changed, 106 insertions(+), 95 deletions(-) delete mode 100644 web/src/components/filter/FilterCheckBox.tsx create mode 100644 web/src/components/filter/FilterSwitch.tsx diff --git a/frigate/api/app.py b/frigate/api/app.py index a324b6a05..b56c9c229 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -139,7 +139,7 @@ def stats_history(): def config(): config_obj: FrigateConfig = current_app.frigate_config config: dict[str, dict[str, any]] = config_obj.model_dump( - mode="json", exclude_none=True + mode="json", warnings="none", exclude_none=True ) # remove the mqtt password diff --git a/frigate/config.py b/frigate/config.py index ebb471028..d3a89e7a1 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -1351,11 +1351,12 @@ class FrigateConfig(FrigateBaseModel): "timestamp_style": ..., }, exclude_unset=True, + warnings="none", ) for name, camera in config.cameras.items(): merged_config = deep_merge( - camera.model_dump(exclude_unset=True), global_config + camera.model_dump(exclude_unset=True, warnings="none"), global_config ) camera_config: CameraConfig = CameraConfig.model_validate( {"name": name, **merged_config} @@ -1466,7 +1467,7 @@ class FrigateConfig(FrigateBaseModel): # Set runtime filter to create masks camera_config.objects.filters[object] = RuntimeFilterConfig( frame_shape=camera_config.frame_shape, - **filter.model_dump(exclude_unset=True), + **filter.model_dump(exclude_unset=True, warnings="none"), ) # Convert motion configuration @@ -1478,7 +1479,9 @@ class FrigateConfig(FrigateBaseModel): camera_config.motion = RuntimeMotionConfig( frame_shape=camera_config.frame_shape, raw_mask=camera_config.motion.mask, - **camera_config.motion.model_dump(exclude_unset=True), + **camera_config.motion.model_dump( + exclude_unset=True, warnings="none" + ), ) camera_config.motion.enabled_in_config = camera_config.motion.enabled @@ -1515,7 +1518,9 @@ class FrigateConfig(FrigateBaseModel): for key, detector in config.detectors.items(): adapter = TypeAdapter(DetectorConfig) model_dict = ( - detector if isinstance(detector, dict) else detector.model_dump() + detector + if isinstance(detector, dict) + else detector.model_dump(warnings="none") ) detector_config: DetectorConfig = adapter.validate_python(model_dict) if detector_config.model is None: @@ -1536,8 +1541,8 @@ class FrigateConfig(FrigateBaseModel): "Customizing more than a detector model path is unsupported." ) merged_model = deep_merge( - detector_config.model.model_dump(exclude_unset=True), - config.model.model_dump(exclude_unset=True), + detector_config.model.model_dump(exclude_unset=True, warnings="none"), + config.model.model_dump(exclude_unset=True, warnings="none"), ) if "path" not in merged_model: diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index 2cd903709..4dbad973f 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -51,7 +51,9 @@ class OnvifController: cam.onvif.port, cam.onvif.user, cam.onvif.password, - wsdl_dir=Path(find_spec("onvif").origin).parent / "../wsdl", + wsdl_dir=str( + Path(find_spec("onvif").origin).parent / "wsdl" + ).replace("dist-packages/onvif", "site-packages"), ), "init": False, "active": False, diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx index e514667ed..c147ab638 100644 --- a/web/src/components/filter/CameraGroupSelector.tsx +++ b/web/src/components/filter/CameraGroupSelector.tsx @@ -22,8 +22,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu"; -import FilterCheckBox from "./FilterCheckBox"; import axios from "axios"; +import FilterSwitch from "./FilterSwitch"; type CameraGroupSelectorProps = { className?: string; @@ -305,7 +305,7 @@ function NewGroupDialog({ open, setOpen, currentGroups }: NewGroupDialogProps) { ...(birdseyeConfig?.enabled ? ["birdseye"] : []), ...Object.keys(config?.cameras ?? {}), ].map((camera) => ( - void; -}; - -export default function FilterCheckBox({ - label, - CheckIcon = LuCheck, - iconClassName = "size-6", - isChecked, - onCheckedChange, -}: FilterCheckBoxProps) { - return ( - - ); -} diff --git a/web/src/components/filter/FilterSwitch.tsx b/web/src/components/filter/FilterSwitch.tsx new file mode 100644 index 000000000..8af6d6ce3 --- /dev/null +++ b/web/src/components/filter/FilterSwitch.tsx @@ -0,0 +1,29 @@ +import { Switch } from "../ui/switch"; +import { Label } from "../ui/label"; + +type FilterSwitchProps = { + label: string; + isChecked: boolean; + onCheckedChange: (checked: boolean) => void; +}; +export default function FilterSwitch({ + label, + isChecked, + onCheckedChange, +}: FilterSwitchProps) { + return ( +
+ + +
+ ); +} diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx index a92399539..2a36b2a60 100644 --- a/web/src/components/filter/ReviewFilterGroup.tsx +++ b/web/src/components/filter/ReviewFilterGroup.tsx @@ -24,12 +24,12 @@ import { isDesktop, isMobile } from "react-device-detect"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Switch } from "../ui/switch"; import { Label } from "../ui/label"; -import FilterCheckBox from "./FilterCheckBox"; import ReviewActivityCalendar from "../overlay/ReviewActivityCalendar"; import MobileReviewSettingsDrawer, { DrawerFeatures, } from "../overlay/MobileReviewSettingsDrawer"; import useOptimisticState from "@/hooks/use-optimistic-state"; +import FilterSwitch from "./FilterSwitch"; const REVIEW_FILTERS = [ "cameras", @@ -248,8 +248,8 @@ export function CamerasFilterButton({ )} -
- + { @@ -260,51 +260,52 @@ export function CamerasFilterButton({ /> {groups.length > 0 && ( <> - + {groups.map(([name, conf]) => { return ( - { - setCurrentCameras([...conf.cameras]); - }} - /> + className="w-full px-2 py-1.5 text-sm text-primary capitalize cursor-pointer rounded-lg hover:bg-muted" + onClick={() => setCurrentCameras([...conf.cameras])} + > + {name} +
); })} )} - - {allCameras.map((item) => ( - { - if (isChecked) { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; + +
+ {allCameras.map((item) => ( + { + if (isChecked) { + const updatedCameras = currentCameras + ? [...currentCameras] + : []; - updatedCameras.push(item); - setCurrentCameras(updatedCameras); - } else { - const updatedCameras = currentCameras - ? [...currentCameras] - : []; - - // can not deselect the last item - if (updatedCameras.length > 1) { - updatedCameras.splice(updatedCameras.indexOf(item), 1); + updatedCameras.push(item); setCurrentCameras(updatedCameras); + } else { + const updatedCameras = currentCameras + ? [...currentCameras] + : []; + + // can not deselect the last item + if (updatedCameras.length > 1) { + updatedCameras.splice(updatedCameras.indexOf(item), 1); + setCurrentCameras(updatedCameras); + } } - } - }} - /> - ))} + }} + /> + ))} +
- +
-
+
diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index 7576a90cb..f923fbd43 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -111,9 +111,13 @@ export default function DynamicVideoPlayer({ return; } + if (isLoading) { + setIsLoading(false); + } + onTimestampUpdate(controller.getProgress(time)); }, - [controller, onTimestampUpdate, isScrubbing], + [controller, onTimestampUpdate, isScrubbing, isLoading], ); // state of playback player diff --git a/web/src/components/settings/AccountSettings.tsx b/web/src/components/settings/AccountSettings.tsx index e5881465e..afa63ae4c 100644 --- a/web/src/components/settings/AccountSettings.tsx +++ b/web/src/components/settings/AccountSettings.tsx @@ -3,16 +3,18 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +import { isDesktop } from "react-device-detect"; import { VscAccount } from "react-icons/vsc"; -import { Button } from "../ui/button"; export default function AccountSettings() { return ( - +
+ +

Account

diff --git a/web/src/components/settings/GeneralSettings.tsx b/web/src/components/settings/GeneralSettings.tsx index 3ffcc540d..bc45f650f 100644 --- a/web/src/components/settings/GeneralSettings.tsx +++ b/web/src/components/settings/GeneralSettings.tsx @@ -118,9 +118,11 @@ export default function GeneralSettings({ className }: GeneralSettings) { - +
+ +

Settings

diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index e735d9a6f..a12f2d162 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -31,7 +31,7 @@ function Logs() { const [logService, setLogService] = useState("frigate"); useEffect(() => { - document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Stats - Frigate`; + document.title = `${logService[0].toUpperCase()}${logService.substring(1)} Logs - Frigate`; }, [logService]); // log data handling @@ -366,7 +366,7 @@ function Logs() { size="sm" onClick={handleCopyLogs} > - +
Copy to Clipboard