mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Use MobilePage for camera group editor (#13710)
* Use MobilePage for camera group editor * alignment * clear editing group name
This commit is contained in:
parent
644ea7be4a
commit
1f9ba1d625
@ -7,8 +7,13 @@ import { Button } from "../ui/button";
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
import { LuPencil, LuPlus } from "react-icons/lu";
|
import { LuPencil, LuPlus } from "react-icons/lu";
|
||||||
import { Dialog, DialogContent, DialogTitle } from "../ui/dialog";
|
import {
|
||||||
import { Drawer, DrawerContent } from "../ui/drawer";
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../ui/dialog";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import {
|
import {
|
||||||
@ -24,6 +29,7 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuPortal,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu";
|
} from "../ui/dropdown-menu";
|
||||||
import {
|
import {
|
||||||
@ -53,6 +59,13 @@ import { cn } from "@/lib/utils";
|
|||||||
import * as LuIcons from "react-icons/lu";
|
import * as LuIcons from "react-icons/lu";
|
||||||
import IconPicker, { IconName, IconRenderer } from "../icons/IconPicker";
|
import IconPicker, { IconName, IconRenderer } from "../icons/IconPicker";
|
||||||
import { isValidIconName } from "@/utils/iconUtil";
|
import { isValidIconName } from "@/utils/iconUtil";
|
||||||
|
import {
|
||||||
|
MobilePage,
|
||||||
|
MobilePageContent,
|
||||||
|
MobilePageDescription,
|
||||||
|
MobilePageHeader,
|
||||||
|
MobilePageTitle,
|
||||||
|
} from "../mobile/MobilePage";
|
||||||
|
|
||||||
type CameraGroupSelectorProps = {
|
type CameraGroupSelectorProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -278,6 +291,7 @@ function NewGroupDialog({
|
|||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setEditState("none");
|
setEditState("none");
|
||||||
|
setEditingGroupName("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
@ -290,8 +304,11 @@ function NewGroupDialog({
|
|||||||
setEditState("edit");
|
setEditState("edit");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const Overlay = isDesktop ? Dialog : Drawer;
|
const Overlay = isDesktop ? Dialog : MobilePage;
|
||||||
const Content = isDesktop ? DialogContent : DrawerContent;
|
const Content = isDesktop ? DialogContent : MobilePageContent;
|
||||||
|
const Header = isDesktop ? DialogHeader : MobilePageHeader;
|
||||||
|
const Description = isDesktop ? DialogDescription : MobilePageDescription;
|
||||||
|
const Title = isDesktop ? DialogTitle : MobilePageTitle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -308,16 +325,36 @@ function NewGroupDialog({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Content
|
<Content
|
||||||
className={`min-w-0 ${isMobile ? "max-h-[90%] w-full rounded-t-2xl p-3" : "max-h-dvh w-6/12 overflow-y-hidden"}`}
|
className={cn(
|
||||||
|
"scrollbar-container overflow-y-auto",
|
||||||
|
isDesktop && "my-4 flex max-h-dvh w-6/12 flex-col",
|
||||||
|
isMobile && "px-4",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="scrollbar-container my-4 flex flex-col overflow-y-auto">
|
{editState === "none" && (
|
||||||
{editState === "none" && (
|
<>
|
||||||
<>
|
<Header
|
||||||
<div className="flex flex-row items-center justify-between py-2">
|
className={cn(isDesktop && "mt-5", "justify-center")}
|
||||||
<DialogTitle>Camera Groups</DialogTitle>
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<Title>Camera Groups</Title>
|
||||||
|
<Description className="sr-only">
|
||||||
|
Edit camera groups
|
||||||
|
</Description>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"absolute",
|
||||||
|
isDesktop && "right-6 top-10",
|
||||||
|
isMobile && "absolute right-0 top-4",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
size="sm"
|
||||||
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
|
className={cn(
|
||||||
|
isDesktop &&
|
||||||
|
"size-6 rounded-md bg-secondary-foreground p-1 text-background",
|
||||||
|
isMobile && "text-secondary-foreground",
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEditState("add");
|
setEditState("add");
|
||||||
}}
|
}}
|
||||||
@ -325,6 +362,8 @@ function NewGroupDialog({
|
|||||||
<LuPlus />
|
<LuPlus />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</Header>
|
||||||
|
<div className="flex flex-col gap-4 md:gap-3">
|
||||||
{currentGroups.map((group) => (
|
{currentGroups.map((group) => (
|
||||||
<CameraGroupRow
|
<CameraGroupRow
|
||||||
key={group[0]}
|
key={group[0]}
|
||||||
@ -333,27 +372,36 @@ function NewGroupDialog({
|
|||||||
onEditGroup={() => onEditGroup(group)}
|
onEditGroup={() => onEditGroup(group)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{editState != "none" && (
|
{editState != "none" && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-3 flex flex-row items-center justify-between">
|
<Header
|
||||||
<DialogTitle>
|
className="mt-2"
|
||||||
{editState == "add" ? "Add" : "Edit"} Camera Group
|
onClose={() => {
|
||||||
</DialogTitle>
|
setEditState("none");
|
||||||
</div>
|
setEditingGroupName("");
|
||||||
<CameraGroupEdit
|
}}
|
||||||
currentGroups={currentGroups}
|
>
|
||||||
editingGroup={editingGroup}
|
<Title>
|
||||||
isLoading={isLoading}
|
{editState == "add" ? "Add" : "Edit"} Camera Group
|
||||||
setIsLoading={setIsLoading}
|
</Title>
|
||||||
onSave={onSave}
|
<Description className="sr-only">
|
||||||
onCancel={onCancel}
|
Edit camera groups
|
||||||
/>
|
</Description>
|
||||||
</>
|
</Header>
|
||||||
)}
|
<CameraGroupEdit
|
||||||
</div>
|
currentGroups={currentGroups}
|
||||||
|
editingGroup={editingGroup}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
onSave={onSave}
|
||||||
|
onCancel={onCancel}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Content>
|
</Content>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</>
|
</>
|
||||||
@ -372,6 +420,12 @@ export function EditGroupDialog({
|
|||||||
currentGroups,
|
currentGroups,
|
||||||
activeGroup,
|
activeGroup,
|
||||||
}: EditGroupDialogProps) {
|
}: EditGroupDialogProps) {
|
||||||
|
const Overlay = isDesktop ? Dialog : MobilePage;
|
||||||
|
const Content = isDesktop ? DialogContent : MobilePageContent;
|
||||||
|
const Header = isDesktop ? DialogHeader : MobilePageHeader;
|
||||||
|
const Description = isDesktop ? DialogDescription : MobilePageDescription;
|
||||||
|
const Title = isDesktop ? DialogTitle : MobilePageTitle;
|
||||||
|
|
||||||
// editing group and state
|
// editing group and state
|
||||||
|
|
||||||
const editingGroup = useMemo(() => {
|
const editingGroup = useMemo(() => {
|
||||||
@ -391,19 +445,24 @@ export function EditGroupDialog({
|
|||||||
position="top-center"
|
position="top-center"
|
||||||
closeButton={true}
|
closeButton={true}
|
||||||
/>
|
/>
|
||||||
<Dialog
|
<Overlay
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
setOpen(open);
|
setOpen(open);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<Content
|
||||||
className={`min-w-0 ${isMobile ? "max-h-[90%] w-full rounded-t-2xl p-3" : "max-h-dvh w-6/12 overflow-y-hidden"}`}
|
className={cn(
|
||||||
|
"min-w-0",
|
||||||
|
isDesktop && "max-h-dvh w-6/12 overflow-y-hidden",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="scrollbar-container my-4 flex flex-col overflow-y-auto">
|
<div className="scrollbar-container flex flex-col overflow-y-auto md:my-4">
|
||||||
<div className="mb-3 flex flex-row items-center justify-between">
|
<Header className="mt-2" onClose={() => setOpen(false)}>
|
||||||
<DialogTitle>Edit Camera Group</DialogTitle>
|
<Title>Edit Camera Group</Title>
|
||||||
</div>
|
<Description className="sr-only">Edit camera group</Description>
|
||||||
|
</Header>
|
||||||
|
|
||||||
<CameraGroupEdit
|
<CameraGroupEdit
|
||||||
currentGroups={currentGroups}
|
currentGroups={currentGroups}
|
||||||
editingGroup={editingGroup}
|
editingGroup={editingGroup}
|
||||||
@ -413,8 +472,8 @@ export function EditGroupDialog({
|
|||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</Content>
|
||||||
</Dialog>
|
</Overlay>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -440,7 +499,7 @@ export function CameraGroupRow({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
key={group[0]}
|
key={group[0]}
|
||||||
className="transition-background my-1.5 flex flex-row items-center justify-between rounded-lg duration-100 md:p-1"
|
className="transition-background flex flex-row items-center justify-between rounded-lg duration-100 md:p-1"
|
||||||
>
|
>
|
||||||
<div className={`flex items-center`}>
|
<div className={`flex items-center`}>
|
||||||
<p className="cursor-default">{group[0]}</p>
|
<p className="cursor-default">{group[0]}</p>
|
||||||
@ -472,12 +531,16 @@ export function CameraGroupRow({
|
|||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<HiOutlineDotsVertical className="size-5" />
|
<HiOutlineDotsVertical className="size-5" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuItem onClick={onEditGroup}>Edit</DropdownMenuItem>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
|
<DropdownMenuItem onClick={onEditGroup}>
|
||||||
Delete
|
Edit
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
<DropdownMenuItem onClick={() => setDeleteDialogOpen(true)}>
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenuPortal>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -659,7 +722,7 @@ export function CameraGroupEdit({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator className="my-2 flex bg-secondary" />
|
<Separator className="my-2 flex bg-secondary" />
|
||||||
<div className="scrollbar-container max-h-[25dvh] overflow-y-auto md:max-h-[40dvh]">
|
<div className="scrollbar-container max-h-[40dvh] overflow-y-auto">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="cameras"
|
name="cameras"
|
||||||
|
@ -32,7 +32,7 @@ export function MobilePage({ children, open, onOpenChange }: MobilePageProps) {
|
|||||||
{isVisible && (
|
{isVisible && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-[100] mb-12 bg-background",
|
"fixed inset-0 z-50 mb-12 bg-background",
|
||||||
isPWA && "mb-16",
|
isPWA && "mb-16",
|
||||||
"landscape:mb-14 landscape:md:mb-16",
|
"landscape:mb-14 landscape:md:mb-16",
|
||||||
)}
|
)}
|
||||||
|
@ -641,7 +641,7 @@ function DetectionReview({
|
|||||||
>
|
>
|
||||||
{filter?.before == undefined && (
|
{filter?.before == undefined && (
|
||||||
<NewReviewData
|
<NewReviewData
|
||||||
className="pointer-events-none absolute left-1/2 z-50 -translate-x-1/2"
|
className="pointer-events-none absolute left-1/2 z-[49] -translate-x-1/2"
|
||||||
contentRef={contentRef}
|
contentRef={contentRef}
|
||||||
reviewItems={currentItems}
|
reviewItems={currentItems}
|
||||||
itemsToReview={loading ? 0 : itemsToReview}
|
itemsToReview={loading ? 0 : itemsToReview}
|
||||||
|
Loading…
Reference in New Issue
Block a user