diff --git a/web/public/locales/en/common.json b/web/public/locales/en/common.json index 64289942ce..e5b3499a2d 100644 --- a/web/public/locales/en/common.json +++ b/web/public/locales/en/common.json @@ -131,6 +131,7 @@ "close": "Close", "expand": "Expand", "collapse": "Collapse", + "clear": "Clear", "copy": "Copy", "copiedToClipboard": "Copied to clipboard", "back": "Back", diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 2e551f2e4b..40e6eb7606 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -180,12 +180,14 @@ "storedLayouts": { "title": "Stored Layouts", "desc": "The layout of cameras in a camera group can be dragged/resized. The positions are stored in your browser's local storage.", - "clearAll": "Clear All Layouts" + "clearAll": "Clear All Layouts", + "clearConfirm": "This will clear the stored layout for every camera group in this browser. This cannot be undone." }, "cameraGroupStreaming": { "title": "Camera Group Streaming Settings", "desc": "Streaming settings for each camera group are stored in your browser's local storage.", - "clearAll": "Clear All Streaming Settings" + "clearAll": "Clear All Streaming Settings", + "clearConfirm": "This will clear the streaming settings for every camera group in this browser. This cannot be undone." }, "backupRestore": { "title": "Backup & Restore", diff --git a/web/src/views/settings/UiSettingsView.tsx b/web/src/views/settings/UiSettingsView.tsx index a07db18cbe..dec53cc455 100644 --- a/web/src/views/settings/UiSettingsView.tsx +++ b/web/src/views/settings/UiSettingsView.tsx @@ -12,7 +12,7 @@ import { useState, } from "react"; import { toast } from "sonner"; -import { Button } from "../../components/ui/button"; +import { Button, buttonVariants } from "../../components/ui/button"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { @@ -36,6 +36,17 @@ import { CONTROL_COLUMN_CLASS_NAME, } from "@/components/card/SettingsGroupCard"; import Heading from "@/components/ui/heading"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { cn } from "@/lib/utils"; import ImportUiSettingsDialog from "@/components/overlay/dialog/ImportUiSettingsDialog"; import { applyImportPayload, @@ -54,6 +65,8 @@ import { const WEEK_STARTS_ON = ["Sunday", "Monday"]; const IMPORT_FAILED_FLAG = "frigate-ui-settings-import-failed"; +type ClearTarget = "layouts" | "streaming"; + type SwitchSettingRowProps = { id: string; label: string; @@ -204,6 +217,24 @@ export default function UiSettingsView() { }); }, [config, t, username]); + const [pendingClear, setPendingClear] = useState(null); + + const clearConfirmCopy = useCallback( + (target: ClearTarget) => + // literal keys per branch: a template key would be invisible to + // npm run i18n:extract, which CI verifies + target === "layouts" + ? { + title: t("general.storedLayouts.clearAll"), + description: t("general.storedLayouts.clearConfirm"), + } + : { + title: t("general.cameraGroupStreaming.clearAll"), + description: t("general.cameraGroupStreaming.clearConfirm"), + }, + [t], + ); + const fileInputRef = useRef(null); const [pendingImport, setPendingImport] = useState<{ name: string; @@ -450,7 +481,7 @@ export default function UiSettingsView() { id="stored-layouts-clear" aria-label={t("general.storedLayouts.clearAll")} className="w-full md:w-auto" - onClick={clearStoredLayouts} + onClick={() => setPendingClear("layouts")} > {t("general.storedLayouts.clearAll")} @@ -466,7 +497,7 @@ export default function UiSettingsView() { id="camera-group-streaming-clear" aria-label={t("general.cameraGroupStreaming.clearAll")} className="w-full md:w-auto" - onClick={clearStreamingSettings} + onClick={() => setPendingClear("streaming")} > {t("general.cameraGroupStreaming.clearAll")} @@ -604,6 +635,45 @@ export default function UiSettingsView() { onConfirm={handleImportConfirm} /> )} + + { + if (!open) { + setPendingClear(null); + } + }} + > + + + + {pendingClear && clearConfirmCopy(pendingClear).title} + + + {pendingClear && clearConfirmCopy(pendingClear).description} + + + + + {t("button.cancel", { ns: "common" })} + + { + if (pendingClear === "layouts") { + clearStoredLayouts(); + } else { + clearStreamingSettings(); + } + + setPendingClear(null); + }} + > + {t("button.clear", { ns: "common" })} + + + + ); }