fix: plugin dependencies not enabling (#150)

This commit is contained in:
megumin 2022-10-23 19:09:02 +01:00 committed by GitHub
parent ff9d904fcb
commit ffbb52512c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 18 deletions

View file

@ -18,19 +18,23 @@
import Plugins from "plugins"; import Plugins from "plugins";
import { showNotice } from "../../api/Notices";
import { Settings, useSettings } from "../../api/settings"; import { Settings, useSettings } from "../../api/settings";
import { startPlugin, stopPlugin } from "../../plugins"; import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
import { Modals } from "../../utils"; import { Logger, Modals } from "../../utils";
import { ChangeList } from "../../utils/ChangeList"; import { ChangeList } from "../../utils/ChangeList";
import { classes, lazyWebpack } from "../../utils/misc"; import { classes, lazyWebpack } from "../../utils/misc";
import { Plugin } from "../../utils/types"; import { Plugin } from "../../utils/types";
import { filters } from "../../webpack"; import { filters } from "../../webpack";
import { Alerts, Button, Forms, Margins, Parser, React, Switch, Text, TextInput, Toasts, Tooltip } from "../../webpack/common"; import { Alerts, Button, Forms, Margins, Parser, React, Switch, Text, TextInput, Toasts, Tooltip } from "../../webpack/common";
import ErrorBoundary from "../ErrorBoundary"; import ErrorBoundary from "../ErrorBoundary";
import { ErrorCard } from "../ErrorCard";
import { Flex } from "../Flex"; import { Flex } from "../Flex";
import PluginModal from "./PluginModal"; import PluginModal from "./PluginModal";
import * as styles from "./styles"; import * as styles from "./styles";
const logger = new Logger("PluginSettings", "#a6d189");
const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
const InputStyles = lazyWebpack(filters.byProps(["inputDefault", "inputWrapper"])); 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> { interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
plugin: Plugin; plugin: Plugin;
disabled: boolean; disabled: boolean;
onRestartNeeded(): void; onRestartNeeded(name: string): void;
} }
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave }: PluginCardProps) { 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() { function isEnabled() {
return settings?.enabled || plugin.started; return pluginSettings?.enabled || plugin.started;
} }
function openModal() { function openModal() {
Modals.openModalLazy(async () => { Modals.openModalLazy(async () => {
return modalProps => { return modalProps => {
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={onRestartNeeded} />; return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
}; };
}); });
} }
function toggleEnabled() { function toggleEnabled() {
const enabled = isEnabled(); const wasEnabled = isEnabled();
const result = enabled ? stopPlugin(plugin) : startPlugin(plugin);
const action = enabled ? "stop" : "start"; // 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) { if (!result) {
logger.error(`Failed to ${action} plugin ${plugin.name}`);
showErrorToast(`Failed to ${action} plugin: ${plugin.name}`); showErrorToast(`Failed to ${action} plugin: ${plugin.name}`);
return; return;
} }
settings.enabled = !settings.enabled;
if (plugin.patches) onRestartNeeded(); pluginSettings.enabled = !wasEnabled;
} }
return ( return (
@ -93,7 +151,18 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
<Flex style={{ marginTop: "auto", width: "100%", height: "100%", alignItems: "center" }}> <Flex style={{ marginTop: "auto", width: "100%", height: "100%", alignItems: "center" }}>
<Text variant="text-md/bold" style={{ flexGrow: "1" }}>{plugin.name}</Text> <Text variant="text-md/bold" style={{ flexGrow: "1" }}>{plugin.name}</Text>
<button role="switch" onClick={() => openModal()} style={styles.SettingsIcon} className="button-12Fmur"> <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> </button>
</Flex> </Flex>
</Switch> </Switch>
@ -170,6 +239,9 @@ export default ErrorBoundary.wrap(function Settings() {
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}> <Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
Plugins Plugins
</Forms.FormTitle> </Forms.FormTitle>
<ReloadRequiredCard plugins={[...changes.getChanges()]} style={{ marginBottom: 16 }} />
<div style={styles.FiltersBar}> <div style={styles.FiltersBar}>
<TextInput value={searchValue.value} placeholder={"Search for a plugin..."} onChange={onSearch} style={{ marginBottom: 24 }} /> <TextInput value={searchValue.value} placeholder={"Search for a plugin..."} onChange={onSearch} style={{ marginBottom: 24 }} />
<div className={InputStyles.inputWrapper}> <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 enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled);
const dependency = enabledDependants?.length; const dependency = enabledDependants?.length;
return <PluginCard return <PluginCard
onRestartNeeded={() => { onRestartNeeded={name => changes.add(name)}
changes.handleChange(plugin.name);
}}
disabled={plugin.required || !!dependency} disabled={plugin.required || !!dependency}
plugin={plugin} plugin={plugin}
/>; />;
@ -223,9 +293,7 @@ export default ErrorBoundary.wrap(function Settings() {
<PluginCard <PluginCard
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onRestartNeeded={() => { onRestartNeeded={name => changes.add(name)}
changes.handleChange(plugin.name);
}}
disabled={plugin.required || !!dependency} disabled={plugin.required || !!dependency}
plugin={plugin} plugin={plugin}
/> />

View file

@ -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) { export function startPlugin(p: Plugin) {
if (p.start) { if (p.start) {
logger.info("Starting plugin", p.name); logger.info("Starting plugin", p.name);

View file

@ -32,6 +32,14 @@ export class ChangeList<T>{
this.set.add(item); this.set.add(item);
} }
public add(item: T) {
return this.set.add(item);
}
public remove(item: T) {
return this.set.delete(item);
}
public getChanges() { public getChanges() {
return this.set.values(); return this.set.values();
} }