2025-07-15 15:57:24 +02:00
|
|
|
/*
|
|
|
|
|
* Vencord, a Discord client mod
|
|
|
|
|
* Copyright (c) 2025 Vendicated and contributors
|
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { showNotice } from "@api/Notices";
|
2025-11-16 15:17:35 +01:00
|
|
|
import { isPluginEnabled, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
|
2025-07-15 14:56:05 -04:00
|
|
|
import { classNameFactory } from "@api/Styles";
|
2025-07-15 15:57:24 +02:00
|
|
|
import { CogWheel, InfoIcon } from "@components/Icons";
|
|
|
|
|
import { AddonCard } from "@components/settings/AddonCard";
|
2025-07-15 14:56:05 -04:00
|
|
|
import { Logger } from "@utils/Logger";
|
2025-10-11 01:42:14 +02:00
|
|
|
import { isObjectEmpty } from "@utils/misc";
|
2025-07-15 15:57:24 +02:00
|
|
|
import { Plugin } from "@utils/types";
|
|
|
|
|
import { React, showToast, Toasts } from "@webpack/common";
|
|
|
|
|
import { Settings } from "Vencord";
|
|
|
|
|
|
2025-09-17 14:35:39 -04:00
|
|
|
import { PluginMeta } from "~plugins";
|
|
|
|
|
|
2025-07-15 15:57:24 +02:00
|
|
|
import { openPluginModal } from "./PluginModal";
|
|
|
|
|
|
2025-07-15 14:56:05 -04:00
|
|
|
const logger = new Logger("PluginCard");
|
|
|
|
|
const cl = classNameFactory("vc-plugins-");
|
2025-07-15 15:57:24 +02:00
|
|
|
interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
|
|
|
|
plugin: Plugin;
|
2025-09-26 00:25:09 -04:00
|
|
|
disabled?: boolean;
|
2025-07-15 15:57:24 +02:00
|
|
|
onRestartNeeded(name: string, key: string): void;
|
|
|
|
|
isNew?: boolean;
|
2025-09-26 00:25:09 -04:00
|
|
|
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
|
|
|
|
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
2025-07-15 15:57:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
|
|
|
|
const settings = Settings.plugins[plugin.name];
|
2025-11-11 11:22:01 -05:00
|
|
|
const pluginMeta = PluginMeta[plugin.name];
|
|
|
|
|
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false;
|
|
|
|
|
const isVencordPlugin = pluginMeta.folderName.startsWith("src/plugins/") ?? false;
|
|
|
|
|
const isUserPlugin = pluginMeta?.userPlugin ?? false;
|
|
|
|
|
const isModifiedPlugin = plugin.isModified ?? false;
|
2025-07-15 15:57:24 +02:00
|
|
|
|
|
|
|
|
const isEnabled = () => isPluginEnabled(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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (restartNeeded) {
|
|
|
|
|
// If any dependencies have patches, don't start the plugin yet.
|
|
|
|
|
settings.enabled = true;
|
|
|
|
|
onRestartNeeded(plugin.name, "enabled");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.
|
|
|
|
|
if (plugin.patches?.length) {
|
|
|
|
|
settings.enabled = !wasEnabled;
|
|
|
|
|
onRestartNeeded(plugin.name, "enabled");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the plugin is enabled, but hasn't been started, then we can just toggle it off.
|
|
|
|
|
if (wasEnabled && !plugin.started) {
|
|
|
|
|
settings.enabled = !wasEnabled;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
|
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
|
settings.enabled = false;
|
|
|
|
|
|
|
|
|
|
const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`;
|
|
|
|
|
showToast(msg, Toasts.Type.FAILURE, {
|
|
|
|
|
position: Toasts.Position.BOTTOM,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
settings.enabled = !wasEnabled;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-12 23:39:43 -04:00
|
|
|
const pluginInfo = [
|
|
|
|
|
{
|
|
|
|
|
condition: isModifiedPlugin,
|
|
|
|
|
src: "https://equicord.org/assets/icons/equicord/modified.png",
|
|
|
|
|
alt: "Modified",
|
|
|
|
|
title: "Modified Vencord Plugin"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
condition: isEquicordPlugin,
|
|
|
|
|
src: "https://equicord.org/assets/icons/equicord/icon.png",
|
|
|
|
|
alt: "Equicord",
|
|
|
|
|
title: "Equicord Plugin"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
condition: isVencordPlugin,
|
|
|
|
|
src: "https://equicord.org/assets/icons/vencord/icon-light.png",
|
|
|
|
|
alt: "Vencord",
|
|
|
|
|
title: "Vencord Plugin"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
condition: isUserPlugin,
|
|
|
|
|
src: "https://equicord.org/assets/icons/misc/userplugin.png",
|
|
|
|
|
alt: "User",
|
|
|
|
|
title: "User Plugin"
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const pluginDetails = pluginInfo.find(p => p.condition);
|
|
|
|
|
|
|
|
|
|
const sourceBadge = pluginDetails ? (
|
2025-09-17 20:43:46 -04:00
|
|
|
<img
|
2025-10-12 23:39:43 -04:00
|
|
|
src={pluginDetails.src}
|
|
|
|
|
alt={pluginDetails.alt}
|
|
|
|
|
className={cl("source")}
|
2025-09-17 20:43:46 -04:00
|
|
|
/>
|
2025-10-12 23:39:43 -04:00
|
|
|
) : null;
|
|
|
|
|
|
|
|
|
|
const tooltip = pluginDetails?.title || "Unknown Plugin";
|
2025-09-17 14:35:39 -04:00
|
|
|
|
2025-07-15 15:57:24 +02:00
|
|
|
return (
|
|
|
|
|
<AddonCard
|
|
|
|
|
name={plugin.name}
|
2025-09-17 14:35:39 -04:00
|
|
|
sourceBadge={sourceBadge}
|
2025-10-12 23:39:43 -04:00
|
|
|
tooltip={tooltip}
|
2025-07-15 15:57:24 +02:00
|
|
|
description={plugin.description}
|
|
|
|
|
isNew={isNew}
|
|
|
|
|
enabled={isEnabled()}
|
|
|
|
|
setEnabled={toggleEnabled}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
onMouseEnter={onMouseEnter}
|
|
|
|
|
onMouseLeave={onMouseLeave}
|
|
|
|
|
infoButton={
|
|
|
|
|
<button
|
|
|
|
|
role="switch"
|
|
|
|
|
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
2025-10-11 01:42:14 +02:00
|
|
|
className={cl("info-button")}
|
2025-07-15 15:57:24 +02:00
|
|
|
>
|
|
|
|
|
{plugin.options && !isObjectEmpty(plugin.options)
|
|
|
|
|
? <CogWheel className={cl("info-icon")} />
|
|
|
|
|
: <InfoIcon className={cl("info-icon")} />
|
|
|
|
|
}
|
|
|
|
|
</button>
|
|
|
|
|
} />
|
|
|
|
|
);
|
|
|
|
|
}
|