Tweak camera group layout editor buttons (#11317)

* tweak layout editor buttons

* remove bubble

* spacing

* button backgrounds
This commit is contained in:
Josh Hawkins 2024-05-09 16:08:22 -05:00 committed by GitHub
parent 8b344cea81
commit 386ffbf5a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 191 additions and 57 deletions

View File

@ -360,6 +360,65 @@ function NewGroupDialog({
); );
} }
type EditGroupDialogProps = {
open: boolean;
setOpen: (open: boolean) => void;
currentGroups: [string, CameraGroupConfig][];
activeGroup?: string;
};
export function EditGroupDialog({
open,
setOpen,
currentGroups,
activeGroup,
}: EditGroupDialogProps) {
// editing group and state
const editingGroup = useMemo(() => {
if (currentGroups && activeGroup) {
return currentGroups.find(([groupName]) => groupName === activeGroup);
} else {
return undefined;
}
}, [currentGroups, activeGroup]);
const [isLoading, setIsLoading] = useState(false);
return (
<>
<Toaster
className="toaster group z-[100]"
position="top-center"
closeButton={true}
/>
<Dialog
open={open}
onOpenChange={(open) => {
setOpen(open);
}}
>
<DialogContent
className={`min-w-0 ${isMobile ? "w-full p-3 rounded-t-2xl max-h-[90%]" : "w-6/12 max-h-dvh overflow-y-hidden"}`}
>
<div className="flex flex-col my-4 overflow-y-auto">
<div className="flex flex-row justify-between items-center mb-3">
<DialogTitle>Edit Camera Group</DialogTitle>
</div>
<CameraGroupEdit
currentGroups={currentGroups}
editingGroup={editingGroup}
isLoading={isLoading}
setIsLoading={setIsLoading}
onSave={() => setOpen(false)}
onCancel={() => setOpen(false)}
/>
</div>
</DialogContent>
</Dialog>
</>
);
}
type CameraGroupRowProps = { type CameraGroupRowProps = {
group: [string, CameraGroupConfig]; group: [string, CameraGroupConfig];
onDeleteGroup: () => void; onDeleteGroup: () => void;
@ -572,7 +631,7 @@ export function CameraGroupEdit({
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="mt-2 space-y-6 overflow-y-auto" className="mt-2 space-y-6 overflow-y-hidden"
> >
<FormField <FormField
control={form.control} control={form.control}
@ -655,7 +714,7 @@ export function CameraGroupEdit({
<Separator className="flex my-2 bg-secondary" /> <Separator className="flex my-2 bg-secondary" />
<div className="flex flex-row gap-2 py-5 md:pb-0"> <div className="flex flex-row gap-2 py-5 md:pb-0">
<Button className="flex flex-1" onClick={onCancel}> <Button type="button" className="flex flex-1" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
<Button <Button

View File

@ -24,15 +24,18 @@ import useSWR from "swr";
import { isDesktop, isMobile, isSafari } from "react-device-detect"; import { isDesktop, isMobile, isSafari } from "react-device-detect";
import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer"; import BirdseyeLivePlayer from "@/components/player/BirdseyeLivePlayer";
import LivePlayer from "@/components/player/LivePlayer"; import LivePlayer from "@/components/player/LivePlayer";
import { IoClose } from "react-icons/io5";
import { LuLayoutDashboard, LuPencil } from "react-icons/lu";
import { cn } from "@/lib/utils";
import { EditGroupDialog } from "@/components/filter/CameraGroupSelector";
import { usePersistedOverlayState } from "@/hooks/use-overlay-state";
import { FaCompress, FaExpand } from "react-icons/fa";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Tooltip, Tooltip,
TooltipContent,
TooltipTrigger, TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { IoClose } from "react-icons/io5";
import { LuMove } from "react-icons/lu";
import { cn } from "@/lib/utils";
type DraggableGridLayoutProps = { type DraggableGridLayoutProps = {
cameras: CameraConfig[]; cameras: CameraConfig[];
@ -67,6 +70,20 @@ export default function DraggableGridLayout({
Layout[] Layout[]
>(`${cameraGroup}-draggable-layout`); >(`${cameraGroup}-draggable-layout`);
const [group] = usePersistedOverlayState("cameraGroup", "default" as string);
const groups = useMemo(() => {
if (!config) {
return [];
}
return Object.entries(config.camera_groups).sort(
(a, b) => a[1].order - b[1].order,
);
}, [config]);
const [editGroup, setEditGroup] = useState(false);
const [currentCameras, setCurrentCameras] = useState<CameraConfig[]>(); const [currentCameras, setCurrentCameras] = useState<CameraConfig[]>();
const [currentIncludeBirdseye, setCurrentIncludeBirdseye] = const [currentIncludeBirdseye, setCurrentIncludeBirdseye] =
useState<boolean>(); useState<boolean>();
@ -252,6 +269,25 @@ export default function DraggableGridLayout({
); );
}, [containerRef, gridContainerRef, containerHeight]); }, [containerRef, gridContainerRef, containerHeight]);
// fullscreen state
useEffect(() => {
if (gridContainerRef.current == null) {
return;
}
const listener = () => {
setFullscreen(document.fullscreenElement != null);
};
document.addEventListener("fullscreenchange", listener);
return () => {
document.removeEventListener("fullscreenchange", listener);
};
}, [gridContainerRef]);
const [fullscreen, setFullscreen] = useState(false);
const cellHeight = useMemo(() => { const cellHeight = useMemo(() => {
const aspectRatio = 16 / 9; const aspectRatio = 16 / 9;
// subtract container margin, 1 camera takes up at least 4 rows // subtract container margin, 1 camera takes up at least 4 rows
@ -286,6 +322,12 @@ export default function DraggableGridLayout({
className="my-2 px-2 pb-8 no-scrollbar overflow-x-hidden" className="my-2 px-2 pb-8 no-scrollbar overflow-x-hidden"
ref={gridContainerRef} ref={gridContainerRef}
> >
<EditGroupDialog
open={editGroup}
setOpen={setEditGroup}
currentGroups={groups}
activeGroup={group}
/>
<ResponsiveGridLayout <ResponsiveGridLayout
className="grid-layout" className="grid-layout"
layouts={{ layouts={{
@ -352,57 +394,90 @@ export default function DraggableGridLayout({
); );
})} })}
</ResponsiveGridLayout> </ResponsiveGridLayout>
{isDesktop && ( {isDesktop && !fullscreen && (
<DesktopEditLayoutButton <div
isEditMode={isEditMode}
setIsEditMode={setIsEditMode}
hasScrollbar={hasScrollbar}
/>
)}
</div>
)}
</>
);
}
type DesktopEditLayoutButtonProps = {
isEditMode?: boolean;
setIsEditMode: React.Dispatch<React.SetStateAction<boolean>>;
hasScrollbar?: boolean | 0 | null;
};
function DesktopEditLayoutButton({
isEditMode,
setIsEditMode,
hasScrollbar,
}: DesktopEditLayoutButtonProps) {
return (
<div className="flex flex-row gap-2 items-center text-primary">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
className={cn( className={cn(
"fixed", "fixed",
isDesktop && "bottom-12 lg:bottom-9", isDesktop && "bottom-12 lg:bottom-9",
isMobile && "bottom-12 lg:bottom-16", isMobile && "bottom-12 lg:bottom-16",
hasScrollbar && isDesktop ? "right-6" : "right-1", hasScrollbar && isDesktop ? "right-6" : "right-3",
"z-50 h-8 w-8 p-0 rounded-full opacity-30 hover:opacity-100 transition-all duration-300", "z-50 flex flex-row gap-2",
)} )}
onClick={() => setIsEditMode((prevIsEditMode) => !prevIsEditMode)} >
<Tooltip>
<TooltipTrigger asChild>
<Button
className="px-2 py-1 bg-secondary-foreground rounded-lg opacity-30 hover:opacity-100 transition-all duration-300"
onClick={() =>
setIsEditMode((prevIsEditMode) => !prevIsEditMode)
}
> >
{isEditMode ? ( {isEditMode ? (
<>
<IoClose className="size-5" /> <IoClose className="size-5" />
</>
) : ( ) : (
<LuMove className="size-5" /> <>
<LuLayoutDashboard className="size-5" />
</>
)} )}
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="left"> <TooltipContent>
{isEditMode ? "Exit Editing" : "Edit Layout"} {isEditMode ? "Exit Editing" : "Edit Layout"}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
{!isEditMode && (
<>
<Tooltip>
<TooltipTrigger asChild>
<Button
className="px-2 py-1 bg-secondary-foreground rounded-lg opacity-30 hover:opacity-100 transition-all duration-300"
onClick={() =>
setEditGroup((prevEditGroup) => !prevEditGroup)
}
>
<LuPencil className="size-5" />
</Button>
</TooltipTrigger>
<TooltipContent>
{isEditMode ? "Exit Editing" : "Edit Camera Group"}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
className="px-2 py-1 bg-secondary-foreground rounded-lg opacity-30 hover:opacity-100 transition-all duration-300"
onClick={() => {
if (fullscreen) {
document.exitFullscreen();
} else {
gridContainerRef.current?.requestFullscreen();
}
}}
>
{fullscreen ? (
<>
<FaCompress className="size-5" />
</>
) : (
<>
<FaExpand className="size-5" />
</>
)}
</Button>
</TooltipTrigger>
<TooltipContent>
{fullscreen ? "Exit Fullscreen" : "Fullscreen"}
</TooltipContent>
</Tooltip>
</>
)}
</div> </div>
)}
</div>
)}
</>
); );
} }

View File

@ -143,11 +143,11 @@
--muted-foreground: hsl(0, 0%, 32%); --muted-foreground: hsl(0, 0%, 32%);
--muted-foreground: 0 0% 32%; --muted-foreground: 0 0% 32%;
--accent: hsl(0, 0%, 15%); --accent: hsl(0, 0%, 19%);
--accent: 0 0% 15%; --accent: 0 0% 19%;
--accent-foreground: hsl(210, 40%, 98%); --accent-foreground: hsl(210, 40%, 95%);
--accent-foreground: 210 40% 98%; --accent-foreground: 210 40% 95%;
--destructive: hsl(0, 62.8%, 30.6%); --destructive: hsl(0, 62.8%, 30.6%);
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;