Add object lifecycle to explore dialog

This commit is contained in:
Nicolas Mowen 2024-09-11 19:52:48 -06:00
parent d2b8d0007b
commit 3e9707d507
3 changed files with 44 additions and 26 deletions

View File

@ -13,7 +13,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ObjectLifecycleSequence } from "@/types/timeline"; import { ObjectLifecycleSequence } from "@/types/timeline";
import Heading from "@/components/ui/heading"; import Heading from "@/components/ui/heading";
import { ReviewDetailPaneType, ReviewSegment } from "@/types/review"; import { ReviewDetailPaneType } from "@/types/review";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
@ -47,14 +47,16 @@ import { AnnotationSettingsPane } from "./AnnotationSettingsPane";
import { TooltipPortal } from "@radix-ui/react-tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip";
type ObjectLifecycleProps = { type ObjectLifecycleProps = {
review: ReviewSegment; className?: string;
event: Event; event: Event;
showBack?: boolean;
setPane: React.Dispatch<React.SetStateAction<ReviewDetailPaneType>>; setPane: React.Dispatch<React.SetStateAction<ReviewDetailPaneType>>;
}; };
export default function ObjectLifecycle({ export default function ObjectLifecycle({
review, className,
event, event,
showBack = true,
setPane, setPane,
}: ObjectLifecycleProps) { }: ObjectLifecycleProps) {
const { data: eventSequence } = useSWR<ObjectLifecycleSequence[]>([ const { data: eventSequence } = useSWR<ObjectLifecycleSequence[]>([
@ -78,13 +80,13 @@ export default function ObjectLifecycle({
const getZoneColor = useCallback( const getZoneColor = useCallback(
(zoneName: string) => { (zoneName: string) => {
const zoneColor = const zoneColor =
config?.cameras?.[review.camera]?.zones?.[zoneName]?.color; config?.cameras?.[event.camera]?.zones?.[zoneName]?.color;
if (zoneColor) { if (zoneColor) {
const reversed = [...zoneColor].reverse(); const reversed = [...zoneColor].reverse();
return reversed; return reversed;
} }
}, },
[config, review], [config, event],
); );
const getZonePolygon = useCallback( const getZonePolygon = useCallback(
@ -93,7 +95,7 @@ export default function ObjectLifecycle({
return; return;
} }
const zonePoints = const zonePoints =
config?.cameras[review.camera].zones[zoneName].coordinates; config?.cameras[event.camera].zones[zoneName].coordinates;
const imgElement = imgRef.current; const imgElement = imgRef.current;
const imgRect = imgElement.getBoundingClientRect(); const imgRect = imgElement.getBoundingClientRect();
@ -110,7 +112,7 @@ export default function ObjectLifecycle({
}, [] as number[]) }, [] as number[])
.join(","); .join(",");
}, },
[config, imgRef, review], [config, imgRef, event],
); );
const [boxStyle, setBoxStyle] = useState<React.CSSProperties | null>(null); const [boxStyle, setBoxStyle] = useState<React.CSSProperties | null>(null);
@ -224,17 +226,19 @@ export default function ObjectLifecycle({
} }
return ( return (
<> <div className={className}>
<div className={cn("flex items-center gap-2")}> {showBack && (
<Button <div className={cn("flex items-center gap-2")}>
className="flex items-center gap-2.5 rounded-lg" <Button
size="sm" className="flex items-center gap-2.5 rounded-lg"
onClick={() => setPane("overview")} size="sm"
> onClick={() => setPane("overview")}
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" /> >
{isDesktop && <div className="text-primary">Back</div>} <IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
</Button> {isDesktop && <div className="text-primary">Back</div>}
</div> </Button>
</div>
)}
<div className="relative mx-auto"> <div className="relative mx-auto">
<ImageLoadingIndicator <ImageLoadingIndicator
@ -513,7 +517,7 @@ export default function ObjectLifecycle({
<CarouselNext /> <CarouselNext />
</Carousel> </Carousel>
</div> </div>
</> </div>
); );
} }

View File

@ -214,11 +214,7 @@ export default function ReviewDetailDialog({
{pane == "details" && selectedEvent && ( {pane == "details" && selectedEvent && (
<div className="scrollbar-container overflow-x-none mt-0 flex size-full flex-col gap-2 overflow-y-auto overflow-x-hidden"> <div className="scrollbar-container overflow-x-none mt-0 flex size-full flex-col gap-2 overflow-y-auto overflow-x-hidden">
<ObjectLifecycle <ObjectLifecycle event={selectedEvent} setPane={setPane} />
review={review}
event={selectedEvent}
setPane={setPane}
/>
</div> </div>
)} )}
</Content> </Content>

View File

@ -36,8 +36,15 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record"; import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
import { FaRegListAlt, FaVideo } from "react-icons/fa"; import { FaRegListAlt, FaVideo } from "react-icons/fa";
import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon";
import { FaRotate } from "react-icons/fa6";
import ObjectLifecycle from "./ObjectLifecycle";
const SEARCH_TABS = ["details", "frigate+", "video"] as const; const SEARCH_TABS = [
"details",
"frigate+",
"video",
"object lifecycle",
] as const;
type SearchTab = (typeof SEARCH_TABS)[number]; type SearchTab = (typeof SEARCH_TABS)[number];
type SearchDetailDialogProps = { type SearchDetailDialogProps = {
@ -104,7 +111,7 @@ export default function SearchDetailDialog({
<Content <Content
className={ className={
isDesktop isDesktop
? "sm:max-w-xl md:max-w-3xl lg:max-w-4xl xl:max-w-7xl" ? "sm:max-w-xl md:max-w-xl lg:max-w-2xl xl:max-w-5xl"
: "max-h-[75dvh] overflow-hidden px-2 pb-4" : "max-h-[75dvh] overflow-hidden px-2 pb-4"
} }
> >
@ -138,6 +145,9 @@ export default function SearchDetailDialog({
{item == "details" && <FaRegListAlt className="size-4" />} {item == "details" && <FaRegListAlt className="size-4" />}
{item == "frigate+" && <FrigatePlusIcon className="size-4" />} {item == "frigate+" && <FrigatePlusIcon className="size-4" />}
{item == "video" && <FaVideo className="size-4" />} {item == "video" && <FaVideo className="size-4" />}
{item == "object lifecycle" && (
<FaRotate className="size-4" />
)}
<div className="capitalize">{item}</div> <div className="capitalize">{item}</div>
</ToggleGroupItem> </ToggleGroupItem>
))} ))}
@ -164,6 +174,14 @@ export default function SearchDetailDialog({
/> />
)} )}
{page == "video" && <VideoTab search={search} config={config} />} {page == "video" && <VideoTab search={search} config={config} />}
{page == "object lifecycle" && (
<ObjectLifecycle
className="w-full"
event={search as unknown as Event}
showBack={false}
setPane={() => {}}
/>
)}
</Content> </Content>
</Overlay> </Overlay>
); );