diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/PluginSettings/index.tsx | 104 | ||||
-rw-r--r-- | src/plugins/index.ts | 20 | ||||
-rw-r--r-- | src/utils/ChangeList.ts | 8 |
3 files changed, 114 insertions, 18 deletions
diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index dbb5161..578c0e0 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<HTMLDivElement> { + 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 ( + <ErrorCard {...props} style={{ padding: "1em", display: "grid", gridTemplateColumns: "1fr auto", gap: 8, ...props.style }}> + <span style={{ margin: "auto 0" }}> + {pluginPrefix} <code>{plugins.join(", ")}</code>{pluginSuffix} + </span> + <Button look={Button.Looks.INVERTED} onClick={() => location.reload()}>Reload</Button> + </ErrorCard> + ); +} + interface PluginCardProps extends React.HTMLProps<HTMLDivElement> { 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 <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={onRestartNeeded} />; + return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => 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 <Flex style={{ marginTop: "auto", width: "100%", height: "100%", alignItems: "center" }}> <Text variant="text-md/bold" style={{ flexGrow: "1" }}>{plugin.name}</Text> <button role="switch" onClick={() => openModal()} style={styles.SettingsIcon} className="button-12Fmur"> - {plugin.options ? <CogWheel /> : <InfoIcon width="24" height="24" />} + {plugin.options + ? <CogWheel + style={{ color: iconHover ? "" : "var(--text-muted)" }} + onMouseEnter={() => setIconHover(true)} + onMouseLeave={() => setIconHover(false)} + /> + : <InfoIcon + width="24" height="24" + style={{ color: iconHover ? "" : "var(--text-muted)" }} + onMouseEnter={() => setIconHover(true)} + onMouseLeave={() => setIconHover(false)} + />} </button> </Flex> </Switch> @@ -170,6 +239,9 @@ export default ErrorBoundary.wrap(function Settings() { <Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}> Plugins </Forms.FormTitle> + + <ReloadRequiredCard plugins={[...changes.getChanges()]} style={{ marginBottom: 16 }} /> + <div style={styles.FiltersBar}> <TextInput value={searchValue.value} placeholder={"Search for a plugin..."} onChange={onSearch} style={{ marginBottom: 24 }} /> <div className={InputStyles.inputWrapper}> @@ -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 <PluginCard - onRestartNeeded={() => { - 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() { <PluginCard onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - onRestartNeeded={() => { - 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 f15a711..2ce9c0f 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 4f6bbb8..cce37a3 100644 --- a/src/utils/ChangeList.ts +++ b/src/utils/ChangeList.ts @@ -32,6 +32,14 @@ export class ChangeList<T>{ 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(); } |