fix: plugin dependencies not enabling (#150)
This commit is contained in:
parent
ff9d904fcb
commit
ffbb52512c
3 changed files with 114 additions and 18 deletions
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue