diff --git a/src/plugins/betterSettings/PluginsSubmenu.tsx b/src/plugins/betterSettings/PluginsSubmenu.tsx new file mode 100644 index 000000000..b22f82a67 --- /dev/null +++ b/src/plugins/betterSettings/PluginsSubmenu.tsx @@ -0,0 +1,68 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openPluginModal } from "@components/PluginSettings/PluginModal"; +import { isObjectEmpty } from "@utils/misc"; +import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common"; + +import Plugins from "~plugins"; + +function onRestartNeeded() { + Alerts.show({ + title: "Restart required", + body:

You have changed settings that require a restart.

, + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: () => location.reload() + }); +} + +export default function PluginsSubmenu() { + const sortedPlugins = useMemo(() => Object.values(Plugins) + .sort((a, b) => a.name.localeCompare(b.name)), []); + const [query, setQuery] = useState(""); + + const search = query.toLowerCase(); + const include = (p: typeof Plugins[keyof typeof Plugins]) => ( + Vencord.Plugins.isPluginEnabled(p.name) + && p.options && !isObjectEmpty(p.options) + && ( + p.name.toLowerCase().includes(search) + || p.description.toLowerCase().includes(search) + || p.tags?.some(t => t.toLowerCase().includes(search)) + ) + ); + + const plugins = sortedPlugins.filter(include); + + return ( + <> + ( + + )} + /> + + {!!plugins.length && } + + {plugins.map(p => ( + openPluginModal(p, onRestartNeeded)} + /> + ))} + + ); +} diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 6a3ded3c1..f0dd89a7a 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -13,6 +13,8 @@ import { waitFor } from "@webpack"; import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common"; import type { HTMLAttributes, ReactElement } from "react"; +import PluginsSubmenu from "./PluginsSubmenu"; + type SettingsEntry = { section: string, label: string; }; const cl = classNameFactory(""); @@ -118,13 +120,21 @@ export default definePlugin({ }, { // Settings cog context menu find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", - replacement: { - match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, - replace: "$1$self.wrapMenu($2)" - } - } + replacement: [ + { + match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, + replace: "$1$self.wrapMenu($2)" + }, + { + match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/, + replace: "$&case 'VencordPlugins':return $self.PluginsSubmenu();" + } + ] + }, ], + PluginsSubmenu, + // This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary // without possibly also catching unrelated errors of children. // diff --git a/src/webpack/common/types/menu.d.ts b/src/webpack/common/types/menu.d.ts index 0b8ab5c66..5ae9062c3 100644 --- a/src/webpack/common/types/menu.d.ts +++ b/src/webpack/common/types/menu.d.ts @@ -72,6 +72,11 @@ export interface Menu { onChange(value: number): void, renderValue?(value: number): string, }>; + MenuSearchControl: RC<{ + query: string + onChange(query: string): void; + placeholder?: string; + }>; } export interface ContextMenuApi {