From e33da68fff411cbbfa28ae4977db829e1624d01e Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 18 May 2026 14:04:55 -0500 Subject: [PATCH] add reordering save text to camera management view --- web/public/locales/en/views/settings.json | 2 + .../views/settings/CameraManagementView.tsx | 98 ++++++++++++++++--- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index edfbb19360..05e272f76f 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -482,6 +482,8 @@ "disableDesc": "Enable a camera that is currently not visible in the UI and disabled in the configuration. A restart of Frigate is required after enabling.", "enableSuccess": "Enabled {{cameraName}} in configuration. Restart Frigate to apply the changes.", "reorderHandle": "Drag to reorder", + "saving": "Saving…", + "saved": "Saved", "friendlyName": { "edit": "Edit camera display name", "title": "Edit Display Name", diff --git a/web/src/views/settings/CameraManagementView.tsx b/web/src/views/settings/CameraManagementView.tsx index 08f6e9d8e4..83f2111c6c 100644 --- a/web/src/views/settings/CameraManagementView.tsx +++ b/web/src/views/settings/CameraManagementView.tsx @@ -15,6 +15,7 @@ import CameraEditForm from "@/components/settings/CameraEditForm"; import CameraWizardDialog from "@/components/settings/CameraWizardDialog"; import DeleteCameraDialog from "@/components/overlay/dialog/DeleteCameraDialog"; import { + LuCheck, LuExternalLink, LuGripVertical, LuPencil, @@ -52,6 +53,10 @@ import { SelectValue, } from "@/components/ui/select"; +const REORDER_SAVED_INDICATOR_MS = 1500; + +type ReorderSaveStatus = "idle" | "saving" | "saved"; + type CameraManagementViewProps = { setUnsavedChanges: React.Dispatch>; profileState?: ProfileState; @@ -113,6 +118,19 @@ export default function CameraManagementView({ }); }, [enabledCameras]); + const [reorderSaveStatus, setReorderSaveStatus] = + useState("idle"); + const reorderSavedTimerRef = useRef | null>( + null, + ); + useEffect(() => { + return () => { + if (reorderSavedTimerRef.current) { + clearTimeout(reorderSavedTimerRef.current); + } + }; + }, []); + const handleReorderDragEnd = useCallback(async () => { const current = orderedCamerasRef.current; if ( @@ -127,14 +145,26 @@ export default function CameraManagementView({ cameraUpdates[cam] = { ui: { order: i * 10 } }; }); + if (reorderSavedTimerRef.current) { + clearTimeout(reorderSavedTimerRef.current); + reorderSavedTimerRef.current = null; + } + setReorderSaveStatus("saving"); + try { await axios.put("config/set", { requires_restart: 0, config_data: { cameras: cameraUpdates }, }); await updateConfig(); + setReorderSaveStatus("saved"); + reorderSavedTimerRef.current = setTimeout(() => { + setReorderSaveStatus("idle"); + reorderSavedTimerRef.current = null; + }, REORDER_SAVED_INDICATOR_MS); } catch (error) { setOrderedCameras(enabledCameras); + setReorderSaveStatus("idle"); const errorMessage = axios.isAxiosError(error) && (error.response?.data?.message || error.response?.data?.detail) @@ -238,22 +268,27 @@ export default function CameraManagementView({

- - {orderedCameras.map((camera) => ( - - ))} - +
+ + {orderedCameras.map((camera) => ( + + ))} + + +

cameraManagement.streams.enableDesc @@ -373,6 +408,37 @@ export default function CameraManagementView({ ); } +type ReorderSaveStatusIndicatorProps = { + status: ReorderSaveStatus; +}; + +function ReorderSaveStatusIndicator({ + status, +}: ReorderSaveStatusIndicatorProps) { + const { t } = useTranslation(["views/settings"]); + return ( +

+ {status === "saving" && ( + + {t("cameraManagement.streams.saving")} + + )} + {status === "saved" && ( + + + {t("cameraManagement.streams.saved")} + + )} +
+ ); +} + type EnabledCameraRowProps = { camera: string; onConfigChanged: () => Promise;