This commit is contained in:
thororen1234 2025-07-15 14:56:05 -04:00
parent 804bdb32c9
commit 1c2d328f5a
No known key found for this signature in database
30 changed files with 754 additions and 296 deletions

View file

@ -24,10 +24,7 @@ import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { CogWheel, DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PluginIcon, PlusIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import { AddonCard } from "@components/VencordSettings/AddonCard";
import { QuickAction, QuickActionCard } from "@components/VencordSettings/quickActions";
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
import { AddonCard, openPluginModal, QuickAction, QuickActionCard, SettingsTab, wrapTab } from "@components/settings";
import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";

View file

@ -0,0 +1,60 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { DefinedSettings, PluginOptionSlider } from "@utils/types";
import { Forms, Slider, useEffect, useState } from "@webpack/common";
interface Props<T> {
option: T;
onChange(newValue: any): void;
pluginSettings: {
[setting: string]: any;
enabled: boolean;
};
id: string;
onError(hasError: boolean): void;
definedSettings?: DefinedSettings;
}
export function SettingSliderComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: Props<PluginOptionSlider>) {
const def = pluginSettings[id] ?? option.default;
const [error, setError] = useState<string | null>(null);
useEffect(() => {
onError(error !== null);
}, [error]);
function handleChange(newValue: number): void {
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setError(null);
onChange(newValue);
}
}
return (
<Forms.FormSection>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<Slider
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers}
minValue={option.markers[0]}
maxValue={option.markers[option.markers.length - 1]}
initialValue={def}
onValueChange={handleChange}
onValueRender={(v: number) => String(v.toFixed(2))}
stickToMarkers={option.stickToMarkers ?? true}
{...option.componentProps}
/>
</Forms.FormSection>
);
}

View file

@ -6,6 +6,7 @@
import "./styles.css";
export { default as ThemesTab } from "../../ThemeSettings/ThemesTab";
export * from "./BaseTab";
export { default as PatchHelperTab } from "./patchHelper";
export { default as PluginsTab } from "./plugins";
@ -13,6 +14,5 @@ export { openContributorModal } from "./plugins/ContributorModal";
export { openPluginModal } from "./plugins/PluginModal";
export { default as BackupAndRestoreTab } from "./sync/BackupAndRestoreTab";
export { default as CloudTab } from "./sync/CloudTab";
export { default as ThemesTab } from "./themes";
export { openUpdaterModal, default as UpdaterTab } from "./updater";
export { default as VencordTab } from "./vencord";

View file

@ -5,18 +5,22 @@
*/
import { showNotice } from "@api/Notices";
import { classNameFactory } from "@api/Styles";
import { CogWheel, InfoIcon } from "@components/Icons";
import { AddonCard } from "@components/settings/AddonCard";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { classes, isObjectEmpty } from "@utils/misc";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { React, showToast, Toasts } from "@webpack/common";
import { Settings } from "Vencord";
import { cl, logger } from ".";
import { openPluginModal } from "./PluginModal";
const logger = new Logger("PluginCard");
const cl = classNameFactory("vc-plugins-");
// Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin, isPluginEnabled } = proxyLazy(() => require("plugins") as typeof import("plugins"));

View file

@ -201,7 +201,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
{renderSettings()}
</Forms.FormSection>
</ModalContent>;
</ModalContent>
{
hasSettings && <ModalFooter>
<Flex flexDirection="column" style={{ width: "100%" }}>

View file

@ -19,29 +19,36 @@
import "./styles.css";
import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { stopPlugin } from "@plugins/index";
import { CogWheel, InfoIcon } from "@components/Icons";
import { openPluginModal, SettingsTab } from "@components/settings";
import { AddonCard } from "@components/settings/AddonCard";
import { ChangeList } from "@utils/ChangeList";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter, useCleanupEffect } from "@utils/react";
import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Flex, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo, useState } from "@webpack/common";
import { Alerts, Button, Card, Flex, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import { JSX } from "react";
import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins";
import { PluginCard } from "./PluginCard";
import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards";
// Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("plugins"));
export const cl = classNameFactory("vc-plugins-");
export const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
function showErrorToast(message: string) {
Toasts.show({
@ -57,25 +64,23 @@ function showErrorToast(message: string) {
function ReloadRequiredCard({ required, enabledPlugins, openDisablePluginsModal, resetCheckAndDo }) {
return (
<Card className={classes(cl("info-card"), required && "vc-warning-card")}>
{required
? (
<>
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
<Forms.FormText className={cl("dep-text")}>
Restart now to apply new plugins and their settings
</Forms.FormText>
<Button onClick={() => location.reload()} className={cl("restart-button")}>
Restart
</Button>
</>
)
: (
<>
<Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle>
<Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText>
<Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText>
</>
)}
{required ? (
<>
<Forms.FormTitle tag="h5">Restart required!</Forms.FormTitle>
<Forms.FormText className={cl("dep-text")}>
Restart now to apply new plugins and their settings
</Forms.FormText>
<Button className={cl("restart-button")} onClick={() => location.reload()}>
Restart
</Button>
</>
) : (
<>
<Forms.FormTitle tag="h5">Plugin Management</Forms.FormTitle>
<Forms.FormText>Press the cog wheel or info icon to get more info on a plugin</Forms.FormText>
<Forms.FormText>Plugins with a cog wheel have settings you can modify!</Forms.FormText>
</>
)}
{enabledPlugins.length > 0 && !required && (
<Button
size={Button.Sizes.SMALL}
@ -93,6 +98,88 @@ function ReloadRequiredCard({ required, enabledPlugins, openDisablePluginsModal,
);
}
interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
plugin: Plugin;
disabled: boolean;
onRestartNeeded(name: string): void;
isNew?: boolean;
}
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name];
const isEnabled = () => Vencord.Plugins.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;
} else if (restartNeeded) {
// If any dependencies have patches, don't start the plugin yet.
settings.enabled = true;
onRestartNeeded(plugin.name);
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);
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}`;
logger.error(msg);
showErrorToast(msg);
return;
}
settings.enabled = !wasEnabled;
}
return (
<AddonCard
name={plugin.name}
description={plugin.description}
isNew={isNew}
enabled={isEnabled()}
setEnabled={toggleEnabled}
disabled={disabled}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
infoButton={
<button
role="switch"
onClick={() => openPluginModal(plugin, onRestartNeeded)}
className={classes(ButtonClasses.button, cl("info-button"))}
>
{plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel className={cl("info-icon")} />
: <InfoIcon className={cl("info-icon")} />}
</button>
}
/>
);
}
const enum SearchStatus {
ALL,
ENABLED,
@ -132,29 +219,28 @@ function ExcludedPluginsList({ search }: { search: string; }) {
);
}
function PluginSettings() {
export default function PluginSettings() {
const settings = useSettings();
const changes = useMemo(() => new ChangeList<string>(), []);
const changes = React.useMemo(() => new ChangeList<string>(), []);
useCleanupEffect(() => {
if (changes.hasChanges)
Alerts.show({
title: "Restart required",
body: (
<>
<p>The following plugins require a restart:</p>
<div>{changes.map((s, i) => (
<>
{i > 0 && ", "}
{Parser.parse("`" + s.split(".")[0] + "`")}
</>
))}</div>
</>
),
confirmText: "Restart now",
cancelText: "Later!",
onConfirm: () => location.reload()
});
React.useEffect(() => {
return () => void (changes.hasChanges && Alerts.show({
title: "Restart required",
body: (
<>
<p>The following plugins require a restart:</p>
<div>{changes.map((s, i) => (
<>
{i > 0 && ", "}
{Parser.parse("`" + s + "`")}
</>
))}</div>
</>
),
confirmText: "Restart now",
cancelText: "Later!",
onConfirm: () => location.reload()
}));
}, []);
const depMap = useMemo(() => {
@ -171,12 +257,10 @@ function PluginSettings() {
return o;
}, []);
const sortedPlugins = useMemo(() =>
Object.values(Plugins).sort((a, b) => a.name.localeCompare(b.name)),
[]
);
const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL });
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
@ -185,19 +269,9 @@ function PluginSettings() {
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const { status } = searchValue;
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
switch (status) {
case SearchStatus.DISABLED:
if (enabled) return false;
break;
case SearchStatus.ENABLED:
if (!enabled) return false;
break;
case SearchStatus.NEW:
if (!newPlugins?.includes(plugin.name)) return false;
break;
}
if (enabled && status === SearchStatus.DISABLED) return false;
if (!enabled && status === SearchStatus.ENABLED) return false;
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (!search.length) return true;
return (
@ -247,7 +321,7 @@ function PluginSettings() {
<PluginCard
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
onRestartNeeded={(name, key) => changes.handleChange(`${name}.${key}`)}
onRestartNeeded={name => changes.handleChange(name)}
disabled={true}
plugin={p}
key={p.name}
@ -258,7 +332,7 @@ function PluginSettings() {
} else {
plugins.push(
<PluginCard
onRestartNeeded={(name, key) => changes.handleChange(`${name}.${key}`)}
onRestartNeeded={name => changes.handleChange(name)}
disabled={false}
plugin={p}
isNew={newPlugins?.includes(p.name)}
@ -472,11 +546,9 @@ function PluginSettings() {
function makeDependencyList(deps: string[]) {
return (
<>
<React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
</>
</React.Fragment>
);
}
export default wrapTab(PluginSettings, "Plugins");

View file

@ -17,25 +17,184 @@
*/
import { useSettings } from "@api/Settings";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { relaunch } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { getRepo, isNewer, UpdateLogger } from "@utils/updater";
import { Forms, React, Switch } from "@webpack/common";
import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
import gitHash from "~git-hash";
import { CommonProps, HashLink, Newer, Updatable } from "./Components";
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
return async () => {
dispatcher(true);
try {
await action();
} catch (e: any) {
UpdateLogger.error("Failed to update", e);
let err: string;
if (!e) {
err = "An unknown error occurred (error is undefined).\nPlease try again.";
} else if (e.code && e.cmd) {
const { code, path, cmd, stderr } = e;
if (code === "ENOENT")
err = `Command \`${path}\` not found.\nPlease install it and try again`;
else {
err = `An error occurred while running \`${cmd}\`:\n`;
err += stderr || `Code \`${code}\`. See the console for more info`;
}
} else {
err = "An unknown error occurred. See the console for more info.";
}
Alerts.show({
title: "Oops!",
body: (
<ErrorCard>
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
</ErrorCard>
)
});
}
finally {
dispatcher(false);
}
};
}
interface CommonProps {
repo: string;
repoPending: boolean;
}
function HashLink({ repo, hash, disabled = false }: { repo: string, hash: string, disabled?: boolean; }) {
return <Link href={`${repo}/commit/${hash}`} disabled={disabled}>
{hash}
</Link>;
}
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
return (
<Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => (
<div key={hash} style={{
marginTop: "0.5em",
marginBottom: "0.5em"
}}>
<code><HashLink {...{ repo, hash }} disabled={repoPending} /></code>
<span style={{
marginLeft: "0.5em",
color: "var(--text-default)"
}}>{message} - {author}</span>
</div>
))}
</Card>
);
}
function Updatable(props: CommonProps) {
const [updates, setUpdates] = React.useState(changes);
const [isChecking, setIsChecking] = React.useState(false);
const [isUpdating, setIsUpdating] = React.useState(false);
const isOutdated = (updates?.length ?? 0) > 0;
return (
<>
<Flex className={classes(Margins.bottom8, Margins.top8)}>
{isOutdated && <Button
size={Button.Sizes.SMALL}
disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsUpdating, async () => {
if (await update()) {
setUpdates([]);
return await new Promise<void>(r => {
Alerts.show({
title: "Update Success!",
body: "Successfully updated. Restart now to apply the changes?",
confirmText: "Restart",
cancelText: "Not now!",
onConfirm() {
relaunch();
r();
},
onCancel: r
});
});
}
})}
>
Update Now
</Button>}
<Button
size={Button.Sizes.SMALL}
disabled={isUpdating || isChecking}
onClick={withDispatcher(setIsChecking, async () => {
const outdated = await checkForUpdates();
if (outdated) {
setUpdates(changes);
} else {
setUpdates([]);
Toasts.show({
message: "No updates found!",
id: Toasts.genId(),
type: Toasts.Type.MESSAGE,
options: {
position: Toasts.Position.BOTTOM
}
});
}
})}
>
Check for Updates
</Button>
</Flex>
{!updates && updateError ? (
<>
<Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText>
<ErrorCard style={{ padding: "1em" }}>
<p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p>
</ErrorCard>
</>
) : (
<Forms.FormText className={Margins.bottom8}>
{isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
</Forms.FormText>
)}
{isOutdated && <Changes updates={updates} {...props} />}
</>
);
}
function Newer(props: CommonProps) {
return (
<>
<Forms.FormText className={Margins.bottom8}>
Your local copy has more recent commits. Please stash or reset them.
</Forms.FormText>
<Changes {...props} updates={changes} />
</>
);
}
function Updater() {
const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
const [repo, err, repoPending] = useAwaiter(getRepo, {
fallbackValue: "Loading...",
onError: e => UpdateLogger.error("Failed to retrieve repo", err)
});
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
React.useEffect(() => {
if (err)
UpdateLogger.error("Failed to retrieve repo", err);
}, [err]);
const commonProps: CommonProps = {
repo,
@ -43,20 +202,19 @@ function Updater() {
};
return (
<SettingsTab title="Vencord Updater">
<SettingsTab title="Equicord Updater">
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
<Switch
value={settings.autoUpdate}
onChange={(v: boolean) => settings.autoUpdate = v}
note="Automatically update Vencord without confirmation prompt"
note="Automatically update Equicord without confirmation prompt"
>
Automatically update
</Switch>
<Switch
value={settings.autoUpdateNotification}
onChange={(v: boolean) => settings.autoUpdateNotification = v}
note="Show a notification when Vencord automatically updates"
note="Shows a notification when Equicord automatically updates"
disabled={!settings.autoUpdate}
>
Get notified when an automatic update completes
@ -75,41 +233,33 @@ function Updater() {
</Link>
)
}
{" "}
(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)
{" "}(<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)
</Forms.FormText>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
{isNewer
? <Newer {...commonProps} />
: <Updatable {...commonProps} />
}
</SettingsTab>
{isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
</SettingsTab >
);
}
export default IS_UPDATER_DISABLED
? null
: wrapTab(Updater, "Updater");
export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");
export const openUpdaterModal = IS_UPDATER_DISABLED
? null
: function () {
const UpdaterTab = wrapTab(Updater, "Updater");
export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () {
const UpdaterTab = wrapTab(Updater, "Updater");
try {
openModal(wrapTab((modalProps: ModalProps) => (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalContent className="vc-updater-modal">
<ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
<UpdaterTab />
</ModalContent>
</ModalRoot>
), "UpdaterModal"));
} catch {
handleSettingsTabError();
}
};
try {
openModal(wrapTab((modalProps: ModalProps) => (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalContent className="vc-updater-modal">
<ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
<UpdaterTab />
</ModalContent>
</ModalRoot>
), "UpdaterModal"));
} catch {
handleSettingsTabError();
}
};

View file

@ -4,14 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import DonateButton from "@components/settings/DonateButton";
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
import { DonateButton } from "@components/settings";
import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Button, GuildMemberStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges";
export const isDonor = (userId: string) => !!(
BadgeAPI.getDonorBadges(userId)?.length > 0
|| GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID)
|| GuildMemberStore.getMember(VC_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID)
);
export function DonateButtonComponent() {

View file

@ -0,0 +1,49 @@
:root {
--vc-donatebutton: #ffc1cbc2;
--vc-donatebutton-secondary: #f7536fe5;
}
.vc-donatebutton {
background-color: var(--background-color) !important;
border-color: var(--vc-donatebutton) !important;
filter: brightness(1);
border-width: .1rem !important;
border-style: solid !important;
}
.vc-donatebutton:hover {
background-color: var(--vc-donatebutton) !important;
border-color: transparent !important;
}
.vc-joindiscordbutton {
background-color: var(--button-secondary-background) !important;
}
.vc-joindiscordbutton:hover {
background-color: var(--button-secondary-background-hover) !important;
}
.vc-settings-card-buttons {
display: flex;
gap: 1em !important;
margin-left: .2rem;
margin-bottom: .2rem;
}
.vc-settingbuttons {
border-radius: 5px !important;
}
.form-switch-warning {
font-size: 16px;
background-color: rgb(240 71 71 / 10%);
color: rgb(240 71 71);
border: 1px solid rgb(240 71 71 / 60%);
border-radius: 4px;
font-weight: 500;
padding: 6px 10px;
text-align: center;
margin-top: 10px;
}

View file

@ -1,44 +1,37 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./VencordTab.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings";
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/index";
import { classNameFactory } from "@api/Styles";
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons";
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
import { DonateButton, InviteButton } from "@components/settings/DonateButton";
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { openContributorModal } from "@components/settings/tabs/plugins/ContributorModal";
import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal";
import { gitRemote } from "@shared/vencordUserAgent";
import { IS_MAC, IS_WINDOWS } from "@utils/constants";
import { DONOR_ROLE_ID, GUILD_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc";
import { identity, isEquicordPluginDev, isPluginDev } from "@utils/misc";
import { relaunch } from "@utils/native";
import { Forms, React, Switch, useMemo, UserStore } from "@webpack/common";
import { Button, Flex, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges";
import { DonateButtonComponent, isDonor } from "./DonateButton";
import { VibrancySettings } from "./MacVibrancySettings";
import { NotificationSection } from "./NotificationSettings";
import { openNotificationSettingsModal } from "./NotificationSettings";
const cl = classNameFactory("vc-settings-");
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
const SHIGGY_DONATE_IMAGE = "https://i.imgur.com/57ATLZu.png";
const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png";
const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png";
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
@ -46,105 +39,114 @@ type KeysOfType<Object, Type> = {
[K in keyof Object]: Object[K] extends Type ? K : never;
}[keyof Object];
function Switches() {
const settings = useSettings(["useQuickCss", "enableReactDevtools", "frameless", "winNativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]);
function EquicordSettings() {
const settings = useSettings();
const Switches = [
{
key: "useQuickCss",
title: "Enable Custom CSS",
note: "Loads your Custom CSS"
},
!IS_WEB && {
key: "enableReactDevtools",
title: "Enable React Developer Tools",
note: "Requires a full restart"
},
!IS_WEB && (!IS_DISCORD_DESKTOP || !IS_WINDOWS ? {
key: "frameless",
title: "Disable the window frame",
note: "Requires a full restart"
} : {
key: "winNativeTitleBar",
title: "Use Windows' native title bar instead of Discord's custom one",
note: "Requires a full restart"
}),
!IS_WEB && {
key: "transparent",
title: "Enable window transparency.",
note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
},
!IS_WEB && IS_WINDOWS && {
key: "winCtrlQ",
title: "Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)",
note: "Requires a full restart"
},
IS_DISCORD_DESKTOP && {
key: "disableMinSize",
title: "Disable minimum window size",
note: "Requires a full restart"
},
] satisfies Array<false | {
key: KeysOfType<typeof settings, boolean>;
title: string;
note: string;
}>;
return Switches.map(s => s && (
<Switch
key={s.key}
value={settings[s.key]}
onChange={v => settings[s.key] = v}
note={s.note}
>
{s.title}
</Switch>
));
}
function VencordSettings() {
const donateImage = useMemo(() =>
Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE,
[]
const donateImage = React.useMemo(
() => (Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE),
[],
);
const needsVibrancySettings = IS_DISCORD_DESKTOP && IS_MAC;
const isWindows = navigator.platform.toLowerCase().startsWith("win");
const isMac = navigator.platform.toLowerCase().startsWith("mac");
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
const user = UserStore.getCurrentUser();
return (
<SettingsTab title="Vencord Settings">
{isDonor(user?.id)
? (
<SpecialCard
title="Donations"
subtitle="Thank you for donating!"
description="You can manage your perks at any time by messaging @vending.machine."
cardImage={VENNIE_DONATOR_IMAGE}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#ED87A9"
>
<DonateButtonComponent />
</SpecialCard>
)
: (
<SpecialCard
title="Support the Project"
description="Please consider supporting the development of Vencord by donating!"
cardImage={donateImage}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#c3a3ce"
>
<DonateButtonComponent />
</SpecialCard>
)
}
const Switches: Array<false | {
key: KeysOfType<typeof settings, boolean>;
title: string;
note: string;
warning: { enabled: boolean; message?: string; };
}
> = [
{
key: "useQuickCss",
title: "Enable Custom CSS",
note: "Loads your Custom CSS",
warning: { enabled: false },
},
!IS_WEB && {
key: "enableReactDevtools",
title: "Enable React Developer Tools",
note: "Requires a full restart",
warning: { enabled: false },
},
!IS_WEB &&
(!IS_DISCORD_DESKTOP || !isWindows
? {
key: "frameless",
title: "Disable the Window Frame",
note: "Requires a full restart",
warning: { enabled: false },
}
: {
key: "winNativeTitleBar",
title:
"Use Windows' native title bar instead of Discord's custom one",
note: "Requires a full restart",
warning: { enabled: false },
}),
!IS_WEB && {
key: "transparent",
title: "Enable Window Transparency",
note: "You need a theme that supports transparency or this will do nothing. Requires a full restart!",
warning: {
enabled: isWindows,
message: "Enabling this will prevent you from snapping this window.",
},
},
!IS_WEB &&
isWindows && {
key: "winCtrlQ",
title:
"Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)",
note: "Requires a full restart",
warning: { enabled: false },
},
IS_DISCORD_DESKTOP && {
key: "disableMinSize",
title: "Disable Minimum Window Size",
note: "Requires a full restart",
warning: { enabled: false },
},
];
{isPluginDev(user?.id) && (
return (
<SettingsTab title="Equicord Settings">
{(isEquicordDonor(user?.id) || isVencordDonor(user?.id)) ? (
<SpecialCard
title="Donations"
subtitle="Thank you for donating!"
description={
isEquicordDonor(user?.id) && isVencordDonor(user?.id)
? "All Vencord users can see your Vencord donor badge, and Equicord users can see your Equicord donor badge. To change your Vencord donor badge, contact @vending.machine. For your Equicord donor badge, make a ticket in Equicord's server."
: isVencordDonor(user?.id)
? "All Vencord users can see your badge! You can manage your perks by messaging @vending.machine."
: "All Equicord users can see your badge! You can manage your perks by making a ticket in Equicord's server."
}
cardImage={VENNIE_DONATOR_IMAGE}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#ED87A9"
>
<DonateButtonComponent />
</SpecialCard>
) : (
<SpecialCard
title="Support the Project"
description="Please consider supporting the development of Equicord by donating!"
cardImage={donateImage}
backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#c3a3ce"
>
<DonateButtonComponent />
</SpecialCard>
)}
{isPluginDev(user?.id) || isEquicordPluginDev(user?.id) && (
<SpecialCard
title="Contributions"
subtitle="Thank you for contributing!"
description="Since you've contributed to Vencord you now have a cool new badge!"
description="Since you've contributed to Equicord you now have a cool new badge!"
cardImage={COZY_CONTRIB_IMAGE}
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
backgroundColor="#EDCC87"
@ -152,7 +154,6 @@ function VencordSettings() {
buttonOnClick={() => openContributorModal(user)}
/>
)}
<Forms.FormSection title="Quick Actions">
<QuickActionCard>
<QuickAction
@ -166,23 +167,27 @@ function VencordSettings() {
action={() => VencordNative.quickCss.openEditor()}
/>
{!IS_WEB && (
<>
<QuickAction
Icon={RestartIcon}
text="Relaunch Discord"
action={relaunch}
/>
<QuickAction
Icon={FolderIcon}
text="Open Settings Folder"
action={() => VencordNative.settings.openFolder()}
/>
</>
<QuickAction
Icon={RestartIcon}
text="Relaunch Discord"
action={relaunch}
/>
)}
{!IS_WEB && (
<QuickAction
Icon={FolderIcon}
text="Open Settings Folder"
action={() => VencordNative.settings.openFolder()}
/>
)}
<QuickAction
Icon={GithubIcon}
text="View Source Code"
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
action={() =>
VencordNative.native.openExternal(
"https://github.com/" + gitRemote,
)
}
/>
</QuickActionCard>
</Forms.FormSection>
@ -190,22 +195,158 @@ function VencordSettings() {
<Forms.FormDivider />
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
<Forms.FormText
className={Margins.bottom20}
style={{ color: "var(--text-muted)" }}
>
Hint: You can change the position of this settings section in the{" "}
<a onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}>
<Button
look={Button.Looks.BLANK}
style={{ color: "var(--text-link)", display: "inline-block" }}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
>
settings of the Settings plugin
</a>!
</Button>
!
</Forms.FormText>
<Switches />
{Switches.map(
s =>
s && (
<Switch
key={s.key}
value={settings[s.key]}
onChange={v => (settings[s.key] = v)}
note={
s.warning.enabled ? (
<>
{s.note}
<div className="form-switch-warning">
{s.warning.message}
</div>
</>
) : (
s.note
)
}
>
{s.title}
</Switch>
),
)}
</Forms.FormSection>
{needsVibrancySettings && (
<>
<Forms.FormTitle tag="h5">
Window vibrancy style (requires restart)
</Forms.FormTitle>
<Select
className={Margins.bottom20}
placeholder="Window vibrancy style"
options={[
// Sorted from most opaque to most transparent
{
label: "No vibrancy",
value: undefined,
},
{
label: "Under Page (window tinting)",
value: "under-page",
},
{
label: "Content",
value: "content",
},
{
label: "Window",
value: "window",
},
{
label: "Selection",
value: "selection",
},
{
label: "Titlebar",
value: "titlebar",
},
{
label: "Header",
value: "header",
},
{
label: "Sidebar",
value: "sidebar",
},
{
label: "Tooltip",
value: "tooltip",
},
{
label: "Menu",
value: "menu",
},
{
label: "Popover",
value: "popover",
},
{
label: "Fullscreen UI (transparent but slightly muted)",
value: "fullscreen-ui",
},
{
label: "HUD (Most transparent)",
value: "hud",
},
]}
select={v => (settings.macosVibrancyStyle = v)}
isSelected={v => settings.macosVibrancyStyle === v}
serialize={identity}
/>
</>
)}
{needsVibrancySettings && <VibrancySettings />}
<NotificationSection />
<Forms.FormSection
className={Margins.top16}
title="Equicord Notifications"
tag="h5"
>
<Flex>
<Button onClick={openNotificationSettingsModal}>
Notification Settings
</Button>
<Button onClick={openNotificationLogModal} style={{ marginLeft: 16 }}>
View Notification Log
</Button>
</Flex>
</Forms.FormSection>
</SettingsTab>
);
}
export default wrapTab(VencordSettings, "Vencord Settings");
function DonateButtonComponent() {
return (
<Flex>
<DonateButton
look={Button.Looks.FILLED}
color={Button.Colors.TRANSPARENT}
style={{ marginTop: "1em" }} />
<InviteButton
look={Button.Looks.FILLED}
color={Button.Colors.TRANSPARENT}
style={{ marginTop: "1em" }} />
</Flex>
);
}
function isVencordDonor(userId: string): boolean {
const donorBadges = BadgeAPI.getDonorBadges(userId);
return GuildMemberStore.getMember(VC_GUILD_ID, userId)?.roles.includes(VC_DONOR_ROLE_ID) || !!donorBadges;
}
export function isEquicordDonor(userId: string): boolean {
const donorBadges = BadgeAPI.getEquicordDonorBadges(userId);
return GuildMemberStore.getMember(GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges;
}
export default wrapTab(EquicordSettings, "Equicord Settings");

View file

@ -7,9 +7,8 @@
import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
export const settings = definePluginSettings({
frequentEmojis: {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { makeDummyUser } from "@components/PluginSettings/PluginModal";
import { makeDummyUser } from "@components/settings/tabs/plugins/PluginModal";
import { Channel, Message } from "@vencord/discord-types";
import { ChannelStore, lodash, Toasts, UserStore } from "@webpack/common";

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { makeDummyUser } from "@components/PluginSettings/PluginModal";
import { makeDummyUser } from "@components/settings/tabs/plugins/PluginModal";
import { MessageType } from "@equicordplugins/holyNotes";
import { copyToClipboard } from "@utils/clipboard";
import { classes } from "@utils/misc";

View file

@ -18,7 +18,7 @@
import "./IconsTab.css";
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
import { SettingsTab, wrapTab } from "@components/settings";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import * as t from "@vencord/discord-types";

View file

@ -17,9 +17,8 @@
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
const settings = definePluginSettings({

View file

@ -9,9 +9,8 @@ import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { ContextMenuApi, FluxDispatcher, Heading, Menu, React, Tooltip, useEffect } from "@webpack/common";
import { RefObject } from "react";

View file

@ -17,10 +17,9 @@
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent";
import { Devs } from "@utils/constants";
import { sleep } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { Message, ReactionEmoji } from "@vencord/discord-types";
import { RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";

View file

@ -6,7 +6,7 @@
import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { PluginCard } from "@components/PluginSettings";
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
import { ChangeList } from "@utils/ChangeList";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Alerts, Button, Flex, Forms, Parser, React, Text, Tooltip, useMemo } from "@webpack/common";

View file

@ -13,9 +13,8 @@ import {
definePluginSettings,
Settings,
} from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
const presendObject: MessageSendListener = (channelId, msg) => {
msg.content = textProcessing(msg.content);

View file

@ -7,10 +7,9 @@
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { makeRange } from "@components/PluginSettings/components";
import { debounce } from "@shared/debounce";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { findByCode, findByProps, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { ChannelRouter, ChannelStore, ContextMenuApi, GuildStore, Menu, PermissionsBits, PermissionStore, React, SelectedChannelStore, Toasts, UserStore, VoiceStateStore } from "@webpack/common";

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Switch } from "@components/Switch";
import { Switch } from "@components/settings";
import { Button, Forms, Select, Slider, useEffect, useState } from "@webpack/common";
import { BrushTool } from "../tools/brush";

View file

@ -5,9 +5,9 @@
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange, SettingSliderComponent } from "@components/PluginSettings/components";
import { SettingSliderComponent } from "@components/ThemeSettings/components/SettingSliderComponent";
import { useAwaiter } from "@utils/react";
import { OptionType } from "@utils/types";
import { makeRange, OptionType } from "@utils/types";
import { Button, showToast, Text, Toasts, useMemo } from "@webpack/common";
import { clearLyricsCache, getLyricsCount, removeTranslations } from "./api";

View file

@ -10,7 +10,7 @@ import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings";
import { ErrorCard } from "@components/ErrorCard";
import { OpenExternalIcon } from "@components/Icons";
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
import { SettingsTab, wrapTab } from "@components/settings";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";

View file

@ -17,9 +17,8 @@
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { Channel, Message, User } from "@vencord/discord-types";
import { findByPropsLazy, findStore } from "@webpack";
import { Button, ChannelStore, GuildRoleStore, NavigationRouter, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";

View file

@ -214,21 +214,19 @@ function getTypeAndChannelId(
return ["", ""];
}
function playSample(tempSettings: any, type: string) {
const s = Object.assign({}, settings.plain, tempSettings);
function playSample(type: string) {
const currentUser = UserStore.getCurrentUser();
const myGuildId = SelectedGuildStore.getGuildId()!;
speak(
formatText(
s[type + "Message"],
settings.store[type + "Message"],
currentUser.username,
"general",
(currentUser as any).globalName ?? currentUser.username,
GuildMemberStore.getNick(myGuildId, currentUser.id) ??
currentUser.username
),
s
);
}
@ -371,7 +369,7 @@ export default definePlugin({
},
},
settingsAboutComponent({ tempSettings: s }) {
settingsAboutComponent() {
const types = useMemo(
() =>
Object.keys(settings.def)
@ -419,7 +417,7 @@ export default definePlugin({
className={"vc-narrator-buttons"}
>
{types.map(t => (
<Button key={t} onClick={() => playSample(s, t)}>
<Button key={t} onClick={() => playSample(t)}>
{wordsToTitle([t])}
</Button>
))}

View file

@ -6,9 +6,8 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import type { Channel } from "@vencord/discord-types";
import { GuildChannelStore, Menu, React, RestAPI, UserStore, VoiceStateStore } from "@webpack/common";

View file

@ -5,9 +5,8 @@
*/
import { definePluginSettings } from "@api/Settings";
import { makeRange } from "@components/PluginSettings/components";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { Text } from "@webpack/common";
import { ReactNode } from "react";

View file

@ -21,7 +21,7 @@ import "./fixDiscordBadgePadding.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import ErrorBoundary from "@components/ErrorBoundary";
import { openContributorModal } from "@components/settings/tabs";
import { isEquicordDonor } from "@components/VencordSettings/VencordTab";
import { isEquicordDonor } from "@components/settings/tabs/vencord";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";

View file

@ -4,10 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DonateButton, VCDonateButton } from "@components/DonateButton";
import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart";
import { ErrorBoundary } from "@components/index";
import { DonateButton, ErrorBoundary, VCDonateButton } from "@components/index";
import { Margins } from "@utils/margins";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import { Forms } from "@webpack/common";

View file

@ -17,11 +17,8 @@
*/
import { Settings } from "@api/Settings";
import { BackupAndRestoreTab, CloudTab, PluginsTab } from "@components/settings";
import { BackupAndRestoreTab, CloudTab, PatchHelperTab, PluginsTab, UpdaterTab, VencordTab } from "@components/settings";
import ThemesTab from "@components/ThemeSettings/ThemesTab";
import PatchHelperTab from "@components/VencordSettings/PatchHelperTab";
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
import VencordTab from "@components/VencordSettings/VencordTab";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";