mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-20 06:30:35 +00:00
Introduce StyleListener API
This commit is contained in:
parent
5e9a9fe836
commit
67dfaa95ba
3 changed files with 89 additions and 35 deletions
|
@ -160,3 +160,6 @@ export const classNameFactory = (prefix: string = "") => (...args: ClassNameFact
|
|||
}
|
||||
return Array.from(classNames, name => prefix + name).join(" ");
|
||||
};
|
||||
|
||||
// items are run every time a new style is loaded by webpack, with `styles` being the content of the new file
|
||||
export const styleListeners = new Set<(styles: string, initial: boolean) => void>();
|
||||
|
|
59
src/plugins/_api/styleListener.ts
Normal file
59
src/plugins/_api/styleListener.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Styles } from "@api/index";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { StartAt } from "@utils/types";
|
||||
import { beforeInitListeners } from "@webpack";
|
||||
import { WebpackInstance } from "discord-types/other";
|
||||
|
||||
export default definePlugin({
|
||||
name: "StyleListenerAPI",
|
||||
description: "API to listen into the contents of css added by webpack",
|
||||
authors: [Devs.F53, Devs.Nuckyz],
|
||||
startAt: StartAt.Init,
|
||||
|
||||
start: async () => {
|
||||
window.requestAnimationFrame(async () => {
|
||||
const initialStyleLink: HTMLLinkElement | null = document.head.querySelector("link[rel=stylesheet]");
|
||||
if (!initialStyleLink) return console.error("StyleListenerAPI failed to get initial stylesheet");
|
||||
|
||||
const styles = await fetch(initialStyleLink.href).then(r => r.text());
|
||||
for (const listener of Styles.styleListeners)
|
||||
listener(styles, true);
|
||||
});
|
||||
|
||||
const wreq = await new Promise<WebpackInstance>(r => beforeInitListeners.add(r));
|
||||
|
||||
const chunksLoading = new Set<string>();
|
||||
const handleChunkCss = wreq.f.css;
|
||||
|
||||
wreq.f.css = function (this: unknown) {
|
||||
const result = Reflect.apply(handleChunkCss, this, arguments);
|
||||
|
||||
if (chunksLoading.has(arguments[0]))
|
||||
return result;
|
||||
chunksLoading.add(arguments[0]);
|
||||
|
||||
if (!(Array.isArray(arguments[1]) && arguments[1].length > 0))
|
||||
return result;
|
||||
|
||||
Promise.all(arguments[1]).then(async () => {
|
||||
await Promise.all(arguments[1]);
|
||||
chunksLoading.delete(arguments[0]);
|
||||
|
||||
const cssFilepath = wreq.p + wreq.k(arguments[0]);
|
||||
const styles = await fetch(cssFilepath)
|
||||
.then(r => r.text()).catch(() => { });
|
||||
if (!styles) return;
|
||||
for (const listener of Styles.styleListeners)
|
||||
listener(styles, false);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
});
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import "./clientTheme.css";
|
||||
|
||||
import { Styles } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -114,19 +115,24 @@ export default definePlugin({
|
|||
authors: [Devs.F53, Devs.Nuckyz],
|
||||
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
|
||||
settings,
|
||||
dependencies: ["StyleListenerAPI"],
|
||||
|
||||
startAt: StartAt.DOMContentLoaded,
|
||||
startAt: StartAt.Init,
|
||||
async start() {
|
||||
updateColorVars(settings.store.color);
|
||||
|
||||
const styles = await getStyles();
|
||||
generateColorOffsets(styles);
|
||||
generateLightModeFixes(styles);
|
||||
const lightFixes = createStyle("clientThemeLightModeFixes");
|
||||
const offsets = createStyle("clientThemeOffsets");
|
||||
Styles.styleListeners.add((styles, initial) => {
|
||||
if (initial) offsets.textContent = generateColorOffsets(styles);
|
||||
lightFixes.textContent += generateLightModeFixes(styles);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
document.getElementById("clientThemeVars")?.remove();
|
||||
document.getElementById("clientThemeOffsets")?.remove();
|
||||
document.getElementById("clientThemeLightModeFixes")?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -159,13 +165,15 @@ function generateColorOffsets(styles) {
|
|||
variableMatch = variableRegex.exec(styles);
|
||||
}
|
||||
|
||||
createStyleSheet("clientThemeOffsets", [
|
||||
return [
|
||||
`.theme-light {\n ${genThemeSpecificOffsets(variableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`,
|
||||
`.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`,
|
||||
].join("\n\n"));
|
||||
].join("\n\n");
|
||||
}
|
||||
|
||||
function generateLightModeFixes(styles) {
|
||||
const out: string[] = [];
|
||||
|
||||
const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm;
|
||||
// get light capturing groups that mention --white-500
|
||||
const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat();
|
||||
|
@ -176,8 +184,10 @@ function generateLightModeFixes(styles) {
|
|||
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}`;
|
||||
if (backgroundGroups.length > 0)
|
||||
out.push(`${backgroundGroups} {\n background: var(--primary-100) \n}`);
|
||||
if (backgroundColorGroups.length > 0)
|
||||
out.push(`${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;
|
||||
|
@ -185,14 +195,11 @@ function generateLightModeFixes(styles) {
|
|||
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}`;
|
||||
// create css to reassign every usage of w500 to p100
|
||||
if (lightBgVars.length > 0)
|
||||
out.push(`.theme-light{\n${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")}\n}`);
|
||||
|
||||
createStyleSheet("clientThemeLightModeFixes", [
|
||||
reassignBackgrounds,
|
||||
reassignBackgroundColors,
|
||||
reassignVariables,
|
||||
].join("\n\n"));
|
||||
return out.join("\n\n");
|
||||
}
|
||||
|
||||
function captureOne(str, regex) {
|
||||
|
@ -208,8 +215,7 @@ function updateColorVars(color: string) {
|
|||
const { hue, saturation, lightness } = hexToHSL(color);
|
||||
|
||||
let style = document.getElementById("clientThemeVars");
|
||||
if (!style)
|
||||
style = createStyleSheet("clientThemeVars");
|
||||
if (!style) style = createStyle("clientThemeVars");
|
||||
|
||||
style.textContent = `:root {
|
||||
--theme-h: ${hue};
|
||||
|
@ -218,28 +224,14 @@ function updateColorVars(color: string) {
|
|||
}`;
|
||||
}
|
||||
|
||||
function createStyleSheet(id, content = "") {
|
||||
function createStyle(id: string) {
|
||||
const style = document.createElement("style");
|
||||
style.setAttribute("id", id);
|
||||
style.textContent = content.split("\n").map(line => line.trim()).join("\n");
|
||||
document.body.appendChild(style);
|
||||
style.id = id;
|
||||
if (document.documentElement) document.documentElement.append(style);
|
||||
else window.requestAnimationFrame(() => document.documentElement.append(style));
|
||||
return style;
|
||||
}
|
||||
|
||||
// returns all of discord's native styles in a single string
|
||||
async function getStyles(): Promise<string> {
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue