feat(DiscordColorways): Update

This commit is contained in:
thororen1234 2024-05-18 14:56:35 -04:00
parent a6dd7fb963
commit be8e970a9e
20 changed files with 2650 additions and 1307 deletions

View file

@ -0,0 +1,74 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { findByProps } from "@webpack";
import { Button, Forms, Text, useState } from "@webpack/common";
import { getAutoPresets } from "../css";
export default function ({ modalProps, onChange, autoColorwayId = "" }: { modalProps: ModalProps, onChange: (autoPresetId: string) => void, autoColorwayId: string; }) {
const [autoId, setAutoId] = useState(autoColorwayId);
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
return <ModalRoot {...modalProps}>
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Auto Preset Settings
</Text>
</ModalHeader>
<ModalContent>
<div className="dc-info-card" style={{ marginTop: "1em" }}>
<strong>About the Auto Colorway</strong>
<span>The auto colorway allows you to use your system's accent color in combination with a selection of presets that will fully utilize it.</span>
</div>
<div style={{ marginBottom: "20px" }}>
<Forms.FormTitle>Presets:</Forms.FormTitle>
{Object.values(getAutoPresets()).map(autoPreset => {
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={autoId === autoPreset.id}>
<div
className={`${radioBar} ${radioPositionLeft}`}
style={{ padding: "10px" }}
onClick={() => {
setAutoId(autoPreset.id);
}}>
<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" />
{autoId === autoPreset.id && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
</svg>
<Text variant="eyebrow" tag="h5">{autoPreset.name}</Text>
</div>
</div>;
})}
</div>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND_NEW}
size={Button.Sizes.MEDIUM}
onClick={() => {
DataStore.set("activeAutoPreset", autoId);
onChange(autoId);
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

@ -5,6 +5,7 @@
*/
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { findByProps } from "@webpack";
import { Button, Forms, ScrollerThin, Switch, Text, useState } from "@webpack/common";
import { getPreset } from "../css";
@ -13,27 +14,31 @@ export default function ({ modalProps, onSettings, presetId, hasTintedText, hasD
const [tintedText, setTintedText] = useState<boolean>(hasTintedText);
const [discordSaturation, setDiscordSaturation] = useState<boolean>(hasDiscordSaturation);
const [preset, setPreset] = useState<string>(presetId);
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
return <ModalRoot {...modalProps} className="colorwaysPresetPicker">
<ModalHeader><Text variant="heading-lg/semibold" tag="h1">Creator Settings</Text></ModalHeader>
<ModalContent className="colorwaysPresetPicker-content">
<div className="colorwaysCreator-settingCat" style={{ marginBottom: "20px" }}>
<Forms.FormTitle style={{ marginBottom: "0" }}>
Presets:
</Forms.FormTitle>
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix style={{ paddingRight: "2px" }}>
{Object.values(getPreset()).map(pre => {
return <div className="colorwaysCreator-settingItm colorwaysCreator-preset" onClick={() => {
setPreset(pre.id);
}}>
<Forms.FormTitle>
Presets:
</Forms.FormTitle>
<ScrollerThin orientation="vertical" paddingFix style={{ paddingRight: "2px", marginBottom: "20px", maxHeight: "250px" }}>
{Object.values(getPreset()).map(pre => {
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={preset === pre.id}>
<div
className={`${radioBar} ${radioPositionLeft}`}
style={{ padding: "10px" }}
onClick={() => {
setPreset(pre.id);
}}>
<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>
</div>
</div>;
})}
</ScrollerThin>
<Switch value={tintedText} onChange={setTintedText}>Use colored text</Switch>
<Switch value={discordSaturation} onChange={setDiscordSaturation} hideBorder style={{ marginBottom: "0" }}>Use Discord's saturation</Switch>
</ModalContent>

View file

@ -9,33 +9,22 @@ import { openModal } from "@utils/modal";
import { FluxDispatcher, Text, Tooltip, useEffect, useState } from "@webpack/common";
import { FluxEvents } from "@webpack/types";
import { getAutoPresets } from "../css";
import { ColorwayObject } from "../types";
import { PalleteIcon } from "./Icons";
import Selector from "./Selector";
export default function ({
listItemClass = "ColorwaySelectorBtnContainer",
listItemWrapperClass = "",
listItemTooltipClass = "colorwaysBtn-tooltipContent"
}: {
listItemClass?: string;
listItemWrapperClass?: string;
listItemTooltipClass?: string;
}) {
export default function () {
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);
}
const [autoPreset, setAutoPreset] = useState<string>("hueRotation");
useEffect(() => {
setButtonVisibility();
(async function () {
setVisibility(await DataStore.get("showColorwaysButton") as boolean);
setIsThin(await DataStore.get("useThinMenuButton") as boolean);
setAutoPreset(await DataStore.get("activeAutoPreset") as string);
})();
});
FluxDispatcher.subscribe("COLORWAYS_UPDATE_BUTTON_HEIGHT" as FluxEvents, ({ isTall }) => {
@ -47,7 +36,13 @@ export default function ({
});
return <Tooltip text={
!isThin ? <><span>Colorways</span><Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Active Colorway: " + activeColorway}</Text></> : <span>{"Active Colorway: " + activeColorway}</span>
<>
{!isThin ? <>
<span>Colorways</span>
<Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Active Colorway: " + activeColorway}</Text>
</> : <span>{"Active Colorway: " + activeColorway}</span>}
{activeColorway === "Auto" ? <Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Auto Preset: " + (getAutoPresets()[autoPreset].name || "None")}</Text> : <></>}
</>
} position="right" tooltipContentClassName="colorwaysBtn-tooltipContent"
>
{({ onMouseEnter, onMouseLeave, onClick }) => visibility ? <div className="ColorwaySelectorBtnContainer">
@ -55,7 +50,8 @@ export default function ({
className={"ColorwaySelectorBtn" + (isThin ? " ColorwaySelectorBtn_thin" : "")}
onMouseEnter={async () => {
onMouseEnter();
setActiveColorway(await DataStore.get("actveColorwayID") || "None");
setActiveColorway((await DataStore.get("activeColorwayObject") as ColorwayObject).id || "None");
setAutoPreset(await DataStore.get("activeAutoPreset") as string);
}}
onMouseLeave={onMouseLeave}
onClick={() => {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import {
ModalContent,
ModalFooter,
@ -16,6 +15,7 @@ import {
import {
Button,
Forms,
Slider,
Text,
TextInput,
useEffect,
@ -25,12 +25,13 @@ import {
import { ColorPicker } from "..";
import { knownThemeVars } from "../constants";
import { generateCss, getPreset, gradientPresetIds, pureGradientBase } from "../css";
import { generateCss, getPreset, gradientPresetIds, PrimarySatDiffs, pureGradientBase } from "../css";
import { Colorway } from "../types";
import { colorToHex, getHex, hexToString } from "../utils";
import { colorToHex, getHex, HexToHSL, hexToString } from "../utils";
import ColorwayCreatorSettingsModal from "./ColorwayCreatorSettingsModal";
import ConflictingColorsModal from "./ConflictingColorsModal";
import InputColorwayIdModal from "./InputColorwayIdModal";
import SaveColorwayModal from "./SaveColorwayModal";
import ThemePreviewCategory from "./ThemePreview";
export default function ({
modalProps,
@ -48,10 +49,9 @@ export default function ({
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[]>(["primary", "secondary", "tertiary", "accent"]);
const [presetColorArray, setPresetColorArray] = useState<string[]>(["accent", "primary", "secondary", "tertiary"]);
const [mutedTextBrightness, setMutedTextBrightness] = useState<number>(Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 100));
const colorProps = {
accent: {
@ -77,11 +77,8 @@ export default function ({
};
useEffect(() => {
const parsedID = colorwayID?.split("colorway:")[1];
if (parsedID) {
if (!parsedID) {
throw new Error("Please enter a Colorway ID");
} else if (!hexToString(parsedID).includes(",")) {
if (colorwayID) {
if (!colorwayID.includes(",")) {
throw new Error("Invalid Colorway ID");
} else {
const setColor = [
@ -90,7 +87,20 @@ export default function ({
setSecondaryColor,
setTertiaryColor
];
hexToString(parsedID).split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
colorwayID.split("|").forEach((prop: string) => {
if (prop.includes(",#")) {
prop.split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
}
if (prop.includes("n:")) {
setColorwayName(prop.split("n:")[1]);
}
if (prop.includes("p:")) {
if (Object.values(getPreset()).map(preset => preset.id).includes(prop.split("p:")[1])) {
setPreset(prop.split("p:")[1]);
setPresetColorArray(getPreset()[prop.split("p:")[1]].colors);
}
}
});
}
}
});
@ -122,7 +132,7 @@ export default function ({
/>
<div className="colorwaysCreator-settingCat">
<Forms.FormTitle style={{ marginBottom: "0" }}>
Colors:
Colors & Values:
</Forms.FormTitle>
<div className="colorwayCreator-colorPreviews">
{presetColorArray.map(presetColor => {
@ -140,6 +150,14 @@ export default function ({
/>;
})}
</div>
<Forms.FormDivider style={{ margin: "10px 0" }} />
<Forms.FormTitle>Muted Text Brightness:</Forms.FormTitle>
<Slider
minValue={0}
maxValue={100}
initialValue={mutedTextBrightness}
onValueChange={setMutedTextBrightness}
/>
</div>
<div
className="colorwaysCreator-setting"
@ -160,7 +178,6 @@ export default function ({
</svg>
</div>
<ThemePreviewCategory
isCollapsed={false}
accent={"#" + accentColor}
primary={"#" + primaryColor}
secondary={"#" + secondaryColor}
@ -170,7 +187,10 @@ export default function ({
secondaryColor,
tertiaryColor,
accentColor
)[preset].preset(discordSaturation) as { full: string, base: string; }).base})}` : ""}
)[preset].preset(discordSaturation) as { full: string, base: string; }).base})}` : (tintedText ? `.colorwaysPreview-modal,.colorwaysPreview-wrapper {
--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]}%) ${mutedTextBrightness || Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 100)}%);
--primary-360: 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%);
}` : "")}
/>
</ModalContent>
<ModalFooter>
@ -179,7 +199,7 @@ export default function ({
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={e => {
onClick={async () => {
var customColorwayCSS: string = "";
if (preset === "default") {
customColorwayCSS = generateCss(
@ -188,24 +208,26 @@ export default function ({
tertiaryColor,
accentColor,
tintedText,
discordSaturation
discordSaturation,
mutedTextBrightness,
(colorwayName || "Colorway")
);
} else {
gradientPresetIds.includes(getPreset()[preset].id) ?
customColorwayCSS = getPreset(
customColorwayCSS = (getPreset(
primaryColor,
secondaryColor,
tertiaryColor,
accentColor
)[preset].preset(discordSaturation).full : customColorwayCSS = getPreset(
)[preset].preset(discordSaturation) as { full: string; }).full : customColorwayCSS = (getPreset(
primaryColor,
secondaryColor,
tertiaryColor,
accentColor
)[preset].preset(discordSaturation);
)[preset].preset(discordSaturation) as string);
}
const customColorway: Colorway = {
name: (colorwayName || "Colorway") + (preset === "default" ? "" : ": Made for " + getPreset()[preset].name),
name: (colorwayName || "Colorway"),
"dc-import": customColorwayCSS,
accent: "#" + accentColor,
primary: "#" + primaryColor,
@ -215,30 +237,22 @@ export default function ({
author: UserStore.getCurrentUser().username,
authorID: UserStore.getCurrentUser().id,
isGradient: gradientPresetIds.includes(getPreset()[preset].id),
linearGradient: gradientPresetIds.includes(getPreset()[preset].id) ? getPreset(
linearGradient: gradientPresetIds.includes(getPreset()[preset].id) ? (getPreset(
primaryColor,
secondaryColor,
tertiaryColor,
accentColor
)[preset].preset(discordSaturation).base : null
)[preset].preset(discordSaturation) as { base: string; }).base : "",
preset: getPreset()[preset].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!();
openModal(props => <SaveColorwayModal modalProps={props} colorways={[customColorway]} onFinish={() => {
modalProps.onClose();
loadUIProps!();
}} />);
}}
>Finish</Button>
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}

View file

@ -41,8 +41,9 @@ export function PalleteIcon(props: IconProps) {
>
<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>
d="M 12,0 C 5.3733333,0 0,5.3733333 0,12 c 0,6.626667 5.3733333,12 12,12 1.106667,0 2,-0.893333 2,-2 0,-0.52 -0.2,-0.986667 -0.52,-1.346667 -0.306667,-0.346666 -0.506667,-0.813333 -0.506667,-1.32 0,-1.106666 0.893334,-2 2,-2 h 2.36 C 21.013333,17.333333 24,14.346667 24,10.666667 24,4.7733333 18.626667,0 12,0 Z M 4.6666667,12 c -1.1066667,0 -2,-0.893333 -2,-2 0,-1.1066667 0.8933333,-2 2,-2 1.1066666,0 2,0.8933333 2,2 0,1.106667 -0.8933334,2 -2,2 z M 8.666667,6.6666667 c -1.106667,0 -2.0000003,-0.8933334 -2.0000003,-2 0,-1.1066667 0.8933333,-2 2.0000003,-2 1.106666,0 2,0.8933333 2,2 0,1.1066666 -0.893334,2 -2,2 z m 6.666666,0 c -1.106666,0 -2,-0.8933334 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.1066666 -0.893333,2 -2,2 z m 4,5.3333333 c -1.106666,0 -2,-0.893333 -2,-2 0,-1.1066667 0.893334,-2 2,-2 1.106667,0 2,0.8933333 2,2 0,1.106667 -0.893333,2 -2,2 z"
/>
</Icon>
);
}
@ -60,3 +61,86 @@ export function CloseIcon(props: IconProps) {
</Icon>
);
}
export function DownloadIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-download-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 2a1 1 0 0 1 1 1v10.59l3.3-3.3a1 1 0 1 1 1.4 1.42l-5 5a1 1 0 0 1-1.4 0l-5-5a1 1 0 1 1 1.4-1.42l3.3 3.3V3a1 1 0 0 1 1-1ZM3 20a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Z"
/>
</Icon>
);
}
export function ImportIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-import-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M.9 3a.9.9 0 0 1 .892.778l.008.123v16.201a.9.9 0 0 1-1.792.121L0 20.102V3.899A.9.9 0 0 1 .9 3Zm14.954 2.26.1-.112a1.2 1.2 0 0 1 1.584-.1l.113.1 5.998 5.998a1.2 1.2 0 0 1 .1 1.584l-.1.112-5.997 6.006a1.2 1.2 0 0 1-1.799-1.584l.1-.113 3.947-3.954H4.8a1.2 1.2 0 0 1-1.191-1.06l-.008-.14a1.2 1.2 0 0 1 1.06-1.192l.14-.008h15.103l-3.95-3.952a1.2 1.2 0 0 1-.1-1.585l.1-.112z"
/>
</Icon>
);
}
export function IDIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-id-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M15.3 14.48c-.46.45-1.08.67-1.86.67h-1.39V9.2h1.39c.78 0 1.4.22 1.86.67.46.45.68 1.22.68 2.31 0 1.1-.22 1.86-.68 2.31Z"
/>
<path
fill="currentColor"
fill-rule="evenodd"
d="M5 2a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm1 15h2.04V7.34H6V17Zm4-9.66V17h3.44c1.46 0 2.6-.42 3.38-1.25.8-.83 1.2-2.02 1.2-3.58s-.4-2.75-1.2-3.58c-.79-.83-1.92-1.25-3.38-1.25H10Z"
clip-rule="evenodd"
/>
</Icon>
);
}
export function CodeIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-code-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M9.6 7.8 4 12l5.6 4.2a1 1 0 0 1 .4.8v1.98c0 .21-.24.33-.4.2l-8.1-6.4a1 1 0 0 1 0-1.56l8.1-6.4c.16-.13.4-.01.4.2V7a1 1 0 0 1-.4.8ZM14.4 7.8 20 12l-5.6 4.2a1 1 0 0 0-.4.8v1.98c0 .21.24.33.4.2l8.1-6.4a1 1 0 0 0 0-1.56l-8.1-6.4a.25.25 0 0 0-.4.2V7a1 1 0 0 0 .4.8Z"
/>
</Icon>
);
}
export function MoreIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-more-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fill-rule="evenodd"
d="M4 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm10-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm8 0a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"
clip-rule="evenodd"
/>
</Icon>
);
}

View file

@ -5,241 +5,258 @@
*/
import * as DataStore from "@api/DataStore";
import { CodeBlock } from "@components/CodeBlock";
import { Flex } from "@components/Flex";
import { openUserProfile } from "@utils/discord";
import {
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
openModal,
} from "@utils/modal";
import { Button, Clipboard, Forms, Text, Toasts, useState } from "@webpack/common";
import { findComponentByCodeLazy } from "@webpack";
import { Button, Clipboard, Forms, Text, TextInput, Toasts, UserStore, useState, useStateFromStores } from "@webpack/common";
import { ColorwayCSS } from "..";
import { generateCss, pureGradientBase } from "../css";
import { Colorway } from "../types";
import { colorToHex, stringToHex } from "../utils";
import ThemePreviewCategory from "./ThemePreview";
import SaveColorwayModal from "./SaveColorwayModal";
import ThemePreview from "./ThemePreview";
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
function RenameColorwayModal({ modalProps, ogName, onFinish, colorwayList }: { modalProps: ModalProps, ogName: string, onFinish: (name: string) => void, colorwayList: Colorway[]; }) {
const [error, setError] = useState<string>("");
const [newName, setNewName] = useState<string>(ogName);
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1" style={{ marginRight: "auto" }}>
Rename Colorway...
</Text>
<ModalCloseButton onClick={() => modalProps.onClose()} />
</ModalHeader>
<ModalContent>
<TextInput
value={newName}
error={error}
onChange={setNewName}
/>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={async () => {
if (!newName) {
return setError("Error: Please enter a valid name");
}
if (colorwayList.map(c => c.name).includes(newName)) {
return setError("Error: Name already exists");
}
onFinish(newName);
modalProps.onClose();
}}
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
}
export default function ({
modalProps,
colorwayProps,
discrimProps = false,
colorway,
loadUIProps
}: {
modalProps: ModalProps;
colorwayProps: Colorway;
discrimProps?: boolean;
colorway: Colorway;
loadUIProps: () => Promise<void>;
}) {
const colors: string[] = colorwayProps.colors || [
const colors: string[] = colorway.colors || [
"accent",
"primary",
"secondary",
"tertiary",
];
const [collapsedCSS, setCollapsedCSS] = useState(true);
return <ModalRoot {...modalProps} className="colorwayCreator-modal">
<ModalHeader>
const profile = useStateFromStores([UserStore], () => UserStore.getUser(colorway.authorID));
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1" style={{ marginRight: "auto" }}>
Colorway Details: {colorwayProps.name}
Colorway: {colorway.name}
</Text>
<ModalCloseButton onClick={() => modalProps.onClose()} />
</ModalHeader>
<ModalContent>
<div className="colorwayInfo-wrapper">
<div className="colorwayInfo-colorSwatches">
{colors.map(color => <div
className="colorwayInfo-colorSwatch"
style={{ backgroundColor: colorwayProps[color] }}
<Flex style={{ gap: "8px", width: "100%" }} flexDirection="column">
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Creator:</Forms.FormTitle>
<Flex style={{ gap: ".5rem" }}>
<UserSummaryItem
users={[profile]}
guildId={undefined}
renderIcon={false}
showDefaultAvatarsForNullUsers
size={32}
showUserPopout
/>
<Text style={{ lineHeight: "32px" }}>{colorway.author}</Text>
</Flex>
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Colors:</Forms.FormTitle>
<Flex style={{ gap: "8px" }}>
{colors.map(color => <div className="colorwayInfo-colorSwatch" style={{ backgroundColor: colorway[color] }} />)}
</Flex>
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Actions:</Forms.FormTitle>
<Flex style={{ gap: "8px" }} flexDirection="column">
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={() => {
Clipboard.copy(colorwayProps[color]);
const colorwayIDArray = `${colorway.accent},${colorway.primary},${colorway.secondary},${colorway.tertiary}|n:${colorway.name}${colorway.preset ? `|p:${colorway.preset}` : ""}`;
const colorwayID = stringToHex(colorwayIDArray);
Clipboard.copy(colorwayID);
Toasts.show({
message: "Copied color successfully",
message: "Copied Colorway ID Successfully",
type: 1,
id: "copy-colorway-color-notify",
id: "copy-colorway-id-notify",
});
}}
/>)}
</div>
<div className="colorwayInfo-row colorwayInfo-author">
<Flex style={{ gap: "10px", width: "100%", alignItems: "center" }}>
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Properties:</Forms.FormTitle>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto", maxWidth: "236px" }}
onClick={() => {
openUserProfile(colorwayProps.authorID);
}}
>
Author: {colorwayProps.author}
</Button>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
onClick={() => {
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>
{discrimProps && <Button
color={Button.Colors.RED}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
style={{ flex: "0 0 auto" }}
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>}
</Flex>
</div>
<div className={"colorwayInfo-row colorwayInfo-css" + (collapsedCSS ? " colorwaysCreator-settingCat-collapsed" : "")}>
<Flex style={{ gap: "10px", width: "100%", alignItems: "center" }}>
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>CSS:</Forms.FormTitle>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
onClick={() => setCollapsedCSS(!collapsedCSS)}
>
{collapsedCSS ? "Show" : "Hide"}
</Button>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
onClick={() => {
Clipboard.copy(colorwayProps["dc-import"]);
Toasts.show({
message: "Copied CSS to Clipboard",
type: 1,
id: "copy-colorway-css-notify",
});
}}
>
Copy
</Button>
{discrimProps ? <Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
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
</Button> : <Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
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
</Button>}
</Flex>
<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}
previewCSS={colorwayProps.isGradient ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${colorwayProps.linearGradient})}` : ""}
/>
</div>
Copy Colorway ID
</Button>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={() => {
Clipboard.copy(colorway["dc-import"]);
Toasts.show({
message: "Copied CSS to Clipboard",
type: 1,
id: "copy-colorway-css-notify",
});
}}
>
Copy CSS
</Button>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={async () => {
const newColorway = {
...colorway,
"dc-import": generateCss(colorToHex(colorway.primary) || "313338", colorToHex(colorway.secondary) || "2b2d31", colorToHex(colorway.tertiary) || "1e1f22", colorToHex(colorway.accent) || "5865f2", true, true, undefined, colorway.name)
};
openModal(props => <SaveColorwayModal modalProps={props} colorways={[newColorway]} onFinish={() => { }} />);
}}
>
Update CSS
</Button>
{colorway.sourceType === "offline" && <Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={async () => {
const offlineSources = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).map(o => o.colorways).filter(colorArr => colorArr.map(color => color.name).includes(colorway.name))[0];
openModal(props => <RenameColorwayModal ogName={colorway.name} colorwayList={offlineSources} modalProps={props} onFinish={async (newName: string) => {
const stores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).map(source => {
if (source.name === colorway.source) {
return {
name: source.name,
colorways: [...source.colorways.filter(colorway => colorway.name !== colorway.name), {
...colorway,
name: newName
}]
};
} else return source;
});
DataStore.set("customColorways", stores);
if ((await DataStore.get("activeColorwayObject")).id === colorway.name) {
DataStore.set("activeColorwayObject", { id: newName, css: colorway.name, sourceType: "offline", source: colorway.source });
}
modalProps.onClose();
loadUIProps();
}} />);
}}
>
Rename
</Button>}
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={() => {
openModal(props => <ModalRoot {...props} className="colorwayInfo-cssModal">
<ModalContent><CodeBlock lang="css" content={colorway["dc-import"]} /></ModalContent>
</ModalRoot>);
}}
>
Show CSS
</Button>
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ width: "100%" }}
onClick={() => {
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
<style>
{colorway.isGradient ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${colorway.linearGradient})}` : ""}
</style>
<ThemePreview
accent={colorway.accent}
primary={colorway.primary}
secondary={colorway.secondary}
tertiary={colorway.tertiary}
isModal
modalProps={props}
/>
</ModalRoot>);
}}
>
Show preview
</Button>
{colorway.sourceType === "offline" && <Button
color={Button.Colors.RED}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
style={{ width: "100%" }}
onClick={async () => {
const oldStores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name !== colorway.source);
const storeToModify = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name === colorway.source)[0];
const newStore = { name: storeToModify.name, colorways: storeToModify.colorways.filter(colorway => colorway.name !== colorway.name) };
DataStore.set("customColorways", [...oldStores, newStore]);
if ((await DataStore.get("activeColorwayObject")).id === colorway.name) {
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
ColorwayCSS.remove();
}
modalProps.onClose();
loadUIProps();
}}
>
Delete
</Button>}
</Flex>
</Flex>
<div style={{ width: "100%", height: "20px" }} />
</ModalContent>
</ModalRoot>;

View file

@ -0,0 +1,207 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { PlusIcon } from "@components/Icons";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { findByProps } from "@webpack";
import { Button, Text, TextInput, useEffect, useState } from "@webpack/common";
import { Colorway } from "../types";
import { StoreNameModal } from "./SettingsTabs/SourceManager";
export default function ({ modalProps, colorways, onFinish }: { modalProps: ModalProps, colorways: Colorway[], onFinish: () => void; }) {
const [offlineColorwayStores, setOfflineColorwayStores] = useState<{ name: string, colorways: Colorway[], id?: string; }[]>([]);
const [storename, setStorename] = useState<string>();
const [noStoreError, setNoStoreError] = useState<boolean>(false);
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
useEffect(() => {
(async () => {
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]);
})();
});
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1">Select Offline Colorway Source</Text>
</ModalHeader>
<ModalContent>
{noStoreError ? <Text variant="text-xs/normal" style={{ color: "var(--text-danger)" }}>Error: No store selected</Text> : <></>}
{offlineColorwayStores.map(store => {
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={storename === store.name}>
<div
className={`${radioBar} ${radioPositionLeft}`}
style={{ padding: "10px" }}
onClick={() => {
setStorename(store.name);
}}>
<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" />
{storename === store.name && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
</svg>
<Text variant="eyebrow" tag="h5">{store.name}</Text>
</div>
</div>;
})}
<div className={`${radioBarItem} ${radioBarItemFilled}`}>
<div
className={`${radioBar} ${radioPositionLeft}`}
style={{ padding: "10px" }}
onClick={() => {
openModal(props => <StoreNameModal modalProps={props} conflicting={false} originalName="" onFinish={async e => {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
}} />);
}}>
<PlusIcon width={24} height={24} />
<Text variant="eyebrow" tag="h5">Create new store...</Text>
</div>
</div>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND_NEW}
size={Button.Sizes.MEDIUM}
onClick={async () => {
setNoStoreError(false);
if (!storename) {
setNoStoreError(true);
} else {
const oldStores: { name: string, colorways: Colorway[], id?: string; }[] | undefined = await DataStore.get("customColorways");
const storeToModify: { name: string, colorways: Colorway[], id?: string; } | undefined = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name === storename)[0];
colorways.map((colorway, i) => {
if (storeToModify.colorways.map(colorway => colorway.name).includes(colorway.name)) {
openModal(props => <ModalRoot {...props}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1">Duplicate Colorway</Text>
</ModalHeader>
<ModalContent>
<Text>A colorway with the same name was found in this store, what do you want to do?</Text>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => {
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways.filter(colorwayy => colorwayy.name !== colorway.name), colorway] };
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
props.onClose();
if (i + 1 === colorways.length) {
modalProps.onClose();
onFinish!();
}
}}
>
Override
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => {
function NewColorwayNameModal({ modalProps, onSelected }: { modalProps: ModalProps, onSelected: (e: string) => void; }) {
const [errorMsg, setErrorMsg] = useState<string>();
const [newColorwayName, setNewColorwayName] = useState("");
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1">Select new name</Text>
</ModalHeader>
<ModalContent>
<TextInput error={errorMsg} value={newColorwayName} onChange={e => setNewColorwayName(e)} placeholder="Enter valid colorway name" />
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
setErrorMsg("");
if (storeToModify!.colorways.map(colorway => colorway.name).includes(newColorwayName)) {
setErrorMsg("Error: Name already exists");
} else {
onSelected(newColorwayName);
if (i + 1 === colorways.length) {
modalProps.onClose();
}
}
}}
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
if (i + 1 === colorways.length) {
modalProps.onClose();
}
}}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
}
openModal(propss => <NewColorwayNameModal modalProps={propss} onSelected={e => {
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, { ...colorway, name: e }] };
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
props.onClose();
if (i + 1 === colorways.length) {
modalProps.onClose();
onFinish!();
}
}} />);
}}
>
Rename
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
onClick={() => {
props.onClose();
}}
>
Select different store
</Button>
</ModalFooter>
</ModalRoot>);
} else {
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, colorway] };
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
if (i + 1 === colorways.length) {
modalProps.onClose();
onFinish();
}
}
});
}
}}
>
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>;
}

File diff suppressed because it is too large Load diff

View file

@ -1,129 +0,0 @@
/*
* 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

@ -12,19 +12,25 @@ export default function () {
const [onDemand, setOnDemand] = useState<boolean>(false);
const [onDemandTinted, setOnDemandTinted] = useState<boolean>(false);
const [onDemandDiscordSat, setOnDemandDiscordSat] = useState<boolean>(false);
const [onDemandOsAccent, setOnDemandOsAccent] = useState<boolean>(false);
async function loadUI() {
const [
onDemandWays,
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
onDemandWaysDiscordSaturation,
onDemandWaysOsAccentColor
] = await DataStore.getMany([
"onDemandWays",
"onDemandWaysTintedText",
"onDemandWaysDiscordSaturation"
"onDemandWaysDiscordSaturation",
"onDemandWaysOsAccentColor"
]);
setOnDemand(onDemandWays);
setOnDemandTinted(onDemandWaysTintedText);
setOnDemandDiscordSat(onDemandWaysDiscordSaturation);
if (getComputedStyle(document.body).getPropertyValue("--os-accent-color") !== "") {
setOnDemandOsAccent(onDemandWaysOsAccentColor);
}
}
const cached_loadUI = useCallback(loadUI, []);
@ -54,7 +60,6 @@ export default function () {
Use tinted text
</Switch>
<Switch
hideBorder
value={onDemandDiscordSat}
onChange={(v: boolean) => {
setOnDemandDiscordSat(v);
@ -64,5 +69,16 @@ export default function () {
>
Use Discord's saturation
</Switch>
<Switch
hideBorder
value={onDemandOsAccent}
onChange={(v: boolean) => {
setOnDemandOsAccent(v);
DataStore.set("onDemandWaysOsAccentColor", v);
}}
disabled={!onDemand || !getComputedStyle(document.body).getPropertyValue("--os-accent-color")}
>
Use Operating System's Accent Color
</Switch>
</SettingsTab>;
}

View file

@ -6,192 +6,64 @@
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 { 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 "../../../discordColorways";
import { defaultColorwaySource, fallbackColorways, knownColorwaySources } from "../../constants";
import { versionData } from "../../.";
import { fallbackColorways } from "../../constants";
import { Colorway } from "../../types";
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, []);
const [showLabelsInSelectorGridView, setShowLabelsInSelectorGridView] = useState<boolean>(false);
useEffect(() => {
cached_loadUI();
(async function () {
const [
customColorways,
colorwaySourceFiles,
showColorwaysButton,
useThinMenuButton,
showLabelsInSelectorGridView
] = await DataStore.getMany([
"customColorways",
"colorwaySourceFiles",
"showColorwaysButton",
"useThinMenuButton",
"showLabelsInSelectorGridView"
]);
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);
setColorways(colorways || fallbackColorways);
setCustomColorways(customColorways.map(source => source.colorways).flat(2));
setColorsButtonVisibility(showColorwaysButton);
setIsButtonThin(useThinMenuButton);
setShowLabelsInSelectorGridView(showLabelsInSelectorGridView);
})();
}, []);
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} className="colorwaySourceModal">
<ModalHeader>
<Text variant="heading-lg/semibold" tag="h1">
Add a source:
</Text>
</ModalHeader>
<TextInput
placeholder="Enter a valid URL..."
onChange={e => colorwaySource = e}
style={{ margin: "8px", width: "calc(100% - 16px)" }}
/>
<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>
<Forms.FormDivider style={{ margin: "20px 0" }} />
<Forms.FormTitle tag="h5">Quick Switch</Forms.FormTitle>
<Switch
value={colorsButtonVisibility}
@ -221,6 +93,16 @@ export default function () {
>
Use thin Quick Switch button
</Switch>
<Forms.FormTitle tag="h5">Selector</Forms.FormTitle>
<Switch
value={showLabelsInSelectorGridView}
onChange={(v: boolean) => {
setShowLabelsInSelectorGridView(v);
DataStore.set("showLabelsInSelectorGridView", v);
}}
>
Show labels in Grid View
</Switch>
<Flex flexDirection="column" style={{ gap: 0 }}>
<h1 style={{
fontFamily: "var(--font-headline)",
@ -272,8 +154,7 @@ export default function () {
marginBottom: "8px"
}}
>
{versionData.creatorVersion}{" "}
(Stable)
{versionData.creatorVersion}{" (Stable)"}
</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Loaded Colorways:
@ -287,7 +168,7 @@ export default function () {
marginBottom: "8px"
}}
>
{[...colorways, ...customColorways].length}
{[...colorways, ...customColorways].length + 1}
</Text>
<Forms.FormTitle style={{ marginBottom: 0 }}>
Project Repositories:

View file

@ -0,0 +1,367 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { Flex } from "@components/Flex";
import { CopyIcon, DeleteIcon } from "@components/Icons";
import { SettingsTab } from "@components/VencordSettings/shared";
import { Logger } from "@utils/Logger";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { chooseFile, saveFile } from "@utils/web";
import { findByProps } from "@webpack";
import { Button, Clipboard, Forms, ScrollerThin, Text, TextInput, useEffect, useState } from "@webpack/common";
import { defaultColorwaySource } from "../../constants";
import { Colorway } from "../../types";
import { DownloadIcon, ImportIcon } from "../Icons";
import Spinner from "../Spinner";
export function StoreNameModal({ modalProps, originalName, onFinish, conflicting }: { modalProps: ModalProps, originalName: string, onFinish: (newName: string) => Promise<void>, conflicting: boolean; }) {
const [error, setError] = useState<string>("");
const [newStoreName, setNewStoreName] = useState<string>(originalName);
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1">{conflicting ? "Duplicate Store Name" : "Give this store a name"}</Text>
</ModalHeader>
<ModalContent>
{conflicting ? <Text>A store with the same name already exists. Please give a different name to the imported store:</Text> : <></>}
<Forms.FormTitle>Name:</Forms.FormTitle>
<TextInput error={error} value={newStoreName} onChange={e => setNewStoreName(e)} style={{ marginBottom: "16px" }} />
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={async () => {
setError("");
if ((await DataStore.get("customColorways")).map(store => store.name).includes(newStoreName)) {
return setError("Error: Store name already exists");
}
onFinish(newStoreName);
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>;
}
function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps, onFinish: (name: string, url: string) => void; }) {
const [colorwaySourceName, setColorwaySourceName] = useState<string>("");
const [colorwaySourceURL, setColorwaySourceURL] = useState<string>("");
const [nameError, setNameError] = useState<string>("");
const [URLError, setURLError] = useState<string>("");
return <ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<Text variant="heading-lg/semibold" tag="h1">
Add a source:
</Text>
</ModalHeader>
<ModalContent>
<Forms.FormTitle>Name:</Forms.FormTitle>
<TextInput
placeholder="Enter a valid Name..."
onChange={setColorwaySourceName}
value={colorwaySourceName}
error={nameError}
/>
<Forms.FormTitle style={{ marginTop: "8px" }}>URL:</Forms.FormTitle>
<TextInput
placeholder="Enter a valid URL..."
onChange={setColorwaySourceURL}
value={colorwaySourceURL}
error={URLError}
style={{ marginBottom: "16px" }}
/>
</ModalContent>
<ModalFooter>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.BRAND}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={async () => {
const sourcesArr: { name: string, url: string; }[] = (await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
if (!colorwaySourceName) {
setNameError("Error: Please enter a valid name");
}
else if (!colorwaySourceURL) {
setURLError("Error: Please enter a valid URL");
}
else if (sourcesArr.map(s => s.name).includes(colorwaySourceName)) {
setNameError("Error: An online source with that name already exists");
}
else if (sourcesArr.map(s => s.url).includes(colorwaySourceURL)) {
setURLError("Error: An online source with that url already exists");
} else {
onFinish(colorwaySourceName, colorwaySourceURL);
modalProps.onClose();
}
}}
>
Finish
</Button>
<Button
style={{ marginLeft: 8 }}
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.FILLED}
onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>;
}
export default function () {
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>();
const [customColorwayStores, setCustomColorwayStores] = useState<{ name: string, colorways: Colorway[]; }[]>([]);
const { item: radioBarItem, itemFilled: radioBarItemFilled } = findByProps("radioBar");
useEffect(() => {
(async function () {
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles"));
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
})();
}, []);
return <SettingsTab title="Sources">
<Flex style={{ gap: "0", marginBottom: "8px", alignItems: "center" }}>
<Forms.FormTitle tag="h5" style={{ marginBottom: 0, flexGrow: 1 }}>Online</Forms.FormTitle>
<Button
className="colorwaysSettings-colorwaySourceAction"
innerClassName="colorwaysSettings-iconButtonInner"
style={{ flexShrink: "0" }}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
onClick={() => {
openModal(props => <AddOnlineStoreModal modalProps={props} onFinish={async (name, url) => {
await DataStore.set("colorwaySourceFiles", [...await DataStore.get("colorwaySourceFiles"), { name: name, url: url }]);
setColorwaySourceFiles([...await DataStore.get("colorwaySourceFiles"), { name: name, url: url }]);
}} />);
}}>
<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...
</Button>
</Flex>
<ScrollerThin orientation="vertical" style={{ maxHeight: "250px" }} className="colorwaysSettings-sourceScroller">
{colorwaySourceFiles?.map((colorwaySourceFile: { name: string, url: string; }, i: number) => <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`}>
<div className="hoverRoll">
<Text className="colorwaysSettings-colorwaySourceLabel hoverRoll_normal">
{colorwaySourceFile.name} {colorwaySourceFile.url === defaultColorwaySource && <div className="colorways-badge">Built-In</div>}
</Text>
<Text className="colorwaysSettings-colorwaySourceLabel hoverRoll_hovered">
{colorwaySourceFile.url}
</Text>
</div>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={() => { Clipboard.copy(colorwaySourceFile.url); }}
>
<CopyIcon width={20} height={20} />
</Button>
{colorwaySourceFile.url !== defaultColorwaySource
&& <>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={async () => {
openModal(props => <StoreNameModal conflicting={false} modalProps={props} originalName={colorwaySourceFile.name || ""} onFinish={async e => {
const modal = openModal(propss => <ModalRoot {...propss} className="colorwaysLoadingModal"><Spinner style={{ color: "#ffffff" }} /></ModalRoot>);
const res = await fetch(colorwaySourceFile.url);
const data = await res.json();
DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: data.colorways || [] }]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
closeModal(modal);
}} />);
}}
>
<DownloadIcon width={20} height={20} />
</Button>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.RED}
look={Button.Looks.OUTLINED}
onClick={async () => {
DataStore.set("colorwaySourceFiles", (await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter((src, ii) => ii !== i));
setColorwaySourceFiles((await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter((src, ii) => ii !== i));
}}
>
<DeleteIcon width={20} height={20} />
</Button>
</>}
</div>
)}
</ScrollerThin>
<Flex style={{ gap: "0", marginBottom: "8px", alignItems: "center" }}>
<Forms.FormTitle tag="h5" style={{ marginBottom: 0, flexGrow: 1 }}>Offline</Forms.FormTitle>
<Button
className="colorwaysSettings-colorwaySourceAction"
innerClassName="colorwaysSettings-iconButtonInner"
style={{ flexShrink: "0", marginLeft: "8px" }}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
onClick={async () => {
if (IS_DISCORD_DESKTOP) {
const [file] = await DiscordNative.fileManager.openFiles({
filters: [
{ name: "DiscordColorways Offline Store", extensions: ["json"] },
{ name: "all", extensions: ["*"] }
]
});
if (file) {
try {
if ((await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]).map(store => store.name).includes(JSON.parse(new TextDecoder().decode(file.data)).name)) {
openModal(props => <StoreNameModal conflicting modalProps={props} originalName={JSON.parse(new TextDecoder().decode(file.data)).name} onFinish={async e => {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: JSON.parse(new TextDecoder().decode(file.data)).colorways }]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
}} />);
} else {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), JSON.parse(new TextDecoder().decode(file.data))]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
}
} 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 {
if ((await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]).map(store => store.name).includes(JSON.parse(reader.result as string).name)) {
openModal(props => <StoreNameModal conflicting modalProps={props} originalName={JSON.parse(reader.result as string).name} onFinish={async e => {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: JSON.parse(reader.result as string).colorways }]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
}} />);
} else {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), JSON.parse(reader.result as string)]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
}
} catch (err) {
new Logger("DiscordColorways").error(err);
}
};
reader.readAsText(file);
}
}}
>
<ImportIcon width={14} height={14} />
Import...
</Button>
<Button
className="colorwaysSettings-colorwaySourceAction"
innerClassName="colorwaysSettings-iconButtonInner"
style={{ flexShrink: "0", marginLeft: "8px" }}
size={Button.Sizes.SMALL}
color={Button.Colors.TRANSPARENT}
onClick={() => {
openModal(props => <StoreNameModal conflicting={false} modalProps={props} originalName="" onFinish={async e => {
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
props.onClose();
}} />);
}}>
<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>
New...
</Button>
</Flex>
<Flex flexDirection="column" style={{ gap: 0 }}>
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") ? <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`}>
<Flex style={{ gap: 0, alignItems: "center", width: "100%", height: "30px" }}>
<Text className="colorwaysSettings-colorwaySourceLabel">OS Accent Color{" "}
<div className="colorways-badge">Built-In</div>
</Text>
</Flex>
</div> : <></>}
{customColorwayStores.map(({ name: customColorwaySourceName, colorways: offlineStoreColorways }) => <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`}>
<Text className="colorwaysSettings-colorwaySourceLabel">
{customColorwaySourceName}
</Text>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={async () => {
console.log(offlineStoreColorways);
if (IS_DISCORD_DESKTOP) {
DiscordNative.fileManager.saveWithDialog(JSON.stringify({ "name": customColorwaySourceName, "colorways": [...offlineStoreColorways] }), `${customColorwaySourceName.replaceAll(" ", "-").toLowerCase()}.colorways.json`);
} else {
saveFile(new File([JSON.stringify({ "name": customColorwaySourceName, "colorways": [...offlineStoreColorways] })], `${customColorwaySourceName.replaceAll(" ", "-").toLowerCase()}.colorways.json`, { type: "application/json" }));
}
}}
>
<DownloadIcon width={20} height={20} />
</Button>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.RED}
look={Button.Looks.OUTLINED}
onClick={async () => {
var sourcesArr: { name: string, colorways: Colorway[]; }[] = [];
const customColorwaySources = await DataStore.get("customColorways");
customColorwaySources.map((source: { name: string, colorways: Colorway[]; }) => {
if (source.name !== customColorwaySourceName) {
sourcesArr.push(source);
}
});
DataStore.set("customColorways", sourcesArr);
setCustomColorwayStores(sourcesArr);
}}
>
<DeleteIcon width={20} height={20} />
</Button>
</div>
)}
</Flex>
</SettingsTab>;
}

View file

@ -0,0 +1,121 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { DataStore } from "@api/index";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { SettingsTab } from "@components/VencordSettings/shared";
import { findByProps } from "@webpack";
import { Button, ScrollerThin, Text, TextInput, Tooltip, useEffect, useState } from "@webpack/common";
import { StoreItem } from "../../types";
import { DownloadIcon } from "../Icons";
export default function () {
const [storeObject, setStoreObject] = useState<StoreItem[]>([]);
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>([]);
const [searchValue, setSearchValue] = useState<string>("");
useEffect(() => {
if (!searchValue) {
(async function () {
const res: Response = await fetch("https://dablulite.vercel.app/");
const data = await res.json();
setStoreObject(data.sources);
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
})();
}
}, []);
const { item: radioBarItem, itemFilled: radioBarItemFilled } = findByProps("radioBar");
return <SettingsTab title="Colorway Store">
<Flex style={{ gap: "0", marginBottom: "8px" }}>
<TextInput
className="colorwaySelector-search"
placeholder="Search for sources..."
value={searchValue}
onChange={setSearchValue}
/>
<Tooltip text="Refresh...">
{({ 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={async function () {
const res: Response = await fetch("https://dablulite.vercel.app/");
const data = await res.json();
setStoreObject(data.sources);
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
}}
>
<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>}
</Tooltip>
</Flex>
<ScrollerThin orientation="vertical" className="colorwaysSettings-sourceScroller">
{storeObject.map((item: StoreItem) =>
item.name.toLowerCase().includes(searchValue.toLowerCase()) ? <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`}>
<Flex flexDirection="column" style={{ gap: ".5rem" }}>
<Text className="colorwaysSettings-colorwaySourceLabelHeader">
{item.name}
</Text>
<Text className="colorwaysSettings-colorwaySourceDesc">
{item.description}
</Text>
<Link className="colorwaysSettings-colorwaySourceDesc" href={"https://github.com/" + item.authorGh}>by {item.authorGh}</Link>
</Flex>
<Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={colorwaySourceFiles.map(source => source.name).includes(item.name) ? Button.Colors.RED : Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={async () => {
if (colorwaySourceFiles.map(source => source.name).includes(item.name)) {
const sourcesArr: { name: string, url: string; }[] = colorwaySourceFiles.filter(source => source.name !== item.name);
DataStore.set("colorwaySourceFiles", sourcesArr);
setColorwaySourceFiles(sourcesArr);
} else {
const sourcesArr: { name: string, url: string; }[] = [...colorwaySourceFiles, { name: item.name, url: item.url }];
DataStore.set("colorwaySourceFiles", sourcesArr);
setColorwaySourceFiles(sourcesArr);
}
}}
>
{colorwaySourceFiles.map(source => source.name).includes(item.name) ? <DeleteIcon width={20} height={20} /> : <DownloadIcon width={20} height={20} />}
</Button>
</div> : <></>
)}
</ScrollerThin>
</SettingsTab>;
}

View file

@ -4,8 +4,10 @@
* 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">
import { CSSProperties } from "react";
export default function ({ className, style }: { className?: string, style?: CSSProperties; }) {
return <div className={"colorwaysBtn-spinner" + (className ? ` ${className}` : "")} role="img" aria-label="Loading" style={style}>
<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" />

View file

@ -5,188 +5,146 @@
*/
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import {
Forms,
Text
} from "@webpack/common";
import { Text } from "@webpack/common";
import { HexToHSL } from "../utils";
import { CloseIcon } from "./Icons";
export default function ({
export default function ThemePreview({
accent,
primary,
secondary,
tertiary,
className,
isCollapsed,
previewCSS,
noContainer
modalProps,
isModal
}: {
accent: string,
primary: string,
secondary: string,
tertiary: string,
className?: string,
isCollapsed: boolean,
previewCSS?: string,
noContainer?: boolean;
modalProps?: ModalProps,
isModal?: boolean;
}) {
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={{ background: `var(--dc-overlay-app-frame, ${tertiary})` }}
>
<div className="colorwaysPreview-titlebar" />
<div className="colorwaysPreview-body">
<div className="colorwayPreview-guilds">
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-guildItem"
style={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${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={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
/>
</div>
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-guildItem"
style={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
/>
</div>
</div>
<div className="colorwayPreview-channels" style={{ background: `var(--dc-overlay-3, ${secondary})` }}>
return <>
<style>
{".colorwaysPreview-wrapper {color: var(--header-secondary); box-shadow: var(--legacy-elevation-border);}" + previewCSS}
</style>
<div
className="colorwaysPreview-wrapper"
style={{ background: `var(--dc-overlay-app-frame, ${tertiary})` }}
>
<div className="colorwaysPreview-titlebar" />
<div className="colorwaysPreview-body">
<div className="colorwayPreview-guilds">
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-userArea"
style={{
background: `var(--dc-secondary-alt, hsl(${HexToHSL(secondary)[0]} ${HexToHSL(secondary)[1]}% ${Math.max(HexToHSL(secondary)[2] - 3.6, 0)}%))`
className="colorwayPreview-guildItem"
style={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${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>);
}
}}
/>
<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}
{isModal ? <CloseIcon style={{ color: "var(--header-secondary)" }} /> : <svg
aria-hidden="true"
role="img"
width="24"
height="24"
viewBox="0 0 24 24"
>
Preview
</Text>
<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-chat" style={{ background: `var(--dc-overlay-chat, ${primary})` }}>
<div className="colorwayPreview-guild">
<div className="colorwayPreview-guildSeparator" style={{ backgroundColor: primary }} />
</div>
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-chatBox"
style={{
background: `var(--dc-overlay-3, hsl(${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + 3.6, 100)}%))`
}}
className="colorwayPreview-guildItem"
style={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
/>
<div className="colorwayPreview-filler" />
</div>
<div className="colorwayPreview-guild">
<div
className="colorwayPreview-topShadow"
className="colorwayPreview-guildItem"
style={{ background: `var(--dc-guild-button, ${primary})` }}
onMouseEnter={e => e.currentTarget.style.background = accent}
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
/>
</div>
</div>
<div className="colorwayPreview-channels" style={{ background: `var(--dc-overlay-3, ${secondary})` }}>
<div
className="colorwayPreview-userArea"
style={{
background: `var(--dc-secondary-alt, hsl(${HexToHSL(secondary)[0]} ${HexToHSL(secondary)[1]}% ${Math.max(HexToHSL(secondary)[2] - 3.6, 0)}%))`
}}
/>
<div className="colorwayPreview-filler">
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--white-500)" }} />
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--primary-360)" }} />
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--primary-500)" }} />
</div>
<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={{ background: `var(--dc-overlay-chat, ${primary})` }}>
<div
className="colorwayPreview-chatBox"
style={{
background: `var(--dc-overlay-3, hsl(${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + 3.6, 100)}%))`
}}
/>
<div className="colorwayPreview-filler" />
<div
className="colorwayPreview-topShadow"
/>
</div>
</div>
);
}
return (
!noContainer ? <div className="colorwaysPreview">
<Forms.FormTitle
style={{ marginBottom: 0 }}
>
Preview
</Forms.FormTitle>
<style>
{previewCSS}
</style>
<ThemePreview
accent={accent}
primary={primary}
secondary={secondary}
tertiary={tertiary}
/>
</div> : <>
<style>
{".colorwaysPreview-wrapper {color: var(--header-secondary); box-shadow: var(--legacy-elevation-border);}" + previewCSS}
</style>
<ThemePreview
accent={accent}
primary={primary}
secondary={secondary}
tertiary={tertiary}
/>
</>
);
</div>
</>;
}

View file

@ -6,17 +6,6 @@
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",

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { UserStore } from "@webpack/common";
import { Plugins } from "Vencord";
import { HexToHSL } from "./utils";
@ -213,7 +214,7 @@ export const colorVariables: string[] = [
"green-100",
];
const PrimarySatDiffs = {
export const PrimarySatDiffs = {
130: 63.9594,
160: 49.4382,
200: 37.5758,
@ -234,7 +235,7 @@ const PrimarySatDiffs = {
800: 25,
};
const BrandSatDiffs = {
export const BrandSatDiffs = {
100: -9.54712,
130: 2.19526,
160: -1.17509,
@ -262,7 +263,7 @@ const BrandSatDiffs = {
900: -52.5074
};
const BrandLightDiffs = {
export const BrandLightDiffs = {
100: 33.5,
130: 32.2,
160: 30.2,
@ -469,8 +470,14 @@ export function gradientBase(accentColor?: string, discordSaturation = false) {
}`;
}
export function generateCss(primaryColor: string, secondaryColor: string, tertiaryColor: string, accentColor: string, tintedText: boolean, discordSaturation: boolean) {
const colorwayCss = `/*Automatically Generated - Colorway Creator V${(Plugins.plugins.DiscordColorways as any).creatorVersion}*/
export function generateCss(primaryColor: string, secondaryColor: string, tertiaryColor: string, accentColor: string, tintedText: boolean, discordSaturation: boolean, mutedTextBrightness?: number, name?: string) {
return `/**
* @name ${name}
* @version ${(Plugins.plugins.DiscordColorways as any).creatorVersion}
* @description Automatically generated Colorway.
* @author ${UserStore.getCurrentUser().username}
* @authorId ${UserStore.getCurrentUser().id}
*/
: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)}%;
@ -498,6 +505,8 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
--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)}%;
}
.theme-dark {
--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]}%;
@ -507,18 +516,29 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
--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)}%;
--interactive-muted: hsl(${HexToHSL("#" + primaryColor)[0]} 0% ${Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 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]}%) ${mutedTextBrightness || Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 100)}%;
--interactive-muted: hsl(${HexToHSL("#" + primaryColor)[0]} ${HexToHSL("#" + primaryColor)[1] / 2}% ${Math.max(Math.min(HexToHSL("#" + primaryColor)[2] - 5, 100), 45)}%);
${tintedText ? `--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%)")};` : ""}
--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%)`)};` : ""}
}
.theme-light {
--white-500-hsl: ${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${Math.min(HexToHSL("#" + primaryColor)[2] + 80, 90)}%;
--primary-130-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + secondaryColor)[1]}%) ${Math.min(HexToHSL("#" + secondaryColor)[2] + 80, 85)}%;
--primary-160-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.min(HexToHSL("#" + secondaryColor)[2] + 76.4, 82.5)}%;
--primary-200-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min(HexToHSL("#" + tertiaryColor)[2] + 80, 80)}%;
}
.emptyPage_feb902,
.scrollerContainer_dda72c,
.container__03ec9,
.header__71942 {
background-color: unset !important;
}
.container__6b2e5,
.container__03ec9,
.header__71942 {
background: transparent !important;
}${(Math.round(HexToHSL("#" + primaryColor)[2]) > 80) ? `\n\n/*Primary*/
.theme-dark .container_bd15da,
.theme-dark .body__616e6,
@ -545,12 +565,6 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
--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;
}
@ -657,26 +671,216 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
--mention-foreground: black !important;
}
/*End Accent*/`: ""}`;
return colorwayCss;
}
export function getPreset(primaryColor?: string, secondaryColor?: string, tertiaryColor?: string, accentColor?: string) {
function cyan(discordSaturation = false) {
export function getAutoPresets(accentColor?: string) {
function hueRotation() {
return `:root:root {
--cyan-accent-color: ${"#" + accentColor};
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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() {
return `:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${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)*${HexToHSL("#" + accentColor)[1]}%) ${Math.min(Math.round((HexToHSL("#" + accentColor)[2] + BrandLightDiffs[900]) * 10) / 10, 100)}%;
}`;
}
function materialYou() {
return `:root:root {
--brand-100-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*90.5%) 56.5;
--brand-130-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*102.2%) 55.2%;
--brand-160-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*98.8%) 53.2%;
--brand-200-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*97.3%) 51.2%;
--brand-230-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*101.6%) 49.3%;
--brand-260-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100.7%) 46.9%;
--brand-300-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100.6%) 44.2%;
--brand-330-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*99.4%) 39.9%;
--brand-345-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*99.5%) 37.1%;
--brand-360-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100.6%) 35.8%;
--brand-400-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100.6%) 30.1%;
--brand-430-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100.1%) 28.1%;
--brand-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*99.9%) 25.8%;
--brand-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*100%) 23%;
--brand-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*75.2%) 17.1%;
--brand-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*50.1%) 10.7%;
--brand-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.2%) 2.4%;
--brand-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.2%) -3.5%;
--brand-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*42%) -8.4%;
--brand-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.8%) -15.8%;
--brand-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.4%) -17.4%;
--brand-760-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.6%) -19.5%;
--brand-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*42.7%) -22.3%;
--brand-830-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*42.6%) -26.8%;
--brand-860-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*41.6%) -32.1%;
--brand-900-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*47.5%) -38.6%;
--primary-800-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*10%) 10.8%;
--primary-730-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*8%) 14.4%;
--primary-700-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*16%) 18%;
--primary-660-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*14%) 12.4%;
--primary-645-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*14%) 14.9%;
--primary-630-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 16%;
--primary-600-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 12%;
--primary-560-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 15.6%;
--primary-530-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 19.2%;
--primary-500-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 22.8%;
--primary-460-hsl: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*12%) 50%;
--primary-430: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
--primary-400: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
--primary-360: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
}
.emptyPage_feb902,
.scrollerContainer_dda72c,
.container__03ec9,
.header__71942 {
background-color: unset !important;
}`;
}
return {
hueRotation: {
name: "Hue Rotation",
id: "hueRotation",
preset: hueRotation
},
accentSwap: {
name: "Accent Swap",
id: "accentSwap",
preset: accentSwap
},
materialYou: {
name: "Material You",
id: "materialYou",
preset: materialYou
}
} as { [key: string]: { name: string, id: string, preset: () => string; }; };
}
export function getPreset(primaryColor?: string, secondaryColor?: string, tertiaryColor?: string, accentColor?: string): { [preset: string]: { name: string, preset: (...args: any) => string | { full: string, base: string; }, id: string, colors: string[]; }; } {
function cyanLegacy(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 cyan2(discordSaturation = false) {
function cyan(discordSaturation = false) {
return `:root:root {
--cyan-accent-color: ${"#" + accentColor};
--cyan-accent-color: #${accentColor};
--cyan-background-primary: hsl(${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${HexToHSL("#" + primaryColor)[2]}%/60%);
--cyan-second-layer: hsl(${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min(HexToHSL("#" + tertiaryColor)[2] + (3.6 * 2), 100)}%/60%);
}`;
}
function nexusRemastered(discordSaturation = false) {
return `:root:root {
--nexus-accent-color: #${accentColor};
--nexus-background-secondary: 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)}%);
--nexus-background-elevated: 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)}%);
--nexus-background-floating: 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)}%);
--nexus-background-tertiary: hsl(${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${HexToHSL("#" + tertiaryColor)[2]}%);
--home-background: hsl(${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${HexToHSL("#" + tertiaryColor)[2]}%);
--nexus-background-primary: hsl(${HexToHSL("#" + primaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + primaryColor)[1]}%) ${HexToHSL("#" + primaryColor)[2]}%);
--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)}%;
--primary-200: ${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[200])) * 10) / 10 : HexToHSL("#" + primaryColor)[1]}%), 90%)` : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")}
}
.theme-dark {
--background-tertiary: var(--primary-700) !important;
}
.theme-light {
--background-tertiary: var(--primary-200) !important;
}`;
}
function modular(discordSaturation = false) {
return `:root:root {
--brand-experiment: #${accentColor};
--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)}% !important;
--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)}% !important;
--primary-330: ${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[330])) * 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%)")};
--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%)")}
}`;
}
function virtualBoy(discordSaturation = false) {
return `:root:root {
--VBaccent: ${HexToHSL("#" + accentColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + accentColor)[1]}%) ${HexToHSL("#" + accentColor)[2]}%;
@ -685,14 +889,6 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
}`;
}
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]};
@ -724,78 +920,6 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
};
}
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 {
default: {
name: "Default",
@ -809,12 +933,18 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
id: "cyan",
colors: ["accent", "primary", "secondary"]
},
cyan2: {
name: "Cyan 2",
preset: cyan2,
id: "cyan2",
cyanLegacy: {
name: "Cyan 1 (Legacy)",
preset: cyanLegacy,
id: "cyanLegacy",
colors: ["accent", "primary", "secondary"]
},
nexusRemastered: {
name: "Nexus Remastered",
preset: nexusRemastered,
id: "nexusRemastered",
colors: ["accent", "primary", "secondary", "tertiary"]
},
virtualBoy: {
name: "Virtual Boy",
preset: virtualBoy,
@ -825,7 +955,7 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
name: "Modular",
preset: modular,
id: "modular",
colors: ["accent"]
colors: ["accent", "primary", "secondary", "tertiary"]
},
solana: {
name: "Solana",
@ -847,19 +977,24 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
},
hueRotation: {
name: "Hue Rotation",
preset: hueRotation,
preset: getAutoPresets(accentColor).hueRotation.preset,
id: "hueRotation",
colors: ["accent"]
},
accentSwap: {
name: "Accent Swap",
preset: accentSwap,
preset: getAutoPresets(accentColor).accentSwap.preset,
id: "accentSwap",
colors: ["accent"]
},
materialYou: {
name: "Material You",
preset: getAutoPresets(accentColor).materialYou.preset,
id: "materialYou",
colors: ["accent"]
}
};
}
export const gradientPresetIds = [
"gradientType1",
"gradientType2"

View file

@ -8,25 +8,38 @@ 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 } from "@utils/constants";
import { openModal } from "@utils/modal";
import { Flex } from "@components/Flex";
import { Devs, EquicordDevs } from "@utils/constants";
import { ModalProps, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { findByProps } from "@webpack";
import {
Button,
Clipboard,
Forms,
Heading,
i18n,
SettingsRouter,
Toasts
} from "@webpack/common";
import { CSSProperties } from "react";
import { Plugins } from "Vencord";
import AutoColorwaySelector from "./components/AutoColorwaySelector";
import ColorPickerModal from "./components/ColorPicker";
import ColorwaysButton from "./components/ColorwaysButton";
import CreatorModal from "./components/CreatorModal";
import Selector from "./components/Selector";
import ManageColorwaysPage from "./components/SettingsTabs/ManageColorwaysPage";
import OnDemandWaysPage from "./components/SettingsTabs/OnDemandPage";
import SettingsPage from "./components/SettingsTabs/SettingsPage";
import SourceManager from "./components/SettingsTabs/SourceManager";
import Store from "./components/SettingsTabs/Store";
import Spinner from "./components/Spinner";
import { defaultColorwaySource } from "./constants";
import { generateCss, getAutoPresets } from "./css";
import style from "./style.css?managed";
import { ColorPickerProps } from "./types";
import { ColorPickerProps, ColorwayObject } from "./types";
import { colorToHex, hexToString } from "./utils";
export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
return <Spinner className="colorways-creator-module-warning" />;
@ -41,7 +54,10 @@ export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
onDemandWaysTintedText,
useThinMenuButton,
onDemandWaysDiscordSaturation,
onDemandWaysColorArray
onDemandWaysOsAccentColor,
activeColorwayObject,
selectorViewMode,
showLabelsInSelectorGridView
] = await DataStore.getMany([
"customColorways",
"colorwaySourceFiles",
@ -50,24 +66,49 @@ export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
"onDemandWaysTintedText",
"useThinMenuButton",
"onDemandWaysDiscordSaturation",
"onDemandWaysColorArray"
"onDemandWaysOsAccentColor",
"activeColorwayObject",
"selectorViewMode",
"showLabelsInSelectorGridView"
]);
const defaults = [
{ name: "customColorways", checkedValue: customColorways, defaults: [] },
{ name: "colorwaySourceFiles", checkedValue: colorwaySourceFiles, defaults: [defaultColorwaySource] },
{ name: "showColorwaysButton", checkedValue: showColorwaysButton, defaults: false },
{ name: "onDemandWays", checkedValue: onDemandWays, defaults: false },
{ name: "onDemandWaysTintedText", checkedValue: onDemandWaysTintedText, defaults: true },
{ name: "useThinMenuButton", checkedValue: useThinMenuButton, defaults: false },
{ name: "onDemandWaysDiscordSaturation", checkedValue: onDemandWaysDiscordSaturation, defaults: false },
{ name: "onDemandWaysColorArray", checkedValue: onDemandWaysColorArray, defaults: ["313338", "2b2d31", "1e1f22", "5865f2"] }
{ name: "showColorwaysButton", value: showColorwaysButton, default: false },
{ name: "onDemandWays", value: onDemandWays, default: false },
{ name: "onDemandWaysTintedText", value: onDemandWaysTintedText, default: true },
{ name: "useThinMenuButton", value: useThinMenuButton, default: false },
{ name: "onDemandWaysDiscordSaturation", value: onDemandWaysDiscordSaturation, default: false },
{ name: "onDemandWaysOsAccentColor", value: onDemandWaysOsAccentColor, default: false },
{ name: "activeColorwayObject", value: activeColorwayObject, default: { id: null, css: null, sourceType: null, source: null } },
{ name: "selectorViewMode", value: selectorViewMode, default: "grid" },
{ name: "showLabelsInSelectorGridView", value: showLabelsInSelectorGridView, default: false }
];
defaults.forEach(({ name, checkedValue, defaults }) => {
if (!checkedValue) DataStore.set(name, defaults);
defaults.forEach(({ name, value, default: def }) => {
if (!value) DataStore.set(name, def);
});
if (customColorways) {
if (!customColorways[0].colorways) {
DataStore.set("customColorways", [{ name: "Custom", colorways: customColorways }]);
}
} else {
DataStore.set("customColorways", []);
}
if (colorwaySourceFiles) {
if (typeof colorwaySourceFiles[0] === "string") {
DataStore.set("colorwaySourceFiles", colorwaySourceFiles.map((sourceURL: string, i: number) => {
return { name: sourceURL === defaultColorwaySource ? "Project Colorway" : `Source #${i}`, url: sourceURL };
}));
}
} else {
DataStore.set("colorwaySourceFiles", [{
name: "Project Colorway",
url: defaultColorwaySource
}]);
}
})();
export const ColorwayCSS = {
@ -85,17 +126,14 @@ export const ColorwayCSS = {
};
export const versionData = {
pluginVersion: "5.6.5.1",
creatorVersion: "1.19",
pluginVersion: "5.7.0b1",
creatorVersion: "1.20",
};
export default definePlugin({
name: "DiscordColorways",
description: "A plugin that offers easy access to simple color schemes/themes for Discord, also known as Colorways",
authors: [{
name: "DaBluLite",
id: 582170007505731594n
}, Devs.ImLvna],
authors: [EquicordDevs.DaBluLite, Devs.ImLvna],
dependencies: ["ServerListAPI", "MessageAccessoriesAPI"],
pluginVersion: versionData.pluginVersion,
creatorVersion: versionData.creatorVersion,
@ -106,6 +144,22 @@ export default definePlugin({
"Open Settings": () => SettingsRouter.open("ColorwaysSettings"),
"Open On-Demand Settings": () => SettingsRouter.open("ColorwaysOnDemand"),
"Manage Colorways...": () => SettingsRouter.open("ColorwaysManagement"),
"Change Auto Colorway Preset": async () => {
const [
activeAutoPreset,
activeColorwayObject
] = await DataStore.getMany([
"activeAutoPreset",
"activeColorwayObject"
]);
openModal((props: ModalProps) => <AutoColorwaySelector autoColorwayId={activeAutoPreset} modalProps={props} onChange={autoPresetId => {
if (activeColorwayObject.id === "Auto") {
const demandedColorway = getAutoPresets(colorToHex(getComputedStyle(document.body).getPropertyValue("--os-accent-color")))[autoPresetId].preset();
DataStore.set("activeColorwayObject", { id: "Auto", css: demandedColorway, sourceType: "online", source: null });
ColorwayCSS.set(demandedColorway);
}
}} />);
}
},
patches: [
// Credits to Kyuuhachi for the BetterSettings plugin patches
@ -136,9 +190,23 @@ export default definePlugin({
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
match: /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/,
match: /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/,
replace: "...$self.makeSettingsCategories($1),$&"
}
},
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
}
},
{
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
replace: "$2.default.open($1);return;"
}
}
],
@ -146,12 +214,67 @@ export default definePlugin({
ColorPicker = e;
},
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) {
const firstChild = settings?.[0];
// lowest two elements... sanity backup
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
const settingsLocation = "belowNitro";
if (!header) return;
const names = {
top: i18n.Messages.USER_SETTINGS,
aboveNitro: i18n.Messages.BILLING_SETTINGS,
belowNitro: i18n.Messages.APP_SETTINGS,
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
};
return header === names[settingsLocation];
},
patchedSettings: new WeakSet(),
addSettings(elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: Record<string, unknown>) {
if (this.patchedSettings.has(elements) || !this.isRightSpot(element)) return;
this.patchedSettings.add(elements);
elements.push(...this.makeSettingsCategories(sectionTypes));
},
makeSettingsCategories(SectionTypes: Record<string, unknown>) {
const { headerText, header } = findByProps("headerText", "header", "separator");
return [
{
section: SectionTypes.HEADER,
section: SectionTypes.CUSTOM,
label: "Discord Colorways",
className: "vc-settings-header"
className: "vc-settings-header",
element: () => <div className={header} style={{
display: "flex",
justifyContent: "space-between",
padding: "6px 10px"
}}>
<Heading
variant="eyebrow"
className={headerText}
style={{
"text-wrap": "wrap",
color: "var(--channels-default)"
} as CSSProperties}
>
Discord Colorways
</Heading>
<Heading
variant="eyebrow"
className={headerText}
style={{
marginLeft: "auto",
color: "var(--channels-default)"
}}
>
v{(Plugins.plugins.DiscordColorways as any).pluginVersion}
</Heading>
</div>
},
{
section: "ColorwaysSelector",
@ -165,6 +288,12 @@ export default definePlugin({
element: SettingsPage,
className: "dc-colorway-settings"
},
{
section: "ColorwaysSourceManager",
label: "Sources",
element: SourceManager,
className: "dc-colorway-sources-manager"
},
{
section: "ColorwaysOnDemand",
label: "On-Demand",
@ -172,10 +301,10 @@ export default definePlugin({
className: "dc-colorway-ondemand"
},
{
section: "ColorwaysManagement",
label: "Manage...",
element: ManageColorwaysPage,
className: "dc-colorway-management"
section: "ColorwaysStore",
label: "Store",
element: Store,
className: "dc-colorway-store"
},
{
section: SectionTypes.DIVIDER
@ -189,19 +318,98 @@ export default definePlugin({
addServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);
enableStyle(style);
ColorwayCSS.set((await DataStore.get("actveColorway")) || "");
ColorwayCSS.set((await DataStore.get("activeColorwayObject") as ColorwayObject).css || "");
addAccessory("colorways-btn", props => String(props.message.content).match(/colorway:[0-9a-f]{0,100}/) ? <Button
onClick={() => openModal(modalProps => <CreatorModal
modalProps={modalProps}
colorwayID={String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)![0]}
/>)}
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
>
Add this Colorway...
</Button> : null);
addAccessory("colorways-btn", props => {
if (String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)) {
return <Flex flexDirection="column">
{String(props.message.content).match(/colorway:[0-9a-f]{0,100}/g)?.map((colorID: string) => {
colorID = hexToString(colorID.split("colorway:")[1]);
return <div className="colorwayMessage">
<div className="discordColorwayPreviewColorContainer" style={{ width: "56px", height: "56px", marginRight: "16px" }}>
{(() => {
if (colorID) {
if (!colorID.includes(",")) {
throw new Error("Invalid Colorway ID");
} else {
return colorID.split("|").filter(string => string.includes(",#"))[0].split(/,#/).map((color: string) => <div className="discordColorwayPreviewColor" style={{ backgroundColor: `#${colorToHex(color)}` }} />);
}
} else return null;
})()}
</div>
<div className="colorwayMessage-contents">
<Forms.FormTitle>Colorway{/n:([A-Za-z0-9]+( [A-Za-z0-9]+)+)/i.exec(colorID) ? `: ${/n:([A-Za-z0-9]+( [A-Za-z0-9]+)+)/i.exec(colorID)![1]}` : ""}</Forms.FormTitle>
<Flex>
<Button
onClick={() => openModal(modalProps => <CreatorModal
modalProps={modalProps}
colorwayID={colorID}
/>)}
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
look={Button.Looks.FILLED}
>
Add this Colorway...
</Button>
<Button
onClick={() => {
Clipboard.copy(colorID);
Toasts.show({
message: "Copied Colorway ID Successfully",
type: 1,
id: "copy-colorway-id-notify",
});
}}
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
look={Button.Looks.FILLED}
>
Copy Colorway ID
</Button>
<Button
onClick={() => {
if (!hexToString(colorID).includes(",")) {
throw new Error("Invalid Colorway ID");
} else {
DataStore.set("activeColorwayObject", {
id: "Temporary Colorway", css: generateCss(
colorToHex(hexToString(colorID).split(/,#/)[1]),
colorToHex(hexToString(colorID).split(/,#/)[2]),
colorToHex(hexToString(colorID).split(/,#/)[3]),
colorToHex(hexToString(colorID).split(/,#/)[0]),
true,
true,
undefined,
"Temporary Colorway"
), sourceType: "temporary", source: null
});
ColorwayCSS.set(generateCss(
colorToHex(hexToString(colorID).split(/,#/)[1]),
colorToHex(hexToString(colorID).split(/,#/)[2]),
colorToHex(hexToString(colorID).split(/,#/)[3]),
colorToHex(hexToString(colorID).split(/,#/)[0]),
true,
true,
undefined,
"Temporary Colorway"
));
}
}}
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
look={Button.Looks.FILLED}
>
Apply temporarily
</Button>
</Flex>
</div>
</div>;
})}
</Flex>;
} else {
return null;
}
});
},
stop() {
removeServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);

File diff suppressed because one or more lines are too long

View file

@ -17,9 +17,10 @@ export interface Colorway {
authorID: string,
colors?: string[],
isGradient?: boolean,
sourceUrl?: string,
sourceName?: string,
linearGradient?: string;
sourceType?: "online" | "offline" | "temporary" | null,
source?: string,
linearGradient?: string,
preset?: string;
}
export interface ColorPickerProps {
@ -29,3 +30,35 @@ export interface ColorPickerProps {
label: any;
onChange(color: number): void;
}
export interface ColorwayObject {
id: string | null,
css: string | null,
sourceType: "online" | "offline" | "temporary" | null,
source: string | null | undefined;
}
export interface SourceObject {
type: "online" | "offline" | "temporary",
source: string,
colorways: Colorway[];
}
export enum SortOptions {
NAME_AZ = 1,
NAME_ZA = 2,
SOURCE_AZ = 3,
SOURCE_ZA = 4
}
export interface StoreObject {
sources: StoreItem[];
}
export interface StoreItem {
name: string,
id: string,
description: string,
url: string,
authorGh: string;
}