diff --git a/ui/v2.5/src/components/Settings/SettingSection.tsx b/ui/v2.5/src/components/Settings/SettingSection.tsx index ad2c9ebae8d..50c65ecc8dc 100644 --- a/ui/v2.5/src/components/Settings/SettingSection.tsx +++ b/ui/v2.5/src/components/Settings/SettingSection.tsx @@ -2,6 +2,7 @@ import React, { PropsWithChildren } from "react"; import { Card } from "react-bootstrap"; import { useIntl } from "react-intl"; import { useSettings } from "./context"; +import { PatchComponent } from "src/patch"; interface ISettingGroup { id?: string; @@ -10,27 +11,27 @@ interface ISettingGroup { advanced?: boolean; } -export const SettingSection: React.FC> = ({ - id, - children, - headingID, - subHeadingID, - advanced, -}) => { - const intl = useIntl(); - const { advancedMode } = useSettings(); +export const SettingSection: React.FC> = + PatchComponent( + "SettingSection", + ({ id, children, headingID, subHeadingID, advanced }) => { + const intl = useIntl(); + const { advancedMode } = useSettings(); - if (advanced && !advancedMode) return null; + if (advanced && !advancedMode) return null; - return ( -
-

{headingID ? intl.formatMessage({ id: headingID }) : undefined}

- {subHeadingID ? ( -
- {intl.formatMessage({ id: subHeadingID })} + return ( +
+

+ {headingID ? intl.formatMessage({ id: headingID }) : undefined} +

+ {subHeadingID ? ( +
+ {intl.formatMessage({ id: subHeadingID })} +
+ ) : undefined} + {children}
- ) : undefined} - {children} -
+ ); + } ); -}; diff --git a/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx index 589d13ef9a9..642349cfbdc 100644 --- a/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx @@ -5,6 +5,7 @@ import { useLoggingSubscribe, queryLogs } from "src/core/StashService"; import { SelectSetting } from "./Inputs"; import { SettingSection } from "./SettingSection"; import { JobTable } from "./Tasks/JobTable"; +import { PatchComponent } from "src/patch"; function convertTime(logEntry: GQL.LogEntryDataFragment) { function pad(val: number) { @@ -31,23 +32,6 @@ function levelClass(level: string) { return level.toLowerCase().trim(); } -interface ILogElementProps { - logEntry: LogEntry; -} - -const LogElement: React.FC = ({ logEntry }) => { - // pad to maximum length of level enum - const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length); - - return ( -
- {logEntry.time} - {level} - {logEntry.message} -
- ); -}; - class LogEntry { public time: string; public level: string; @@ -66,92 +50,115 @@ class LogEntry { } } +interface ILogElementProps { + logEntry: LogEntry; +} + +const LogElement: React.FC = PatchComponent( + "LogElement", + ({ logEntry }) => { + // pad to maximum length of level enum + const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length); + + return ( +
+ {logEntry.time} + {level} + {logEntry.message} +
+ ); + } +); + // maximum number of log entries to keep - entries are discarded oldest-first const MAX_LOG_ENTRIES = 50000; // maximum number of log entries to display const MAX_DISPLAY_LOG_ENTRIES = 1000; const logLevels = ["Trace", "Debug", "Info", "Warning", "Error"]; -export const SettingsLogsPanel: React.FC = () => { - const [entries, setEntries] = useState([]); - const { data, error } = useLoggingSubscribe(); - const [logLevel, setLogLevel] = useState("Info"); - const intl = useIntl(); - - useEffect(() => { - async function getInitialLogs() { - const logQuery = await queryLogs(); - if (logQuery.error) return; - - const initEntries = logQuery.data.logs.map((e) => new LogEntry(e)); - if (initEntries.length !== 0) { - setEntries((prev) => { - return [...prev, ...initEntries].slice(0, MAX_LOG_ENTRIES); - }); +export const SettingsLogsPanel: React.FC = PatchComponent( + "SettingsLogsPanel", + () => { + const [entries, setEntries] = useState([]); + const { data, error } = useLoggingSubscribe(); + const [logLevel, setLogLevel] = useState("Info"); + const intl = useIntl(); + + useEffect(() => { + async function getInitialLogs() { + const logQuery = await queryLogs(); + if (logQuery.error) return; + + const initEntries = logQuery.data.logs.map((e) => new LogEntry(e)); + if (initEntries.length !== 0) { + setEntries((prev) => { + return [...prev, ...initEntries].slice(0, MAX_LOG_ENTRIES); + }); + } } - } - - getInitialLogs(); - }, []); - - useEffect(() => { - if (!data) return; - - const newEntries = data.loggingSubscribe.map((e) => new LogEntry(e)); - newEntries.reverse(); - setEntries((prev) => { - return [...newEntries, ...prev].slice(0, MAX_LOG_ENTRIES); - }); - }, [data]); - const displayEntries = entries - .filter(filterByLogLevel) - .slice(0, MAX_DISPLAY_LOG_ENTRIES); - - function maybeRenderError() { - if (error) { - return ( -
- Error connecting to log server: {error.message} -
- ); + getInitialLogs(); + }, []); + + useEffect(() => { + if (!data) return; + + const newEntries = data.loggingSubscribe.map((e) => new LogEntry(e)); + newEntries.reverse(); + setEntries((prev) => { + return [...newEntries, ...prev].slice(0, MAX_LOG_ENTRIES); + }); + }, [data]); + + const displayEntries = entries + .filter(filterByLogLevel) + .slice(0, MAX_DISPLAY_LOG_ENTRIES); + + function maybeRenderError() { + if (error) { + return ( +
+ Error connecting to log server: {error.message} +
+ ); + } } - } - function filterByLogLevel(logEntry: LogEntry) { - if (logLevel === "Trace") return true; + function filterByLogLevel(logEntry: LogEntry) { + if (logLevel === "Trace") return true; - const logLevelIndex = logLevels.indexOf(logLevel); - const levelIndex = logLevels.indexOf(logEntry.level); + const logLevelIndex = logLevels.indexOf(logLevel); + const levelIndex = logLevels.indexOf(logEntry.level); - return levelIndex >= logLevelIndex; - } + return levelIndex >= logLevelIndex; + } - return ( - <> -

{intl.formatMessage({ id: "config.tasks.job_queue" })}

- - - setLogLevel(v)} - > - {logLevels.map((level) => ( - + return ( + <> +

{intl.formatMessage({ id: "config.tasks.job_queue" })}

+ + + setLogLevel(v)} + > + {logLevels.map((level) => ( + + ))} + + + +
+ {maybeRenderError()} + {displayEntries.map((logEntry) => ( + ))} - - - -
- {maybeRenderError()} - {displayEntries.map((logEntry) => ( - - ))} -
- - ); -}; +
+ + ); + } +); diff --git a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx index a4ad2b5a7ad..d369f577ea6 100644 --- a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx @@ -103,165 +103,168 @@ const PluginSettings: React.FC<{ ); }); -export const SettingsPluginsPanel: React.FC = () => { - const Toast = useToast(); - const intl = useIntl(); +export const SettingsPluginsPanel: React.FC = PatchComponent( + "SettingsPluginsPanel", + () => { + const Toast = useToast(); + const intl = useIntl(); - const { loading: configLoading } = useSettings(); - const { data, loading } = usePlugins(); + const { loading: configLoading } = useSettings(); + const { data, loading } = usePlugins(); - const [changedPluginID, setChangedPluginID] = React.useState< - string | undefined - >(); + const [changedPluginID, setChangedPluginID] = React.useState< + string | undefined + >(); - async function onReloadPlugins() { - try { - await mutateReloadPlugins(); - } catch (e) { - Toast.error(e); - } - } - - const pluginElements = useMemo(() => { - function renderLink(url?: string) { - if (url) { - return ( - - ); + async function onReloadPlugins() { + try { + await mutateReloadPlugins(); + } catch (e) { + Toast.error(e); } } - function renderEnableButton(pluginID: string, enabled: boolean) { - async function onClick() { - try { - await mutateSetPluginsEnabled({ [pluginID]: !enabled }); - } catch (e) { - Toast.error(e); + const pluginElements = useMemo(() => { + function renderLink(url?: string) { + if (url) { + return ( + + ); } - - setChangedPluginID(pluginID); } - return ( - - ); - } + function renderEnableButton(pluginID: string, enabled: boolean) { + async function onClick() { + try { + await mutateSetPluginsEnabled({ [pluginID]: !enabled }); + } catch (e) { + Toast.error(e); + } - function onReloadUI() { - window.location.reload(); - } + setChangedPluginID(pluginID); + } - function maybeRenderReloadUI(pluginID: string) { - if (pluginID === changedPluginID) { return ( - ); } - } - function renderPlugins() { - const elements = (data?.plugins ?? []).map((plugin) => ( - - {renderLink(plugin.url ?? undefined)} - {maybeRenderReloadUI(plugin.id)} - {renderEnableButton(plugin.id, plugin.enabled)} - - } - > - {renderPluginHooks(plugin.hooks ?? undefined)} - - - )); + function onReloadUI() { + window.location.reload(); + } - return
{elements}
; - } + function maybeRenderReloadUI(pluginID: string) { + if (pluginID === changedPluginID) { + return ( + + ); + } + } - function renderPluginHooks( - hooks?: Pick[] - ) { - if (!hooks || hooks.length === 0) { - return; + function renderPlugins() { + const elements = (data?.plugins ?? []).map((plugin) => ( + + {renderLink(plugin.url ?? undefined)} + {maybeRenderReloadUI(plugin.id)} + {renderEnableButton(plugin.id, plugin.enabled)} + + } + > + {renderPluginHooks(plugin.hooks ?? undefined)} + + + )); + + return
{elements}
; } - return ( -
-
-
- -
- {hooks.map((h) => ( -
-
{h.name}
- -
    - {h.hooks?.map((hh) => ( -
  • - {hh} -
  • - ))} -
-
- {h.description} -
- ))} + function renderPluginHooks( + hooks?: Pick[] + ) { + if (!hooks || hooks.length === 0) { + return; + } + + return ( +
+
+
+ +
+ {hooks.map((h) => ( +
+
{h.name}
+ +
    + {h.hooks?.map((hh) => ( +
  • + {hh} +
  • + ))} +
+
+ {h.description} +
+ ))} +
+
-
-
- ); - } + ); + } - return renderPlugins(); - }, [data?.plugins, intl, Toast, changedPluginID]); + return renderPlugins(); + }, [data?.plugins, intl, Toast, changedPluginID]); - if (loading || configLoading) return ; + if (loading || configLoading) return ; - return ( - <> - - + return ( + <> + + - - - - - {pluginElements} - - - ); -}; + + + + + {pluginElements} + + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx index 6859fce1862..69931d2422d 100644 --- a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx @@ -26,13 +26,14 @@ import { import { ExternalLink } from "../Shared/ExternalLink"; import { ClearableInput } from "../Shared/ClearableInput"; import { Counter } from "../Shared/Counter"; +import { PatchComponent } from "src/patch"; // Import PatchComponent const ScraperTable: React.FC< PropsWithChildren<{ entityType: string; count?: number; }> -> = ({ entityType, count, children }) => { +> = PatchComponent("ScraperTable", ({ entityType, count, children }) => { const intl = useIntl(); const titleEl = useMemo(() => { @@ -72,12 +73,12 @@ const ScraperTable: React.FC< ); -}; +}); const ScrapeTypeList: React.FC<{ types: ScrapeType[]; entityType: string; -}> = ({ types, entityType }) => { +}> = PatchComponent("ScrapeTypeList", ({ types, entityType }) => { const intl = useIntl(); const typeStrings = useMemo( @@ -103,13 +104,13 @@ const ScrapeTypeList: React.FC<{ ))} ); -}; +}); interface IURLList { urls: string[]; } -const URLList: React.FC = ({ urls }) => { +const URLList: React.FC = PatchComponent("URLList", ({ urls }) => { const items = useMemo(() => { function linkSite(url: string) { const u = new URL(url); @@ -134,26 +135,29 @@ const URLList: React.FC = ({ urls }) => { }, [urls]); return
    {items}
; -}; +}); const ScraperTableRow: React.FC<{ name: string; entityType: string; supportedScrapes: ScrapeType[]; urls: string[]; -}> = ({ name, entityType, supportedScrapes, urls }) => { - return ( - - {name} - - - - - - - - ); -}; +}> = PatchComponent( + "ScraperTableRow", + ({ name, entityType, supportedScrapes, urls }) => { + return ( + + {name} + + + + + + + + ); + } +); function filterScraper(filter: string) { return (name: string, urls: string[] | undefined | null) => { @@ -166,7 +170,7 @@ function filterScraper(filter: string) { }; } -const ScrapersSection: React.FC = () => { +const ScrapersSection: React.FC = PatchComponent("ScrapersSection", () => { const Toast = useToast(); const intl = useIntl(); @@ -310,60 +314,63 @@ const ScrapersSection: React.FC = () => {
); -}; - -export const SettingsScrapingPanel: React.FC = () => { - const { general, scraping, loading, error, saveGeneral, saveScraping } = - useSettings(); - - if (error) return

{error.message}

; - if (loading) return ; - - return ( - <> - saveGeneral({ stashBoxes: v })} - /> - - - saveScraping({ scraperUserAgent: v })} - /> +}); - saveScraping({ scraperCDPPath: v })} - /> +export const SettingsScrapingPanel: React.FC = PatchComponent( + "SettingsScrapingPanel", + () => { + const { general, scraping, loading, error, saveGeneral, saveScraping } = + useSettings(); - saveScraping({ scraperCertCheck: v })} - /> + if (error) return

{error.message}

; + if (loading) return ; - saveScraping({ excludeTagPatterns: v })} + return ( + <> + saveGeneral({ stashBoxes: v })} /> -
- - - - - - ); -}; + + saveScraping({ scraperUserAgent: v })} + /> + + saveScraping({ scraperCDPPath: v })} + /> + + saveScraping({ scraperCertCheck: v })} + /> + + saveScraping({ excludeTagPatterns: v })} + /> + + + + + + + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx b/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx index aaed1e7d4ef..63b1d201ea2 100644 --- a/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsSecurityPanel.tsx @@ -8,6 +8,7 @@ import { useSettings } from "./context"; import { LoadingIndicator } from "../Shared/LoadingIndicator"; import { useToast } from "src/hooks/Toast"; import { useGenerateAPIKey } from "src/core/StashService"; +import { PatchComponent } from "src/patch"; // Import PatchComponent type AuthenticationSettingsInput = Pick< GQL.ConfigGeneralInput, @@ -19,151 +20,156 @@ interface IAuthenticationInput { setValue: (v: AuthenticationSettingsInput) => void; } -const AuthenticationInput: React.FC = ({ - value, - setValue, -}) => { - const intl = useIntl(); - - function set(v: Partial) { - setValue({ - ...value, - ...v, - }); - } +const AuthenticationInput: React.FC = PatchComponent( + "AuthenticationInput", + ({ value, setValue }) => { + const intl = useIntl(); - const { username, password } = value; - - return ( -
- -
{intl.formatMessage({ id: "config.general.auth.username" })}
- ) => - set({ username: e.currentTarget.value }) - } - /> - - {intl.formatMessage({ id: "config.general.auth.username_desc" })} - -
- -
{intl.formatMessage({ id: "config.general.auth.password" })}
- ) => - set({ password: e.currentTarget.value }) - } - /> - - {intl.formatMessage({ id: "config.general.auth.password_desc" })} - -
-
- ); -}; - -export const SettingsSecurityPanel: React.FC = () => { - const intl = useIntl(); - const Toast = useToast(); - - const { general, apiKey, loading, error, saveGeneral, refetch } = - useSettings(); - - const [generateAPIKey] = useGenerateAPIKey(); - - async function onGenerateAPIKey() { - try { - await generateAPIKey({ - variables: { - input: {}, - }, + function set(v: Partial) { + setValue({ + ...value, + ...v, }); - refetch(); - } catch (e) { - Toast.error(e); } + + const { username, password } = value; + + return ( +
+ +
{intl.formatMessage({ id: "config.general.auth.username" })}
+ ) => + set({ username: e.currentTarget.value }) + } + /> + + {intl.formatMessage({ id: "config.general.auth.username_desc" })} + +
+ +
{intl.formatMessage({ id: "config.general.auth.password" })}
+ ) => + set({ password: e.currentTarget.value }) + } + /> + + {intl.formatMessage({ id: "config.general.auth.password_desc" })} + +
+
+ ); } +); + +export const SettingsSecurityPanel: React.FC = PatchComponent( + "SettingsSecurityPanel", + () => { + const intl = useIntl(); + const Toast = useToast(); + + const { general, apiKey, loading, error, saveGeneral, refetch } = + useSettings(); - async function onClearAPIKey() { - try { - await generateAPIKey({ - variables: { - input: { - clear: true, + const [generateAPIKey] = useGenerateAPIKey(); + + async function onGenerateAPIKey() { + try { + await generateAPIKey({ + variables: { + input: {}, }, - }, - }); - refetch(); - } catch (e) { - Toast.error(e); + }); + refetch(); + } catch (e) { + Toast.error(e); + } } - } - if (error) return

{error.message}

; - if (loading) return ; - - return ( - <> - - - id="authentication-settings" - headingID="config.general.auth.credentials.heading" - subHeadingID="config.general.auth.credentials.description" - value={{ - username: general.username, - password: general.password, - }} - onChange={(v) => saveGeneral(v)} - renderField={(value, setValue) => ( - - )} - renderValue={(v) => { - if (v?.username && v?.password) - return {v?.username ?? ""}; - return <>; - }} - /> - -
-
-

{intl.formatMessage({ id: "config.general.auth.api_key" })}

- -
{apiKey}
- -
- {intl.formatMessage({ id: "config.general.auth.api_key_desc" })} + async function onClearAPIKey() { + try { + await generateAPIKey({ + variables: { + input: { + clear: true, + }, + }, + }); + refetch(); + } catch (e) { + Toast.error(e); + } + } + + if (error) return

{error.message}

; + if (loading) return ; + + return ( + <> + + + id="authentication-settings" + headingID="config.general.auth.credentials.heading" + subHeadingID="config.general.auth.credentials.description" + value={{ + username: general.username, + password: general.password, + }} + onChange={(v) => saveGeneral(v)} + renderField={(value, setValue) => ( + + )} + renderValue={(v) => { + if (v?.username && v?.password) + return {v?.username ?? ""}; + return <>; + }} + /> + +
+
+

+ {intl.formatMessage({ id: "config.general.auth.api_key" })} +

+ +
{apiKey}
+ +
+ {intl.formatMessage({ id: "config.general.auth.api_key_desc" })} +
+
+
+ +
-
- - -
-
- - saveGeneral({ maxSessionAge: v })} - /> - - - ); -}; + + saveGeneral({ maxSessionAge: v })} + /> + + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx index e1322c43292..6fa77478c05 100644 --- a/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsServicesPanel.tsx @@ -31,486 +31,503 @@ import { faTimes, faUserClock, } from "@fortawesome/free-solid-svg-icons"; +import { PatchComponent } from "src/patch"; // Import PatchComponent const defaultDLNAPort = 1338; -export const SettingsServicesPanel: React.FC = () => { - const intl = useIntl(); - const Toast = useToast(); +export const SettingsServicesPanel: React.FC = PatchComponent( + "SettingsServicesPanel", + () => { + const intl = useIntl(); + const Toast = useToast(); - const { dlna, loading: configLoading, error, saveDLNA } = useSettings(); + const { dlna, loading: configLoading, error, saveDLNA } = useSettings(); - // undefined to hide dialog, true for enable, false for disable - const [enableDisable, setEnableDisable] = useState(); + // undefined to hide dialog, true for enable, false for disable + const [enableDisable, setEnableDisable] = useState(); - const [enableUntilRestart, setEnableUntilRestart] = useState(false); - const [enableDuration, setEnableDuration] = useState(0); + const [enableUntilRestart, setEnableUntilRestart] = + useState(false); + const [enableDuration, setEnableDuration] = useState(0); - const [ipEntry, setIPEntry] = useState(""); - const [tempIP, setTempIP] = useState(); + const [ipEntry, setIPEntry] = useState(""); + const [tempIP, setTempIP] = useState(); - const { data: statusData, loading, refetch: statusRefetch } = useDLNAStatus(); + const { + data: statusData, + loading, + refetch: statusRefetch, + } = useDLNAStatus(); - const [enableDLNA] = useEnableDLNA(); - const [disableDLNA] = useDisableDLNA(); - const [addTempDLANIP] = useAddTempDLNAIP(); - const [removeTempDLNAIP] = useRemoveTempDLNAIP(); + const [enableDLNA] = useEnableDLNA(); + const [disableDLNA] = useDisableDLNA(); + const [addTempDLANIP] = useAddTempDLNAIP(); + const [removeTempDLNAIP] = useRemoveTempDLNAIP(); - if (error) return

{error.message}

; - if (loading || configLoading) return ; + if (error) return

{error.message}

; + if (loading || configLoading) return ; - async function onTempEnable() { - const input = { - variables: { - input: { - duration: enableUntilRestart ? undefined : enableDuration, + async function onTempEnable() { + const input = { + variables: { + input: { + duration: enableUntilRestart ? undefined : enableDuration, + }, }, - }, - }; + }; + + try { + if (enableDisable) { + await enableDLNA(input); + Toast.success( + intl.formatMessage({ + id: "config.dlna.enabled_dlna_temporarily", + }) + ); + } else { + await disableDLNA(input); + Toast.success( + intl.formatMessage({ + id: "config.dlna.disabled_dlna_temporarily", + }) + ); + } + } catch (e) { + Toast.error(e); + } finally { + setEnableDisable(undefined); + statusRefetch(); + } + } - try { - if (enableDisable) { - await enableDLNA(input); - Toast.success( - intl.formatMessage({ - id: "config.dlna.enabled_dlna_temporarily", - }) - ); - } else { - await disableDLNA(input); + async function onAllowTempIP() { + if (!tempIP) { + return; + } + + const input = { + variables: { + input: { + duration: enableUntilRestart ? undefined : enableDuration, + address: tempIP, + }, + }, + }; + + try { + await addTempDLANIP(input); Toast.success( intl.formatMessage({ - id: "config.dlna.disabled_dlna_temporarily", + id: "config.dlna.allowed_ip_temporarily", }) ); + } catch (e) { + Toast.error(e); + } finally { + setTempIP(undefined); + statusRefetch(); } - } catch (e) { - Toast.error(e); - } finally { - setEnableDisable(undefined); - statusRefetch(); - } - } - - async function onAllowTempIP() { - if (!tempIP) { - return; } - const input = { - variables: { - input: { - duration: enableUntilRestart ? undefined : enableDuration, - address: tempIP, + async function onDisallowTempIP(address: string) { + const input = { + variables: { + input: { + address, + }, }, - }, - }; - - try { - await addTempDLANIP(input); - Toast.success( - intl.formatMessage({ - id: "config.dlna.allowed_ip_temporarily", - }) - ); - } catch (e) { - Toast.error(e); - } finally { - setTempIP(undefined); - statusRefetch(); + }; + + try { + await removeTempDLNAIP(input); + Toast.success(intl.formatMessage({ id: "config.dlna.disallowed_ip" })); + } catch (e) { + Toast.error(e); + } finally { + statusRefetch(); + } } - } - async function onDisallowTempIP(address: string) { - const input = { - variables: { - input: { - address, - }, - }, - }; + function renderDeadline(until?: string | null) { + if (until) { + const deadline = new Date(until); + return `until ${intl.formatDate(deadline)}`; + } - try { - await removeTempDLNAIP(input); - Toast.success(intl.formatMessage({ id: "config.dlna.disallowed_ip" })); - } catch (e) { - Toast.error(e); - } finally { - statusRefetch(); + return ""; } - } - function renderDeadline(until?: string | null) { - if (until) { - const deadline = new Date(until); - return `until ${intl.formatDate(deadline)}`; - } + function renderStatus() { + if (!statusData) { + return ""; + } - return ""; - } + const { dlnaStatus } = statusData; + const runningText = intl.formatMessage({ + id: dlnaStatus.running ? "actions.running" : "actions.not_running", + }); - function renderStatus() { - if (!statusData) { - return ""; + return `${runningText} ${renderDeadline(dlnaStatus.until)}`; } - const { dlnaStatus } = statusData; - const runningText = intl.formatMessage({ - id: dlnaStatus.running ? "actions.running" : "actions.not_running", - }); - - return `${runningText} ${renderDeadline(dlnaStatus.until)}`; - } + function renderEnableButton() { + // if enabled by default, then show the disable temporarily + // if disabled by default, then show enable temporarily + if (dlna.enabled) { + return ( + + ); + } - function renderEnableButton() { - // if enabled by default, then show the disable temporarily - // if disabled by default, then show enable temporarily - if (dlna.enabled) { return ( - ); } - return ( - - ); - } + function canCancel() { + if (!statusData || !dlna) { + return false; + } - function canCancel() { - if (!statusData || !dlna) { - return false; + const { dlnaStatus } = statusData; + const { enabled } = dlna; + + return dlnaStatus.until || dlnaStatus.running !== enabled; } - const { dlnaStatus } = statusData; - const { enabled } = dlna; + async function cancelTempBehaviour() { + if (!canCancel()) { + return; + } - return dlnaStatus.until || dlnaStatus.running !== enabled; - } + const running = statusData?.dlnaStatus.running; - async function cancelTempBehaviour() { - if (!canCancel()) { - return; + const input = { + variables: { + input: {}, + }, + }; + + try { + if (!running) { + await enableDLNA(input); + } else { + await disableDLNA(input); + } + Toast.success( + intl.formatMessage({ + id: "config.dlna.successfully_cancelled_temporary_behaviour", + }) + ); + } catch (e) { + Toast.error(e); + } finally { + setEnableDisable(undefined); + statusRefetch(); + } } - const running = statusData?.dlnaStatus.running; - - const input = { - variables: { - input: {}, - }, - }; - - try { - if (!running) { - await enableDLNA(input); - } else { - await disableDLNA(input); + function renderTempCancelButton() { + if (!canCancel()) { + return; } - Toast.success( - intl.formatMessage({ - id: "config.dlna.successfully_cancelled_temporary_behaviour", - }) + + return ( + ); - } catch (e) { - Toast.error(e); - } finally { - setEnableDisable(undefined); - statusRefetch(); } - } - function renderTempCancelButton() { - if (!canCancel()) { - return; - } + function renderTempEnableDialog() { + const text: string = enableDisable ? "enable" : "disable"; + const capitalised = `${text[0].toUpperCase()}${text.slice(1)}`; - return ( - - ); - } + return ( + setEnableDisable(undefined), + variant: "secondary", + }} + > +

{capitalised} temporarily

+ + setEnableUntilRestart(!enableUntilRestart)} + /> + - function renderTempEnableDialog() { - const text: string = enableDisable ? "enable" : "disable"; - const capitalised = `${text[0].toUpperCase()}${text.slice(1)}`; + + setEnableDuration(v ?? 0)} + disabled={enableUntilRestart} + /> + + Duration to {text} for - in minutes. + + +
+ ); + } - return ( - setEnableDisable(undefined), - variant: "secondary", - }} - > -

{capitalised} temporarily

- - setEnableUntilRestart(!enableUntilRestart)} - /> - + function renderTempWhitelistDialog() { + return ( + setTempIP(undefined), + variant: "secondary", + }} + > +

{`Allow ${tempIP} temporarily`}

+ + setEnableUntilRestart(!enableUntilRestart)} + /> + - - setEnableDuration(v ?? 0)} - disabled={enableUntilRestart} - /> - - Duration to {text} for - in minutes. - - -
- ); - } + + setEnableDuration(v ?? 0)} + disabled={enableUntilRestart} + /> + + Duration to allow for - in minutes. + + +
+ ); + } - function renderTempWhitelistDialog() { - return ( - setTempIP(undefined), - variant: "secondary", - }} - > -

{`Allow ${tempIP} temporarily`}

- - setEnableUntilRestart(!enableUntilRestart)} - /> - + function renderAllowedIPs() { + if ( + !statusData || + statusData.dlnaStatus.allowedIPAddresses.length === 0 + ) { + return; + } - - setEnableDuration(v ?? 0)} - disabled={enableUntilRestart} - /> - - Duration to allow for - in minutes. - - -
- ); - } + const { allowedIPAddresses } = statusData.dlnaStatus; + return ( + +
+ {intl.formatMessage({ id: "config.dlna.allowed_ip_addresses" })} +
- function renderAllowedIPs() { - if (!statusData || statusData.dlnaStatus.allowedIPAddresses.length === 0) { - return; +
    + {allowedIPAddresses.map((a) => ( +
  • +
    + {a.ipAddress} +
    + {renderDeadline(a.until)} +
    + +
    + +
    +
  • + ))} +
+
+ ); } - const { allowedIPAddresses } = statusData.dlnaStatus; - return ( - -
- {intl.formatMessage({ id: "config.dlna.allowed_ip_addresses" })} -
+ function renderRecentIPs() { + if (!statusData) { + return; + } + const { recentIPAddresses } = statusData.dlnaStatus; + return (
    - {allowedIPAddresses.map((a) => ( -
  • + {recentIPAddresses.map((a) => ( +
  • - {a.ipAddress} -
    - {renderDeadline(a.until)} + {a}
    - -
    +
  • ))} -
-
- ); - } - - function renderRecentIPs() { - if (!statusData) { - return; - } - - const { recentIPAddresses } = statusData.dlnaStatus; - return ( -
    - {recentIPAddresses.map((a) => ( -
  • +
  • - {a} + ) => + setIPEntry(e.currentTarget.value) + } + />
    -
    +
  • - ))} -
  • -
    - ) => - setIPEntry(e.currentTarget.value) - } +
+ ); + } + + const DLNASettingsForm: React.FC = () => { + return ( + <> + + stash } + )} + value={dlna.serverName ?? undefined} + onChange={(v) => saveDLNA({ serverName: v })} /> -
-
- -
- - - ); - } - const DLNASettingsForm: React.FC = () => { - return ( - <> - - stash } - )} - value={dlna.serverName ?? undefined} - onChange={(v) => saveDLNA({ serverName: v })} - /> - - saveDLNA({ port: v ? v : defaultDLNAPort })} - /> - - saveDLNA({ enabled: v })} - /> - - saveDLNA({ interfaces: v })} - /> - - * } - )} - defaultNewValue="*" - value={dlna.whitelistedIPs ?? undefined} - onChange={(v) => saveDLNA({ whitelistedIPs: v })} - /> - - saveDLNA({ videoSortOrder: v })} - > - {Array.from(videoSortOrderIntlMap.entries()).map((v) => ( - - ))} - - - - ); - }; + saveDLNA({ port: v ? v : defaultDLNAPort })} + /> - return ( -
- {renderTempEnableDialog()} - {renderTempWhitelistDialog()} + saveDLNA({ enabled: v })} + /> -

DLNA

+ saveDLNA({ interfaces: v })} + /> - -
- {intl.formatMessage({ id: "status" }, { statusText: renderStatus() })} -
-
+ * } + )} + defaultNewValue="*" + value={dlna.whitelistedIPs ?? undefined} + onChange={(v) => saveDLNA({ whitelistedIPs: v })} + /> - - - {renderEnableButton()} - {renderTempCancelButton()} + saveDLNA({ videoSortOrder: v })} + > + {Array.from(videoSortOrderIntlMap.entries()).map((v) => ( + + ))} + + + + ); + }; + + return ( +
+ {renderTempEnableDialog()} + {renderTempWhitelistDialog()} + +

DLNA

+ + +
+ {intl.formatMessage( + { id: "status" }, + { statusText: renderStatus() } + )} +
- {renderAllowedIPs()} + + + {renderEnableButton()} + {renderTempCancelButton()} + - -
- {intl.formatMessage({ id: "config.dlna.recent_ip_addresses" })} -
- {renderRecentIPs()} - - + {renderAllowedIPs()} + + +
+ {intl.formatMessage({ id: "config.dlna.recent_ip_addresses" })} +
+ {renderRecentIPs()} + + +
-
-
+ - -
- ); -}; + +
+ ); + } +); diff --git a/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx b/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx index a3ab150dbc5..1223b5fe1b5 100644 --- a/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsSystemPanel.tsx @@ -20,452 +20,458 @@ import { FormattedMessage, useIntl } from "react-intl"; import { Button } from "react-bootstrap"; import { useToast } from "src/hooks/Toast"; import { useHistory } from "react-router-dom"; - -export const SettingsConfigurationPanel: React.FC = () => { - const intl = useIntl(); - const Toast = useToast(); - const history = useHistory(); - - const { general, loading, error, saveGeneral } = useSettings(); - const [mutateDownloadFFMpeg] = GQL.useDownloadFfMpegMutation(); - - const transcodeQualities = [ - GQL.StreamingResolutionEnum.Low, - GQL.StreamingResolutionEnum.Standard, - GQL.StreamingResolutionEnum.StandardHd, - GQL.StreamingResolutionEnum.FullHd, - GQL.StreamingResolutionEnum.FourK, - GQL.StreamingResolutionEnum.Original, - ].map(resolutionToString); - - function resolutionToString(r: GQL.StreamingResolutionEnum | undefined) { - switch (r) { - case GQL.StreamingResolutionEnum.Low: - return "240p"; - case GQL.StreamingResolutionEnum.Standard: - return "480p"; - case GQL.StreamingResolutionEnum.StandardHd: - return "720p"; - case GQL.StreamingResolutionEnum.FullHd: - return "1080p"; - case GQL.StreamingResolutionEnum.FourK: - return "4k"; - case GQL.StreamingResolutionEnum.Original: - return "Original"; +import { PatchComponent } from "src/patch"; + +export const SettingsConfigurationPanel: React.FC = PatchComponent( + "SettingsConfigurationPanel", + () => { + const intl = useIntl(); + const Toast = useToast(); + const history = useHistory(); + + const { general, loading, error, saveGeneral } = useSettings(); + const [mutateDownloadFFMpeg] = GQL.useDownloadFfMpegMutation(); + + const transcodeQualities = [ + GQL.StreamingResolutionEnum.Low, + GQL.StreamingResolutionEnum.Standard, + GQL.StreamingResolutionEnum.StandardHd, + GQL.StreamingResolutionEnum.FullHd, + GQL.StreamingResolutionEnum.FourK, + GQL.StreamingResolutionEnum.Original, + ].map(resolutionToString); + + function resolutionToString(r: GQL.StreamingResolutionEnum | undefined) { + switch (r) { + case GQL.StreamingResolutionEnum.Low: + return "240p"; + case GQL.StreamingResolutionEnum.Standard: + return "480p"; + case GQL.StreamingResolutionEnum.StandardHd: + return "720p"; + case GQL.StreamingResolutionEnum.FullHd: + return "1080p"; + case GQL.StreamingResolutionEnum.FourK: + return "4k"; + case GQL.StreamingResolutionEnum.Original: + return "Original"; + } + + return "Original"; } - return "Original"; - } - - function translateQuality(quality: string) { - switch (quality) { - case "240p": - return GQL.StreamingResolutionEnum.Low; - case "480p": - return GQL.StreamingResolutionEnum.Standard; - case "720p": - return GQL.StreamingResolutionEnum.StandardHd; - case "1080p": - return GQL.StreamingResolutionEnum.FullHd; - case "4k": - return GQL.StreamingResolutionEnum.FourK; - case "Original": - return GQL.StreamingResolutionEnum.Original; + function translateQuality(quality: string) { + switch (quality) { + case "240p": + return GQL.StreamingResolutionEnum.Low; + case "480p": + return GQL.StreamingResolutionEnum.Standard; + case "720p": + return GQL.StreamingResolutionEnum.StandardHd; + case "1080p": + return GQL.StreamingResolutionEnum.FullHd; + case "4k": + return GQL.StreamingResolutionEnum.FourK; + case "Original": + return GQL.StreamingResolutionEnum.Original; + } + + return GQL.StreamingResolutionEnum.Original; } - return GQL.StreamingResolutionEnum.Original; - } + const namingHashAlgorithms = [ + GQL.HashAlgorithm.Md5, + GQL.HashAlgorithm.Oshash, + ].map(namingHashToString); + + function namingHashToString(value: GQL.HashAlgorithm | undefined) { + switch (value) { + case GQL.HashAlgorithm.Oshash: + return "oshash"; + case GQL.HashAlgorithm.Md5: + return "MD5"; + } - const namingHashAlgorithms = [ - GQL.HashAlgorithm.Md5, - GQL.HashAlgorithm.Oshash, - ].map(namingHashToString); - - function namingHashToString(value: GQL.HashAlgorithm | undefined) { - switch (value) { - case GQL.HashAlgorithm.Oshash: - return "oshash"; - case GQL.HashAlgorithm.Md5: - return "MD5"; + return "MD5"; } - return "MD5"; - } + function translateNamingHash(value: string) { + switch (value) { + case "oshash": + return GQL.HashAlgorithm.Oshash; + case "MD5": + return GQL.HashAlgorithm.Md5; + } - function translateNamingHash(value: string) { - switch (value) { - case "oshash": - return GQL.HashAlgorithm.Oshash; - case "MD5": - return GQL.HashAlgorithm.Md5; + return GQL.HashAlgorithm.Md5; } - return GQL.HashAlgorithm.Md5; - } + function blobStorageTypeToID(value: GQL.BlobsStorageType | undefined) { + switch (value) { + case GQL.BlobsStorageType.Database: + return "blobs_storage_type.database"; + case GQL.BlobsStorageType.Filesystem: + return "blobs_storage_type.filesystem"; + } - function blobStorageTypeToID(value: GQL.BlobsStorageType | undefined) { - switch (value) { - case GQL.BlobsStorageType.Database: - return "blobs_storage_type.database"; - case GQL.BlobsStorageType.Filesystem: - return "blobs_storage_type.filesystem"; + return "blobs_storage_type.database"; } - return "blobs_storage_type.database"; - } - - async function onDownloadFFMpeg() { - try { - await mutateDownloadFFMpeg(); - // navigate to tasks page to see the progress - history.push("/settings?tab=tasks"); - } catch (e) { - Toast.error(e); + async function onDownloadFFMpeg() { + try { + await mutateDownloadFFMpeg(); + // navigate to tasks page to see the progress + history.push("/settings?tab=tasks"); + } catch (e) { + Toast.error(e); + } } - } - if (error) return

{error.message}

; - if (loading) return ; - - return ( - <> - - saveGeneral({ generatedPath: v })} - /> - - saveGeneral({ cachePath: v })} - /> - - saveGeneral({ scrapersPath: v })} - /> - - saveGeneral({ pluginsPath: v })} - /> - - saveGeneral({ metadataPath: v })} - /> - - saveGeneral({ customPerformerImageLocation: v })} - /> - - saveGeneral({ ffmpegPath: v })} - /> - - saveGeneral({ ffprobePath: v })} - /> - - + if (error) return

{error.message}

; + if (loading) return ; + + return ( + <> + + saveGeneral({ generatedPath: v })} + /> + + saveGeneral({ cachePath: v })} + /> + + saveGeneral({ scrapersPath: v })} + /> + + saveGeneral({ pluginsPath: v })} + /> + + saveGeneral({ metadataPath: v })} + /> + + saveGeneral({ customPerformerImageLocation: v })} + /> + + saveGeneral({ ffmpegPath: v })} + /> + + saveGeneral({ ffprobePath: v })} + /> + + + + + } + subHeadingID="config.general.ffmpeg.download_ffmpeg.description" + > + - - - saveGeneral({ pythonPath: v })} - /> - - saveGeneral({ backupDirectoryPath: v })} - /> - - - - saveGeneral({ databasePath: v })} - /> - - saveGeneral({ blobsStorage: v as GQL.BlobsStorageType }) - } - > - {Object.values(GQL.BlobsStorageType).map((q) => ( - - ))} - - saveGeneral({ blobsPath: v })} - /> - - - - saveGeneral({ calculateMD5: v })} - /> - - - saveGeneral({ videoFileNamingAlgorithm: translateNamingHash(v) }) - } - > - {namingHashAlgorithms.map((q) => ( - - ))} - - - - - - saveGeneral({ maxTranscodeSize: translateQuality(v) }) - } - value={resolutionToString(general.maxTranscodeSize ?? undefined)} - > - {transcodeQualities.map((q) => ( - - ))} - - - - saveGeneral({ maxStreamingTranscodeSize: translateQuality(v) }) - } - value={resolutionToString( - general.maxStreamingTranscodeSize ?? undefined - )} - > - {transcodeQualities.map((q) => ( - - ))} - - - saveGeneral({ transcodeHardwareAcceleration: v })} - /> - - saveGeneral({ transcodeInputArgs: v })} - value={general.transcodeInputArgs ?? []} - /> - saveGeneral({ transcodeOutputArgs: v })} - value={general.transcodeOutputArgs ?? []} - /> - - saveGeneral({ liveTranscodeInputArgs: v })} - value={general.liveTranscodeInputArgs ?? []} - /> - saveGeneral({ liveTranscodeOutputArgs: v })} - value={general.liveTranscodeOutputArgs ?? []} - /> - - - - saveGeneral({ parallelTasks: v })} - /> - - - - - saveGeneral({ - previewPreset: (v as GQL.PreviewPreset) ?? undefined, - }) - } - > - {Object.keys(GQL.PreviewPreset).map((p) => ( - - ))} - - - saveGeneral({ previewAudio: v })} - /> - - - id="video-preview-settings" - headingID="dialogs.scene_gen.preview_generation_options" - value={{ - previewExcludeEnd: general.previewExcludeEnd, - previewExcludeStart: general.previewExcludeStart, - previewSegmentDuration: general.previewSegmentDuration, - previewSegments: general.previewSegments, - }} - onChange={(v) => saveGeneral(v)} - renderField={(value, setValue) => ( - - )} - renderValue={() => { - return <>; - }} - /> - - - - saveGeneral({ drawFunscriptHeatmapRange: v })} - /> - - - - saveGeneral({ logFile: v })} - /> - - saveGeneral({ logOut: v })} - /> - - saveGeneral({ logLevel: v })} - value={general.logLevel ?? undefined} - > - {["Trace", "Debug", "Info", "Warning", "Error"].map((o) => ( - - ))} - - - saveGeneral({ logAccess: v })} - /> - - - ); -}; + +
+ + saveGeneral({ pythonPath: v })} + /> + + saveGeneral({ backupDirectoryPath: v })} + /> +
+ + + saveGeneral({ databasePath: v })} + /> + + saveGeneral({ blobsStorage: v as GQL.BlobsStorageType }) + } + > + {Object.values(GQL.BlobsStorageType).map((q) => ( + + ))} + + saveGeneral({ blobsPath: v })} + /> + + + + saveGeneral({ calculateMD5: v })} + /> + + + saveGeneral({ + videoFileNamingAlgorithm: translateNamingHash(v), + }) + } + > + {namingHashAlgorithms.map((q) => ( + + ))} + + + + + + saveGeneral({ maxTranscodeSize: translateQuality(v) }) + } + value={resolutionToString(general.maxTranscodeSize ?? undefined)} + > + {transcodeQualities.map((q) => ( + + ))} + + + + saveGeneral({ maxStreamingTranscodeSize: translateQuality(v) }) + } + value={resolutionToString( + general.maxStreamingTranscodeSize ?? undefined + )} + > + {transcodeQualities.map((q) => ( + + ))} + + + saveGeneral({ transcodeHardwareAcceleration: v })} + /> + + saveGeneral({ transcodeInputArgs: v })} + value={general.transcodeInputArgs ?? []} + /> + saveGeneral({ transcodeOutputArgs: v })} + value={general.transcodeOutputArgs ?? []} + /> + + saveGeneral({ liveTranscodeInputArgs: v })} + value={general.liveTranscodeInputArgs ?? []} + /> + saveGeneral({ liveTranscodeOutputArgs: v })} + value={general.liveTranscodeOutputArgs ?? []} + /> + + + + saveGeneral({ parallelTasks: v })} + /> + + + + + saveGeneral({ + previewPreset: (v as GQL.PreviewPreset) ?? undefined, + }) + } + > + {Object.keys(GQL.PreviewPreset).map((p) => ( + + ))} + + + saveGeneral({ previewAudio: v })} + /> + + + id="video-preview-settings" + headingID="dialogs.scene_gen.preview_generation_options" + value={{ + previewExcludeEnd: general.previewExcludeEnd, + previewExcludeStart: general.previewExcludeStart, + previewSegmentDuration: general.previewSegmentDuration, + previewSegments: general.previewSegments, + }} + onChange={(v) => saveGeneral(v)} + renderField={(value, setValue) => ( + + )} + renderValue={() => { + return <>; + }} + /> + + + + saveGeneral({ drawFunscriptHeatmapRange: v })} + /> + + + + saveGeneral({ logFile: v })} + /> + + saveGeneral({ logOut: v })} + /> + + saveGeneral({ logLevel: v })} + value={general.logLevel ?? undefined} + > + {["Trace", "Debug", "Info", "Warning", "Error"].map((o) => ( + + ))} + + + saveGeneral({ logAccess: v })} + /> + + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx index 9d0c4cfd13f..8492a40267b 100644 --- a/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx +++ b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx @@ -4,210 +4,216 @@ import { FormattedMessage, useIntl } from "react-intl"; import { SettingSection } from "./SettingSection"; import * as GQL from "src/core/generated-graphql"; import { SettingModal } from "./Inputs"; +import { PatchComponent } from "src/patch"; export interface IStashBoxModal { value: GQL.StashBoxInput; close: (v?: GQL.StashBoxInput) => void; } -export const StashBoxModal: React.FC = ({ value, close }) => { - const intl = useIntl(); - const endpoint = useRef(null); - const apiKey = useRef(null); - - const [validate, { data, loading }] = GQL.useValidateStashBoxLazyQuery({ - fetchPolicy: "network-only", - }); - - const handleValidate = () => { - validate({ - variables: { - input: { - endpoint: endpoint.current?.value ?? "", - api_key: apiKey.current?.value ?? "", - name: "test", - }, - }, +export const StashBoxModal: React.FC = PatchComponent( + "StashBoxModal", + ({ value, close }: IStashBoxModal) => { + const intl = useIntl(); + const endpoint = useRef(null); + const apiKey = useRef(null); + + const [validate, { data, loading }] = GQL.useValidateStashBoxLazyQuery({ + fetchPolicy: "network-only", }); - }; - - return ( - - headingID="config.stashbox.title" - value={value} - renderField={(v, setValue) => ( - <> - -
- {intl.formatMessage({ - id: "config.stashbox.name", - })} -
- 0} - onChange={(e: React.ChangeEvent) => - setValue({ ...v!, name: e.currentTarget.value }) - } - /> -
- - -
- {intl.formatMessage({ - id: "config.stashbox.graphql_endpoint", - })} -
- 0} - onChange={(e: React.ChangeEvent) => - setValue({ ...v!, endpoint: e.currentTarget.value.trim() }) - } - ref={endpoint} - /> -
- - -
- {intl.formatMessage({ - id: "config.stashbox.api_key", - })} -
- 0} - onChange={(e: React.ChangeEvent) => - setValue({ ...v!, api_key: e.currentTarget.value.trim() }) - } - ref={apiKey} - /> -
- - - - {data && ( - { + validate({ + variables: { + input: { + endpoint: endpoint.current?.value ?? "", + api_key: apiKey.current?.value ?? "", + name: "test", + }, + }, + }); + }; + + return ( + + headingID="config.stashbox.title" + value={value} + renderField={(v, setValue) => ( + <> + +
+ {intl.formatMessage({ + id: "config.stashbox.name", + })} +
+ 0} + onChange={(e: React.ChangeEvent) => + setValue({ ...v!, name: e.currentTarget.value }) + } + /> +
+ + +
+ {intl.formatMessage({ + id: "config.stashbox.graphql_endpoint", + })} +
+ 0} + onChange={(e: React.ChangeEvent) => + setValue({ ...v!, endpoint: e.currentTarget.value.trim() }) } + ref={endpoint} + /> +
+ + +
+ {intl.formatMessage({ + id: "config.stashbox.api_key", + })} +
+ 0} + onChange={(e: React.ChangeEvent) => + setValue({ ...v!, api_key: e.currentTarget.value.trim() }) + } + ref={apiKey} + /> +
+ + +
+ {data && ( + + {data.validateStashBoxCredentials?.status} + + )} +
+ + )} + close={close} + /> + ); + } +); interface IStashBoxSetting { value: GQL.StashBoxInput[]; onChange: (v: GQL.StashBoxInput[]) => void; } -export const StashBoxSetting: React.FC = ({ - value, - onChange, -}) => { - const [isCreating, setIsCreating] = useState(false); - const [editingIndex, setEditingIndex] = useState(); - - function onEdit(index: number) { - setEditingIndex(index); - } - - function onDelete(index: number) { - onChange(value.filter((v, i) => i !== index)); - } - - function onNew() { - setIsCreating(true); - } - - return ( - - {isCreating ? ( - { - if (v) onChange([...value, v]); - setIsCreating(false); - }} - /> - ) : undefined} - - {editingIndex !== undefined ? ( - { - if (v) - onChange( - value.map((vv, index) => { - if (index === editingIndex) { - return v; - } - return vv; - }) - ); - setEditingIndex(undefined); - }} - /> - ) : undefined} - - {value.map((b, index) => ( - // eslint-disable-next-line react/no-array-index-key -
-
-

{b.name ?? `#${index}`}

-
{b.endpoint ?? ""}
+export const StashBoxSetting: React.FC = PatchComponent( + "StashBoxSetting", + ({ value, onChange }: IStashBoxSetting) => { + const [isCreating, setIsCreating] = useState(false); + const [editingIndex, setEditingIndex] = useState(); + + function onEdit(index: number) { + setEditingIndex(index); + } + + function onDelete(index: number) { + onChange(value.filter((v, i) => i !== index)); + } + + function onNew() { + setIsCreating(true); + } + + return ( + + {isCreating ? ( + { + if (v) onChange([...value, v]); + setIsCreating(false); + }} + /> + ) : undefined} + + {editingIndex !== undefined ? ( + { + if (v) + onChange( + value.map((vv, index) => { + if (index === editingIndex) { + return v; + } + return vv; + }) + ); + setEditingIndex(undefined); + }} + /> + ) : undefined} + + {value.map((b, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+
+

{b.name ?? `#${index}`}

+
{b.endpoint ?? ""}
+
+
+ + +
+ ))} +
+
- -
- ))} -
-
-
- -
-
- - ); -}; + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/StashConfiguration.tsx b/ui/v2.5/src/components/Settings/StashConfiguration.tsx index 9680f2b1842..1a626807637 100644 --- a/ui/v2.5/src/components/Settings/StashConfiguration.tsx +++ b/ui/v2.5/src/components/Settings/StashConfiguration.tsx @@ -7,6 +7,7 @@ import * as GQL from "src/core/generated-graphql"; import { FolderSelectDialog } from "../Shared/FolderSelect/FolderSelectDialog"; import { BooleanSetting } from "./Inputs"; import { SettingSection } from "./SettingSection"; +import { PatchComponent } from "src/patch"; interface IStashProps { index: number; @@ -16,193 +17,193 @@ interface IStashProps { onDelete: () => void; } -const Stash: React.FC = ({ - index, - stash, - onSave, - onEdit, - onDelete, -}) => { - // eslint-disable-next-line - const handleInput = (key: string, value: any) => { - const newObj = { - ...stash, - [key]: value, +const Stash: React.FC = PatchComponent( + "Stash", + ({ index, stash, onSave, onEdit, onDelete }: IStashProps) => { + // eslint-disable-next-line + const handleInput = (key: string, value: any) => { + const newObj = { + ...stash, + [key]: value, + }; + onSave(newObj); }; - onSave(newObj); - }; - - const classAdd = index % 2 === 1 ? "bg-dark" : ""; - - return ( - - - {stash.path} - - - {/* NOTE - language is opposite to meaning: - internally exclude flags, displayed as include */} -
-
- -
- handleInput("excludeVideo", !v)} - /> -
- - - -
-
- -
- handleInput("excludeImage", !v)} - /> -
- - - - - - - - onEdit()}> - - - onDelete()}> - - - - - -
- ); -}; + + const classAdd = index % 2 === 1 ? "bg-dark" : ""; + + return ( + + + {stash.path} + + + {/* NOTE - language is opposite to meaning: + internally exclude flags, displayed as include */} +
+
+ +
+ handleInput("excludeVideo", !v)} + /> +
+ + + +
+
+ +
+ handleInput("excludeImage", !v)} + /> +
+ + + + + + + + onEdit()}> + + + onDelete()}> + + + + + +
+ ); + } +); interface IStashConfigurationProps { stashes: GQL.StashConfig[]; setStashes: (v: GQL.StashConfig[]) => void; } -const StashConfiguration: React.FC = ({ - stashes, - setStashes, -}) => { - const [isCreating, setIsCreating] = useState(false); - const [editingIndex, setEditingIndex] = useState(); - - function onEdit(index: number) { - setEditingIndex(index); - } - - function onDelete(index: number) { - setStashes(stashes.filter((v, i) => i !== index)); - } - - function onNew() { - setIsCreating(true); - } - - const handleSave = (index: number, stash: GQL.StashConfig) => - setStashes(stashes.map((s, i) => (i === index ? stash : s))); - - return ( - <> - {isCreating ? ( - { - if (v) - setStashes([ - ...stashes, - { - path: v, - excludeVideo: false, - excludeImage: false, - }, - ]); - setIsCreating(false); - }} - /> - ) : undefined} - - {editingIndex !== undefined ? ( - { - if (v) - setStashes( - stashes.map((vv, index) => { - if (index === editingIndex) { - return { - ...vv, - path: v, - }; - } - return vv; - }) - ); - setEditingIndex(undefined); - }} - /> - ) : undefined} - -
- {stashes.length > 0 && ( - -
- -
-
- -
-
- -
-
- )} - {stashes.map((stash, index) => ( - handleSave(index, s)} - onEdit={() => onEdit(index)} - onDelete={() => onDelete(index)} - key={stash.path} +const StashConfiguration: React.FC = PatchComponent( + "StashConfiguration", + ({ stashes, setStashes }: IStashConfigurationProps) => { + const [isCreating, setIsCreating] = useState(false); + const [editingIndex, setEditingIndex] = useState(); + + function onEdit(index: number) { + setEditingIndex(index); + } + + function onDelete(index: number) { + setStashes(stashes.filter((v, i) => i !== index)); + } + + function onNew() { + setIsCreating(true); + } + + const handleSave = (index: number, stash: GQL.StashConfig) => + setStashes(stashes.map((s, i) => (i === index ? stash : s))); + + return ( + <> + {isCreating ? ( + { + if (v) + setStashes([ + ...stashes, + { + path: v, + excludeVideo: false, + excludeImage: false, + }, + ]); + setIsCreating(false); + }} + /> + ) : undefined} + + {editingIndex !== undefined ? ( + { + if (v) + setStashes( + stashes.map((vv, index) => { + if (index === editingIndex) { + return { + ...vv, + path: v, + }; + } + return vv; + }) + ); + setEditingIndex(undefined); + }} /> - ))} - -
- - ); -}; + ) : undefined} + +
+ {stashes.length > 0 && ( + +
+ +
+
+ +
+
+ +
+
+ )} + {stashes.map((stash, index) => ( + handleSave(index, s)} + onEdit={() => onEdit(index)} + onDelete={() => onDelete(index)} + key={stash.path} + /> + ))} + +
+ + ); + } +); interface IStashSetting { value: GQL.StashConfigInput[]; onChange: (v: GQL.StashConfigInput[]) => void; } -export const StashSetting: React.FC = ({ value, onChange }) => { - return ( - - onChange(v)} /> - - ); -}; +export const StashSetting: React.FC = PatchComponent( + "StashSetting", + ({ value, onChange }: IStashSetting) => { + return ( + + onChange(v)} /> + + ); + } +); export default StashConfiguration; diff --git a/ui/v2.5/src/components/Settings/Tasks/CleanGeneratedDialog.tsx b/ui/v2.5/src/components/Settings/Tasks/CleanGeneratedDialog.tsx index 7160aeb7d75..dab33decffa 100644 --- a/ui/v2.5/src/components/Settings/Tasks/CleanGeneratedDialog.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/CleanGeneratedDialog.tsx @@ -7,64 +7,68 @@ import { faTrashAlt } from "@fortawesome/free-solid-svg-icons"; import { SettingSection } from "../SettingSection"; import { useSettings } from "../context"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; +import { PatchComponent } from "src/patch"; const CleanGeneratedOptions: React.FC<{ options: GQL.CleanGeneratedInput; setOptions: (s: GQL.CleanGeneratedInput) => void; -}> = ({ options, setOptions: setOptionsState }) => { - function setOptions(input: Partial) { - setOptionsState({ ...options, ...input }); - } +}> = PatchComponent( + "CleanGeneratedOptions", + ({ options, setOptions: setOptionsState }) => { + function setOptions(input: Partial) { + setOptionsState({ ...options, ...input }); + } - return ( - <> - setOptions({ blobFiles: v })} - /> - setOptions({ screenshots: v })} - /> - setOptions({ sprites: v })} - /> - setOptions({ transcodes: v })} - /> - setOptions({ markers: v })} - /> - setOptions({ imageThumbnails: v })} - /> - setOptions({ dryRun: v })} - /> - - ); -}; + return ( + <> + setOptions({ blobFiles: v })} + /> + setOptions({ screenshots: v })} + /> + setOptions({ sprites: v })} + /> + setOptions({ transcodes: v })} + /> + setOptions({ markers: v })} + /> + setOptions({ imageThumbnails: v })} + /> + setOptions({ dryRun: v })} + /> + + ); + } +); export const CleanGeneratedDialog: React.FC<{ onClose: (input?: GQL.CleanGeneratedInput) => void; diff --git a/ui/v2.5/src/components/Settings/Tasks/DataManagementTasks.tsx b/ui/v2.5/src/components/Settings/Tasks/DataManagementTasks.tsx index f445b4332cc..e2ff4d3b3bf 100644 --- a/ui/v2.5/src/components/Settings/Tasks/DataManagementTasks.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/DataManagementTasks.tsx @@ -31,6 +31,7 @@ import { faTrashAlt, } from "@fortawesome/free-solid-svg-icons"; import { CleanGeneratedDialog } from "./CleanGeneratedDialog"; +import { PatchComponent } from "src/patch"; // Import PatchComponent interface ICleanDialog { pathSelection?: boolean; @@ -38,677 +39,688 @@ interface ICleanDialog { onClose: (paths?: string[]) => void; } -const CleanDialog: React.FC = ({ - pathSelection = false, - dryRun, - onClose, -}) => { - const intl = useIntl(); - const { configuration } = React.useContext(ConfigurationContext); +const CleanDialog: React.FC = PatchComponent( + "CleanDialog", + ({ pathSelection = false, dryRun, onClose }: ICleanDialog) => { + const intl = useIntl(); + const { configuration } = React.useContext(ConfigurationContext); - const libraryPaths = configuration?.general.stashes.map((s) => s.path); + const libraryPaths = configuration?.general.stashes.map((s) => s.path); - const [paths, setPaths] = useState([]); - const [currentDirectory, setCurrentDirectory] = useState(""); + const [paths, setPaths] = useState([]); + const [currentDirectory, setCurrentDirectory] = useState(""); - function removePath(p: string) { - setPaths(paths.filter((path) => path !== p)); - } - - function addPath(p: string) { - if (p && !paths.includes(p)) { - setPaths(paths.concat(p)); + function removePath(p: string) { + setPaths(paths.filter((path) => path !== p)); } - } - let msg; - if (dryRun) { - msg = ( -

{intl.formatMessage({ id: "actions.tasks.dry_mode_selected" })}

- ); - } else { - msg = ( -

{intl.formatMessage({ id: "actions.tasks.clean_confirm_message" })}

- ); - } - - return ( - onClose(paths), - }} - cancel={{ onClick: () => onClose() }} - > -
-
- {paths.map((p) => ( - - - {p} - - - - - - ))} - - {pathSelection ? ( - addPath(currentDirectory)} - > - - - } - /> - ) : undefined} -
- - {msg} -
-
- ); -}; - -interface ICleanOptions { - options: GQL.CleanMetadataInput; - setOptions: (s: GQL.CleanMetadataInput) => void; -} - -const CleanOptions: React.FC = ({ - options, - setOptions: setOptionsState, -}) => { - function setOptions(input: Partial) { - setOptionsState({ ...options, ...input }); - } - - return ( - <> - setOptions({ dryRun: v })} - /> - - ); -}; - -interface IDataManagementTasks { - setIsBackupRunning: (v: boolean) => void; - setIsAnonymiseRunning: (v: boolean) => void; -} - -export const DataManagementTasks: React.FC = ({ - setIsBackupRunning, - setIsAnonymiseRunning, -}) => { - const intl = useIntl(); - const Toast = useToast(); - const [dialogOpen, setDialogOpenState] = useState({ - importAlert: false, - import: false, - clean: false, - cleanAlert: false, - cleanGenerated: false, - }); - - const [cleanOptions, setCleanOptions] = useState({ - dryRun: false, - }); - - const [migrateBlobsOptions, setMigrateBlobsOptions] = - useState({ - deleteOld: true, - }); - - const [migrateSceneScreenshotsOptions, setMigrateSceneScreenshotsOptions] = - useState({ - deleteFiles: false, - overwriteExisting: false, - }); - - type DialogOpenState = typeof dialogOpen; - - function setDialogOpen(s: Partial) { - setDialogOpenState((v) => { - return { ...v, ...s }; - }); - } + function addPath(p: string) { + if (p && !paths.includes(p)) { + setPaths(paths.concat(p)); + } + } - async function onImport() { - setDialogOpen({ importAlert: false }); - try { - await mutateMetadataImport(); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { operation_name: intl.formatMessage({ id: "actions.import" }) } - ) + let msg; + if (dryRun) { + msg = ( +

{intl.formatMessage({ id: "actions.tasks.dry_mode_selected" })}

+ ); + } else { + msg = ( +

+ {intl.formatMessage({ id: "actions.tasks.clean_confirm_message" })} +

); - } catch (e) { - Toast.error(e); } - } - function renderImportAlert() { return ( onClose(paths), }} - cancel={{ onClick: () => setDialogOpen({ importAlert: false }) }} + cancel={{ onClick: () => onClose() }} > -

{intl.formatMessage({ id: "actions.tasks.import_warning" })}

+
+
+ {paths.map((p) => ( + + + {p} + + + + + + ))} + + {pathSelection ? ( + addPath(currentDirectory)} + > + + + } + /> + ) : undefined} +
+ + {msg} +
); } +); - function renderImportDialog() { - if (!dialogOpen.import) { - return; +interface ICleanOptions { + options: GQL.CleanMetadataInput; + setOptions: (s: GQL.CleanMetadataInput) => void; +} + +const CleanOptions: React.FC = PatchComponent( + "CleanOptions", + ({ options, setOptions: setOptionsState }: ICleanOptions) => { + function setOptions(input: Partial) { + setOptionsState({ ...options, ...input }); } - return setDialogOpen({ import: false })} />; + return ( + <> + setOptions({ dryRun: v })} + /> + + ); } +); + +interface IDataManagementTasks { + setIsBackupRunning: (v: boolean) => void; + setIsAnonymiseRunning: (v: boolean) => void; +} - async function onClean(paths?: string[]) { - try { - await mutateMetadataClean({ - ...cleanOptions, - paths, +export const DataManagementTasks: React.FC = + PatchComponent( + "DataManagementTasks", + ({ setIsBackupRunning, setIsAnonymiseRunning }: IDataManagementTasks) => { + const intl = useIntl(); + const Toast = useToast(); + const [dialogOpen, setDialogOpenState] = useState({ + importAlert: false, + import: false, + clean: false, + cleanAlert: false, + cleanGenerated: false, }); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { operation_name: intl.formatMessage({ id: "actions.clean" }) } - ) - ); - } catch (e) { - Toast.error(e); - } finally { - setDialogOpen({ clean: false }); - } - } + const [cleanOptions, setCleanOptions] = useState({ + dryRun: false, + }); - async function onCleanGenerated(options: GQL.CleanGeneratedInput) { - try { - await mutateCleanGenerated({ - ...options, + const [migrateBlobsOptions, setMigrateBlobsOptions] = + useState({ + deleteOld: true, + }); + + const [ + migrateSceneScreenshotsOptions, + setMigrateSceneScreenshotsOptions, + ] = useState({ + deleteFiles: false, + overwriteExisting: false, }); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { - operation_name: intl.formatMessage({ - id: "actions.clean_generated", - }), - } - ) - ); - } catch (e) { - Toast.error(e); - } - } + type DialogOpenState = typeof dialogOpen; - async function onMigrateHashNaming() { - try { - await mutateMigrateHashNaming(); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { - operation_name: intl.formatMessage({ - id: "actions.hash_migration", - }), - } - ) - ); - } catch (err) { - Toast.error(err); - } - } + function setDialogOpen(s: Partial) { + setDialogOpenState((v) => { + return { ...v, ...s }; + }); + } - async function onMigrateSceneScreenshots() { - try { - await mutateMigrateSceneScreenshots(migrateSceneScreenshotsOptions); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { - operation_name: intl.formatMessage({ - id: "actions.migrate_scene_screenshots", - }), - } - ) - ); - } catch (err) { - Toast.error(err); - } - } + async function onImport() { + setDialogOpen({ importAlert: false }); + try { + await mutateMetadataImport(); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { operation_name: intl.formatMessage({ id: "actions.import" }) } + ) + ); + } catch (e) { + Toast.error(e); + } + } - async function onMigrateBlobs() { - try { - await mutateMigrateBlobs(migrateBlobsOptions); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { - operation_name: intl.formatMessage({ - id: "actions.migrate_blobs", - }), - } - ) - ); - } catch (err) { - Toast.error(err); - } - } + function renderImportAlert() { + return ( + setDialogOpen({ importAlert: false }) }} + > +

{intl.formatMessage({ id: "actions.tasks.import_warning" })}

+
+ ); + } - async function onExport() { - try { - await mutateMetadataExport(); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { operation_name: intl.formatMessage({ id: "actions.export" }) } - ) - ); - } catch (err) { - Toast.error(err); - } - } + function renderImportDialog() { + if (!dialogOpen.import) { + return; + } - async function onBackup(download?: boolean) { - try { - setIsBackupRunning(true); - const ret = await mutateBackupDatabase({ - download, - }); + return ( + setDialogOpen({ import: false })} /> + ); + } - // download the result - if (download && ret.data && ret.data.backupDatabase) { - const link = ret.data.backupDatabase; - downloadFile(link); + async function onClean(paths?: string[]) { + try { + await mutateMetadataClean({ + ...cleanOptions, + paths, + }); + + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { operation_name: intl.formatMessage({ id: "actions.clean" }) } + ) + ); + } catch (e) { + Toast.error(e); + } finally { + setDialogOpen({ clean: false }); + } } - } catch (e) { - Toast.error(e); - } finally { - setIsBackupRunning(false); - } - } - async function onOptimiseDatabase() { - try { - await mutateOptimiseDatabase(); - Toast.success( - intl.formatMessage( - { id: "config.tasks.added_job_to_queue" }, - { - operation_name: intl.formatMessage({ - id: "actions.optimise_database", - }), - } - ) - ); - } catch (e) { - Toast.error(e); - } - } + async function onCleanGenerated(options: GQL.CleanGeneratedInput) { + try { + await mutateCleanGenerated({ + ...options, + }); + + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { + operation_name: intl.formatMessage({ + id: "actions.clean_generated", + }), + } + ) + ); + } catch (e) { + Toast.error(e); + } + } - async function onAnonymise(download?: boolean) { - try { - setIsAnonymiseRunning(true); - const ret = await mutateAnonymiseDatabase({ - download, - }); + async function onMigrateHashNaming() { + try { + await mutateMigrateHashNaming(); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { + operation_name: intl.formatMessage({ + id: "actions.hash_migration", + }), + } + ) + ); + } catch (err) { + Toast.error(err); + } + } - // download the result - if (download && ret.data && ret.data.anonymiseDatabase) { - const link = ret.data.anonymiseDatabase; - downloadFile(link); + async function onMigrateSceneScreenshots() { + try { + await mutateMigrateSceneScreenshots(migrateSceneScreenshotsOptions); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { + operation_name: intl.formatMessage({ + id: "actions.migrate_scene_screenshots", + }), + } + ) + ); + } catch (err) { + Toast.error(err); + } } - } catch (e) { - Toast.error(e); - } finally { - setIsAnonymiseRunning(false); - } - } - return ( - - {renderImportAlert()} - {renderImportDialog()} - {dialogOpen.cleanAlert || dialogOpen.clean ? ( - { - // undefined means cancelled - if (p !== undefined) { - if (dialogOpen.cleanAlert) { - // don't provide paths - onClean(); - } else { - onClean(p); + async function onMigrateBlobs() { + try { + await mutateMigrateBlobs(migrateBlobsOptions); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { + operation_name: intl.formatMessage({ + id: "actions.migrate_blobs", + }), } - } + ) + ); + } catch (err) { + Toast.error(err); + } + } - setDialogOpen({ - clean: false, - cleanAlert: false, - }); - }} - /> - ) : ( - dialogOpen.clean - )} - {dialogOpen.cleanGenerated && ( - { - if (options) { - onCleanGenerated(options); - } - - setDialogOpen({ cleanGenerated: false }); - }} - /> - )} - - -
- - - - - - - } - subHeadingID="config.tasks.cleanup_desc" - > - - - - setCleanOptions(o)} - /> -
+ async function onExport() { + try { + await mutateMetadataExport(); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { operation_name: intl.formatMessage({ id: "actions.export" }) } + ) + ); + } catch (err) { + Toast.error(err); + } + } -
- } - subHeadingID="config.tasks.clean_generated.description" - > - - -
+ async function onBackup(download?: boolean) { + try { + setIsBackupRunning(true); + const ret = await mutateBackupDatabase({ + download, + }); + + // download the result + if (download && ret.data && ret.data.backupDatabase) { + const link = ret.data.backupDatabase; + downloadFile(link); + } + } catch (e) { + Toast.error(e); + } finally { + setIsBackupRunning(false); + } + } + + async function onOptimiseDatabase() { + try { + await mutateOptimiseDatabase(); + Toast.success( + intl.formatMessage( + { id: "config.tasks.added_job_to_queue" }, + { + operation_name: intl.formatMessage({ + id: "actions.optimise_database", + }), + } + ) + ); + } catch (e) { + Toast.error(e); + } + } - - -
- - + async function onAnonymise(download?: boolean) { + try { + setIsAnonymiseRunning(true); + const ret = await mutateAnonymiseDatabase({ + download, + }); + + // download the result + if (download && ret.data && ret.data.anonymiseDatabase) { + const link = ret.data.anonymiseDatabase; + downloadFile(link); } - > - -
-
- - - - - - - - - - - - - - - - - - [origFilename].sqlite.[schemaVersion].[YYYYMMDD_HHMMSS] - - ), - } + } catch (e) { + Toast.error(e); + } finally { + setIsAnonymiseRunning(false); + } + } + + return ( + + {renderImportAlert()} + {renderImportDialog()} + {dialogOpen.cleanAlert || dialogOpen.clean ? ( + { + // undefined means cancelled + if (p !== undefined) { + if (dialogOpen.cleanAlert) { + // don't provide paths + onClean(); + } else { + onClean(p); + } + } + + setDialogOpen({ + clean: false, + cleanAlert: false, + }); + }} + /> + ) : ( + dialogOpen.clean )} - > - - - - - - - - - - - [origFilename].anonymous.sqlite.[schemaVersion].[YYYYMMDD_HHMMSS] - - ), - } + {dialogOpen.cleanGenerated && ( + { + if (options) { + onCleanGenerated(options); + } + + setDialogOpen({ cleanGenerated: false }); + }} + /> )} - > - - - - - - - - - - - - - -
- - - - - - setMigrateBlobsOptions({ ...migrateBlobsOptions, deleteOld: v }) - } - /> -
-
- - + + + setCleanOptions(o)} + /> +
+ +
+ } + subHeadingID="config.tasks.clean_generated.description" + > + + +
+ + + +
+ + + } > - - -
- - - setMigrateSceneScreenshotsOptions({ - ...migrateSceneScreenshotsOptions, - overwriteExisting: v, - }) - } - /> - - - setMigrateSceneScreenshotsOptions({ - ...migrateSceneScreenshotsOptions, - deleteFiles: v, - }) - } - /> -
- - + + + + + + + + + + + + + + + + + + + + + [origFilename].sqlite.[schemaVersion].[YYYYMMDD_HHMMSS] + + ), + } + )} + > + + + + + + + + + + + [origFilename].anonymous.sqlite.[schemaVersion].[YYYYMMDD_HHMMSS] + + ), + } + )} + > + + + + + + + + + + + + + +
+ + + + + + setMigrateBlobsOptions({ + ...migrateBlobsOptions, + deleteOld: v, + }) + } + /> +
+ +
+ + + + + + setMigrateSceneScreenshotsOptions({ + ...migrateSceneScreenshotsOptions, + overwriteExisting: v, + }) + } + /> + + + setMigrateSceneScreenshotsOptions({ + ...migrateSceneScreenshotsOptions, + deleteFiles: v, + }) + } + /> +
+
+ + ); + } ); -}; diff --git a/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx b/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx index 9fdaf09f4d9..45875a7b19b 100644 --- a/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/DirectorySelectionDialog.tsx @@ -10,6 +10,7 @@ import { Icon } from "src/components/Shared/Icon"; import { ModalComponent } from "src/components/Shared/Modal"; import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect"; import { ConfigurationContext } from "src/hooks/Config"; +import { PatchComponent } from "src/patch"; interface IDirectorySelectionDialogProps { animation?: boolean; @@ -18,80 +19,82 @@ interface IDirectorySelectionDialogProps { onClose: (paths?: string[]) => void; } -export const DirectorySelectionDialog: React.FC< - IDirectorySelectionDialogProps -> = ({ animation, allowEmpty = false, initialPaths = [], onClose }) => { - const intl = useIntl(); - const { configuration } = React.useContext(ConfigurationContext); +export const DirectorySelectionDialog: React.FC = + PatchComponent( + "DirectorySelectionDialog", + ({ animation, allowEmpty = false, initialPaths = [], onClose }) => { + const intl = useIntl(); + const { configuration } = React.useContext(ConfigurationContext); - const libraryPaths = configuration?.general.stashes.map((s) => s.path); + const libraryPaths = configuration?.general.stashes.map((s) => s.path); - const [paths, setPaths] = useState(initialPaths); - const [currentDirectory, setCurrentDirectory] = useState(""); + const [paths, setPaths] = useState(initialPaths); + const [currentDirectory, setCurrentDirectory] = useState(""); - function removePath(p: string) { - setPaths(paths.filter((path) => path !== p)); - } + function removePath(p: string) { + setPaths(paths.filter((path) => path !== p)); + } - function addPath(p: string) { - if (p && !paths.includes(p)) { - setPaths(paths.concat(p)); - } - } + function addPath(p: string) { + if (p && !paths.includes(p)) { + setPaths(paths.concat(p)); + } + } - return ( - { - onClose(paths); - }, - text: intl.formatMessage({ id: "actions.confirm" }), - }} - cancel={{ - onClick: () => onClose(), - text: intl.formatMessage({ id: "actions.cancel" }), - variant: "secondary", - }} - > -
- {paths.map((p) => ( - - - {p} - - - - - - ))} + return ( + { + onClose(paths); + }, + text: intl.formatMessage({ id: "actions.confirm" }), + }} + cancel={{ + onClick: () => onClose(), + text: intl.formatMessage({ id: "actions.cancel" }), + variant: "secondary", + }} + > +
+ {paths.map((p) => ( + + + {p} + + + + + + ))} - addPath(currentDirectory)} - > - - - } - /> -
-
+ addPath(currentDirectory)} + > + + + } + /> +
+
+ ); + } ); -}; diff --git a/ui/v2.5/src/components/Settings/Tasks/GenerateOptions.tsx b/ui/v2.5/src/components/Settings/Tasks/GenerateOptions.tsx index c0127b5db33..ad98ff82f5e 100644 --- a/ui/v2.5/src/components/Settings/Tasks/GenerateOptions.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/GenerateOptions.tsx @@ -5,6 +5,7 @@ import { VideoPreviewInput, VideoPreviewSettingsInput, } from "../GeneratePreviewOptions"; +import { PatchComponent } from "src/patch"; interface IGenerateOptions { type?: "scene" | "image"; @@ -13,172 +14,169 @@ interface IGenerateOptions { setOptions: (s: GQL.GenerateMetadataInput) => void; } -export const GenerateOptions: React.FC = ({ - type, - selection, - options, - setOptions: setOptionsState, -}) => { - const previewOptions: GQL.GeneratePreviewOptionsInput = - options.previewOptions ?? {}; +export const GenerateOptions: React.FC = PatchComponent( + "GenerateOptions", + ({ type, selection, options, setOptions: setOptionsState }) => { + const previewOptions: GQL.GeneratePreviewOptionsInput = + options.previewOptions ?? {}; - function setOptions(input: Partial) { - setOptionsState({ ...options, ...input }); - } - - const showSceneOptions = !type || type === "scene"; - const showImageOptions = !type || type === "image"; + function setOptions(input: Partial) { + setOptionsState({ ...options, ...input }); + } - return ( - <> - {showSceneOptions && ( - <> - setOptions({ covers: v })} - /> - setOptions({ previews: v })} - /> - setOptions({ imagePreviews: v })} - /> + const showSceneOptions = !type || type === "scene"; + const showImageOptions = !type || type === "image"; - {/* #2251 - only allow preview generation options to be overridden when generating from a selection */} - {selection ? ( - - id="video-preview-settings" + return ( + <> + {showSceneOptions && ( + <> + setOptions({ covers: v })} + /> + setOptions({ previews: v })} + /> + setOptions({ previewOptions: v })} - renderField={(value, setValue) => ( - - )} - renderValue={() => { - return <>; - }} + headingID="dialogs.scene_gen.image_previews" + tooltipID="dialogs.scene_gen.image_previews_tooltip" + onChange={(v) => setOptions({ imagePreviews: v })} /> - ) : undefined} + {/* #2251 - only allow preview generation options to be overridden when generating from a selection */} + {selection ? ( + + id="video-preview-settings" + className="sub-setting" + disabled={!options.previews} + headingID="dialogs.scene_gen.override_preview_generation_options" + tooltipID="dialogs.scene_gen.override_preview_generation_options_desc" + value={{ + previewExcludeEnd: previewOptions.previewExcludeEnd, + previewExcludeStart: previewOptions.previewExcludeStart, + previewSegmentDuration: previewOptions.previewSegmentDuration, + previewSegments: previewOptions.previewSegments, + }} + onChange={(v) => setOptions({ previewOptions: v })} + renderField={(value, setValue) => ( + + )} + renderValue={() => { + return <>; + }} + /> + ) : undefined} - setOptions({ sprites: v })} - /> - setOptions({ markers: v })} - /> - - setOptions({ - markerImagePreviews: v, - }) - } - /> - setOptions({ markerScreenshots: v })} - /> - - setOptions({ transcodes: v })} - /> - {selection ? ( + setOptions({ sprites: v })} + /> + setOptions({ markers: v })} + /> + + setOptions({ + markerImagePreviews: v, + }) + } + /> setOptions({ forceTranscodes: v })} + checked={options.markerScreenshots ?? false} + disabled={!options.markers} + headingID="dialogs.scene_gen.marker_screenshots" + tooltipID="dialogs.scene_gen.marker_screenshots_tooltip" + onChange={(v) => setOptions({ markerScreenshots: v })} + /> + + setOptions({ transcodes: v })} /> - ) : undefined} + {selection ? ( + setOptions({ forceTranscodes: v })} + /> + ) : undefined} - setOptions({ phashes: v })} - /> + setOptions({ phashes: v })} + /> - setOptions({ interactiveHeatmapsSpeeds: v })} - /> - - )} - {showImageOptions && ( - <> - setOptions({ clipPreviews: v })} - /> - setOptions({ imageThumbnails: v })} - /> - - )} - setOptions({ overwrite: v })} - /> - - ); -}; + setOptions({ interactiveHeatmapsSpeeds: v })} + /> + + )} + {showImageOptions && ( + <> + setOptions({ clipPreviews: v })} + /> + setOptions({ imageThumbnails: v })} + /> + + )} + setOptions({ overwrite: v })} + /> + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/Tasks/ImportDialog.tsx b/ui/v2.5/src/components/Settings/Tasks/ImportDialog.tsx index dce5ba92f2e..d59d704172b 100644 --- a/ui/v2.5/src/components/Settings/Tasks/ImportDialog.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/ImportDialog.tsx @@ -6,171 +6,173 @@ import * as GQL from "src/core/generated-graphql"; import { useToast } from "src/hooks/Toast"; import { useIntl } from "react-intl"; import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"; +import { PatchComponent } from "src/patch"; interface IImportDialogProps { onClose: () => void; } -export const ImportDialog: React.FC = ( - props: IImportDialogProps -) => { - const [duplicateBehaviour, setDuplicateBehaviour] = useState( - duplicateHandlingToString(GQL.ImportDuplicateEnum.Ignore) - ); - - const [missingRefBehaviour, setMissingRefBehaviour] = useState( - missingRefHandlingToString(GQL.ImportMissingRefEnum.Fail) - ); - - const [file, setFile] = useState(); - - // Network state - const [isRunning, setIsRunning] = useState(false); - - const intl = useIntl(); - const Toast = useToast(); - - function duplicateHandlingToString( - value: GQL.ImportDuplicateEnum | undefined - ) { - switch (value) { - case GQL.ImportDuplicateEnum.Fail: - return "Fail"; - case GQL.ImportDuplicateEnum.Ignore: - return "Ignore"; - case GQL.ImportDuplicateEnum.Overwrite: - return "Overwrite"; - } - return "Ignore"; - } +export const ImportDialog: React.FC = PatchComponent( + "ImportDialog", + (props: IImportDialogProps) => { + const [duplicateBehaviour, setDuplicateBehaviour] = useState( + duplicateHandlingToString(GQL.ImportDuplicateEnum.Ignore) + ); - function translateDuplicateHandling(value: string) { - switch (value) { - case "Fail": - return GQL.ImportDuplicateEnum.Fail; - case "Ignore": - return GQL.ImportDuplicateEnum.Ignore; - case "Overwrite": - return GQL.ImportDuplicateEnum.Overwrite; - } + const [missingRefBehaviour, setMissingRefBehaviour] = useState( + missingRefHandlingToString(GQL.ImportMissingRefEnum.Fail) + ); - return GQL.ImportDuplicateEnum.Ignore; - } + const [file, setFile] = useState(); - function missingRefHandlingToString( - value: GQL.ImportMissingRefEnum | undefined - ) { - switch (value) { - case GQL.ImportMissingRefEnum.Fail: - return "Fail"; - case GQL.ImportMissingRefEnum.Ignore: - return "Ignore"; - case GQL.ImportMissingRefEnum.Create: - return "Create"; - } - return "Fail"; - } + // Network state + const [isRunning, setIsRunning] = useState(false); + + const intl = useIntl(); + const Toast = useToast(); - function translateMissingRefHandling(value: string) { - switch (value) { - case "Fail": - return GQL.ImportMissingRefEnum.Fail; - case "Ignore": - return GQL.ImportMissingRefEnum.Ignore; - case "Create": - return GQL.ImportMissingRefEnum.Create; + function duplicateHandlingToString( + value: GQL.ImportDuplicateEnum | undefined + ) { + switch (value) { + case GQL.ImportDuplicateEnum.Fail: + return "Fail"; + case GQL.ImportDuplicateEnum.Ignore: + return "Ignore"; + case GQL.ImportDuplicateEnum.Overwrite: + return "Overwrite"; + } + return "Ignore"; } - return GQL.ImportMissingRefEnum.Fail; - } + function translateDuplicateHandling(value: string) { + switch (value) { + case "Fail": + return GQL.ImportDuplicateEnum.Fail; + case "Ignore": + return GQL.ImportDuplicateEnum.Ignore; + case "Overwrite": + return GQL.ImportDuplicateEnum.Overwrite; + } + + return GQL.ImportDuplicateEnum.Ignore; + } - function onFileChange(event: React.ChangeEvent) { - if ( - event.target.validity.valid && - event.target.files && - event.target.files.length > 0 + function missingRefHandlingToString( + value: GQL.ImportMissingRefEnum | undefined ) { - setFile(event.target.files[0]); + switch (value) { + case GQL.ImportMissingRefEnum.Fail: + return "Fail"; + case GQL.ImportMissingRefEnum.Ignore: + return "Ignore"; + case GQL.ImportMissingRefEnum.Create: + return "Create"; + } + return "Fail"; } - } - async function onImport() { - if (!file) return; - - try { - setIsRunning(true); - await mutateImportObjects({ - duplicateBehaviour: translateDuplicateHandling(duplicateBehaviour), - missingRefBehaviour: translateMissingRefHandling(missingRefBehaviour), - file, - }); - setIsRunning(false); - Toast.success(intl.formatMessage({ id: "toast.started_importing" })); - } catch (e) { - Toast.error(e); - } finally { - props.onClose(); + function translateMissingRefHandling(value: string) { + switch (value) { + case "Fail": + return GQL.ImportMissingRefEnum.Fail; + case "Ignore": + return GQL.ImportMissingRefEnum.Ignore; + case "Create": + return GQL.ImportMissingRefEnum.Create; + } + + return GQL.ImportMissingRefEnum.Fail; + } + + function onFileChange(event: React.ChangeEvent) { + if ( + event.target.validity.valid && + event.target.files && + event.target.files.length > 0 + ) { + setFile(event.target.files[0]); + } + } + + async function onImport() { + if (!file) return; + + try { + setIsRunning(true); + await mutateImportObjects({ + duplicateBehaviour: translateDuplicateHandling(duplicateBehaviour), + missingRefBehaviour: translateMissingRefHandling(missingRefBehaviour), + file, + }); + setIsRunning(false); + Toast.success(intl.formatMessage({ id: "toast.started_importing" })); + } catch (e) { + Toast.error(e); + } finally { + props.onClose(); + } } - } - return ( - { - onImport(); - }, - text: intl.formatMessage({ id: "actions.import" }), - }} - cancel={{ - onClick: () => props.onClose(), - text: intl.formatMessage({ id: "actions.cancel" }), - variant: "secondary", - }} - disabled={!file} - isRunning={isRunning} - > -
-
- -
Import zip file
- -
- -
Duplicate object handling
- ) => - setDuplicateBehaviour(e.currentTarget.value) - } - > - {Object.values(GQL.ImportDuplicateEnum).map((p) => ( - - ))} - -
- - -
Missing reference handling
- ) => - setMissingRefBehaviour(e.currentTarget.value) - } - > - {Object.values(GQL.ImportMissingRefEnum).map((p) => ( - - ))} - -
-
-
-
- ); -}; + return ( + { + onImport(); + }, + text: intl.formatMessage({ id: "actions.import" }), + }} + cancel={{ + onClick: () => props.onClose(), + text: intl.formatMessage({ id: "actions.cancel" }), + variant: "secondary", + }} + disabled={!file} + isRunning={isRunning} + > +
+
+ +
Import zip file
+ +
+ +
Duplicate object handling
+ ) => + setDuplicateBehaviour(e.currentTarget.value) + } + > + {Object.values(GQL.ImportDuplicateEnum).map((p) => ( + + ))} + +
+ + +
Missing reference handling
+ ) => + setMissingRefBehaviour(e.currentTarget.value) + } + > + {Object.values(GQL.ImportMissingRefEnum).map((p) => ( + + ))} + +
+
+
+
+ ); + } +); diff --git a/ui/v2.5/src/components/Settings/Tasks/JobTable.tsx b/ui/v2.5/src/components/Settings/Tasks/JobTable.tsx index 82ed46c854b..9befd3e4e3f 100644 --- a/ui/v2.5/src/components/Settings/Tasks/JobTable.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/JobTable.tsx @@ -17,6 +17,7 @@ import { faHourglassStart, faTimes, } from "@fortawesome/free-solid-svg-icons"; +import { PatchComponent } from "src/patch"; type JobFragment = Pick< GQL.Job, @@ -27,7 +28,7 @@ interface IJob { job: JobFragment; } -const Task: React.FC = ({ job }) => { +const Task: React.FC = PatchComponent("Task", ({ job }) => { const [stopping, setStopping] = useState(false); const [className, setClassName] = useState(""); @@ -169,9 +170,9 @@ const Task: React.FC = ({ job }) => {
); -}; +}); -export const JobTable: React.FC = () => { +export const JobTable: React.FC = PatchComponent("JobTable", () => { const intl = useIntl(); const jobStatus = useJobQueue(); const jobsSubscribe = useJobsSubscribe(); @@ -233,4 +234,4 @@ export const JobTable: React.FC = () => { ); -}; +}); diff --git a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx index 1cab7cfb65c..4d09e6cf060 100644 --- a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx @@ -20,55 +20,56 @@ import { ManualLink } from "src/components/Help/context"; import { Icon } from "src/components/Shared/Icon"; import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { useSettings } from "../context"; +import { PatchComponent } from "src/patch"; interface IAutoTagOptions { options: GQL.AutoTagMetadataInput; setOptions: (s: GQL.AutoTagMetadataInput) => void; } -const AutoTagOptions: React.FC = ({ - options, - setOptions: setOptionsState, -}) => { - const { performers, studios, tags } = options; - const wildcard = ["*"]; +const AutoTagOptions: React.FC = PatchComponent( + "AutoTagOptions", + ({ options, setOptions: setOptionsState }) => { + const { performers, studios, tags } = options; + const wildcard = ["*"]; - function set(v?: boolean) { - if (v) { - return wildcard; + function set(v?: boolean) { + if (v) { + return wildcard; + } + return []; } - return []; - } - function setOptions(input: Partial) { - setOptionsState({ ...options, ...input }); - } + function setOptions(input: Partial) { + setOptionsState({ ...options, ...input }); + } - return ( - <> - setOptions({ performers: set(v) })} - /> - setOptions({ studios: set(v) })} - /> - setOptions({ tags: set(v) })} - /> - - ); -}; + return ( + <> + setOptions({ performers: set(v) })} + /> + setOptions({ studios: set(v) })} + /> + setOptions({ tags: set(v) })} + /> + + ); + } +); -export const LibraryTasks: React.FC = () => { +export const LibraryTasks: React.FC = PatchComponent("LibraryTasks", () => { const intl = useIntl(); const Toast = useToast(); const { ui, saveUI, loading } = useSettings(); @@ -154,6 +155,7 @@ export const LibraryTasks: React.FC = () => { // combine the defaults with the system preview generation settings // only do this once // don't do this if UI had a default + if (!configRead && !taskDefaults?.generate) { if (configuration?.defaults.generate) { const { generate } = configuration.defaults; @@ -444,4 +446,4 @@ export const LibraryTasks: React.FC = () => {
); -}; +}); diff --git a/ui/v2.5/src/components/Settings/Tasks/PluginTasks.tsx b/ui/v2.5/src/components/Settings/Tasks/PluginTasks.tsx index a0f78a3f02f..c375eac34f2 100644 --- a/ui/v2.5/src/components/Settings/Tasks/PluginTasks.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/PluginTasks.tsx @@ -6,11 +6,12 @@ import { useToast } from "src/hooks/Toast"; import * as GQL from "src/core/generated-graphql"; import { SettingSection } from "../SettingSection"; import { Setting, SettingGroup } from "../Inputs"; +import { PatchComponent } from "src/patch"; type Plugin = Pick; type PluginTask = Pick; -export const PluginTasks: React.FC = () => { +export const PluginTasks: React.FC = PatchComponent("PluginTasks", () => { const intl = useIntl(); const Toast = useToast(); @@ -73,4 +74,4 @@ export const PluginTasks: React.FC = () => { ); -}; +}); diff --git a/ui/v2.5/src/components/Settings/Tasks/ScanOptions.tsx b/ui/v2.5/src/components/Settings/Tasks/ScanOptions.tsx index c23916d2eef..0e3438557f4 100644 --- a/ui/v2.5/src/components/Settings/Tasks/ScanOptions.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/ScanOptions.tsx @@ -1,82 +1,83 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; import { BooleanSetting } from "../Inputs"; +import { PatchComponent } from "src/patch"; interface IScanOptions { options: GQL.ScanMetadataInput; setOptions: (s: GQL.ScanMetadataInput) => void; } -export const ScanOptions: React.FC = ({ - options, - setOptions: setOptionsState, -}) => { - const { - scanGenerateCovers, - scanGeneratePreviews, - scanGenerateImagePreviews, - scanGenerateSprites, - scanGeneratePhashes, - scanGenerateThumbnails, - scanGenerateClipPreviews, - } = options; +export const ScanOptions: React.FC = PatchComponent( + "ScanOptions", + ({ options, setOptions: setOptionsState }) => { + const { + scanGenerateCovers, + scanGeneratePreviews, + scanGenerateImagePreviews, + scanGenerateSprites, + scanGeneratePhashes, + scanGenerateThumbnails, + scanGenerateClipPreviews, + } = options; - function setOptions(input: Partial) { - setOptionsState({ ...options, ...input }); - } + function setOptions(input: Partial) { + setOptionsState({ ...options, ...input }); + } - return ( - <> - setOptions({ scanGenerateCovers: v })} - /> - setOptions({ scanGeneratePreviews: v })} - /> - setOptions({ scanGenerateImagePreviews: v })} - /> + return ( + <> + setOptions({ scanGenerateCovers: v })} + /> + setOptions({ scanGeneratePreviews: v })} + /> + setOptions({ scanGenerateImagePreviews: v })} + /> - setOptions({ scanGenerateSprites: v })} - /> - setOptions({ scanGeneratePhashes: v })} - /> - setOptions({ scanGenerateThumbnails: v })} - /> - setOptions({ scanGenerateClipPreviews: v })} - /> - - ); -}; + setOptions({ scanGenerateSprites: v })} + /> + setOptions({ scanGeneratePhashes: v })} + /> + setOptions({ scanGenerateThumbnails: v })} + /> + setOptions({ scanGenerateClipPreviews: v })} + /> + + ); + } +); diff --git a/ui/v2.5/src/components/Settings/Tasks/SettingsTasksPanel.tsx b/ui/v2.5/src/components/Settings/Tasks/SettingsTasksPanel.tsx index 69db8b21634..4b21f4d2e69 100644 --- a/ui/v2.5/src/components/Settings/Tasks/SettingsTasksPanel.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/SettingsTasksPanel.tsx @@ -5,47 +5,54 @@ import { LibraryTasks } from "./LibraryTasks"; import { DataManagementTasks } from "./DataManagementTasks"; import { PluginTasks } from "./PluginTasks"; import { JobTable } from "./JobTable"; +import { PatchComponent } from "src/patch"; -export const SettingsTasksPanel: React.FC = () => { - const intl = useIntl(); - const [isBackupRunning, setIsBackupRunning] = useState(false); - const [isAnonymiseRunning, setIsAnonymiseRunning] = useState(false); +export const SettingsTasksPanel: React.FC = PatchComponent( + "SettingsTasksPanel", + () => { + const intl = useIntl(); + const [isBackupRunning, setIsBackupRunning] = useState(false); + const [isAnonymiseRunning, setIsAnonymiseRunning] = + useState(false); - if (isBackupRunning) { - return ( - - ); - } + if (isBackupRunning) { + return ( + + ); + } - if (isAnonymiseRunning) { - return ( - - ); - } + if (isAnonymiseRunning) { + return ( + + ); + } - return ( -
-
-

{intl.formatMessage({ id: "config.tasks.job_queue" })}

- -
+ return ( +
+
+

{intl.formatMessage({ id: "config.tasks.job_queue" })}

+ +
-
- -
- -
- +
+ +
+ +
+ +
-
- ); -}; + ); + } +);