) : (
- {isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
+ {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
)}
diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts
index 6254bc826..47d400eb6 100644
--- a/src/main/ipcMain.ts
+++ b/src/main/ipcMain.ts
@@ -139,8 +139,15 @@ export function initIpc(mainWindow: BrowserWindow) {
}
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
+ const title = "Vencord QuickCSS Editor";
+ const existingWindow = BrowserWindow.getAllWindows().find(w => w.title === title);
+ if (existingWindow && !existingWindow.isDestroyed()) {
+ existingWindow.focus();
+ return;
+ }
+
const win = new BrowserWindow({
- title: "Vencord QuickCSS Editor",
+ title,
autoHideMenuBar: true,
darkTheme: true,
webPreferences: {
diff --git a/src/main/updater/git.ts b/src/main/updater/git.ts
index e96026443..2ff3ba512 100644
--- a/src/main/updater/git.ts
+++ b/src/main/updater/git.ts
@@ -73,6 +73,8 @@ async function build() {
const command = isFlatpak ? "flatpak-spawn" : "node";
const args = isFlatpak ? ["--host", "node", "scripts/build/build.mjs"] : ["scripts/build/build.mjs"];
+ if (IS_DEV) args.push("--dev");
+
const res = await execFile(command, args, opts);
return !res.stderr.includes("Build failed");
diff --git a/src/plugins/anonymiseFileNames/index.ts b/src/plugins/anonymiseFileNames/index.ts
deleted file mode 100644
index 9e69d7a93..000000000
--- a/src/plugins/anonymiseFileNames/index.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 { Settings } from "@api/Settings";
-import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
-
-const enum Methods {
- Random,
- Consistent,
- Timestamp,
-}
-
-const tarExtMatcher = /\.tar\.\w+$/;
-
-export default definePlugin({
- name: "AnonymiseFileNames",
- authors: [Devs.obscurity],
- description: "Anonymise uploaded file names",
- patches: [
- {
- find: "instantBatchUpload:function",
- replacement: {
- match: /uploadFiles:(.{1,2}),/,
- replace:
- "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f.filename)),$1(...args)),",
- },
- },
- ],
-
- options: {
- method: {
- description: "Anonymising method",
- type: OptionType.SELECT,
- options: [
- { label: "Random Characters", value: Methods.Random, default: true },
- { label: "Consistent", value: Methods.Consistent },
- { label: "Timestamp (4chan-like)", value: Methods.Timestamp },
- ],
- },
- randomisedLength: {
- description: "Random characters length",
- type: OptionType.NUMBER,
- default: 7,
- disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Random,
- },
- consistent: {
- description: "Consistent filename",
- type: OptionType.STRING,
- default: "image",
- disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Consistent,
- },
- },
-
- anonymise(file: string) {
- let name = "image";
- const tarMatch = tarExtMatcher.exec(file);
- const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
- const ext = extIdx !== -1 ? file.slice(extIdx) : "";
-
- switch (Settings.plugins.AnonymiseFileNames.method) {
- case Methods.Random:
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- name = Array.from(
- { length: Settings.plugins.AnonymiseFileNames.randomisedLength },
- () => chars[Math.floor(Math.random() * chars.length)]
- ).join("");
- break;
- case Methods.Consistent:
- name = Settings.plugins.AnonymiseFileNames.consistent;
- break;
- case Methods.Timestamp:
- // UNIX timestamp in nanos, i could not find a better dependency-less way
- name = `${Math.floor(Date.now() / 1000)}${Math.floor(window.performance.now())}`;
- break;
- }
- return name + ext;
- },
-});
diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx
new file mode 100644
index 000000000..845aa756d
--- /dev/null
+++ b/src/plugins/anonymiseFileNames/index.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 { Upload } from "@api/MessageEvents";
+import { definePluginSettings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import { findByCodeLazy, findByPropsLazy } from "@webpack";
+
+type AnonUpload = Upload & { anonymise?: boolean; };
+
+const ActionBarIcon = findByCodeLazy(".actionBarIcon)");
+const UploadDraft = findByPropsLazy("popFirstFile", "update");
+
+const enum Methods {
+ Random,
+ Consistent,
+ Timestamp,
+}
+
+const tarExtMatcher = /\.tar\.\w+$/;
+
+const settings = definePluginSettings({
+ anonymiseByDefault: {
+ description: "Whether to anonymise file names by default",
+ type: OptionType.BOOLEAN,
+ default: true,
+ },
+ method: {
+ description: "Anonymising method",
+ type: OptionType.SELECT,
+ options: [
+ { label: "Random Characters", value: Methods.Random, default: true },
+ { label: "Consistent", value: Methods.Consistent },
+ { label: "Timestamp", value: Methods.Timestamp },
+ ],
+ },
+ randomisedLength: {
+ description: "Random characters length",
+ type: OptionType.NUMBER,
+ default: 7,
+ disabled: () => settings.store.method !== Methods.Random,
+ },
+ consistent: {
+ description: "Consistent filename",
+ type: OptionType.STRING,
+ default: "image",
+ disabled: () => settings.store.method !== Methods.Consistent,
+ },
+});
+
+export default definePlugin({
+ name: "AnonymiseFileNames",
+ authors: [Devs.obscurity],
+ description: "Anonymise uploaded file names",
+ patches: [
+ {
+ find: "instantBatchUpload:function",
+ replacement: {
+ match: /uploadFiles:(.{1,2}),/,
+ replace:
+ "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),",
+ },
+ },
+ {
+ find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
+ replacement: {
+ match: /(?<=children:\[)(?=.{10,80}tooltip:\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
+ replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
+ },
+ },
+ ],
+ settings,
+
+ renderIcon: ErrorBoundary.wrap(({ upload, channelId, draftType }: { upload: AnonUpload; draftType: unknown; channelId: string; }) => {
+ const anonymise = upload.anonymise ?? settings.store.anonymiseByDefault;
+ return (
+
{
+ upload.anonymise = !anonymise;
+ UploadDraft.update(channelId, upload.id, draftType, {}); // dummy update so component rerenders
+ }}
+ >
+ {anonymise
+ ?
+ :
+ }
+
+ );
+ }, { noop: true }),
+
+ anonymise(upload: AnonUpload) {
+ if ((upload.anonymise ?? settings.store.anonymiseByDefault) === false) return upload.filename;
+
+ const file = upload.filename;
+ const tarMatch = tarExtMatcher.exec(file);
+ const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
+ const ext = extIdx !== -1 ? file.slice(extIdx) : "";
+
+ switch (settings.store.method) {
+ case Methods.Random:
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ return Array.from(
+ { length: settings.store.randomisedLength },
+ () => chars[Math.floor(Math.random() * chars.length)]
+ ).join("") + ext;
+ case Methods.Consistent:
+ return settings.store.consistent + ext;
+ case Methods.Timestamp:
+ return Date.now() + ext;
+ }
+ },
+});
diff --git a/src/plugins/betterGifPicker/index.ts b/src/plugins/betterGifPicker/index.ts
new file mode 100644
index 000000000..09bb570d7
--- /dev/null
+++ b/src/plugins/betterGifPicker/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "BetterGifPicker",
+ description: "Makes the gif picker open the favourite category by default",
+ authors: [Devs.Samwich],
+ patches: [
+ {
+ find: ".GIFPickerResultTypes.SEARCH",
+ replacement: [{
+ match: "this.state={resultType:null}",
+ replace: 'this.state={resultType:"Favorites"}'
+ }]
+ }
+ ]
+});
diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx
index acad564da..40bbe30a8 100644
--- a/src/plugins/biggerStreamPreview/index.tsx
+++ b/src/plugins/biggerStreamPreview/index.tsx
@@ -82,7 +82,7 @@ export const streamContextPatch: NavContextMenuPatchCallback = (children, { stre
};
export const userContextPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => {
- return addViewStreamContext(children, { userId: user.id });
+ if (user) return addViewStreamContext(children, { userId: user.id });
};
export default definePlugin({
diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts
index 0633b717d..95a59c037 100644
--- a/src/plugins/clearURLs/defaultRules.ts
+++ b/src/plugins/clearURLs/defaultRules.ts
@@ -153,5 +153,6 @@ export const defaultRules = [
"utm_term",
"si@open.spotify.com",
"igshid",
+ "igsh",
"share_id@reddit.com",
];
diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css
index 023f88bd2..64aefdcf5 100644
--- a/src/plugins/clientTheme/clientTheme.css
+++ b/src/plugins/clientTheme/clientTheme.css
@@ -19,6 +19,16 @@
border: thin solid var(--background-modifier-accent) !important;
}
-.client-theme-warning {
+.client-theme-warning * {
color: var(--text-danger);
}
+
+.client-theme-contrast-warning {
+ background-color: var(--background-primary);
+ padding: 0.5rem;
+ border-radius: .5rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx
index d75929961..5d8cd4dc0 100644
--- a/src/plugins/clientTheme/index.tsx
+++ b/src/plugins/clientTheme/index.tsx
@@ -8,19 +8,19 @@ import "./clientTheme.css";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import { getTheme, Theme } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import definePlugin, { OptionType, StartAt } from "@utils/types";
-import { findComponentByCodeLazy } from "@webpack";
-import { Button, Forms } from "@webpack/common";
+import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
+import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
"#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42",
- "#3C2E42", "#422938"
+ "#3C2E42", "#422938", "#b6908f", "#bfa088", "#d3c77d",
+ "#86ac86", "#88aab3", "#8693b5", "#8a89ba", "#ad94bb",
];
function onPickColor(color: number) {
@@ -30,9 +30,35 @@ function onPickColor(color: number) {
updateColorVars(hexColor);
}
+const { saveClientTheme } = findByPropsLazy("saveClientTheme");
+
+function setTheme(theme: string) {
+ saveClientTheme({ theme });
+}
+
+const ThemeStore = findStoreLazy("ThemeStore");
+const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
+
function ThemeSettings() {
- const lightnessWarning = hexToLightness(settings.store.color) > 45;
- const lightModeWarning = getTheme() === Theme.Light;
+ const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme);
+ const isLightTheme = theme === "light";
+ const oppositeTheme = isLightTheme ? "dark" : "light";
+
+ const nitroTheme = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset);
+ const nitroThemeEnabled = nitroTheme !== undefined;
+
+ const selectedLuminance = relativeLuminance(settings.store.color);
+
+ let contrastWarning = false, fixableContrast = true;
+ if ((isLightTheme && selectedLuminance < 0.26) || !isLightTheme && selectedLuminance > 0.12)
+ contrastWarning = true;
+ if (selectedLuminance < 0.26 && selectedLuminance > 0.12)
+ fixableContrast = false;
+ // light mode with values greater than 65 leads to background colors getting crushed together and poor text contrast for muted channels
+ if (isLightTheme && selectedLuminance > 0.65) {
+ contrastWarning = true;
+ fixableContrast = false;
+ }
return (
@@ -48,15 +74,18 @@ function ThemeSettings() {
suggestedColors={colorPresets}
/>
- {lightnessWarning || lightModeWarning
- ?
-
-
Your theme won't look good:
- {lightnessWarning &&
Selected color is very light}
- {lightModeWarning &&
Light mode isn't supported}
+ {(contrastWarning || nitroThemeEnabled) && (<>
+
+
+
+ Warning, your theme won't look good:
+ {contrastWarning && Selected color won't contrast well with text}
+ {nitroThemeEnabled && Nitro themes aren't supported}
+
+ {(contrastWarning && fixableContrast) &&
}
+ {(nitroThemeEnabled) &&
}
- : null
- }
+ >)}
);
}
@@ -87,9 +116,12 @@ export default definePlugin({
settings,
startAt: StartAt.DOMContentLoaded,
- start() {
+ async start() {
updateColorVars(settings.store.color);
- generateColorOffsets();
+
+ const styles = await getStyles();
+ generateColorOffsets(styles);
+ generateLightModeFixes(styles);
},
stop() {
@@ -98,56 +130,86 @@ export default definePlugin({
}
});
-const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g;
+const variableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g;
+const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g;
+const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g;
-async function generateColorOffsets() {
-
- const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
- const variableLightness = {} as Record
;
-
- // Search all stylesheets for color variables
- for (const styleLinkNode of styleLinkNodes) {
- const cssLink = styleLinkNode.getAttribute("href");
- if (!cssLink) continue;
-
- const res = await fetch(cssLink);
- const cssString = await res.text();
-
- // Get lightness values of --primary variables >=500
- let variableMatch = variableRegex.exec(cssString);
- while (variableMatch !== null) {
- const [, variable, lightness] = variableMatch;
- variableLightness[variable] = parseFloat(lightness);
- variableMatch = variableRegex.exec(cssString);
- }
- }
-
- // Generate offsets
- const lightnessOffsets = Object.entries(variableLightness)
+// generates variables per theme by:
+// - matching regex (so we can limit what variables are included in light/dark theme, otherwise text becomes unreadable)
+// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600)
+function genThemeSpecificOffsets(variableLightness: Record, regex: RegExp, centerVariable: string): string {
+ return Object.entries(variableLightness).filter(([key]) => key.search(regex) > -1)
.map(([key, lightness]) => {
- const lightnessOffset = lightness - variableLightness["--primary-600-hsl"];
+ const lightnessOffset = lightness - variableLightness[centerVariable];
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`;
})
.join("\n");
+}
- const style = document.createElement("style");
- style.setAttribute("id", "clientThemeOffsets");
- style.textContent = `:root:root {
- ${lightnessOffsets}
- }`;
- document.head.appendChild(style);
+
+function generateColorOffsets(styles) {
+ const variableLightness = {} as Record;
+
+ // Get lightness values of --primary variables
+ let variableMatch = variableRegex.exec(styles);
+ while (variableMatch !== null) {
+ const [, variable, lightness] = variableMatch;
+ variableLightness[variable] = parseFloat(lightness);
+ variableMatch = variableRegex.exec(styles);
+ }
+
+ createStyleSheet("clientThemeOffsets", [
+ `.theme-light {\n ${genThemeSpecificOffsets(variableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`,
+ `.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`,
+ ].join("\n\n"));
+}
+
+function generateLightModeFixes(styles) {
+ const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm;
+ // get light capturing groups that mention --white-500
+ const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat();
+
+ const groupBackgroundRegex = /^([^{]*)\{background:var\(--white-500\)/m;
+ const groupBackgroundColorRegex = /^([^{]*)\{background-color:var\(--white-500\)/m;
+ // find all capturing groups that assign background or background-color directly to w500
+ const backgroundGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundRegex)).join(",\n");
+ const backgroundColorGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundColorRegex)).join(",\n");
+ // create css to reassign them to --primary-100
+ const reassignBackgrounds = `${backgroundGroups} {\n background: var(--primary-100) \n}`;
+ const reassignBackgroundColors = `${backgroundColorGroups} {\n background-color: var(--primary-100) \n}`;
+
+ const groupBgVarRegex = /\.theme-light\{([^}]*--[^:}]*(?:background|bg)[^:}]*:var\(--white-500\)[^}]*)\}/m;
+ const bgVarRegex = /^(--[^:]*(?:background|bg)[^:]*):var\(--white-500\)/m;
+ // get all global variables used for backgrounds
+ const lightVars = mapReject(relevantStyles, style => captureOne(style, groupBgVarRegex)) // get the insides of capture groups that have at least one background var with w500
+ .map(str => str.split(";")).flat(); // captureGroupInsides[] -> cssRule[]
+ const lightBgVars = mapReject(lightVars, variable => captureOne(variable, bgVarRegex)); // remove vars that aren't for backgrounds or w500
+ // create css to reassign every var
+ const reassignVariables = `.theme-light {\n ${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")} \n}`;
+
+ createStyleSheet("clientThemeLightModeFixes", [
+ reassignBackgrounds,
+ reassignBackgroundColors,
+ reassignVariables,
+ ].join("\n\n"));
+}
+
+function captureOne(str, regex) {
+ const result = str.match(regex);
+ return (result === null) ? null : result[1];
+}
+
+function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
+ return _.reject(arr.map(mapFunc), rejectFunc);
}
function updateColorVars(color: string) {
const { hue, saturation, lightness } = hexToHSL(color);
let style = document.getElementById("clientThemeVars");
- if (!style) {
- style = document.createElement("style");
- style.setAttribute("id", "clientThemeVars");
- document.head.appendChild(style);
- }
+ if (!style)
+ style = createStyleSheet("clientThemeVars");
style.textContent = `:root {
--theme-h: ${hue};
@@ -156,6 +218,28 @@ function updateColorVars(color: string) {
}`;
}
+function createStyleSheet(id, content = "") {
+ const style = document.createElement("style");
+ style.setAttribute("id", id);
+ style.textContent = content.split("\n").map(line => line.trim()).join("\n");
+ document.body.appendChild(style);
+ return style;
+}
+
+// returns all of discord's native styles in a single string
+async function getStyles(): Promise {
+ let out = "";
+ const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
+ for (const styleLinkNode of styleLinkNodes) {
+ const cssLink = styleLinkNode.getAttribute("href");
+ if (!cssLink) continue;
+
+ const res = await fetch(cssLink);
+ out += await res.text();
+ }
+ return out;
+}
+
// https://css-tricks.com/converting-color-spaces-in-javascript/
function hexToHSL(hexCode: string) {
// Hex => RGB normalized to 0-1
@@ -198,17 +282,14 @@ function hexToHSL(hexCode: string) {
return { hue, saturation, lightness };
}
-// Minimized math just for lightness, lowers lag when changing colors
-function hexToLightness(hexCode: string) {
- // Hex => RGB normalized to 0-1
- const r = parseInt(hexCode.substring(0, 2), 16) / 255;
- const g = parseInt(hexCode.substring(2, 4), 16) / 255;
- const b = parseInt(hexCode.substring(4, 6), 16) / 255;
+// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
+function relativeLuminance(hexCode: string) {
+ const normalize = (x: number) =>
+ x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
- const cMax = Math.max(r, g, b);
- const cMin = Math.min(r, g, b);
+ const r = normalize(parseInt(hexCode.substring(0, 2), 16) / 255);
+ const g = normalize(parseInt(hexCode.substring(2, 4), 16) / 255);
+ const b = normalize(parseInt(hexCode.substring(4, 6), 16) / 255);
- const lightness = 100 * ((cMax + cMin) / 2);
-
- return lightness;
+ return r * 0.2126 + g * 0.7152 + b * 0.0722;
}
diff --git a/src/plugins/copyUserURLs/index.tsx b/src/plugins/copyUserURLs/index.tsx
index e3c336fb2..9f69674cf 100644
--- a/src/plugins/copyUserURLs/index.tsx
+++ b/src/plugins/copyUserURLs/index.tsx
@@ -30,6 +30,8 @@ interface UserContextProps {
}
const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => {
+ if (!user) return;
+
children.push(
-
-
- You can also access Decor decorations from the {
- e.preventDefault();
- closeAllModals();
- FluxDispatcher.dispatch({ type: "USER_SETTINGS_MODAL_SET_SECTION", section: "Profile Customization" });
- }}
- >Profiles page.
-
- ;
- }
- }
-});
export default definePlugin({
name: "Decor",
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
diff --git a/src/plugins/decor/settings.tsx b/src/plugins/decor/settings.tsx
new file mode 100644
index 000000000..0d3628cc6
--- /dev/null
+++ b/src/plugins/decor/settings.tsx
@@ -0,0 +1,47 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2023 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import { Link } from "@components/Link";
+import { Margins } from "@utils/margins";
+import { classes } from "@utils/misc";
+import { closeAllModals } from "@utils/modal";
+import { OptionType } from "@utils/types";
+import { FluxDispatcher, Forms } from "@webpack/common";
+
+import DecorSection from "./ui/components/DecorSection";
+
+export const settings = definePluginSettings({
+ changeDecoration: {
+ type: OptionType.COMPONENT,
+ description: "Change your avatar decoration",
+ component() {
+ if (!Vencord.Plugins.plugins.Decor.started) return
;
+ }
+ },
+ agreedToGuidelines: {
+ type: OptionType.BOOLEAN,
+ description: "Agreed to guidelines",
+ hidden: true,
+ default: false
+ }
+});
diff --git a/src/plugins/decor/ui/components/index.ts b/src/plugins/decor/ui/components/index.ts
index 8f39a10ee..8fe41fc92 100644
--- a/src/plugins/decor/ui/components/index.ts
+++ b/src/plugins/decor/ui/components/index.ts
@@ -19,7 +19,7 @@ export let DecorationGridItem: DecorationGridItemComponent;
export const setDecorationGridItem = v => DecorationGridItem = v;
export const AvatarDecorationModalPreview = LazyComponentWebpack(() => {
- const component = findComponentByCode("AvatarDecorationModalPreview");
+ const component = findComponentByCode(".shopPreviewBanner");
return React.memo(component);
});
diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts
index 52b169d77..0ead602e2 100644
--- a/src/plugins/decor/ui/index.ts
+++ b/src/plugins/decor/ui/index.ts
@@ -5,9 +5,10 @@
*/
import { classNameFactory } from "@api/Styles";
-import { extractAndLoadChunksLazy } from "@webpack";
+import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
export const cl = classNameFactory("vc-decor-");
+export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
export const requireAvatarDecorationModal = extractAndLoadChunksLazy(["openAvatarDecorationModal:"]);
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
index bed007174..5fbe165ce 100644
--- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
+++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
@@ -4,12 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
+import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
-import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
-import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
+import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
+import { findComponentByCodeLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
@@ -18,16 +19,17 @@ import { GUILD_ID, INVITE_KEY } from "../../lib/constants";
import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
-import { cl, requireAvatarDecorationModal } from "../";
+import { settings } from "../../settings";
+import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
import { AvatarDecorationModalPreview } from "../components";
import DecorationGridCreate from "../components/DecorationGridCreate";
import DecorationGridNone from "../components/DecorationGridNone";
import DecorDecorationGridDecoration from "../components/DecorDecorationGridDecoration";
import SectionedGridList from "../components/SectionedGridList";
import { openCreateDecorationModal } from "./CreateDecorationModal";
+import { openGuidelinesModal } from "./GuidelinesModal";
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
-const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
function usePresets() {
const [presets, setPresets] = useState