use mobile page component for review and search detail

This commit is contained in:
Josh Hawkins 2024-09-12 14:21:01 -05:00
parent 73b3daedee
commit 3cdaccb6af
2 changed files with 88 additions and 57 deletions

View File

@ -6,13 +6,6 @@ import {
SheetHeader,
SheetTitle,
} from "../../ui/sheet";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
} from "../../ui/drawer";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
@ -20,7 +13,7 @@ import { getIconForLabel } from "@/utils/iconUtil";
import { useApiHost } from "@/api";
import { ReviewDetailPaneType, ReviewSegment } from "@/types/review";
import { Event } from "@/types/event";
import { useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog";
import ObjectLifecycle from "./ObjectLifecycle";
@ -37,6 +30,13 @@ import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { baseUrl } from "@/api/baseUrl";
import { shareOrCopy } from "@/utils/browserUtil";
import {
MobilePage,
MobilePageContent,
MobilePageDescription,
MobilePageHeader,
MobilePageTitle,
} from "@/components/mobile/MobilePage";
type ReviewDetailDialogProps = {
review?: ReviewSegment;
@ -80,11 +80,19 @@ export default function ReviewDetailDialog({
const [selectedEvent, setSelectedEvent] = useState<Event>();
const [pane, setPane] = useState<ReviewDetailPaneType>("overview");
const Overlay = isDesktop ? Sheet : Drawer;
const Content = isDesktop ? SheetContent : DrawerContent;
const Header = isDesktop ? SheetHeader : DrawerHeader;
const Title = isDesktop ? SheetTitle : DrawerTitle;
const Description = isDesktop ? SheetDescription : DrawerDescription;
// dialog and mobile page
const [isOpen, setIsOpen] = useState(review != undefined);
useEffect(() => {
setIsOpen(review != undefined);
}, [review]);
const Overlay = isDesktop ? Sheet : MobilePage;
const Content = isDesktop ? SheetContent : MobilePageContent;
const Header = isDesktop ? SheetHeader : MobilePageHeader;
const Title = isDesktop ? SheetTitle : MobilePageTitle;
const Description = isDesktop ? SheetDescription : MobilePageDescription;
if (!review) {
return;
@ -93,7 +101,7 @@ export default function ReviewDetailDialog({
return (
<>
<Overlay
open={review != undefined}
open={isOpen}
onOpenChange={(open) => {
if (!open) {
setReview(undefined);
@ -114,19 +122,43 @@ export default function ReviewDetailDialog({
<Content
className={cn(
isDesktop
? pane == "overview"
"scrollbar-container overflow-y-auto",
isDesktop && pane == "overview"
? "sm:max-w-xl"
: "pt-2 sm:max-w-4xl"
: "max-h-[80dvh] overflow-hidden p-2 pb-4",
: "pt-2 sm:max-w-4xl",
isMobile && "px-4",
)}
>
<Header className="sr-only">
<Title>Review Item Details</Title>
<Description>Review item details</Description>
</Header>
<span tabIndex={0} className="sr-only" />
{pane == "overview" && (
<div className="scrollbar-container mt-3 flex size-full flex-col gap-5 overflow-y-auto">
<Header className="justify-center" onClose={() => setIsOpen(false)}>
<Title>Review Item Details</Title>
<Description className="sr-only">Review item details</Description>
<div
className={cn(
"absolute",
isDesktop && "right-1 top-8",
isMobile && "right-0 top-3",
)}
>
<Tooltip>
<TooltipTrigger>
<Button
size="sm"
onClick={() =>
shareOrCopy(`${baseUrl}review?id=${review.id}`)
}
>
<FaShareAlt className="size-4 text-secondary-foreground" />
</Button>
</TooltipTrigger>
<TooltipContent>Share this review item</TooltipContent>
</Tooltip>
</div>
</Header>
)}
{pane == "overview" && (
<div className="flex flex-col gap-5 md:mt-3">
<div className="flex w-full flex-row">
<div className="flex w-full flex-col gap-3">
<div className="flex flex-col gap-1.5">
@ -139,21 +171,11 @@ export default function ReviewDetailDialog({
<div className="text-sm text-primary/40">Timestamp</div>
<div className="text-sm">{formattedDate}</div>
</div>
<Button
className="flex max-w-24 gap-2"
variant="secondary"
size="sm"
onClick={() =>
shareOrCopy(`${baseUrl}review?id=${review.id}`)
}
>
<FaShareAlt className="size-4" />
</Button>
</div>
<div className="flex w-full flex-col items-center gap-2">
<div className="flex w-full flex-col gap-1.5">
<div className="text-sm text-primary/40">Objects</div>
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-scroll text-sm capitalize">
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-auto text-sm capitalize">
{events?.map((event) => {
return (
<div

View File

@ -1,11 +1,4 @@
import { isDesktop, isIOS, isMobile } from "react-device-detect";
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
} from "../../ui/drawer";
import { SearchResult } from "@/types/search";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
@ -37,6 +30,13 @@ import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
import { FaImage, FaRegListAlt, FaVideo } from "react-icons/fa";
import { FaRotate } from "react-icons/fa6";
import ObjectLifecycle from "./ObjectLifecycle";
import {
MobilePage,
MobilePageContent,
MobilePageDescription,
MobilePageHeader,
MobilePageTitle,
} from "@/components/mobile/MobilePage";
const SEARCH_TABS = [
"details",
@ -65,6 +65,14 @@ export default function SearchDetailDialog({
const [page, setPage] = useState<SearchTab>("details");
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
// dialog and mobile page
const [isOpen, setIsOpen] = useState(search != undefined);
useEffect(() => {
setIsOpen(search != undefined);
}, [search]);
const searchTabs = useMemo(() => {
if (!config || !search) {
return [];
@ -102,15 +110,15 @@ export default function SearchDetailDialog({
// content
const Overlay = isDesktop ? Dialog : Drawer;
const Content = isDesktop ? DialogContent : DrawerContent;
const Header = isDesktop ? DialogHeader : DrawerHeader;
const Title = isDesktop ? DialogTitle : DrawerTitle;
const Description = isDesktop ? DialogDescription : DrawerDescription;
const Overlay = isDesktop ? Dialog : MobilePage;
const Content = isDesktop ? DialogContent : MobilePageContent;
const Header = isDesktop ? DialogHeader : MobilePageHeader;
const Title = isDesktop ? DialogTitle : MobilePageTitle;
const Description = isDesktop ? DialogDescription : MobilePageDescription;
return (
<Overlay
open={search != undefined}
open={isOpen}
onOpenChange={(open) => {
if (!open) {
setSearch(undefined);
@ -118,15 +126,16 @@ export default function SearchDetailDialog({
}}
>
<Content
className={
isDesktop
? "sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl"
: "max-h-[75dvh] overflow-hidden px-2 pb-4"
}
className={cn(
"scrollbar-container overflow-y-auto",
isDesktop &&
"max-h-[95dvh] sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl",
isMobile && "px-4",
)}
>
<Header className="sr-only">
<Header onClose={() => setIsOpen(false)}>
<Title>Tracked Object Details</Title>
<Description>Tracked object details</Description>
<Description className="sr-only">Tracked object details</Description>
</Header>
<ScrollArea
className={cn("w-full whitespace-nowrap", isMobile && "my-2")}
@ -274,7 +283,7 @@ function ObjectDetailsTab({
}, [desc, search]);
return (
<div className="mt-3 flex size-full flex-col gap-5 md:mt-0">
<div className="flex flex-col gap-5">
<div className="flex w-full flex-row">
<div className="flex w-full flex-col gap-3">
<div className="flex flex-col gap-1.5">
@ -302,7 +311,7 @@ function ObjectDetailsTab({
<div className="text-sm">{formattedDate}</div>
</div>
</div>
<div className="flex w-full flex-col gap-2 px-6">
<div className="flex w-full flex-col gap-2 pl-6">
<img
className="aspect-video select-none rounded-lg object-contain transition-opacity"
style={