diff --git a/frontend/src/pages/Settings/components/Layout.tsx b/frontend/src/pages/Settings/components/Layout.tsx index 7aae8a991..623893fff 100644 --- a/frontend/src/pages/Settings/components/Layout.tsx +++ b/frontend/src/pages/Settings/components/Layout.tsx @@ -10,30 +10,13 @@ import { Badge, Container, Group, LoadingOverlay } from "@mantine/core"; import { useForm } from "@mantine/form"; import { useDocumentTitle } from "@mantine/hooks"; import { FunctionComponent, ReactNode, useCallback, useMemo } from "react"; -import { enabledLanguageKey, languageProfileKey } from "../keys"; import { FormContext, FormValues } from "../utilities/FormValues"; +import { + SubmitHooksProvider, + useSubmitHooksSource, +} from "../utilities/HooksProvider"; import { SettingsProvider } from "../utilities/SettingsProvider"; -type SubmitHookType = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: (value: any) => unknown; -}; - -export const submitHooks: SubmitHookType = { - [languageProfileKey]: (value) => JSON.stringify(value), - [enabledLanguageKey]: (value: Language.Info[]) => value.map((v) => v.code2), -}; - -function invokeHooks(settings: LooseObject) { - for (const key in settings) { - if (key in submitHooks) { - const value = settings[key]; - const fn = submitHooks[key]; - settings[key] = fn(value); - } - } -} - interface Props { name: string; children: ReactNode; @@ -45,6 +28,8 @@ const Layout: FunctionComponent = (props) => { const { data: settings, isLoading, isRefetching } = useSystemSettings(); const { mutate, isLoading: isMutating } = useSettingsMutation(); + const submitHooks = useSubmitHooksSource(); + const form = useForm({ initialValues: { settings: {}, @@ -66,7 +51,7 @@ const Layout: FunctionComponent = (props) => { if (Object.keys(settings).length > 0) { const settingsToSubmit = { ...settings }; - invokeHooks(settingsToSubmit); + submitHooks.invoke(settingsToSubmit); LOG("info", "submitting settings", settingsToSubmit); mutate(settingsToSubmit); } @@ -77,7 +62,7 @@ const Layout: FunctionComponent = (props) => { updateStorage(storagesToSubmit); } }, - [mutate, updateStorage] + [mutate, submitHooks, updateStorage] ); const totalStagedCount = useMemo(() => { @@ -100,30 +85,36 @@ const Layout: FunctionComponent = (props) => { return ( -
- - - - - - - - {children} - - -
+ +
+ + + + + + + + {children} + + +
+
); diff --git a/frontend/src/pages/Settings/utilities/HooksProvider.tsx b/frontend/src/pages/Settings/utilities/HooksProvider.tsx new file mode 100644 index 000000000..2c49f174a --- /dev/null +++ b/frontend/src/pages/Settings/utilities/HooksProvider.tsx @@ -0,0 +1,95 @@ +import { + createContext, + FunctionComponent, + useCallback, + useContext, + useMemo, + useRef, + useState, +} from "react"; +import { enabledLanguageKey, languageProfileKey } from "../keys"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type HookType = (value: any) => unknown; + +export type SubmitHookType = { + [key: string]: HookType; +}; + +export type SubmitHookModifierType = { + add: (key: string, fn: HookType) => void; + remove: (key: string) => void; + invoke: (settings: LooseObject) => void; +}; + +const SubmitHooksContext = createContext(null); + +type SubmitHooksProviderProps = { + value: SubmitHookModifierType; +}; + +export const SubmitHooksProvider: FunctionComponent< + SubmitHooksProviderProps +> = ({ value, children }) => { + return ( + + {children} + + ); +}; + +export function useSubmitHooks() { + const context = useContext(SubmitHooksContext); + + if (context === null) { + throw new Error( + "useSubmitHooksModifier must be used within a SubmitHooksProvider" + ); + } + + return context; +} + +export function useSubmitHooksSource(): SubmitHookModifierType { + const [submitHooks, setSubmitHooks] = useState({ + [languageProfileKey]: (value) => JSON.stringify(value), + [enabledLanguageKey]: (value: Language.Info[]) => value.map((v) => v.code2), + }); + const hooksRef = useRef(submitHooks); + + const invokeHooks = useCallback((settings: LooseObject) => { + const hooks = hooksRef.current; + for (const key in settings) { + if (key in hooks) { + const value = settings[key]; + const fn = hooks[key]; + settings[key] = fn(value); + } + } + }, []); + + const addHook = useCallback( + (key: string, fn: (value: unknown) => unknown) => { + setSubmitHooks((hooks) => ({ ...hooks, [key]: fn })); + }, + [] + ); + + const removeHook = useCallback((key: string) => { + setSubmitHooks((hooks) => { + const newHooks = { ...hooks }; + delete newHooks[key]; + + return newHooks; + }); + }, []); + + return useMemo( + () => ({ + add: addHook, + remove: removeHook, + invoke: invokeHooks, + }), + [addHook, invokeHooks, removeHook] + ); +} diff --git a/frontend/src/pages/Settings/utilities/hooks.ts b/frontend/src/pages/Settings/utilities/hooks.ts index 09f054cc3..5f011e7e9 100644 --- a/frontend/src/pages/Settings/utilities/hooks.ts +++ b/frontend/src/pages/Settings/utilities/hooks.ts @@ -1,13 +1,13 @@ import { LOG } from "@/utilities/console"; import { get, isNull, isUndefined, uniqBy } from "lodash"; import { useCallback, useEffect, useMemo, useRef } from "react"; -import { submitHooks } from "../components"; import { FormKey, useFormActions, useStagedValues, } from "../utilities/FormValues"; import { useSettings } from "../utilities/SettingsProvider"; +import { useSubmitHooks } from "./HooksProvider"; export interface BaseInput { disabled?: boolean; @@ -52,20 +52,22 @@ export function useSettingValue( const optionsRef = useRef(options); + const submitHooks = useSubmitHooks(); + useEffect(() => { const onSubmit = optionsRef.current?.onSubmit; if (onSubmit) { LOG("info", "Adding submit hook for", key); - submitHooks[key] = onSubmit; + submitHooks.add(key, onSubmit); } return () => { if (key in submitHooks) { LOG("info", "Removing submit hook for", key); - delete submitHooks[key]; + submitHooks.remove(key); } }; - }, [key]); + }, [key, submitHooks]); const originalValue = useMemo(() => { const onLoaded = optionsRef.current?.onLoaded;