mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-19 19:06:16 +01:00
Tweak camera group layout editor buttons (#11317)
* tweak layout editor buttons * remove bubble * spacing * button backgrounds
This commit is contained in:
parent
8b344cea81
commit
386ffbf5a6
@ -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
|
||||||
|
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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%;
|
||||||
|
Loading…
Reference in New Issue
Block a user