diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index dbb51619..578c0e07 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -18,19 +18,23 @@ import Plugins from "plugins"; +import { showNotice } from "../../api/Notices"; import { Settings, useSettings } from "../../api/settings"; -import { startPlugin, stopPlugin } from "../../plugins"; -import { Modals } from "../../utils"; +import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins"; +import { Logger, Modals } from "../../utils"; import { ChangeList } from "../../utils/ChangeList"; import { classes, lazyWebpack } from "../../utils/misc"; import { Plugin } from "../../utils/types"; import { filters } from "../../webpack"; import { Alerts, Button, Forms, Margins, Parser, React, Switch, Text, TextInput, Toasts, Tooltip } from "../../webpack/common"; import ErrorBoundary from "../ErrorBoundary"; +import { ErrorCard } from "../ErrorCard"; import { Flex } from "../Flex"; import PluginModal from "./PluginModal"; import * as styles from "./styles"; +const logger = new Logger("PluginSettings", "#a6d189"); + const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); const InputStyles = lazyWebpack(filters.byProps(["inputDefault", "inputWrapper"])); @@ -48,37 +52,91 @@ function showErrorToast(message: string) { }); } +interface ReloadRequiredCardProps extends React.HTMLProps { + plugins: string[]; +} + +function ReloadRequiredCard({ plugins, ...props }: ReloadRequiredCardProps) { + if (plugins.length === 0) return null; + + const pluginPrefix = plugins.length === 1 ? "The plugin" : "The following plugins require a reload to apply changes:"; + const pluginSuffix = plugins.length === 1 ? " requires a reload to apply changes." : "."; + + return ( + + + {pluginPrefix} {plugins.join(", ")}{pluginSuffix} + + + + ); +} + interface PluginCardProps extends React.HTMLProps { plugin: Plugin; disabled: boolean; - onRestartNeeded(): void; + onRestartNeeded(name: string): void; } function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave }: PluginCardProps) { - const settings = useSettings().plugins[plugin.name]; + const settings = useSettings(); + const pluginSettings = settings.plugins[plugin.name]; + + const [iconHover, setIconHover] = React.useState(false); function isEnabled() { - return settings?.enabled || plugin.started; + return pluginSettings?.enabled || plugin.started; } function openModal() { Modals.openModalLazy(async () => { return modalProps => { - return ; + return onRestartNeeded(plugin.name)} />; }; }); } function toggleEnabled() { - const enabled = isEnabled(); - const result = enabled ? stopPlugin(plugin) : startPlugin(plugin); - const action = enabled ? "stop" : "start"; + const wasEnabled = isEnabled(); + + // If we're enabling a plugin, make sure all deps are enabled recursively. + if (!wasEnabled) { + const { restartNeeded, failures } = startDependenciesRecursive(plugin); + if (failures.length) { + logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`); + showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null); + return; + } else if (restartNeeded) { + // If any dependencies have patches, don't start the plugin yet. + pluginSettings.enabled = true; + onRestartNeeded(plugin.name); + return; + } + } + + // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes. + if (plugin.patches) { + pluginSettings.enabled = !wasEnabled; + onRestartNeeded(plugin.name); + return; + } + + // If the plugin is enabled, but hasn't been started, then we can just toggle it off. + if (wasEnabled && !plugin.started) { + pluginSettings.enabled = !wasEnabled; + return; + } + + const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin); + const action = wasEnabled ? "stop" : "start"; + if (!result) { + logger.error(`Failed to ${action} plugin ${plugin.name}`); showErrorToast(`Failed to ${action} plugin: ${plugin.name}`); return; } - settings.enabled = !settings.enabled; - if (plugin.patches) onRestartNeeded(); + + pluginSettings.enabled = !wasEnabled; } return ( @@ -93,7 +151,18 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe {plugin.name} @@ -170,6 +239,9 @@ export default ErrorBoundary.wrap(function Settings() { Plugins + + +
@@ -195,9 +267,7 @@ export default ErrorBoundary.wrap(function Settings() { const enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled); const dependency = enabledDependants?.length; return { - changes.handleChange(plugin.name); - }} + onRestartNeeded={name => changes.add(name)} disabled={plugin.required || !!dependency} plugin={plugin} />; @@ -223,9 +293,7 @@ export default ErrorBoundary.wrap(function Settings() { { - changes.handleChange(plugin.name); - }} + onRestartNeeded={name => changes.add(name)} disabled={plugin.required || !!dependency} plugin={plugin} /> diff --git a/src/plugins/index.ts b/src/plugins/index.ts index f15a7118..2ce9c0f2 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -42,6 +42,26 @@ export function startAllPlugins() { } } +export function startDependenciesRecursive(p: Plugin) { + let restartNeeded = false; + const failures: string[] = []; + if (p.dependencies) for (const dep of p.dependencies) { + if (!Settings.plugins[dep].enabled) { + startDependenciesRecursive(Plugins[dep]); + // If the plugin has patches, don't start the plugin, just enable it. + if (Plugins[dep].patches) { + logger.warn(`Enabling dependency ${dep} requires restart.`); + Settings.plugins[dep].enabled = true; + restartNeeded = true; + continue; + } + const result = startPlugin(Plugins[dep]); + if (!result) failures.push(dep); + } + } + return { restartNeeded, failures }; +} + export function startPlugin(p: Plugin) { if (p.start) { logger.info("Starting plugin", p.name); diff --git a/src/utils/ChangeList.ts b/src/utils/ChangeList.ts index 4f6bbb88..cce37a35 100644 --- a/src/utils/ChangeList.ts +++ b/src/utils/ChangeList.ts @@ -32,6 +32,14 @@ export class ChangeList{ this.set.add(item); } + public add(item: T) { + return this.set.add(item); + } + + public remove(item: T) { + return this.set.delete(item); + } + public getChanges() { return this.set.values(); }