Plugin settings (#4143)

* Add backend support for plugin settings
* Add plugin settings config
* Add UI support for plugin settings
This commit is contained in:
WithoutPants
2023-10-18 14:09:13 +11:00
committed by GitHub
parent 06d8353f4f
commit 2b8718100b
24 changed files with 445 additions and 57 deletions

View File

@@ -13,6 +13,7 @@ import {
useConfigureDLNA,
useConfigureGeneral,
useConfigureInterface,
useConfigurePlugin,
useConfigureScraping,
useConfigureUI,
} from "src/core/StashService";
@@ -21,6 +22,7 @@ import { useToast } from "src/hooks/Toast";
import { withoutTypename } from "src/utils/data";
import { Icon } from "../Shared/Icon";
type PluginSettings = Record<string, Record<string, unknown>>;
export interface ISettingsContextState {
loading: boolean;
error: ApolloError | undefined;
@@ -30,6 +32,7 @@ export interface ISettingsContextState {
scraping: GQL.ConfigScrapingInput;
dlna: GQL.ConfigDlnaInput;
ui: IUIConfig;
plugins: PluginSettings;
// apikey isn't directly settable, so expose it here
apiKey: string;
@@ -40,28 +43,23 @@ export interface ISettingsContextState {
saveScraping: (input: Partial<GQL.ConfigScrapingInput>) => void;
saveDLNA: (input: Partial<GQL.ConfigDlnaInput>) => void;
saveUI: (input: Partial<IUIConfig>) => void;
savePluginSettings: (pluginID: string, input: {}) => void;
refetch: () => void;
}
export const SettingStateContext = React.createContext<ISettingsContextState>({
loading: false,
error: undefined,
general: {},
interface: {},
defaults: {},
scraping: {},
dlna: {},
ui: {},
apiKey: "",
saveGeneral: () => {},
saveInterface: () => {},
saveDefaults: () => {},
saveScraping: () => {},
saveDLNA: () => {},
saveUI: () => {},
refetch: () => {},
});
export const SettingStateContext =
React.createContext<ISettingsContextState | null>(null);
export const useSettings = () => {
const context = React.useContext(SettingStateContext);
if (context === null) {
throw new Error("useSettings must be used within a SettingsContext");
}
return context;
};
export const SettingsContext: React.FC = ({ children }) => {
const Toast = useToast();
@@ -97,6 +95,10 @@ export const SettingsContext: React.FC = ({ children }) => {
const [pendingUI, setPendingUI] = useState<{}>();
const [updateUIConfig] = useConfigureUI();
const [plugins, setPlugins] = useState<PluginSettings>({});
const [pendingPlugins, setPendingPlugins] = useState<PluginSettings>();
const [updatePluginConfig] = useConfigurePlugin();
const [updateSuccess, setUpdateSuccess] = useState<boolean>();
const [apiKey, setApiKey] = useState("");
@@ -132,6 +134,7 @@ export const SettingsContext: React.FC = ({ children }) => {
setScraping({ ...withoutTypename(data.configuration.scraping) });
setDLNA({ ...withoutTypename(data.configuration.dlna) });
setUI(data.configuration.ui);
setPlugins(data.configuration.plugins);
}, [data, error]);
const resetSuccess = useDebounce(() => setUpdateSuccess(undefined), 4000);
@@ -433,6 +436,63 @@ export const SettingsContext: React.FC = ({ children }) => {
});
}
// saves the configuration if no further changes are made after a half second
const savePluginConfig = useDebounce(async (input: PluginSettings) => {
try {
setUpdateSuccess(undefined);
for (const pluginID in input) {
await updatePluginConfig({
variables: {
plugin_id: pluginID,
input: input[pluginID],
},
});
}
setPendingPlugins(undefined);
onSuccess();
} catch (e) {
setSaveError(e);
}
}, 500);
useEffect(() => {
if (!pendingPlugins) {
return;
}
savePluginConfig(pendingPlugins);
}, [pendingPlugins, savePluginConfig]);
function savePluginSettings(
pluginID: string,
input: Record<string, unknown>
) {
if (!plugins) {
return;
}
setPlugins({
...plugins,
[pluginID]: input,
});
setPendingPlugins((current) => {
if (!current) {
// use full UI object to ensure nothing is wiped
return {
...plugins,
[pluginID]: input,
};
}
return {
...current,
[pluginID]: input,
};
});
}
function maybeRenderLoadingIndicator() {
if (updateSuccess === false) {
return (
@@ -448,7 +508,8 @@ export const SettingsContext: React.FC = ({ children }) => {
pendingDefaults ||
pendingScraping ||
pendingDLNA ||
pendingUI
pendingUI ||
pendingPlugins
) {
return (
<div className="loading-indicator">
@@ -480,6 +541,7 @@ export const SettingsContext: React.FC = ({ children }) => {
scraping,
dlna,
ui,
plugins,
saveGeneral,
saveInterface,
saveDefaults,
@@ -487,6 +549,7 @@ export const SettingsContext: React.FC = ({ children }) => {
saveDLNA,
saveUI,
refetch,
savePluginSettings,
}}
>
{maybeRenderLoadingIndicator()}