* 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
14 changed files with 106 additions and 95 deletions

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,51 +260,52 @@ 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 />
{allCameras.map((item) => (
<FilterCheckBox
key={item}
isChecked={currentCameras?.includes(item) ?? false}
label={item.replaceAll("_", " ")}
onCheckedChange={(isChecked) => {
if (isChecked) {
const updatedCameras = currentCameras
? [...currentCameras]
: [];
<DropdownMenuSeparator className="my-2" />
<div className="flex flex-col gap-2.5">
{allCameras.map((item) => (
<FilterSwitch
key={item}
isChecked={currentCameras?.includes(item) ?? false}
label={item.replaceAll("_", " ")}
onCheckedChange={(isChecked) => {
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);
}
}
}
}}
/>
))}
}}
/>
))}
</div>
</div>
<DropdownMenuSeparator />
<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>