/* * 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 "./PluginModal.css"; import { generateId } from "@api/Commands"; import { hasAnyVisibleSettings, isSettingHidden } from "@api/PluginManager"; import { useSettings } from "@api/Settings"; import { BaseText } from "@components/BaseText"; import { Button } from "@components/Button"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Paragraph } from "@components/Paragraph"; import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; import { classNameFactory } from "@utils/css"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin, PluginTag } from "@utils/types"; import { User } from "@vencord/discord-types"; import { findComponentByCodeLazy, findCssClassesLazy } from "@webpack"; import { Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; import { OptionComponentMap } from "./components"; import { openContributorModal } from "./ContributorModal"; import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); const AvatarStyles = findCssClassesLazy("moreUsers", "avatar", "clickableAvatar"); const CloseButton = findComponentByCodeLazy("CLOSE_BUTTON_LABEL"); const ConfirmModal = findComponentByCodeLazy('parentComponent:"ConfirmModal"'); const WarningIcon = findComponentByCodeLazy("3.15H3.29c-1.74"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; interface PluginModalProps extends ModalProps { plugin: Plugin; onRestartNeeded(key: string): void; } export function makeDummyUser(user: { username: string; id?: string; avatar?: string; }) { const newUser = new UserRecord({ username: user.username, id: user.id ?? generateId(), avatar: user.avatar, /** To stop discord making unwanted requests... */ bot: true, }); FluxDispatcher.dispatch({ type: "USER_UPDATE", user: newUser, }); return newUser; } function PluginTags({ tags }: { tags: PluginTag[]; }) { return (
{tags.map(tag => (
{tag}
))}
); } export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) { const pluginSettings = useSettings([`plugins.${plugin.name}.*`]).plugins[plugin.name]; const hasSettings = hasAnyVisibleSettings(plugin); // avoid layout shift by showing dummy users while loading users const fallbackAuthors = useMemo(() => [makeDummyUser({ username: "Loading...", id: "-1465912127305809920" })], []); const [authors, setAuthors] = useState[]>([]); useEffect(() => { (async () => { for (const [index, user] of plugin.authors.slice(0, 6).entries()) { try { const author = user.id ? await UserUtils.getUser(String(user.id)) .catch(() => makeDummyUser({ username: user.name })) : makeDummyUser({ username: user.name }); setAuthors(a => [...a, author]); } catch (e) { continue; } } })(); }, [plugin.authors]); function handleResetClick() { openWarningModal(plugin, onRestartNeeded); } function renderSettings() { const { settings } = plugin; if (!hasSettings || !settings) return There are no settings for this plugin.; const options = Object.entries(settings.def).map(([key, setting]) => { if (setting.type === OptionType.CUSTOM) return null; if (isSettingHidden(settings, setting)) return null; function onChange(newValue: any) { const option = plugin.settings!.def[key]; if (!option || option.type === OptionType.CUSTOM) return; pluginSettings[key] = newValue; if (option.restartNeeded) onRestartNeeded(key); } const Component = OptionComponentMap[setting.type]; return ( ); }); return (
{options}
); } function renderMoreUsers(_label: string) { const remainingAuthors = plugin.authors.slice(6); return ( u.name).join(", ")}> {({ onMouseEnter, onMouseLeave }) => (
+{remainingAuthors.length}
)}
); } const pluginMeta = PluginMeta[plugin.name]; const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; return (
{plugin.name} {plugin.description} {!!plugin.tags?.length && } {!!plugin.settingsAboutComponent && (
)}
Authors
( isEquicordPlugin ? openContributorModal(user) : openContributorModal(user)} > {user.username} )} />
Settings {renderSettings()}
{hasSettings ? ( {({ onMouseEnter, onMouseLeave }) => ( )} ) :
} {!pluginMeta.userPlugin && (
)} ); } export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string, key: string) => void) { openModal(modalProps => ( onRestartNeeded?.(plugin.name, key)} /> )); } function resetSettings(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) { const defaultSettings = plugin.settings?.def; const pluginName = plugin.name; if (!defaultSettings) return; const newSettings: Record = {}; let restartNeeded = false; for (const key in defaultSettings) { if (key === "enabled") continue; const setting = defaultSettings[key]; setting.type = setting.type ?? OptionType.STRING; if (setting.type === OptionType.STRING) { newSettings[key] = setting.default !== undefined && setting.default !== "" ? setting.default : ""; } else if ("default" in setting && setting.default !== undefined) { newSettings[key] = setting.default; } if (setting?.restartNeeded) { restartNeeded = true; } } const currentSettings = plugin.settings?.store; if (currentSettings) { Object.assign(currentSettings, newSettings); } if (restartNeeded) { onRestartNeeded?.(plugin.name); } Toasts.show({ message: `Settings for ${pluginName} have been reset.`, id: Toasts.genId(), type: Toasts.Type.SUCCESS, options: { position: Toasts.Position.TOP } }); } export function openWarningModal(plugin?: Plugin | null, onRestartNeeded?: (pluginName: string) => void, isPlugin = true, enabledPlugins?: number | null, reset?: () => void) { openModal(props => ( { if (isPlugin && plugin) { resetSettings(plugin, onRestartNeeded); } else { reset?.(); } }} onCancel={props.onClose} > {isPlugin ? <>Are you sure you want to reset all settings for {plugin?.name} to their default values? : `Are you sure you want to disable ${enabledPlugins} plugins?` }
This action cannot be undone.
)); }