Move plus dialog to separate component

This commit is contained in:
Nicolas Mowen 2024-08-14 13:15:35 -06:00
parent 943114c052
commit 9d18061d0f
2 changed files with 165 additions and 138 deletions

View File

@ -0,0 +1,123 @@
import { baseUrl } from "@/api/baseUrl";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Event } from "@/types/event";
import { FrigateConfig } from "@/types/frigateConfig";
import axios from "axios";
import { useCallback, useMemo } from "react";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import useSWR from "swr";
type FrigatePlusDialogProps = {
upload?: Event;
onClose: () => void;
onEventUploaded: () => void;
};
export function FrigatePlusDialog({
upload,
onClose,
onEventUploaded,
}: FrigatePlusDialogProps) {
const { data: config } = useSWR<FrigateConfig>("config");
// layout
const grow = useMemo(() => {
if (!config || !upload) {
return "";
}
const camera = config.cameras[upload.camera];
if (!camera) {
return "";
}
if (camera.detect.width / camera.detect.height < 16 / 9) {
return "aspect-video object-contain";
}
return "";
}, [config, upload]);
// upload
const onSubmitToPlus = useCallback(
async (falsePositive: boolean) => {
if (!upload) {
return;
}
falsePositive
? axios.put(`events/${upload.id}/false_positive`)
: axios.post(`events/${upload.id}/plus`, {
include_annotation: 1,
});
onEventUploaded();
onClose();
},
[upload, onClose, onEventUploaded],
);
return (
<Dialog
open={upload != undefined}
onOpenChange={(open) => (!open ? onClose() : null)}
>
<DialogContent className="md:max-w-3xl lg:max-w-4xl xl:max-w-7xl">
<TransformWrapper minScale={1.0} wheel={{ smoothStep: 0.005 }}>
<DialogHeader>
<DialogTitle>Submit To Frigate+</DialogTitle>
<DialogDescription>
Objects in locations you want to avoid are not false positives.
Submitting them as false positives will confuse the model.
</DialogDescription>
</DialogHeader>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
}}
contentStyle={{
position: "relative",
width: "100%",
height: "100%",
}}
>
{upload?.id && (
<img
className={`w-full ${grow} bg-black`}
src={`${baseUrl}api/events/${upload?.id}/snapshot.jpg`}
alt={`${upload?.label}`}
/>
)}
</TransformComponent>
<DialogFooter>
<Button onClick={onClose}>Cancel</Button>
<Button
className="bg-success"
onClick={() => onSubmitToPlus(false)}
>
This is a {upload?.label}
</Button>
<Button
className="text-white"
variant="destructive"
onClick={() => onSubmitToPlus(true)}
>
This is not a {upload?.label}
</Button>
</DialogFooter>
</TransformWrapper>
</DialogContent>
</Dialog>
);
}

View File

@ -3,15 +3,8 @@ import { CamerasFilterButton } from "@/components/filter/CamerasFilterButton";
import { GeneralFilterContent } from "@/components/filter/ReviewFilterGroup"; import { GeneralFilterContent } from "@/components/filter/ReviewFilterGroup";
import Chip from "@/components/indicators/Chip"; import Chip from "@/components/indicators/Chip";
import ActivityIndicator from "@/components/indicators/activity-indicator"; import ActivityIndicator from "@/components/indicators/activity-indicator";
import { FrigatePlusDialog } from "@/components/overlay/dialog/FrigatePlusDialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { import {
DropdownMenu, DropdownMenu,
@ -45,12 +38,11 @@ import { LuFolderX } from "react-icons/lu";
import { PiSlidersHorizontalFill } from "react-icons/pi"; import { PiSlidersHorizontalFill } from "react-icons/pi";
import useSWR from "swr"; import useSWR from "swr";
import useSWRInfinite from "swr/infinite"; import useSWRInfinite from "swr/infinite";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
const API_LIMIT = 100; const API_LIMIT = 100;
export default function SubmitPlus() { export default function SubmitPlus() {
const { data: config } = useSWR<FrigateConfig>("config"); // title
useEffect(() => { useEffect(() => {
document.title = "Plus - Frigate"; document.title = "Plus - Frigate";
@ -155,41 +147,41 @@ export default function SubmitPlus() {
[isValidating, isDone, size, setSize], [isValidating, isDone, size, setSize],
); );
// layout return (
<div className="flex size-full flex-col">
const grow = useMemo(() => { <div className="scrollbar-container flex h-16 w-full items-center justify-between overflow-x-auto px-2">
if (!config || !upload) { <PlusFilterGroup
return ""; selectedCameras={selectedCameras}
} selectedLabels={selectedLabels}
selectedScoreRange={scoreRange}
const camera = config.cameras[upload.camera]; setSelectedCameras={setSelectedCameras}
setSelectedLabels={setSelectedLabels}
if (!camera) { setSelectedScoreRange={setScoreRange}
return ""; />
} <PlusSortSelector selectedSort={sort} setSelectedSort={setSort} />
</div>
if (camera.detect.width / camera.detect.height < 16 / 9) { <div className="no-scrollbar flex size-full flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
return "aspect-video object-contain"; {!events?.length ? (
} <>
{isValidating ? (
return ""; <ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
}, [config, upload]); ) : (
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
const onSubmitToPlus = useCallback( <LuFolderX className="size-16" />
async (falsePositive: boolean) => { No snapshots found
if (!upload) { </div>
return; )}
} </>
) : (
falsePositive <>
? axios.put(`events/${upload.id}/false_positive`) <div className="grid w-full gap-2 p-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
: axios.post(`events/${upload.id}/plus`, { <FrigatePlusDialog
include_annotation: 1, upload={upload}
}); onClose={() => setUpload(undefined)}
onEventUploaded={() => {
refresh( refresh(
(data: Event[][] | undefined) => { (data: Event[][] | undefined) => {
if (!data) { if (!data || !upload) {
return data; return data;
} }
@ -221,96 +213,8 @@ export default function SubmitPlus() {
}, },
{ revalidate: false, populateCache: true }, { revalidate: false, populateCache: true },
); );
setUpload(undefined);
},
[refresh, upload],
);
return (
<div className="flex size-full flex-col">
<div className="scrollbar-container flex h-16 w-full items-center justify-between overflow-x-auto px-2">
<PlusFilterGroup
selectedCameras={selectedCameras}
selectedLabels={selectedLabels}
selectedScoreRange={scoreRange}
setSelectedCameras={setSelectedCameras}
setSelectedLabels={setSelectedLabels}
setSelectedScoreRange={setScoreRange}
/>
<PlusSortSelector selectedSort={sort} setSelectedSort={setSort} />
</div>
<div className="no-scrollbar flex size-full flex-1 flex-wrap content-start gap-2 overflow-y-auto md:gap-4">
{!events?.length ? (
<>
{isValidating ? (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
) : (
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
<LuFolderX className="size-16" />
No snapshots found
</div>
)}
</>
) : (
<>
<div className="grid w-full gap-2 p-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
<Dialog
open={upload != undefined}
onOpenChange={(open) => (!open ? setUpload(undefined) : null)}
>
<DialogContent className="md:max-w-3xl lg:max-w-4xl xl:max-w-7xl">
<TransformWrapper
minScale={1.0}
wheel={{ smoothStep: 0.005 }}
>
<DialogHeader>
<DialogTitle>Submit To Frigate+</DialogTitle>
<DialogDescription>
Objects in locations you want to avoid are not false
positives. Submitting them as false positives will
confuse the model.
</DialogDescription>
</DialogHeader>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%",
}} }}
contentStyle={{
position: "relative",
width: "100%",
height: "100%",
}}
>
{upload?.id && (
<img
className={`w-full ${grow} bg-black`}
src={`${baseUrl}api/events/${upload?.id}/snapshot.jpg`}
alt={`${upload?.label}`}
/> />
)}
</TransformComponent>
<DialogFooter>
<Button onClick={() => setUpload(undefined)}>
Cancel
</Button>
<Button
className="bg-success"
onClick={() => onSubmitToPlus(false)}
>
This is a {upload?.label}
</Button>
<Button
className="text-white"
variant="destructive"
onClick={() => onSubmitToPlus(true)}
>
This is not a {upload?.label}
</Button>
</DialogFooter>
</TransformWrapper>
</DialogContent>
</Dialog>
{events?.map((event) => { {events?.map((event) => {
if (event.data.type != "object" || event.plus_id) { if (event.data.type != "object" || event.plus_id) {