diff --git a/web/public/locales/en/components/dialog.json b/web/public/locales/en/components/dialog.json
index a56c2b1da..907a61add 100644
--- a/web/public/locales/en/components/dialog.json
+++ b/web/public/locales/en/components/dialog.json
@@ -48,6 +48,10 @@
"name": {
"placeholder": "Name the Export"
},
+ "case": {
+ "label": "Case",
+ "placeholder": "Select a case"
+ },
"select": "Select",
"export": "Export",
"selectOrExport": "Select or Export",
diff --git a/web/src/components/card/ExportCard.tsx b/web/src/components/card/ExportCard.tsx
index fc7964c18..c8d9c4c65 100644
--- a/web/src/components/card/ExportCard.tsx
+++ b/web/src/components/card/ExportCard.tsx
@@ -1,6 +1,6 @@
import ActivityIndicator from "../indicators/activity-indicator";
import { Button } from "../ui/button";
-import { useCallback, useState } from "react";
+import { useCallback, useMemo, useState } from "react";
import { isMobile } from "react-device-detect";
import { FiMoreVertical } from "react-icons/fi";
import { Skeleton } from "../ui/skeleton";
@@ -32,18 +32,37 @@ import { FaFolder } from "react-icons/fa";
type CaseCardProps = {
className: string;
exportCase: ExportCase;
+ exports: Export[];
onSelect: () => void;
};
-export function CaseCard({ className, exportCase, onSelect }: CaseCardProps) {
+export function CaseCard({
+ className,
+ exportCase,
+ exports,
+ onSelect,
+}: CaseCardProps) {
+ const firstExport = useMemo(
+ () => exports.find((exp) => exp.thumb_path && exp.thumb_path.length > 0),
+ [exports],
+ );
+
return (
onSelect()}
>
-
+ {firstExport && (
+

+ )}
+
+
diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx
index 976b20042..5d68d88db 100644
--- a/web/src/components/overlay/ExportDialog.tsx
+++ b/web/src/components/overlay/ExportDialog.tsx
@@ -22,7 +22,14 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { TimezoneAwareCalendar } from "./ReviewActivityCalendar";
-import { SelectSeparator } from "../ui/select";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+} from "../ui/select";
import { isDesktop, isIOS, isMobile } from "react-device-detect";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import SaveExportOverlay from "./SaveExportOverlay";
@@ -31,6 +38,7 @@ 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";
const EXPORT_OPTIONS = [
"1",
@@ -67,6 +75,9 @@ export default function ExportDialog({
}: ExportDialogProps) {
const { t } = useTranslation(["components/dialog"]);
const [name, setName] = useState("");
+ const [selectedCaseId, setSelectedCaseId] = useState
(
+ undefined,
+ );
const onStartExport = useCallback(() => {
if (!range) {
@@ -89,6 +100,7 @@ export default function ExportDialog({
{
playback: "realtime",
name,
+ export_case_id: selectedCaseId || undefined,
},
)
.then((response) => {
@@ -102,6 +114,7 @@ export default function ExportDialog({
),
});
setName("");
+ setSelectedCaseId(undefined);
setRange(undefined);
setMode("none");
}
@@ -118,10 +131,11 @@ export default function ExportDialog({
{ position: "top-center" },
);
});
- }, [camera, name, range, setRange, setName, setMode, t]);
+ }, [camera, name, range, selectedCaseId, setRange, setName, setMode, t]);
const handleCancel = useCallback(() => {
setName("");
+ setSelectedCaseId(undefined);
setMode("none");
setRange(undefined);
}, [setMode, setRange]);
@@ -190,8 +204,10 @@ export default function ExportDialog({
currentTime={currentTime}
range={range}
name={name}
+ selectedCaseId={selectedCaseId}
onStartExport={onStartExport}
setName={setName}
+ setSelectedCaseId={setSelectedCaseId}
setRange={setRange}
setMode={setMode}
onCancel={handleCancel}
@@ -207,8 +223,10 @@ type ExportContentProps = {
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;
@@ -218,14 +236,17 @@ export function ExportContent({
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) => {
@@ -320,6 +341,44 @@ export function ExportContent({
value={name}
onChange={(e) => setName(e.target.value)}
/>
+
+
+
+
{isDesktop && }
(
+ undefined,
+ );
const onStartExport = useCallback(() => {
if (!range) {
toast.error(t("toast.error.noValidTimeSelected"), {
@@ -96,6 +99,7 @@ export default function MobileReviewSettingsDrawer({
{
playback: "realtime",
name,
+ export_case_id: selectedCaseId || undefined,
},
)
.then((response) => {
@@ -114,6 +118,7 @@ export default function MobileReviewSettingsDrawer({
},
);
setName("");
+ setSelectedCaseId(undefined);
setRange(undefined);
setMode("none");
}
@@ -133,7 +138,7 @@ export default function MobileReviewSettingsDrawer({
},
);
});
- }, [camera, name, range, setRange, setName, setMode, t]);
+ }, [camera, name, range, selectedCaseId, setRange, setName, setMode, t]);
// filters
@@ -200,8 +205,10 @@ export default function MobileReviewSettingsDrawer({
currentTime={currentTime}
range={range}
name={name}
+ selectedCaseId={selectedCaseId}
onStartExport={onStartExport}
setName={setName}
+ setSelectedCaseId={setSelectedCaseId}
setRange={setRange}
setMode={(mode) => {
setMode(mode);
@@ -213,6 +220,7 @@ export default function MobileReviewSettingsDrawer({
onCancel={() => {
setMode("none");
setRange(undefined);
+ setSelectedCaseId(undefined);
setDrawerMode("select");
}}
/>
diff --git a/web/src/pages/Exports.tsx b/web/src/pages/Exports.tsx
index 5b4a3ef3a..2f4cbd53d 100644
--- a/web/src/pages/Exports.tsx
+++ b/web/src/pages/Exports.tsx
@@ -321,6 +321,7 @@ function Exports() {
search={search}
cases={filteredCases}
exports={exports}
+ exportsByCase={exportsByCase}
setSelectedCaseId={setSelectedCaseId}
setSelected={setSelected}
renameClip={onHandleRename}
@@ -337,6 +338,7 @@ type AllExportsViewProps = {
search: string;
cases?: ExportCase[];
exports: Export[];
+ exportsByCase: { [caseId: string]: Export[] };
setSelectedCaseId: (id: string) => void;
setSelected: (e: Export) => void;
renameClip: (id: string, update: string) => void;
@@ -348,6 +350,7 @@ function AllExportsView({
search,
cases,
exports,
+ exportsByCase,
setSelectedCaseId,
setSelected,
renameClip,
@@ -404,6 +407,7 @@ function AllExportsView({
: "hidden"
}
exportCase={item}
+ exports={exportsByCase[item.id] || []}
onSelect={() => {
setSelectedCaseId(item.id);
}}