mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-13 20:07:36 +03:00
add reset logic to global config view
This commit is contained in:
parent
f9e5969b7d
commit
dbc8e4c2d1
@ -1,7 +1,7 @@
|
|||||||
// Global Configuration View
|
// Global Configuration View
|
||||||
// Main view for configuring global Frigate settings
|
// 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 useSWR from "swr";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -21,6 +21,7 @@ import type { RJSFSchema } from "@rjsf/utils";
|
|||||||
import type { FrigateConfig } from "@/types/frigateConfig";
|
import type { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { extractSchemaSection } from "@/lib/config-schema";
|
import { extractSchemaSection } from "@/lib/config-schema";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
@ -380,6 +381,7 @@ interface GlobalConfigSectionProps {
|
|||||||
schema: RJSFSchema | null;
|
schema: RJSFSchema | null;
|
||||||
config: FrigateConfig | undefined;
|
config: FrigateConfig | undefined;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalConfigSection = memo(function GlobalConfigSection({
|
const GlobalConfigSection = memo(function GlobalConfigSection({
|
||||||
@ -387,6 +389,7 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
schema,
|
schema,
|
||||||
config,
|
config,
|
||||||
onSave,
|
onSave,
|
||||||
|
title,
|
||||||
}: GlobalConfigSectionProps) {
|
}: GlobalConfigSectionProps) {
|
||||||
const sectionConfig = globalSectionConfigs[sectionKey];
|
const sectionConfig = globalSectionConfigs[sectionKey];
|
||||||
const { t } = useTranslation([
|
const { t } = useTranslation([
|
||||||
@ -396,19 +399,52 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
]);
|
]);
|
||||||
const [pendingData, setPendingData] = useState<unknown | null>(null);
|
const [pendingData, setPendingData] = useState<unknown | null>(null);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [formKey, setFormKey] = useState(0);
|
||||||
|
const isResettingRef = useRef(false);
|
||||||
|
|
||||||
const formData = useMemo((): unknown => {
|
const formData = useMemo((): unknown => {
|
||||||
if (!config) return {};
|
if (!config) return {};
|
||||||
return (config as unknown as Record<string, unknown>)[sectionKey];
|
return (config as unknown as Record<string, unknown>)[sectionKey];
|
||||||
}, [config, sectionKey]);
|
}, [config, sectionKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPendingData(null);
|
||||||
|
}, [formData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isResettingRef.current) {
|
||||||
|
isResettingRef.current = false;
|
||||||
|
}
|
||||||
|
}, [formKey]);
|
||||||
|
|
||||||
const hasChanges = useMemo(() => {
|
const hasChanges = useMemo(() => {
|
||||||
if (!pendingData) return false;
|
if (!pendingData) return false;
|
||||||
return !isEqual(formData, pendingData);
|
return !isEqual(formData, pendingData);
|
||||||
}, [formData, pendingData]);
|
}, [formData, pendingData]);
|
||||||
|
|
||||||
const handleChange = useCallback((data: unknown) => {
|
const handleChange = useCallback(
|
||||||
setPendingData(data);
|
(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 () => {
|
const handleSave = useCallback(async () => {
|
||||||
@ -450,7 +486,16 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Heading as="h4">{title}</Heading>
|
||||||
|
{hasChanges && (
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
{t("modified", { ns: "common", defaultValue: "Modified" })}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ConfigForm
|
<ConfigForm
|
||||||
|
key={formKey}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
formData={pendingData || formData}
|
formData={pendingData || formData}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -463,7 +508,7 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-2">
|
<div className="flex items-center justify-between pt-2">
|
||||||
<div>
|
<div className="flex items-center gap-2">
|
||||||
{hasChanges && (
|
{hasChanges && (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{t("unsavedChanges", {
|
{t("unsavedChanges", {
|
||||||
@ -473,16 +518,28 @@ const GlobalConfigSection = memo(function GlobalConfigSection({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div className="flex items-center gap-2">
|
||||||
onClick={handleSave}
|
{hasChanges && (
|
||||||
disabled={!hasChanges || isSaving}
|
<Button
|
||||||
className="gap-2"
|
onClick={handleReset}
|
||||||
>
|
variant="outline"
|
||||||
<LuSave className="h-4 w-4" />
|
disabled={isSaving}
|
||||||
{isSaving
|
className="gap-2"
|
||||||
? t("saving", { ns: "common", defaultValue: "Saving..." })
|
>
|
||||||
: t("save", { ns: "common", defaultValue: "Save" })}
|
{t("reset", { ns: "common", defaultValue: "Reset" })}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!hasChanges || isSaving}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<LuSave className="h-4 w-4" />
|
||||||
|
{isSaving
|
||||||
|
? t("saving", { ns: "common", defaultValue: "Saving..." })
|
||||||
|
: t("save", { ns: "common", defaultValue: "Save" })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -679,57 +736,61 @@ export default function GlobalConfigView() {
|
|||||||
|
|
||||||
{activeTab === "system" && (
|
{activeTab === "system" && (
|
||||||
<>
|
<>
|
||||||
{systemSections.map((sectionKey) => (
|
{systemSections.map((sectionKey) => {
|
||||||
<div
|
const sectionTitle = t("label", {
|
||||||
key={sectionKey}
|
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
||||||
className={cn(
|
defaultValue:
|
||||||
activeSection === sectionKey ? "block" : "hidden",
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
)}
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
>
|
});
|
||||||
<Heading as="h4" className="mb-4">
|
|
||||||
{t("label", {
|
return (
|
||||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
<div
|
||||||
defaultValue:
|
key={sectionKey}
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
className={cn(
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
activeSection === sectionKey ? "block" : "hidden",
|
||||||
})}
|
)}
|
||||||
</Heading>
|
>
|
||||||
<GlobalConfigSection
|
<GlobalConfigSection
|
||||||
sectionKey={sectionKey}
|
sectionKey={sectionKey}
|
||||||
schema={extractSchemaSection(schema, sectionKey)}
|
schema={extractSchemaSection(schema, sectionKey)}
|
||||||
config={config}
|
config={config}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
title={sectionTitle}
|
||||||
</div>
|
/>
|
||||||
))}
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "integrations" && (
|
{activeTab === "integrations" && (
|
||||||
<>
|
<>
|
||||||
{integrationSections.map((sectionKey) => (
|
{integrationSections.map((sectionKey) => {
|
||||||
<div
|
const sectionTitle = t("label", {
|
||||||
key={sectionKey}
|
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
||||||
className={cn(
|
defaultValue:
|
||||||
activeSection === sectionKey ? "block" : "hidden",
|
sectionKey.charAt(0).toUpperCase() +
|
||||||
)}
|
sectionKey.slice(1).replace(/_/g, " "),
|
||||||
>
|
});
|
||||||
<Heading as="h4" className="mb-4">
|
|
||||||
{t("label", {
|
return (
|
||||||
ns: globalSectionConfigs[sectionKey].i18nNamespace,
|
<div
|
||||||
defaultValue:
|
key={sectionKey}
|
||||||
sectionKey.charAt(0).toUpperCase() +
|
className={cn(
|
||||||
sectionKey.slice(1).replace(/_/g, " "),
|
activeSection === sectionKey ? "block" : "hidden",
|
||||||
})}
|
)}
|
||||||
</Heading>
|
>
|
||||||
<GlobalConfigSection
|
<GlobalConfigSection
|
||||||
sectionKey={sectionKey}
|
sectionKey={sectionKey}
|
||||||
schema={extractSchemaSection(schema, sectionKey)}
|
schema={extractSchemaSection(schema, sectionKey)}
|
||||||
config={config}
|
config={config}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
title={sectionTitle}
|
||||||
</div>
|
/>
|
||||||
))}
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user