Use MobilePage for camera group editor (#13710)

* Use MobilePage for camera group editor

* alignment

* clear editing group name
This commit is contained in:
Josh Hawkins 2024-09-12 16:06:56 -05:00 committed by GitHub
parent 644ea7be4a
commit 1f9ba1d625
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 114 additions and 51 deletions

View File

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

View File

@ -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",
)} )}

View File

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