Skip to content

Commit

Permalink
Try to fix languages profiles editor by introducing a new submit hook…
Browse files Browse the repository at this point in the history
…s source in the settings page #1924
  • Loading branch information
LASER-Yi committed Oct 25, 2022
1 parent 70fe145 commit c08ba5f
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 51 deletions.
85 changes: 38 additions & 47 deletions frontend/src/pages/Settings/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,6 +28,8 @@ const Layout: FunctionComponent<Props> = (props) => {
const { data: settings, isLoading, isRefetching } = useSystemSettings();
const { mutate, isLoading: isMutating } = useSettingsMutation();

const submitHooks = useSubmitHooksSource();

const form = useForm<FormValues>({
initialValues: {
settings: {},
Expand All @@ -66,7 +51,7 @@ const Layout: FunctionComponent<Props> = (props) => {

if (Object.keys(settings).length > 0) {
const settingsToSubmit = { ...settings };
invokeHooks(settingsToSubmit);
submitHooks.invoke(settingsToSubmit);
LOG("info", "submitting settings", settingsToSubmit);
mutate(settingsToSubmit);
}
Expand All @@ -77,7 +62,7 @@ const Layout: FunctionComponent<Props> = (props) => {
updateStorage(storagesToSubmit);
}
},
[mutate, updateStorage]
[mutate, submitHooks, updateStorage]
);

const totalStagedCount = useMemo(() => {
Expand All @@ -100,30 +85,36 @@ const Layout: FunctionComponent<Props> = (props) => {
return (
<SettingsProvider value={settings}>
<LoadingProvider value={isLoading || isMutating}>
<form onSubmit={form.onSubmit(submit)}>
<Toolbox>
<Group>
<Toolbox.Button
type="submit"
icon={faSave}
loading={isMutating}
disabled={totalStagedCount === 0}
rightIcon={
<Badge size="xs" radius="sm" hidden={totalStagedCount === 0}>
{totalStagedCount}
</Badge>
}
>
Save
</Toolbox.Button>
</Group>
</Toolbox>
<FormContext.Provider value={form}>
<Container size="xl" mx={0}>
{children}
</Container>
</FormContext.Provider>
</form>
<SubmitHooksProvider value={submitHooks}>
<form onSubmit={form.onSubmit(submit)}>
<Toolbox>
<Group>
<Toolbox.Button
type="submit"
icon={faSave}
loading={isMutating}
disabled={totalStagedCount === 0}
rightIcon={
<Badge
size="xs"
radius="sm"
hidden={totalStagedCount === 0}
>
{totalStagedCount}
</Badge>
}
>
Save
</Toolbox.Button>
</Group>
</Toolbox>
<FormContext.Provider value={form}>
<Container size="xl" mx={0}>
{children}
</Container>
</FormContext.Provider>
</form>
</SubmitHooksProvider>
</LoadingProvider>
</SettingsProvider>
);
Expand Down
95 changes: 95 additions & 0 deletions frontend/src/pages/Settings/utilities/HooksProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<SubmitHookModifierType | null>(null);

type SubmitHooksProviderProps = {
value: SubmitHookModifierType;
};

export const SubmitHooksProvider: FunctionComponent<
SubmitHooksProviderProps
> = ({ value, children }) => {
return (
<SubmitHooksContext.Provider value={value}>
{children}
</SubmitHooksContext.Provider>
);
};

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<SubmitHookType>({
[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]
);
}
10 changes: 6 additions & 4 deletions frontend/src/pages/Settings/utilities/hooks.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
disabled?: boolean;
Expand Down Expand Up @@ -52,20 +52,22 @@ export function useSettingValue<T>(

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;
Expand Down

0 comments on commit c08ba5f

Please sign in to comment.