import { useCallback, useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "../ui/dialog"; 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"; import { Input } from "../ui/input"; import { TimeRange } from "@/types/timeline"; import useSWR from "swr"; import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue, } from "../ui/select"; import { isDesktop, isMobile } from "react-device-detect"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import SaveExportOverlay from "./SaveExportOverlay"; import { baseUrl } from "@/api/baseUrl"; import { cn } from "@/lib/utils"; import { GenericVideoPlayer } from "../player/GenericVideoPlayer"; import { useTranslation } from "react-i18next"; import { ExportCase } from "@/types/export"; import { CustomTimeSelector } from "./CustomTimeSelector"; const EXPORT_OPTIONS = [ "1", "4", "8", "12", "24", "timeline", "custom", ] as const; type ExportOption = (typeof EXPORT_OPTIONS)[number]; type ExportDialogProps = { camera: string; latestTime: number; currentTime: number; range?: TimeRange; mode: ExportMode; showPreview: boolean; setRange: (range: TimeRange | undefined) => void; setMode: (mode: ExportMode) => void; setShowPreview: (showPreview: boolean) => void; }; export default function ExportDialog({ camera, latestTime, currentTime, range, mode, showPreview, setRange, setMode, setShowPreview, }: ExportDialogProps) { const { t } = useTranslation(["components/dialog"]); const [name, setName] = useState(""); const [selectedCaseId, setSelectedCaseId] = useState( undefined, ); const onStartExport = useCallback(() => { if (!range) { toast.error(t("export.toast.error.noVaildTimeSelected"), { position: "top-center", }); return; } if (range.before < range.after) { toast.error(t("export.toast.error.endTimeMustAfterStartTime"), { position: "top-center", }); return; } axios .post( `export/${camera}/start/${Math.round(range.after)}/end/${Math.round(range.before)}`, { playback: "realtime", name, export_case_id: selectedCaseId || undefined, }, ) .then((response) => { if (response.status == 200) { toast.success(t("export.toast.success"), { position: "top-center", action: ( ), }); setName(""); setSelectedCaseId(undefined); setRange(undefined); setMode("none"); } }) .catch((error) => { const errorMessage = error.response?.data?.message || error.response?.data?.detail || "Unknown error"; toast.error( t("export.toast.error.failed", { error: errorMessage, }), { position: "top-center" }, ); }); }, [camera, name, range, selectedCaseId, setRange, setName, setMode, t]); const handleCancel = useCallback(() => { setName(""); setSelectedCaseId(undefined); setMode("none"); setRange(undefined); }, [setMode, setRange]); const Overlay = isDesktop ? Dialog : Drawer; const Trigger = isDesktop ? DialogTrigger : DrawerTrigger; const Content = isDesktop ? DialogContent : DrawerContent; return ( <> setShowPreview(true)} onSave={() => onStartExport()} onCancel={handleCancel} /> { if (!open) { setMode("none"); } }} > {!isDesktop && ( )} ); } type ExportContentProps = { latestTime: number; currentTime: number; range?: TimeRange; name: string; selectedCaseId?: string; onStartExport: () => void; setName: (name: string) => void; setSelectedCaseId: (caseId: string | undefined) => void; setRange: (range: TimeRange | undefined) => void; setMode: (mode: ExportMode) => void; onCancel: () => void; }; export function ExportContent({ latestTime, currentTime, range, name, selectedCaseId, onStartExport, setName, setSelectedCaseId, setRange, setMode, onCancel, }: ExportContentProps) { const { t } = useTranslation(["components/dialog"]); const [selectedOption, setSelectedOption] = useState("1"); const { data: cases } = useSWR("cases"); const onSelectTime = useCallback( (option: ExportOption) => { setSelectedOption(option); const now = new Date(latestTime * 1000); let start = 0; switch (option) { 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": start = latestTime - 3600; break; } setRange({ before: latestTime, after: start, }); }, [latestTime, setRange], ); return (
{isDesktop && ( <> {t("menu.export", { ns: "common" })} )} onSelectTime(value as ExportOption)} > {EXPORT_OPTIONS.map((opt) => { return (
); })}
{selectedOption == "custom" && ( )} setName(e.target.value)} />
{isDesktop && }
{t("button.cancel", { ns: "common" })}
); } type ExportPreviewDialogProps = { camera: string; range?: TimeRange; showPreview: boolean; setShowPreview: (showPreview: boolean) => void; }; export function ExportPreviewDialog({ camera, range, showPreview, setShowPreview, }: ExportPreviewDialogProps) { const { t } = useTranslation(["components/dialog"]); if (!range) { return null; } const source = `${baseUrl}vod/${camera}/start/${range.after}/end/${range.before}/index.m3u8`; return ( {t("export.fromTimeline.previewExport")} {t("export.fromTimeline.previewExport")} ); }