* 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
This commit is contained in:
Nicolas Mowen 2024-04-16 14:55:24 -06:00 committed by GitHub
parent a823a18496
commit 9be5951076
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 106 additions and 95 deletions

View File

@ -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

View File

@ -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:

View File

@ -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,

View File

@ -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) => (
<FilterCheckBox
<FilterSwitch
key={camera}
isChecked={cameras.includes(camera)}
label={camera.replaceAll("_", " ")}

View File

@ -1,34 +0,0 @@
import { LuCheck } from "react-icons/lu";
import { Button } from "../ui/button";
import { IconType } from "react-icons";
type FilterCheckBoxProps = {
label: string;
CheckIcon?: IconType;
iconClassName?: string;
isChecked: boolean;
onCheckedChange: (isChecked: boolean) => void;
};
export default function FilterCheckBox({
label,
CheckIcon = LuCheck,
iconClassName = "size-6",
isChecked,
onCheckedChange,
}: FilterCheckBoxProps) {
return (
<Button
className="capitalize flex justify-between items-center cursor-pointer w-full text-primary"
variant="ghost"
onClick={() => onCheckedChange(!isChecked)}
>
{isChecked ? (
<CheckIcon className={iconClassName} />
) : (
<div className={iconClassName} />
)}
<div className="ml-1 w-full flex justify-start">{label}</div>
</Button>
);
}

View File

@ -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 (
<div className="flex justify-between items-center gap-1">
<Label
className="w-full mx-2 text-primary capitalize cursor-pointer"
htmlFor={label}
>
{label}
</Label>
<Switch
id={label}
checked={isChecked}
onCheckedChange={onCheckedChange}
/>
</div>
);
}

View File

@ -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({
<DropdownMenuSeparator />
</>
)}
<div className="h-auto overflow-y-auto overflow-x-hidden">
<FilterCheckBox
<div className="h-auto pt-2 overflow-y-auto overflow-x-hidden">
<FilterSwitch
isChecked={currentCameras == undefined}
label="All Cameras"
onCheckedChange={(isChecked) => {
@ -260,24 +260,24 @@ export function CamerasFilterButton({
/>
{groups.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuSeparator className="mt-2" />
{groups.map(([name, conf]) => {
return (
<FilterCheckBox
<div
key={name}
label={name}
isChecked={false}
onCheckedChange={() => {
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}
</div>
);
})}
</>
)}
<DropdownMenuSeparator />
<DropdownMenuSeparator className="my-2" />
<div className="flex flex-col gap-2.5">
{allCameras.map((item) => (
<FilterCheckBox
<FilterSwitch
key={item}
isChecked={currentCameras?.includes(item) ?? false}
label={item.replaceAll("_", " ")}
@ -304,7 +304,8 @@ export function CamerasFilterButton({
/>
))}
</div>
<DropdownMenuSeparator />
</div>
<DropdownMenuSeparator className="my-2" />
<div className="p-2 flex justify-evenly items-center">
<Button
variant="select"

View File

@ -72,7 +72,7 @@ export function ThresholdBarGraph({
} else if (value >= threshold.warning) {
return "#FF9966";
} else {
return (systemTheme || theme) == "dark" ? "#404040" : "#E5E5E5";
return "#217930";
}
},
],

View File

@ -11,8 +11,8 @@ import { IconType } from "react-icons";
const variants = {
primary: {
active: "font-bold text-white bg-selected",
inactive: "text-secondary-foreground bg-secondary",
active: "font-bold text-white bg-selected hover:bg-selected/80",
inactive: "text-secondary-foreground bg-secondary hover:bg-muted",
},
secondary: {
active: "font-bold text-selected",

View File

@ -32,7 +32,7 @@ function Sidebar() {
);
})}
</div>
<div className="flex flex-col items-center mb-8">
<div className="flex flex-col items-center gap-4 mb-8">
<GeneralSettings />
<AccountSettings />
</div>

View File

@ -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

View File

@ -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 (
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost">
<VscAccount />
</Button>
<div
className={`flex flex-col justify-center items-center ${isDesktop ? "rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer" : "text-secondary-foreground"}`}
>
<VscAccount className="size-5 md:m-[6px]" />
</div>
</TooltipTrigger>
<TooltipContent side="right">
<p>Account</p>

View File

@ -118,9 +118,11 @@ export default function GeneralSettings({ className }: GeneralSettings) {
<a href="#">
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost">
<LuSettings />
</Button>
<div
className={`flex flex-col justify-center items-center ${isDesktop ? "rounded-lg text-secondary-foreground bg-secondary hover:bg-muted cursor-pointer" : "text-secondary-foreground"}`}
>
<LuSettings className="size-5 md:m-[6px]" />
</div>
</TooltipTrigger>
<TooltipContent side="right">
<p>Settings</p>

View File

@ -31,7 +31,7 @@ function Logs() {
const [logService, setLogService] = useState<LogType>("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}
>
<FaCopy />
<FaCopy className="text-secondary-foreground" />
<div className="hidden md:block text-primary">
Copy to Clipboard
</div>