/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
import Plugins from "~plugins";
import { showNotice } from "../../api/Notices";
import { Settings, useSettings } from "../../api/settings";
import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
import { ChangeList } from "../../utils/ChangeList";
import Logger from "../../utils/Logger";
import { classes, LazyComponent, lazyWebpack } from "../../utils/misc";
import { openModalLazy } from "../../utils/modal";
import { Plugin } from "../../utils/types";
import { filters, findByCode } from "../../webpack";
import { Alerts, Button, Forms, Margins, Parser, React, Select, Switch, Text, TextInput, Toasts, Tooltip } from "../../webpack/common";
import ErrorBoundary from "../ErrorBoundary";
import { ErrorCard } from "../ErrorCard";
import { Flex } from "../Flex";
import { handleComponentFailed } from "../handleComponentFailed";
import PluginModal from "./PluginModal";
import * as styles from "./styles";
const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = lazyWebpack(filters.byProps("inputDefault", "inputWrapper"));
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
function showErrorToast(message: string) {
Toasts.show({
message,
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
options: {
position: Toasts.Position.BOTTOM
}
});
}
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(name: string): void;
}
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave }: PluginCardProps) {
const settings = useSettings();
const pluginSettings = settings.plugins[plugin.name];
const [iconHover, setIconHover] = React.useState(false);
function isEnabled() {
return pluginSettings?.enabled || plugin.started;
}
function openModal() {
openModalLazy(async () => {
return modalProps => {
return onRestartNeeded(plugin.name)} />;
};
});
}
function toggleEnabled() {
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;
}
pluginSettings.enabled = !wasEnabled;
}
return (
{plugin.description}
}
hideBorder={true}
>
{plugin.name}
);
}
export default ErrorBoundary.wrap(function Settings() {
const settings = useSettings();
const changes = React.useMemo(() => new ChangeList(), []);
React.useEffect(() => {
return () => void (changes.hasChanges && Alerts.show({
title: "Restart required",
body: (
<>
);
}, {
message: "Failed to render the Plugin Settings. If this persists, try using the installer to reinstall!",
onError: handleComponentFailed,
});
function makeDependencyList(deps: string[]) {
return (
This plugin is required by:
{deps.map((dep: string) => {dep})}
);
}
function dependencyCheck(pluginName: string, depMap: Record): string[] {
return depMap[pluginName]?.filter(d => Settings.plugins[d].enabled) || [];
}