diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index 01220eb4..569c3f0a 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -16,11 +16,10 @@
* along with this program. If not, see .
*/
-import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import { React, SettingsRouter } from "@webpack/common";
+import { React } from "@webpack/common";
import gitHash from "~git-hash";
@@ -30,23 +29,6 @@ export default definePlugin({
authors: [Devs.Ven, Devs.Megu],
required: true,
- contextMenus: {
- // The settings shortcuts in the user settings cog context menu
- // read the elements from a hardcoded map which for obvious reason
- // doesn't contain our sections. This patches the actions of our
- // sections to manually use SettingsRouter (which only works on desktop
- // but the context menu is usually not available on mobile anyway)
- "user-settings-cog"(children) {
- const section = findGroupChildrenByChildId("VencordSettings", children);
- section?.forEach(c => {
- const id = c?.props?.id;
- if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
- c!.props.action = () => SettingsRouter.open(id);
- }
- });
- }
- },
-
patches: [{
find: ".versionHash",
replacement: [
@@ -75,6 +57,12 @@ export default definePlugin({
},
replace: "...$self.makeSettingsCategories($1),$&"
}
+ }, {
+ find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
+ replacement: {
+ match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
+ replace: "$2.default.open($1);return;"
+ }
}],
customSections: [] as ((SectionTypes: Record) => any)[],
diff --git a/src/plugins/betterSettings/README.md b/src/plugins/betterSettings/README.md
new file mode 100644
index 00000000..127c6ce7
--- /dev/null
+++ b/src/plugins/betterSettings/README.md
@@ -0,0 +1,9 @@
+# BetterSettings
+
+Improves Discord's Settings via multiple (toggleable) changes:
+- makes opening settings much faster
+- removes the scuffed transition animation
+- organises the settings cog context menu into categories
+
+![](https://github.com/Vendicated/Vencord/assets/45497981/e8d67a95-3909-4be5-8281-8cf9d2f1c30e)
+
diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx
new file mode 100644
index 00000000..6d3c8798
--- /dev/null
+++ b/src/plugins/betterSettings/index.tsx
@@ -0,0 +1,177 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import { classNameFactory } from "@api/Styles";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import { findByPropsLazy } from "@webpack";
+import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
+import type { HTMLAttributes, ReactElement } from "react";
+
+type SettingsEntry = { section: string, label: string; };
+
+const cl = classNameFactory("");
+const Classes = findByPropsLazy("animating", "baseLayer", "bg", "layer", "layers");
+
+const settings = definePluginSettings({
+ disableFade: {
+ description: "Disable the crossfade animation",
+ type: OptionType.BOOLEAN,
+ default: true,
+ restartNeeded: true
+ },
+ organizeMenu: {
+ description: "Organizes the settings cog context menu into categories",
+ type: OptionType.BOOLEAN,
+ default: true
+ },
+ eagerLoad: {
+ description: "Removes the loading delay when opening the menu for the first time",
+ type: OptionType.BOOLEAN,
+ default: true,
+ restartNeeded: true
+ }
+});
+
+interface LayerProps extends HTMLAttributes {
+ mode: "SHOWN" | "HIDDEN";
+ baseLayer?: boolean;
+}
+
+function Layer({ mode, baseLayer = false, ...props }: LayerProps) {
+ const hidden = mode === "HIDDEN";
+ const containerRef = useRef(null);
+
+ useEffect(() => () => {
+ ComponentDispatch.dispatch("LAYER_POP_START");
+ ComponentDispatch.dispatch("LAYER_POP_COMPLETE");
+ }, []);
+
+ const node = (
+
+ );
+
+ return baseLayer
+ ? node
+ : {node};
+}
+
+export default definePlugin({
+ name: "BetterSettings",
+ description: "Enhances your settings-menu-opening experience",
+ authors: [Devs.Kyuuhachi],
+ settings,
+
+ patches: [
+ {
+ find: "this.renderArtisanalHack()",
+ replacement: [
+ { // Fade in on layer
+ match: /(?<=(\i)\.contextType=\i\.AccessibilityPreferencesContext;)/,
+ replace: "$1=$self.Layer;",
+ predicate: () => settings.store.disableFade
+ },
+ { // Lazy-load contents
+ match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g,
+ replace: "$&,_:$1",
+ predicate: () => settings.store.eagerLoad
+ }
+ ]
+ },
+ { // For some reason standardSidebarView also has a small fade-in
+ find: "DefaultCustomContentScroller:function()",
+ replacement: [
+ {
+ match: /\(0,\i\.useTransition\)\((\i)/,
+ replace: "(_cb=>_cb(void 0,$1))||$&"
+ },
+ {
+ match: /\i\.animated\.div/,
+ replace: '"div"'
+ }
+ ],
+ predicate: () => settings.store.disableFade
+ },
+ { // Load menu stuff on hover, not on click
+ find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
+ replacement: {
+ match: /(?<=handleOpenSettingsContextMenu.{0,250}?\i\.el\(("[^"]+")\)\.then\([^;]*?("\d+").*?Messages\.USER_SETTINGS,)(?=onClick:)/,
+ replace: "onMouseEnter(){Vencord.Webpack.wreq.el($1).then(()=>Vencord.Webpack.wreq($2));},"
+ },
+ predicate: () => settings.store.eagerLoad
+ },
+ { // Settings cog context menu
+ find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
+ replacement: {
+ match: /\(0,\i.default\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/,
+ replace: "$self.wrapMenu($&)"
+ }
+ }
+ ],
+
+ Layer(props: LayerProps) {
+ return (
+ props.children as any}>
+
+
+ );
+ },
+
+ wrapMenu(list: SettingsEntry[]) {
+ if (!settings.store.organizeMenu) return list;
+
+ const items = [{ label: null as string | null, items: [] as SettingsEntry[] }];
+
+ for (const item of list) {
+ if (item.section === "HEADER") {
+ items.push({ label: item.label, items: [] });
+ } else if (item.section === "DIVIDER") {
+ items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
+ } else {
+ items.at(-1)!.items.push(item);
+ }
+ }
+
+ return {
+ filter(predicate: (item: SettingsEntry) => boolean) {
+ for (const category of items) {
+ category.items = category.items.filter(predicate);
+ }
+ return this;
+ },
+ map(render: (item: SettingsEntry) => ReactElement) {
+ return items
+ .filter(a => a.items.length > 0)
+ .map(({ label, items }) => {
+ const children = items.map(render);
+ if (label) {
+ return (
+ );
+ } else {
+ return children;
+ }
+ });
+ }
+ };
+ }
+});
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index 048e65d6..24477c72 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -47,6 +47,7 @@ export let Paginator: t.Paginator;
export let ScrollerThin: t.ScrollerThin;
export let Clickable: t.Clickable;
export let Avatar: t.Avatar;
+export let FocusLock: t.FocusLock;
// token lagger real
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
export let useToken: t.useToken;
@@ -58,6 +59,6 @@ export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap"
export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
waitFor(["FormItem", "Button"], m => {
- ({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar } = m);
+ ({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar, FocusLock } = m);
Forms = m;
});
diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts
index 72a8a69b..3e3ffa4b 100644
--- a/src/webpack/common/types/components.d.ts
+++ b/src/webpack/common/types/components.d.ts
@@ -453,3 +453,7 @@ export type Avatar = ComponentType>;
+
+type FocusLock = ComponentType
+}>>;