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,16 +14,20 @@ 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" }}>
<Forms.FormTitle>
Presets:
</Forms.FormTitle>
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix style={{ paddingRight: "2px" }}>
<ScrollerThin orientation="vertical" paddingFix style={{ paddingRight: "2px", marginBottom: "20px", maxHeight: "250px" }}>
{Object.values(getPreset()).map(pre => {
return <div className="colorwaysCreator-settingItm colorwaysCreator-preset" onClick={() => {
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">
@ -30,10 +35,10 @@ export default function ({ modalProps, onSettings, presetId, hasTintedText, hasD
{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>
</div>;
})}
</ScrollerThin>
</div>
<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);
}
);
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,85 +5,128 @@
*/
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] }}
onClick={() => {
Clipboard.copy(colorwayProps[color]);
Toasts.show({
message: "Copied color successfully",
type: 1,
id: "copy-colorway-color-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>
<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={{ flex: "0 0 auto", maxWidth: "236px" }}
style={{ width: "100%" }}
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 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({
@ -95,57 +138,13 @@ export default function ({
>
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" }}
style={{ width: "100%" }}
onClick={() => {
Clipboard.copy(colorwayProps["dc-import"]);
Clipboard.copy(colorway["dc-import"]);
Toasts.show({
message: "Copied CSS to Clipboard",
type: 1,
@ -153,93 +152,111 @@ export default function ({
});
}}
>
Copy
Copy CSS
</Button>
{discrimProps ? <Button
<Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
style={{ width: "100%" }}
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();
}
});
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
</Button> : <Button
Update CSS
</Button>
{colorway.sourceType === "offline" && <Button
color={Button.Colors.PRIMARY}
size={Button.Sizes.MEDIUM}
look={Button.Looks.OUTLINED}
style={{ flex: "0 0 auto" }}
style={{ width: "100%" }}
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]);
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();
}
});
}} />);
}}
>
Update
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>
<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>
</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>;
}

View file

@ -8,33 +8,37 @@
import * as DataStore from "@api/DataStore";
import { Flex } from "@components/Flex";
import { DeleteIcon, PlusIcon } from "@components/Icons";
import { SettingsTab } from "@components/VencordSettings/shared";
import { ModalContent, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { findByProps, findByPropsLazy } from "@webpack";
import {
Button,
ButtonLooks,
Clipboard,
Forms,
Menu,
Popout,
ScrollerThin,
Select,
SettingsRouter,
Text,
TextInput,
Toasts,
Tooltip,
useCallback,
useEffect,
useState,
useState
} from "@webpack/common";
import { ReactNode } from "react";
import { ColorwayCSS } from "..";
import { defaultColorwaySource, fallbackColorways } from "../constants";
import { generateCss, gradientBase } from "../css";
import { Colorway } from "../types";
import { colorToHex } from "../utils";
import { generateCss, getAutoPresets, gradientBase } from "../css";
import { Colorway, ColorwayObject, SortOptions, SourceObject } from "../types";
import { colorToHex, getHex, stringToHex } from "../utils";
import AutoColorwaySelector from "./AutoColorwaySelector";
import ColorPickerModal from "./ColorPicker";
import CreatorModal from "./CreatorModal";
import { CloseIcon } from "./Icons";
import { CodeIcon, IDIcon, MoreIcon, PalleteIcon } from "./Icons";
import ColorwayInfoModal from "./InfoModal";
const { SelectionCircle } = findByPropsLazy("SelectionCircle");
@ -55,7 +59,7 @@ function SelectorContainer({ children, isSettings, modalProps }: { children: Rea
function SelectorHeader({ children, isSettings }: { children: ReactNode, isSettings?: boolean; }) {
if (!isSettings) {
return <ModalHeader>
return <ModalHeader separator={false}>
{children}
</ModalHeader>;
} else {
@ -75,106 +79,73 @@ function SelectorContent({ children, isSettings }: { children: ReactNode, isSett
export default function ({
modalProps,
isSettings
isSettings,
onSelected
}: {
modalProps: ModalProps,
isSettings?: boolean;
isSettings?: boolean,
onSelected?: (colorways: Colorway[]) => void;
}): JSX.Element | any {
const [currentColorway, setCurrentColorway] = useState<string>("");
const [colorways, setColorways] = useState<Colorway[]>([]);
const [thirdPartyColorways, setThirdPartyColorways] = useState<Colorway[]>([]);
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
const [searchString, setSearchString] = useState<string>("");
const [loaderHeight, setLoaderHeight] = useState<string>("2px");
const [visibility, setVisibility] = useState<string>("all");
const [showReloadMenu, setShowReloadMenu] = useState(false);
let visibleColorwayArray: Colorway[];
const [colorwayData, setColorwayData] = useState<SourceObject[]>([]);
const [searchValue, setSearchValue] = useState<string>("");
const [sortBy, setSortBy] = useState<SortOptions>(SortOptions.NAME_AZ);
const [activeColorwayObject, setActiveColorwayObject] = useState<ColorwayObject>({ id: null, css: null, sourceType: null, source: null });
const [customColorwayData, setCustomColorwayData] = useState<SourceObject[]>([]);
const [loaderHeight, setLoaderHeight] = useState<"2px" | "0px">("2px");
const [visibleSources, setVisibleSources] = useState<string>("all");
const [showReloadMenu, setShowReloadMenu] = useState<boolean>(false);
const [viewMode, setViewMode] = useState<"list" | "grid">("grid");
const [showLabelsInSelectorGridView, setShowLabelsInSelectorGridView] = useState<boolean>(false);
const [showSortingMenu, setShowSotringMenu] = useState<boolean>(false);
switch (visibility) {
case "all":
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
case "official":
visibleColorwayArray = [...colorways];
break;
case "3rdparty":
visibleColorwayArray = [...thirdPartyColorways];
break;
case "custom":
visibleColorwayArray = [...customColorways];
break;
default:
visibleColorwayArray = [...colorways, ...thirdPartyColorways, ...customColorways];
break;
}
const { item: radioBarItem, itemFilled: radioBarItemFilled } = findByProps("radioBar");
const filters = [
{
name: "All",
id: "all",
sources: [...colorwayData, ...customColorwayData]
},
...colorwayData.map((source) => ({
name: source.source,
id: source.source.toLowerCase().replaceAll(" ", "-"),
sources: [source]
})),
...customColorwayData.map((source) => ({
name: source.source,
id: source.source.toLowerCase().replaceAll(" ", "-"),
sources: [source]
}))
];
async function loadUI(force?: boolean) {
setActiveColorwayObject(await DataStore.get("activeColorwayObject") as ColorwayObject);
setViewMode(await DataStore.get("selectorViewMode") as "list" | "grid");
setShowLabelsInSelectorGridView(await DataStore.get("showLabelsInSelectorGridView") as boolean);
setLoaderHeight("0px");
setCustomColorwayData((await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).map((colorSrc: { name: string, colorways: Colorway[], id?: string; }) => ({ type: "offline", source: colorSrc.name, colorways: colorSrc.colorways })));
const onlineSources: { name: string, url: string; }[] = await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[];
async function loadUI() {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url)
onlineSources.map((source) =>
fetch(source.url, force ? { cache: "no-store" } : {})
)
);
const data = await Promise.all(
responses.map((res: Response) =>
res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; })
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
}
const cached_loadUI = useCallback(loadUI, [setColorways, setCustomColorways, setCurrentColorway]);
async function searchColorways(e: string) {
if (!e) {
cached_loadUI();
return;
}
const colorwaySourceFiles = await DataStore.get("colorwaySourceFiles");
const data = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url).then((res) => res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; }))
)
);
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.get("customColorways");
var results: Colorway[] = [];
(colorways || fallbackColorways).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
results.push(Colorway);
});
var thirdPartyResults: Colorway[] = [];
(thirdPartyColorwaysArr).find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
thirdPartyResults.push(Colorway);
});
var customResults: Colorway[] = [];
baseData.find((Colorway: Colorway) => {
if (Colorway.name.toLowerCase().includes(e.toLowerCase()))
customResults.push(Colorway);
});
setColorways(results);
setThirdPartyColorways(thirdPartyResults);
setCustomColorways(customResults);
setColorwayData(await Promise.all(
responses
.map((res, i) => ({ response: res, name: onlineSources[i].name }))
.map((res: { response: Response, name: string; }) =>
res.response.json().then(dt => ({ colorways: dt.colorways as Colorway[], source: res.name, type: "online" })).catch(() => ({ colorways: [] as Colorway[], source: res.name, type: "online" }))
)) as { type: "online" | "offline" | "temporary", source: string, colorways: Colorway[]; }[]);
}
useEffect(() => {
if (!searchString) {
cached_loadUI();
if (!searchValue) {
loadUI();
}
setLoaderHeight("0px");
}, [searchString]);
}, []);
function ReloadPopout(onClose: () => void) {
return (
@ -185,34 +156,70 @@ export default function ({
<Menu.MenuItem
id="dc-force-reload"
label="Force Reload"
action={async () => {
setLoaderHeight("2px");
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
action={() => loadUI(true)}
/>
</Menu.Menu>
);
const responses: Response[] = await Promise.all(
colorwaySourceFiles.map((url: string) =>
fetch(url, { cache: "no-store" })
)
);
const data = await Promise.all(
responses.map((res: Response) => {
setLoaderHeight("0px");
return res.json().then(dt => { return { colorways: dt.colorways, url: res.url }; }).catch(() => { return { colorways: [], url: res.url }; });
}
));
const colorways = data.flatMap((json) => json.url === defaultColorwaySource ? json.colorways : []);
const thirdPartyColorwaysArr = data.flatMap((json) => json.url !== defaultColorwaySource ? json.colorways : []);
const baseData = await DataStore.getMany([
"customColorways",
"actveColorwayID",
]);
setColorways(colorways || fallbackColorways);
setThirdPartyColorways(thirdPartyColorwaysArr);
setCustomColorways(baseData[0]);
setCurrentColorway(baseData[1]);
function SortingPopout(onClose: () => void) {
return (
<Menu.Menu
navId="dc-selector-options-menu"
onClose={onClose}
>
<Menu.MenuGroup label="View">
<Menu.MenuRadioItem
group="selector-viewMode"
id="selector-viewMode_grid"
label="Grid"
checked={viewMode === "grid"}
action={() => {
setViewMode("grid");
DataStore.set("selectorViewMode", "grid");
}}
/>
<Menu.MenuRadioItem
group="selector-viewMode"
id="selector-viewMode_list"
label="List"
checked={viewMode === "list"}
action={() => {
setViewMode("list");
DataStore.set("selectorViewMode", "list");
}}
/>
</Menu.MenuGroup>
<Menu.MenuGroup label="Sort By">
<Menu.MenuRadioItem
group="sort-colorways"
id="sort-colorways_name-az"
label="Name (A-Z)"
checked={sortBy === SortOptions.NAME_AZ}
action={() => setSortBy(SortOptions.NAME_AZ)}
/>
<Menu.MenuRadioItem
group="sort-colorways"
id="sort-colorways_name-za"
label="Name (Z-A)"
checked={sortBy === SortOptions.NAME_ZA}
action={() => setSortBy(SortOptions.NAME_ZA)}
/>
<Menu.MenuRadioItem
group="sort-colorways"
id="sort-colorways_source-az"
label="Source (A-Z)"
checked={sortBy === SortOptions.SOURCE_AZ}
action={() => setSortBy(SortOptions.SOURCE_AZ)}
/>
<Menu.MenuRadioItem
group="sort-colorways"
id="sort-colorways_source-za"
label="Source (Z-A)"
checked={sortBy === SortOptions.SOURCE_ZA}
action={() => setSortBy(SortOptions.SOURCE_ZA)}
/>
</Menu.MenuGroup>
</Menu.Menu>
);
}
@ -220,34 +227,14 @@ export default function ({
return (
<SelectorContainer modalProps={modalProps} isSettings={isSettings}>
<SelectorHeader isSettings={isSettings}>
<Select className="colorwaySelector-pill colorwaySelector-pill_select" options={[{
value: "all",
label: "All"
},
{
value: "official",
label: "Official"
},
{
value: "3rdparty",
label: "3rd-Party"
},
{
value: "custom",
label: "Custom"
}]} select={value => {
setVisibility(value);
}} isSelected={value => visibility === value} serialize={String} />
<TextInput
inputClassName="colorwaySelector-searchInput"
className="colorwaySelector-search"
placeholder="Search for Colorways..."
value={searchString}
onChange={(e: string) => [searchColorways, setSearchString].forEach(t => t(e))}
value={searchValue}
onChange={setSearchValue}
/>
<Tooltip text="Refresh Colorways...">
{({ onMouseEnter, onMouseLeave }) => {
return <Popout
{({ onMouseEnter, onMouseLeave }) => <Popout
position="bottom"
align="right"
animation={Popout.Animation.NONE}
@ -255,8 +242,7 @@ export default function ({
onRequestClose={() => setShowReloadMenu(false)}
renderPopout={() => ReloadPopout(() => setShowReloadMenu(false))}
>
{(_, { isShown }) => (
<Button
{(_, { isShown }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
@ -267,9 +253,9 @@ export default function ({
onMouseLeave={isShown ? () => { } : onMouseLeave}
onClick={() => {
setLoaderHeight("2px");
cached_loadUI().then(() => setLoaderHeight("0px"));
loadUI().then(() => setLoaderHeight("0px"));
}}
onContextMenu={() => { onMouseLeave(); setShowReloadMenu(v => !v); }}
onContextMenu={() => { onMouseLeave(); setShowReloadMenu(!showReloadMenu); }}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -287,41 +273,16 @@ export default function ({
width="24"
height="24"
/>
<path d="M6.351,6.351C7.824,4.871,9.828,4,12,4c4.411,0,8,3.589,8,8h2c0-5.515-4.486-10-10-10 C9.285,2,6.779,3.089,4.938,4.938L3,3v6h6L6.351,6.351z" />
<path d="M17.649,17.649C16.176,19.129,14.173,20,12,20c-4.411,0-8-3.589-8-8H2c0,5.515,4.486,10,10,10 c2.716,0,5.221-1.089,7.062-2.938L21,21v-6h-6L17.649,17.649z" />
</svg>
</Button>
)}
</Popout>;
}}
</Tooltip>
{!isSettings ? <Tooltip text="Open Settings">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
id="colorway-opensettings"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
SettingsRouter.open("ColorwaysSettings");
modalProps.onClose();
}}
>
<svg
aria-hidden="true"
role="img"
width="20"
height="20"
style={{ padding: "6px", boxSizing: "content-box" }}
viewBox="0 0 24 24"
>
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z" />
<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> : <></>}
</Popout>}
</Tooltip>
<Tooltip text="Create Colorway...">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
@ -333,25 +294,35 @@ export default function ({
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <CreatorModal
modalProps={props}
loadUIProps={cached_loadUI}
loadUIProps={loadUI}
/>)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="20"
height="20"
style={{ padding: "6px", boxSizing: "content-box" }}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"
/>
</svg>
<PlusIcon width={20} height={20} style={{ padding: "6px", boxSizing: "content-box" }} />
</Button>}
</Tooltip>
<Tooltip text="Selector Options">
{({ onMouseEnter, onMouseLeave }) => <Popout
position="bottom"
align="right"
animation={Popout.Animation.NONE}
shouldShow={showSortingMenu}
onRequestClose={() => setShowSotringMenu(false)}
renderPopout={() => SortingPopout(() => setShowSotringMenu(false))}
>
{(_, { isShown }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
onMouseEnter={isShown ? () => { } : onMouseEnter}
onMouseLeave={isShown ? () => { } : onMouseLeave}
onClick={() => { onMouseLeave(); setShowSotringMenu(!showSortingMenu); }}
>
<MoreIcon width={20} height={20} style={{ padding: "6px", boxSizing: "content-box" }} />
</Button>}
</Popout>}
</Tooltip>
<Tooltip text="Open Color Stealer">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
@ -364,126 +335,226 @@ export default function ({
onMouseLeave={onMouseLeave}
onClick={() => openModal((props) => <ColorPickerModal modalProps={props} />)}
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" style={{ padding: "6px", boxSizing: "content-box" }} fill="currentColor" viewBox="0 0 16 16">
<path d="M12.433 10.07C14.133 10.585 16 11.15 16 8a8 8 0 1 0-8 8c1.996 0 1.826-1.504 1.649-3.08-.124-1.101-.252-2.237.351-2.92.465-.527 1.42-.237 2.433.07zM8 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM5 6.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm.5 6.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z" />
</svg>
<PalleteIcon width={20} height={20} style={{ padding: "6px", boxSizing: "content-box" }} />
</Button>}
</Tooltip>
{!isSettings ? <Tooltip text="Close">
{isSettings ? <Select
className={"colorwaySelector-sources " + ButtonLooks.OUTLINED + " colorwaySelector-sources_settings"}
look={1}
popoutClassName="colorwaySelector-sourceSelect"
options={filters.map(filter => ({ label: filter.name, value: (filter.id as string) }))}
select={value => setVisibleSources(value)}
isSelected={value => visibleSources === value}
serialize={String}
popoutPosition="bottom" /> : <></>}
</SelectorHeader>
<SelectorContent isSettings={isSettings}>
<div className="colorwaysLoader-barContainer"><div className="colorwaysLoader-bar" style={{ height: loaderHeight }} /></div>
<ScrollerThin style={{ maxHeight: isSettings ? "unset" : "450px" }} className={"ColorwaySelectorWrapper " + (viewMode === "grid" ? "ColorwaySelectorWrapper-grid" : "ColorwaySelectorWrapper-list") + (showLabelsInSelectorGridView ? " colorwaySelector-gridWithLabels" : "")}>
{activeColorwayObject.sourceType === "temporary" && <Tooltip text="Temporary Colorway">
{({ onMouseEnter, onMouseLeave }) => <div
className={viewMode === "grid" ? "discordColorway" : `${radioBarItem} ${radioBarItemFilled} discordColorway-listItem`}
id="colorway-Temporary"
aria-checked={activeColorwayObject.id === "Auto" && activeColorwayObject.source === null}
onMouseEnter={viewMode === "grid" ? onMouseEnter : () => { }}
onMouseLeave={viewMode === "grid" ? onMouseLeave : () => { }}
onClick={async () => {
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
setActiveColorwayObject({ id: null, css: null, sourceType: null, source: null });
ColorwayCSS.remove();
}}
>
{viewMode === "list" && <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" />
{activeColorwayObject.id === "Temporary Colorway" && activeColorwayObject.sourceType === "temporary" && <circle cx="12" cy="12" r="5" fill="currentColor" />}
</svg>}
<div className="discordColorwayPreviewColorContainer">
<div
className="discordColorwayPreviewColor"
style={{ backgroundColor: "var(--brand-500)" }} />
<div
className="discordColorwayPreviewColor"
style={{ backgroundColor: "var(--background-primary)" }} />
<div
className="discordColorwayPreviewColor"
style={{ backgroundColor: "var(--background-secondary)" }} />
<div
className="discordColorwayPreviewColor"
style={{ backgroundColor: "var(--background-tertiary)" }} />
</div>
<div className="colorwaySelectionCircle">
{(activeColorwayObject.id === "Temporary Colorway" && activeColorwayObject.sourceType === "temporary" && viewMode === "grid") && <SelectionCircle />}
</div>
{(showLabelsInSelectorGridView || viewMode === "list") && <Text className={"colorwayLabel" + ((showLabelsInSelectorGridView && viewMode === "grid") ? " labelInGrid" : "")}>Temporary Colorway</Text>}
{viewMode === "list" && <>
<Tooltip text="Add Colorway">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
id="colorwaySelector-pill_closeSelector"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => modalProps.onClose()}
onClick={async e => {
e.stopPropagation();
const colorwayID = stringToHex(`#${colorToHex(getHex(getComputedStyle(document.body).getPropertyValue("--brand-500")))},#${colorToHex(getHex(getComputedStyle(document.body).getPropertyValue("--background-primary")))},#${colorToHex(getHex(getComputedStyle(document.body).getPropertyValue("--background-secondary")))},#${colorToHex(getHex(getComputedStyle(document.body).getPropertyValue("--background-tertiary")))}`);
openModal(props => <CreatorModal modalProps={props} colorwayID={colorwayID} loadUIProps={loadUI} />);
}}
>
<CloseIcon style={{ padding: "6px", boxSizing: "content-box" }} width={20} height={20} />
<PlusIcon width={20} height={20} />
</Button>}
</Tooltip>
</>}
</div>}
</Tooltip>}
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") && ["all", "official"].includes(visibleSources) && "auto".includes(searchValue.toLowerCase()) ? <Tooltip text="Auto">
{({ onMouseEnter, onMouseLeave }) => <div
className={viewMode === "grid" ? "discordColorway" : `${radioBarItem} ${radioBarItemFilled} discordColorway-listItem`}
id="colorway-Auto"
aria-checked={activeColorwayObject.id === "Auto" && activeColorwayObject.source === null}
onMouseEnter={viewMode === "grid" ? onMouseEnter : () => { }}
onMouseLeave={viewMode === "grid" ? onMouseLeave : () => { }}
onClick={async () => {
const activeAutoPreset = await DataStore.get("activeAutoPreset");
if (activeColorwayObject.id === "Auto") {
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
setActiveColorwayObject({ id: null, css: null, sourceType: null, source: null });
ColorwayCSS.remove();
} else {
if (!activeAutoPreset) {
openModal((props: ModalProps) => <AutoColorwaySelector autoColorwayId="" modalProps={props} onChange={autoPresetId => {
const demandedColorway = getAutoPresets(colorToHex(getComputedStyle(document.body).getPropertyValue("--os-accent-color")).slice(0, 6))[autoPresetId].preset();
ColorwayCSS.set(demandedColorway);
DataStore.set("activeColorwayObject", { id: "Auto", css: demandedColorway, sourceType: "online", source: null });
setActiveColorwayObject({ id: "Auto", css: demandedColorway, sourceType: "online", source: null });
}} />);
} else {
const autoColorway = getAutoPresets(colorToHex(getComputedStyle(document.body).getPropertyValue("--os-accent-color")).slice(0, 6))[activeAutoPreset].preset();
DataStore.set("activeColorwayObject", { id: "Auto", css: autoColorway, sourceType: "online", source: null });
setActiveColorwayObject({ id: "Auto", css: autoColorway, sourceType: "online", source: null });
ColorwayCSS.set(autoColorway);
}
}
}}
>
{viewMode === "list" && <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" />
{activeColorwayObject.id === "Auto" && activeColorwayObject.source === null && <circle cx="12" cy="12" r="5" fill="currentColor" />}
</svg>}
<div className="discordColorwayPreviewColorContainer" style={{ backgroundColor: "var(--os-accent-color)" }} />
<div className="colorwaySelectionCircle">
{(activeColorwayObject.id === "Auto" && activeColorwayObject.source === null && viewMode === "grid") && <SelectionCircle />}
</div>
{(showLabelsInSelectorGridView || viewMode === "list") && <Text className={"colorwayLabel" + ((showLabelsInSelectorGridView && viewMode === "grid") ? " labelInGrid" : "")}>Auto</Text>}
<div
className="colorwayInfoIconContainer"
onClick={async (e) => {
e.stopPropagation();
const activeAutoPreset = await DataStore.get("activeAutoPreset");
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")).slice(0, 6))[autoPresetId].preset();
DataStore.set("activeColorwayObject", { id: "Auto", css: demandedColorway, sourceType: "online", source: null });
setActiveColorwayObject({ id: "Auto", css: demandedColorway, sourceType: "online", source: null });
ColorwayCSS.set(demandedColorway);
}
}} />);
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" style={{ margin: "4px" }} viewBox="0 0 24 24" fill="currentColor">
<path d="M 21.2856,9.6 H 24 v 4.8 H 21.2868 C 20.9976,15.5172 20.52,16.5576 19.878,17.4768 L 21.6,19.2 19.2,21.6 17.478,19.8768 c -0.9216,0.642 -1.9596,1.1208 -3.078,1.4088 V 24 H 9.6 V 21.2856 C 8.4828,20.9976 7.4436,20.5188 6.5232,19.8768 L 4.8,21.6 2.4,19.2 4.1232,17.4768 C 3.4812,16.5588 3.0024,15.5184 2.7144,14.4 H 0 V 9.6 H 2.7144 C 3.0024,8.4816 3.48,7.4424 4.1232,6.5232 L 2.4,4.8 4.8,2.4 6.5232,4.1232 C 7.4424,3.48 8.4816,3.0024 9.6,2.7144 V 0 h 4.8 v 2.7132 c 1.1184,0.2892 2.1564,0.7668 3.078,1.4088 l 1.722,-1.7232 2.4,2.4 -1.7232,1.7244 c 0.642,0.9192 1.1208,1.9596 1.4088,3.0768 z M 12,16.8 c 2.65092,0 4.8,-2.14908 4.8,-4.8 0,-2.650968 -2.14908,-4.8 -4.8,-4.8 -2.650968,0 -4.8,2.149032 -4.8,4.8 0,2.65092 2.149032,4.8 4.8,4.8 z" />
</svg>
</div>
</div>}
</Tooltip> : <></>}
</SelectorHeader>
<SelectorContent isSettings={isSettings}>
<div className="colorwaysLoader-barContainer"><div className="colorwaysLoader-bar" style={{ height: loaderHeight }} /></div>
<ScrollerThin style={{ maxHeight: "450px" }} className="ColorwaySelectorWrapper">
{visibleColorwayArray.length === 0 &&
<Forms.FormTitle
{(!getComputedStyle(document.body).getPropertyValue("--os-accent-color") || !["all", "official"].includes(visibleSources)) && !filters.filter(filter => filter.id === visibleSources)[0].sources.map(source => source.colorways).flat().length ? <Forms.FormTitle
style={{
marginBottom: 0,
width: "100%",
textAlign: "center",
textAlign: "center"
}}
>
No colorways...
</Forms.FormTitle>
</Forms.FormTitle> : <></>}
{filters.map(filter => filter.id).includes(visibleSources) && (
filters
.filter(filter => filter.id === visibleSources)[0].sources
.map(({ colorways, source, type }) => colorways.map((colorway: Colorway) => ({ ...colorway, sourceType: type, source: source, preset: colorway.preset || (colorway.isGradient ? "Gradient" : "Default") })))
.flat()
.sort((a, b) => {
switch (sortBy) {
case SortOptions.NAME_AZ:
return a.name.localeCompare(b.name);
case SortOptions.NAME_ZA:
return b.name.localeCompare(a.name);
case SortOptions.SOURCE_AZ:
return a.source.localeCompare(b.source);
case SortOptions.SOURCE_ZA:
return b.source.localeCompare(a.source);
default:
return a.name.localeCompare(b.name);
}
{["all", "official", "3rdparty", "custom"].includes(visibility) && (
visibleColorwayArray.map((color, ind) => {
})
.map((color: Colorway) => {
var colors: Array<string> = color.colors || [
"accent",
"primary",
"secondary",
"tertiary",
];
return (
return (color.name.toLowerCase().includes(searchValue.toLowerCase()) ?
<Tooltip text={color.name}>
{({ onMouseEnter, onMouseLeave }) => {
return (
<div
className="discordColorway"
className={viewMode === "grid" ? "discordColorway" : `${radioBarItem} ${radioBarItemFilled} discordColorway-listItem`}
id={"colorway-" + color.name}
data-last-official={
ind + 1 === colorways.length
}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseEnter={viewMode === "grid" ? onMouseEnter : () => { }}
onMouseLeave={viewMode === "grid" ? onMouseLeave : () => { }}
aria-checked={activeColorwayObject.id === color.name && activeColorwayObject.source === color.source}
onClick={async () => {
const [
onDemandWays,
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
onDemandWaysDiscordSaturation,
onDemandWaysOsAccentColor
] = await DataStore.getMany([
"onDemandWays",
"onDemandWaysTintedText",
"onDemandWaysDiscordSaturation"
"onDemandWaysDiscordSaturation",
"onDemandWaysOsAccentColor"
]);
if (currentColorway === color.name) {
DataStore.set("actveColorwayID", null);
DataStore.set("actveColorway", null);
if (activeColorwayObject.id === color.name && activeColorwayObject.source === color.source) {
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
setActiveColorwayObject({ id: null, css: null, sourceType: null, source: null });
ColorwayCSS.remove();
} else {
DataStore.set("activeColorwayColors", color.colors);
DataStore.set("actveColorwayID", color.name);
if (onDemandWays) {
const demandedColorway = !color.isGradient ? generateCss(
colorToHex(color.primary),
colorToHex(color.secondary),
colorToHex(color.tertiary),
colorToHex(color.accent),
colorToHex(onDemandWaysOsAccentColor ? getComputedStyle(document.body).getPropertyValue("--os-accent-color") : color.accent).slice(0, 6),
onDemandWaysTintedText,
onDemandWaysDiscordSaturation
) : gradientBase(colorToHex(color.accent), onDemandWaysDiscordSaturation) + `:root:root {--custom-theme-background: linear-gradient(${color.linearGradient})}`;
DataStore.set("actveColorway", demandedColorway);
onDemandWaysDiscordSaturation,
undefined,
color.name
) : gradientBase(colorToHex(onDemandWaysOsAccentColor ? getComputedStyle(document.body).getPropertyValue("--os-accent-color") : color.accent), onDemandWaysDiscordSaturation) + `:root:root {--custom-theme-background: linear-gradient(${color.linearGradient})}`;
ColorwayCSS.set(demandedColorway);
setActiveColorwayObject({ id: color.name, css: demandedColorway, sourceType: color.type, source: color.source });
DataStore.set("activeColorwayObject", { id: color.name, css: demandedColorway, sourceType: color.type, source: color.source });
} else {
DataStore.set("actveColorway", color["dc-import"]);
ColorwayCSS.set(color["dc-import"]);
setActiveColorwayObject({ id: color.name, css: color["dc-import"], sourceType: color.type, source: color.source });
DataStore.set("activeColorwayObject", { id: color.name, css: color["dc-import"], sourceType: color.type, source: color.source });
}
}
setCurrentColorway(await DataStore.get("actveColorwayID") as string);
}}
>
<div
className="colorwayInfoIconContainer"
onClick={(e) => {
e.stopPropagation();
openModal((props) => (
<ColorwayInfoModal
modalProps={
props
}
colorwayProps={
color
}
discrimProps={customColorways.includes(
color
)}
loadUIProps={cached_loadUI}
/>
));
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg>
</div>
{viewMode === "list" && <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" />
{activeColorwayObject.id === color.name && activeColorwayObject.source === color.source && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
</svg>}
<div className="discordColorwayPreviewColorContainer">
{!color.isGradient ? colors.map((colorItm) => <div
className="discordColorwayPreviewColor"
@ -497,16 +568,142 @@ export default function ({
}}
/>}
</div>
{currentColorway === color.name && <SelectionCircle />}
<div className="colorwaySelectionCircle">
{(activeColorwayObject.id === color.name && activeColorwayObject.source === color.source && viewMode === "grid") && <SelectionCircle />}
</div>
{(showLabelsInSelectorGridView || viewMode === "list") && <Text className={"colorwayLabel" + ((showLabelsInSelectorGridView && viewMode === "grid") ? " labelInGrid" : "")}>{color.name}</Text>}
<div
className="colorwayInfoIconContainer"
onClick={(e) => {
e.stopPropagation();
openModal((props) => <ColorwayInfoModal
modalProps={props}
colorway={color}
loadUIProps={loadUI}
/>);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg>
</div>
{viewMode === "list" && <>
<Tooltip text="Copy Colorway CSS">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async e => {
e.stopPropagation();
Clipboard.copy(color["dc-import"]);
Toasts.show({
message: "Copied Colorway CSS Successfully",
type: 1,
id: "copy-colorway-css-notify",
});
}}
>
<CodeIcon width={20} height={20} />
</Button>}</Tooltip>
<Tooltip text="Copy Colorway ID">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async e => {
e.stopPropagation();
const colorwayIDArray = `${color.accent},${color.primary},${color.secondary},${color.tertiary}|n:${color.name}${color.preset ? `|p:${color.preset}` : ""}`;
const colorwayID = stringToHex(colorwayIDArray);
Clipboard.copy(colorwayID);
Toasts.show({
message: "Copied Colorway ID Successfully",
type: 1,
id: "copy-colorway-id-notify",
});
}}
>
<IDIcon width={20} height={20} />
</Button>}
</Tooltip>
{color.sourceType === "offline" && <Tooltip text="Delete Colorway">
{({ onMouseEnter, onMouseLeave }) => <Button
innerClassName="colorwaysSettings-iconButtonInner"
size={Button.Sizes.ICON}
color={Button.Colors.RED}
look={Button.Looks.OUTLINED}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={async e => {
e.stopPropagation();
const oldStores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(sourcee => sourcee.name !== color.source);
const storeToModify = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(sourcee => sourcee.name === color.source)[0];
const newStore = { name: storeToModify.name, colorways: storeToModify.colorways.filter(colorway => colorway.name !== color.name) };
DataStore.set("customColorways", [...oldStores, newStore]);
setCustomColorwayData([...oldStores, newStore].map((colorSrc: { name: string, colorways: Colorway[], id?: string; }) =>
({ type: "offline", source: colorSrc.name, colorways: colorSrc.colorways })));
if ((await DataStore.get("activeColorwayObject")).id === color.name) {
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
setActiveColorwayObject({ id: null, css: null, sourceType: null, source: null });
ColorwayCSS.remove();
}
}}
>
<DeleteIcon width={20} height={20} />
</Button>}
</Tooltip>}
</>}
</div>
);
}}
</Tooltip>
</Tooltip> : <></>
);
})
)}
</ScrollerThin>
</SelectorContent>
{!isSettings ? <ModalFooter>
<Button
size={Button.Sizes.MEDIUM}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
onClick={() => {
SettingsRouter.open("ColorwaysSettings");
modalProps.onClose();
}}
>
Settings
</Button>
<Button
size={Button.Sizes.MEDIUM}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
onClick={() => modalProps.onClose()}
>
Close
</Button>
<Select
className={"colorwaySelector-sources " + ButtonLooks.OUTLINED}
look={1}
popoutClassName="colorwaySelector-sourceSelect"
options={filters.map(filter => { return { label: filter.name, value: (filter.id as string) }; })}
select={value => setVisibleSources(value)}
isSelected={value => visibleSources === value}
serialize={String}
popoutPosition="top" />
</ModalFooter> : <></>}
</SelectorContainer >
);
}

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,40 +6,44 @@
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);
const [showLabelsInSelectorGridView, setShowLabelsInSelectorGridView] = useState<boolean>(false);
async function loadUI() {
const colorwaySourceFiles = await DataStore.get(
"colorwaySourceFiles"
);
useEffect(() => {
(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)
@ -50,148 +54,16 @@ export default function () {
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);
setCustomColorways(customColorways.map(source => source.colorways).flat(2));
setColorsButtonVisibility(showColorwaysButton);
setIsButtonThin(useThinMenuButton);
}
const cached_loadUI = useCallback(loadUI, []);
useEffect(() => {
cached_loadUI();
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,49 +5,32 @@
*/
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 (
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})` }}
@ -127,7 +110,11 @@ export default function ({
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-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={{
@ -159,34 +146,5 @@ export default function ({
</div>
</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}
/>
</>
);
</>;
}

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
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={String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)![0]}
colorwayID={colorID}
/>)}
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
look={Button.Looks.FILLED}
>
Add this Colorway...
</Button> : null);
</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;
}