Add export dialog functionality

This commit is contained in:
Nicolas Mowen 2024-03-26 12:50:07 -06:00
parent c763d9e89d
commit 63df9d4338
4 changed files with 111 additions and 12 deletions

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useCallback, useState } from "react";
import {
Dialog,
DialogClose,
@ -11,6 +11,10 @@ import {
import { Label } from "../ui/label";
import { RadioGroup, RadioGroupItem } from "../ui/radio-group";
import { Button } from "../ui/button";
import { ExportMode } from "@/types/filter";
import { FaArrowDown } from "react-icons/fa";
import axios from "axios";
import { toast } from "sonner";
const EXPORT_OPTIONS = [
"1",
@ -23,13 +27,87 @@ const EXPORT_OPTIONS = [
] as const;
type ExportOption = (typeof EXPORT_OPTIONS)[number];
export default function ExportDialog() {
type ExportDialogProps = {
camera: string;
mode: ExportMode;
setMode: (mode: ExportMode) => void;
};
export default function ExportDialog({
camera,
mode,
setMode,
}: ExportDialogProps) {
const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
const onStartExport = useCallback(() => {
const now = new Date();
let end = now.getTime() / 1000;
let start;
switch (selectedOption) {
case "1":
now.setHours(now.getHours() - 1);
start = now.getTime() / 1000;
break;
case "4":
now.setHours(now.getHours() - 4);
start = now.getTime() / 1000;
break;
case "8":
now.setHours(now.getHours() - 8);
start = now.getTime() / 1000;
break;
case "12":
now.setHours(now.getHours() - 12);
start = now.getTime() / 1000;
break;
case "24":
now.setHours(now.getHours() - 24);
start = now.getTime() / 1000;
break;
case "custom":
end = 0;
break;
}
axios
.post(`export/${camera}/start/${start}/end/${end}`, {
playback: "realtime",
})
.then((response) => {
if (response.status == 200) {
toast.success(
"Successfully started export. View the file in the /exports folder.",
{ position: "top-center" },
);
}
})
.catch((error) => {
if (error.response?.data?.message) {
toast.error(
`Failed to start export: ${error.response.data.message}`,
{ position: "top-center" },
);
} else {
toast.error(`Failed to start export: ${error.message}`, {
position: "top-center",
});
}
});
}, [camera, selectedOption]);
return (
<Dialog open>
<DialogTrigger>
<div></div>
<Dialog open={mode == "select"}>
<DialogTrigger asChild>
<Button
className="flex items-center gap-2"
variant="secondary"
size="sm"
onClick={() => setMode("select")}
>
<FaArrowDown className="p-1 fill-secondary bg-muted-foreground rounded-md" />
Export
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
@ -61,8 +139,19 @@ export default function ExportDialog() {
})}
</RadioGroup>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<Button variant="select" size="sm">
<DialogClose onClick={() => setMode("none")}>Cancel</DialogClose>
<Button
variant="select"
size="sm"
onClick={() => {
if (selectedOption == "timeline") {
setMode("timeline");
} else {
onStartExport();
setMode("none");
}
}}
>
{selectedOption == "timeline" ? "Select" : "Export"}
</Button>
</DialogFooter>

View File

@ -10,7 +10,6 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";
import axios from "axios";
import { useCallback, useState } from "react";
import useSWR from "swr";
@ -42,8 +41,6 @@ function Export() {
return (
<div className="size-full p-2 overflow-hidden flex flex-col">
<Toaster />
<AlertDialog
open={deleteClip != undefined}
onOpenChange={() => setDeleteClip(undefined)}

View File

@ -1,3 +1,5 @@
// allow any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type FilterType = { [searchKey: string]: any };
export type ExportMode = "select" | "timeline" | "none";

View File

@ -12,6 +12,7 @@ import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { useOverlayState } from "@/hooks/use-overlay-state";
import { ExportMode } from "@/types/filter";
import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview";
import {
@ -33,6 +34,7 @@ import { isDesktop, isMobile } from "react-device-detect";
import { FaCircle, FaVideo } from "react-icons/fa";
import { IoMdArrowRoundBack } from "react-icons/io";
import { useNavigate } from "react-router-dom";
import { Toaster } from "@/components/ui/sonner";
import useSWR from "swr";
const SEGMENT_DURATION = 30;
@ -75,6 +77,10 @@ export function RecordingView({
[reviewItems, mainCamera],
);
// export
const [exportMode, setExportMode] = useState<ExportMode>("none");
// timeline
const [timelineType, setTimelineType] = useOverlayState<TimelineType>(
@ -211,6 +217,7 @@ export function RecordingView({
return (
<div ref={contentRef} className="size-full flex flex-col">
<Toaster />
<div className={`w-full h-10 flex items-center justify-between pr-1`}>
<Button className="rounded-lg" onClick={() => navigate(-1)}>
<IoMdArrowRoundBack className="size-5 mr-[10px]" />
@ -246,7 +253,11 @@ export function RecordingView({
</DrawerContent>
</Drawer>
)}
<ExportDialog />
<ExportDialog
camera={mainCamera}
mode={exportMode}
setMode={setExportMode}
/>
<ReviewFilterGroup
filters={["date", "general"]}
reviewSummary={reviewSummary}
@ -285,7 +296,7 @@ export function RecordingView({
</div>
<div
className={`flex h-full mb-2 justify-center overflow-hidden ${isDesktop ? "" : "flex-col"}`}
className={`flex h-full my-2 justify-center overflow-hidden ${isDesktop ? "" : "flex-col"}`}
>
<div className="flex flex-1 flex-wrap">
<div