mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-25 06:17:02 -04:00
Add DiscordColorways Back
This commit is contained in:
parent
d99f5aa28a
commit
0865d4bb58
25 changed files with 6832 additions and 1 deletions
|
@ -21,7 +21,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
||||||
- Request for plugins from Discord.
|
- Request for plugins from Discord.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Extra included plugins (58 additional plugins)</summary>
|
<summary>Extra included plugins (61 additional plugins)</summary>
|
||||||
|
|
||||||
- AllCallTimers by MaxHerbold and D3SOX
|
- AllCallTimers by MaxHerbold and D3SOX
|
||||||
- AltKrispSwitch by newwares
|
- AltKrispSwitch by newwares
|
||||||
|
@ -33,10 +33,12 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
||||||
- CleanChannelName by AutumnVN
|
- CleanChannelName by AutumnVN
|
||||||
- CopyUserMention by Cortex and castdrian
|
- CopyUserMention by Cortex and castdrian
|
||||||
- CustomAppIcons by Happy Enderman and SerStars
|
- CustomAppIcons by Happy Enderman and SerStars
|
||||||
|
- DiscordColorways by DaBluLite
|
||||||
- DNDWhilePlaying by thororen
|
- DNDWhilePlaying by thororen
|
||||||
- DoNotLeak by Perny
|
- DoNotLeak by Perny
|
||||||
- DoubleCounterBypass by nyx
|
- DoubleCounterBypass by nyx
|
||||||
- EmojiDumper by Cortex, Samwich, Woosh
|
- EmojiDumper by Cortex, Samwich, Woosh
|
||||||
|
- Encryptcord by Inbestigator
|
||||||
- EquicordCSS by FoxStorm1 and thororen (and all respective css developers)
|
- EquicordCSS by FoxStorm1 and thororen (and all respective css developers)
|
||||||
- ExportContacts by dat_insanity
|
- ExportContacts by dat_insanity
|
||||||
- FindReply by newwares
|
- FindReply by newwares
|
||||||
|
@ -68,6 +70,7 @@ An enhanced version of [Vencord](https://github.com/Vendicated/Vencord) by [Vend
|
||||||
- Search by JacobTm and thororen
|
- Search by JacobTm and thororen
|
||||||
- SearchFix by Jaxx
|
- SearchFix by Jaxx
|
||||||
- Sekai Stickers by MaiKokain
|
- Sekai Stickers by MaiKokain
|
||||||
|
- ServerSearch by camila314
|
||||||
- ShowBadgesInChat by Inbestigator and KrystalSkull
|
- ShowBadgesInChat by Inbestigator and KrystalSkull
|
||||||
- Slap by Korbo
|
- Slap by Korbo
|
||||||
- SoundBoardLogger by Moxxie, fres, echo, thororen
|
- SoundBoardLogger by Moxxie, fres, echo, thororen
|
||||||
|
|
|
@ -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>;
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { CopyIcon } from "@components/Icons";
|
||||||
|
import {
|
||||||
|
ModalProps,
|
||||||
|
ModalRoot,
|
||||||
|
} from "@utils/modal";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Clipboard,
|
||||||
|
ScrollerThin,
|
||||||
|
TextInput,
|
||||||
|
Toasts,
|
||||||
|
useState,
|
||||||
|
} from "@webpack/common";
|
||||||
|
|
||||||
|
import { mainColors } from "../constants";
|
||||||
|
import { colorVariables } from "../css";
|
||||||
|
import { getHex } from "../utils";
|
||||||
|
|
||||||
|
export default function ({ modalProps }: { modalProps: ModalProps; }) {
|
||||||
|
const [ColorVars, setColorVars] = useState<string[]>(colorVariables);
|
||||||
|
const [collapsedSettings, setCollapsedSettings] = useState<boolean>(true);
|
||||||
|
let results: string[];
|
||||||
|
function searchToolboxItems(e: string) {
|
||||||
|
results = [];
|
||||||
|
colorVariables.find((colorVariable: string) => {
|
||||||
|
if (colorVariable.toLowerCase().includes(e.toLowerCase())) {
|
||||||
|
results.push(colorVariable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setColorVars(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ModalRoot {...modalProps} className="colorwayColorpicker">
|
||||||
|
<Flex style={{ gap: "8px", marginBottom: "8px" }}>
|
||||||
|
<TextInput
|
||||||
|
className="colorwaysColorpicker-search"
|
||||||
|
placeholder="Search for a color:"
|
||||||
|
onChange={e => {
|
||||||
|
searchToolboxItems(e);
|
||||||
|
if (e) {
|
||||||
|
setCollapsedSettings(false);
|
||||||
|
} else {
|
||||||
|
setCollapsedSettings(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.ICON}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => setCollapsedSettings(!collapsedSettings)}
|
||||||
|
>
|
||||||
|
<svg width="32" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img">
|
||||||
|
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<ScrollerThin style={{ color: "var(--text-normal)" }} orientation="vertical" className={collapsedSettings ? " colorwaysColorpicker-collapsed" : ""} paddingFix>
|
||||||
|
{ColorVars.map((colorVariable: string) => <div
|
||||||
|
id={`colorways-colorstealer-item_${colorVariable}`}
|
||||||
|
className="colorwaysCreator-settingItm colorwaysCreator-toolboxItm"
|
||||||
|
onClick={() => {
|
||||||
|
Clipboard.copy(getHex(getComputedStyle(document.body).getPropertyValue("--" + colorVariable)));
|
||||||
|
Toasts.show({ message: "Color " + colorVariable + " copied to clipboard", id: "toolbox-color-var-copied", type: 1 });
|
||||||
|
}} style={{ "--brand-experiment": `var(--${colorVariable})` } as React.CSSProperties}>
|
||||||
|
{`Copy ${colorVariable}`}
|
||||||
|
</div>)}
|
||||||
|
</ScrollerThin>
|
||||||
|
<Flex style={{ justifyContent: "space-between", marginTop: "8px" }} wrap="wrap" className={collapsedSettings ? "" : " colorwaysColorpicker-collapsed"}>
|
||||||
|
{mainColors.map(mainColor => <div
|
||||||
|
id={`colorways-toolbox_copy-${mainColor.name}`}
|
||||||
|
className="colorwayToolbox-listItem"
|
||||||
|
>
|
||||||
|
<CopyIcon onClick={() => {
|
||||||
|
Clipboard.copy(getHex(getComputedStyle(document.body).getPropertyValue(mainColor.var)));
|
||||||
|
Toasts.show({ message: `${mainColor.title} color copied to clipboard`, id: `toolbox-${mainColor.name}-color-copied`, type: 1 });
|
||||||
|
}} width={20} height={20} className="colorwayToolbox-listItemSVG" />
|
||||||
|
<span className="colorwaysToolbox-label">{`Copy ${mainColor.title} Color`}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</ModalRoot>;
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function ({ modalProps, onSettings, presetId, hasTintedText, hasDiscordSaturation }: { modalProps: ModalProps, presetId: string, hasTintedText: boolean, hasDiscordSaturation: boolean, onSettings: ({ presetId, tintedText, discordSaturation }: { presetId: string, tintedText: boolean, discordSaturation: boolean; }) => void; }) {
|
||||||
|
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">
|
||||||
|
<Forms.FormTitle>
|
||||||
|
Presets:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<ScrollerThin orientation="vertical" paddingFix style={{ paddingRight: "2px", marginBottom: "20px", maxHeight: "250px" }}>
|
||||||
|
{Object.values(getPreset()).map(pre => {
|
||||||
|
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={preset === pre.id}>
|
||||||
|
<div
|
||||||
|
className={`${radioBar} ${radioPositionLeft}`}
|
||||||
|
style={{ padding: "10px" }}
|
||||||
|
onClick={() => {
|
||||||
|
setPreset(pre.id);
|
||||||
|
}}>
|
||||||
|
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" fill="currentColor" />
|
||||||
|
{preset === pre.id && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
|
||||||
|
</svg>
|
||||||
|
<Text variant="eyebrow" tag="h5">{pre.name}</Text>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</ScrollerThin>
|
||||||
|
<Switch value={tintedText} onChange={setTintedText}>Use colored text</Switch>
|
||||||
|
<Switch value={discordSaturation} onChange={setDiscordSaturation} hideBorder style={{ marginBottom: "0" }}>Use Discord's saturation</Switch>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.BRAND_NEW}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
onClick={() => {
|
||||||
|
onSettings({ presetId: preset, discordSaturation: discordSaturation, tintedText: tintedText });
|
||||||
|
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>;
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { openModal } from "@utils/modal";
|
||||||
|
import { FluxDispatcher, Text, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
|
import { FluxEvents } from "@webpack/types";
|
||||||
|
|
||||||
|
import { getAutoPresets } from "../css";
|
||||||
|
import { ColorwayObject } from "../types";
|
||||||
|
import { PalleteIcon } from "./Icons";
|
||||||
|
import Selector from "./Selector";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [activeColorway, setActiveColorway] = useState<string>("None");
|
||||||
|
const [visibility, setVisibility] = useState<boolean>(true);
|
||||||
|
const [isThin, setIsThin] = useState<boolean>(false);
|
||||||
|
const [autoPreset, setAutoPreset] = useState<string>("hueRotation");
|
||||||
|
useEffect(() => {
|
||||||
|
(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 }) => {
|
||||||
|
setIsThin(isTall);
|
||||||
|
});
|
||||||
|
|
||||||
|
FluxDispatcher.subscribe("COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents, ({ isVisible }) => {
|
||||||
|
setVisibility(isVisible);
|
||||||
|
});
|
||||||
|
|
||||||
|
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>}
|
||||||
|
{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">
|
||||||
|
<div
|
||||||
|
className={"ColorwaySelectorBtn" + (isThin ? " ColorwaySelectorBtn_thin" : "")}
|
||||||
|
onMouseEnter={async () => {
|
||||||
|
onMouseEnter();
|
||||||
|
setActiveColorway((await DataStore.get("activeColorwayObject") as ColorwayObject).id || "None");
|
||||||
|
setAutoPreset(await DataStore.get("activeAutoPreset") as string);
|
||||||
|
}}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={() => {
|
||||||
|
onClick();
|
||||||
|
openModal((props: any) => <Selector modalProps={props} />);
|
||||||
|
}}
|
||||||
|
>{isThin ? <Text variant="text-xs/normal" style={{ color: "var(--header-primary)", fontWeight: 700, fontSize: 9 }}>Colorways</Text> : <PalleteIcon />}</div>
|
||||||
|
</div> : <></>}
|
||||||
|
</Tooltip>;
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||||
|
import { Button, Forms, ScrollerThin, Text, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { knownThemeVars } from "../constants";
|
||||||
|
import { getFontOnBg, getHex } from "../utils";
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
modalProps,
|
||||||
|
onFinished
|
||||||
|
}: {
|
||||||
|
modalProps: ModalProps;
|
||||||
|
onFinished: ({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) => void;
|
||||||
|
}) {
|
||||||
|
const [accentColor, setAccentColor] = useState<string>(getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--brand-experiment")
|
||||||
|
));
|
||||||
|
const [primaryColor, setPrimaryColor] = useState<string>(getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-primary")
|
||||||
|
));
|
||||||
|
const [secondaryColor, setSecondaryColor] = useState<string>(getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-secondary")
|
||||||
|
));
|
||||||
|
const [tertiaryColor, setTertiaryColor] = useState<string>(getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-tertiary")
|
||||||
|
));
|
||||||
|
return <ModalRoot {...modalProps} className="colorwayCreator-modal">
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-lg/semibold" tag="h1">
|
||||||
|
Conflicting Colors Found
|
||||||
|
</Text>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent className="colorwayCreator-menuWrapper">
|
||||||
|
<Text className="colorwaysConflictingColors-warning">Multiple known themes have been found, select the colors you want to copy from below:</Text>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>Colors to copy:</Forms.FormTitle>
|
||||||
|
<div className="colorwayCreator-colorPreviews">
|
||||||
|
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: primaryColor, color: getFontOnBg(primaryColor) }} >Primary</div>
|
||||||
|
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: secondaryColor, color: getFontOnBg(secondaryColor) }} >Secondary</div>
|
||||||
|
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: tertiaryColor, color: getFontOnBg(tertiaryColor) }} >Tertiary</div>
|
||||||
|
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: accentColor, color: getFontOnBg(accentColor) }} >Accent</div>
|
||||||
|
</div>
|
||||||
|
<div className="colorwaysCreator-settingCat">
|
||||||
|
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix>
|
||||||
|
<div
|
||||||
|
id="colorways-colorstealer-item_Default"
|
||||||
|
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
||||||
|
>
|
||||||
|
<Forms.FormTitle>Discord</Forms.FormTitle>
|
||||||
|
<div className="colorwayCreator-colorPreviews">
|
||||||
|
<div
|
||||||
|
className="colorwayCreator-colorPreview" style={{
|
||||||
|
backgroundColor: getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-primary")
|
||||||
|
),
|
||||||
|
color: getFontOnBg(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-primary")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onClick={() => setPrimaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-primary")
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>Primary</div>
|
||||||
|
<div
|
||||||
|
className="colorwayCreator-colorPreview" style={{
|
||||||
|
backgroundColor: getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-secondary")
|
||||||
|
),
|
||||||
|
color: getFontOnBg(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-secondary")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onClick={() => setSecondaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-secondary")
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>Secondary</div>
|
||||||
|
<div
|
||||||
|
className="colorwayCreator-colorPreview" style={{
|
||||||
|
backgroundColor: getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-tertiary")
|
||||||
|
),
|
||||||
|
color: getFontOnBg(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-tertiary")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onClick={() => setTertiaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-tertiary")
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>Tertiary</div>
|
||||||
|
<div
|
||||||
|
className="colorwayCreator-colorPreview" style={{
|
||||||
|
backgroundColor: getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--brand-experiment")
|
||||||
|
),
|
||||||
|
color: getFontOnBg(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--brand-experiment")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onClick={() => setAccentColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--brand-experiment")
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
>Accent</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{Object.values(knownThemeVars).map((theme: any, i) => {
|
||||||
|
if (getComputedStyle(document.body).getPropertyValue(theme.variable)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id={
|
||||||
|
"colorways-colorstealer-item_" +
|
||||||
|
Object.keys(knownThemeVars)[i]
|
||||||
|
}
|
||||||
|
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
||||||
|
>
|
||||||
|
<Forms.FormTitle>{Object.keys(knownThemeVars)[i] + (theme.alt ? " (Main)" : "")}</Forms.FormTitle>
|
||||||
|
<div className="colorwayCreator-colorPreviews">
|
||||||
|
{theme.primary && getComputedStyle(document.body).getPropertyValue(theme.primary).match(/^\d.*%$/)
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`),
|
||||||
|
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setPrimaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primary)})`));
|
||||||
|
}}
|
||||||
|
>Primary</div>
|
||||||
|
: (
|
||||||
|
theme.primary
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)),
|
||||||
|
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setPrimaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.primary)));
|
||||||
|
}}
|
||||||
|
>Primary</div>
|
||||||
|
: (theme.primaryVariables
|
||||||
|
&& <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_primary"
|
||||||
|
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`)) }}
|
||||||
|
onClick={() => {
|
||||||
|
setPrimaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.primaryVariables.l)})`));
|
||||||
|
}}
|
||||||
|
>Primary</div>))
|
||||||
|
}
|
||||||
|
{theme.secondary && getComputedStyle(document.body).getPropertyValue(theme.secondary).match(/^\d.*%$/)
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`),
|
||||||
|
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSecondaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondary)})`));
|
||||||
|
}}
|
||||||
|
>Secondary</div>
|
||||||
|
: (theme.secondary
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)),
|
||||||
|
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSecondaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.secondary)));
|
||||||
|
}}
|
||||||
|
>Secondary</div>
|
||||||
|
: (theme.secondaryVariables
|
||||||
|
&& <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_secondary"
|
||||||
|
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`)) }}
|
||||||
|
onClick={() => {
|
||||||
|
setSecondaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.secondaryVariables.l)})`));
|
||||||
|
}}
|
||||||
|
>Secondary</div>))
|
||||||
|
}
|
||||||
|
{theme.tertiary && getComputedStyle(document.body).getPropertyValue(theme.tertiary).match(/^\d.*%$/)
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`),
|
||||||
|
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setTertiaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiary)})`));
|
||||||
|
}}
|
||||||
|
>Tertiary</div>
|
||||||
|
: (theme.tertiary
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)),
|
||||||
|
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setTertiaryColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.tertiary)));
|
||||||
|
}}
|
||||||
|
>Tertiary</div>
|
||||||
|
: (theme.tertiaryVariables
|
||||||
|
&& <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_tertiary"
|
||||||
|
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`)) }}
|
||||||
|
onClick={() => {
|
||||||
|
setTertiaryColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.tertiaryVariables.l)})`));
|
||||||
|
}}
|
||||||
|
>Tertiary</div>))}
|
||||||
|
{theme.accent && getComputedStyle(document.body).getPropertyValue(theme.accent).match(/^\d.*%$/)
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`),
|
||||||
|
color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setAccentColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accent)})`));
|
||||||
|
}}
|
||||||
|
>Accent</div>
|
||||||
|
: (theme.accent
|
||||||
|
? <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
|
||||||
|
style={{
|
||||||
|
backgroundColor: getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)),
|
||||||
|
color: getFontOnBg(getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)))
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setAccentColor(getHex(getComputedStyle(document.body).getPropertyValue(theme.accent)));
|
||||||
|
}}
|
||||||
|
>Accent</div>
|
||||||
|
: (theme.accentVariables
|
||||||
|
&& <div
|
||||||
|
className="colorwayCreator-colorPreview colorwayCreator-colorPreview_accent"
|
||||||
|
style={{ backgroundColor: `hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`, color: getFontOnBg(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`)) }}
|
||||||
|
onClick={() => {
|
||||||
|
setAccentColor(getHex(`hsl(${getComputedStyle(document.body).getPropertyValue(theme.accentVariables.h)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.s)} ${!getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l).includes("%") ? (getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l) + "%") : getComputedStyle(document.body).getPropertyValue(theme.accentVariables.l)})`));
|
||||||
|
}}
|
||||||
|
>Accent</div>))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ScrollerThin>
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.BRAND}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
onClick={() => {
|
||||||
|
onFinished({
|
||||||
|
accent: accentColor,
|
||||||
|
primary: primaryColor,
|
||||||
|
secondary: secondaryColor,
|
||||||
|
tertiary: tertiaryColor
|
||||||
|
});
|
||||||
|
modalProps.onClose();
|
||||||
|
}}
|
||||||
|
>Finish</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot >;
|
||||||
|
}
|
349
src/equicordplugins/discordColorways/components/CreatorModal.tsx
Normal file
349
src/equicordplugins/discordColorways/components/CreatorModal.tsx
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalProps,
|
||||||
|
ModalRoot,
|
||||||
|
openModal,
|
||||||
|
} from "@utils/modal";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Forms,
|
||||||
|
Slider,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
useEffect,
|
||||||
|
UserStore,
|
||||||
|
useState,
|
||||||
|
} from "@webpack/common";
|
||||||
|
|
||||||
|
import { ColorPicker, versionData } from "..";
|
||||||
|
import { knownThemeVars } from "../constants";
|
||||||
|
import { generateCss, getPreset, gradientPresetIds, PrimarySatDiffs, pureGradientBase } from "../css";
|
||||||
|
import { Colorway } from "../types";
|
||||||
|
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,
|
||||||
|
loadUIProps,
|
||||||
|
colorwayID
|
||||||
|
}: {
|
||||||
|
modalProps: ModalProps;
|
||||||
|
loadUIProps?: () => Promise<void>;
|
||||||
|
colorwayID?: string;
|
||||||
|
}) {
|
||||||
|
const [accentColor, setAccentColor] = useState<string>("5865f2");
|
||||||
|
const [primaryColor, setPrimaryColor] = useState<string>("313338");
|
||||||
|
const [secondaryColor, setSecondaryColor] = useState<string>("2b2d31");
|
||||||
|
const [tertiaryColor, setTertiaryColor] = useState<string>("1e1f22");
|
||||||
|
const [colorwayName, setColorwayName] = useState<string>("");
|
||||||
|
const [tintedText, setTintedText] = useState<boolean>(true);
|
||||||
|
const [discordSaturation, setDiscordSaturation] = useState<boolean>(true);
|
||||||
|
const [preset, setPreset] = useState<string>("default");
|
||||||
|
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: {
|
||||||
|
get: accentColor,
|
||||||
|
set: setAccentColor,
|
||||||
|
name: "Accent"
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
get: primaryColor,
|
||||||
|
set: setPrimaryColor,
|
||||||
|
name: "Primary"
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
get: secondaryColor,
|
||||||
|
set: setSecondaryColor,
|
||||||
|
name: "Secondary"
|
||||||
|
},
|
||||||
|
tertiary: {
|
||||||
|
get: tertiaryColor,
|
||||||
|
set: setTertiaryColor,
|
||||||
|
name: "Tertiary"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (colorwayID) {
|
||||||
|
if (!colorwayID.includes(",")) {
|
||||||
|
throw new Error("Invalid Colorway ID");
|
||||||
|
} else {
|
||||||
|
const setColor = [
|
||||||
|
setAccentColor,
|
||||||
|
setPrimaryColor,
|
||||||
|
setSecondaryColor,
|
||||||
|
setTertiaryColor
|
||||||
|
];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const colorPickerProps = {
|
||||||
|
suggestedColors: [
|
||||||
|
"#313338",
|
||||||
|
"#2b2d31",
|
||||||
|
"#1e1f22",
|
||||||
|
"#5865f2",
|
||||||
|
],
|
||||||
|
showEyeDropper: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRoot {...modalProps} className="colorwayCreator-modal">
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-lg/semibold" tag="h1">
|
||||||
|
Create Colorway
|
||||||
|
</Text>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent className="colorwayCreator-menuWrapper">
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
||||||
|
Name:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Give your Colorway a name"
|
||||||
|
value={colorwayName}
|
||||||
|
onChange={setColorwayName}
|
||||||
|
/>
|
||||||
|
<div className="colorwaysCreator-settingCat">
|
||||||
|
<Forms.FormTitle style={{ marginBottom: "0" }}>
|
||||||
|
Colors & Values:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<div className="colorwayCreator-colorPreviews">
|
||||||
|
{presetColorArray.map(presetColor => {
|
||||||
|
return <ColorPicker
|
||||||
|
label={<Text className="colorwaysPicker-colorLabel">{colorProps[presetColor].name}</Text>}
|
||||||
|
color={parseInt(colorProps[presetColor].get, 16)}
|
||||||
|
onChange={(color: number) => {
|
||||||
|
let hexColor = color.toString(16);
|
||||||
|
while (hexColor.length < 6) {
|
||||||
|
hexColor = "0" + hexColor;
|
||||||
|
}
|
||||||
|
colorProps[presetColor].set(hexColor);
|
||||||
|
}}
|
||||||
|
{...colorPickerProps}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<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"
|
||||||
|
onClick={() => openModal((props: ModalProps) => <ColorwayCreatorSettingsModal
|
||||||
|
modalProps={props}
|
||||||
|
hasDiscordSaturation={discordSaturation}
|
||||||
|
hasTintedText={tintedText}
|
||||||
|
presetId={preset}
|
||||||
|
onSettings={({ presetId, tintedText, discordSaturation }) => {
|
||||||
|
setPreset(presetId);
|
||||||
|
setPresetColorArray(getPreset()[presetId].colors);
|
||||||
|
setDiscordSaturation(discordSaturation);
|
||||||
|
setTintedText(tintedText);
|
||||||
|
}} />)}>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>Settings & Presets</Forms.FormTitle>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img" style={{ rotate: "-90deg" }}>
|
||||||
|
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ThemePreviewCategory
|
||||||
|
accent={"#" + accentColor}
|
||||||
|
primary={"#" + primaryColor}
|
||||||
|
secondary={"#" + secondaryColor}
|
||||||
|
tertiary={"#" + tertiaryColor}
|
||||||
|
previewCSS={gradientPresetIds.includes(getPreset()[preset].id) ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${(getPreset(
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
tertiaryColor,
|
||||||
|
accentColor
|
||||||
|
)[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>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.BRAND}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
onClick={async () => {
|
||||||
|
var customColorwayCSS: string = "";
|
||||||
|
if (preset === "default") {
|
||||||
|
customColorwayCSS = generateCss(
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
tertiaryColor,
|
||||||
|
accentColor,
|
||||||
|
tintedText,
|
||||||
|
discordSaturation,
|
||||||
|
mutedTextBrightness,
|
||||||
|
(colorwayName || "Colorway")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
gradientPresetIds.includes(getPreset()[preset].id) ?
|
||||||
|
customColorwayCSS = `/**
|
||||||
|
* @name ${colorwayName || "Colorway"}
|
||||||
|
* @version ${versionData.creatorVersion}
|
||||||
|
* @description Automatically generated Colorway.
|
||||||
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
|
* @preset Gradient
|
||||||
|
*/
|
||||||
|
${(getPreset(primaryColor, secondaryColor, tertiaryColor, accentColor)[preset].preset(discordSaturation) as { full: string; }).full}` : customColorwayCSS = `/**
|
||||||
|
* @name ${colorwayName || "Colorway"}
|
||||||
|
* @version ${versionData.creatorVersion}
|
||||||
|
* @description Automatically generated Colorway.
|
||||||
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
|
* @preset ${getPreset()[preset].name}
|
||||||
|
*/
|
||||||
|
${(getPreset(primaryColor, secondaryColor, tertiaryColor, accentColor)[preset].preset(discordSaturation) as string)}`;
|
||||||
|
}
|
||||||
|
const customColorway: Colorway = {
|
||||||
|
name: (colorwayName || "Colorway"),
|
||||||
|
"dc-import": customColorwayCSS,
|
||||||
|
accent: "#" + accentColor,
|
||||||
|
primary: "#" + primaryColor,
|
||||||
|
secondary: "#" + secondaryColor,
|
||||||
|
tertiary: "#" + tertiaryColor,
|
||||||
|
colors: presetColorArray,
|
||||||
|
author: UserStore.getCurrentUser().username,
|
||||||
|
authorID: UserStore.getCurrentUser().id,
|
||||||
|
isGradient: gradientPresetIds.includes(getPreset()[preset].id),
|
||||||
|
linearGradient: gradientPresetIds.includes(getPreset()[preset].id) ? (getPreset(
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
tertiaryColor,
|
||||||
|
accentColor
|
||||||
|
)[preset].preset(discordSaturation) as { base: string; }).base : "",
|
||||||
|
preset: getPreset()[preset].id,
|
||||||
|
creatorVersion: versionData.creatorVersion
|
||||||
|
};
|
||||||
|
openModal(props => <SaveColorwayModal modalProps={props} colorways={[customColorway]} onFinish={() => {
|
||||||
|
modalProps.onClose();
|
||||||
|
loadUIProps!();
|
||||||
|
}} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => {
|
||||||
|
function setAllColors({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) {
|
||||||
|
setAccentColor(accent.split("#")[1]);
|
||||||
|
setPrimaryColor(primary.split("#")[1]);
|
||||||
|
setSecondaryColor(secondary.split("#")[1]);
|
||||||
|
setTertiaryColor(tertiary.split("#")[1]);
|
||||||
|
}
|
||||||
|
var copiedThemes = ["Discord"];
|
||||||
|
Object.values(knownThemeVars).map((theme: { variable: string; variableType?: string; }, i: number) => {
|
||||||
|
if (getComputedStyle(document.body).getPropertyValue(theme.variable)) {
|
||||||
|
copiedThemes.push(Object.keys(knownThemeVars)[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (copiedThemes.length > 1) {
|
||||||
|
openModal(props => <ConflictingColorsModal modalProps={props} onFinished={setAllColors} />);
|
||||||
|
} else {
|
||||||
|
setPrimaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-primary")
|
||||||
|
).split("#")[1]
|
||||||
|
);
|
||||||
|
setSecondaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-secondary")
|
||||||
|
).split("#")[1]
|
||||||
|
);
|
||||||
|
setTertiaryColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--background-tertiary")
|
||||||
|
).split("#")[1]
|
||||||
|
);
|
||||||
|
setAccentColor(
|
||||||
|
getHex(
|
||||||
|
getComputedStyle(
|
||||||
|
document.body
|
||||||
|
).getPropertyValue("--brand-experiment")
|
||||||
|
).split("#")[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy Current Colors
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => openModal((props: any) => <InputColorwayIdModal modalProps={props} onColorwayId={colorwayID => {
|
||||||
|
const setColor = [
|
||||||
|
setAccentColor,
|
||||||
|
setPrimaryColor,
|
||||||
|
setSecondaryColor,
|
||||||
|
setTertiaryColor
|
||||||
|
];
|
||||||
|
hexToString(colorwayID).split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
|
||||||
|
}} />)}
|
||||||
|
>
|
||||||
|
Enter Colorway ID
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => {
|
||||||
|
modalProps.onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
146
src/equicordplugins/discordColorways/components/Icons.tsx
Normal file
146
src/equicordplugins/discordColorways/components/Icons.tsx
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
|
import type { PropsWithChildren, SVGProps } from "react";
|
||||||
|
|
||||||
|
interface BaseIconProps extends IconProps {
|
||||||
|
viewBox: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IconProps extends SVGProps<SVGSVGElement> {
|
||||||
|
className?: string;
|
||||||
|
height?: string | number;
|
||||||
|
width?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={classes(className, "vc-icon")}
|
||||||
|
role="img"
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox={viewBox}
|
||||||
|
{...svgProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PalleteIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-pallete-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M 12,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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CloseIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-close-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
301
src/equicordplugins/discordColorways/components/InfoModal.tsx
Normal file
301
src/equicordplugins/discordColorways/components/InfoModal.tsx
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { CodeBlock } from "@components/CodeBlock";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import {
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalProps,
|
||||||
|
ModalRoot,
|
||||||
|
openModal,
|
||||||
|
} from "@utils/modal";
|
||||||
|
import { saveFile } from "@utils/web";
|
||||||
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
import { Button, Clipboard, Forms, Text, TextInput, Toasts, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
|
import { ColorwayCSS, versionData } from "..";
|
||||||
|
import { generateCss, pureGradientBase } from "../css";
|
||||||
|
import { Colorway } from "../types";
|
||||||
|
import { colorToHex, stringToHex } from "../utils";
|
||||||
|
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,
|
||||||
|
colorway,
|
||||||
|
loadUIProps
|
||||||
|
}: {
|
||||||
|
modalProps: ModalProps;
|
||||||
|
colorway: Colorway;
|
||||||
|
loadUIProps: () => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const colors: string[] = colorway.colors || [
|
||||||
|
"accent",
|
||||||
|
"primary",
|
||||||
|
"secondary",
|
||||||
|
"tertiary",
|
||||||
|
];
|
||||||
|
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: {colorway.name}
|
||||||
|
</Text>
|
||||||
|
<ModalCloseButton onClick={() => modalProps.onClose()} />
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent>
|
||||||
|
<Flex style={{ gap: "8px", width: "100%" }} flexDirection="column">
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Creator:</Forms.FormTitle>
|
||||||
|
<Flex style={{ gap: ".5rem" }}>
|
||||||
|
<UserSummaryItem
|
||||||
|
users={[profile]}
|
||||||
|
guildId={undefined}
|
||||||
|
renderIcon={false}
|
||||||
|
showDefaultAvatarsForNullUsers
|
||||||
|
size={32}
|
||||||
|
showUserPopout
|
||||||
|
/>
|
||||||
|
<Text style={{ lineHeight: "32px" }}>{colorway.author}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Colors:</Forms.FormTitle>
|
||||||
|
<Flex style={{ gap: "8px" }}>
|
||||||
|
{colors.map(color => <div className="colorwayInfo-colorSwatch" style={{ backgroundColor: colorway[color] }} />)}
|
||||||
|
</Flex>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Actions:</Forms.FormTitle>
|
||||||
|
<Flex style={{ gap: "8px" }} flexDirection="column">
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => {
|
||||||
|
const colorwayIDArray = `${colorway.accent},${colorway.primary},${colorway.secondary},${colorway.tertiary}|n:${colorway.name}${colorway.preset ? `|p:${colorway.preset}` : ""}`;
|
||||||
|
const colorwayID = stringToHex(colorwayIDArray);
|
||||||
|
Clipboard.copy(colorwayID);
|
||||||
|
Toasts.show({
|
||||||
|
message: "Copied Colorway ID Successfully",
|
||||||
|
type: 1,
|
||||||
|
id: "copy-colorway-id-notify",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy Colorway ID
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => {
|
||||||
|
Clipboard.copy(colorway["dc-import"]);
|
||||||
|
Toasts.show({
|
||||||
|
message: "Copied CSS to Clipboard",
|
||||||
|
type: 1,
|
||||||
|
id: "copy-colorway-css-notify",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy CSS
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={async () => {
|
||||||
|
const newColorway = {
|
||||||
|
...colorway,
|
||||||
|
"dc-import": generateCss(colorToHex(colorway.primary) || "313338", colorToHex(colorway.secondary) || "2b2d31", colorToHex(colorway.tertiary) || "1e1f22", colorToHex(colorway.accent) || "5865f2", true, true, undefined, colorway.name)
|
||||||
|
};
|
||||||
|
openModal(props => <SaveColorwayModal modalProps={props} colorways={[newColorway]} onFinish={() => { }} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Update CSS
|
||||||
|
</Button>
|
||||||
|
{colorway.sourceType === "offline" && <Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={async () => {
|
||||||
|
const offlineSources = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).map(o => o.colorways).filter(colorArr => colorArr.map(color => color.name).includes(colorway.name))[0];
|
||||||
|
openModal(props => <RenameColorwayModal ogName={colorway.name} colorwayList={offlineSources} modalProps={props} onFinish={async (newName: string) => {
|
||||||
|
const stores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).map(source => {
|
||||||
|
if (source.name === colorway.source) {
|
||||||
|
return {
|
||||||
|
name: source.name,
|
||||||
|
colorways: [...source.colorways.filter(colorway => colorway.name !== colorway.name), {
|
||||||
|
...colorway,
|
||||||
|
name: newName
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
} else return source;
|
||||||
|
});
|
||||||
|
DataStore.set("customColorways", stores);
|
||||||
|
if ((await DataStore.get("activeColorwayObject")).id === colorway.name) {
|
||||||
|
DataStore.set("activeColorwayObject", { id: newName, css: colorway.name, sourceType: "offline", source: colorway.source });
|
||||||
|
}
|
||||||
|
modalProps.onClose();
|
||||||
|
loadUIProps();
|
||||||
|
}} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</Button>}
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => {
|
||||||
|
openModal(props => <ModalRoot {...props} className="colorwayInfo-cssModal">
|
||||||
|
<ModalContent><CodeBlock lang="css" content={colorway["dc-import"]} /></ModalContent>
|
||||||
|
</ModalRoot>);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show CSS
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => {
|
||||||
|
if (!colorway["dc-import"].includes("@name")) {
|
||||||
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
DiscordNative.fileManager.saveWithDialog(`/**
|
||||||
|
* @name ${colorway.name || "Colorway"}
|
||||||
|
* @version ${versionData.creatorVersion}
|
||||||
|
* @description Automatically generated Colorway.
|
||||||
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
|
*/
|
||||||
|
${colorway["dc-import"].replace((colorway["dc-import"].match(/\/\*.+\*\//) || [""])[0], "").replaceAll("url(//", "url(https://").replaceAll("url(\"//", "url(\"https://")}`, `${colorway.name.replaceAll(" ", "-").toLowerCase()}.theme.css`);
|
||||||
|
} else {
|
||||||
|
saveFile(new File([`/**
|
||||||
|
* @name ${colorway.name || "Colorway"}
|
||||||
|
* @version ${versionData.creatorVersion}
|
||||||
|
* @description Automatically generated Colorway.
|
||||||
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
|
*/
|
||||||
|
${colorway["dc-import"].replace((colorway["dc-import"].match(/\/\*.+\*\//) || [""])[0], "").replaceAll("url(//", "url(https://").replaceAll("url(\"//", "url(\"https://")}`], `${colorway.name.replaceAll(" ", "-").toLowerCase()}.theme.css`, { type: "text/plain" }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
DiscordNative.fileManager.saveWithDialog(colorway["dc-import"], `${colorway.name.replaceAll(" ", "-").toLowerCase()}.theme.css`);
|
||||||
|
} else {
|
||||||
|
saveFile(new File([colorway["dc-import"]], `${colorway.name.replaceAll(" ", "-").toLowerCase()}.theme.css`, { type: "text/plain" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download CSS
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => {
|
||||||
|
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
|
||||||
|
<style>
|
||||||
|
{colorway.isGradient ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${colorway.linearGradient})}` : ""}
|
||||||
|
</style>
|
||||||
|
<ThemePreview
|
||||||
|
accent={colorway.accent}
|
||||||
|
primary={colorway.primary}
|
||||||
|
secondary={colorway.secondary}
|
||||||
|
tertiary={colorway.tertiary}
|
||||||
|
isModal
|
||||||
|
modalProps={props}
|
||||||
|
/>
|
||||||
|
</ModalRoot>);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Show preview
|
||||||
|
</Button>
|
||||||
|
{colorway.sourceType === "offline" && <Button
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={async () => {
|
||||||
|
const oldStores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name !== colorway.source);
|
||||||
|
const storeToModify = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name === colorway.source)[0];
|
||||||
|
const newStore = { name: storeToModify.name, colorways: storeToModify.colorways.filter(colorway => colorway.name !== colorway.name) };
|
||||||
|
DataStore.set("customColorways", [...oldStores, newStore]);
|
||||||
|
if ((await DataStore.get("activeColorwayObject")).id === colorway.name) {
|
||||||
|
DataStore.set("activeColorwayObject", { id: null, css: null, sourceType: null, source: null });
|
||||||
|
ColorwayCSS.remove();
|
||||||
|
}
|
||||||
|
modalProps.onClose();
|
||||||
|
loadUIProps();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<div style={{ width: "100%", height: "20px" }} />
|
||||||
|
</ModalContent>
|
||||||
|
</ModalRoot>;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ModalContent, ModalFooter, ModalProps, ModalRoot } from "@utils/modal";
|
||||||
|
import { Button, Forms, TextInput, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { hexToString } from "../utils";
|
||||||
|
|
||||||
|
export default function ({ modalProps, onColorwayId }: { modalProps: ModalProps, onColorwayId: (colorwayID: string) => void; }) {
|
||||||
|
const [colorwayID, setColorwayID] = useState<string>("");
|
||||||
|
return <ModalRoot {...modalProps} className="colorwaysCreator-noMinHeight">
|
||||||
|
<ModalContent className="colorwaysCreator-noHeader colorwaysCreator-noMinHeight">
|
||||||
|
<Forms.FormTitle>Colorway ID:</Forms.FormTitle>
|
||||||
|
<TextInput placeholder="Enter Colorway ID" onInput={e => setColorwayID(e.currentTarget.value)} />
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.BRAND}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
onClick={() => {
|
||||||
|
if (!colorwayID) {
|
||||||
|
throw new Error("Please enter a Colorway ID");
|
||||||
|
} else if (!hexToString(colorwayID).includes(",")) {
|
||||||
|
throw new Error("Invalid Colorway ID");
|
||||||
|
} else {
|
||||||
|
onColorwayId(colorwayID);
|
||||||
|
modalProps.onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => modalProps.onClose()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>;
|
||||||
|
}
|
|
@ -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>;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return <div style={{
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: "calc(100% + 4px)",
|
||||||
|
height: "calc(100% + 4px)",
|
||||||
|
position: "absolute",
|
||||||
|
top: "-2px",
|
||||||
|
left: "-2px",
|
||||||
|
cursor: "default",
|
||||||
|
pointerEvents: "none",
|
||||||
|
boxShadow: "inset 0 0 0 2px var(--brand-500),inset 0 0 0 4px var(--background-primary)"
|
||||||
|
}}>
|
||||||
|
<svg style={{ position: "absolute", right: "0" }} aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="var(--white-500)" />
|
||||||
|
<path fill="currentColor" fill-rule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm5.7-13.3a1 1 0 0 0-1.4-1.4L10 14.58l-2.3-2.3a1 1 0 0 0-1.4 1.42l3 3a1 1 0 0 0 1.4 0l7-7Z" clip-rule="evenodd" style={{ color: "var(--brand-500)" }} />
|
||||||
|
</svg>
|
||||||
|
</div>;
|
||||||
|
}
|
838
src/equicordplugins/discordColorways/components/Selector.tsx
Normal file
838
src/equicordplugins/discordColorways/components/Selector.tsx
Normal file
|
@ -0,0 +1,838 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable arrow-parens */
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { DeleteIcon, PlusIcon } from "@components/Icons";
|
||||||
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
|
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||||
|
import { findByProps } from "@webpack";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonLooks,
|
||||||
|
Clipboard,
|
||||||
|
Forms,
|
||||||
|
Menu,
|
||||||
|
Popout,
|
||||||
|
ScrollerThin,
|
||||||
|
Select,
|
||||||
|
SettingsRouter,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Toasts,
|
||||||
|
Tooltip,
|
||||||
|
useEffect,
|
||||||
|
useState
|
||||||
|
} from "@webpack/common";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { ColorwayCSS } from "..";
|
||||||
|
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 { CodeIcon, IDIcon, MoreIcon, PalleteIcon } from "./Icons";
|
||||||
|
import ColorwayInfoModal from "./InfoModal";
|
||||||
|
import SelectionCircle from "./SelectionCircle";
|
||||||
|
|
||||||
|
function SelectorContainer({ children, isSettings, modalProps }: { children: ReactNode, isSettings?: boolean, modalProps: ModalProps; }) {
|
||||||
|
if (!isSettings) {
|
||||||
|
return <ModalRoot {...modalProps} className="colorwaySelectorModal">
|
||||||
|
{children}
|
||||||
|
</ModalRoot>;
|
||||||
|
} else {
|
||||||
|
return <SettingsTab title="Colors">
|
||||||
|
<div className="colorwaysSettingsSelector-wrapper">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</SettingsTab>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectorHeader({ children, isSettings }: { children: ReactNode, isSettings?: boolean; }) {
|
||||||
|
if (!isSettings) {
|
||||||
|
return <ModalHeader separator={false}>
|
||||||
|
{children}
|
||||||
|
</ModalHeader>;
|
||||||
|
} else {
|
||||||
|
return <Flex style={{ gap: "0" }}>
|
||||||
|
{children}
|
||||||
|
</Flex>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectorContent({ children, isSettings }: { children: ReactNode, isSettings?: boolean; }) {
|
||||||
|
if (!isSettings) {
|
||||||
|
return <ModalContent className="colorwaySelectorModalContent">{children}</ModalContent>;
|
||||||
|
} else {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
modalProps,
|
||||||
|
isSettings,
|
||||||
|
settings = { selectorType: "normal" }
|
||||||
|
}: {
|
||||||
|
modalProps: ModalProps,
|
||||||
|
isSettings?: boolean,
|
||||||
|
settings?: { selectorType: "preview" | "multiple-selection" | "normal", previewSource?: string, onSelected?: (colorways: Colorway[]) => void; };
|
||||||
|
}): JSX.Element | any {
|
||||||
|
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);
|
||||||
|
const [selectedColorways, setSelectedColorways] = useState<Colorway[]>([]);
|
||||||
|
const [errorCode, setErrorCode] = useState<number>(0);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
if (settings.previewSource) {
|
||||||
|
|
||||||
|
const res: Response = await fetch(settings.previewSource);
|
||||||
|
|
||||||
|
const dataPromise = res.json().then(data => data).catch(() => ({ colorways: [], errorCode: 1, errorMsg: "Colorway Source format is invalid" }));
|
||||||
|
|
||||||
|
const data = await dataPromise;
|
||||||
|
|
||||||
|
if (data.errorCode) {
|
||||||
|
setErrorCode(data.errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorwayList: Colorway[] = data.css ? data.css.map(customStore => customStore.colorways).flat() : data.colorways;
|
||||||
|
|
||||||
|
setColorwayData([{ colorways: colorwayList || [], source: res.url, type: "online" }] as { type: "online" | "offline" | "temporary", source: string, colorways: Colorway[]; }[]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
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; }[];
|
||||||
|
|
||||||
|
const responses: Response[] = await Promise.all(
|
||||||
|
onlineSources.map((source) =>
|
||||||
|
fetch(source.url, force ? { cache: "no-store" } : {})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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(() => { loadUI(); }, [searchValue]);
|
||||||
|
|
||||||
|
function ReloadPopout(onClose: () => void) {
|
||||||
|
return (
|
||||||
|
<Menu.Menu
|
||||||
|
navId="dc-reload-menu"
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="dc-force-reload"
|
||||||
|
label="Force Reload"
|
||||||
|
action={() => loadUI(true)}
|
||||||
|
/>
|
||||||
|
</Menu.Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectorContainer modalProps={modalProps} isSettings={isSettings}>
|
||||||
|
<SelectorHeader isSettings={isSettings}>
|
||||||
|
{settings.selectorType !== "preview" ? <>
|
||||||
|
<TextInput
|
||||||
|
className="colorwaySelector-search"
|
||||||
|
placeholder="Search for Colorways..."
|
||||||
|
value={searchValue}
|
||||||
|
onChange={setSearchValue}
|
||||||
|
/>
|
||||||
|
<Tooltip text="Refresh Colorways...">
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => <Popout
|
||||||
|
position="bottom"
|
||||||
|
align="right"
|
||||||
|
animation={Popout.Animation.NONE}
|
||||||
|
shouldShow={showReloadMenu}
|
||||||
|
onRequestClose={() => setShowReloadMenu(false)}
|
||||||
|
renderPopout={() => ReloadPopout(() => setShowReloadMenu(false))}
|
||||||
|
>
|
||||||
|
{(_, { isShown }) => <Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.ICON}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ marginLeft: "8px" }}
|
||||||
|
id="colorway-refreshcolorway"
|
||||||
|
onMouseEnter={isShown ? () => { } : onMouseEnter}
|
||||||
|
onMouseLeave={isShown ? () => { } : onMouseLeave}
|
||||||
|
onClick={() => {
|
||||||
|
setLoaderHeight("2px");
|
||||||
|
loadUI().then(() => setLoaderHeight("0px"));
|
||||||
|
}}
|
||||||
|
onContextMenu={() => { onMouseLeave(); setShowReloadMenu(!showReloadMenu); }}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
style={{ padding: "6px", boxSizing: "content-box" }}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
y="0"
|
||||||
|
fill="none"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.351,6.351C7.824,4.871,9.828,4,12,4c4.411,0,8,3.589,8,8h2c0-5.515-4.486-10-10-10 C9.285,2,6.779,3.089,4.938,4.938L3,3v6h6L6.351,6.351z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.649,17.649C16.176,19.129,14.173,20,12,20c-4.411,0-8-3.589-8-8H2c0,5.515,4.486,10,10,10 c2.716,0,5.221-1.089,7.062-2.938L21,21v-6h-6L17.649,17.649z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Button>}
|
||||||
|
</Popout>}
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip text="Create Colorway...">
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => <Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.ICON}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ marginLeft: "8px" }}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={() => openModal((props) => <CreatorModal
|
||||||
|
modalProps={props}
|
||||||
|
loadUIProps={loadUI}
|
||||||
|
/>)}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
size={Button.Sizes.ICON}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ marginLeft: "8px" }}
|
||||||
|
id="colorway-opencolorstealer"
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={() => openModal((props) => <ColorPickerModal modalProps={props} />)}
|
||||||
|
>
|
||||||
|
<PalleteIcon width={20} height={20} style={{ padding: "6px", boxSizing: "content-box" }} />
|
||||||
|
</Button>}
|
||||||
|
</Tooltip>
|
||||||
|
{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" /> : <></>}
|
||||||
|
</> : <Text variant="heading-lg/semibold" tag="h1">
|
||||||
|
Preview...
|
||||||
|
</Text>}
|
||||||
|
</SelectorHeader>
|
||||||
|
<SelectorContent isSettings={isSettings}>
|
||||||
|
<div className="colorwaysLoader-barContainer"><div className="colorwaysLoader-bar" style={{ height: loaderHeight }} /></div>
|
||||||
|
{settings.selectorType === "multiple-selection" && <Forms.FormTitle>Available</Forms.FormTitle>}
|
||||||
|
<ScrollerThin style={{ maxHeight: settings.selectorType === "multiple-selection" ? "50%" : (isSettings ? "unset" : "450px") }} className={"ColorwaySelectorWrapper " + (viewMode === "grid" ? "ColorwaySelectorWrapper-grid" : "ColorwaySelectorWrapper-list") + (showLabelsInSelectorGridView ? " colorwaySelector-gridWithLabels" : "")}>
|
||||||
|
{(activeColorwayObject.sourceType === "temporary" && settings.selectorType === "normal" && settings.selectorType === "normal") && <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}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
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} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon width={20} height={20} />
|
||||||
|
</Button>}
|
||||||
|
</Tooltip>
|
||||||
|
</>}
|
||||||
|
</div>}
|
||||||
|
</Tooltip>}
|
||||||
|
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") && ["all", "official"].includes(visibleSources) && settings.selectorType === "normal" && "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> : <></>}
|
||||||
|
{(!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"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No colorways...
|
||||||
|
</Forms.FormTitle> : <></>}
|
||||||
|
{errorCode !== 0 && <Forms.FormTitle
|
||||||
|
style={{
|
||||||
|
marginBottom: 0,
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{errorCode === 1 && "Error: Invalid Colorway Source Format. If this error persists, contact the source author to resolve the issue."}
|
||||||
|
</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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map((color: Colorway) => {
|
||||||
|
const colors: string[] = color.colors || [
|
||||||
|
"accent",
|
||||||
|
"primary",
|
||||||
|
"secondary",
|
||||||
|
"tertiary",
|
||||||
|
];
|
||||||
|
return (color.name.toLowerCase().includes(searchValue.toLowerCase()) ?
|
||||||
|
<Tooltip text={color.name}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={viewMode === "grid" ? "discordColorway" : `${radioBarItem} ${radioBarItemFilled} discordColorway-listItem`}
|
||||||
|
id={"colorway-" + color.name}
|
||||||
|
onMouseEnter={viewMode === "grid" ? onMouseEnter : () => { }}
|
||||||
|
onMouseLeave={viewMode === "grid" ? onMouseLeave : () => { }}
|
||||||
|
aria-checked={activeColorwayObject.id === color.name && activeColorwayObject.source === color.source}
|
||||||
|
onClick={async () => {
|
||||||
|
if (settings.selectorType === "normal") {
|
||||||
|
const [
|
||||||
|
onDemandWays,
|
||||||
|
onDemandWaysTintedText,
|
||||||
|
onDemandWaysDiscordSaturation,
|
||||||
|
onDemandWaysOsAccentColor
|
||||||
|
] = await DataStore.getMany([
|
||||||
|
"onDemandWays",
|
||||||
|
"onDemandWaysTintedText",
|
||||||
|
"onDemandWaysDiscordSaturation",
|
||||||
|
"onDemandWaysOsAccentColor"
|
||||||
|
]);
|
||||||
|
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 {
|
||||||
|
if (onDemandWays) {
|
||||||
|
const demandedColorway = !color.isGradient ? generateCss(
|
||||||
|
colorToHex(color.primary),
|
||||||
|
colorToHex(color.secondary),
|
||||||
|
colorToHex(color.tertiary),
|
||||||
|
colorToHex(onDemandWaysOsAccentColor ? getComputedStyle(document.body).getPropertyValue("--os-accent-color") : color.accent).slice(0, 6),
|
||||||
|
onDemandWaysTintedText,
|
||||||
|
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 {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (settings.selectorType === "multiple-selection") {
|
||||||
|
setSelectedColorways([...selectedColorways, color]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(viewMode === "list" && settings.selectorType === "normal") && <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"
|
||||||
|
style={{
|
||||||
|
backgroundColor: color[colorItm],
|
||||||
|
}}
|
||||||
|
/>) : <div
|
||||||
|
className="discordColorwayPreviewColor"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(${color.linearGradient})`,
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
{settings.selectorType === "normal" && <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>}
|
||||||
|
{settings.selectorType === "normal" && <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" && settings.selectorType !== "preview") && <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> : <></>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</ScrollerThin>
|
||||||
|
{settings.selectorType === "multiple-selection" && <>
|
||||||
|
<Forms.FormTitle style={{ marginTop: "8px" }}>Selected</Forms.FormTitle>
|
||||||
|
<ScrollerThin style={{ maxHeight: "50%" }} className={"ColorwaySelectorWrapper " + (viewMode === "grid" ? "ColorwaySelectorWrapper-grid" : "ColorwaySelectorWrapper-list") + (showLabelsInSelectorGridView ? " colorwaySelector-gridWithLabels" : "")}>
|
||||||
|
{selectedColorways.map((color: Colorway, i: number) => {
|
||||||
|
const colors: string[] = color.colors || [
|
||||||
|
"accent",
|
||||||
|
"primary",
|
||||||
|
"secondary",
|
||||||
|
"tertiary",
|
||||||
|
];
|
||||||
|
return <Tooltip text={color.name}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={viewMode === "grid" ? "discordColorway" : `${radioBarItem} ${radioBarItemFilled} discordColorway-listItem`}
|
||||||
|
id={"colorway-" + color.name}
|
||||||
|
onMouseEnter={viewMode === "grid" ? onMouseEnter : () => { }}
|
||||||
|
onMouseLeave={viewMode === "grid" ? onMouseLeave : () => { }}
|
||||||
|
aria-checked={activeColorwayObject.id === color.name && activeColorwayObject.source === color.source}
|
||||||
|
onClick={() => setSelectedColorways(selectedColorways.filter((colorway, ii) => ii !== i))}
|
||||||
|
>
|
||||||
|
{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"
|
||||||
|
style={{
|
||||||
|
backgroundColor: color[colorItm],
|
||||||
|
}}
|
||||||
|
/>) : <div
|
||||||
|
className="discordColorwayPreviewColor"
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(${color.linearGradient})`,
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
<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>}
|
||||||
|
{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>
|
||||||
|
</>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Tooltip>;
|
||||||
|
})}
|
||||||
|
</ScrollerThin>
|
||||||
|
</>}
|
||||||
|
</SelectorContent>
|
||||||
|
{(!isSettings && settings.selectorType !== "preview") ? <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 >
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore } from "@api/index";
|
||||||
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
|
import { Switch, useCallback, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [onDemand, setOnDemand] = useState<boolean>(false);
|
||||||
|
const [onDemandTinted, setOnDemandTinted] = useState<boolean>(false);
|
||||||
|
const [onDemandDiscordSat, setOnDemandDiscordSat] = useState<boolean>(false);
|
||||||
|
const [onDemandOsAccent, setOnDemandOsAccent] = useState<boolean>(false);
|
||||||
|
async function loadUI() {
|
||||||
|
const [
|
||||||
|
onDemandWays,
|
||||||
|
onDemandWaysTintedText,
|
||||||
|
onDemandWaysDiscordSaturation,
|
||||||
|
onDemandWaysOsAccentColor
|
||||||
|
] = await DataStore.getMany([
|
||||||
|
"onDemandWays",
|
||||||
|
"onDemandWaysTintedText",
|
||||||
|
"onDemandWaysDiscordSaturation",
|
||||||
|
"onDemandWaysOsAccentColor"
|
||||||
|
]);
|
||||||
|
setOnDemand(onDemandWays);
|
||||||
|
setOnDemandTinted(onDemandWaysTintedText);
|
||||||
|
setOnDemandDiscordSat(onDemandWaysDiscordSaturation);
|
||||||
|
if (getComputedStyle(document.body).getPropertyValue("--os-accent-color") !== "") {
|
||||||
|
setOnDemandOsAccent(onDemandWaysOsAccentColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached_loadUI = useCallback(loadUI, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cached_loadUI();
|
||||||
|
}, []);
|
||||||
|
return <SettingsTab title="On-Demand">
|
||||||
|
<Switch
|
||||||
|
value={onDemand}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setOnDemand(v);
|
||||||
|
DataStore.set("onDemandWays", v);
|
||||||
|
}}
|
||||||
|
note="Always utilise the latest of what DiscordColorways has to offer. CSS is being directly generated on the device and gets applied in the place of the normal import/CSS given by the colorway."
|
||||||
|
>
|
||||||
|
Enable Colorways On Demand
|
||||||
|
</Switch>
|
||||||
|
<Switch
|
||||||
|
value={onDemandTinted}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setOnDemandTinted(v);
|
||||||
|
DataStore.set("onDemandWaysTintedText", v);
|
||||||
|
}}
|
||||||
|
disabled={!onDemand}
|
||||||
|
>
|
||||||
|
Use tinted text
|
||||||
|
</Switch>
|
||||||
|
<Switch
|
||||||
|
value={onDemandDiscordSat}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setOnDemandDiscordSat(v);
|
||||||
|
DataStore.set("onDemandWaysDiscordSaturation", v);
|
||||||
|
}}
|
||||||
|
disabled={!onDemand}
|
||||||
|
>
|
||||||
|
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>;
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
* 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 { Link } from "@components/Link";
|
||||||
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
|
import {
|
||||||
|
FluxDispatcher,
|
||||||
|
Forms,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
useEffect,
|
||||||
|
useState
|
||||||
|
} from "@webpack/common";
|
||||||
|
import { FluxEvents } from "@webpack/types";
|
||||||
|
|
||||||
|
import { versionData } from "../../.";
|
||||||
|
import { fallbackColorways } from "../../constants";
|
||||||
|
import { Colorway } from "../../types";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [colorways, setColorways] = useState<Colorway[]>([]);
|
||||||
|
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
|
||||||
|
const [colorsButtonVisibility, setColorsButtonVisibility] = useState<boolean>(false);
|
||||||
|
const [isButtonThin, setIsButtonThin] = useState<boolean>(false);
|
||||||
|
const [showLabelsInSelectorGridView, setShowLabelsInSelectorGridView] = useState<boolean>(false);
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const data = await Promise.all(
|
||||||
|
responses.map((res: Response) =>
|
||||||
|
res.json().catch(() => { return { colorways: [] }; })
|
||||||
|
));
|
||||||
|
const colorways = data.flatMap(json => json.colorways);
|
||||||
|
setColorways(colorways || fallbackColorways);
|
||||||
|
setCustomColorways(customColorways.map(source => source.colorways).flat(2));
|
||||||
|
setColorsButtonVisibility(showColorwaysButton);
|
||||||
|
setIsButtonThin(useThinMenuButton);
|
||||||
|
setShowLabelsInSelectorGridView(showLabelsInSelectorGridView);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <SettingsTab title="Settings">
|
||||||
|
<div className="colorwaysSettingsPage-wrapper">
|
||||||
|
<Forms.FormTitle tag="h5">Quick Switch</Forms.FormTitle>
|
||||||
|
<Switch
|
||||||
|
value={colorsButtonVisibility}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setColorsButtonVisibility(v);
|
||||||
|
DataStore.set("showColorwaysButton", v);
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents,
|
||||||
|
isVisible: v
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
note="Shows a button on the top of the servers list that opens a colorway selector modal."
|
||||||
|
>
|
||||||
|
Enable Quick Switch
|
||||||
|
</Switch>
|
||||||
|
<Switch
|
||||||
|
value={isButtonThin}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setIsButtonThin(v);
|
||||||
|
DataStore.set("useThinMenuButton", v);
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "COLORWAYS_UPDATE_BUTTON_HEIGHT" as FluxEvents,
|
||||||
|
isTall: v
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
note="Replaces the icon on the colorways launcher button with text, making it more compact."
|
||||||
|
>
|
||||||
|
Use thin Quick Switch button
|
||||||
|
</Switch>
|
||||||
|
<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)",
|
||||||
|
fontSize: "24px",
|
||||||
|
color: "var(--header-primary)",
|
||||||
|
lineHeight: "31px",
|
||||||
|
marginBottom: "0"
|
||||||
|
}}>
|
||||||
|
Discord <span style={{
|
||||||
|
fontFamily: "var(--font-display)",
|
||||||
|
fontSize: "24px",
|
||||||
|
backgroundColor: "var(--brand-500)",
|
||||||
|
padding: "0 4px",
|
||||||
|
borderRadius: "4px"
|
||||||
|
}}>Colorways</span>
|
||||||
|
</h1>
|
||||||
|
<Text
|
||||||
|
variant="text-xs/normal"
|
||||||
|
style={{
|
||||||
|
color: "var(--text-normal)",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "14px",
|
||||||
|
marginBottom: "12px"
|
||||||
|
}}
|
||||||
|
>by Project Colorway</Text>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
||||||
|
Plugin Version:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Text
|
||||||
|
variant="text-xs/normal"
|
||||||
|
style={{
|
||||||
|
color: "var(--text-muted)",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "14px",
|
||||||
|
marginBottom: "8px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{versionData.pluginVersion}
|
||||||
|
</Text>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
||||||
|
Creator Version:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Text
|
||||||
|
variant="text-xs/normal"
|
||||||
|
style={{
|
||||||
|
color: "var(--text-muted)",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "14px",
|
||||||
|
marginBottom: "8px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{versionData.creatorVersion}{" (Stable)"}
|
||||||
|
</Text>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
||||||
|
Loaded Colorways:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Text
|
||||||
|
variant="text-xs/normal"
|
||||||
|
style={{
|
||||||
|
color: "var(--text-muted)",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: "14px",
|
||||||
|
marginBottom: "8px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[...colorways, ...customColorways].length + 1}
|
||||||
|
</Text>
|
||||||
|
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
||||||
|
Project Repositories:
|
||||||
|
</Forms.FormTitle>
|
||||||
|
<Forms.FormText style={{ marginBottom: "8px" }}>
|
||||||
|
<Link href="https://github.com/DaBluLite/DiscordColorways">DiscordColorways</Link>
|
||||||
|
<br />
|
||||||
|
<Link href="https://github.com/DaBluLite/ProjectColorway">Project Colorway</Link>
|
||||||
|
</Forms.FormText>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
</SettingsTab>;
|
||||||
|
}
|
|
@ -0,0 +1,387 @@
|
||||||
|
/*
|
||||||
|
* 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, PlusIcon } 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>("");
|
||||||
|
const [nameReadOnly, setNameReadOnly] = useState<boolean>(false);
|
||||||
|
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}
|
||||||
|
readOnly={nameReadOnly}
|
||||||
|
disabled={nameReadOnly}
|
||||||
|
/>
|
||||||
|
<Forms.FormTitle style={{ marginTop: "8px" }}>URL:</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Enter a valid URL..."
|
||||||
|
onChange={value => {
|
||||||
|
setColorwaySourceURL(value);
|
||||||
|
if (value === defaultColorwaySource) {
|
||||||
|
setNameReadOnly(true);
|
||||||
|
setColorwaySourceName("Project Colorway");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
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.OUTLINED}
|
||||||
|
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") as { name: string, url: string; }[]);
|
||||||
|
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: "50%" }} className="colorwaysSettings-sourceScroller">
|
||||||
|
{!colorwaySourceFiles.length && <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }} onClick={() => {
|
||||||
|
DataStore.set("colorwaySourceFiles", [{ name: "Project Colorway", url: defaultColorwaySource }]);
|
||||||
|
setColorwaySourceFiles([{ name: "Project Colorway", url: defaultColorwaySource }]);
|
||||||
|
}}>
|
||||||
|
<PlusIcon width={24} height={24} />
|
||||||
|
<Text className="colorwaysSettings-colorwaySourceLabel">
|
||||||
|
Add Project Colorway Source
|
||||||
|
</Text>
|
||||||
|
</div>}
|
||||||
|
{colorwaySourceFiles.map((colorwaySourceFile: { name: string, url: string; }, i: number) => <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
|
<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>
|
||||||
|
<Flex style={{ marginLeft: "auto", gap: "8px" }}>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={() => { Clipboard.copy(colorwaySourceFile.url); }}
|
||||||
|
>
|
||||||
|
<CopyIcon width={14} height={14} /> Copy URL
|
||||||
|
</Button>
|
||||||
|
{colorwaySourceFile.url !== defaultColorwaySource
|
||||||
|
&& <>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
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={14} height={14} /> Download...
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
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={14} height={14} /> Remove
|
||||||
|
</Button>
|
||||||
|
</>}
|
||||||
|
</Flex>
|
||||||
|
</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>
|
||||||
|
<ScrollerThin orientation="vertical" style={{ maxHeight: "50%" }} className="colorwaysSettings-sourceScroller">
|
||||||
|
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") ? <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
|
<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`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
|
<Text className="colorwaysSettings-colorwaySourceLabel">
|
||||||
|
{customColorwaySourceName}
|
||||||
|
</Text>
|
||||||
|
<Flex style={{ marginLeft: "auto", gap: "8px" }}>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={async () => {
|
||||||
|
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={14} height={14} /> Export as...
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
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} /> Remove
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ScrollerThin>
|
||||||
|
</SettingsTab>;
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/*
|
||||||
|
* 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 { getTheme, Theme } from "@utils/discord";
|
||||||
|
import { openModal } from "@utils/modal";
|
||||||
|
import { findByProps } from "@webpack";
|
||||||
|
import { Button, ScrollerThin, Text, TextInput, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
|
import { StoreItem } from "../../types";
|
||||||
|
import { DownloadIcon, PalleteIcon } from "../Icons";
|
||||||
|
import Selector from "../Selector";
|
||||||
|
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
|
function GithubIcon() {
|
||||||
|
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||||
|
return <img src={src} alt="GitHub" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
|
<Flex flexDirection="column" style={{ gap: ".5rem", marginBottom: "8px" }}>
|
||||||
|
<Text className="colorwaysSettings-colorwaySourceLabelHeader">
|
||||||
|
{item.name}
|
||||||
|
</Text>
|
||||||
|
<Text className="colorwaysSettings-colorwaySourceDesc">
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
<Text className="colorwaysSettings-colorwaySourceDesc" style={{ opacity: ".8" }}>
|
||||||
|
by {item.authorGh}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex style={{ gap: "8px", alignItems: "center", width: "100%" }}>
|
||||||
|
<Link href={"https://github.com/" + item.authorGh}><GithubIcon /></Link>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={colorwaySourceFiles.map(source => source.name).includes(item.name) ? Button.Colors.RED : Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
style={{ marginLeft: "auto" }}
|
||||||
|
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={14} height={14} /> Remove</> : <><DownloadIcon width={14} height={14} /> Add to Sources</>}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
innerClassName="colorwaysSettings-iconButtonInner"
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.OUTLINED}
|
||||||
|
onClick={async () => {
|
||||||
|
openModal(props => <Selector modalProps={props} settings={{ selectorType: "preview", previewSource: item.url }} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PalleteIcon width={14} height={14} />{" "}Preview
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</div> : <></>
|
||||||
|
)}
|
||||||
|
</ScrollerThin>
|
||||||
|
</SettingsTab>;
|
||||||
|
}
|
19
src/equicordplugins/discordColorways/components/Spinner.tsx
Normal file
19
src/equicordplugins/discordColorways/components/Spinner.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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" />
|
||||||
|
<circle className="colorwaysBtn-spinnerBeam colorwaysBtn-spinnerBeam2" cx="50" cy="50" r="20" />
|
||||||
|
<circle className="colorwaysBtn-spinnerBeam" cx="50" cy="50" r="20" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
150
src/equicordplugins/discordColorways/components/ThemePreview.tsx
Normal file
150
src/equicordplugins/discordColorways/components/ThemePreview.tsx
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||||
|
import { Text } from "@webpack/common";
|
||||||
|
|
||||||
|
import { HexToHSL } from "../utils";
|
||||||
|
import { CloseIcon } from "./Icons";
|
||||||
|
|
||||||
|
export default function ThemePreview({
|
||||||
|
accent,
|
||||||
|
primary,
|
||||||
|
secondary,
|
||||||
|
tertiary,
|
||||||
|
previewCSS,
|
||||||
|
modalProps,
|
||||||
|
isModal
|
||||||
|
}: {
|
||||||
|
accent: string,
|
||||||
|
primary: string,
|
||||||
|
secondary: string,
|
||||||
|
tertiary: string,
|
||||||
|
previewCSS?: string,
|
||||||
|
modalProps?: ModalProps,
|
||||||
|
isModal?: boolean;
|
||||||
|
}) {
|
||||||
|
return <>
|
||||||
|
<style>
|
||||||
|
{".colorwaysPreview-wrapper {color: var(--header-secondary); box-shadow: var(--legacy-elevation-border);}" + previewCSS}
|
||||||
|
</style>
|
||||||
|
<div
|
||||||
|
className="colorwaysPreview-wrapper"
|
||||||
|
style={{ background: `var(--dc-overlay-app-frame, ${tertiary})` }}
|
||||||
|
>
|
||||||
|
<div className="colorwaysPreview-titlebar" />
|
||||||
|
<div className="colorwaysPreview-body">
|
||||||
|
<div className="colorwayPreview-guilds">
|
||||||
|
<div className="colorwayPreview-guild">
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-guildItem"
|
||||||
|
style={{ background: `var(--dc-guild-button, ${primary})` }}
|
||||||
|
onMouseEnter={e => e.currentTarget.style.background = accent}
|
||||||
|
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
|
||||||
|
onClick={() => {
|
||||||
|
if (isModal) {
|
||||||
|
modalProps?.onClose();
|
||||||
|
} else {
|
||||||
|
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
|
||||||
|
<style>
|
||||||
|
{previewCSS}
|
||||||
|
</style>
|
||||||
|
<ThemePreview accent={accent} primary={primary} secondary={secondary} tertiary={tertiary} isModal modalProps={props} />
|
||||||
|
</ModalRoot>);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isModal ? <CloseIcon style={{ color: "var(--header-secondary)" }} /> : <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19,3H14V5h5v5h2V5A2,2,0,0,0,19,3Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19,19H14v2h5a2,2,0,0,0,2-2V14H19Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M3,5v5H5V5h5V3H5A2,2,0,0,0,3,5Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5,14H3v5a2,2,0,0,0,2,2h5V19H5Z"
|
||||||
|
/>
|
||||||
|
</svg>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="colorwayPreview-guild">
|
||||||
|
<div className="colorwayPreview-guildSeparator" style={{ backgroundColor: primary }} />
|
||||||
|
</div>
|
||||||
|
<div className="colorwayPreview-guild">
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-guildItem"
|
||||||
|
style={{ background: `var(--dc-guild-button, ${primary})` }}
|
||||||
|
onMouseEnter={e => e.currentTarget.style.background = accent}
|
||||||
|
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="colorwayPreview-guild">
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-guildItem"
|
||||||
|
style={{ background: `var(--dc-guild-button, ${primary})` }}
|
||||||
|
onMouseEnter={e => e.currentTarget.style.background = accent}
|
||||||
|
onMouseLeave={e => e.currentTarget.style.background = `var(--dc-guild-button, ${primary})`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="colorwayPreview-channels" style={{ background: `var(--dc-overlay-3, ${secondary})` }}>
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-userArea"
|
||||||
|
style={{
|
||||||
|
background: `var(--dc-secondary-alt, hsl(${HexToHSL(secondary)[0]} ${HexToHSL(secondary)[1]}% ${Math.max(HexToHSL(secondary)[2] - 3.6, 0)}%))`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="colorwayPreview-filler">
|
||||||
|
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--white-500)" }} />
|
||||||
|
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--primary-360)" }} />
|
||||||
|
<div className="colorwayPreview-channel" style={{ backgroundColor: "var(--primary-500)" }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-topShadow"
|
||||||
|
style={{
|
||||||
|
"--primary-900-hsl": `${HexToHSL(tertiary)[0]} ${HexToHSL(tertiary)[1]}% ${Math.max(HexToHSL(tertiary)[2] - (3.6 * 6), 0)}%`,
|
||||||
|
"--primary-500-hsl": `${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + (3.6 * 3), 100)}%`
|
||||||
|
} as React.CSSProperties}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
tag="div"
|
||||||
|
variant="text-md/semibold"
|
||||||
|
lineClamp={1}
|
||||||
|
selectable={false}
|
||||||
|
>
|
||||||
|
Preview
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="colorwayPreview-chat" style={{ background: `var(--dc-overlay-chat, ${primary})` }}>
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-chatBox"
|
||||||
|
style={{
|
||||||
|
background: `var(--dc-overlay-3, hsl(${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + 3.6, 100)}%))`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="colorwayPreview-filler" />
|
||||||
|
<div
|
||||||
|
className="colorwayPreview-topShadow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
313
src/equicordplugins/discordColorways/constants.ts
Normal file
313
src/equicordplugins/discordColorways/constants.ts
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const defaultColorwaySource = "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json";
|
||||||
|
|
||||||
|
export const fallbackColorways = [
|
||||||
|
{
|
||||||
|
name: "Keyboard Purple",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(235 85.6% 64.7%)",
|
||||||
|
primary: "#222456",
|
||||||
|
secondary: "#1c1f48",
|
||||||
|
tertiary: "#080d1d",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/KeyboardPurple/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Eclipse",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(87 85.6% 64.7%)",
|
||||||
|
primary: "#000000",
|
||||||
|
secondary: "#181818",
|
||||||
|
tertiary: "#0a0a0a",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Eclipse/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Cyan",
|
||||||
|
original: false,
|
||||||
|
accent: "#009f88",
|
||||||
|
primary: "#202226",
|
||||||
|
secondary: "#1c1e21",
|
||||||
|
tertiary: "#141517",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Cyan/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Spotify",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(141 76% 48%)",
|
||||||
|
primary: "#121212",
|
||||||
|
secondary: "#090909",
|
||||||
|
tertiary: "#090909",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Spotify/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bright n' Blue",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(234, 68%, 33%)",
|
||||||
|
primary: "#394aae",
|
||||||
|
secondary: "#29379d",
|
||||||
|
tertiary: "#1b278d",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/BrightBlue/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Still Young",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(58 85.6% 89%)",
|
||||||
|
primary: "#443a31",
|
||||||
|
secondary: "#7c3d3e",
|
||||||
|
tertiary: "#207578",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/StillYoung/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sea",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(184, 100%, 50%)",
|
||||||
|
primary: "#07353b",
|
||||||
|
secondary: "#0b5e60",
|
||||||
|
tertiary: "#08201d",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Sea/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Lava",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(4, 80.4%, 32%)",
|
||||||
|
primary: "#401b17",
|
||||||
|
secondary: "#351917",
|
||||||
|
tertiary: "#230b0b",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Lava/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Solid Pink",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(340, 55.2%, 56.3%)",
|
||||||
|
primary: "#1e151c",
|
||||||
|
secondary: "#21181f",
|
||||||
|
tertiary: "#291e27",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/SolidPink/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sand",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(41, 31%, 45%)",
|
||||||
|
primary: "#7f6c43",
|
||||||
|
secondary: "#665b33",
|
||||||
|
tertiary: "#5c5733",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Sand/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AMOLED",
|
||||||
|
original: true,
|
||||||
|
accent: "hsl(235 85.6% 64.7%)",
|
||||||
|
primary: "#000000",
|
||||||
|
secondary: "#000000",
|
||||||
|
tertiary: "#000000",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Amoled/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Zorin",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(200, 89%, 86%)",
|
||||||
|
primary: "#171d20",
|
||||||
|
secondary: "#171d20",
|
||||||
|
tertiary: "#1e2529",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Zorin/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Desaturated",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(227, 58%, 65%)",
|
||||||
|
primary: "#35383d",
|
||||||
|
secondary: "#2c2f34",
|
||||||
|
tertiary: "#1e1f24",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Desaturated/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Crimson",
|
||||||
|
original: false,
|
||||||
|
accent: "hsl(0, 100%, 50%)",
|
||||||
|
primary: "#050000",
|
||||||
|
secondary: "#0a0000",
|
||||||
|
tertiary: "#0f0000",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Crimson/import.css);",
|
||||||
|
author: "Riddim_GLiTCH",
|
||||||
|
authorID: "801089753038061669",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jupiter",
|
||||||
|
original: true,
|
||||||
|
accent: "#ffd89b",
|
||||||
|
primary: "#ffd89b",
|
||||||
|
secondary: "#19547b",
|
||||||
|
tertiary: "#1e1f22",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Jupiter/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
isGradient: true,
|
||||||
|
colors: ["accent", "primary", "secondary"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Neon Candy",
|
||||||
|
original: true,
|
||||||
|
accent: "#FC00FF",
|
||||||
|
primary: "#00DBDE",
|
||||||
|
secondary: "#00DBDE",
|
||||||
|
tertiary: "#00DBDE",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/NeonCandy/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
isGradient: true,
|
||||||
|
colors: ["accent", "primary"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Wildberry",
|
||||||
|
original: false,
|
||||||
|
accent: "#f40172",
|
||||||
|
primary: "#180029",
|
||||||
|
secondary: "#340057",
|
||||||
|
tertiary: "#4b007a",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Wildberry/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Facebook",
|
||||||
|
original: false,
|
||||||
|
accent: "#2375e1",
|
||||||
|
primary: "#18191a",
|
||||||
|
secondary: "#242526",
|
||||||
|
tertiary: "#3a3b3c",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Facebook/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Material You",
|
||||||
|
original: false,
|
||||||
|
accent: "#004977",
|
||||||
|
primary: "#1f1f1f",
|
||||||
|
secondary: "#28292a",
|
||||||
|
tertiary: "#2d2f31",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/MaterialYou/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Discord Teal",
|
||||||
|
original: false,
|
||||||
|
accent: "#175f6d",
|
||||||
|
primary: "#313338",
|
||||||
|
secondary: "#2b2d31",
|
||||||
|
tertiary: "#1e1f22",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/css-snippets/DiscordTeal/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
colors: ["accent"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "黄昏の花 (Twilight Blossom)",
|
||||||
|
original: true,
|
||||||
|
accent: "#e100ff",
|
||||||
|
primary: "#04000a",
|
||||||
|
secondary: "#0b0024",
|
||||||
|
tertiary: "#210042",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/TwilightBlossom/import.css);",
|
||||||
|
author: "Riddim_GLiTCH",
|
||||||
|
authorID: "801089753038061669",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Chai",
|
||||||
|
original: true,
|
||||||
|
accent: "#59cd51",
|
||||||
|
primary: "#1c1e15",
|
||||||
|
secondary: "#1e2118",
|
||||||
|
tertiary: "#24291e",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/Chai/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CS1.6",
|
||||||
|
original: false,
|
||||||
|
accent: "#929a8d",
|
||||||
|
primary: "#3f4738",
|
||||||
|
secondary: "#5b6c51",
|
||||||
|
tertiary: "#4d5945",
|
||||||
|
"dc-import": "@import url(//dablulite.github.io/DiscordColorways/CS16/import.css);",
|
||||||
|
author: "DaBluLite",
|
||||||
|
authorID: "582170007505731594",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export const knownThemeVars = {
|
||||||
|
"Cyan": {
|
||||||
|
variable: "--cyan-accent-color",
|
||||||
|
accent: "--cyan-accent-color",
|
||||||
|
primary: "--cyan-background-primary",
|
||||||
|
secondary: "--cyan-background-secondary"
|
||||||
|
},
|
||||||
|
"Virtual Boy": {
|
||||||
|
variable: "--VBaccent",
|
||||||
|
tertiary: "--VBaccent-muted",
|
||||||
|
alt: {
|
||||||
|
tertiary: "--VBaccent-dimmest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Modular": {
|
||||||
|
variable: "--modular-hue",
|
||||||
|
accentVariables: {
|
||||||
|
h: "--modular-hue",
|
||||||
|
s: "--modular-saturation",
|
||||||
|
l: "--modular-lightness"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Solana": {
|
||||||
|
variable: "--accent-hue",
|
||||||
|
accentVariables: {
|
||||||
|
h: "--accent-hue",
|
||||||
|
s: "--accent-saturation",
|
||||||
|
l: "--accent-brightness"
|
||||||
|
},
|
||||||
|
primaryVariables: {
|
||||||
|
h: "--background-accent-hue",
|
||||||
|
s: "--background-accent-saturation",
|
||||||
|
l: "--background-accent-brightness"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mainColors = [
|
||||||
|
{ name: "accent", title: "Accent", var: "--brand-experiment" },
|
||||||
|
{ name: "primary", title: "Primary", var: "--background-primary" },
|
||||||
|
{ name: "secondary", title: "Secondary", var: "--background-secondary" },
|
||||||
|
{ name: "tertiary", title: "Tertiary", var: "--background-tertiary" }
|
||||||
|
];
|
1001
src/equicordplugins/discordColorways/css.ts
Normal file
1001
src/equicordplugins/discordColorways/css.ts
Normal file
File diff suppressed because it is too large
Load diff
416
src/equicordplugins/discordColorways/index.tsx
Normal file
416
src/equicordplugins/discordColorways/index.tsx
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
|
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
|
||||||
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import { 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 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, ColorwayObject } from "./types";
|
||||||
|
import { colorToHex, hexToString } from "./utils";
|
||||||
|
|
||||||
|
export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
|
||||||
|
return <Spinner className="colorways-creator-module-warning" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
const [
|
||||||
|
customColorways,
|
||||||
|
colorwaySourceFiles,
|
||||||
|
showColorwaysButton,
|
||||||
|
onDemandWays,
|
||||||
|
onDemandWaysTintedText,
|
||||||
|
useThinMenuButton,
|
||||||
|
onDemandWaysDiscordSaturation,
|
||||||
|
onDemandWaysOsAccentColor,
|
||||||
|
activeColorwayObject,
|
||||||
|
selectorViewMode,
|
||||||
|
showLabelsInSelectorGridView
|
||||||
|
] = await DataStore.getMany([
|
||||||
|
"customColorways",
|
||||||
|
"colorwaySourceFiles",
|
||||||
|
"showColorwaysButton",
|
||||||
|
"onDemandWays",
|
||||||
|
"onDemandWaysTintedText",
|
||||||
|
"useThinMenuButton",
|
||||||
|
"onDemandWaysDiscordSaturation",
|
||||||
|
"onDemandWaysOsAccentColor",
|
||||||
|
"activeColorwayObject",
|
||||||
|
"selectorViewMode",
|
||||||
|
"showLabelsInSelectorGridView"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const defaults = [
|
||||||
|
{ 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, 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 = {
|
||||||
|
get: () => document.getElementById("activeColorwayCSS")!.textContent || "",
|
||||||
|
set: (e: string) => {
|
||||||
|
if (!document.getElementById("activeColorwayCSS")) {
|
||||||
|
document.head.append(Object.assign(document.createElement("style"), {
|
||||||
|
id: "activeColorwayCSS",
|
||||||
|
textContent: e
|
||||||
|
}));
|
||||||
|
} else document.getElementById("activeColorwayCSS")!.textContent = e;
|
||||||
|
},
|
||||||
|
remove: () => document.getElementById("activeColorwayCSS")!.remove(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const versionData = {
|
||||||
|
pluginVersion: "5.7.1",
|
||||||
|
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: [EquicordDevs.DaBluLite, Devs.ImLvna],
|
||||||
|
dependencies: ["ServerListAPI", "MessageAccessoriesAPI"],
|
||||||
|
pluginVersion: versionData.pluginVersion,
|
||||||
|
creatorVersion: versionData.creatorVersion,
|
||||||
|
toolboxActions: {
|
||||||
|
"Change Colorway": () => openModal(props => <Selector modalProps={props} />),
|
||||||
|
"Open Colorway Creator": () => openModal(props => <CreatorModal modalProps={props} />),
|
||||||
|
"Open Color Stealer": () => openModal(props => <ColorPickerModal modalProps={props} />),
|
||||||
|
"Open Settings": () => SettingsRouter.open("ColorwaysSettings"),
|
||||||
|
"Open On-Demand Settings": () => SettingsRouter.open("ColorwaysOnDemand"),
|
||||||
|
"Manage Colorways...": () => SettingsRouter.open("ColorwaysManagement"),
|
||||||
|
"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
|
||||||
|
{
|
||||||
|
find: "this.renderArtisanalHack()",
|
||||||
|
replacement: {
|
||||||
|
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g,
|
||||||
|
replace: "$&,_:$1",
|
||||||
|
predicate: () => true
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,)(?=\1\(this)/,
|
||||||
|
replace: "(async ()=>$2)(),"
|
||||||
|
},
|
||||||
|
predicate: () => true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "colorPickerFooter:",
|
||||||
|
replacement: {
|
||||||
|
match: /function (\i).{0,200}colorPickerFooter:/,
|
||||||
|
replace: "$self.ColorPicker=$1;$&",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=section:(.{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;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
set ColorPicker(e) {
|
||||||
|
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.CUSTOM,
|
||||||
|
label: "Discord Colorways",
|
||||||
|
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",
|
||||||
|
label: "Colorways",
|
||||||
|
element: () => <Selector isSettings modalProps={{ onClose: () => new Promise(() => true), transitionState: 1 }} />,
|
||||||
|
className: "dc-colorway-selector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysSettings",
|
||||||
|
label: "Settings",
|
||||||
|
element: SettingsPage,
|
||||||
|
className: "dc-colorway-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysSourceManager",
|
||||||
|
label: "Sources",
|
||||||
|
element: SourceManager,
|
||||||
|
className: "dc-colorway-sources-manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysOnDemand",
|
||||||
|
label: "On-Demand",
|
||||||
|
element: OnDemandWaysPage,
|
||||||
|
className: "dc-colorway-ondemand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysStore",
|
||||||
|
label: "Store",
|
||||||
|
element: Store,
|
||||||
|
className: "dc-colorway-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: SectionTypes.DIVIDER
|
||||||
|
}
|
||||||
|
].filter(Boolean);
|
||||||
|
},
|
||||||
|
|
||||||
|
ColorwaysButton: () => <ColorwaysButton />,
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
addServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);
|
||||||
|
|
||||||
|
enableStyle(style);
|
||||||
|
ColorwayCSS.set((await DataStore.get("activeColorwayObject") as ColorwayObject).css || "");
|
||||||
|
|
||||||
|
addAccessory("colorways-btn", props => {
|
||||||
|
if (String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)) {
|
||||||
|
return <Flex flexDirection="column">
|
||||||
|
{String(props.message.content).match(/colorway:[0-9a-f]{0,100}/g)?.map((colorID: string) => {
|
||||||
|
colorID = hexToString(colorID.split("colorway:")[1]);
|
||||||
|
return <div className="colorwayMessage">
|
||||||
|
<div className="discordColorwayPreviewColorContainer" style={{ width: "56px", height: "56px", marginRight: "16px" }}>
|
||||||
|
{(() => {
|
||||||
|
if (colorID) {
|
||||||
|
if (!colorID.includes(",")) {
|
||||||
|
throw new Error("Invalid Colorway ID");
|
||||||
|
} else {
|
||||||
|
return colorID.split("|").filter(string => string.includes(",#"))[0].split(/,#/).map((color: string) => <div className="discordColorwayPreviewColor" style={{ backgroundColor: `#${colorToHex(color)}` }} />);
|
||||||
|
}
|
||||||
|
} else return null;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="colorwayMessage-contents">
|
||||||
|
<Forms.FormTitle>Colorway{/n:([A-Za-z0-9]+( [A-Za-z0-9]+)+)/i.exec(colorID) ? `: ${/n:([A-Za-z0-9]+( [A-Za-z0-9]+)+)/i.exec(colorID)![1]}` : ""}</Forms.FormTitle>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
onClick={() => openModal(modalProps => <CreatorModal
|
||||||
|
modalProps={modalProps}
|
||||||
|
colorwayID={colorID}
|
||||||
|
/>)}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
>
|
||||||
|
Add this Colorway...
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
Clipboard.copy(colorID);
|
||||||
|
Toasts.show({
|
||||||
|
message: "Copied Colorway ID Successfully",
|
||||||
|
type: 1,
|
||||||
|
id: "copy-colorway-id-notify",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
>
|
||||||
|
Copy Colorway ID
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (!colorID.includes(",")) {
|
||||||
|
throw new Error("Invalid Colorway ID");
|
||||||
|
} else {
|
||||||
|
colorID.split("|").forEach((prop: string) => {
|
||||||
|
if (prop.includes(",#")) {
|
||||||
|
DataStore.set("activeColorwayObject", {
|
||||||
|
id: "Temporary Colorway", css: generateCss(
|
||||||
|
colorToHex(prop.split(/,#/)[1]),
|
||||||
|
colorToHex(prop.split(/,#/)[2]),
|
||||||
|
colorToHex(prop.split(/,#/)[3]),
|
||||||
|
colorToHex(prop.split(/,#/)[0]),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
32,
|
||||||
|
"Temporary Colorway"
|
||||||
|
), sourceType: "temporary", source: null
|
||||||
|
});
|
||||||
|
ColorwayCSS.set(generateCss(
|
||||||
|
colorToHex(prop.split(/,#/)[1]),
|
||||||
|
colorToHex(prop.split(/,#/)[2]),
|
||||||
|
colorToHex(prop.split(/,#/)[3]),
|
||||||
|
colorToHex(prop.split(/,#/)[0]),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
32,
|
||||||
|
"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);
|
||||||
|
disableStyle(style);
|
||||||
|
ColorwayCSS.remove();
|
||||||
|
removeAccessory("colorways-btn");
|
||||||
|
},
|
||||||
|
});
|
1379
src/equicordplugins/discordColorways/style.css
Normal file
1379
src/equicordplugins/discordColorways/style.css
Normal file
File diff suppressed because it is too large
Load diff
65
src/equicordplugins/discordColorways/types.ts
Normal file
65
src/equicordplugins/discordColorways/types.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Colorway {
|
||||||
|
[key: string]: any,
|
||||||
|
name: string,
|
||||||
|
"dc-import": string,
|
||||||
|
accent: string,
|
||||||
|
primary: string,
|
||||||
|
secondary: string,
|
||||||
|
tertiary: string,
|
||||||
|
original?: boolean,
|
||||||
|
author: string,
|
||||||
|
authorID: string,
|
||||||
|
colors?: string[],
|
||||||
|
isGradient?: boolean,
|
||||||
|
sourceType?: "online" | "offline" | "temporary" | null,
|
||||||
|
source?: string,
|
||||||
|
linearGradient?: string,
|
||||||
|
preset?: string,
|
||||||
|
creatorVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColorPickerProps {
|
||||||
|
color: number;
|
||||||
|
showEyeDropper: boolean;
|
||||||
|
suggestedColors: string[];
|
||||||
|
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;
|
||||||
|
}
|
150
src/equicordplugins/discordColorways/utils.ts
Normal file
150
src/equicordplugins/discordColorways/utils.ts
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function HexToHSL(H: string) {
|
||||||
|
let r: any = 0, g: any = 0, b: any = 0;
|
||||||
|
if (H.length === 4) r = "0x" + H[1] + H[1], g = "0x" + H[2] + H[2], b = "0x" + H[3] + H[3];
|
||||||
|
else if (H.length === 7) {
|
||||||
|
r = "0x" + H[1] + H[2];
|
||||||
|
g = "0x" + H[3] + H[4];
|
||||||
|
b = "0x" + H[5] + H[6];
|
||||||
|
}
|
||||||
|
r /= 255, g /= 255, b /= 255;
|
||||||
|
var cmin = Math.min(r, g, b),
|
||||||
|
cmax = Math.max(r, g, b),
|
||||||
|
delta = cmax - cmin,
|
||||||
|
h = 0,
|
||||||
|
s = 0,
|
||||||
|
l = 0;
|
||||||
|
if (delta === 0) h = 0;
|
||||||
|
else if (cmax === r) h = ((g - b) / delta) % 6;
|
||||||
|
else if (cmax === g) h = (b - r) / delta + 2;
|
||||||
|
else h = (r - g) / delta + 4;
|
||||||
|
h = Math.round(h * 60);
|
||||||
|
if (h < 0) h += 360;
|
||||||
|
l = (cmax + cmin) / 2;
|
||||||
|
s = delta === 0
|
||||||
|
? 0
|
||||||
|
: delta / (1 - Math.abs(2 * l - 1));
|
||||||
|
s = +(s * 100).toFixed(1);
|
||||||
|
l = +(l * 100).toFixed(1);
|
||||||
|
|
||||||
|
return [Math.round(h), Math.round(s), Math.round(l)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const canonicalizeHex = (hex: string) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
|
ctx.fillStyle = hex;
|
||||||
|
hex = ctx.fillStyle;
|
||||||
|
canvas.remove();
|
||||||
|
|
||||||
|
return hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringToHex = (str: string) => {
|
||||||
|
let hex = "";
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < str.length;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
const charCode = str.charCodeAt(i);
|
||||||
|
const hexValue = charCode.toString(16);
|
||||||
|
hex += hexValue.padStart(2, "0");
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hexToString = (hex: string) => {
|
||||||
|
let str = "";
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
const hexValue = hex.substr(i, 2);
|
||||||
|
const decimalValue = parseInt(hexValue, 16);
|
||||||
|
str += String.fromCharCode(decimalValue);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getHex(str: string): string {
|
||||||
|
const color = Object.assign(
|
||||||
|
document.createElement("canvas").getContext("2d") as {},
|
||||||
|
{ fillStyle: str }
|
||||||
|
).fillStyle;
|
||||||
|
if (color.includes("rgba(")) {
|
||||||
|
return getHex(String([...color.split(",").slice(0, 3), ")"]).replace(",)", ")").replace("a", ""));
|
||||||
|
} else {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFontOnBg(bgColor: string) {
|
||||||
|
var color = (bgColor.charAt(0) === "#") ? bgColor.substring(1, 7) : bgColor;
|
||||||
|
var r = parseInt(color.substring(0, 2), 16);
|
||||||
|
var g = parseInt(color.substring(2, 4), 16);
|
||||||
|
var b = parseInt(color.substring(4, 6), 16);
|
||||||
|
return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
|
||||||
|
"#000000" : "#ffffff";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function $e(funcArray: Array<(...vars: any) => void>, ...vars: any[]) {
|
||||||
|
funcArray.forEach(e => e(vars));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hslToHex(h: number, s: number, l: number) {
|
||||||
|
h /= 360;
|
||||||
|
s /= 100;
|
||||||
|
l /= 100;
|
||||||
|
let r: any, g: any, b: any;
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p: number, q: number, t: number) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
const toHex = (x: number) => {
|
||||||
|
const hex = Math.round(x * 255).toString(16);
|
||||||
|
return hex.length === 1 ? "0" + hex : hex;
|
||||||
|
};
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rgbToHex(r: number, g: number, b: number) {
|
||||||
|
const toHex = (x: number) => {
|
||||||
|
const hex = Math.round(x * 255).toString(16);
|
||||||
|
return hex.length === 1 ? "0" + hex : hex;
|
||||||
|
};
|
||||||
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function colorToHex(color: string) {
|
||||||
|
var colorType = "hex";
|
||||||
|
if (color.includes("hsl")) {
|
||||||
|
colorType = "hsl";
|
||||||
|
} else if (color.includes("rgb")) {
|
||||||
|
colorType = "rgb";
|
||||||
|
}
|
||||||
|
color = color.replaceAll(",", "").replace(/.+?\(/, "").replace(")", "").replaceAll(/[ \t]+\/[ \t]+/g, " ").replaceAll("%", "").replaceAll("/", "");
|
||||||
|
if (colorType === "hsl") {
|
||||||
|
color = hslToHex(Number(color.split(" ")[0]), Number(color.split(" ")[1]), Number(color.split(" ")[2]));
|
||||||
|
}
|
||||||
|
if (colorType === "rgb") {
|
||||||
|
color = rgbToHex(Number(color.split(" ")[0]), Number(color.split(" ")[1]), Number(color.split(" ")[2]));
|
||||||
|
}
|
||||||
|
return color.replace("#", "");
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue