From dbc8e4c2d15cdcc105da81922212a6e0b062ed63 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sun, 25 Jan 2026 11:20:22 -0600 Subject: [PATCH] add reset logic to global config view --- web/src/views/settings/GlobalConfigView.tsx | 181 +++++++++++++------- 1 file changed, 121 insertions(+), 60 deletions(-) diff --git a/web/src/views/settings/GlobalConfigView.tsx b/web/src/views/settings/GlobalConfigView.tsx index 454a1b459..41e59c086 100644 --- a/web/src/views/settings/GlobalConfigView.tsx +++ b/web/src/views/settings/GlobalConfigView.tsx @@ -1,7 +1,7 @@ // Global Configuration View // Main view for configuring global Frigate settings -import { useMemo, useCallback, useState, memo } from "react"; +import { useMemo, useCallback, useState, useEffect, memo, useRef } from "react"; import useSWR from "swr"; import axios from "axios"; import { toast } from "sonner"; @@ -21,6 +21,7 @@ import type { RJSFSchema } from "@rjsf/utils"; import type { FrigateConfig } from "@/types/frigateConfig"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; import { extractSchemaSection } from "@/lib/config-schema"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import Heading from "@/components/ui/heading"; @@ -380,6 +381,7 @@ interface GlobalConfigSectionProps { schema: RJSFSchema | null; config: FrigateConfig | undefined; onSave: () => void; + title: string; } const GlobalConfigSection = memo(function GlobalConfigSection({ @@ -387,6 +389,7 @@ const GlobalConfigSection = memo(function GlobalConfigSection({ schema, config, onSave, + title, }: GlobalConfigSectionProps) { const sectionConfig = globalSectionConfigs[sectionKey]; const { t } = useTranslation([ @@ -396,19 +399,52 @@ const GlobalConfigSection = memo(function GlobalConfigSection({ ]); const [pendingData, setPendingData] = useState(null); const [isSaving, setIsSaving] = useState(false); + const [formKey, setFormKey] = useState(0); + const isResettingRef = useRef(false); const formData = useMemo((): unknown => { if (!config) return {}; return (config as unknown as Record)[sectionKey]; }, [config, sectionKey]); + useEffect(() => { + setPendingData(null); + }, [formData]); + + useEffect(() => { + if (isResettingRef.current) { + isResettingRef.current = false; + } + }, [formKey]); + const hasChanges = useMemo(() => { if (!pendingData) return false; return !isEqual(formData, pendingData); }, [formData, pendingData]); - const handleChange = useCallback((data: unknown) => { - setPendingData(data); + const handleChange = useCallback( + (data: unknown) => { + if (isResettingRef.current) { + setPendingData(null); + return; + } + if (!data || typeof data !== "object") { + setPendingData(null); + return; + } + if (isEqual(formData, data)) { + setPendingData(null); + return; + } + setPendingData(data); + }, + [formData], + ); + + const handleReset = useCallback(() => { + isResettingRef.current = true; + setPendingData(null); + setFormKey((prev) => prev + 1); }, []); const handleSave = useCallback(async () => { @@ -450,7 +486,16 @@ const GlobalConfigSection = memo(function GlobalConfigSection({ return (
+
+ {title} + {hasChanges && ( + + {t("modified", { ns: "common", defaultValue: "Modified" })} + + )} +
-
+
{hasChanges && ( {t("unsavedChanges", { @@ -473,16 +518,28 @@ const GlobalConfigSection = memo(function GlobalConfigSection({ )}
- +
+ {hasChanges && ( + + )} + +
); @@ -679,57 +736,61 @@ export default function GlobalConfigView() { {activeTab === "system" && ( <> - {systemSections.map((sectionKey) => ( -
- - {t("label", { - ns: globalSectionConfigs[sectionKey].i18nNamespace, - defaultValue: - sectionKey.charAt(0).toUpperCase() + - sectionKey.slice(1).replace(/_/g, " "), - })} - - -
- ))} + {systemSections.map((sectionKey) => { + const sectionTitle = t("label", { + ns: globalSectionConfigs[sectionKey].i18nNamespace, + defaultValue: + sectionKey.charAt(0).toUpperCase() + + sectionKey.slice(1).replace(/_/g, " "), + }); + + return ( +
+ +
+ ); + })} )} {activeTab === "integrations" && ( <> - {integrationSections.map((sectionKey) => ( -
- - {t("label", { - ns: globalSectionConfigs[sectionKey].i18nNamespace, - defaultValue: - sectionKey.charAt(0).toUpperCase() + - sectionKey.slice(1).replace(/_/g, " "), - })} - - -
- ))} + {integrationSections.map((sectionKey) => { + const sectionTitle = t("label", { + ns: globalSectionConfigs[sectionKey].i18nNamespace, + defaultValue: + sectionKey.charAt(0).toUpperCase() + + sectionKey.slice(1).replace(/_/g, " "), + }); + + return ( +
+ +
+ ); + })} )}