More Plugins

This commit is contained in:
thororen 2024-04-25 18:12:36 -04:00
parent b0880ba7af
commit a634f41886
30 changed files with 6155 additions and 97 deletions

View file

@ -0,0 +1,88 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { UserStore, useState } from "@webpack/common";
let avatarUrl = "";
const settings = definePluginSettings({
default: {
type: OptionType.BOOLEAN,
description: "Enable avatar preview by default.",
default: true
}
});
function toggle(enabled: boolean) {
const avatars = document.querySelectorAll(".shopCard__3d319 .avatarContainer_e11d35 .wrapper__3ed10 .mask_d5067d foreignObject .avatarStack__789b4");
for (const avatar of avatars) {
const img = avatar.querySelector("img");
if (img) img.src = enabled ? avatarUrl : "https://canary.discord.com/assets/6d8f0708e196aaad2550.png";
}
}
const PreviewToggle = () => {
const [enabled, setEnabled] = useState(settings.store.default);
toggle(enabled);
return (
<button type="button" className="fieldButton__1edf0 button__581d0 lookFilled__950dd colorPrimary_ebe632 sizeSmall_da7d10 grow__4c8a4" style={{
marginLeft: "8px"
}} onClick={() => {
setEnabled(!enabled);
toggle(!enabled);
}}>
<div className="contents__322f4">
{enabled ? "Disable" : "Enable"} Avatar Preview
</div>
</button>
);
};
export default definePlugin({
name: "BetterShopPreview",
description: "Uses your avatar for avatar decoration previews in the Discord Shop.",
authors: [EquicordDevs.Tolgchu],
settings,
patches: [
{
find: "}),(0,l.jsx)(T.default.Title,{className:en.title,children:er.default.Messages.COLLECTIBLES_SHOP})]",
replacement: [{
match: "{className:en.title,children:er.default.Messages.COLLECTIBLES_SHOP}",
replace: "{className:en.title,children:[er.default.Messages.COLLECTIBLES_SHOP,$self.PreviewToggle()]}"
}]
}
],
PreviewToggle,
async start() {
const user = UserStore.getCurrentUser();
const url = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.gif`;
await fetch(url).then(response => {
if (response.ok) avatarUrl = url;
else avatarUrl = url.replace(".gif", ".png");
});
},
stop() { }
});

View file

@ -1,45 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export function goofs() {
setTimeout(() => {
(document.querySelector('[class^="wordmarkWindows"]')?.firstElementChild as any).style.display = "none";
document.querySelector('[class^="wordmarkWindows"]')?.prepend(Object.assign(document.createElement("div"), {
style: `height: 16px;
width: 51px;
color: var(--text-muted);
font-size: xx-small;
text-align: center;
margin-top: 5px;
font-weight: 600;
transform: scaleX(2.5);
letter-spacing: -1px;`,
innerText: "Skype"
}
));
document.onmousedown = e => {
const hit = Object.assign(document.createElement("div"), {
style: `left: ${e.clientX}px;
top: ${e.clientY}px;
color: white;
text-align: center;
position: absolute;
z-index: 9999999;
pointer-events: none;
transform: translate(-50%, -50%);
font-size: 2rem;`,
innerText: "×"
});
const hitSound = Object.assign(document.createElement("audio"), { src: "https://cdn.discordapp.com/attachments/545600181744173068/1090841136458899516/hit.wav", autoplay: true, volume: 0.15 });
hitSound.onended = () => {
hit.removeChild(hitSound);
document.body.removeChild(hit);
};
hit.appendChild(hitSound);
document.body.appendChild(hit);
};
}, 1000);
}

View file

@ -1,27 +1,14 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
* 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 { EquicordDevs } from "@utils/constants";
import definePlugin, { OptionType, StartAt } from "@utils/types";
import definePlugin, { OptionType } from "@utils/types";
import { Forms, Menu, TextInput, useState } from "@webpack/common";
import { goofs } from "./goofs";
import { cooldown, denormalize, normalize } from "./utils";
const settings = definePluginSettings({
@ -54,12 +41,6 @@ const settings = definePluginSettings({
default: true,
type: OptionType.BOOLEAN,
},
goofs: {
description: "Goofs and gags :^)",
default: false,
type: OptionType.BOOLEAN,
restartNeeded: true,
},
bitrates: {
description: "ADVANCED: ONLY USE FOR TESTING PURPOSES!",
default: false,
@ -67,19 +48,19 @@ const settings = definePluginSettings({
restartNeeded: false,
},
targetBitrate: {
description: "Bitrate",
description: "Target Bitrate (seemingly no effect)",
default: 600000,
type: OptionType.NUMBER,
hidden: true
},
minBitrate: {
description: "Bitrate",
description: "Minimum Bitrate (forces the bitrate to be at LEAST this)",
default: 500000,
type: OptionType.NUMBER,
hidden: true
},
maxBitrate: {
description: "Bitrate",
description: "Maxmimum Bitrate (seems to not be reached most of the time)",
default: 8000000,
type: OptionType.NUMBER,
hidden: true
@ -102,57 +83,57 @@ export default definePlugin({
{
find: "ApplicationStreamSettingRequirements)",
replacement: {
match: /for\(let . of ..ApplicationStreamSettingRequirements\).+?!1/,
match: /for\(let \i of \i\.ApplicationStreamSettingRequirements\).+?!1/,
replace: "return !0"
}
},
{
find: "ApplicationStreamFPSButtonsWithSuffixLabel.map",
replacement: {
match: /(.=)(.{19}FPS.+?([A-z]{1,2}).{11}>([A-z]{1,2}).[A-z]{1,2},([A-z]{1,2}),[A-z]{1,2},([A-z.]+).+?\}\)),/,
replace: "$1[$self.CustomRange($4,$5,$3,$6,'fps'),...$2],"
match: /(\i=)(.{19}FPS.+?(\i).{11}>(\i).\i,(\i),\i,([A-z.]+).+?\}\)),/,
replace: (_, g1, g2, g3, g4, g5, g6) => `${g1}[$self.CustomRange(${g4},${g5},${g3},${g6},'fps'),...${g2}],`
}
},
{
find: "ApplicationStreamResolutionButtonsWithSuffixLabel.map",
replacement: {
match: /(.=)(.{19}Resolution.+?([A-z]{1,2}).{11}>([A-z]{1,2}).[A-z]{1,2},[A-z]{1,2},([A-z]{1,2}),([A-z.]+).+?\}\));/,
replace: "$1[$self.CustomRange($4,$3,$5,$6,'resolution'),...$2];"
match: /(\i=)(.{19}Resolution.+?(\i).{11}>(\i).\i,\i,(\i),([A-z.]+).+?\}\));/,
replace: (_, g1, g2, g3, g4, g5, g6) => `${g1}[$self.CustomRange(${g4},${g3},${g5},${g6},'resolution'),...${g2}];`
}
},
{
find: "=4e6",
find: "\"remoteSinkWantsPixelCount\",\"remoteSinkWantsMaxFramerate\"",
replacement: {
match: /=4e6/,
replace: "=8e6"
match: /(max:|\i=)4e6,/,
replace: (_, g1) => `${g1}8e6,`
}
},
{
find: "\"Discord_Clip_\".concat",
find: "\"remoteSinkWantsPixelCount\",\"remoteSinkWantsMaxFramerate\"",
replacement: {
match: /(=15e)3/, // disable discord idle fps reduction
replace: "$18"
match: /(\i)=15e3/, // disable discord idle fps reduction
replace: (_, g1) => `${g1}=15e8`
}
},
{
find: "updateRemoteWantsFramerate(){",
replacement: {
match: /updateRemoteWantsFramerate..\{/, // disable discord mute fps reduction
replace: "$&return;"
match: /updateRemoteWantsFramerate\(\)\{/, // disable discord mute fps reduction
replace: match => `${match}return;`
}
},
{
find: "{getQuality(",
replacement: {
match: /(bitrateMin:).+?(,bitrateMax:).+?(,bitrateTarget:).+?,/,
replace: "$1$self.getMinBitrate()$2$self.getMaxBitrate()$3$self.getTargetBitrate(),"
match: /bitrateMin:.+?,bitrateMax:.+?,bitrateTarget:.+?,/,
replace: "bitrateMin:$self.getMinBitrate(),bitrateMax:$self.getMaxBitrate(),bitrateTarget:$self.getTargetBitrate(),"
}
},
{
find: "ApplicationStreamResolutionButtonsWithSuffixLabel.map",
replacement: {
match: /(stream-settings-resolution-.+?children:.)/,
replace: "$1$self.settings.store.bitrates?$self.BitrateGroup():null,"
match: /stream-settings-resolution-.+?children:\[/,
replace: match => `${match}$self.settings.store.bitrates?$self.BitrateGroup():null,`
}
}
],
@ -174,7 +155,9 @@ export default definePlugin({
<Menu.MenuSliderControl
onChange={onChange}
renderValue={() => value + (group === "fps" ? " FPS" : "p")}
value={normalize((group === "fps" ? fps : res), minValue, maxValue)}>
value={normalize((group === "fps" ? fps : res), minValue, maxValue) || 0}
minValue={0}
maxValue={100}>
</Menu.MenuSliderControl>
</Menu.MenuControlItem>);
},
@ -197,7 +180,9 @@ export default definePlugin({
<Menu.MenuSliderControl
onChange={onChange}
renderValue={() => Math.round(bitrate / 1000) + "kbps"}
value={normalize(bitrate, name === "min" ? 1000 : minBitrate, name === "max" ? 20000000 : maxBitrate)}>
value={normalize(bitrate, name === "min" ? 1000 : minBitrate, name === "max" ? 20000000 : maxBitrate) || 0}
minValue={0}
maxValue={100}>
</Menu.MenuSliderControl>
</Menu.MenuControlItem>);
},
@ -212,10 +197,5 @@ export default definePlugin({
getMaxBitrate() {
const { maxBitrate } = settings.store;
return maxBitrate;
},
start() {
if (settings.store.goofs)
goofs();
},
startAt: StartAt.DOMContentLoaded
}
});

View file

@ -0,0 +1,220 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Flex } from "@components/Flex";
import { CopyIcon } from "@components/Icons";
import {
ModalProps,
ModalRoot,
} from "@utils/modal";
import {
Button,
Clipboard,
ScrollerThin,
TextInput,
Toasts,
useState,
} from "@webpack/common";
import { colorVariables } from "../css";
interface ToolboxItem {
title: string;
onClick: () => void;
id?: string;
iconClassName?: string;
}
const ColorVarItems: ToolboxItem[] = colorVariables.map((colorVariable: string) => {
return {
title: "Copy " + colorVariable,
onClick: () => {
function getHex(str: string): string { return Object.assign(document.createElement("canvas").getContext("2d") as {}, { fillStyle: str }).fillStyle; }
Clipboard.copy(getHex(getComputedStyle(document.body).getPropertyValue("--" + colorVariable)));
Toasts.show({ message: "Color " + colorVariable + " copied to clipboard", id: "toolbox-color-var-copied", type: 1 });
},
id: colorVariable
};
});
const ToolboxItems: ToolboxItem[] = [
{
title: "Copy Accent Color",
onClick: () => {
function getHex(str: string): string {
return Object.assign(
document.createElement("canvas").getContext("2d") as {},
{ fillStyle: str }
).fillStyle;
}
Clipboard.copy(
getHex(
getComputedStyle(document.body).getPropertyValue(
"--brand-experiment"
)
)
);
Toasts.show({
message: "Accent color copied to clipboard",
id: "toolbox-accent-color-copied",
type: 1,
});
},
id: "colorways-toolbox_copy-accent",
iconClassName: "copy",
},
{
title: "Copy Primary Color",
onClick: () => {
function getHex(str: string): string {
return Object.assign(
document.createElement("canvas").getContext("2d") as {},
{ fillStyle: str }
).fillStyle;
}
Clipboard.copy(
getHex(
getComputedStyle(document.body).getPropertyValue(
"--background-primary"
)
)
);
Toasts.show({
message: "Primary color copied to clipboard",
id: "toolbox-primary-color-copied",
type: 1,
});
},
id: "colorways-toolbox_copy-primary",
iconClassName: "copy",
},
{
title: "Copy Secondary Color",
onClick: () => {
function getHex(str: string): string {
return Object.assign(
document.createElement("canvas").getContext("2d") as {},
{ fillStyle: str }
).fillStyle;
}
Clipboard.copy(
getHex(
getComputedStyle(document.body).getPropertyValue(
"--background-secondary"
)
)
);
Toasts.show({
message: "Secondary color copied to clipboard",
id: "toolbox-secondary-color-copied",
type: 1,
});
},
id: "colorways-toolbox_copy-secondary",
iconClassName: "copy",
},
{
title: "Copy Tertiary Color",
onClick: () => {
function getHex(str: string): string {
return Object.assign(
document.createElement("canvas").getContext("2d") as {},
{ fillStyle: str }
).fillStyle;
}
Clipboard.copy(
getHex(
getComputedStyle(document.body).getPropertyValue(
"--background-tertiary"
)
)
);
Toasts.show({
message: "Tertiary color copied to clipboard",
id: "toolbox-tertiary-color-copied",
type: 1,
});
},
id: "colorways-toolbox_copy-tertiary",
iconClassName: "copy",
}
];
export default function ({ modalProps }: { modalProps: ModalProps; }) {
const [colorVarItems, setColorVarItems] = useState<ToolboxItem[]>(ColorVarItems);
const [collapsedSettings, setCollapsedSettings] = useState<boolean>(true);
let results: ToolboxItem[];
function searchToolboxItems(e: string) {
results = [];
ColorVarItems.find((ToolboxItem: ToolboxItem) => {
if (ToolboxItem.title.toLowerCase().includes(e.toLowerCase())) {
results.push(ToolboxItem);
}
});
setColorVarItems(results);
}
return (
<ModalRoot {...modalProps} className="colorwayColorpicker">
<Flex style={{ gap: "8px", marginBottom: "8px" }}>
<TextInput
className="colorwaysColorpicker-search"
placeholder="Search for a color:"
onChange={e => {
searchToolboxItems(e);
if (e) {
setCollapsedSettings(false);
} else {
setCollapsedSettings(true);
}
}}
/>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.TRANSPARENT}
onClick={() => setCollapsedSettings(!collapsedSettings)}
>
<svg width="32" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img">
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
</svg>
</Button>
</Flex>
<ScrollerThin style={{ color: "var(--text-normal)" }} orientation="vertical" className={collapsedSettings ? " colorwaysColorpicker-collapsed" : ""} paddingFix>
{colorVarItems.map((toolboxItem: ToolboxItem) => {
return (
<div
id={
"colorways-colorstealer-item_" +
toolboxItem.id
}
className="colorwaysCreator-settingItm colorwaysCreator-toolboxItm"
onClick={toolboxItem.onClick}
style={
{
"--brand-experiment":
"var(--" + toolboxItem.id + ")",
} as React.CSSProperties
}
>
{toolboxItem.title}
</div>
);
})}
</ScrollerThin>
<Flex style={{ justifyContent: "space-between", marginTop: "8px" }} wrap="wrap" className={collapsedSettings ? "" : " colorwaysColorpicker-collapsed"}>
{ToolboxItems.map((toolboxItem: ToolboxItem, i: number) => <div
id={toolboxItem.id || `colorways-toolbox_item-${i}`}
className="colorwayToolbox-listItem"
>
<CopyIcon onClick={toolboxItem.onClick} width={20} height={20} className="colorwayToolbox-listItemSVG" />
<span className="colorwaysToolbox-label">{toolboxItem.title}</span>
</div>
)}
</Flex>
</ModalRoot>
);
}

View file

@ -0,0 +1,94 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import { openModal } from "@utils/modal";
import { FluxDispatcher, Text, Tooltip, useEffect, useState } from "@webpack/common";
import { FluxEvents } from "@webpack/types";
import { PalleteIcon } from "./Icons";
import SelectorModal from "./SelectorModal";
export default function ({
listItemClass = "ColorwaySelectorBtnContainer",
listItemWrapperClass = "",
listItemTooltipClass = "colorwaysBtn-tooltipContent"
}: {
listItemClass?: string;
listItemWrapperClass?: string;
listItemTooltipClass?: string;
}) {
const [activeColorway, setActiveColorway] = useState<string>("None");
const [visibility, setVisibility] = useState<boolean>(true);
const [isThin, setIsThin] = useState<boolean>(false);
async function setButtonVisibility() {
const [showColorwaysButton, useThinMenuButton] = await DataStore.getMany([
"showColorwaysButton",
"useThinMenuButton"
]);
setVisibility(showColorwaysButton);
setIsThin(useThinMenuButton);
}
useEffect(() => {
setButtonVisibility();
});
FluxDispatcher.subscribe("COLORWAYS_UPDATE_BUTTON_HEIGHT" as FluxEvents, ({ isTall }) => {
setIsThin(isTall);
});
FluxDispatcher.subscribe("COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents, ({ isVisible }) => {
setVisibility(isVisible);
});
if (!isThin) {
return (<Tooltip text={[
<span>Colorways</span>,
<Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Active Colorway: " + activeColorway}</Text>
]} position="right" tooltipContentClassName={listItemTooltipClass}
>
{({ onMouseEnter, onMouseLeave, onClick }) => visibility ? <div className={listItemClass}>
<div
className={listItemWrapperClass + " ColorwaySelectorBtn"}
onMouseEnter={async () => {
onMouseEnter();
setActiveColorway(await DataStore.get("actveColorwayID") || "None");
}}
onMouseLeave={onMouseLeave}
onClick={() => {
onClick();
openModal(props => <SelectorModal modalProps={props} />);
}}
><PalleteIcon /></div>
</div> : <></>}
</Tooltip>
);
} else {
return (<Tooltip text={[
<span>Colorways</span>,
<Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Active Colorway: " + activeColorway}</Text>
]} position="right" tooltipContentClassName={listItemTooltipClass}
>
{({ onMouseEnter, onMouseLeave, onClick }) => visibility ? <div className={listItemClass}>
<div
className={listItemWrapperClass + " ColorwaySelectorBtn ColorwaySelectorBtn_thin"}
onMouseEnter={async () => {
onMouseEnter();
setActiveColorway(await DataStore.get("actveColorwayID") || "None");
}}
onMouseLeave={onMouseLeave}
onClick={() => {
onClick();
openModal(props => <SelectorModal modalProps={props} />);
}}
><Text variant="text-xs/normal" style={{ color: "var(--header-primary)", fontWeight: 700, fontSize: 9 }}>Colorways</Text></div>
</div> : <></>}
</Tooltip>
);
}
}

View file

@ -0,0 +1,318 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, Forms, ScrollerThin, Text, useState } from "@webpack/common";
import { knownThemeVars } from "../constants";
import { getFontOnBg, getHex } from "../utils";
export default function ({
modalProps,
onFinished
}: {
modalProps: ModalProps;
onFinished: ({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) => void;
}) {
const [accentColor, setAccentColor] = useState<string>(getHex(
getComputedStyle(
document.body
).getPropertyValue("--brand-experiment")
));
const [primaryColor, setPrimaryColor] = useState<string>(getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-primary")
));
const [secondaryColor, setSecondaryColor] = useState<string>(getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-secondary")
));
const [tertiaryColor, setTertiaryColor] = useState<string>(getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-tertiary")
));
return <ModalRoot {...modalProps} className="colorwayCreator-modal">
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Conflicting Colors Found
</Text>
</ModalHeader>
<ModalContent className="colorwayCreator-menuWrapper">
<Text className="colorwaysConflictingColors-warning">Multiple known themes have been found, select the colors you want to copy from below:</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>Colors to copy:</Forms.FormTitle>
<div className="colorwayCreator-colorPreviews">
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: primaryColor, color: getFontOnBg(primaryColor) }} >Primary</div>
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: secondaryColor, color: getFontOnBg(secondaryColor) }} >Secondary</div>
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: tertiaryColor, color: getFontOnBg(tertiaryColor) }} >Tertiary</div>
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: accentColor, color: getFontOnBg(accentColor) }} >Accent</div>
</div>
<div className="colorwaysCreator-settingCat">
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix>
<div
id="colorways-colorstealer-item_Default"
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
>
<Forms.FormTitle>Discord</Forms.FormTitle>
<div className="colorwayCreator-colorPreviews">
<div
className="colorwayCreator-colorPreview" style={{
backgroundColor: getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-primary")
),
color: getFontOnBg(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-primary")
)
)
}}
onClick={() => setPrimaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-primary")
)
)}
>Primary</div>
<div
className="colorwayCreator-colorPreview" style={{
backgroundColor: getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-secondary")
),
color: getFontOnBg(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-secondary")
)
)
}}
onClick={() => setSecondaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-secondary")
)
)}
>Secondary</div>
<div
className="colorwayCreator-colorPreview" style={{
backgroundColor: getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-tertiary")
),
color: getFontOnBg(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-tertiary")
)
)
}}
onClick={() => setTertiaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-tertiary")
)
)}
>Tertiary</div>
<div
className="colorwayCreator-colorPreview" style={{
backgroundColor: getHex(
getComputedStyle(
document.body
).getPropertyValue("--brand-experiment")
),
color: getFontOnBg(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--brand-experiment")
)
)
}}
onClick={() => setAccentColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--brand-experiment")
)
)}
>Accent</div>
</div>
</div>
{Object.values(knownThemeVars).map((theme: any, i) => {
if (getComputedStyle(document.body).getPropertyValue(theme.variable)) {
return (
<div
id={
"colorways-colorstealer-item_" +
Object.keys(knownThemeVars)[i]
}
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
>
<Forms.FormTitle>{Object.keys(knownThemeVars)[i] + (theme.alt ? " (Main)" : "")}</Forms.FormTitle>
<div className="colorwayCreator-colorPreviews">
{theme.primary && getComputedStyle(document.body).getPropertyValue(theme.primary).match(/^\d.*%$/)
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
style={{
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`),
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`))
}}
onClick={() => {
setPrimaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`));
}}
>Primary</div>
: (
theme.primary
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
style={{
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)),
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)))
}}
onClick={() => {
setPrimaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)));
}}
>Primary</div>
: (theme.primaryVariables
&& <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`)) }}
onClick={() => {
setPrimaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`));
}}
>Primary</div>))
}
{theme.secondary && getComputedStyle(document.body).getPropertyValue(theme.secondary).match(/^\d.*%$/)
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
style={{
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`),
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`))
}}
onClick={() => {
setSecondaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`));
}}
>Secondary</div>
: (theme.secondary
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
style={{
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)),
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)))
}}
onClick={() => {
setSecondaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)));
}}
>Secondary</div>
: (theme.secondaryVariables
&& <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`)) }}
onClick={() => {
setSecondaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`));
}}
>Secondary</div>))
}
{theme.tertiary && getComputedStyle(document.body).getPropertyValue(theme.tertiary).match(/^\d.*%$/)
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
style={{
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`),
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`))
}}
onClick={() => {
setTertiaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`));
}}
>Tertiary</div>
: (theme.tertiary
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
style={{
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)),
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)))
}}
onClick={() => {
setTertiaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)));
}}
>Tertiary</div>
: (theme.tertiaryVariables
&& <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`)) }}
onClick={() => {
setTertiaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`));
}}
>Tertiary</div>))}
{theme.accent && getComputedStyle(document.body).getPropertyValue(theme.accent).match(/^\d.*%$/)
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
style={{
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`),
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`))
}}
onClick={() => {
setAccentColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`));
}}
>Accent</div>
: (theme.accent
? <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
style={{
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)),
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)))
}}
onClick={() => {
setAccentColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)));
}}
>Accent</div>
: (theme.accentVariables
&& <div
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`)) }}
onClick={() => {
setAccentColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`));
}}
>Accent</div>))}
</div>
</div>
);
}
})}
</ScrollerThin>
</div>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => {
onFinished({
accent: accentColor,
primary: primaryColor,
secondary: secondaryColor,
tertiary: tertiaryColor
});
modalProps.onClose();
}}
>Finish</Button>
</ModalFooter>
</ModalRoot >;
}

View file

@ -0,0 +1,342 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import {
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
openModal,
} from "@utils/modal";
import {
Button,
Forms,
ScrollerThin,
Switch,
Text,
TextInput,
useEffect,
UserStore,
useState,
} from "@webpack/common";
import { ColorPicker } from "..";
import { knownThemeVars } from "../constants";
import { generateCss, getPreset } from "../css";
import { Colorway } from "../types";
import { colorToHex, getHex, hexToString } from "../utils";
import ConflictingColorsModal from "./ConflictingColorsModal";
import InputColorwayIdModal from "./InputColorwayIdModal";
import ThemePreviewCategory from "./ThemePreview";
export default function ({
modalProps,
loadUIProps,
colorwayID
}: {
modalProps: ModalProps;
loadUIProps?: () => Promise<void>;
colorwayID?: string;
}) {
const [accentColor, setAccentColor] = useState<string>("5865f2");
const [primaryColor, setPrimaryColor] = useState<string>("313338");
const [secondaryColor, setSecondaryColor] = useState<string>("2b2d31");
const [tertiaryColor, setTertiaryColor] = useState<string>("1e1f22");
const [colorwayName, setColorwayName] = useState<string>("");
const [tintedText, setTintedText] = useState<boolean>(true);
const [discordSaturation, setDiscordSaturation] = useState<boolean>(true);
const [collapsedSettings, setCollapsedSettings] = useState<boolean>(true);
const [collapsedPresets, setCollapsedPresets] = useState<boolean>(true);
const [preset, setPreset] = useState<string>("default");
const [presetColorArray, setPresetColorArray] = useState<string[]>(["accent", "primary", "secondary", "tertiary"]);
const colorProps = {
accent: {
get: accentColor,
set: setAccentColor,
name: "Accent"
},
primary: {
get: primaryColor,
set: setPrimaryColor,
name: "Primary"
},
secondary: {
get: secondaryColor,
set: setSecondaryColor,
name: "Secondary"
},
tertiary: {
get: tertiaryColor,
set: setTertiaryColor,
name: "Tertiary"
}
};
useEffect(() => {
const parsedID = colorwayID?.split("colorway:")[1];
if (parsedID) {
const allEqual = (arr: any[]) => arr.every(v => v === arr[0]);
if (!parsedID) {
throw new Error("Please enter a Colorway ID");
} else if (parsedID.length < 62) {
throw new Error("Invalid Colorway ID");
} else if (!hexToString(parsedID).includes(",")) {
throw new Error("Invalid Colorway ID");
} else if (!allEqual(hexToString(parsedID).split(",").map((e: string) => e.match("#")!.length)) && hexToString(parsedID).split(",").map((e: string) => e.match("#")!.length)[0] !== 1) {
throw new Error("Invalid Colorway ID");
} else {
const colorArray: string[] = hexToString(parsedID).split(",");
setAccentColor(colorArray[0].split("#")[1]);
setPrimaryColor(colorArray[1].split("#")[1]);
setSecondaryColor(colorArray[2].split("#")[1]);
setTertiaryColor(colorArray[3].split("#")[1]);
}
}
});
const colorPickerProps = {
suggestedColors: [
"#313338",
"#2b2d31",
"#1e1f22",
"#5865f2",
],
showEyeDropper: true
};
return (
<ModalRoot {...modalProps} className="colorwayCreator-modal">
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Create Colorway
</Text>
</ModalHeader>
<ModalContent className="colorwayCreator-menuWrapper">
<Forms.FormTitle style={{ marginBottom: 0 }}>
Name:
</Forms.FormTitle>
<TextInput
placeholder="Give your Colorway a name"
value={colorwayName}
onChange={setColorwayName}
/>
<div className="colorwaysCreator-settingCat">
<Forms.FormTitle style={{ marginBottom: 0 }}>
Colors:
</Forms.FormTitle>
<div className="colorwayCreator-colorPreviews">
{presetColorArray.map(presetColor => {
return <ColorPicker
label={<Text className="colorwaysPicker-colorLabel">{colorProps[presetColor].name}</Text>}
color={parseInt(colorProps[presetColor].get, 16)}
onChange={(color: number) => {
let hexColor = color.toString(16);
while (hexColor.length < 6) {
hexColor = "0" + hexColor;
}
colorProps[presetColor].set(hexColor);
}}
{...colorPickerProps}
/>;
})}
</div>
</div>
<div className={`colorwaysCreator-settingCat${collapsedSettings ? " colorwaysCreator-settingCat-collapsed" : ""}`}>
<div
className="colorwaysCreator-settingItm colorwaysCreator-settingHeader"
onClick={() => setCollapsedSettings(!collapsedSettings)}>
<Forms.FormTitle style={{ marginBottom: 0 }}>Settings</Forms.FormTitle>
<svg className="expand-3Nh1P5 transition-30IQBn directionDown-2w0MZz" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img">
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
</svg>
</div>
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix>
<div className="colorwaysCreator-settingItm" onClick={() => setTintedText(!tintedText)}>
<Text variant="eyebrow" tag="h5">Use colored text</Text>
<Switch value={tintedText} onChange={setTintedText} hideBorder={true} style={{ marginBottom: 0 }} />
</div>
<div className="colorwaysCreator-settingItm" onClick={() => setDiscordSaturation(!discordSaturation)}>
<Text variant="eyebrow" tag="h5">Use Discord's saturation</Text>
<Switch value={discordSaturation} onChange={setDiscordSaturation} hideBorder={true} style={{ marginBottom: 0 }} />
</div>
</ScrollerThin>
</div>
<div className={`colorwaysCreator-settingCat${collapsedPresets ? " colorwaysCreator-settingCat-collapsed" : ""}`}>
<div
className="colorwaysCreator-settingItm colorwaysCreator-settingHeader"
onClick={() => setCollapsedPresets(!collapsedPresets)}>
<Forms.FormTitle style={{ marginBottom: 0 }}>Presets</Forms.FormTitle>
<svg className="expand-3Nh1P5 transition-30IQBn directionDown-2w0MZz" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img">
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
</svg>
</div>
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList">
<div className="colorwaysCreator-settingItm colorwaysCreator-preset" onClick={() => {
setPreset("default");
setPresetColorArray(["primary", "secondary", "tertiary", "accent"]);
}}>
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" fill="currentColor" />
{preset === "default" && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
</svg>
<Text variant="eyebrow" tag="h5">Default</Text>
</div>
{Object.values(getPreset()).map(pre => {
return <div className="colorwaysCreator-settingItm colorwaysCreator-preset" onClick={() => {
setPreset(pre.id);
setPresetColorArray(pre.colors);
}}>
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" fill="currentColor" />
{preset === pre.id && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
</svg>
<Text variant="eyebrow" tag="h5">{pre.name}</Text>
</div>;
})}
</ScrollerThin>
</div>
<ThemePreviewCategory isCollapsed={false} accent={"#" + accentColor} primary={"#" + primaryColor} secondary={"#" + secondaryColor} tertiary={"#" + tertiaryColor} />
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={e => {
var customColorwayCSS: string = "";
if (preset === "default") {
customColorwayCSS = generateCss(
primaryColor,
secondaryColor,
tertiaryColor,
accentColor,
tintedText,
discordSaturation
);
} else {
customColorwayCSS = getPreset(
primaryColor,
secondaryColor,
tertiaryColor,
accentColor
)[preset].preset(discordSaturation);
}
const customColorway: Colorway = {
name: (colorwayName || "Colorway") + (preset === "default" ? "" : ": Made for " + getPreset()[preset].name),
"dc-import": customColorwayCSS,
accent: "#" + accentColor,
primary: "#" + primaryColor,
secondary: "#" + secondaryColor,
tertiary: "#" + tertiaryColor,
colors: presetColorArray,
author: UserStore.getCurrentUser().username,
authorID: UserStore.getCurrentUser().id,
};
const customColorwaysArray: Colorway[] = [customColorway];
DataStore.get("customColorways").then(
customColorways => {
customColorways.forEach(
(color: Colorway, i: number) => {
if (color.name !== customColorway.name) {
customColorwaysArray.push(color);
}
}
);
DataStore.set("customColorways", customColorwaysArray);
}
);
modalProps.onClose();
loadUIProps!();
}}
>Finish</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
function setAllColors({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) {
setAccentColor(accent.split("#")[1]);
setPrimaryColor(primary.split("#")[1]);
setSecondaryColor(secondary.split("#")[1]);
setTertiaryColor(tertiary.split("#")[1]);
}
var copiedThemes = ["Discord"];
Object.values(knownThemeVars).map((theme: { variable: string; variableType?: string; }, i: number) => {
if (getComputedStyle(document.body).getPropertyValue(theme.variable)) {
copiedThemes.push(Object.keys(knownThemeVars)[i]);
}
});
if (copiedThemes.length > 1) {
openModal(props => <ConflictingColorsModal modalProps={props} onFinished={setAllColors} />);
} else {
setPrimaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-primary")
).split("#")[1]
);
setSecondaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-secondary")
).split("#")[1]
);
setTertiaryColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--background-tertiary")
).split("#")[1]
);
setAccentColor(
getHex(
getComputedStyle(
document.body
).getPropertyValue("--brand-experiment")
).split("#")[1]
);
}
}}
>
Copy Current Colors
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => openModal((props: any) => <InputColorwayIdModal modalProps={props} onColorwayId={colorwayID => {
const setColor = [
setAccentColor,
setPrimaryColor,
setSecondaryColor,
setTertiaryColor
];
hexToString(colorwayID).split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
}} />)}
>
Enter Colorway ID
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
modalProps.onClose();
}}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
);
}

View file

@ -0,0 +1,15 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export default () => {
return <div style={{
width: "100%",
height: 1,
borderTop: "thin solid var(--background-modifier-accent)",
marginTop: "var(--custom-margin-margin-medium)",
marginBottom: 20
}} />;
};

View file

@ -0,0 +1,62 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { classes } from "@utils/misc";
import type { PropsWithChildren, SVGProps } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
}
interface IconProps extends SVGProps<SVGSVGElement> {
className?: string;
height?: string | number;
width?: string | number;
}
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
return (
<svg
className={classes(className, "vc-icon")}
role="img"
width={width}
height={height}
viewBox={viewBox}
{...svgProps}
>
{children}
</svg>
);
}
export function PalleteIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-pallete-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M 12 7.5 C 13.242188 7.5 14.25 6.492188 14.25 5.25 C 14.25 4.007812 13.242188 3 12 3 C 10.757812 3 9.75 4.007812 9.75 5.25 C 9.75 6.492188 10.757812 7.5 12 7.5 Z M 18 12 C 19.242188 12 20.25 10.992188 20.25 9.75 C 20.25 8.507812 19.242188 7.5 18 7.5 C 16.757812 7.5 15.75 8.507812 15.75 9.75 C 15.75 10.992188 16.757812 12 18 12 Z M 8.25 10.5 C 8.25 11.742188 7.242188 12.75 6 12.75 C 4.757812 12.75 3.75 11.742188 3.75 10.5 C 3.75 9.257812 4.757812 8.25 6 8.25 C 7.242188 8.25 8.25 9.257812 8.25 10.5 Z M 9 19.5 C 10.242188 19.5 11.25 18.492188 11.25 17.25 C 11.25 16.007812 10.242188 15 9 15 C 7.757812 15 6.75 16.007812 6.75 17.25 C 6.75 18.492188 7.757812 19.5 9 19.5 Z M 9 19.5 M 24 12 C 24 16.726562 21.199219 15.878906 18.648438 15.105469 C 17.128906 14.644531 15.699219 14.210938 15 15 C 14.09375 16.023438 14.289062 17.726562 14.472656 19.378906 C 14.738281 21.742188 14.992188 24 12 24 C 5.371094 24 0 18.628906 0 12 C 0 5.371094 5.371094 0 12 0 C 18.628906 0 24 5.371094 24 12 Z M 12 22.5 C 12.917969 22.5 12.980469 22.242188 12.984375 22.234375 C 13.097656 22.015625 13.167969 21.539062 13.085938 20.558594 C 13.066406 20.304688 13.03125 20.003906 12.996094 19.671875 C 12.917969 18.976562 12.828125 18.164062 12.820312 17.476562 C 12.804688 16.417969 12.945312 15.0625 13.875 14.007812 C 14.429688 13.382812 15.140625 13.140625 15.78125 13.078125 C 16.390625 13.023438 17 13.117188 17.523438 13.234375 C 18.039062 13.351562 18.574219 13.515625 19.058594 13.660156 L 19.101562 13.675781 C 19.621094 13.832031 20.089844 13.972656 20.53125 14.074219 C 21.511719 14.296875 21.886719 14.199219 22.019531 14.109375 C 22.074219 14.070312 22.5 13.742188 22.5 12 C 22.5 6.199219 17.800781 1.5 12 1.5 C 6.199219 1.5 1.5 6.199219 1.5 12 C 1.5 17.800781 6.199219 22.5 12 22.5 Z M 12 22.5"
/></Icon>
);
}
export function CloseIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-close-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"
/>
</Icon>
);
}

View file

@ -0,0 +1,264 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import { openUserProfile } from "@utils/discord";
import {
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
} from "@utils/modal";
import { Button, Clipboard, Forms, Text, Toasts } from "@webpack/common";
import { ColorwayCSS } from "..";
import { generateCss } from "../css";
import { Colorway } from "../types";
import { colorToHex } from "../utils";
import ThemePreviewCategory from "./ThemePreview";
export default function ({
modalProps,
colorwayProps,
discrimProps = false,
loadUIProps
}: {
modalProps: ModalProps;
colorwayProps: Colorway;
discrimProps?: boolean;
loadUIProps: () => Promise<void>;
}) {
const colors: string[] = colorwayProps.colors || [
"accent",
"primary",
"secondary",
"tertiary",
];
return (
<ModalRoot {...modalProps} className="colorwayCreator-modal">
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Colorway Details: {colorwayProps.name}
</Text>
</ModalHeader>
<ModalContent>
<div className="colorwayInfo-wrapper">
<div className="colorwayInfo-colorSwatches">
{colors.map(color => {
return (
<div
className="colorwayInfo-colorSwatch"
style={{
backgroundColor: colorwayProps[color],
}}
onClick={() => {
Clipboard.copy(colorwayProps[color]);
Toasts.show({
message:
"Copied color successfully",
type: 1,
id: "copy-colorway-color-notify",
});
}}
></div>
);
})}
</div>
<div className="colorwayInfo-row colorwayInfo-author">
<Forms.FormTitle style={{ marginBottom: 0 }}>
Author:
</Forms.FormTitle>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => {
openUserProfile(colorwayProps.authorID);
}}
>
{colorwayProps.author}
</Button>
</div>
<div className="colorwayInfo-row colorwayInfo-css">
<Forms.FormTitle style={{ marginBottom: 0 }}>
CSS:
</Forms.FormTitle>
<Text
variant="code"
selectable={true}
className="colorwayInfo-cssCodeblock"
>
{colorwayProps["dc-import"]}
</Text>
</div>
<ThemePreviewCategory
isCollapsed={true}
className="colorwayInfo-lastCat"
accent={colorwayProps.accent}
primary={colorwayProps.primary}
secondary={colorwayProps.secondary}
tertiary={colorwayProps.tertiary}
></ThemePreviewCategory>
</div>
</ModalContent>
<ModalFooter>
{discrimProps && <Button
style={{ marginLeft: 8 }}
color={Button.Colors.RED}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={async () => {
const customColorways = await DataStore.get("customColorways");
const actveColorwayID = await DataStore.get("actveColorwayID");
const customColorwaysArray: Colorway[] = [];
customColorways.map((color: Colorway, i: number) => {
if (customColorways.length > 0) {
if (color.name !== colorwayProps.name) {
customColorwaysArray.push(color);
}
if (++i === customColorways.length) {
DataStore.set("customColorways", customColorwaysArray);
}
if (actveColorwayID === colorwayProps.name) {
DataStore.set("actveColorway", null);
DataStore.set("actveColorwayID", null);
ColorwayCSS.set("");
}
modalProps.onClose();
loadUIProps();
}
});
}}
>
Delete...
</Button>}
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
const stringToHex = (str: string) => {
let hex = "";
for (let i = 0; i < str.length; i++) {
const charCode = str.charCodeAt(i);
const hexValue = charCode.toString(16);
// Pad with zeros to ensure two-digit representation
hex += hexValue.padStart(2, "0");
}
return hex;
};
const colorwayIDArray = `${colorwayProps.accent},${colorwayProps.primary},${colorwayProps.secondary},${colorwayProps.tertiary}`;
const colorwayID = stringToHex(colorwayIDArray);
Clipboard.copy(colorwayID);
Toasts.show({
message: "Copied Colorway ID Successfully",
type: 1,
id: "copy-colorway-id-notify",
});
}}
>
Copy Colorway ID
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
Clipboard.copy(colorwayProps["dc-import"]);
Toasts.show({
message: "Copied CSS to Clipboard",
type: 1,
id: "copy-colorway-css-notify",
});
}}
>
Copy CSS
</Button>
{discrimProps ? <Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={async () => {
const customColorways = await DataStore.get("customColorways");
const customColorwaysArray: Colorway[] = [];
customColorways.map((color: Colorway, i: number) => {
if (customColorways.length > 0) {
if (color.name === colorwayProps.name) {
color["dc-import"] = generateCss(colorToHex(color.primary) || "313338", colorToHex(color.secondary) || "2b2d31", colorToHex(color.tertiary) || "1e1f22", colorToHex(color.accent) || "5865f2", true, true);
customColorwaysArray.push(color);
} else {
customColorwaysArray.push(color);
}
if (++i === customColorways.length) {
DataStore.set("customColorways", customColorwaysArray);
}
modalProps.onClose();
loadUIProps();
}
});
}}
>
Update CSS
</Button> : <Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={async () => {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const colorways = data.flatMap(json => json.colorways);
const customColorways = await DataStore.get("customColorways");
const customColorwaysArray: Colorway[] = [];
colorways.map((color: Colorway, i: number) => {
if (colorways.length > 0) {
if (color.name === colorwayProps.name) {
color.name += " (Custom)";
color["dc-import"] = generateCss(colorToHex(color.primary) || "313338", colorToHex(color.secondary) || "2b2d31", colorToHex(color.tertiary) || "1e1f22", colorToHex(color.accent) || "5865f2", true, true);
customColorwaysArray.push(color);
}
if (++i === colorways.length) {
DataStore.set("customColorways", [...customColorways, ...customColorwaysArray]);
}
modalProps.onClose();
loadUIProps();
}
});
}}
>
Update CSS (Local)
</Button>}
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
modalProps.onClose();
}}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
);
}

View file

@ -0,0 +1,49 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalContent, ModalFooter, ModalProps, ModalRoot } from "@utils/modal";
import { Button, Forms, TextInput, useState } from "@webpack/common";
import { hexToString } from "../utils";
export default function ({ modalProps, onColorwayId }: { modalProps: ModalProps, onColorwayId: (colorwayID: string) => void; }) {
const [colorwayID, setColorwayID] = useState<string>("");
return <ModalRoot {...modalProps} className="colorwaysCreator-noMinHeight">
<ModalContent className="colorwaysCreator-noHeader colorwaysCreator-noMinHeight">
<Forms.FormTitle>Colorway ID:</Forms.FormTitle>
<TextInput placeholder="Enter Colorway ID" onInput={e => setColorwayID(e.currentTarget.value)} />
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => {
if (!colorwayID) {
throw new Error("Please enter a Colorway ID");
} else if (!hexToString(colorwayID).includes(",")) {
throw new Error("Invalid Colorway ID");
} else {
onColorwayId(colorwayID);
modalProps.onClose();
}
}}
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
}

View file

@ -0,0 +1,469 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* eslint-disable arrow-parens */
import * as DataStore from "@api/DataStore";
import { ModalContent, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import {
Button,
Forms,
Menu,
Popout,
ScrollerThin,
Select,
SettingsRouter,
TextInput,
Tooltip,
useCallback,
useEffect,
useState,
} from "@webpack/common";
import { ColorwayCSS } from "..";
import { defaultColorwaySource, fallbackColorways } from "../constants";
import { generateCss } from "../css";
import { Colorway } from "../types";
import { colorToHex } from "../utils";
import ColorPickerModal from "./ColorPicker";
import CreatorModal from "./CreatorModal";
import { CloseIcon } from "./Icons";
import ColorwayInfoModal from "./InfoModal";
const { SelectionCircle } = findByPropsLazy("SelectionCircle");
export default function ({
modalProps,
}: {
modalProps: ModalProps;
}): JSX.Element | any {
const [currentColorway, setCurrentColorway] = useState<string>("");
const [colorways, setColorways] = useState<Colorway[]>([]);
const [thirdPartyColorways, setThirdPartyColorways] = useState<Colorway[]>([]);
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
const [searchString, setSearchString] = useState<string>("");
const [loaderHeight, setLoaderHeight] = useState<string>("2px");
const [visibility, setVisibility] = useState<string>("all");
const [showReloadMenu, setShowReloadMenu] = useState(false);
let visibleColorwayArray: Colorway[];
switch (visibility) {
case "all":
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
case "official":
visibleColorwayArray = [...colorways];
break;
case "3rdparty":
visibleColorwayArray = [...thirdPartyColorways];
break;
case "custom":
visibleColorwayArray = [...customColorways];
break;
default:
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
}
async function loadUI() {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
}
const cached_loadUI = useCallback(loadUI, [setColorways, setCustomColorways, setCurrentColorway]);
async function searchColorways(e: string) {
if (!e) {
cached_loadUI();
return;
}
const colorwaySourceFiles = await DataStore.get("colorwaySourceFiles");
const data = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url).then((res) => res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; }))
)
);
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.get("customColorways");
var results: Colorway[] = [];
(colorways || fallbackColorways).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
results.push(Colorway);
});
var thirdPartyResults: Colorway[] = [];
(thirdPartyColorwaysArr).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
thirdPartyResults.push(Colorway);
});
var customResults: Colorway[] = [];
baseData.find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
customResults.push(Colorway);
});
setColorways(results);
setThirdPartyColorways(thirdPartyResults);
setCustomColorways(customResults);
}
useEffect(() => {
if (!searchString) {
cached_loadUI();
}
setLoaderHeight("0px");
}, [searchString]);
function ReloadPopout(onClose: () => void) {
return (
<Menu.Menu
navId="dc-reload-menu"
onClose={onClose}
>
<Menu.MenuItem
id="dc-force-reload"
label="Force Reload"
action={async () => {
setLoaderHeight("2px");
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url, { cache: "no-store" })
)
);
const data = await Promise.all(
responses.map((res: Response) => {
setLoaderHeight("0px");
return res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; });
}
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
}}
/>
</Menu.Menu>
);
}
return (
<ModalRoot {...modalProps} className="colorwaySelectorModal">
<ModalHeader>
<Select className="colorwaySelector-pill colorwaySelector-pill_select" options={[{
value: "all",
label: "All"
},
{
value: "official",
label: "Official"
},
{
value: "3rdparty",
label: "3rd-Party"
},
{
value: "custom",
label: "Custom"
}]} select={value => {
setVisibility(value);
}} isSelected={value => visibility === value} serialize={String} />
<TextInput
inputClassName="colorwaySelector-searchInput"
className="colorwaySelector-search"
placeholder="Search for Colorways..."
value={searchString}
onChange={(e: string) => [searchColorways, setSearchString].forEach(t => t(e))}
/>
<Tooltip text="Refresh Colorways...">
{({ onMouseEnter, onMouseLeave }) => {
return <Popout
position="bottom"
align="right"
animation={Popout.Animation.NONE}
shouldShow={showReloadMenu}
onRequestClose={() => setShowReloadMenu(false)}
renderPopout={() => ReloadPopout(() => setShowReloadMenu(false))}
>
{(_, { isShown }) => (
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-refreshcolorway"
onMouseEnter={isShown ? () => { } : onMouseEnter}
onMouseLeave={isShown ? () => { } : onMouseLeave}
onClick={() => {
setLoaderHeight("2px");
cached_loadUI().then(() => setLoaderHeight("0px"));
}}
onContextMenu={() => { onMouseLeave(); setShowReloadMenu(v => !v); }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
style={{ padding: "6px" }}
viewBox="0 0 24 24"
fill="currentColor"
>
<rect
y="0"
fill="none"
width="24"
height="24"
/>
<path d="M6.351,6.351C7.824,4.871,9.828,4,12,4c4.411,0,8,3.589,8,8h2c0-5.515-4.486-10-10-10 C9.285,2,6.779,3.089,4.938,4.938L3,3v6h6L6.351,6.351z" />
<path d="M17.649,17.649C16.176,19.129,14.173,20,12,20c-4.411,0-8-3.589-8-8H2c0,5.515,4.486,10,10,10 c2.716,0,5.221-1.089,7.062-2.938L21,21v-6h-6L17.649,17.649z" />
</svg>
</Button>
)}
</Popout>;
}}
</Tooltip>
<Tooltip text="Open Settings">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-opensettings"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
SettingsRouter.open("ColorwaysSettings");
modalProps.onClose();
}}
>
<svg
aria-hidden="true"
role="img"
width="20"
height="20"
style={{ padding: "6px" }}
viewBox="0 0 24 24"
>
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z" />
</svg>
</Button>}
</Tooltip>
<Tooltip text="Create Colorway...">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <CreatorModal
modalProps={props}
loadUIProps={cached_loadUI}
/>)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="20"
height="20"
style={{ padding: "6px" }}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"
/>
</svg>
</Button>}
</Tooltip>
<Tooltip text="Open Color Stealer">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-opencolorstealer"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <ColorPickerModal modalProps={props} />)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" style={{ padding: "6px" }} fill="currentColor" viewBox="0 0 16 16">
<path d="M12.433 10.07C14.133 10.585 16 11.15 16 8a8 8 0 1 0-8 8c1.996 0 1.826-1.504 1.649-3.08-.124-1.101-.252-2.237.351-2.92.465-.527 1.42-.237 2.433.07zM8 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM5 6.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm.5 6.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z" />
</svg>
</Button>}
</Tooltip>
<Tooltip text="Close">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
id="colorwaySelector-pill_closeSelector"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => modalProps.onClose()}
>
<CloseIcon style={{ padding: "6px" }} width={20} height={20} />
</Button>}
</Tooltip>
</ModalHeader>
<ModalContent className="colorwaySelectorModalContent">
<div className="colorwaysLoader-barContainer"><div className="colorwaysLoader-bar" style={{ height: loaderHeight }} /></div>
<ScrollerThin style={{ maxHeight: "450px" }} className="ColorwaySelectorWrapper">
{visibleColorwayArray.length === 0 &&
<Forms.FormTitle
style={{
marginBottom: 0,
width: "100%",
textAlign: "center",
}}
>
No colorways...
</Forms.FormTitle>
}
{["all", "official", "3rdparty", "custom"].includes(visibility) && (
visibleColorwayArray.map((color, ind) => {
var colors: Array<string> = color.colors || [
"accent",
"primary",
"secondary",
"tertiary",
];
return (
<Tooltip text={color.name}>
{({ onMouseEnter, onMouseLeave }) => {
return (
<div
className="discordColorway"
id={"colorway-" + color.name}
data-last-official={
ind + 1 === colorways.length
}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async () => {
const [
onDemandWays,
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
] = await DataStore.getMany([
"onDemandWays",
"onDemandWaysTintedText",
"onDemandWaysDiscordSaturation"
]);
if (currentColorway === color.name) {
DataStore.set("actveColorwayID", null);
DataStore.set("actveColorway", null);
ColorwayCSS.remove();
} else {
DataStore.set("activeColorwayColors", color.colors);
DataStore.set("actveColorwayID", color.name);
if (onDemandWays) {
const demandedColorway = generateCss(
colorToHex(color.primary),
colorToHex(color.secondary),
colorToHex(color.tertiary),
colorToHex(color.accent),
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
);
DataStore.set("actveColorway", demandedColorway);
ColorwayCSS.set(demandedColorway);
} else {
DataStore.set("actveColorway", color["dc-import"]);
ColorwayCSS.set(color["dc-import"]);
}
}
setCurrentColorway(await DataStore.get("actveColorwayID") as string);
}}
>
<div
className="colorwayInfoIconContainer"
onClick={(e) => {
e.stopPropagation();
openModal((props) => (
<ColorwayInfoModal
modalProps={
props
}
colorwayProps={
color
}
discrimProps={customColorways.includes(
color
)}
loadUIProps={cached_loadUI}
/>
));
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg>
</div>
<div className="discordColorwayPreviewColorContainer">
{colors.map((colorItm) => <div
className="discordColorwayPreviewColor"
style={{
backgroundColor: color[colorItm],
}}
/>
)}
</div>
{currentColorway === color.name && <SelectionCircle />}
</div>
);
}}
</Tooltip>
);
})
)}
</ScrollerThin>
</ModalContent >
</ModalRoot >
);
}

View file

@ -0,0 +1,129 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { Flex } from "@components/Flex";
import { SettingsTab } from "@components/VencordSettings/shared";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { chooseFile, saveFile } from "@utils/web";
import { Button, Card, Forms, Text } from "@webpack/common";
import { defaultColorwaySource } from "../../constants";
import { generateCss } from "../../css";
import { Colorway } from "../../types";
export default function () {
return <SettingsTab title="Manage Colorways">
<Forms.FormSection title="Import/Export">
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
<Flex flexDirection="column">
<strong>Warning</strong>
<span>Importing a colorways file will overwrite your current custom colorways.</span>
</Flex>
</Card>
<Text variant="text-md/normal" className={Margins.bottom8}>
You can import and export your custom colorways as a JSON file.
This allows you to easily transfer them to another device/installation.
</Text>
<Flex>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
if (IS_DISCORD_DESKTOP) {
const [file] = await DiscordNative.fileManager.openFiles({
filters: [
{ name: "Discord Colorways List", extensions: ["json"] },
{ name: "all", extensions: ["*"] }
]
});
if (file) {
try {
await DataStore.set("customColorways", JSON.parse(new TextDecoder().decode(file.data)));
} catch (err) {
new Logger("DiscordColorways").error(err);
}
}
} else {
const file = await chooseFile("application/json");
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
try {
await DataStore.set("customColorways", JSON.parse(reader.result as string));
} catch (err) {
new Logger("DiscordColorways").error(err);
}
};
reader.readAsText(file);
}
}}>
Import Colorways
</Button>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
if (IS_DISCORD_DESKTOP) {
DiscordNative.fileManager.saveWithDialog(JSON.stringify(await DataStore.get("customColorways") as string), "colorways.json");
} else {
saveFile(new File([JSON.stringify(await DataStore.get("customColorways") as string)], "colorways.json", { type: "application/json" }));
}
}}>
Export Colorways
</Button>
</Flex>
</Forms.FormSection>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormSection title="Transfer 3rd Party Colorways to local index (3rd-Party > Custom):">
<Flex>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const thirdPartyColorwaysArr: Colorway[] = data.flatMap(json => json.url !== defaultColorwaySource ? json.colorways : []);
const customColorways: Colorway[] = await DataStore.get("customColorways") as Colorway[];
DataStore.set("customColorways", [...customColorways, ...thirdPartyColorwaysArr.map(({ name: nameOld, ...rest }) => ({ name: (nameOld + " (Custom)"), ...rest }))]);
}}>
As-Is
</Button>
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const thirdPartyColorwaysArr: Colorway[] = data.flatMap(json => json.url !== defaultColorwaySource ? json.colorways : []);
const customColorways: Colorway[] = await DataStore.get("customColorways") as Colorway[];
DataStore.set("customColorways", [...customColorways, ...thirdPartyColorwaysArr.map(({ name: nameOld, "dc-import": oldImport, ...rest }: Colorway) => ({ name: (nameOld + " (Custom)"), "dc-import": generateCss(rest.primary.split("#")[1] || "313338", rest.secondary.split("#")[1] || "2b2d31", rest.tertiary.split("#")[1] || "1e1f22", rest.accent.split("#")[1] || "5865f2", true, true), ...rest }))]);
}}>
With Updated CSS
</Button>
</Flex>
</Forms.FormSection>
</SettingsTab>;
}

View file

@ -0,0 +1,68 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { SettingsTab } from "@components/VencordSettings/shared";
import { Switch, useCallback, useEffect, useState } from "@webpack/common";
export default function () {
const [onDemand, setOnDemand] = useState<boolean>(false);
const [onDemandTinted, setOnDemandTinted] = useState<boolean>(false);
const [onDemandDiscordSat, setOnDemandDiscordSat] = useState<boolean>(false);
async function loadUI() {
const [
onDemandWays,
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
] = await DataStore.getMany([
"onDemandWays",
"onDemandWaysTintedText",
"onDemandWaysDiscordSaturation"
]);
setOnDemand(onDemandWays);
setOnDemandTinted(onDemandWaysTintedText);
setOnDemandDiscordSat(onDemandWaysDiscordSaturation);
}
const cached_loadUI = useCallback(loadUI, []);
useEffect(() => {
cached_loadUI();
}, []);
return <SettingsTab title="On-Demand">
<Switch
value={onDemand}
onChange={(v: boolean) => {
setOnDemand(v);
DataStore.set("onDemandWays", v);
}}
note="Always utilise the latest of what DiscordColorways has to offer. CSS is being directly generated on the device and gets applied in the place of the normal import/CSS given by the colorway."
>
Enable Colorways On Demand
</Switch>
<Switch
value={onDemandTinted}
onChange={(v: boolean) => {
setOnDemandTinted(v);
DataStore.set("onDemandWaysTintedText", v);
}}
disabled={!onDemand}
>
Use tinted text
</Switch>
<Switch
hideBorder
value={onDemandDiscordSat}
onChange={(v: boolean) => {
setOnDemandDiscordSat(v);
DataStore.set("onDemandWaysDiscordSaturation", v);
}}
disabled={!onDemand}
>
Use Discord's saturation
</Switch>
</SettingsTab>;
}

View file

@ -0,0 +1,421 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* eslint-disable arrow-parens */
import * as DataStore from "@api/DataStore";
import { Flex } from "@components/Flex";
import { SettingsTab } from "@components/VencordSettings/shared";
import { openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import {
Button,
Forms,
Menu,
Popout,
Select,
TextInput,
Tooltip,
useCallback,
useEffect,
useState,
} from "@webpack/common";
import { ColorwayCSS } from "../..";
import { defaultColorwaySource, fallbackColorways } from "../../constants";
import { generateCss } from "../../css";
import { Colorway } from "../../types";
import { colorToHex } from "../../utils";
import ColorPickerModal from "../ColorPicker";
import CreatorModal from "../CreatorModal";
import ColorwayInfoModal from "../InfoModal";
const { SelectionCircle } = findByPropsLazy("SelectionCircle");
export default function (): JSX.Element | any {
const [currentColorway, setCurrentColorway] = useState<string>("");
const [colorways, setColorways] = useState<Colorway[]>([]);
const [thirdPartyColorways, setThirdPartyColorways] = useState<Colorway[]>([]);
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
const [searchString, setSearchString] = useState<string>("");
const [loaderHeight, setLoaderHeight] = useState<string>("2px");
const [visibility, setVisibility] = useState<string>("all");
const [showReloadMenu, setShowReloadMenu] = useState(false);
let visibleColorwayArray: Colorway[];
switch (visibility) {
case "all":
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
case "official":
visibleColorwayArray = [...colorways];
break;
case "3rdparty":
visibleColorwayArray = [...thirdPartyColorways];
break;
case "custom":
visibleColorwayArray = [...customColorways];
break;
default:
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
}
async function loadUI() {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
}
const cached_loadUI = useCallback(loadUI, [setColorways, setCustomColorways, setCurrentColorway]);
async function searchColorways(e: string) {
if (!e) {
cached_loadUI();
return;
}
const colorwaySourceFiles = await DataStore.get("colorwaySourceFiles");
const data = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url).then((res) => res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; }))
)
);
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.get("customColorways");
var results: Colorway[] = [];
(colorways || fallbackColorways).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
results.push(Colorway);
});
var thirdPartyResults: Colorway[] = [];
(thirdPartyColorwaysArr).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
thirdPartyResults.push(Colorway);
});
var customResults: Colorway[] = [];
baseData.find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
customResults.push(Colorway);
});
setColorways(results);
setThirdPartyColorways(thirdPartyResults);
setCustomColorways(customResults);
}
useEffect(() => {
if (!searchString) {
cached_loadUI();
}
setLoaderHeight("0px");
}, [searchString]);
function ReloadPopout(onClose: () => void) {
return (
<Menu.Menu
navId="dc-reload-menu"
onClose={onClose}
>
<Menu.MenuItem
id="dc-force-reload"
label="Force Reload"
action={async () => {
setLoaderHeight("2px");
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url, { cache: "no-store" })
)
);
const data = await Promise.all(
responses.map((res: Response) => {
setLoaderHeight("0px");
return res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; });
}
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
}}
/>
</Menu.Menu>
);
}
return (
<SettingsTab title="Colors">
<div className="colorwaysSettingsSelector-wrapper">
<Flex style={{ gap: "0" }}>
<Select className="colorwaySelector-pill colorwaySelector-pill_select" options={[{
value: "all",
label: "All"
},
{
value: "official",
label: "Official"
},
{
value: "3rdparty",
label: "3rd-Party"
},
{
value: "custom",
label: "Custom"
}]} select={value => {
setVisibility(value);
}} isSelected={value => visibility === value} serialize={String} />
<TextInput
inputClassName="colorwaySelector-searchInput"
className="colorwaySelector-search"
placeholder="Search for Colorways..."
value={searchString}
onChange={(e: string) => [searchColorways, setSearchString].forEach(t => t(e))}
/>
<Tooltip text="Refresh Colorways...">
{({ onMouseEnter, onMouseLeave }) => {
return <Popout
position="bottom"
align="right"
animation={Popout.Animation.NONE}
shouldShow={showReloadMenu}
onRequestClose={() => setShowReloadMenu(false)}
renderPopout={() => ReloadPopout(() => setShowReloadMenu(false))}
>
{(_, { isShown }) => (
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-refreshcolorway"
onMouseEnter={isShown ? () => { } : onMouseEnter}
onMouseLeave={isShown ? () => { } : onMouseLeave}
onClick={() => {
setLoaderHeight("2px");
cached_loadUI().then(() => setLoaderHeight("0px"));
}}
onContextMenu={() => { onMouseLeave(); setShowReloadMenu(v => !v); }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
style={{ padding: "6px", boxSizing: "content-box" }}
viewBox="0 0 24 24"
fill="currentColor"
>
<rect
y="0"
fill="none"
width="24"
height="24"
/>
<path d="M6.351,6.351C7.824,4.871,9.828,4,12,4c4.411,0,8,3.589,8,8h2c0-5.515-4.486-10-10-10 C9.285,2,6.779,3.089,4.938,4.938L3,3v6h6L6.351,6.351z" />
<path d="M17.649,17.649C16.176,19.129,14.173,20,12,20c-4.411,0-8-3.589-8-8H2c0,5.515,4.486,10,10,10 c2.716,0,5.221-1.089,7.062-2.938L21,21v-6h-6L17.649,17.649z" />
</svg>
</Button>
)}
</Popout>;
}}
</Tooltip>
<Tooltip text="Create Colorway...">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <CreatorModal
modalProps={props}
loadUIProps={cached_loadUI}
/>)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="20"
height="20"
style={{ padding: "6px", boxSizing: "content-box" }}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"
/>
</svg>
</Button>}
</Tooltip>
<Tooltip text="Open Color Stealer">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-opencolorstealer"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <ColorPickerModal modalProps={props} />)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" style={{ padding: "6px", boxSizing: "content-box" }} fill="currentColor" viewBox="0 0 16 16">
<path d="M12.433 10.07C14.133 10.585 16 11.15 16 8a8 8 0 1 0-8 8c1.996 0 1.826-1.504 1.649-3.08-.124-1.101-.252-2.237.351-2.92.465-.527 1.42-.237 2.433.07zM8 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM5 6.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm.5 6.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z" />
</svg>
</Button>}
</Tooltip>
</Flex>
<div className="colorwaysLoader-barContainer"><div className="colorwaysLoader-bar" style={{ height: loaderHeight }} /></div>
<div className="ColorwaySelectorWrapper">
{visibleColorwayArray.length === 0 &&
<Forms.FormTitle
style={{
marginBottom: 0,
width: "100%",
textAlign: "center",
}}
>
No colorways...
</Forms.FormTitle>
}
{visibleColorwayArray.map((color, ind) => {
var colors: Array<string> = color.colors || [
"accent",
"primary",
"secondary",
"tertiary",
];
return (
<Tooltip text={color.name}>
{({ onMouseEnter, onMouseLeave }) => {
return (
<div
className="discordColorway"
id={"colorway-" + color.name}
data-last-official={
ind + 1 === colorways.length
}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async () => {
const [
onDemandWays,
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
] = await DataStore.getMany([
"onDemandWays",
"onDemandWaysTintedText",
"onDemandWaysDiscordSaturation"
]);
if (currentColorway === color.name) {
DataStore.set("actveColorwayID", null);
DataStore.set("actveColorway", null);
ColorwayCSS.remove();
} else {
DataStore.set("activeColorwayColors", color.colors);
DataStore.set("actveColorwayID", color.name);
if (onDemandWays) {
const demandedColorway = generateCss(
colorToHex(color.primary),
colorToHex(color.secondary),
colorToHex(color.tertiary),
colorToHex(color.accent),
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
);
DataStore.set("actveColorway", demandedColorway);
ColorwayCSS.set(demandedColorway);
} else {
DataStore.set("actveColorway", color["dc-import"]);
ColorwayCSS.set(color["dc-import"]);
}
}
setCurrentColorway(await DataStore.get("actveColorwayID") as string);
}}
>
<div
className="colorwayInfoIconContainer"
onClick={(e) => {
e.stopPropagation();
openModal((props) => (
<ColorwayInfoModal
modalProps={
props
}
colorwayProps={
color
}
discrimProps={customColorways.includes(
color
)}
loadUIProps={cached_loadUI}
/>
));
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg>
</div>
<div className="discordColorwayPreviewColorContainer">
{colors.map((colorItm) => <div
className="discordColorwayPreviewColor"
style={{
backgroundColor: color[colorItm],
}}
/>
)}
</div>
{currentColorway === color.name && <SelectionCircle />}
</div>
);
}}
</Tooltip>
);
})}
</div>
</div>
</SettingsTab>
);
}

View file

@ -0,0 +1,305 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { Flex } from "@components/Flex";
import { CopyIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { SettingsTab } from "@components/VencordSettings/shared";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import {
Button,
Clipboard,
FluxDispatcher,
Forms,
Switch,
Text,
TextInput,
useCallback,
useEffect,
useState
} from "@webpack/common";
import { FluxEvents } from "@webpack/types";
import { versionData } from "../..";
import { defaultColorwaySource, fallbackColorways, knownColorwaySources } from "../../constants";
import { Colorway } from "../../types";
import Divider from "../Divider";
import { CloseIcon } from "../Icons";
export default function () {
const [colorways, setColorways] = useState<Colorway[]>([]);
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<string[]>();
const [colorsButtonVisibility, setColorsButtonVisibility] = useState<boolean>(false);
const [isButtonThin, setIsButtonThin] = useState<boolean>(false);
async function loadUI() {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().catch(() => { return { colorways: [] }; })
));
const colorways = data.flatMap(json => json.colorways);
const [
customColorways,
colorwaySourceFiless,
showColorwaysButton,
useThinMenuButton
] = await DataStore.getMany([
"customColorways",
"colorwaySourceFiles",
"showColorwaysButton",
"useThinMenuButton"
]);
setColorways(colorways || fallbackColorways);
setCustomColorways(customColorways);
setColorwaySourceFiles(colorwaySourceFiless);
setColorsButtonVisibility(showColorwaysButton);
setIsButtonThin(useThinMenuButton);
}
const cached_loadUI = useCallback(loadUI, []);
useEffect(() => {
cached_loadUI();
}, []);
return <SettingsTab title="Settings">
<div className="colorwaysSettingsPage-wrapper">
<Flex style={{ gap: "0", marginBottom: "8px" }}>
<Forms.FormTitle tag="h5" style={{ width: "100%", marginBottom: "0", lineHeight: "32px" }}>Sources</Forms.FormTitle>
<Button
className="colorwaysSettings-colorwaySourceAction"
innerClassName="colorwaysSettings-iconButtonInner"
style={{ flexShrink: "0" }}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
onClick={() => {
openModal(props => {
var colorwaySource = "";
return <ModalRoot {...props}>
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Add a source:
</Text>
</ModalHeader>
<ModalContent>
<TextInput
placeholder="Enter a valid URL..."
onChange={e => colorwaySource = e}
/>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={async () => {
var sourcesArr: string[] = [];
const colorwaySourceFilesArr = await DataStore.get("colorwaySourceFiles");
colorwaySourceFilesArr.map((source: string) => sourcesArr.push(source));
if (colorwaySource !== defaultColorwaySource) {
sourcesArr.push(colorwaySource);
}
DataStore.set("colorwaySourceFiles", sourcesArr);
setColorwaySourceFiles(sourcesArr);
props.onClose();
}}
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => props.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
});
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="14"
height="14"
viewBox="0 0 24 24">
<path
fill="currentColor"
d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"
/>
</svg>
Add a source...
</Button>
</Flex>
<Flex flexDirection="column">
{colorwaySourceFiles?.map((colorwaySourceFile: string) => <div className="colorwaysSettings-colorwaySource">
{knownColorwaySources.find(o => o.url === colorwaySourceFile) ? <div className="hoverRoll">
<Text className="colorwaysSettings-colorwaySourceLabel hoverRoll_normal">
{knownColorwaySources.find(o => o.url === colorwaySourceFile)!.name} {colorwaySourceFile === defaultColorwaySource && <div className="colorways-badge">DEFAULT</div>}
</Text>
<Text className="colorwaysSettings-colorwaySourceLabel hoverRoll_hovered">
{colorwaySourceFile}
</Text>
</div>
: <Text className="colorwaysSettings-colorwaySourceLabel">
{colorwaySourceFile}
</Text>}
{colorwaySourceFile !== defaultColorwaySource
&& <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={async () => {
var sourcesArr: string[] = [];
const colorwaySourceFilesArr = await DataStore.get("colorwaySourceFiles");
colorwaySourceFilesArr.map((source: string) => {
if (source !== colorwaySourceFile) {
sourcesArr.push(source);
}
});
DataStore.set("colorwaySourceFiles", sourcesArr);
setColorwaySourceFiles(sourcesArr);
}}
>
<CloseIcon width={20} height={20} />
</Button>}
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={() => { Clipboard.copy(colorwaySourceFile); }}
>
<CopyIcon width={20} height={20} />
</Button>
</div>
)}
</Flex>
<Divider />
<Forms.FormTitle tag="h5">Quick Switch</Forms.FormTitle>
<Switch
value={colorsButtonVisibility}
onChange={(v: boolean) => {
setColorsButtonVisibility(v);
DataStore.set("showColorwaysButton", v);
FluxDispatcher.dispatch({
type: "COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents,
isVisible: v
});
}}
note="Shows a button on the top of the servers list that opens a colorway selector modal."
>
Enable Quick Switch
</Switch>
<Switch
value={isButtonThin}
onChange={(v: boolean) => {
setIsButtonThin(v);
DataStore.set("useThinMenuButton", v);
FluxDispatcher.dispatch({
type: "COLORWAYS_UPDATE_BUTTON_HEIGHT" as FluxEvents,
isTall: v
});
}}
note="Replaces the icon on the colorways launcher button with text, making it more compact."
>
Use thin Quick Switch button
</Switch>
<Flex flexDirection="column" style={{ gap: 0 }}>
<h1 style={{
fontFamily: "var(--font-headline)",
fontSize: "24px",
color: "var(--header-primary)",
lineHeight: "31px",
marginBottom: "0"
}}>
Discord <span style={{
fontFamily: "var(--font-display)",
fontSize: "24px",
backgroundColor: "var(--brand-500)",
padding: "0 4px",
borderRadius: "4px"
}}>Colorways</span>
</h1>
<Text
variant="text-xs/normal"
style={{
color: "var(--text-normal)",
fontWeight: 500,
fontSize: "14px",
marginBottom: "12px"
}}
>by Project Colorway</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Plugin Version:
</Forms.FormTitle>
<Text
variant="text-xs/normal"
style={{
color: "var(--text-muted)",
fontWeight: 500,
fontSize: "14px",
marginBottom: "8px"
}}
>
{versionData.pluginVersion}
</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Creator Version:
</Forms.FormTitle>
<Text
variant="text-xs/normal"
style={{
color: "var(--text-muted)",
fontWeight: 500,
fontSize: "14px",
marginBottom: "8px"
}}
>
{versionData.creatorVersion}{" "}
(Stable)
</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Loaded Colorways:
</Forms.FormTitle>
<Text
variant="text-xs/normal"
style={{
color: "var(--text-muted)",
fontWeight: 500,
fontSize: "14px",
marginBottom: "8px"
}}
>
{[...colorways, ...customColorways].length}
</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Project Repositories:
</Forms.FormTitle>
<Forms.FormText style={{ marginBottom: "8px" }}>
<Link href="https://github.com/DaBluLite/DiscordColorways">DiscordColorways</Link>
<br />
<Link href="https://github.com/DaBluLite/ProjectColorway">Project Colorway</Link>
</Forms.FormText>
</Flex>
</div>
</SettingsTab>;
}

View file

@ -0,0 +1,17 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export default function ({ className }: { className?: string; }) {
return <div className={"colorwaysBtn-spinner" + (className ? " " + className : "")} role="img" aria-label="Loading">
<div className="colorwaysBtn-spinnerInner">
<svg className="colorwaysBtn-spinnerCircular" viewBox="25 25 50 50" fill="currentColor">
<circle className="colorwaysBtn-spinnerBeam colorwaysBtn-spinnerBeam3" cx="50" cy="50" r="20" />
<circle className="colorwaysBtn-spinnerBeam colorwaysBtn-spinnerBeam2" cx="50" cy="50" r="20" />
<circle className="colorwaysBtn-spinnerBeam" cx="50" cy="50" r="20" />
</svg>
</div>
</div>;
}

View file

@ -0,0 +1,209 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import {
Forms,
Text,
useState
} from "@webpack/common";
import { HexToHSL } from "../utils";
import { CloseIcon } from "./Icons";
export default function ({
accent,
primary,
secondary,
tertiary,
className,
isCollapsed,
previewCSS
}: {
accent: string,
primary: string,
secondary: string,
tertiary: string,
className?: string,
isCollapsed: boolean,
previewCSS?: string;
}) {
const [collapsed, setCollapsed] = useState<boolean>(isCollapsed);
function ThemePreview({
accent,
primary,
secondary,
tertiary,
isModal,
modalProps
}: {
accent: string,
primary: string,
secondary: string,
tertiary: string,
isModal?: boolean,
modalProps?: ModalProps;
}) {
return (
<div
className="colorwaysPreview-wrapper"
style={{ backgroundColor: tertiary }}
>
<div className="colorwaysPreview-titlebar" />
<div className="colorwaysPreview-body">
<div className="colorwayPreview-guilds">
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-guildItem"
style={{ backgroundColor: primary }}
onMouseEnter={e => e.currentTarget.style.backgroundColor = accent}
onMouseLeave={e => e.currentTarget.style.backgroundColor = primary}
onClick={() => {
if (isModal) {
modalProps?.onClose();
} else {
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
<style>
{previewCSS}
</style>
<ThemePreview accent={accent} primary={primary} secondary={secondary} tertiary={tertiary} isModal modalProps={props} />
</ModalRoot>);
}
}}
>
{isModal ? <CloseIcon style={{ color: "var(--header-secondary)" }} /> : <svg
aria-hidden="true"
role="img"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M19,3H14V5h5v5h2V5A2,2,0,0,0,19,3Z"
/>
<path
fill="currentColor"
d="M19,19H14v2h5a2,2,0,0,0,2-2V14H19Z"
/>
<path
fill="currentColor"
d="M3,5v5H5V5h5V3H5A2,2,0,0,0,3,5Z"
/>
<path
fill="currentColor"
d="M5,14H3v5a2,2,0,0,0,2,2h5V19H5Z"
/>
</svg>}
</div>
</div>
<div className="colorwayPreview-guild">
<div className="colorwayPreview-guildSeparator" style={{ backgroundColor: primary }} />
</div>
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-guildItem"
style={{ backgroundColor: primary }}
onMouseEnter={e => { e.currentTarget.style.backgroundColor = accent; }}
onMouseLeave={e => { e.currentTarget.style.backgroundColor = primary; }}
/>
</div>
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-guildItem"
style={{ backgroundColor: primary }}
onMouseEnter={e => { e.currentTarget.style.backgroundColor = accent; }}
onMouseLeave={e => { e.currentTarget.style.backgroundColor = primary; }}
/>
</div>
</div>
<div className="colorwayPreview-channels" style={{ backgroundColor: secondary }}>
<div
className="colorwayPreview-userArea"
style={{
backgroundColor: "hsl(" + HexToHSL(secondary)[0] + " " + HexToHSL(secondary)[1] + "% " + Math.max(HexToHSL(secondary)[2] - 3.6, 0) + "%)"
}}
/>
<div className="colorwayPreview-filler" />
<div
className="colorwayPreview-topShadow"
style={{
"--primary-900-hsl": `${HexToHSL(tertiary)[0]} ${HexToHSL(tertiary)[1]}% ${Math.max(HexToHSL(tertiary)[2] - (3.6 * 6), 0)}%`,
"--primary-500-hsl": `${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + (3.6 * 3), 100)}%`
} as React.CSSProperties}
>
<Text
tag="div"
variant="text-md/semibold"
lineClamp={1}
selectable={false}
>
Preview
</Text>
</div>
</div>
<div className="colorwayPreview-chat" style={{ backgroundColor: primary }}>
<div
className="colorwayPreview-chatBox"
style={{
backgroundColor: "hsl(" + HexToHSL(primary)[0] + " " + HexToHSL(primary)[1] + "% " + Math.min(HexToHSL(primary)[2] + 3.6, 100) + "%)"
}}
/>
<div className="colorwayPreview-filler" />
<div
className="colorwayPreview-topShadow"
style={{
"--primary-900-hsl": `${HexToHSL(tertiary)[0]} ${HexToHSL(tertiary)[1]}% ${Math.max(HexToHSL(tertiary)[2] - (3.6 * 6), 0)}%`
} as React.CSSProperties}
/>
</div>
</div>
</div>
);
}
return (
<div className={`${collapsed ? "colorwaysPreview colorwaysPreview-collapsed" : "colorwaysPreview"} ${className || ""}`}>
<div
className="colorwaysCreator-settingItm colorwaysCreator-settingHeader"
onClick={() => setCollapsed(!collapsed)}
>
<Forms.FormTitle
style={{ marginBottom: 0 }}
>
Preview
</Forms.FormTitle>
<svg
className="expand-3Nh1P5 transition-30IQBn directionDown-2w0MZz"
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden="true"
role="img"
>
<path
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
d="M7 10L12 15 17 10"
aria-hidden="true"
/>
</svg>
</div>
<style>
{previewCSS}
</style>
<ThemePreview
accent={accent}
primary={primary}
secondary={secondary}
tertiary={tertiary}
/>
</div>
);
}

View file

@ -0,0 +1,317 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export const defaultColorwaySource = "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json";
export const knownColorwaySources = [
{
name: "Project Colorway",
url: "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json"
},
{
name: "DaBluLite's Personal Colorways",
url: "https://raw.githubusercontent.com/DaBluLite/dablulite.github.io/master/colorways/index.json"
}
];
export const fallbackColorways = [
{
name: "Keyboard Purple",
original: false,
accent: "hsl(235 85.6% 64.7%)",
primary: "#222456",
secondary: "#1c1f48",
tertiary: "#080d1d",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/KeyboardPurple/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Eclipse",
original: false,
accent: "hsl(87 85.6% 64.7%)",
primary: "#000000",
secondary: "#181818",
tertiary: "#0a0a0a",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Eclipse/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Cyan",
original: false,
accent: "#009f88",
primary: "#202226",
secondary: "#1c1e21",
tertiary: "#141517",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Cyan/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Spotify",
original: false,
accent: "hsl(141 76% 48%)",
primary: "#121212",
secondary: "#090909",
tertiary: "#090909",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Spotify/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Bright n' Blue",
original: true,
accent: "hsl(234, 68%, 33%)",
primary: "#394aae",
secondary: "#29379d",
tertiary: "#1b278d",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/BrightBlue/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Still Young",
original: true,
accent: "hsl(58 85.6% 89%)",
primary: "#443a31",
secondary: "#7c3d3e",
tertiary: "#207578",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/StillYoung/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Sea",
original: true,
accent: "hsl(184, 100%, 50%)",
primary: "#07353b",
secondary: "#0b5e60",
tertiary: "#08201d",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Sea/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Lava",
original: true,
accent: "hsl(4, 80.4%, 32%)",
primary: "#401b17",
secondary: "#351917",
tertiary: "#230b0b",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Lava/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Solid Pink",
original: true,
accent: "hsl(340, 55.2%, 56.3%)",
primary: "#1e151c",
secondary: "#21181f",
tertiary: "#291e27",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/SolidPink/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Sand",
original: true,
accent: "hsl(41, 31%, 45%)",
primary: "#7f6c43",
secondary: "#665b33",
tertiary: "#5c5733",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Sand/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "AMOLED",
original: true,
accent: "hsl(235 85.6% 64.7%)",
primary: "#000000",
secondary: "#000000",
tertiary: "#000000",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Amoled/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Zorin",
original: false,
accent: "hsl(200, 89%, 86%)",
primary: "#171d20",
secondary: "#171d20",
tertiary: "#1e2529",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Zorin/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Desaturated",
original: false,
accent: "hsl(227, 58%, 65%)",
primary: "#35383d",
secondary: "#2c2f34",
tertiary: "#1e1f24",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Desaturated/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Crimson",
original: false,
accent: "hsl(0, 100%, 50%)",
primary: "#050000",
secondary: "#0a0000",
tertiary: "#0f0000",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Crimson/import.css);",
author: "Riddim_GLiTCH",
authorID: "801089753038061669",
},
{
name: "Jupiter",
original: true,
accent: "#ffd89b",
primary: "#ffd89b",
secondary: "#19547b",
tertiary: "#1e1f22",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Jupiter/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
isGradient: true,
colors: ["accent", "primary", "secondary"],
},
{
name: "Neon Candy",
original: true,
accent: "#FC00FF",
primary: "#00DBDE",
secondary: "#00DBDE",
tertiary: "#00DBDE",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/NeonCandy/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
isGradient: true,
colors: ["accent", "primary"],
},
{
name: "Wildberry",
original: false,
accent: "#f40172",
primary: "#180029",
secondary: "#340057",
tertiary: "#4b007a",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Wildberry/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Facebook",
original: false,
accent: "#2375e1",
primary: "#18191a",
secondary: "#242526",
tertiary: "#3a3b3c",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Facebook/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Material You",
original: false,
accent: "#004977",
primary: "#1f1f1f",
secondary: "#28292a",
tertiary: "#2d2f31",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/MaterialYou/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "Discord Teal",
original: false,
accent: "#175f6d",
primary: "#313338",
secondary: "#2b2d31",
tertiary: "#1e1f22",
"dc-import": "@import url(//dablulite.github.io/css-snippets/DiscordTeal/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
colors: ["accent"],
},
{
name: "黄昏の花 (Twilight Blossom)",
original: true,
accent: "#e100ff",
primary: "#04000a",
secondary: "#0b0024",
tertiary: "#210042",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/TwilightBlossom/import.css);",
author: "Riddim_GLiTCH",
authorID: "801089753038061669",
},
{
name: "Chai",
original: true,
accent: "#59cd51",
primary: "#1c1e15",
secondary: "#1e2118",
tertiary: "#24291e",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Chai/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
{
name: "CS1.6",
original: false,
accent: "#929a8d",
primary: "#3f4738",
secondary: "#5b6c51",
tertiary: "#4d5945",
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/CS16/import.css);",
author: "DaBluLite",
authorID: "582170007505731594",
},
];
export const knownThemeVars = {
"Cyan": {
variable: "--cyan-accent-color",
accent: "--cyan-accent-color",
primary: "--cyan-background-primary",
secondary: "--cyan-background-secondary"
},
"Virtual Boy": {
variable: "--VBaccent",
tertiary: "--VBaccent-muted",
alt: {
tertiary: "--VBaccent-dimmest"
}
},
"Modular": {
variable: "--modular-hue",
accentVariables: {
h: "--modular-hue",
s: "--modular-saturation",
l: "--modular-lightness"
}
},
"Solana": {
variable: "--accent-hue",
accentVariables: {
h: "--accent-hue",
s: "--accent-saturation",
l: "--accent-brightness"
},
primaryVariables: {
h: "--background-accent-hue",
s: "--background-accent-saturation",
l: "--background-accent-brightness"
}
}
};

View file

@ -0,0 +1,771 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Plugins } from "Vencord";
import { colorToHex, HexToHSL } from "./utils";
export const colorVariables: string[] = [
"brand-100",
"brand-130",
"brand-160",
"brand-200",
"brand-230",
"brand-260",
"brand-300",
"brand-330",
"brand-345",
"brand-360",
"brand-400",
"brand-430",
"brand-460",
"brand-500",
"brand-530",
"brand-560",
"brand-600",
"brand-630",
"brand-660",
"brand-700",
"brand-730",
"brand-760",
"brand-800",
"brand-830",
"brand-860",
"brand-900",
"primary-900",
"primary-860",
"primary-830",
"primary-800",
"primary-760",
"primary-730",
"primary-700",
"primary-660",
"primary-645",
"primary-630",
"primary-600",
"primary-560",
"primary-530",
"primary-500",
"primary-460",
"primary-430",
"primary-400",
"primary-360",
"primary-330",
"primary-300",
"primary-260",
"primary-230",
"primary-200",
"primary-160",
"primary-130",
"primary-100",
"white-900",
"white-860",
"white-830",
"white-800",
"white-760",
"white-730",
"white-700",
"white-660",
"white-630",
"white-600",
"white-560",
"white-530",
"white-500",
"white-460",
"white-430",
"white-400",
"white-360",
"white-330",
"white-300",
"white-260",
"white-230",
"white-200",
"white-160",
"white-130",
"white-100",
"teal-900",
"teal-860",
"teal-830",
"teal-800",
"teal-760",
"teal-730",
"teal-700",
"teal-660",
"teal-630",
"teal-600",
"teal-560",
"teal-530",
"teal-500",
"teal-460",
"teal-430",
"teal-400",
"teal-360",
"teal-330",
"teal-300",
"teal-260",
"teal-230",
"teal-200",
"teal-160",
"teal-130",
"teal-100",
"black-900",
"black-860",
"black-830",
"black-800",
"black-760",
"black-730",
"black-700",
"black-660",
"black-630",
"black-600",
"black-560",
"black-530",
"black-500",
"black-460",
"black-430",
"black-400",
"black-360",
"black-330",
"black-300",
"black-260",
"black-230",
"black-200",
"black-160",
"black-130",
"black-100",
"red-900",
"red-860",
"red-830",
"red-800",
"red-760",
"red-730",
"red-700",
"red-660",
"red-630",
"red-600",
"red-560",
"red-530",
"red-500",
"red-460",
"red-430",
"red-400",
"red-360",
"red-330",
"red-300",
"red-260",
"red-230",
"red-200",
"red-160",
"red-130",
"red-100",
"yellow-900",
"yellow-860",
"yellow-830",
"yellow-800",
"yellow-760",
"yellow-730",
"yellow-700",
"yellow-660",
"yellow-630",
"yellow-600",
"yellow-560",
"yellow-530",
"yellow-500",
"yellow-460",
"yellow-430",
"yellow-400",
"yellow-360",
"yellow-330",
"yellow-300",
"yellow-260",
"yellow-230",
"yellow-200",
"yellow-160",
"yellow-130",
"yellow-100",
"green-900",
"green-860",
"green-830",
"green-800",
"green-760",
"green-730",
"green-700",
"green-660",
"green-630",
"green-600",
"green-560",
"green-530",
"green-500",
"green-460",
"green-430",
"green-400",
"green-360",
"green-330",
"green-300",
"green-260",
"green-230",
"green-200",
"green-160",
"green-130",
"green-100",
];
const PrimarySatDiffs = {
130: 63.9594,
160: 49.4382,
200: 37.5758,
230: 30.3797,
260: 22.5166,
300: 32.5,
330: 27.0968,
345: 22.5166,
360: 18.9189,
400: -14.4,
430: -33.0435,
460: 25.2101,
500: -11.0236,
530: -3.0303,
645: 7.40741,
660: 3.0303,
730: 11.9403,
800: 25,
};
const BrandSatDiffs = {
100: -9.54712,
130: 2.19526,
160: -1.17509,
200: -2.72351,
230: 1.62225,
260: 0.698487,
300: 0.582411,
330: -0.585823,
345: -0.468384,
360: 0.582411,
400: 0.582411,
430: 0.116754,
460: -0.116891,
530: -24.8194,
560: -49.927,
600: -58.8057,
630: -58.8057,
660: -58.0256,
700: -58.2202,
730: -58.6103,
760: -58.4151,
800: -57.2502,
830: -57.4436,
860: -58.4151,
900: -52.5074
};
const BrandLightDiffs = {
100: 33.5,
130: 32.2,
160: 30.2,
200: 28.2,
230: 26.2999,
260: 23.8999,
300: 21.2,
330: 16.8999,
345: 14.0999,
360: 12.7999,
400: 7.0999,
430: 5.0999,
460: 2.7999,
530: -5.9,
560: -12.3,
600: -20.6,
630: -26.5,
660: -31.4,
700: -38.8,
730: -40.4,
760: -42.5,
800: -45.3,
830: -49.8,
860: -55.1,
900: -61.6
};
function gradientBase(accentColor?: string, discordSaturation = false) {
return `@import url(//dablulite.github.io/css-snippets/NoLightInDark/import.css);
@import url(//dablulite.github.io/css-snippets/NitroThemesFix/import.css);
.theme-dark {
--bg-overlay-color: 0 0 0;
--bg-overlay-color-inverse: 255 255 255;
--bg-overlay-opacity-1: 0.85;
--bg-overlay-opacity-2: 0.8;
--bg-overlay-opacity-3: 0.7;
--bg-overlay-opacity-4: 0.5;
--bg-overlay-opacity-5: 0.4;
--bg-overlay-opacity-6: 0.1;
--bg-overlay-opacity-hover: 0.5;
--bg-overlay-opacity-hover-inverse: 0.08;
--bg-overlay-opacity-active: 0.45;
--bg-overlay-opacity-active-inverse: 0.1;
--bg-overlay-opacity-selected: 0.4;
--bg-overlay-opacity-selected-inverse: 0.15;
--bg-overlay-opacity-chat: 0.8;
--bg-overlay-opacity-home: 0.85;
--bg-overlay-opacity-home-card: 0.8;
--bg-overlay-opacity-app-frame: var(--bg-overlay-opacity-4);
}
.theme-light {
--bg-overlay-color: 255 255 255;
--bg-overlay-color-inverse: 0 0 0;
--bg-overlay-opacity-1: 0.9;
--bg-overlay-opacity-2: 0.8;
--bg-overlay-opacity-3: 0.7;
--bg-overlay-opacity-4: 0.6;
--bg-overlay-opacity-5: 0.3;
--bg-overlay-opacity-6: 0.15;
--bg-overlay-opacity-hover: 0.7;
--bg-overlay-opacity-hover-inverse: 0.02;
--bg-overlay-opacity-active: 0.65;
--bg-overlay-opacity-active-inverse: 0.03;
--bg-overlay-opacity-selected: 0.6;
--bg-overlay-opacity-selected-inverse: 0.04;
--bg-overlay-opacity-chat: 0.9;
--bg-overlay-opacity-home: 0.7;
--bg-overlay-opacity-home-card: 0.9;
--bg-overlay-opacity-app-frame: var(--bg-overlay-opacity-5);
}
.children_cde9af:after, .form_d8a4a1:before {
content: none;
}
.scroller_de945b {
background: var(--bg-overlay-app-frame,var(--background-tertiary));
}
.expandedFolderBackground_b1385f {
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
}
.wrapper__8436d:not(:hover):not(.selected_ae80f7) .childWrapper_a6ce15 {
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
}
.folder__17546:has(.expandedFolderIconWrapper__324c1) {
background: var(--bg-overlay-6,var(--background-secondary));
}
.circleIconButton__05cf2:not(.selected_aded59) {
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
}
.auto_a3c0bd::-webkit-scrollbar-thumb,
.thin_b1c063::-webkit-scrollbar-thumb {
background-size: 200vh;
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),to(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)))),var(--custom-theme-background);
background-image: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),var(--custom-theme-background);
}
.auto_a3c0bd::-webkit-scrollbar-track {
background-size: 200vh;
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color)/.4)),to(rgb(var(--bg-overlay-color)/.4))),var(--custom-theme-background);
background-image: linear-gradient(rgb(var(--bg-overlay-color)/.4),rgb(var(--bg-overlay-color)/.4)),var(--custom-theme-background);
}
:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[100])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[100]) * 10) / 10, 0)};
--brand-130-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[130])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[130]) * 10) / 10, 0)}%;
--brand-160-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[160])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[160]) * 10) / 10, 0)}%;
--brand-200-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[200])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[200]) * 10) / 10, 0)}%;
--brand-230-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[230])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[230]) * 10) / 10, 0)}%;
--brand-260-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[260])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[260]) * 10) / 10, 0)}%;
--brand-300-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[300])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[300]) * 10) / 10, 0)}%;
--brand-330-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[330])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[330]) * 10) / 10, 0)}%;
--brand-345-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[345])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[345]) * 10) / 10, 0)}%;
--brand-360-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[360])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[360]) * 10) / 10, 0)}%;
--brand-400-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[400])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[400]) * 10) / 10, 0)}%;
--brand-430-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[430])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[430]) * 10) / 10, 0)}%;
--brand-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[460])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[460]) * 10) / 10, 0)}%;
--brand-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
--brand-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[530])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[530]) * 10) / 10, 100)}%;
--brand-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[560])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[560]) * 10) / 10, 100)}%;
--brand-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[600])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[600]) * 10) / 10, 100)}%;
--brand-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[630])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[630]) * 10) / 10, 100)}%;
--brand-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[660])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[660]) * 10) / 10, 100)}%;
--brand-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[700])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[700]) * 10) / 10, 100)}%;
--brand-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[730])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[730]) * 10) / 10, 100)}%;
--brand-760-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[760])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[760]) * 10) / 10, 100)}%;
--brand-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[800])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[800]) * 10) / 10, 100)}%;
--brand-830-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[830])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[830]) * 10) / 10, 100)}%;
--brand-860-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[860])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[860]) * 10) / 10, 100)}%;
--brand-900-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[900])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[900]) * 10) / 10, 100)}%;
--bg-overlay-1: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-1)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-1))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-2: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-2)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-2))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-3: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-3)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-3))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-4: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-4)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-4))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-5: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-5)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-5))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-6: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-hover: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-hover-inverse)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-hover-inverse))) fixed 0 0/cover,linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-hover)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-hover))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-active: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-active-inverse)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-active-inverse))) fixed 0 0/cover,linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-active)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-active))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-selected: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-selected-inverse)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-selected-inverse))) fixed 0 0/cover,linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-selected)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-selected))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-chat: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-chat)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-chat))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-home: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-home)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-home))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-home-card: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-home-card)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-home-card))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;
--bg-overlay-app-frame: linear-gradient(rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-app-frame)),rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-app-frame))) fixed 0 0/cover,var(--custom-theme-background) fixed 0 0/cover;`;
}
export function generateCss(primaryColor: string, secondaryColor: string, tertiaryColor: string, accentColor: string, tintedText: boolean, discordSaturation: boolean) {
primaryColor = colorToHex(primaryColor);
secondaryColor = colorToHex(secondaryColor);
tertiaryColor = colorToHex(tertiaryColor);
accentColor = colorToHex(accentColor);
const colorwayCss = `/*Automatically Generated - Colorway Creator V${(Plugins.plugins.DiscordColorways as any).creatorVersion}*/
:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[100])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[100]) * 10) / 10, 0)};
--brand-130-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[130])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[130]) * 10) / 10, 0)}%;
--brand-160-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[160])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[160]) * 10) / 10, 0)}%;
--brand-200-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[200])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[200]) * 10) / 10, 0)}%;
--brand-230-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[230])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[230]) * 10) / 10, 0)}%;
--brand-260-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[260])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[260]) * 10) / 10, 0)}%;
--brand-300-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[300])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[300]) * 10) / 10, 0)}%;
--brand-330-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[330])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[330]) * 10) / 10, 0)}%;
--brand-345-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[345])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[345]) * 10) / 10, 0)}%;
--brand-360-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[360])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[360]) * 10) / 10, 0)}%;
--brand-400-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[400])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[400]) * 10) / 10, 0)}%;
--brand-430-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[430])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[430]) * 10) / 10, 0)}%;
--brand-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[460])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[460]) * 10) / 10, 0)}%;
--brand-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
--brand-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[530])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[530]) * 10) / 10, 100)}%;
--brand-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[560])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[560]) * 10) / 10, 100)}%;
--brand-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[600])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[600]) * 10) / 10, 100)}%;
--brand-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[630])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[630]) * 10) / 10, 100)}%;
--brand-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[660])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[660]) * 10) / 10, 100)}%;
--brand-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[700])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[700]) * 10) / 10, 100)}%;
--brand-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[730])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[730]) * 10) / 10, 100)}%;
--brand-760-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[760])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[760]) * 10) / 10, 100)}%;
--brand-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[800])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[800]) * 10) / 10, 100)}%;
--brand-830-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[830])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[830]) * 10) / 10, 100)}%;
--brand-860-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[860])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[860]) * 10) / 10, 100)}%;
--brand-900-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[900])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[900]) * 10) / 10, 100)}%;
--primary-800-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + tertiaryColor)[1] / 100) * (100 + PrimarySatDiffs[800])) * 10) / 10 : HexToHSL("#" + tertiaryColor)[1]}%) ${Math.max(HexToHSL("#" + tertiaryColor)[2] - (3.6 * 2), 0)}%;
--primary-730-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + tertiaryColor)[1] / 100) * (100 + PrimarySatDiffs[730])) * 10) / 10 : HexToHSL("#" + tertiaryColor)[1]}%) ${Math.max(HexToHSL("#" + tertiaryColor)[2] - 3.6, 0)}%;
--primary-700-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${HexToHSL("#" + tertiaryColor)[2]}%;
--primary-660-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + secondaryColor)[1] / 100) * (100 + PrimarySatDiffs[660])) * 10) / 10 : HexToHSL("#" + secondaryColor)[1]}%) ${Math.max(HexToHSL("#" + secondaryColor)[2] - 3.6, 0)}%;
--primary-645-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + secondaryColor)[1] / 100) * (100 + PrimarySatDiffs[645])) * 10) / 10 : HexToHSL("#" + secondaryColor)[1]}%) ${Math.max(HexToHSL("#" + secondaryColor)[2] - 1.1, 0)}%;
--primary-630-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + secondaryColor)[1]}%) ${HexToHSL("#" + secondaryColor)[2]}%;
--primary-600-hsl: ${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${HexToHSL("#" + primaryColor)[2]}%;
--primary-560-hsl: ${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${Math.min(HexToHSL("#" + primaryColor)[2] + 3.6, 100)}%;
--primary-530-hsl: ${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + primaryColor)[1] / 100) * (100 + PrimarySatDiffs[530])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%) ${Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 2), 100)}%;
--primary-500-hsl: ${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + primaryColor)[1] / 100) * (100 + PrimarySatDiffs[500])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%) ${Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 100)}%;${tintedText ? `\n --primary-460-hsl: 0 calc(var(--saturation-factor, 1)*0%) 50%;
--primary-430: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + `, calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + primaryColor)[1] / 100) * (100 + PrimarySatDiffs[430])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%), 90%)` : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")};
--primary-400: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + `, calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + primaryColor)[1] / 100) * (100 + PrimarySatDiffs[400])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%), 90%)` : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")};
--primary-360: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + `, calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + primaryColor)[1] / 100) * (100 + PrimarySatDiffs[360])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%), 90%)` : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")};` : ""}
}
.emptyPage_feb902,
.scrollerContainer_dda72c,
.container__03ec9,
.header__71942 {
background-color: unset !important;
}${(Math.round(HexToHSL("#" + primaryColor)[2]) > 80) ? `\n\n/*Primary*/
.theme-dark .container_bd15da,
.theme-dark .body__616e6,
.theme-dark .toolbar__62fb5,
.theme-dark .container_e1387b,
.theme-dark .messageContent_abea64,
.theme-dark .attachButtonPlus_fd0021,
.theme-dark .username__0b0e7:not([style]),
.theme-dark .children_cde9af,
.theme-dark .buttonContainer__6de7e,
.theme-dark .listItem__48528,
.theme-dark .body__616e6 .caret__33d19,
.theme-dark .body__616e6 .titleWrapper_d6133e > h1,
.theme-dark .body__616e6 .icon_ae0b42 {
--white-500: black !important;
--interactive-normal: black !important;
--text-normal: black !important;
--text-muted: black !important;
--header-primary: black !important;
--header-secondary: black !important;
}
.theme-dark .contentRegionScroller__9ae20 :not(.mtk1,.mtk2,.mtk3,.mtk4,.mtk5,.mtk6,.mtk7,.mtk8,.mtk9,.monaco-editor .line-numbers) {
--white-500: black !important;
}
.theme-dark .container__6b2e5,
.theme-dark .container__03ec9,
.theme-dark .header__71942 {
background: transparent;
}
.theme-dark .container__26baa {
--channel-icon: black;
}
.theme-dark .callContainer__1477d {
--white-500: ${(HexToHSL("#" + tertiaryColor)[2] > 80) ? "black" : "white"} !important;
}
.theme-dark .channelTextArea_c2094b {
--text-normal: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
}
.theme-dark .placeholder_dec8c7 {
--channel-text-area-placeholder: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
opacity: .6;
}
.theme-dark .colorwaySelectorIcon {
background-color: black;
}
.theme-dark .root_a28985 > .header__5e5a6 > h1 {
color: black;
}
/*End Primary*/`: ""}${(HexToHSL("#" + secondaryColor)[2] > 80) ? `\n\n/*Secondary*/
.theme-dark .wrapper__3c6d5 *,
.theme-dark .sidebar_e031be *:not(.hasBanner__04337 *),
.theme-dark .members__573eb *:not([style]),
.theme-dark .sidebarRegionScroller__8113e *,
.theme-dark .header__8e271,
.theme-dark .lookFilled__950dd.colorPrimary_ebe632 {
--white-500: black !important;
--channels-default: black !important;
--channel-icon: black !important;
--interactive-normal: var(--white-500);
--interactive-hover: var(--white-500);
--interactive-active: var(--white-500);
}
.theme-dark .channelRow__538ef {
background-color: var(--background-secondary);
}
.theme-dark .channelRow__538ef * {
--channel-icon: black;
}
.theme-dark #app-mount .activity_bafb94 {
--channels-default: var(--white-500) !important;
}
.theme-dark .nameTag__77ab2 {
--header-primary: black !important;
--header-secondary: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 90%)" : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")} !important;
}
.theme-dark .bannerVisible_ef30fe .headerContent__6fcc7 {
color: #fff;
}
.theme-dark .embedFull__14919 {
--text-normal: black;
}
/*End Secondary*/`: ""}${HexToHSL("#" + tertiaryColor)[2] > 80 ? `\n\n/*Tertiary*/
.theme-dark .winButton_f17fb6,
.theme-dark .searchBar__310d8 *,
.theme-dark .wordmarkWindows_ffbc5e,
.theme-dark .searchBar__5a20a *,
.theme-dark .searchBarComponent__8f95f {
--white-500: black !important;
}
.theme-dark [style="background-color: var(--background-secondary);"] {
color: ${HexToHSL("#" + secondaryColor)[2] > 80 ? "black" : "white"};
}
.theme-dark .popout__24e32 > * {
--interactive-normal: black !important;
--header-secondary: black !important;
}
.theme-dark .tooltip__7b090 {
--text-normal: black !important;
}
.theme-dark .children_cde9af .icon_ae0b42 {
color: var(--interactive-active) !important;
}
/*End Tertiary*/`: ""}${HexToHSL("#" + accentColor)[2] > 80 ? `\n\n/*Accent*/
.selected_aded59 *,
.selected_ae80f7 *,
#app-mount .lookFilled__950dd.colorBrand__27d57:not(.buttonColor__7bad9),
.colorDefault_e361cf.focused_dcafb9,
.row__9e25f:hover,
.colorwayInfoIcon,
.checkmarkCircle_b1b1cc > circle {
--white-500: black !important;
}
.ColorwaySelectorBtn:hover .vc-pallete-icon {
color: #000 !important;
}
:root:root {
--mention-foreground: black !important;
}
/*End Accent*/`: ""}`;
return colorwayCss;
}
export function getPreset(primaryColor?: string, secondaryColor?: string, tertiaryColor?: string, accentColor?: string) {
function cyan(discordSaturation = false) {
return `:root:root {
--cyan-accent-color: ${"#" + accentColor};
--cyan-background-primary: hsl(${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${HexToHSL("#" + primaryColor)[2]}%/40%);
--cyan-background-secondary: hsl(${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min(HexToHSL("#" + tertiaryColor)[2] + (3.6 * 2), 100)}%);
}`;
}
function virtualBoy(discordSaturation = false) {
return `:root:root {
--VBaccent: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
--VBaccent-muted: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.max(((HexToHSL("#" + tertiaryColor)[2]) - 10), 0)}%;
--VBaccent-dimmest: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min((HexToHSL("#" + tertiaryColor)[2] + (3.6 * 5) - 3), 100)}%;
}`;
}
function modular(discordSaturation = false) {
return `:root:root {
--modular-hue: ${HexToHSL("#" + accentColor)[0]};
--modular-saturation: calc(var(--saturation-factor, 1)${HexToHSL("#" + accentColor)[1]}%);
--modular-lightness: ${HexToHSL("#" + accentColor)[2]}%;
}`;
}
function solana(discordSaturation = false) {
return `:root:root {
--accent-hue: ${HexToHSL("#" + accentColor)[0]};
--accent-saturation: calc(var(--saturation-factor, 1)${HexToHSL("#" + accentColor)[1]}%);
--accent-brightness: ${HexToHSL("#" + accentColor)[2]}%;
--background-accent-hue: ${HexToHSL("#" + primaryColor)[0]};
--background-accent-saturation: calc(var(--saturation-factor, 1)${HexToHSL("#" + primaryColor)[1]}%);
--background-accent-brightness: ${HexToHSL("#" + primaryColor)[2]}%;
--background-overlay-opacity: 0%;
}`;
}
function gradientType1(discordSaturation = false) {
return `${gradientBase(accentColor, discordSaturation)}
--custom-theme-background: linear-gradient(239.16deg, #${primaryColor} 10.39%, #${secondaryColor} 26.87%, #${tertiaryColor} 48.31%, hsl(${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + secondaryColor)[1]}%) ${Math.min(HexToHSL("#" + secondaryColor)[2] + 3.6, 100)}%) 64.98%, #${primaryColor} 92.5%);
}`;
}
function gradientType2(discordSaturation = false) {
return `${gradientBase(accentColor, discordSaturation)}
--custom-theme-background: linear-gradient(48.17deg, #${primaryColor} 11.21%, #${secondaryColor} 61.92%);
}`;
}
function hueRotation(discordSaturation = false) {
return `:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[100])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[100]) * 10) / 10, 0)};
--brand-130-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[130])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[130]) * 10) / 10, 0)}%;
--brand-160-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[160])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[160]) * 10) / 10, 0)}%;
--brand-200-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[200])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[200]) * 10) / 10, 0)}%;
--brand-230-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[230])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[230]) * 10) / 10, 0)}%;
--brand-260-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[260])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[260]) * 10) / 10, 0)}%;
--brand-300-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[300])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[300]) * 10) / 10, 0)}%;
--brand-330-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[330])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[330]) * 10) / 10, 0)}%;
--brand-345-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[345])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[345]) * 10) / 10, 0)}%;
--brand-360-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[360])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[360]) * 10) / 10, 0)}%;
--brand-400-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[400])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[400]) * 10) / 10, 0)}%;
--brand-430-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[430])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[430]) * 10) / 10, 0)}%;
--brand-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[460])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[460]) * 10) / 10, 0)}%;
--brand-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
--brand-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[530])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[530]) * 10) / 10, 100)}%;
--brand-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[560])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[560]) * 10) / 10, 100)}%;
--brand-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[600])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[600]) * 10) / 10, 100)}%;
--brand-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[630])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[630]) * 10) / 10, 100)}%;
--brand-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[660])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[660]) * 10) / 10, 100)}%;
--brand-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[700])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[700]) * 10) / 10, 100)}%;
--brand-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[730])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[730]) * 10) / 10, 100)}%;
--brand-760-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[760])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[760]) * 10) / 10, 100)}%;
--brand-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[800])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[800]) * 10) / 10, 100)}%;
--brand-830-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[830])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[830]) * 10) / 10, 100)}%;
--brand-860-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[860])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[860]) * 10) / 10, 100)}%;
--brand-900-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[900])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[900]) * 10) / 10, 100)}%;
--primary-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 7%;
--primary-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*10%) 13%;
--primary-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*10%) 13%;
--primary-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 15%;
--primary-645-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 16%;
--primary-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 18%;
--primary-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 21%;
--primary-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 24%;
--primary-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 24%;
--primary-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*11%) 24%;
}`;
}
function accentSwap(discordSaturation = false) {
return `:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[100])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[100]) * 10) / 10, 0)};
--brand-130-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[130])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[130]) * 10) / 10, 0)}%;
--brand-160-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[160])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[160]) * 10) / 10, 0)}%;
--brand-200-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[200])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[200]) * 10) / 10, 0)}%;
--brand-230-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[230])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[230]) * 10) / 10, 0)}%;
--brand-260-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[260])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[260]) * 10) / 10, 0)}%;
--brand-300-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[300])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[300]) * 10) / 10, 0)}%;
--brand-330-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[330])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[330]) * 10) / 10, 0)}%;
--brand-345-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[345])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[345]) * 10) / 10, 0)}%;
--brand-360-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[360])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[360]) * 10) / 10, 0)}%;
--brand-400-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[400])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[400]) * 10) / 10, 0)}%;
--brand-430-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[430])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[430]) * 10) / 10, 0)}%;
--brand-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[460])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.max(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[460]) * 10) / 10, 0)}%;
--brand-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
--brand-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[530])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[530]) * 10) / 10, 100)}%;
--brand-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[560])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[560]) * 10) / 10, 100)}%;
--brand-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[600])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[600]) * 10) / 10, 100)}%;
--brand-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[630])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[630]) * 10) / 10, 100)}%;
--brand-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[660])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[660]) * 10) / 10, 100)}%;
--brand-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[700])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[700]) * 10) / 10, 100)}%;
--brand-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[730])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[730]) * 10) / 10, 100)}%;
--brand-760-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[760])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[760]) * 10) / 10, 100)}%;
--brand-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[800])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[800]) * 10) / 10, 100)}%;
--brand-830-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[830])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[830]) * 10) / 10, 100)}%;
--brand-860-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[860])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[860]) * 10) / 10, 100)}%;
--brand-900-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + accentColor)[1] / 100) * (100 + BrandSatDiffs[900])) * 10) / 10 : HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[900]) * 10) / 10, 100)}%;
}`;
}
return {
cyan: {
name: "Cyan",
preset: cyan,
id: "cyan",
colors: ["primary", "secondary", "accent"]
},
virtualBoy: {
name: "Virtual Boy",
preset: virtualBoy,
id: "virtualBoy",
colors: ["tertiary", "accent"]
},
modular: {
name: "Modular",
preset: modular,
id: "modular",
colors: ["accent"]
},
solana: {
name: "Solana",
preset: solana,
id: "solana",
colors: ["primary", "accent"]
},
gradientType1: {
name: "Gradient Type 1",
preset: gradientType1,
id: "gradientType1",
colors: ["primary", "secondary", "tertiary", "accent"]
},
gradientType2: {
name: "Gradient Type 2",
preset: gradientType2,
id: "gradientType2",
colors: ["primary", "secondary", "accent"]
},
hueRotation: {
name: "Hue Rotation",
preset: hueRotation,
id: "hueRotation",
colors: ["accent"]
},
accentSwap: {
name: "Accent Swap",
preset: accentSwap,
id: "accentSwap",
colors: ["accent"]
}
};
}

View file

@ -0,0 +1,222 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs, EquicordDevs } from "@utils/constants";
import { openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import {
Button,
SettingsRouter,
} from "@webpack/common";
import ColorPickerModal from "./components/ColorPicker";
import ColorwaysButton from "./components/ColorwaysButton";
import CreatorModal from "./components/CreatorModal";
import SelectorModal from "./components/SelectorModal";
import ManageColorwaysPage from "./components/SettingsTabs/ManageColorwaysPage";
import OnDemandWaysPage from "./components/SettingsTabs/OnDemandPage";
import SelectorPage from "./components/SettingsTabs/SelectorPage";
import SettingsPage from "./components/SettingsTabs/SettingsPage";
import Spinner from "./components/Spinner";
import { defaultColorwaySource } from "./constants";
import style from "./style.css?managed";
import { ColorPickerProps } from "./types";
export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
return <Spinner className="colorways-creator-module-warning" />;
};
(async function () {
const [
customColorways,
colorwaySourcesFiles,
showColorwaysButton,
onDemandWays,
onDemandWaysTintedText,
useThinMenuButton,
onDemandWaysDiscordSaturation,
onDemandWaysColorArray
] = await DataStore.getMany([
"customColorways",
"colorwaySourceFiles",
"showColorwaysButton",
"onDemandWays",
"onDemandWaysTintedText",
"useThinMenuButton",
"onDemandWaysDiscordSaturation",
"onDemandWaysColorArray"
]);
if (!customColorways)
DataStore.set("customColorways", []);
if (!colorwaySourcesFiles)
DataStore.set("colorwaySourceFiles", [defaultColorwaySource]);
if (!showColorwaysButton)
DataStore.set("showColorwaysButton", false);
if (!onDemandWays)
DataStore.set("onDemandWays", false);
if (!onDemandWaysTintedText)
DataStore.set("onDemandWaysTintedText", true);
if (!useThinMenuButton)
DataStore.set("useThinMenuButton", false);
if (!onDemandWaysDiscordSaturation)
DataStore.set("onDemandWaysDiscordSaturation", false);
if (!onDemandWaysColorArray)
DataStore.set("onDemandWaysColorArray", ["313338", "2b2d31", "1e1f22", "5865f2"]);
})();
export const ColorwayCSS = {
get: () => document.getElementById("activeColorwayCSS")?.textContent || "",
set: (e: string) => {
if (!document.getElementById("activeColorwayCSS")) {
var activeColorwayCSS: HTMLStyleElement =
document.createElement("style");
activeColorwayCSS.id = "activeColorwayCSS";
activeColorwayCSS.textContent = e;
document.head.append(activeColorwayCSS);
} else document.getElementById("activeColorwayCSS")!.textContent = e;
},
remove: () => document.getElementById("activeColorwayCSS")!.remove(),
};
export const versionData = {
pluginVersion: "5.6.3",
creatorVersion: "1.18.1",
};
export default definePlugin({
name: "DiscordColorways",
description:
"A plugin that offers easy access to simple color schemes/themes for Discord, also known as Colorways",
authors: [EquicordDevs.DaBluLite, Devs.ImLvna],
dependencies: ["ServerListAPI", "MessageAccessoriesAPI"],
pluginVersion: versionData.pluginVersion,
creatorVersion: versionData.creatorVersion,
toolboxActions: {
"Change Colorway": () => openModal(props => <SelectorModal modalProps={props} />),
"Open Colorway Creator": () => openModal(props => <CreatorModal modalProps={props} />),
"Open Color Stealer": () => openModal(props => <ColorPickerModal modalProps={props} />),
"Open Settings": () => SettingsRouter.open("ColorwaysSettings"),
"Open On-Demand Settings": () => SettingsRouter.open("ColorwaysOnDemand"),
"Manage Colorways...": () => SettingsRouter.open("ColorwaysManagement"),
},
patches: [
// Credits to Kyuuhachi for the BetterSettings plugin patches
{
find: "this.renderArtisanalHack()",
replacement: {
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g,
replace: "$&,_:$1",
predicate: () => true
}
},
{
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
replacement: {
match: /(?<=(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,)(?=\1\(this)/,
replace: "(async ()=>$2)(),"
},
predicate: () => true
},
{
find: "colorPickerFooter:",
replacement: {
match: /function (\i).{0,200}colorPickerFooter:/,
replace: "$self.ColorPicker=$1;$&",
},
},
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
match: /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/,
replace: "...$self.makeSettingsCategories($1),$&"
}
}
],
set ColorPicker(e) {
ColorPicker = e;
},
makeSettingsCategories(SectionTypes: Record<string, unknown>) {
console.log(SectionTypes);
return [
{
section: SectionTypes.HEADER,
label: "DiscordColorways",
className: "vc-settings-header"
},
{
section: "ColorwaysSelector",
label: "Colorways",
element: SelectorPage,
className: "dc-colorway-selector"
},
{
section: "ColorwaysSettings",
label: "Settings",
element: SettingsPage,
className: "dc-colorway-settings"
},
{
section: "ColorwaysOnDemand",
label: "On-Demand",
element: OnDemandWaysPage,
className: "dc-colorway-ondemand"
},
{
section: "ColorwaysManagement",
label: "Manage...",
element: ManageColorwaysPage,
className: "dc-colorway-management"
},
{
section: SectionTypes.DIVIDER
}
].filter(Boolean);
},
ColorwaysButton: () => <ColorwaysButton />,
async start() {
addServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);
enableStyle(style);
ColorwayCSS.set((await DataStore.get("actveColorway")) || "");
addAccessory("colorways-btn", props => {
if (String(props.message.content).match(/colorway:[0-9a-f]{0,71}/))
return <Button onClick={() => {
openModal(propss => (
<CreatorModal
modalProps={propss}
colorwayID={String(props.message.content).match(/colorway:[0-9a-f]{0,71}/)![0]}
/>
));
}} size={Button.Sizes.SMALL} color={Button.Colors.PRIMARY}>Add this Colorway...</Button>;
return null;
});
},
stop() {
removeServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);
disableStyle(style);
ColorwayCSS.remove();
removeAccessory("colorways-btn");
},
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export interface Colorway {
name: string,
"dc-import": string,
accent: string,
primary: string,
secondary: string,
tertiary: string,
original?: boolean,
author: string,
authorID: string,
colors?: string[],
isGradient?: boolean,
sourceUrl?: string,
sourceName?: string;
}
export interface ColorPickerProps {
color: number;
showEyeDropper: boolean;
suggestedColors: string[];
label: any;
onChange(color: number): void;
}

View file

@ -0,0 +1,150 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export function HexToHSL(H: string) {
let r: any = 0, g: any = 0, b: any = 0;
if (H.length === 4) r = "0x" + H[1] + H[1], g = "0x" + H[2] + H[2], b = "0x" + H[3] + H[3];
else if (H.length === 7) {
r = "0x" + H[1] + H[2];
g = "0x" + H[3] + H[4];
b = "0x" + H[5] + H[6];
}
r /= 255, g /= 255, b /= 255;
var cmin = Math.min(r, g, b),
cmax = Math.max(r, g, b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
if (delta === 0) h = 0;
else if (cmax === r) h = ((g - b) / delta) % 6;
else if (cmax === g) h = (b - r) / delta + 2;
else h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0) h += 360;
l = (cmax + cmin) / 2;
s = delta === 0
? 0
: delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return [Math.round(h), Math.round(s), Math.round(l)];
}
export const canonicalizeHex = (hex: string) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = hex;
hex = ctx.fillStyle;
canvas.remove();
return hex;
};
export const stringToHex = (str: string) => {
let hex = "";
for (
let i = 0;
i < str.length;
i++
) {
const charCode = str.charCodeAt(i);
const hexValue = charCode.toString(16);
hex += hexValue.padStart(2, "0");
}
return hex;
};
export const hexToString = (hex: string) => {
let str = "";
for (let i = 0; i < hex.length; i += 2) {
const hexValue = hex.substr(i, 2);
const decimalValue = parseInt(hexValue, 16);
str += String.fromCharCode(decimalValue);
}
return str;
};
export function getHex(str: string): string {
const color = Object.assign(
document.createElement("canvas").getContext("2d") as {},
{ fillStyle: str }
).fillStyle;
if (color.includes("rgba(")) {
return getHex(String([...color.split(",").slice(0, 3), ")"]).replace(",)", ")").replace("a", ""));
} else {
return color;
}
}
export function getFontOnBg(bgColor: string) {
var color = (bgColor.charAt(0) === "#") ? bgColor.substring(1, 7) : bgColor;
var r = parseInt(color.substring(0, 2), 16);
var g = parseInt(color.substring(2, 4), 16);
var b = parseInt(color.substring(4, 6), 16);
return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
"#000000" : "#ffffff";
}
export function $e(funcArray: Array<(...vars: any) => void>, ...vars: any[]) {
funcArray.forEach(e => e(vars));
}
export function hslToHex(h: number, s: number, l: number) {
h /= 360;
s /= 100;
l /= 100;
let r: any, g: any, b: any;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = (x: number) => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
export function rgbToHex(r: number, g: number, b: number) {
const toHex = (x: number) => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
export function colorToHex(color: string) {
var colorType = "hex";
if (color.includes("hsl")) {
colorType = "hsl";
} else if (color.includes("rgb")) {
colorType = "rgb";
}
color = color.replaceAll(",", "").replace(/.+?\(/, "").replace(")", "").replaceAll(/[ \t]+\/[ \t]+/g, " ").replaceAll("%", "").replaceAll("/", "");
if (colorType === "hsl") {
color = hslToHex(Number(color.split(" ")[0]), Number(color.split(" ")[1]), Number(color.split(" ")[2]));
}
if (colorType === "rgb") {
color = rgbToHex(Number(color.split(" ")[0]), Number(color.split(" ")[1]), Number(color.split(" ")[2]));
}
return color.replace("#", "");
}

View file

@ -0,0 +1,33 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { PermissionStore } from "@webpack/common";
export default definePlugin({
name: "GodMode",
description: "Get all permissions (client-side).",
authors: [EquicordDevs.Tolgchu],
start: () => {
["can", "canAccessMemberSafetyPage", "canAccessGuildSettings", "canBasicChannel", "canImpersonateRole", "canManageUser", "canWithPartialContext", "getGuildVersion", "getChannelsVersion", "getChannelPermissions", "getHighestRole", "initialize", "constructor", "isRoleHigher"].forEach(a => PermissionStore.__proto__[a] = () => !0);
},
stop: () => { }
});

View file

@ -0,0 +1,127 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { addButton, removeButton } from "@api/MessagePopover";
import { classNameFactory } from "@api/Styles";
import { EquicordDevs } from "@utils/constants";
import { sendMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import { ChannelStore, Menu } from "@webpack/common";
import { Message } from "discord-types/general";
function RepeatMessageIcon({ className }: { className?: string; }) {
return (
<svg
viewBox="0 -960 960 960"
height={24}
width={24}
className={classes(classNameFactory("vc-repeat-")("icon"), className)}
>
<path fill="currentColor" d="m274-200 34 34q12 12 11.5 28T308-110q-12 12-28.5 12.5T251-109L148-212q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l103-103q12-12 28.5-11.5T308-370q11 12 11.5 28T308-314l-34 34h406v-120q0-17 11.5-28.5T720-440q17 0 28.5 11.5T760-400v120q0 33-23.5 56.5T680-200H274Zm412-480H280v120q0 17-11.5 28.5T240-520q-17 0-28.5-11.5T200-560v-120q0-33 23.5-56.5T280-760h406l-34-34q-12-12-11.5-28t11.5-28q12-12 28.5-12.5T709-851l103 103q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L709-589q-12 12-28.5 11.5T652-590q-11-12-11.5-28t11.5-28l34-34Z" />
</svg>
);
}
let shift = false;
function repeatMessage(channelId: string, id: string, content: string, stickers: any[]) {
sendMessage(channelId, {
content
}, true, {
allowedMentions: {
parse: [],
replied_user: false
},
messageReference: shift ? {
channel_id: channelId,
message_id: id
} : undefined,
stickerIds: stickers.map(s => s.id)
});
}
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
if (!message.content && message.stickerItems.length === 0) return;
const group = findGroupChildrenByChildId("copy-text", children);
if (!group) return;
group.splice(group.findIndex(c => c?.props?.id === "reply") + 1, 0, (
<Menu.MenuItem
id="vc-repeat"
label="Repeat"
icon={RepeatMessageIcon}
action={async () => repeatMessage(message.channel_id, message.id, message.content, message.stickerItems)}
/>
));
};
function setTitle(title: string) {
const contextMenuOption = document.querySelector("#message-vc-repeat .label__563c3");
if (contextMenuOption) contextMenuOption.innerHTML = title;
}
const keyupListener = (event: KeyboardEvent) => {
if (event.key === "Shift") {
shift = false;
setTitle("Repeat");
}
};
const keydownListener = (event: KeyboardEvent) => {
if (event.key === "Shift") {
shift = true;
setTitle("Repeat and Reply");
}
};
export default definePlugin({
name: "RepeatMessage",
description: "Allows you to repeat messages quickly. If you hold shift while clicking the Repeat option, it will reply to the message.",
authors: [EquicordDevs.Tolgchu],
contextMenus: {
"message": messageCtxPatch
},
start() {
addButton("vc-repeat", message => {
if (!message.content && message.stickerItems.length === 0) return null;
return {
label: "Repeat (Click) / Repeat and Reply (Shift + Click)",
icon: RepeatMessageIcon,
message,
channel: ChannelStore.getChannel(message.channel_id),
onClick: async () => repeatMessage(message.channel_id, message.id, message.content, message.stickerItems)
};
});
document.addEventListener("keyup", keyupListener);
document.addEventListener("keydown", keydownListener);
},
stop() {
removeButton("vc-repeat");
document.removeEventListener("keyup", keyupListener);
document.removeEventListener("keydown", keydownListener);
},
});

View file

@ -0,0 +1,158 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
import { classNameFactory, disableStyle, enableStyle } from "@api/Styles";
import { EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { Button, Forms, showToast, Toasts, useState } from "@webpack/common";
import style from "./style.css?managed";
function runCode(code: string) {
try {
const output = Function(`return () => {${code}}`)()();
showToast(`${output}`, Toasts.Type.SUCCESS);
} catch (e) {
new Logger("Run in Console").error(e);
showToast(`${e}`, Toasts.Type.FAILURE);
}
}
const cl = classNameFactory("vc-ric-");
function EditCodeModal({ rootProps, close, value }: { rootProps: ModalProps, close(): void; value: string; }) {
const [code, setCode] = useState(value);
return (
<ModalRoot {...rootProps} className="vc-ric-modal" >
<ModalHeader className={cl("modal-header")}>
<Forms.FormTitle tag="h2">
Edit Code & Run
</Forms.FormTitle>
<ModalCloseButton onClick={close} />
</ModalHeader>
<ModalContent className={cl("modal-content-code-input")}>
<textarea
className="inputDefault__22335 input_f27786 textArea__6e373 scrollbarDefault__3545a scrollbar_b61b2b"
value={code}
onChange={e => setCode(e.currentTarget.value)}
/>
</ModalContent>
<ModalFooter>
<Button
onClick={() => {
runCode(code);
close();
}}
>Run</Button>
</ModalFooter>
</ModalRoot>
);
}
function RunCode() {
return (
<path fill="currentColor" d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H160v400Zm187-200-76-76q-12-12-11.5-28t12.5-28q12-11 28-11.5t28 11.5l104 104q12 12 12 28t-12 28L328-308q-11 11-27.5 11.5T272-308q-11-11-11-28t11-28l75-76Zm173 160q-17 0-28.5-11.5T480-320q0-17 11.5-28.5T520-360h160q17 0 28.5 11.5T720-320q0 17-11.5 28.5T680-280H520Z" />
);
}
function EditCode() {
return (
<path fill="currentColor" d="m193-479 155 155q11 11 11 28t-11 28q-11 11-28 11t-28-11L108-452q-6-6-8.5-13T97-480q0-8 2.5-15t8.5-13l184-184q12-12 28.5-12t28.5 12q12 12 12 28.5T349-635L193-479Zm574-2L612-636q-11-11-11-28t11-28q11-11 28-11t28 11l184 184q6 6 8.5 13t2.5 15q0 8-2.5 15t-8.5 13L668-268q-12 12-28 11.5T612-269q-12-12-12-28.5t12-28.5l155-155Z" />
);
}
function RunIcon({ className, text }: { className?: string; text: string; }) {
const [shift, setShift] = useState(false);
document.addEventListener("keyup", event => {
if (event.key === "Shift") try {
setShift(false);
} catch (e) { }
});
document.addEventListener("keydown", event => {
if (event.key === "Shift") try {
setShift(true);
} catch (e) { }
});
return (
<div role="button">
<svg
viewBox="0 -960 960 960"
height={16}
width={16}
className={classes(classNameFactory("vc-run-in-console-")("icon"), className)}
onClick={() => {
if (shift) {
const key = openModal(props => (
<EditCodeModal
rootProps={props}
close={() => closeModal(key)}
value={text}
/>
));
} else runCode(text);
}}
>
{shift ? <EditCode /> : <RunCode />}
</svg>
</div>
);
}
function replaceIcon(icon: Function) {
const svg = document.querySelector(".vc-run-in-console-icon");
if (!svg) return;
svg.firstChild?.remove();
svg.appendChild(icon());
}
export default definePlugin({
name: "RunInConsole",
description: "Allows you to run code blocks in the console. Press Shift to edit the code before running.",
authors: [EquicordDevs.Tolgchu],
patches: [
{
find: 'p.SUPPORTS_COPY?(0,i.jsx)("div",{className:G.codeActions',
replacement: [
{
match: 'p.SUPPORTS_COPY?(0,i.jsx)("div",{className:G.codeActions,children:(0,i.jsx)(w,{text:e.content})}):null',
replace: 'p.SUPPORTS_COPY?(0,i.jsx)("div",{className:G.codeActions,children:[(0,i.jsx)(w,{text:e.content}),(0,i.jsx)($self.RunIcon,{text:e.content})]}):null'
}
]
}
],
RunIcon,
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
},
});

View file

@ -0,0 +1,27 @@
/* stylelint-disable selector-class-pattern */
.vc-ric-modal.small_f8e677 {
height: 800px;
width: 1000px;
}
.vc-ric-modal-content-code-input textarea {
height: 95%;
width: 100%;
padding: 10px;
font-size: 14px;
font-family: monospace;
resize: none;
}
.vc-ric-modal-header {
justify-content: space-between;
align-content: center;
}
.vc-ric-modal-header h1 {
margin: 0;
}
.vc-ric-modal-header button {
padding: 0;
}

View file

@ -0,0 +1,65 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 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 <https://www.gnu.org/licenses/>.
*/
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
import { EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { React, useEffect, useState } from "@webpack/common";
let lastState = false;
const ReverseMessageToggle: ChatBarButton = ({ isMainChat }) => {
const [enabled, setEnabled] = useState(lastState);
function setEnabledValue(value: boolean) {
lastState = value;
setEnabled(value);
}
useEffect(() => {
const listener: SendListener = async (_, message) => {
if (enabled && message.content) message.content = message.content.split("").reverse().join("");
};
addPreSendListener(listener);
return () => void removePreSendListener(listener);
}, [enabled]);
if (!isMainChat) return null;
return (
<ChatBarButton
tooltip={enabled ? "Disable Reverse Message" : "Enable Reverse Message"}
onClick={() => setEnabledValue(!enabled)}
>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill={enabled ? "var(--green-360)" : "currentColor"} d="M482-160q-134 0-228-93t-94-227v-7l-36 36q-11 11-28 11t-28-11q-11-11-11-28t11-28l104-104q12-12 28-12t28 12l104 104q11 11 11 28t-11 28q-11 11-28 11t-28-11l-36-36v7q0 100 70.5 170T482-240q16 0 31.5-2t30.5-7q17-5 32 1t23 21q8 16 1.5 31.5T577-175q-23 8-47 11.5t-48 3.5Zm-4-560q-16 0-31.5 2t-30.5 7q-17 5-32.5-1T360-733q-8-15-1.5-30.5T381-784q24-8 48-12t49-4q134 0 228 93t94 227v7l36-36q11-11 28-11t28 11q11 11 11 28t-11 28L788-349q-12 12-28 12t-28-12L628-453q-11-11-11-28t11-28q11-11 28-11t28 11l36 36v-7q0-100-70.5-170T478-720Z" /></svg>
</ChatBarButton>
);
};
export default definePlugin({
name: "TalkInReverse",
authors: [EquicordDevs.Tolgchu],
description: "Reverses the message content before sending it.",
dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"],
start: () => addChatBarButton("ReverseMessageToggle", ReverseMessageToggle),
stop: () => removeChatBarButton("ReverseMessageToggle")
});

View file

@ -583,6 +583,14 @@ export const EquicordDevs = Object.freeze({
name: "Panniku",
id: 703634705152606318n,
},
Tolgchu: {
name: "✨Tolgchu✨",
id: 329671025312923648n,
},
DaBluLite: {
name: "DaBluLite",
id: 582170007505731594n,
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly