aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/PluginSettings/index.tsx104
-rw-r--r--src/plugins/index.ts20
-rw-r--r--src/utils/ChangeList.ts8
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();
}