mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-20 11:57:02 -04:00
Merge branch 'dev'
This commit is contained in:
commit
64fe43281a
40 changed files with 4493 additions and 2364 deletions
18
src/equicordplugins/discordColorways/colorwaysAPI.ts
Normal file
18
src/equicordplugins/discordColorways/colorwaysAPI.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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") as any).textContent = e;
|
||||||
|
},
|
||||||
|
remove: () => document.getElementById("activeColorwayCSS")?.remove(),
|
||||||
|
};
|
|
@ -4,52 +4,49 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, useEffect, useState } from "../";
|
||||||
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";
|
import { getAutoPresets } from "../css";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
export default function ({ modalProps, onChange, autoColorwayId = "" }: { modalProps: ModalProps, onChange: (autoPresetId: string) => void, autoColorwayId: string; }) {
|
export default function ({ modalProps, onChange, autoColorwayId = "" }: { modalProps: ModalProps, onChange: (autoPresetId: string) => void, autoColorwayId: string; }) {
|
||||||
const [autoId, setAutoId] = useState(autoColorwayId);
|
const [autoId, setAutoId] = useState(autoColorwayId);
|
||||||
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
|
const [theme, setTheme] = useState("discord");
|
||||||
return <ModalRoot {...modalProps}>
|
|
||||||
<ModalHeader>
|
useEffect(() => {
|
||||||
<Text variant="heading-lg/semibold" tag="h1">
|
async function load() {
|
||||||
Auto Preset Settings
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
</Text>
|
}
|
||||||
</ModalHeader>
|
load();
|
||||||
<ModalContent>
|
}, []);
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Auto Preset Settings
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
<div className="dc-info-card" style={{ marginTop: "1em" }}>
|
<div className="dc-info-card" style={{ marginTop: "1em" }}>
|
||||||
<strong>About the Auto Colorway</strong>
|
<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>
|
<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>
|
||||||
<div style={{ marginBottom: "20px" }}>
|
<div style={{ marginBottom: "20px" }}>
|
||||||
<Forms.FormTitle>Presets:</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">Presets:</span>
|
||||||
{Object.values(getAutoPresets()).map(autoPreset => {
|
{Object.values(getAutoPresets()).map(autoPreset => <div
|
||||||
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={autoId === autoPreset.id}>
|
className="discordColorway"
|
||||||
<div
|
aria-checked={autoId === autoPreset.id}
|
||||||
className={`${radioBar} ${radioPositionLeft}`}
|
style={{ padding: "10px", marginBottom: "8px" }}
|
||||||
style={{ padding: "10px" }}
|
onClick={() => {
|
||||||
onClick={() => {
|
setAutoId(autoPreset.id);
|
||||||
setAutoId(autoPreset.id);
|
}}>
|
||||||
}}>
|
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<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" />
|
||||||
<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" />}
|
||||||
{autoId === autoPreset.id && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
|
</svg>
|
||||||
</svg>
|
<span className="colorwayLabel">{autoPreset.name}</span>
|
||||||
<Text variant="eyebrow" tag="h5">{autoPreset.name}</Text>
|
</div>)}
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND_NEW}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
DataStore.set("activeAutoPreset", autoId);
|
DataStore.set("activeAutoPreset", autoId);
|
||||||
onChange(autoId);
|
onChange(autoId);
|
||||||
|
@ -57,18 +54,15 @@ export default function ({ modalProps, onChange, autoColorwayId = "" }: { modalP
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,25 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Flex } from "@components/Flex";
|
import { DataStore, Toasts, useEffect, useState } from "..";
|
||||||
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 { mainColors } from "../constants";
|
||||||
import { colorVariables } from "../css";
|
import { colorVariables } from "../css";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
import { getHex } from "../utils";
|
import { getHex } from "../utils";
|
||||||
|
import { CopyIcon } from "./Icons";
|
||||||
|
|
||||||
export default function ({ modalProps }: { modalProps: ModalProps; }) {
|
export default function ({ modalProps }: { modalProps: ModalProps; }) {
|
||||||
const [ColorVars, setColorVars] = useState<string[]>(colorVariables);
|
const [ColorVars, setColorVars] = useState<string[]>(colorVariables);
|
||||||
const [collapsedSettings, setCollapsedSettings] = useState<boolean>(true);
|
const [collapsedSettings, setCollapsedSettings] = useState<boolean>(true);
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
let results: string[];
|
let results: string[];
|
||||||
function searchToolboxItems(e: string) {
|
function searchToolboxItems(e: string) {
|
||||||
results = [];
|
results = [];
|
||||||
|
@ -37,55 +34,53 @@ export default function ({ modalProps }: { modalProps: ModalProps; }) {
|
||||||
setColorVars(results);
|
setColorVars(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ModalRoot {...modalProps} className="colorwayColorpicker">
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<Flex style={{ gap: "8px", marginBottom: "8px" }}>
|
<div style={{ gap: "8px", marginBottom: "8px", display: "flex" }}>
|
||||||
<TextInput
|
<input
|
||||||
className="colorwaysColorpicker-search"
|
type="text"
|
||||||
|
className="colorwaySelector-search"
|
||||||
placeholder="Search for a color:"
|
placeholder="Search for a color:"
|
||||||
onChange={e => {
|
onChange={({ currentTarget: { value } }) => {
|
||||||
searchToolboxItems(e);
|
searchToolboxItems(value);
|
||||||
if (e) {
|
if (value) {
|
||||||
setCollapsedSettings(false);
|
setCollapsedSettings(false);
|
||||||
} else {
|
} else {
|
||||||
setCollapsedSettings(true);
|
setCollapsedSettings(true);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<button
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.ICON}
|
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => setCollapsedSettings(!collapsedSettings)}
|
onClick={() => setCollapsedSettings(!collapsedSettings)}
|
||||||
>
|
>
|
||||||
<svg width="32" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img">
|
<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" />
|
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
|
||||||
</svg>
|
</svg>
|
||||||
</Button>
|
</button>
|
||||||
</Flex>
|
</div>
|
||||||
<ScrollerThin style={{ color: "var(--text-normal)" }} orientation="vertical" className={collapsedSettings ? " colorwaysColorpicker-collapsed" : ""} paddingFix>
|
<div style={{ color: "var(--text-normal)", overflow: "hidden auto", scrollbarWidth: "none" }} className={collapsedSettings ? " colorwaysColorpicker-collapsed" : ""}>
|
||||||
{ColorVars.map((colorVariable: string) => <div
|
{ColorVars.map((colorVariable: string) => <div
|
||||||
id={`colorways-colorstealer-item_${colorVariable}`}
|
id={`colorways-colorstealer-item_${colorVariable}`}
|
||||||
className="colorwaysCreator-settingItm colorwaysCreator-toolboxItm"
|
className="colorwaysCreator-settingItm colorwaysCreator-toolboxItm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Clipboard.copy(getHex(getComputedStyle(document.body).getPropertyValue("--" + colorVariable)));
|
navigator.clipboard.writeText(getHex(getComputedStyle(document.body).getPropertyValue("--" + colorVariable)));
|
||||||
Toasts.show({ message: "Color " + colorVariable + " copied to clipboard", id: "toolbox-color-var-copied", type: 1 });
|
Toasts.show({ message: "Color " + colorVariable + " copied to clipboard", id: "toolbox-color-var-copied", type: 1 });
|
||||||
}} style={{ "--brand-experiment": `var(--${colorVariable})` } as React.CSSProperties}>
|
}} style={{ "--brand-experiment": `var(--${colorVariable})` } as React.CSSProperties}>
|
||||||
{`Copy ${colorVariable}`}
|
{`Copy ${colorVariable}`}
|
||||||
</div>)}
|
</div>)}
|
||||||
</ScrollerThin>
|
</div>
|
||||||
<Flex style={{ justifyContent: "space-between", marginTop: "8px" }} wrap="wrap" className={collapsedSettings ? "" : " colorwaysColorpicker-collapsed"}>
|
<div style={{ justifyContent: "space-between", marginTop: "8px", flexWrap: "wrap", gap: "1em" }} className={collapsedSettings ? "" : " colorwaysColorpicker-collapsed"}>
|
||||||
{mainColors.map(mainColor => <div
|
{mainColors.map(mainColor => <div
|
||||||
id={`colorways-toolbox_copy-${mainColor.name}`}
|
id={`colorways-toolbox_copy-${mainColor.name}`}
|
||||||
className="colorwayToolbox-listItem"
|
className="colorwayToolbox-listItem"
|
||||||
>
|
>
|
||||||
<CopyIcon onClick={() => {
|
<CopyIcon onClick={() => {
|
||||||
Clipboard.copy(getHex(getComputedStyle(document.body).getPropertyValue(mainColor.var)));
|
navigator.clipboard.writeText(getHex(getComputedStyle(document.body).getPropertyValue(mainColor.var)));
|
||||||
Toasts.show({ message: `${mainColor.title} color copied to clipboard`, id: `toolbox-${mainColor.name}-color-copied`, type: 1 });
|
Toasts.show({ message: `${mainColor.title} color copied to clipboard`, id: `toolbox-${mainColor.name}-color-copied`, type: 1 });
|
||||||
}} width={20} height={20} className="colorwayToolbox-listItemSVG" />
|
}} width={20} height={20} className="colorwayToolbox-listItemSVG" />
|
||||||
<span className="colorwaysToolbox-label">{`Copy ${mainColor.title} Color`}</span>
|
<span className="colorwaysToolbox-label">{`Copy ${mainColor.title} Color`}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,67 +4,70 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
import { DataStore, useEffect, useState } from "..";
|
||||||
import { findByProps } from "@webpack";
|
|
||||||
import { Button, Forms, ScrollerThin, Switch, Text, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { getPreset } from "../css";
|
import { getPreset } from "../css";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
import Setting from "./Setting";
|
||||||
|
import Switch from "./Switch";
|
||||||
|
|
||||||
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; }) {
|
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 [tintedText, setTintedText] = useState<boolean>(hasTintedText);
|
||||||
const [discordSaturation, setDiscordSaturation] = useState<boolean>(hasDiscordSaturation);
|
const [discordSaturation, setDiscordSaturation] = useState<boolean>(hasDiscordSaturation);
|
||||||
const [preset, setPreset] = useState<string>(presetId);
|
const [preset, setPreset] = useState<string>(presetId);
|
||||||
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
|
const [theme, setTheme] = useState("discord");
|
||||||
return <ModalRoot {...modalProps} className="colorwaysPresetPicker">
|
|
||||||
<ModalHeader><Text variant="heading-lg/semibold" tag="h1">Creator Settings</Text></ModalHeader>
|
useEffect(() => {
|
||||||
<ModalContent className="colorwaysPresetPicker-content">
|
async function load() {
|
||||||
<Forms.FormTitle>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">Creator Settings</h2>
|
||||||
|
<div className="colorwaysModalContent" style={{
|
||||||
|
minWidth: "500px"
|
||||||
|
}}>
|
||||||
|
<span className="colorwaysModalSectionHeader">
|
||||||
Presets:
|
Presets:
|
||||||
</Forms.FormTitle>
|
</span>
|
||||||
<ScrollerThin orientation="vertical" paddingFix style={{ paddingRight: "2px", marginBottom: "20px", maxHeight: "250px" }}>
|
<div className="colorwaysScroller" style={{ paddingRight: "2px", marginBottom: "20px", maxHeight: "250px" }}>
|
||||||
{Object.values(getPreset()).map(pre => {
|
{Object.values(getPreset()).map(pre => <div
|
||||||
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={preset === pre.id}>
|
aria-checked={preset === pre.id}
|
||||||
<div
|
className="discordColorway"
|
||||||
className={`${radioBar} ${radioPositionLeft}`}
|
style={{ padding: "10px", marginBottom: "8px" }}
|
||||||
style={{ padding: "10px" }}
|
onClick={() => {
|
||||||
onClick={() => {
|
setPreset(pre.id);
|
||||||
setPreset(pre.id);
|
}}>
|
||||||
}}>
|
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<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" />
|
||||||
<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" />}
|
||||||
{preset === pre.id && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
|
</svg>
|
||||||
</svg>
|
<span className="colorwayLabel">{pre.name}</span>
|
||||||
<Text variant="eyebrow" tag="h5">{pre.name}</Text>
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
<Setting divider>
|
||||||
})}
|
<Switch value={tintedText} onChange={setTintedText} label="Use colored text" />
|
||||||
</ScrollerThin>
|
</Setting>
|
||||||
<Switch value={tintedText} onChange={setTintedText}>Use colored text</Switch>
|
<Switch value={discordSaturation} onChange={setDiscordSaturation} label="Use Discord's saturation" />
|
||||||
<Switch value={discordSaturation} onChange={setDiscordSaturation} hideBorder style={{ marginBottom: "0" }}>Use Discord's saturation</Switch>
|
</div>
|
||||||
</ModalContent>
|
<div className="colorwaysModalFooter">
|
||||||
<ModalFooter>
|
<button
|
||||||
<Button
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
style={{ marginLeft: 8 }}
|
|
||||||
color={Button.Colors.BRAND_NEW}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSettings({ presetId: preset, discordSaturation: discordSaturation, tintedText: tintedText });
|
onSettings({ presetId: preset, discordSaturation: discordSaturation, tintedText: tintedText });
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
117
src/equicordplugins/discordColorways/components/ColorwayID.tsx
Normal file
117
src/equicordplugins/discordColorways/components/ColorwayID.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore, openModal, Toasts, useEffect, useState } from "..";
|
||||||
|
import { ColorwayCSS } from "../colorwaysAPI";
|
||||||
|
import { generateCss } from "../css";
|
||||||
|
import { colorToHex, hexToString } from "../utils";
|
||||||
|
import CreatorModal from "./CreatorModal";
|
||||||
|
|
||||||
|
export let changeThemeIDCard: (theme: string) => void = () => { };
|
||||||
|
|
||||||
|
export default function ({ props }) {
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
changeThemeIDCard = theme => setTheme(theme);
|
||||||
|
load();
|
||||||
|
return () => {
|
||||||
|
changeThemeIDCard = () => { };
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
if (String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)) {
|
||||||
|
return <div className="colorwayIDCard" data-theme={theme}>
|
||||||
|
{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">
|
||||||
|
<span className="colorwaysModalSectionHeader">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]}` : ""}</span>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "1em"
|
||||||
|
}}>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
onClick={() => openModal(modalProps => <CreatorModal
|
||||||
|
modalProps={modalProps}
|
||||||
|
colorwayID={colorID}
|
||||||
|
/>)}
|
||||||
|
>
|
||||||
|
Add this Colorway...
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(colorID);
|
||||||
|
Toasts.show({
|
||||||
|
message: "Copied Colorway ID Successfully",
|
||||||
|
type: 1,
|
||||||
|
id: "copy-colorway-id-notify",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy Colorway ID
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
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"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Apply temporarily
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,12 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import { DataStore, FluxDispatcher, FluxEvents, openModal, useEffect, useState } from "..";
|
||||||
import { openModal } from "@utils/modal";
|
|
||||||
import { FluxDispatcher, Text, Tooltip, useEffect, useState } from "@webpack/common";
|
|
||||||
import { FluxEvents } from "@webpack/types";
|
|
||||||
|
|
||||||
import { getAutoPresets } from "../css";
|
import { getAutoPresets } from "../css";
|
||||||
import { ColorwayObject } from "../types";
|
import { ColorwayObject } from "../types";
|
||||||
import { PalleteIcon } from "./Icons";
|
import { PalleteIcon } from "./Icons";
|
||||||
import Selector from "./Selector";
|
import Selector from "./MainModal";
|
||||||
|
import Tooltip from "./Tooltip";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const [activeColorway, setActiveColorway] = useState<string>("None");
|
const [activeColorway, setActiveColorway] = useState<string>("None");
|
||||||
|
@ -39,11 +36,11 @@ export default function () {
|
||||||
<>
|
<>
|
||||||
{!isThin ? <>
|
{!isThin ? <>
|
||||||
<span>Colorways</span>
|
<span>Colorways</span>
|
||||||
<Text variant="text-xs/normal" style={{ color: "var(--text-muted)", fontWeight: 500 }}>{"Active Colorway: " + activeColorway}</Text>
|
<span style={{ color: "var(--text-muted)", fontWeight: 500, fontSize: 12 }}>{"Active Colorway: " + activeColorway}</span>
|
||||||
</> : <span>{"Active Colorway: " + activeColorway}</span>}
|
</> : <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> : <></>}
|
{activeColorway === "Auto" ? <span style={{ color: "var(--text-muted)", fontWeight: 500, fontSize: 12 }}>{"Auto Preset: " + (getAutoPresets()[autoPreset].name || "None")}</span> : <></>}
|
||||||
</>
|
</>
|
||||||
} position="right" tooltipContentClassName="colorwaysBtn-tooltipContent"
|
} position="right"
|
||||||
>
|
>
|
||||||
{({ onMouseEnter, onMouseLeave, onClick }) => visibility ? <div className="ColorwaySelectorBtnContainer">
|
{({ onMouseEnter, onMouseLeave, onClick }) => visibility ? <div className="ColorwaySelectorBtnContainer">
|
||||||
<div
|
<div
|
||||||
|
@ -58,7 +55,7 @@ export default function () {
|
||||||
onClick();
|
onClick();
|
||||||
openModal((props: any) => <Selector modalProps={props} />);
|
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>
|
>{isThin ? <span style={{ color: "var(--header-primary)", fontWeight: 700, fontSize: 9 }}>Colorways</span> : <PalleteIcon />}</div>
|
||||||
</div> : <></>}
|
</div> : <></>}
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
import { DataStore, useEffect, useState } from "..";
|
||||||
import { Button, Forms, ScrollerThin, Text, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { knownThemeVars } from "../constants";
|
import { knownThemeVars } from "../constants";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
import { getFontOnBg, getHex } from "../utils";
|
import { getFontOnBg, getHex } from "../utils";
|
||||||
|
|
||||||
export default function ({
|
export default function ({
|
||||||
|
@ -37,15 +36,22 @@ export default function ({
|
||||||
document.body
|
document.body
|
||||||
).getPropertyValue("--background-tertiary")
|
).getPropertyValue("--background-tertiary")
|
||||||
));
|
));
|
||||||
return <ModalRoot {...modalProps} className="colorwayCreator-modal">
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1">
|
useEffect(() => {
|
||||||
Conflicting Colors Found
|
async function load() {
|
||||||
</Text>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
</ModalHeader>
|
}
|
||||||
<ModalContent className="colorwayCreator-menuWrapper">
|
load();
|
||||||
<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>
|
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Conflicting Colors Found
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
<span className="colorwaysConflictingColors-warning">Multiple known themes have been found, select the colors you want to copy from below:</span>
|
||||||
|
<span className="colorwaysModalSectionHeader">Colors to copy:</span>
|
||||||
<div className="colorwayCreator-colorPreviews">
|
<div className="colorwayCreator-colorPreviews">
|
||||||
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: primaryColor, color: getFontOnBg(primaryColor) }} >Primary</div>
|
<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: secondaryColor, color: getFontOnBg(secondaryColor) }} >Secondary</div>
|
||||||
|
@ -53,12 +59,12 @@ export default function ({
|
||||||
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: accentColor, color: getFontOnBg(accentColor) }} >Accent</div>
|
<div className="colorwayCreator-colorPreview" style={{ backgroundColor: accentColor, color: getFontOnBg(accentColor) }} >Accent</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="colorwaysCreator-settingCat">
|
<div className="colorwaysCreator-settingCat">
|
||||||
<ScrollerThin orientation="vertical" className="colorwaysCreator-settingsList" paddingFix>
|
<div className="colorwaysCreator-settingsList">
|
||||||
<div
|
<div
|
||||||
id="colorways-colorstealer-item_Default"
|
id="colorways-colorstealer-item_Default"
|
||||||
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
||||||
>
|
>
|
||||||
<Forms.FormTitle>Discord</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">Discord</span>
|
||||||
<div className="colorwayCreator-colorPreviews">
|
<div className="colorwayCreator-colorPreviews">
|
||||||
<div
|
<div
|
||||||
className="colorwayCreator-colorPreview" style={{
|
className="colorwayCreator-colorPreview" style={{
|
||||||
|
@ -164,7 +170,7 @@ export default function ({
|
||||||
}
|
}
|
||||||
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
className="colorwaysCreator-settingItm colorwaysCreator-colorPreviewItm"
|
||||||
>
|
>
|
||||||
<Forms.FormTitle>{Object.keys(knownThemeVars)[i] + (theme.alt ? " (Main)" : "")}</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">{Object.keys(knownThemeVars)[i] + (theme.alt ? " (Main)" : "")}</span>
|
||||||
<div className="colorwayCreator-colorPreviews">
|
<div className="colorwayCreator-colorPreviews">
|
||||||
{theme.primary && getComputedStyle(document.body).getPropertyValue(theme.primary).match(/^\d.*%$/)
|
{theme.primary && getComputedStyle(document.body).getPropertyValue(theme.primary).match(/^\d.*%$/)
|
||||||
? <div
|
? <div
|
||||||
|
@ -294,15 +300,12 @@ export default function ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</ScrollerThin>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFinished({
|
onFinished({
|
||||||
accent: accentColor,
|
accent: accentColor,
|
||||||
|
@ -312,7 +315,7 @@ export default function ({
|
||||||
});
|
});
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>Finish</Button>
|
>Finish</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot >;
|
</div >;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,30 +4,12 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { ColorPicker, DataStore, openModal, PluginProps, Slider, useEffect, useReducer, UserStore, useState } from "..";
|
||||||
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 { knownThemeVars } from "../constants";
|
||||||
import { generateCss, getPreset, gradientPresetIds, PrimarySatDiffs, pureGradientBase } from "../css";
|
import { generateCss, getPreset, gradientPresetIds, PrimarySatDiffs, pureGradientBase } from "../css";
|
||||||
import { Colorway } from "../types";
|
import { Colorway, ModalProps } from "../types";
|
||||||
import { colorToHex, getHex, HexToHSL, hexToString } from "../utils";
|
import { colorToHex, getHex, HexToHSL, hexToString } from "../utils";
|
||||||
|
import { updateRemoteSources } from "../wsClient";
|
||||||
import ColorwayCreatorSettingsModal from "./ColorwayCreatorSettingsModal";
|
import ColorwayCreatorSettingsModal from "./ColorwayCreatorSettingsModal";
|
||||||
import ConflictingColorsModal from "./ConflictingColorsModal";
|
import ConflictingColorsModal from "./ConflictingColorsModal";
|
||||||
import InputColorwayIdModal from "./InputColorwayIdModal";
|
import InputColorwayIdModal from "./InputColorwayIdModal";
|
||||||
|
@ -35,61 +17,98 @@ import SaveColorwayModal from "./SaveColorwayModal";
|
||||||
import ThemePreviewCategory from "./ThemePreview";
|
import ThemePreviewCategory from "./ThemePreview";
|
||||||
export default function ({
|
export default function ({
|
||||||
modalProps,
|
modalProps,
|
||||||
loadUIProps,
|
loadUIProps = () => new Promise(() => { }),
|
||||||
colorwayID
|
colorwayID
|
||||||
}: {
|
}: {
|
||||||
modalProps: ModalProps;
|
modalProps: ModalProps;
|
||||||
loadUIProps?: () => Promise<void>;
|
loadUIProps?: () => Promise<void>;
|
||||||
colorwayID?: string;
|
colorwayID?: string;
|
||||||
}) {
|
}) {
|
||||||
const [accentColor, setAccentColor] = useState<string>("5865f2");
|
const [colors, updateColors] = useReducer((colors: {
|
||||||
const [primaryColor, setPrimaryColor] = useState<string>("313338");
|
accent: string,
|
||||||
const [secondaryColor, setSecondaryColor] = useState<string>("2b2d31");
|
primary: string,
|
||||||
const [tertiaryColor, setTertiaryColor] = useState<string>("1e1f22");
|
secondary: string,
|
||||||
|
tertiary: string;
|
||||||
|
}, action: {
|
||||||
|
task: "accent" | "primary" | "secondary" | "tertiary" | "all",
|
||||||
|
color?: string;
|
||||||
|
colorObj?: {
|
||||||
|
accent: string,
|
||||||
|
primary: string,
|
||||||
|
secondary: string,
|
||||||
|
tertiary: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
if (action.task === "all") {
|
||||||
|
return { ...action.colorObj } as {
|
||||||
|
accent: string,
|
||||||
|
primary: string,
|
||||||
|
secondary: string,
|
||||||
|
tertiary: string;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { ...colors, [action.task as "accent" | "primary" | "secondary" | "tertiary"]: action.color } as {
|
||||||
|
accent: string,
|
||||||
|
primary: string,
|
||||||
|
secondary: string,
|
||||||
|
tertiary: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
accent: "5865f2",
|
||||||
|
primary: "313338",
|
||||||
|
secondary: "2b2d31",
|
||||||
|
tertiary: "1e1f22"
|
||||||
|
});
|
||||||
const [colorwayName, setColorwayName] = useState<string>("");
|
const [colorwayName, setColorwayName] = useState<string>("");
|
||||||
const [tintedText, setTintedText] = useState<boolean>(true);
|
const [tintedText, setTintedText] = useState<boolean>(true);
|
||||||
const [discordSaturation, setDiscordSaturation] = useState<boolean>(true);
|
const [discordSaturation, setDiscordSaturation] = useState<boolean>(true);
|
||||||
const [preset, setPreset] = useState<string>("default");
|
const [preset, setPreset] = useState<string>("default");
|
||||||
const [presetColorArray, setPresetColorArray] = useState<string[]>(["accent", "primary", "secondary", "tertiary"]);
|
const [presetColorArray, setPresetColorArray] = useState<string[]>(["accent", "primary", "secondary", "tertiary"]);
|
||||||
const [mutedTextBrightness, setMutedTextBrightness] = useState<number>(Math.min(HexToHSL("#" + primaryColor)[2] + (3.6 * 3), 100));
|
const [mutedTextBrightness, setMutedTextBrightness] = useState<number>(Math.min(HexToHSL("#" + colors.primary)[2] + (3.6 * 3), 100));
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
const colorProps = {
|
useEffect(() => {
|
||||||
accent: {
|
async function load() {
|
||||||
get: accentColor,
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
};
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setColor = [
|
||||||
|
"accent",
|
||||||
|
"primary",
|
||||||
|
"secondary",
|
||||||
|
"tertiary"
|
||||||
|
] as ("accent" | "primary" | "secondary" | "tertiary")[];
|
||||||
|
|
||||||
|
const colorProps = [
|
||||||
|
{
|
||||||
|
name: "Accent",
|
||||||
|
id: "accent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Primary",
|
||||||
|
id: "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Secondary",
|
||||||
|
id: "secondary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tertiary",
|
||||||
|
id: "tertiary"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (colorwayID) {
|
if (colorwayID) {
|
||||||
if (!colorwayID.includes(",")) {
|
if (!colorwayID.includes(",")) {
|
||||||
throw new Error("Invalid Colorway ID");
|
throw new Error("Invalid Colorway ID");
|
||||||
} else {
|
} else {
|
||||||
const setColor = [
|
|
||||||
setAccentColor,
|
|
||||||
setPrimaryColor,
|
|
||||||
setSecondaryColor,
|
|
||||||
setTertiaryColor
|
|
||||||
];
|
|
||||||
colorwayID.split("|").forEach((prop: string) => {
|
colorwayID.split("|").forEach((prop: string) => {
|
||||||
if (prop.includes(",#")) {
|
if (prop.includes(",#")) {
|
||||||
prop.split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
|
prop.split(/,#/).forEach((color: string, i: number) => updateColors({ task: setColor[i], color: colorToHex(color) }));
|
||||||
}
|
}
|
||||||
if (prop.includes("n:")) {
|
if (prop.includes("n:")) {
|
||||||
setColorwayName(prop.split("n:")[1]);
|
setColorwayName(prop.split("n:")[1]);
|
||||||
|
@ -115,43 +134,54 @@ export default function ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot {...modalProps} className="colorwayCreator-modal">
|
<div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<ModalHeader>
|
<h2 className="colorwaysModalHeader">Create a Colorway</h2>
|
||||||
<Text variant="heading-lg/semibold" tag="h1">
|
<div className="colorwaysModalContent" style={{ minWidth: 500 }}>
|
||||||
Create Colorway
|
<span className="colorwaysModalSectionHeader">Name:</span>
|
||||||
</Text>
|
<input
|
||||||
</ModalHeader>
|
type="text"
|
||||||
<ModalContent className="colorwayCreator-menuWrapper">
|
className="colorwaySelector-search"
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
|
||||||
Name:
|
|
||||||
</Forms.FormTitle>
|
|
||||||
<TextInput
|
|
||||||
placeholder="Give your Colorway a name"
|
placeholder="Give your Colorway a name"
|
||||||
value={colorwayName}
|
value={colorwayName}
|
||||||
onChange={setColorwayName}
|
onInput={e => setColorwayName(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<div className="colorwaysCreator-settingCat">
|
<div className="colorwaysCreator-settingCat">
|
||||||
<Forms.FormTitle style={{ marginBottom: "0" }}>
|
<span className="colorwaysModalSectionHeader">Colors & Values:</span>
|
||||||
Colors & Values:
|
|
||||||
</Forms.FormTitle>
|
|
||||||
<div className="colorwayCreator-colorPreviews">
|
<div className="colorwayCreator-colorPreviews">
|
||||||
{presetColorArray.map(presetColor => {
|
{colorProps.filter(color => presetColorArray.includes(color.id) || Object.keys(getPreset()[preset].calculated! || {}).includes(color.id)).map(presetColor => {
|
||||||
return <ColorPicker
|
return <ColorPicker
|
||||||
label={<Text className="colorwaysPicker-colorLabel">{colorProps[presetColor].name}</Text>}
|
label={<span className="colorwaysPicker-colorLabel">{Object.keys(getPreset()[preset].calculated! || {}).includes(presetColor.id) ? (presetColor.name + " (Calculated)") : presetColor.name}</span>}
|
||||||
color={parseInt(colorProps[presetColor].get, 16)}
|
color={!Object.keys(
|
||||||
|
getPreset()[preset].calculated! || {}
|
||||||
|
).includes(presetColor.id) ?
|
||||||
|
parseInt(colors[presetColor.id], 16) :
|
||||||
|
parseInt(
|
||||||
|
colorToHex(
|
||||||
|
getPreset(
|
||||||
|
colors.primary,
|
||||||
|
colors.secondary,
|
||||||
|
colors.tertiary,
|
||||||
|
colors.accent
|
||||||
|
)[preset].calculated![presetColor.id]
|
||||||
|
),
|
||||||
|
16
|
||||||
|
)
|
||||||
|
}
|
||||||
onChange={(color: number) => {
|
onChange={(color: number) => {
|
||||||
let hexColor = color.toString(16);
|
if (!Object.keys(getPreset()[preset].calculated! || {}).includes(presetColor.id)) {
|
||||||
while (hexColor.length < 6) {
|
let hexColor = color.toString(16);
|
||||||
hexColor = "0" + hexColor;
|
while (hexColor.length < 6) {
|
||||||
|
hexColor = "0" + hexColor;
|
||||||
|
}
|
||||||
|
updateColors({ task: presetColor.id as "accent" | "primary" | "secondary" | "tertiary", color: hexColor });
|
||||||
}
|
}
|
||||||
colorProps[presetColor].set(hexColor);
|
|
||||||
}}
|
}}
|
||||||
{...colorPickerProps}
|
{...colorPickerProps}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Forms.FormDivider style={{ margin: "10px 0" }} />
|
<div className="colorwaysSettingsDivider" style={{ margin: "10px 0" }} />
|
||||||
<Forms.FormTitle>Muted Text Brightness:</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">Muted Text Brightness:</span>
|
||||||
<Slider
|
<Slider
|
||||||
minValue={0}
|
minValue={0}
|
||||||
maxValue={100}
|
maxValue={100}
|
||||||
|
@ -172,41 +202,38 @@ export default function ({
|
||||||
setDiscordSaturation(discordSaturation);
|
setDiscordSaturation(discordSaturation);
|
||||||
setTintedText(tintedText);
|
setTintedText(tintedText);
|
||||||
}} />)}>
|
}} />)}>
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>Settings & Presets</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">Settings & Presets</span>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" role="img" style={{ rotate: "-90deg" }}>
|
<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" />
|
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 10L12 15 17 10" aria-hidden="true" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ThemePreviewCategory
|
<ThemePreviewCategory
|
||||||
accent={"#" + accentColor}
|
accent={"#" + colors.accent}
|
||||||
primary={"#" + primaryColor}
|
primary={"#" + colors.primary}
|
||||||
secondary={"#" + secondaryColor}
|
secondary={"#" + colors.secondary}
|
||||||
tertiary={"#" + tertiaryColor}
|
tertiary={"#" + colors.tertiary}
|
||||||
previewCSS={gradientPresetIds.includes(getPreset()[preset].id) ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${(getPreset(
|
previewCSS={gradientPresetIds.includes(getPreset()[preset].id) ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${(getPreset(
|
||||||
primaryColor,
|
colors.primary,
|
||||||
secondaryColor,
|
colors.secondary,
|
||||||
tertiaryColor,
|
colors.tertiary,
|
||||||
accentColor
|
colors.accent
|
||||||
)[preset].preset(discordSaturation) as { full: string, base: string; }).base})}` : (tintedText ? `.colorwaysPreview-modal,.colorwaysPreview-wrapper {
|
)[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-500: hsl(${HexToHSL("#" + colors.primary)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + colors.primary)[1] / 100) * (100 + PrimarySatDiffs[500])) * 10) / 10 : HexToHSL("#" + colors.primary)[1]}%) ${mutedTextBrightness || Math.min(HexToHSL("#" + colors.primary)[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%);
|
--primary-360: hsl(${HexToHSL("#" + colors.secondary)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + colors.primary)[1] / 100) * (100 + PrimarySatDiffs[360])) * 10) / 10 : HexToHSL("#" + colors.primary)[1]}%) 90%);
|
||||||
}` : "")}
|
}` : "")}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
var customColorwayCSS: string = "";
|
var customColorwayCSS: string = "";
|
||||||
if (preset === "default") {
|
if (preset === "default") {
|
||||||
customColorwayCSS = generateCss(
|
customColorwayCSS = generateCss(
|
||||||
primaryColor,
|
colors.primary,
|
||||||
secondaryColor,
|
colors.secondary,
|
||||||
tertiaryColor,
|
colors.tertiary,
|
||||||
accentColor,
|
colors.accent,
|
||||||
tintedText,
|
tintedText,
|
||||||
discordSaturation,
|
discordSaturation,
|
||||||
mutedTextBrightness,
|
mutedTextBrightness,
|
||||||
|
@ -216,61 +243,64 @@ export default function ({
|
||||||
gradientPresetIds.includes(getPreset()[preset].id) ?
|
gradientPresetIds.includes(getPreset()[preset].id) ?
|
||||||
customColorwayCSS = `/**
|
customColorwayCSS = `/**
|
||||||
* @name ${colorwayName || "Colorway"}
|
* @name ${colorwayName || "Colorway"}
|
||||||
* @version ${versionData.creatorVersion}
|
* @version ${PluginProps.creatorVersion}
|
||||||
* @description Automatically generated Colorway.
|
* @description Automatically generated Colorway.
|
||||||
* @author ${UserStore.getCurrentUser().username}
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
* @authorId ${UserStore.getCurrentUser().id}
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
* @preset Gradient
|
* @preset Gradient
|
||||||
*/
|
*/
|
||||||
${(getPreset(primaryColor, secondaryColor, tertiaryColor, accentColor)[preset].preset(discordSaturation) as { full: string; }).full}` : customColorwayCSS = `/**
|
${(getPreset(colors.primary, colors.secondary, colors.tertiary, colors.accent)[preset].preset(discordSaturation) as { full: string; }).full}` : customColorwayCSS = `/**
|
||||||
* @name ${colorwayName || "Colorway"}
|
* @name ${colorwayName || "Colorway"}
|
||||||
* @version ${versionData.creatorVersion}
|
* @version ${PluginProps.creatorVersion}
|
||||||
* @description Automatically generated Colorway.
|
* @description Automatically generated Colorway.
|
||||||
* @author ${UserStore.getCurrentUser().username}
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
* @authorId ${UserStore.getCurrentUser().id}
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
* @preset ${getPreset()[preset].name}
|
* @preset ${getPreset()[preset].name}
|
||||||
*/
|
*/
|
||||||
${(getPreset(primaryColor, secondaryColor, tertiaryColor, accentColor)[preset].preset(discordSaturation) as string)}`;
|
${(getPreset(colors.primary, colors.secondary, colors.tertiary, colors.accent)[preset].preset(discordSaturation) as string)}`;
|
||||||
}
|
}
|
||||||
const customColorway: Colorway = {
|
const customColorway: Colorway = {
|
||||||
name: (colorwayName || "Colorway"),
|
name: (colorwayName || "Colorway"),
|
||||||
"dc-import": customColorwayCSS,
|
"dc-import": customColorwayCSS,
|
||||||
accent: "#" + accentColor,
|
accent: "#" + colors.accent,
|
||||||
primary: "#" + primaryColor,
|
primary: "#" + colors.primary,
|
||||||
secondary: "#" + secondaryColor,
|
secondary: "#" + colors.secondary,
|
||||||
tertiary: "#" + tertiaryColor,
|
tertiary: "#" + colors.tertiary,
|
||||||
colors: presetColorArray,
|
colors: presetColorArray,
|
||||||
author: UserStore.getCurrentUser().username,
|
author: UserStore.getCurrentUser().username,
|
||||||
authorID: UserStore.getCurrentUser().id,
|
authorID: UserStore.getCurrentUser().id,
|
||||||
isGradient: gradientPresetIds.includes(getPreset()[preset].id),
|
isGradient: gradientPresetIds.includes(getPreset()[preset].id),
|
||||||
linearGradient: gradientPresetIds.includes(getPreset()[preset].id) ? (getPreset(
|
linearGradient: gradientPresetIds.includes(getPreset()[preset].id) ? (getPreset(
|
||||||
primaryColor,
|
colors.primary,
|
||||||
secondaryColor,
|
colors.secondary,
|
||||||
tertiaryColor,
|
colors.tertiary,
|
||||||
accentColor
|
colors.accent
|
||||||
)[preset].preset(discordSaturation) as { base: string; }).base : "",
|
)[preset].preset(discordSaturation) as { base: string; }).base : "",
|
||||||
preset: getPreset()[preset].id,
|
preset: getPreset()[preset].id,
|
||||||
creatorVersion: versionData.creatorVersion
|
creatorVersion: PluginProps.creatorVersion
|
||||||
};
|
};
|
||||||
openModal(props => <SaveColorwayModal modalProps={props} colorways={[customColorway]} onFinish={() => {
|
openModal(props => <SaveColorwayModal modalProps={props} colorways={[customColorway]} onFinish={() => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
loadUIProps!();
|
loadUIProps();
|
||||||
|
updateRemoteSources();
|
||||||
}} />);
|
}} />);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
function setAllColors({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) {
|
function setAllColors({ accent, primary, secondary, tertiary }: { accent: string, primary: string, secondary: string, tertiary: string; }) {
|
||||||
setAccentColor(accent.split("#")[1]);
|
updateColors({
|
||||||
setPrimaryColor(primary.split("#")[1]);
|
task: "all",
|
||||||
setSecondaryColor(secondary.split("#")[1]);
|
colorObj: {
|
||||||
setTertiaryColor(tertiary.split("#")[1]);
|
accent: accent.split("#")[1],
|
||||||
|
primary: primary.split("#")[1],
|
||||||
|
secondary: secondary.split("#")[1],
|
||||||
|
tertiary: tertiary.split("#")[1]
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
var copiedThemes = ["Discord"];
|
var copiedThemes = ["Discord"];
|
||||||
Object.values(knownThemeVars).map((theme: { variable: string; variableType?: string; }, i: number) => {
|
Object.values(knownThemeVars).map((theme: { variable: string; variableType?: string; }, i: number) => {
|
||||||
|
@ -281,69 +311,53 @@ export default function ({
|
||||||
if (copiedThemes.length > 1) {
|
if (copiedThemes.length > 1) {
|
||||||
openModal(props => <ConflictingColorsModal modalProps={props} onFinished={setAllColors} />);
|
openModal(props => <ConflictingColorsModal modalProps={props} onFinished={setAllColors} />);
|
||||||
} else {
|
} else {
|
||||||
setPrimaryColor(
|
updateColors({
|
||||||
getHex(
|
task: "all", colorObj: {
|
||||||
getComputedStyle(
|
primary: getHex(
|
||||||
document.body
|
getComputedStyle(
|
||||||
).getPropertyValue("--background-primary")
|
document.body
|
||||||
).split("#")[1]
|
).getPropertyValue("--primary-600")
|
||||||
);
|
).split("#")[1],
|
||||||
setSecondaryColor(
|
secondary: getHex(
|
||||||
getHex(
|
getComputedStyle(
|
||||||
getComputedStyle(
|
document.body
|
||||||
document.body
|
).getPropertyValue("--primary-630")
|
||||||
).getPropertyValue("--background-secondary")
|
).split("#")[1],
|
||||||
).split("#")[1]
|
tertiary: getHex(
|
||||||
);
|
getComputedStyle(
|
||||||
setTertiaryColor(
|
document.body
|
||||||
getHex(
|
).getPropertyValue("--primary-700")
|
||||||
getComputedStyle(
|
).split("#")[1],
|
||||||
document.body
|
accent: getHex(
|
||||||
).getPropertyValue("--background-tertiary")
|
getComputedStyle(
|
||||||
).split("#")[1]
|
document.body
|
||||||
);
|
).getPropertyValue("--brand-experiment")
|
||||||
setAccentColor(
|
).split("#")[1]
|
||||||
getHex(
|
}
|
||||||
getComputedStyle(
|
});
|
||||||
document.body
|
|
||||||
).getPropertyValue("--brand-experiment")
|
|
||||||
).split("#")[1]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy Current Colors
|
Copy Current Colors
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => openModal((props: any) => <InputColorwayIdModal modalProps={props} onColorwayId={colorwayID => {
|
onClick={() => openModal((props: any) => <InputColorwayIdModal modalProps={props} onColorwayId={colorwayID => {
|
||||||
const setColor = [
|
hexToString(colorwayID).split(/,#/).forEach((color: string, i: number) => updateColors({ task: setColor[i], color: colorToHex(color) }));
|
||||||
setAccentColor,
|
|
||||||
setPrimaryColor,
|
|
||||||
setSecondaryColor,
|
|
||||||
setTertiaryColor
|
|
||||||
];
|
|
||||||
hexToString(colorwayID).split(/,#/).forEach((color: string, i: number) => setColor[i](colorToHex(color)));
|
|
||||||
}} />)}
|
}} />)}
|
||||||
>
|
>
|
||||||
Enter Colorway ID
|
Enter Colorway ID
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useState } from "..";
|
||||||
|
import { SortOptions } from "../types";
|
||||||
|
import { SortIcon } from "./Icons";
|
||||||
|
|
||||||
|
export default function ({ sort, onSortChange }: { sort: SortOptions, onSortChange: (newSort: SortOptions) => void; }) {
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
function rightClickContextMenu(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
setShowMenu(!showMenu);
|
||||||
|
setPos({
|
||||||
|
x: e.currentTarget.getBoundingClientRect().x,
|
||||||
|
y: e.currentTarget.getBoundingClientRect().y + e.currentTarget.offsetHeight + 8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onPageClick(this: Window, e: globalThis.MouseEvent) {
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("click", onPageClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("click", onPageClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onSortChange_internal(newSort: SortOptions) {
|
||||||
|
onSortChange(newSort);
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{showMenu ? <nav className="colorwaysContextMenu" style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: `${pos.y}px`,
|
||||||
|
left: `${pos.x}px`
|
||||||
|
}}>
|
||||||
|
<button onClick={() => onSortChange_internal(1)} className="colorwaysContextMenuItm">
|
||||||
|
Name (A-Z)
|
||||||
|
<svg aria-hidden="true" role="img" width="18" height="18" viewBox="0 0 24 24" style={{
|
||||||
|
marginLeft: "8px"
|
||||||
|
}}>
|
||||||
|
<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" />
|
||||||
|
{sort === 1 ? <circle className="colorwaysRadioSelected" cx="12" cy="12" r="5" /> : null}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onSortChange_internal(2)} className="colorwaysContextMenuItm">
|
||||||
|
Name (Z-A)
|
||||||
|
<svg aria-hidden="true" role="img" width="18" height="18" viewBox="0 0 24 24" style={{
|
||||||
|
marginLeft: "8px"
|
||||||
|
}}>
|
||||||
|
<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" />
|
||||||
|
{sort === 2 ? <circle className="colorwaysRadioSelected" cx="12" cy="12" r="5" /> : null}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onSortChange_internal(3)} className="colorwaysContextMenuItm">
|
||||||
|
Source (A-Z)
|
||||||
|
<svg aria-hidden="true" role="img" width="18" height="18" viewBox="0 0 24 24" style={{
|
||||||
|
marginLeft: "8px"
|
||||||
|
}}>
|
||||||
|
<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" />
|
||||||
|
{sort === 3 ? <circle className="colorwaysRadioSelected" cx="12" cy="12" r="5" /> : null}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => onSortChange_internal(4)} className="colorwaysContextMenuItm">
|
||||||
|
Source (Z-A)
|
||||||
|
<svg aria-hidden="true" role="img" width="18" height="18" viewBox="0 0 24 24" style={{
|
||||||
|
marginLeft: "8px"
|
||||||
|
}}>
|
||||||
|
<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" />
|
||||||
|
{sort === 4 ? <circle className="colorwaysRadioSelected" cx="12" cy="12" r="5" /> : null}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</nav> : null}
|
||||||
|
<button className="colorwaysPillButton" onClick={rightClickContextMenu}><SortIcon width={14} height={14} /> Sort By...</button>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -1,26 +1,35 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* Vencord, a modification for Discord's desktop app
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
*
|
||||||
*/
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { classes } from "@utils/misc";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { PropsWithChildren, SVGProps } from "react";
|
|
||||||
|
import { classes } from "../utils";
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IconProps extends SVGProps<SVGSVGElement> {
|
type IconProps = JSX.IntrinsicElements["svg"];
|
||||||
className?: string;
|
|
||||||
height?: string | number;
|
|
||||||
width?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={classes(className, "vc-icon")}
|
className={classes(className, "dc-icon")}
|
||||||
role="img"
|
role="img"
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
@ -32,16 +41,221 @@ function Icon({ height = 24, width = 24, className, children, viewBox, ...svgPro
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PalleteIcon(props: IconProps) {
|
/**
|
||||||
|
* Discord's link icon, as seen in the Message context menu "Copy Message Link" option
|
||||||
|
*/
|
||||||
|
export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
className={classes(className, "dc-link-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
||||||
|
<rect width={width} height={height} />
|
||||||
|
</g>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discord's copy icon, as seen in the user popout right of the username when clicking
|
||||||
|
* your own username in the bottom left user panel
|
||||||
|
*/
|
||||||
|
export function CopyIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-pallete-icon")}
|
className={classes(props.className, "dc-copy-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
|
||||||
|
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" />
|
||||||
|
</g>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discord's open external icon, as seen in the user profile connections
|
||||||
|
*/
|
||||||
|
export function OpenExternalIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-open-external-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="nonzero"
|
||||||
|
points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImageIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-image-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InfoIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-info-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
transform="translate(2 2)"
|
||||||
|
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discord's screenshare icon, as seen in the connection panel
|
||||||
|
*/
|
||||||
|
export function ScreenshareIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-screenshare-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImageVisible(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-image-visible")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ImageInvisible(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-image-invisible")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Microphone(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-microphone")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" />
|
||||||
|
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" />
|
||||||
|
</Icon >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CogWheel(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-cog-wheel")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clipRule="evenodd"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ReplyIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-reply-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10 8.26667V4L3 11.4667L10 18.9333V14.56C15 14.56 18.5 16.2667 21 20C20 14.6667 17 9.33333 10 8.26667Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-delete-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SearchIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-search-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M21.707 20.293L16.314 14.9C17.403 13.504 18 11.799 18 10C18 7.863 17.167 5.854 15.656 4.344C14.146 2.832 12.137 2 10 2C7.863 2 5.854 2.832 4.344 4.344C2.833 5.854 2 7.863 2 10C2 12.137 2.833 14.146 4.344 15.656C5.854 17.168 7.863 18 10 18C11.799 18 13.504 17.404 14.9 16.314L20.293 21.706L21.707 20.293ZM10 16C8.397 16 6.891 15.376 5.758 14.243C4.624 13.11 4 11.603 4 10C4 8.398 4.624 6.891 5.758 5.758C6.891 4.624 8.397 4 10 4C11.603 4 13.109 4.624 14.242 5.758C15.376 6.891 16 8.398 16 10C16 11.603 15.376 13.11 14.242 14.243C13.109 15.376 11.603 16 10 16Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function PlusIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-plus-icon")}
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
fill-rule="nonzero"
|
||||||
|
fill="currentColor"
|
||||||
|
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -51,7 +265,7 @@ export function CloseIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-close-icon")}
|
className={classes(props.className, "dc-close-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -62,11 +276,76 @@ export function CloseIcon(props: IconProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SwatchIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-swatch-icon")}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
style={{ padding: "4px" }}
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="M0 .5A.5.5 0 0 1 .5 0h5a.5.5 0 0 1 .5.5v5.277l4.147-4.131a.5.5 0 0 1 .707 0l3.535 3.536a.5.5 0 0 1 0 .708L10.261 10H15.5a.5.5 0 0 1 .5.5v5a.5.5 0 0 1-.5.5H3a2.99 2.99 0 0 1-2.121-.879A2.99 2.99 0 0 1 0 13.044m6-.21 7.328-7.3-2.829-2.828L6 7.188v5.647zM4.5 13a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0zM15 15v-4H9.258l-4.015 4H15zM0 .5v12.495V.5z" />
|
||||||
|
<path fill="currentColor" d="M0 12.995V13a3.07 3.07 0 0 0 0-.005z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PalleteIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-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 NoEntrySignIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-no-entry-sign-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8 0-1.85.63-3.55 1.69-4.9L16.9 18.31C15.55 19.37 13.85 20 12 20zm6.31-3.1L7.1 5.69C8.45 4.63 10.15 4 12 4c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function DownloadIcon(props: IconProps) {
|
export function DownloadIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-download-icon")}
|
className={classes(props.className, "dc-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 SafetyIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-safety-icon")}
|
||||||
|
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -81,7 +360,7 @@ export function ImportIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-import-icon")}
|
className={classes(props.className, "dc-import-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -96,7 +375,22 @@ export function IDIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-id-icon")}
|
viewBox="0 0 24 24"
|
||||||
|
className={classes(props.className, "dc-id-icon")}
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
>
|
||||||
|
<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 NotesIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-notes-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -117,7 +411,7 @@ export function CodeIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-code-icon")}
|
className={classes(props.className, "dc-code-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -132,7 +426,7 @@ export function MoreIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-more-icon")}
|
className={classes(props.className, "dc-more-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
@ -144,3 +438,112 @@ export function MoreIcon(props: IconProps) {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SortIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-sort-icon")}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M3.5 3.5a.5.5 0 0 0-1 0v8.793l-1.146-1.147a.5.5 0 0 0-.708.708l2 1.999.007.007a.497.497 0 0 0 .7-.006l2-2a.5.5 0 0 0-.707-.708L3.5 12.293zm4 .5a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1zm0 3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm0 3a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1zM7 12.5a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 0-1h-7a.5.5 0 0 0-.5.5"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FolderIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-folder-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 5a3 3 0 0 1 3-3h3.93a2 2 0 0 1 1.66.9L12 5h7a3 3 0 0 1 3 3v11a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-log-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M3.11 8H6v10.82c0 .86.37 1.68 1 2.27.46.43 1.02.71 1.63.84A1 1 0 0 0 9 22h10a4 4 0 0 0 4-4v-1a2 2 0 0 0-2-2h-1V5a3 3 0 0 0-3-3H4.67c-.87 0-1.7.32-2.34.9-.63.6-1 1.42-1 2.28 0 .71.3 1.35.52 1.75a5.35 5.35 0 0 0 .48.7l.01.01h.01L3.11 7l-.76.65a1 1 0 0 0 .76.35Zm1.56-4c-.38 0-.72.14-.97.37-.24.23-.37.52-.37.81a1.69 1.69 0 0 0 .3.82H6v-.83c0-.29-.13-.58-.37-.8C5.4 4.14 5.04 4 4.67 4Zm5 13a3.58 3.58 0 0 1 0 3H19a2 2 0 0 0 2-2v-1H9.66ZM3.86 6.35ZM11 8a1 1 0 1 0 0 2h5a1 1 0 1 0 0-2h-5Zm-1 5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RestartIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-restart-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 0 1 14.93-4H15a1 1 0 1 0 0 2h6a1 1 0 0 0 1-1V3a1 1 0 1 0-2 0v3a9.98 9.98 0 0 0-18 6 10 10 0 0 0 16.29 7.78 1 1 0 0 0-1.26-1.56A8 8 0 0 1 4 12Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PaintbrushIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-paintbrush-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.35 7.24C15.9 6.67 16 5.8 16 5a3 3 0 1 1 3 3c-.8 0-1.67.09-2.24.65a1.5 1.5 0 0 0 0 2.11l1.12 1.12a3 3 0 0 1 0 4.24l-5 5a3 3 0 0 1-4.25 0l-5.76-5.75a3 3 0 0 1 0-4.24l4.04-4.04.97-.97a3 3 0 0 1 4.24 0l1.12 1.12c.58.58 1.52.58 2.1 0ZM6.9 9.9 4.3 12.54a1 1 0 0 0 0 1.42l2.17 2.17.83-.84a1 1 0 0 1 1.42 1.42l-.84.83.59.59 1.83-1.84a1 1 0 0 1 1.42 1.42l-1.84 1.83.17.17a1 1 0 0 0 1.42 0l2.63-2.62L6.9 9.9Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PencilIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-pencil-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InternetIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "dc-internet-icon")}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m7.5-6.923c-.67.204-1.335.82-1.887 1.855q-.215.403-.395.872c.705.157 1.472.257 2.282.287zM4.249 3.539q.214-.577.481-1.078a7 7 0 0 1 .597-.933A7 7 0 0 0 3.051 3.05q.544.277 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9 9 0 0 1-1.565-.667A6.96 6.96 0 0 0 1.018 7.5zm1.4-2.741a12.3 12.3 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332M8.5 5.09V7.5h2.99a12.3 12.3 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.6 13.6 0 0 1 7.5 10.91V8.5zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741zm-3.282 3.696q.18.469.395.872c.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a7 7 0 0 1-.598-.933 9 9 0 0 1-.481-1.079 8.4 8.4 0 0 0-1.198.49 7 7 0 0 0 2.276 1.522zm-1.383-2.964A13.4 13.4 0 0 1 3.508 8.5h-2.49a6.96 6.96 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667m6.728 2.964a7 7 0 0 0 2.275-1.521 8.4 8.4 0 0 0-1.197-.49 9 9 0 0 1-.481 1.078 7 7 0 0 1-.597.933M8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855q.216-.403.395-.872A12.6 12.6 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.96 6.96 0 0 0 14.982 8.5h-2.49a13.4 13.4 0 0 1-.437 3.008M14.982 7.5a6.96 6.96 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008zM11.27 2.461q.266.502.482 1.078a8.4 8.4 0 0 0 1.196-.49 7 7 0 0 0-2.275-1.52c.218.283.418.597.597.932m-.488 1.343a8 8 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -4,54 +4,43 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
import { DataStore, openModal, PluginProps, Toasts, useEffect, UserStore, useState, useStateFromStores } from "..";
|
||||||
import { CodeBlock } from "@components/CodeBlock";
|
import { ColorwayCSS } from "../colorwaysAPI";
|
||||||
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 { generateCss, pureGradientBase } from "../css";
|
||||||
import { Colorway } from "../types";
|
import { Colorway, ModalProps } from "../types";
|
||||||
import { colorToHex, stringToHex } from "../utils";
|
import { colorToHex, saveFile, stringToHex } from "../utils";
|
||||||
import SaveColorwayModal from "./SaveColorwayModal";
|
import SaveColorwayModal from "./SaveColorwayModal";
|
||||||
import ThemePreview from "./ThemePreview";
|
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[]; }) {
|
function RenameColorwayModal({ modalProps, ogName, onFinish, colorwayList }: { modalProps: ModalProps, ogName: string, onFinish: (name: string) => void, colorwayList: Colorway[]; }) {
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [newName, setNewName] = useState<string>(ogName);
|
const [newName, setNewName] = useState<string>(ogName);
|
||||||
return <ModalRoot {...modalProps}>
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader separator={false}>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1" style={{ marginRight: "auto" }}>
|
useEffect(() => {
|
||||||
Rename Colorway...
|
async function load() {
|
||||||
</Text>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
<ModalCloseButton onClick={() => modalProps.onClose()} />
|
}
|
||||||
</ModalHeader>
|
load();
|
||||||
<ModalContent>
|
}, []);
|
||||||
<TextInput
|
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Rename Colorway...
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="colorwaySelector-search"
|
||||||
value={newName}
|
value={newName}
|
||||||
error={error}
|
onInput={({ currentTarget: { value } }) => {
|
||||||
onChange={setNewName}
|
setNewName(value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!newName) {
|
if (!newName) {
|
||||||
return setError("Error: Please enter a valid name");
|
return setError("Error: Please enter a valid name");
|
||||||
|
@ -64,18 +53,15 @@ function RenameColorwayModal({ modalProps, ogName, onFinish, colorwayList }: { m
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={() => modalProps.onClose()}
|
onClick={() => modalProps.onClose()}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({
|
export default function ({
|
||||||
|
@ -94,42 +80,48 @@ export default function ({
|
||||||
"tertiary",
|
"tertiary",
|
||||||
];
|
];
|
||||||
const profile = useStateFromStores([UserStore], () => UserStore.getUser(colorway.authorID));
|
const profile = useStateFromStores([UserStore], () => UserStore.getUser(colorway.authorID));
|
||||||
return <ModalRoot {...modalProps}>
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader separator={false}>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1" style={{ marginRight: "auto" }}>
|
useEffect(() => {
|
||||||
Colorway: {colorway.name}
|
async function load() {
|
||||||
</Text>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
<ModalCloseButton onClick={() => modalProps.onClose()} />
|
}
|
||||||
</ModalHeader>
|
load();
|
||||||
<ModalContent>
|
}, []);
|
||||||
<Flex style={{ gap: "8px", width: "100%" }} flexDirection="column">
|
|
||||||
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Creator:</Forms.FormTitle>
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<Flex style={{ gap: ".5rem" }}>
|
<h2 className="colorwaysModalHeader">
|
||||||
<UserSummaryItem
|
Colorway: {colorway.name}
|
||||||
users={[profile]}
|
</h2>
|
||||||
guildId={undefined}
|
<div className="colorwaysModalContent">
|
||||||
renderIcon={false}
|
<div style={{ gap: "8px", width: "100%", display: "flex", flexDirection: "column" }}>
|
||||||
showDefaultAvatarsForNullUsers
|
<span className="colorwaysModalSectionHeader">Creator:</span>
|
||||||
size={32}
|
<div style={{ gap: ".5rem", display: "flex" }}>
|
||||||
showUserPopout
|
{<img src={`https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.webp?size=32`} width={32} height={32} style={{
|
||||||
/>
|
borderRadius: "32px"
|
||||||
<Text style={{ lineHeight: "32px" }}>{colorway.author}</Text>
|
}} />}
|
||||||
</Flex>
|
<span className="colorwaysModalSectionHeader" style={{ lineHeight: "32px" }} onClick={() => {
|
||||||
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Colors:</Forms.FormTitle>
|
navigator.clipboard.writeText(profile.username);
|
||||||
<Flex style={{ gap: "8px" }}>
|
Toasts.show({
|
||||||
|
message: "Copied Colorway Author Username Successfully",
|
||||||
|
type: 1,
|
||||||
|
id: "copy-colorway-author-username-notify",
|
||||||
|
});
|
||||||
|
}}>{colorway.author}</span>
|
||||||
|
</div>
|
||||||
|
<span className="colorwaysModalSectionHeader">Colors:</span>
|
||||||
|
<div style={{ gap: "8px", display: "flex" }}>
|
||||||
{colors.map(color => <div className="colorwayInfo-colorSwatch" style={{ backgroundColor: colorway[color] }} />)}
|
{colors.map(color => <div className="colorwayInfo-colorSwatch" style={{ backgroundColor: colorway[color] }} />)}
|
||||||
</Flex>
|
</div>
|
||||||
<Forms.FormTitle style={{ marginBottom: 0, width: "100%" }}>Actions:</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">Actions:</span>
|
||||||
<Flex style={{ gap: "8px" }} flexDirection="column">
|
<div style={{ gap: "8px", flexDirection: "column", display: "flex" }}>
|
||||||
<Button
|
<button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const colorwayIDArray = `${colorway.accent},${colorway.primary},${colorway.secondary},${colorway.tertiary}|n:${colorway.name}${colorway.preset ? `|p:${colorway.preset}` : ""}`;
|
const colorwayIDArray = `${colorway.accent},${colorway.primary},${colorway.secondary},${colorway.tertiary}|n:${colorway.name}${colorway.preset ? `|p:${colorway.preset}` : ""}`;
|
||||||
const colorwayID = stringToHex(colorwayIDArray);
|
const colorwayID = stringToHex(colorwayIDArray);
|
||||||
Clipboard.copy(colorwayID);
|
navigator.clipboard.writeText(colorwayID);
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message: "Copied Colorway ID Successfully",
|
message: "Copied Colorway ID Successfully",
|
||||||
type: 1,
|
type: 1,
|
||||||
|
@ -138,14 +130,12 @@ export default function ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy Colorway ID
|
Copy Colorway ID
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Clipboard.copy(colorway["dc-import"]);
|
navigator.clipboard.writeText(colorway["dc-import"]);
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message: "Copied CSS to Clipboard",
|
message: "Copied CSS to Clipboard",
|
||||||
type: 1,
|
type: 1,
|
||||||
|
@ -154,11 +144,9 @@ export default function ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy CSS
|
Copy CSS
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const newColorway = {
|
const newColorway = {
|
||||||
|
@ -169,11 +157,9 @@ export default function ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Update CSS
|
Update CSS
|
||||||
</Button>
|
</button>
|
||||||
{colorway.sourceType === "offline" && <Button
|
{colorway.sourceType === "offline" && <button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={async () => {
|
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];
|
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];
|
||||||
|
@ -199,64 +185,32 @@ export default function ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Rename
|
Rename
|
||||||
</Button>}
|
</button>}
|
||||||
<Button
|
<button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
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%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!colorway["dc-import"].includes("@name")) {
|
if (!colorway["dc-import"].includes("@name")) {
|
||||||
if (IS_DISCORD_DESKTOP) {
|
saveFile(new File([`/**
|
||||||
DiscordNative.fileManager.saveWithDialog(`/**
|
|
||||||
* @name ${colorway.name || "Colorway"}
|
* @name ${colorway.name || "Colorway"}
|
||||||
* @version ${versionData.creatorVersion}
|
* @version ${PluginProps.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.
|
* @description Automatically generated Colorway.
|
||||||
* @author ${UserStore.getCurrentUser().username}
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
* @authorId ${UserStore.getCurrentUser().id}
|
* @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" }));
|
${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 {
|
} else {
|
||||||
if (IS_DISCORD_DESKTOP) {
|
saveFile(new File([colorway["dc-import"]], `${colorway.name.replaceAll(" ", "-").toLowerCase()}.theme.css`, { type: "text/plain" }));
|
||||||
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
|
Download CSS
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
color={Button.Colors.PRIMARY}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
|
openModal((props: ModalProps) => <div className={`colorwaysPreview-modal ${props.transitionState === 2 ? "closing" : ""} ${props.transitionState === 4 ? "hidden" : ""}`}>
|
||||||
<style>
|
<style>
|
||||||
{colorway.isGradient ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${colorway.linearGradient})}` : ""}
|
{colorway.isGradient ? pureGradientBase + `.colorwaysPreview-modal,.colorwaysPreview-wrapper {--gradient-theme-bg: linear-gradient(${colorway.linearGradient})}` : ""}
|
||||||
</style>
|
</style>
|
||||||
|
@ -268,15 +222,13 @@ export default function ({
|
||||||
isModal
|
isModal
|
||||||
modalProps={props}
|
modalProps={props}
|
||||||
/>
|
/>
|
||||||
</ModalRoot>);
|
</div>);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Show preview
|
Show preview
|
||||||
</Button>
|
</button>
|
||||||
{colorway.sourceType === "offline" && <Button
|
{colorway.sourceType === "offline" && <button
|
||||||
color={Button.Colors.RED}
|
className="colorwaysPillButton"
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const oldStores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name !== colorway.source);
|
const oldStores = (await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]).filter(source => source.name !== colorway.source);
|
||||||
|
@ -292,10 +244,9 @@ export default function ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>}
|
</button>}
|
||||||
</Flex>
|
</div>
|
||||||
</Flex>
|
</div>
|
||||||
<div style={{ width: "100%", height: "20px" }} />
|
</div>
|
||||||
</ModalContent>
|
</div>;
|
||||||
</ModalRoot>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,33 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModalContent, ModalFooter, ModalProps, ModalRoot } from "@utils/modal";
|
import { DataStore, useEffect, useState } from "..";
|
||||||
import { Button, Forms, TextInput, useState } from "@webpack/common";
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
import { hexToString } from "../utils";
|
import { hexToString } from "../utils";
|
||||||
|
|
||||||
export default function ({ modalProps, onColorwayId }: { modalProps: ModalProps, onColorwayId: (colorwayID: string) => void; }) {
|
export default function ({ modalProps, onColorwayId }: { modalProps: ModalProps, onColorwayId: (colorwayID: string) => void; }) {
|
||||||
const [colorwayID, setColorwayID] = useState<string>("");
|
const [colorwayID, setColorwayID] = useState<string>("");
|
||||||
return <ModalRoot {...modalProps} className="colorwaysCreator-noMinHeight">
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalContent className="colorwaysCreator-noHeader colorwaysCreator-noMinHeight">
|
|
||||||
<Forms.FormTitle>Colorway ID:</Forms.FormTitle>
|
useEffect(() => {
|
||||||
<TextInput placeholder="Enter Colorway ID" onInput={e => setColorwayID(e.currentTarget.value)} />
|
async function load() {
|
||||||
</ModalContent>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
<ModalFooter>
|
}
|
||||||
<Button
|
load();
|
||||||
style={{ marginLeft: 8 }}
|
}, []);
|
||||||
color={Button.Colors.BRAND}
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
size={Button.Sizes.MEDIUM}
|
<div className="colorwaysModalContent">
|
||||||
look={Button.Looks.FILLED}
|
<span className="colorwaysModalSectionHeader">Colorway ID:</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="colorwaySelector-search"
|
||||||
|
placeholder="Enter Colorway ID"
|
||||||
|
onInput={({ currentTarget: { value } }) => setColorwayID(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="colorwaysModalFooter">
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!colorwayID) {
|
if (!colorwayID) {
|
||||||
throw new Error("Please enter a Colorway ID");
|
throw new Error("Please enter a Colorway ID");
|
||||||
|
@ -34,16 +43,13 @@ export default function ({ modalProps, onColorwayId }: { modalProps: ModalProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => modalProps.onClose()}
|
onClick={() => modalProps.onClose()}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
114
src/equicordplugins/discordColorways/components/MainModal.tsx
Normal file
114
src/equicordplugins/discordColorways/components/MainModal.tsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MouseEvent, MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
import { DataStore, useEffect, useRef, useState } from "../";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
import { restartWS, updateRemoteSources, wsOpen } from "../wsClient";
|
||||||
|
// eslint-disable-next-line no-duplicate-imports
|
||||||
|
import { boundKey as bk } from "../wsClient";
|
||||||
|
import Selector from "./Selector";
|
||||||
|
import SettingsPage from "./SettingsTabs/SettingsPage";
|
||||||
|
import SourceManager from "./SettingsTabs/SourceManager";
|
||||||
|
import Store from "./SettingsTabs/Store";
|
||||||
|
|
||||||
|
export let changeTheme = (theme: string) => { };
|
||||||
|
export let updateWSMain: (status: boolean) => void = () => { };
|
||||||
|
export let updateBoundKeyMain: (boundKey: { [managerKey: string]: string; }) => void = () => { };
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
modalProps
|
||||||
|
}: {
|
||||||
|
modalProps: ModalProps;
|
||||||
|
}): JSX.Element | any {
|
||||||
|
const [activeTab, setActiveTab] = useState<"selector" | "settings" | "sources" | "store" | "ws_connection">("selector");
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
const [wsConnected, setWsConnected] = useState(wsOpen);
|
||||||
|
const [boundKey, setBoundKey] = useState<{ [managerKey: string]: string; }>(bk as { [managerKey: string]: string; });
|
||||||
|
const menuProps = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
updateWSMain = status => setWsConnected(status);
|
||||||
|
changeTheme = (theme: string) => setTheme(theme);
|
||||||
|
updateBoundKeyMain = bound => setBoundKey(bound);
|
||||||
|
load();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
updateWSMain = () => { };
|
||||||
|
changeTheme = () => { };
|
||||||
|
updateBoundKeyMain = () => { };
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function SidebarTab({ id, title, icon, bottom }: { id: "selector" | "settings" | "sources" | "store" | "ws_connection", title: string, icon: JSX.Element, bottom?: boolean; }) {
|
||||||
|
return <div className={"colorwaySelectorSidebar-tab" + (id === activeTab ? " active" : "")} style={bottom ? { marginTop: "auto" } : {}} onClick={!bottom ? ((() => setActiveTab(id)) as unknown as MouseEventHandler<HTMLDivElement>) : rightClickContextMenu}>{icon}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rightClickContextMenu: MouseEventHandler<HTMLDivElement> = (e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
setShowMenu(!showMenu);
|
||||||
|
setPos({
|
||||||
|
x: e.currentTarget.getBoundingClientRect().x + e.currentTarget.offsetWidth + 8,
|
||||||
|
y: e.currentTarget.getBoundingClientRect().y + e.currentTarget.offsetHeight - (menuProps.current as unknown as HTMLElement).offsetHeight
|
||||||
|
});
|
||||||
|
};
|
||||||
|
function onPageClick(this: Window, e: globalThis.MouseEvent) {
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("click", onPageClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("click", onPageClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={`colorwaySelectorModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme} {...modalProps}>
|
||||||
|
<div className="colorwaySelectorSidebar">
|
||||||
|
<SidebarTab icon={<></>} id="selector" title="Change Colorway" />
|
||||||
|
<SidebarTab icon={<></>} id="settings" title="Settings" />
|
||||||
|
<SidebarTab icon={<></>} id="sources" title="Sources" />
|
||||||
|
<SidebarTab icon={<></>} id="store" title="Store" />
|
||||||
|
<SidebarTab bottom icon={<></>} id="ws_connection" title="Manager Connection" />
|
||||||
|
</div>
|
||||||
|
<div className="colorwayModalContent">
|
||||||
|
{activeTab === "selector" && <Selector />}
|
||||||
|
{activeTab === "sources" && <SourceManager />}
|
||||||
|
{activeTab === "store" && <Store />}
|
||||||
|
{activeTab === "settings" && <div style={{ padding: "16px" }}><SettingsPage /></div>}
|
||||||
|
</div>
|
||||||
|
<div ref={menuProps} className={`colorwaysManagerConnectionMenu ${showMenu ? "visible" : ""}`} style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: `${pos.y}px`,
|
||||||
|
left: `${pos.x}px`
|
||||||
|
}}>
|
||||||
|
<span>Manager Connection Status: {wsConnected ? "Connected" : "Disconnected"}</span>
|
||||||
|
{wsConnected ? <>
|
||||||
|
<span className="colorwaysManagerConnectionValue">Bound Key: <b>{JSON.stringify(boundKey)}</b></span>
|
||||||
|
<button className="colorwaysPillButton" style={{
|
||||||
|
marginTop: "4px"
|
||||||
|
}} onClick={() => navigator.clipboard.writeText(JSON.stringify(boundKey))}>Copy Bound Key</button>
|
||||||
|
<button className="colorwaysPillButton" style={{
|
||||||
|
marginTop: "4px"
|
||||||
|
}} onClick={restartWS}>Reset Connection</button>
|
||||||
|
<button className="colorwaysPillButton" style={{
|
||||||
|
marginTop: "4px"
|
||||||
|
}} onClick={updateRemoteSources}>Update Remote Sources</button>
|
||||||
|
</> : <></>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore, useEffect, useState } from "..";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
|
export default function ({ modalProps }: { modalProps: ModalProps; }) {
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Project Colorway has moved
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
<span style={{ maxWidth: "600px", color: "var(--text-normal)" }}>
|
||||||
|
In the process of creating a more solid foundation
|
||||||
|
for Project Colorway, the main Project Colorway repository has been
|
||||||
|
moved from <a role="link" target="_blank" href="https://github.com/DaBluLite/ProjectColorway">https://github.com/DaBluLite/ProjectColorway</a> to{" "}
|
||||||
|
<a role="link" target="_blank" href="https://github.com/ProjectColorway/ProjectColorway">https://github.com/ProjectColorway/ProjectColorway</a>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span style={{ textAlign: "center", color: "var(--text-normal)" }}>The default Project Colorway source has been automatically updated/re-added.</span>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "..";
|
||||||
|
|
||||||
|
export default function ({ onClick, onForceReload }: { onClick: () => void, onForceReload: () => void; }) {
|
||||||
|
const menuProps = useRef(null);
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
function rightClickContextMenu(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
setShowMenu(!showMenu);
|
||||||
|
setPos({
|
||||||
|
x: e.currentTarget.getBoundingClientRect().x,
|
||||||
|
y: e.currentTarget.getBoundingClientRect().y + e.currentTarget.offsetHeight + 8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onPageClick(this: Window, e: globalThis.MouseEvent) {
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("click", onPageClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("click", onPageClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onForceReload_internal() {
|
||||||
|
onForceReload();
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{showMenu ? <nav className="colorwaysContextMenu" ref={menuProps} style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: `${pos.y}px`,
|
||||||
|
left: `${pos.x}px`
|
||||||
|
}}>
|
||||||
|
<button onClick={onForceReload_internal} className="colorwaysContextMenuItm">
|
||||||
|
Force Refresh
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
style={{ boxSizing: "content-box", marginLeft: "8px" }}
|
||||||
|
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>
|
||||||
|
</nav> : null}
|
||||||
|
<button className="colorwaysPillButton" onContextMenu={rightClickContextMenu} onClick={onClick}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
style={{ 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>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -4,67 +4,63 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, openModal, useEffect, useState } from "..";
|
||||||
import { PlusIcon } from "@components/Icons";
|
import { Colorway, ModalProps } from "../types";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
import { PlusIcon } from "./Icons";
|
||||||
import { findByProps } from "@webpack";
|
|
||||||
import { Button, Text, TextInput, useEffect, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import { Colorway } from "../types";
|
|
||||||
import { StoreNameModal } from "./SettingsTabs/SourceManager";
|
import { StoreNameModal } from "./SettingsTabs/SourceManager";
|
||||||
|
|
||||||
export default function ({ modalProps, colorways, onFinish }: { modalProps: ModalProps, colorways: Colorway[], onFinish: () => void; }) {
|
export default function ({ modalProps, colorways, onFinish }: { modalProps: ModalProps, colorways: Colorway[], onFinish: () => void; }) {
|
||||||
const [offlineColorwayStores, setOfflineColorwayStores] = useState<{ name: string, colorways: Colorway[], id?: string; }[]>([]);
|
const [offlineColorwayStores, setOfflineColorwayStores] = useState<{ name: string, colorways: Colorway[], id?: string; }[]>([]);
|
||||||
const [storename, setStorename] = useState<string>();
|
const [storename, setStorename] = useState<string>();
|
||||||
const [noStoreError, setNoStoreError] = useState<boolean>(false);
|
const [noStoreError, setNoStoreError] = useState<boolean>(false);
|
||||||
const { radioBar, item: radioBarItem, itemFilled: radioBarItemFilled, radioPositionLeft } = findByProps("radioBar");
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]);
|
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[], id?: string; }[]);
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
return <ModalRoot {...modalProps}>
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader separator={false}>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1">Select Offline Colorway Source</Text>
|
useEffect(() => {
|
||||||
</ModalHeader>
|
async function load() {
|
||||||
<ModalContent>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
{noStoreError ? <Text variant="text-xs/normal" style={{ color: "var(--text-danger)" }}>Error: No store selected</Text> : <></>}
|
}
|
||||||
{offlineColorwayStores.map(store => {
|
load();
|
||||||
return <div className={`${radioBarItem} ${radioBarItemFilled}`} aria-checked={storename === store.name}>
|
}, []);
|
||||||
<div
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
className={`${radioBar} ${radioPositionLeft}`}
|
<h2 className="colorwaysModalHeader">
|
||||||
style={{ padding: "10px" }}
|
Save to source:
|
||||||
onClick={() => {
|
</h2>
|
||||||
setStorename(store.name);
|
<div className="colorwaysModalContent">
|
||||||
}}>
|
{noStoreError ? <span style={{ color: "var(--text-danger)" }}>Error: No store selected</span> : <></>}
|
||||||
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
|
{offlineColorwayStores.map(store => <div
|
||||||
<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" />
|
className="discordColorway"
|
||||||
{storename === store.name && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
|
style={{ padding: "10px" }}
|
||||||
</svg>
|
aria-checked={storename === store.name}
|
||||||
<Text variant="eyebrow" tag="h5">{store.name}</Text>
|
onClick={() => {
|
||||||
</div>
|
setStorename(store.name);
|
||||||
</div>;
|
}}>
|
||||||
})}
|
<svg aria-hidden="true" role="img" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<div className={`${radioBarItem} ${radioBarItemFilled}`}>
|
<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" />
|
||||||
<div
|
{storename === store.name && <circle cx="12" cy="12" r="5" className="radioIconForeground-3wH3aU" fill="currentColor" />}
|
||||||
className={`${radioBar} ${radioPositionLeft}`}
|
</svg>
|
||||||
style={{ padding: "10px" }}
|
<span className="colorwayLabel">{store.name}</span>
|
||||||
onClick={() => {
|
</div>)}
|
||||||
openModal(props => <StoreNameModal modalProps={props} conflicting={false} originalName="" onFinish={async e => {
|
<div
|
||||||
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
|
className="discordColorway"
|
||||||
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
style={{ padding: "10px" }}
|
||||||
}} />);
|
onClick={() => {
|
||||||
}}>
|
openModal(props => <StoreNameModal modalProps={props} conflicting={false} originalName="" onFinish={async e => {
|
||||||
<PlusIcon width={24} height={24} />
|
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
|
||||||
<Text variant="eyebrow" tag="h5">Create new store...</Text>
|
setOfflineColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
||||||
</div>
|
}} />);
|
||||||
|
}}>
|
||||||
|
<PlusIcon width={24} height={24} />
|
||||||
|
<span className="colorwayLabel">Create new store...</span>
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND_NEW}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setNoStoreError(false);
|
setNoStoreError(false);
|
||||||
if (!storename) {
|
if (!storename) {
|
||||||
|
@ -74,19 +70,16 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
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];
|
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) => {
|
colorways.map((colorway, i) => {
|
||||||
if (storeToModify.colorways.map(colorway => colorway.name).includes(colorway.name)) {
|
if (storeToModify.colorways.map(colorway => colorway.name).includes(colorway.name)) {
|
||||||
openModal(props => <ModalRoot {...props}>
|
openModal(props => <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<ModalHeader separator={false}>
|
<h2 className="colorwaysModalHeader">
|
||||||
<Text variant="heading-lg/semibold" tag="h1">Duplicate Colorway</Text>
|
Duplicate Colorway
|
||||||
</ModalHeader>
|
</h2>
|
||||||
<ModalContent>
|
<div className="colorwaysModalContent">
|
||||||
<Text>A colorway with the same name was found in this store, what do you want to do?</Text>
|
<span className="colorwaysModalSectionHeader">A colorway with the same name was found in this store, what do you want to do?</span>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways.filter(colorwayy => colorwayy.name !== colorway.name), colorway] };
|
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]);
|
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
|
||||||
|
@ -98,29 +91,28 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Override
|
Override
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
function NewColorwayNameModal({ modalProps, onSelected }: { modalProps: ModalProps, onSelected: (e: string) => void; }) {
|
function NewColorwayNameModal({ modalProps, onSelected }: { modalProps: ModalProps, onSelected: (e: string) => void; }) {
|
||||||
const [errorMsg, setErrorMsg] = useState<string>();
|
const [errorMsg, setErrorMsg] = useState<string>();
|
||||||
const [newColorwayName, setNewColorwayName] = useState("");
|
const [newColorwayName, setNewColorwayName] = useState("");
|
||||||
return <ModalRoot {...modalProps}>
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<ModalHeader separator={false}>
|
<h2 className="colorwaysModalHeader">
|
||||||
<Text variant="heading-lg/semibold" tag="h1">Select new name</Text>
|
Select new name
|
||||||
</ModalHeader>
|
</h2>
|
||||||
<ModalContent>
|
<div className="colorwaysModalContent">
|
||||||
<TextInput error={errorMsg} value={newColorwayName} onChange={e => setNewColorwayName(e)} placeholder="Enter valid colorway name" />
|
<input
|
||||||
</ModalContent>
|
type="text"
|
||||||
<ModalFooter>
|
className="colorwaySelector-search"
|
||||||
<Button
|
value={newColorwayName}
|
||||||
style={{ marginLeft: 8 }}
|
onInput={({ currentTarget: { value } }) => setNewColorwayName(value)}
|
||||||
color={Button.Colors.PRIMARY}
|
placeholder="Enter valid colorway name" />
|
||||||
size={Button.Sizes.MEDIUM}
|
</div>
|
||||||
look={Button.Looks.OUTLINED}
|
<div className="colorwaysModalFooter">
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setErrorMsg("");
|
setErrorMsg("");
|
||||||
if (storeToModify!.colorways.map(colorway => colorway.name).includes(newColorwayName)) {
|
if (storeToModify!.colorways.map(colorway => colorway.name).includes(newColorwayName)) {
|
||||||
|
@ -134,12 +126,9 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (i + 1 === colorways.length) {
|
if (i + 1 === colorways.length) {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
|
@ -147,9 +136,9 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
openModal(propss => <NewColorwayNameModal modalProps={propss} onSelected={e => {
|
openModal(propss => <NewColorwayNameModal modalProps={propss} onSelected={e => {
|
||||||
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, { ...colorway, name: e }] };
|
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, { ...colorway, name: e }] };
|
||||||
|
@ -163,20 +152,17 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Rename
|
Rename
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onClose();
|
props.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Select different store
|
Select different store
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>);
|
</div>);
|
||||||
} else {
|
} else {
|
||||||
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, colorway] };
|
const newStore = { name: storeToModify.name, colorways: [...storeToModify.colorways, colorway] };
|
||||||
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
|
DataStore.set("customColorways", [...oldStores!.filter(source => source.name !== storename), newStore]);
|
||||||
|
@ -190,18 +176,15 @@ export default function ({ modalProps, colorways, onFinish }: { modalProps: Moda
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modalProps.onClose();
|
modalProps.onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* 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>;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
26
src/equicordplugins/discordColorways/components/Setting.tsx
Normal file
26
src/equicordplugins/discordColorways/components/Setting.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ReactNode } from "../";
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
children,
|
||||||
|
divider = false,
|
||||||
|
disabled = false
|
||||||
|
}: { children: ReactNode, divider?: boolean, disabled?: boolean; }) {
|
||||||
|
return <div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
marginBottom: "20px"
|
||||||
|
}}>
|
||||||
|
{disabled ? <div style={{
|
||||||
|
pointerEvents: "none",
|
||||||
|
opacity: .5,
|
||||||
|
cursor: "not-allowed"
|
||||||
|
}}>{children}</div> : children}
|
||||||
|
{divider && <div className="colorwaysSettingsDivider" />}
|
||||||
|
</div>;
|
||||||
|
}
|
|
@ -4,15 +4,27 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, ReactNode, useCallback, useEffect, useState } from "../../";
|
||||||
import { SettingsTab } from "@components/VencordSettings/shared";
|
import Setting from "../Setting";
|
||||||
import { Switch, useCallback, useEffect, useState } from "@webpack/common";
|
import Switch from "../Switch";
|
||||||
|
|
||||||
export default function () {
|
export default function ({
|
||||||
|
hasTheme = false
|
||||||
|
}: {
|
||||||
|
hasTheme: boolean;
|
||||||
|
}) {
|
||||||
const [onDemand, setOnDemand] = useState<boolean>(false);
|
const [onDemand, setOnDemand] = useState<boolean>(false);
|
||||||
const [onDemandTinted, setOnDemandTinted] = useState<boolean>(false);
|
const [onDemandTinted, setOnDemandTinted] = useState<boolean>(false);
|
||||||
const [onDemandDiscordSat, setOnDemandDiscordSat] = useState<boolean>(false);
|
const [onDemandDiscordSat, setOnDemandDiscordSat] = useState<boolean>(false);
|
||||||
const [onDemandOsAccent, setOnDemandOsAccent] = useState<boolean>(false);
|
const [onDemandOsAccent, setOnDemandOsAccent] = useState<boolean>(false);
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
async function loadUI() {
|
async function loadUI() {
|
||||||
const [
|
const [
|
||||||
onDemandWays,
|
onDemandWays,
|
||||||
|
@ -38,47 +50,53 @@ export default function () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cached_loadUI();
|
cached_loadUI();
|
||||||
}, []);
|
}, []);
|
||||||
return <SettingsTab title="On-Demand">
|
|
||||||
<Switch
|
function Container({ children }: { children: ReactNode; }) {
|
||||||
value={onDemand}
|
if (hasTheme) return <div className="colorwaysModalTab" data-theme={theme}>{children}</div>;
|
||||||
onChange={(v: boolean) => {
|
else return <div className="colorwaysModalTab">{children}</div>;
|
||||||
setOnDemand(v);
|
}
|
||||||
DataStore.set("onDemandWays", v);
|
|
||||||
}}
|
return <Container>
|
||||||
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."
|
<Setting divider>
|
||||||
>
|
<Switch
|
||||||
Enable Colorways On Demand
|
label="Enable Colorways On Demand"
|
||||||
</Switch>
|
id="onDemandWays"
|
||||||
<Switch
|
value={onDemand}
|
||||||
value={onDemandTinted}
|
onChange={(v: boolean) => {
|
||||||
onChange={(v: boolean) => {
|
setOnDemand(v);
|
||||||
setOnDemandTinted(v);
|
DataStore.set("onDemandWays", v);
|
||||||
DataStore.set("onDemandWaysTintedText", v);
|
}} />
|
||||||
}}
|
<span className="colorwaysNote">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.</span>
|
||||||
disabled={!onDemand}
|
</Setting>
|
||||||
>
|
<Setting divider disabled={!onDemand}>
|
||||||
Use tinted text
|
<Switch
|
||||||
</Switch>
|
label="Use tinted text"
|
||||||
<Switch
|
id="onDemandWaysTintedText"
|
||||||
value={onDemandDiscordSat}
|
value={onDemandTinted}
|
||||||
onChange={(v: boolean) => {
|
onChange={(v: boolean) => {
|
||||||
setOnDemandDiscordSat(v);
|
setOnDemandTinted(v);
|
||||||
DataStore.set("onDemandWaysDiscordSaturation", v);
|
DataStore.set("onDemandWaysTintedText", v);
|
||||||
}}
|
}} />
|
||||||
disabled={!onDemand}
|
</Setting>
|
||||||
>
|
<Setting divider disabled={!onDemand}>
|
||||||
Use Discord's saturation
|
<Switch
|
||||||
</Switch>
|
label="Use Discord's saturation"
|
||||||
<Switch
|
id="onDemandWaysDiscordSaturation"
|
||||||
hideBorder
|
value={onDemandDiscordSat}
|
||||||
value={onDemandOsAccent}
|
onChange={(v: boolean) => {
|
||||||
onChange={(v: boolean) => {
|
setOnDemandDiscordSat(v);
|
||||||
setOnDemandOsAccent(v);
|
DataStore.set("onDemandWaysDiscordSaturation", v);
|
||||||
DataStore.set("onDemandWaysOsAccentColor", v);
|
}} />
|
||||||
}}
|
</Setting>
|
||||||
disabled={!onDemand || !getComputedStyle(document.body).getPropertyValue("--os-accent-color")}
|
<Setting disabled={!onDemand || !getComputedStyle(document.body).getPropertyValue("--os-accent-color")}>
|
||||||
>
|
<Switch
|
||||||
Use Operating System's Accent Color
|
label="Use Operating System's Accent Color"
|
||||||
</Switch>
|
id="onDemandWaysOsAccentColor"
|
||||||
</SettingsTab>;
|
value={onDemandOsAccent}
|
||||||
|
onChange={(v: boolean) => {
|
||||||
|
setOnDemandOsAccent(v);
|
||||||
|
DataStore.set("onDemandWaysOsAccentColor", v);
|
||||||
|
}} />
|
||||||
|
</Setting>
|
||||||
|
</Container>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,48 +4,52 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, FluxDispatcher, FluxEvents, PluginProps, ReactNode, useEffect, useState } from "../../";
|
||||||
import { Flex } from "@components/Flex";
|
import { defaultColorwaySource, fallbackColorways, nullColorwayObj } from "../../constants";
|
||||||
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";
|
import { Colorway } from "../../types";
|
||||||
|
import { connect, updateShouldAutoconnect } from "../../wsClient";
|
||||||
|
import { changeThemeIDCard } from "../ColorwayID";
|
||||||
|
import { changeTheme as changeThemeMain } from "../MainModal";
|
||||||
|
import Setting from "../Setting";
|
||||||
|
import Switch from "../Switch";
|
||||||
|
|
||||||
export default function () {
|
function changeTheme(theme: string) {
|
||||||
|
changeThemeMain(theme);
|
||||||
|
changeThemeIDCard(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
hasTheme = false
|
||||||
|
}: {
|
||||||
|
hasTheme?: boolean;
|
||||||
|
}) {
|
||||||
const [colorways, setColorways] = useState<Colorway[]>([]);
|
const [colorways, setColorways] = useState<Colorway[]>([]);
|
||||||
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
|
const [customColorways, setCustomColorways] = useState<Colorway[]>([]);
|
||||||
const [colorsButtonVisibility, setColorsButtonVisibility] = useState<boolean>(false);
|
const [colorsButtonVisibility, setColorsButtonVisibility] = useState<boolean>(false);
|
||||||
const [isButtonThin, setIsButtonThin] = useState<boolean>(false);
|
const [theme, setTheme] = useState("discord");
|
||||||
const [showLabelsInSelectorGridView, setShowLabelsInSelectorGridView] = useState<boolean>(false);
|
const [shouldAutoconnect, setShouldAutoconnect] = useState<"1" | "2">("1");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
setShouldAutoconnect(await DataStore.get("colorwaysManagerDoAutoconnect") as "1" | "2");
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async function () {
|
(async function () {
|
||||||
const [
|
const [
|
||||||
customColorways,
|
customColorways,
|
||||||
colorwaySourceFiles,
|
colorwaySourceFiles,
|
||||||
showColorwaysButton,
|
showColorwaysButton
|
||||||
useThinMenuButton,
|
|
||||||
showLabelsInSelectorGridView
|
|
||||||
] = await DataStore.getMany([
|
] = await DataStore.getMany([
|
||||||
"customColorways",
|
"customColorways",
|
||||||
"colorwaySourceFiles",
|
"colorwaySourceFiles",
|
||||||
"showColorwaysButton",
|
"showColorwaysButton"
|
||||||
"useThinMenuButton",
|
|
||||||
"showLabelsInSelectorGridView"
|
|
||||||
]);
|
]);
|
||||||
const responses: Response[] = await Promise.all(
|
const responses: Response[] = await Promise.all(
|
||||||
colorwaySourceFiles.map((url: string) =>
|
colorwaySourceFiles.map(({ url }: { url: string; }) =>
|
||||||
fetch(url)
|
fetch(url)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -57,16 +61,21 @@ export default function () {
|
||||||
setColorways(colorways || fallbackColorways);
|
setColorways(colorways || fallbackColorways);
|
||||||
setCustomColorways(customColorways.map(source => source.colorways).flat(2));
|
setCustomColorways(customColorways.map(source => source.colorways).flat(2));
|
||||||
setColorsButtonVisibility(showColorwaysButton);
|
setColorsButtonVisibility(showColorwaysButton);
|
||||||
setIsButtonThin(useThinMenuButton);
|
|
||||||
setShowLabelsInSelectorGridView(showLabelsInSelectorGridView);
|
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <SettingsTab title="Settings">
|
function Container({ children }: { children: ReactNode; }) {
|
||||||
<div className="colorwaysSettingsPage-wrapper">
|
if (hasTheme) return <div className="colorwaysModalTab" data-theme={theme}>{children}</div>;
|
||||||
<Forms.FormTitle tag="h5">Quick Switch</Forms.FormTitle>
|
else return <div className="colorwaysModalTab">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Container>
|
||||||
|
<span className="colorwaysModalSectionHeader">Quick Switch</span>
|
||||||
|
<Setting divider>
|
||||||
<Switch
|
<Switch
|
||||||
value={colorsButtonVisibility}
|
value={colorsButtonVisibility}
|
||||||
|
label="Enable Quick Switch"
|
||||||
|
id="showColorwaysButton"
|
||||||
onChange={(v: boolean) => {
|
onChange={(v: boolean) => {
|
||||||
setColorsButtonVisibility(v);
|
setColorsButtonVisibility(v);
|
||||||
DataStore.set("showColorwaysButton", v);
|
DataStore.set("showColorwaysButton", v);
|
||||||
|
@ -74,111 +83,198 @@ export default function () {
|
||||||
type: "COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents,
|
type: "COLORWAYS_UPDATE_BUTTON_VISIBILITY" as FluxEvents,
|
||||||
isVisible: v
|
isVisible: v
|
||||||
});
|
});
|
||||||
}}
|
}} />
|
||||||
note="Shows a button on the top of the servers list that opens a colorway selector modal."
|
<span className="colorwaysNote">Shows a button on the top of the servers list that opens a colorway selector modal.</span>
|
||||||
>
|
</Setting>
|
||||||
Enable Quick Switch
|
<span className="colorwaysModalSectionHeader">Appearance</span>
|
||||||
</Switch>
|
<Setting divider>
|
||||||
<Switch
|
<div style={{
|
||||||
value={isButtonThin}
|
display: "flex",
|
||||||
onChange={(v: boolean) => {
|
flexDirection: "row",
|
||||||
setIsButtonThin(v);
|
width: "100%",
|
||||||
DataStore.set("useThinMenuButton", v);
|
alignItems: "center",
|
||||||
FluxDispatcher.dispatch({
|
cursor: "pointer"
|
||||||
type: "COLORWAYS_UPDATE_BUTTON_HEIGHT" as FluxEvents,
|
}}>
|
||||||
isTall: v
|
<label className="colorwaySwitch-label">Plugin Theme</label>
|
||||||
});
|
<select
|
||||||
}}
|
className="colorwaysPillButton"
|
||||||
note="Replaces the icon on the colorways launcher button with text, making it more compact."
|
style={{ border: "none" }}
|
||||||
>
|
onChange={e => {
|
||||||
Use thin Quick Switch button
|
setTheme(e.currentTarget.value);
|
||||||
</Switch>
|
DataStore.set("colorwaysPluginTheme", e.currentTarget.value);
|
||||||
<Forms.FormTitle tag="h5">Selector</Forms.FormTitle>
|
changeTheme(e.currentTarget.value);
|
||||||
<Switch
|
}}
|
||||||
value={showLabelsInSelectorGridView}
|
value={theme}
|
||||||
onChange={(v: boolean) => {
|
>
|
||||||
setShowLabelsInSelectorGridView(v);
|
<option value="discord">Discord (Default)</option>
|
||||||
DataStore.set("showLabelsInSelectorGridView", v);
|
<option value="colorish">Colorish</option>
|
||||||
}}
|
</select>
|
||||||
>
|
</div>
|
||||||
Show labels in Grid View
|
</Setting>
|
||||||
</Switch>
|
<span className="colorwaysModalSectionHeader">Manager</span>
|
||||||
<Flex flexDirection="column" style={{ gap: 0 }}>
|
<Setting>
|
||||||
<h1 style={{
|
<div style={{
|
||||||
fontFamily: "var(--font-headline)",
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}>
|
||||||
|
<label className="colorwaySwitch-label">Automatically retry to connect to Manager</label>
|
||||||
|
<select
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
style={{ border: "none" }}
|
||||||
|
onChange={({ currentTarget: { value } }) => {
|
||||||
|
if (value === "1") {
|
||||||
|
DataStore.set("colorwaysManagerDoAutoconnect", true);
|
||||||
|
updateShouldAutoconnect(true);
|
||||||
|
} else {
|
||||||
|
DataStore.set("colorwaysManagerDoAutoconnect", false);
|
||||||
|
updateShouldAutoconnect(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={shouldAutoconnect}
|
||||||
|
>
|
||||||
|
<option value="1">On (Default)</option>
|
||||||
|
<option value="2">Off</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</Setting>
|
||||||
|
<Setting divider>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}>
|
||||||
|
<label className="colorwaySwitch-label">Try to connect to Manager manually</label>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
onClick={() => connect()}
|
||||||
|
value={shouldAutoconnect}
|
||||||
|
>
|
||||||
|
Try to connect...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Setting>
|
||||||
|
<Setting divider>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}>
|
||||||
|
<label className="colorwaySwitch-label">Reset plugin to default settings (CANNOT BE UNDONE)</label>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
onClick={() => {
|
||||||
|
DataStore.setMany([
|
||||||
|
["customColorways", []],
|
||||||
|
["colorwaySourceFiles", [{
|
||||||
|
name: "Project Colorway",
|
||||||
|
url: defaultColorwaySource
|
||||||
|
}]],
|
||||||
|
["showColorwaysButton", false],
|
||||||
|
["onDemandWays", false],
|
||||||
|
["onDemandWaysTintedText", true],
|
||||||
|
["onDemandWaysDiscordSaturation", false],
|
||||||
|
["onDemandWaysOsAccentColor", false],
|
||||||
|
["activeColorwayObject", nullColorwayObj],
|
||||||
|
["colorwaysPluginTheme", "discord"],
|
||||||
|
["colorwaysBoundManagers", []],
|
||||||
|
["colorwaysManagerAutoconnectPeriod", 3000],
|
||||||
|
["colorwaysManagerDoAutoconnect", true]
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset...
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span className="colorwaysNote">Reset the plugin to its default settings. All bound managers, sources, and colorways will be deleted. Please reload Discord after use.</span>
|
||||||
|
</Setting>
|
||||||
|
<div style={{ flexDirection: "column", display: "flex" }}>
|
||||||
|
<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",
|
fontSize: "24px",
|
||||||
color: "var(--header-primary)",
|
backgroundColor: "var(--brand-500)",
|
||||||
lineHeight: "31px",
|
padding: "0 4px",
|
||||||
marginBottom: "0"
|
borderRadius: "4px"
|
||||||
}}>
|
}}>Colorways</span>
|
||||||
Discord <span style={{
|
</h1>
|
||||||
fontFamily: "var(--font-display)",
|
<span
|
||||||
fontSize: "24px",
|
style={{
|
||||||
backgroundColor: "var(--brand-500)",
|
color: "var(--text-normal)",
|
||||||
padding: "0 4px",
|
fontWeight: 500,
|
||||||
borderRadius: "4px"
|
fontSize: "14px",
|
||||||
}}>Colorways</span>
|
marginBottom: "12px"
|
||||||
</h1>
|
}}
|
||||||
<Text
|
>by Project Colorway</span>
|
||||||
variant="text-xs/normal"
|
<span className="colorwaysModalSectionHeader">
|
||||||
style={{
|
Plugin Version:
|
||||||
color: "var(--text-normal)",
|
</span>
|
||||||
fontWeight: 500,
|
<span
|
||||||
fontSize: "14px",
|
style={{
|
||||||
marginBottom: "12px"
|
color: "var(--text-muted)",
|
||||||
}}
|
fontWeight: 500,
|
||||||
>by Project Colorway</Text>
|
fontSize: "14px",
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
marginBottom: "8px"
|
||||||
Plugin Version:
|
}}
|
||||||
</Forms.FormTitle>
|
>
|
||||||
<Text
|
{PluginProps.pluginVersion} ({PluginProps.clientMod})
|
||||||
variant="text-xs/normal"
|
</span>
|
||||||
style={{
|
<span className="colorwaysModalSectionHeader">
|
||||||
color: "var(--text-muted)",
|
UI Version:
|
||||||
fontWeight: 500,
|
</span>
|
||||||
fontSize: "14px",
|
<span
|
||||||
marginBottom: "8px"
|
style={{
|
||||||
}}
|
color: "var(--text-muted)",
|
||||||
>
|
fontWeight: 500,
|
||||||
{versionData.pluginVersion}
|
fontSize: "14px",
|
||||||
</Text>
|
marginBottom: "8px"
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
}}
|
||||||
Creator Version:
|
>
|
||||||
</Forms.FormTitle>
|
{PluginProps.UIVersion}
|
||||||
<Text
|
</span>
|
||||||
variant="text-xs/normal"
|
<span className="colorwaysModalSectionHeader">
|
||||||
style={{
|
Creator Version:
|
||||||
color: "var(--text-muted)",
|
</span>
|
||||||
fontWeight: 500,
|
<span
|
||||||
fontSize: "14px",
|
style={{
|
||||||
marginBottom: "8px"
|
color: "var(--text-muted)",
|
||||||
}}
|
fontWeight: 500,
|
||||||
>
|
fontSize: "14px",
|
||||||
{versionData.creatorVersion}{" (Stable)"}
|
marginBottom: "8px"
|
||||||
</Text>
|
}}
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
>
|
||||||
Loaded Colorways:
|
{PluginProps.creatorVersion}
|
||||||
</Forms.FormTitle>
|
</span>
|
||||||
<Text
|
<span className="colorwaysModalSectionHeader">
|
||||||
variant="text-xs/normal"
|
Loaded Colorways:
|
||||||
style={{
|
</span>
|
||||||
color: "var(--text-muted)",
|
<span
|
||||||
fontWeight: 500,
|
style={{
|
||||||
fontSize: "14px",
|
color: "var(--text-muted)",
|
||||||
marginBottom: "8px"
|
fontWeight: 500,
|
||||||
}}
|
fontSize: "14px",
|
||||||
>
|
marginBottom: "8px"
|
||||||
{[...colorways, ...customColorways].length + 1}
|
}}
|
||||||
</Text>
|
>
|
||||||
<Forms.FormTitle style={{ marginBottom: 0 }}>
|
{[...colorways, ...customColorways].length}
|
||||||
Project Repositories:
|
</span>
|
||||||
</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader">
|
||||||
<Forms.FormText style={{ marginBottom: "8px" }}>
|
Project Repositories:
|
||||||
<Link href="https://github.com/DaBluLite/DiscordColorways">DiscordColorways</Link>
|
</span>
|
||||||
<br />
|
<a role="link" target="_blank" href="https://github.com/DaBluLite/DiscordColorways">DiscordColorways</a>
|
||||||
<Link href="https://github.com/DaBluLite/ProjectColorway">Project Colorway</Link>
|
<a role="link" target="_blank" href="https://github.com/DaBluLite/ProjectColorway">Project Colorway</a>
|
||||||
</Forms.FormText>
|
|
||||||
</Flex>
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsTab>;
|
</Container>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,39 +4,39 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, openModal, ReactNode, useEffect, useState } from "../../";
|
||||||
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 { defaultColorwaySource } from "../../constants";
|
||||||
import { Colorway } from "../../types";
|
import { Colorway, ModalProps } from "../../types";
|
||||||
import { DownloadIcon, ImportIcon } from "../Icons";
|
import { chooseFile, saveFile } from "../../utils";
|
||||||
import Spinner from "../Spinner";
|
import { updateRemoteSources } from "../../wsClient";
|
||||||
|
import { CopyIcon, DeleteIcon, DownloadIcon, ImportIcon, PlusIcon } from "../Icons";
|
||||||
|
import TabBar from "../TabBar";
|
||||||
|
|
||||||
export function StoreNameModal({ modalProps, originalName, onFinish, conflicting }: { modalProps: ModalProps, originalName: string, onFinish: (newName: string) => Promise<void>, conflicting: boolean; }) {
|
export function StoreNameModal({ modalProps, originalName, onFinish, conflicting }: { modalProps: ModalProps, originalName: string, onFinish: (newName: string) => Promise<void>, conflicting: boolean; }) {
|
||||||
const [error, setError] = useState<string>("");
|
const [error, setError] = useState<string>("");
|
||||||
const [newStoreName, setNewStoreName] = useState<string>(originalName);
|
const [newStoreName, setNewStoreName] = useState<string>(originalName);
|
||||||
return <ModalRoot {...modalProps}>
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader separator={false}>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1">{conflicting ? "Duplicate Store Name" : "Give this store a name"}</Text>
|
useEffect(() => {
|
||||||
</ModalHeader>
|
async function load() {
|
||||||
<ModalContent>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
{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>
|
load();
|
||||||
<TextInput error={error} value={newStoreName} onChange={e => setNewStoreName(e)} style={{ marginBottom: "16px" }} />
|
}, []);
|
||||||
</ModalContent>
|
|
||||||
<ModalFooter>
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
<Button
|
<h2 className="colorwaysModalHeader">
|
||||||
|
{conflicting ? "Duplicate Store Name" : "Give this store a name"}
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
{conflicting ? <span className="colorwaysModalSectionHeader">A store with the same name already exists. Please give a different name to the imported store:</span> : <></>}
|
||||||
|
<span className="colorwaysModalSectionHeader">Name:</span>
|
||||||
|
<input type="text" className="colorwaySelector-search" value={newStoreName} onChange={({ currentTarget: { value } }) => setNewStoreName(value)} style={{ marginBottom: "16px" }} />
|
||||||
|
</div>
|
||||||
|
<div className="colorwaysModalFooter">
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setError("");
|
setError("");
|
||||||
if ((await DataStore.get("customColorways")).map(store => store.name).includes(newStoreName)) {
|
if ((await DataStore.get("customColorways")).map(store => store.name).includes(newStoreName)) {
|
||||||
|
@ -47,18 +47,16 @@ export function StoreNameModal({ modalProps, originalName, onFinish, conflicting
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => modalProps.onClose()}
|
onClick={() => modalProps.onClose()}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps, onFinish: (name: string, url: string) => void; }) {
|
function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps, onFinish: (name: string, url: string) => void; }) {
|
||||||
|
@ -67,26 +65,35 @@ function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps,
|
||||||
const [nameError, setNameError] = useState<string>("");
|
const [nameError, setNameError] = useState<string>("");
|
||||||
const [URLError, setURLError] = useState<string>("");
|
const [URLError, setURLError] = useState<string>("");
|
||||||
const [nameReadOnly, setNameReadOnly] = useState<boolean>(false);
|
const [nameReadOnly, setNameReadOnly] = useState<boolean>(false);
|
||||||
return <ModalRoot {...modalProps}>
|
const [theme, setTheme] = useState("discord");
|
||||||
<ModalHeader separator={false}>
|
|
||||||
<Text variant="heading-lg/semibold" tag="h1">
|
useEffect(() => {
|
||||||
Add a source:
|
async function load() {
|
||||||
</Text>
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
</ModalHeader>
|
}
|
||||||
<ModalContent>
|
load();
|
||||||
<Forms.FormTitle>Name:</Forms.FormTitle>
|
}, []);
|
||||||
<TextInput
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Add a source:
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
<span className="colorwaysModalSectionHeader">Name:</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="colorwaySelector-search"
|
||||||
placeholder="Enter a valid Name..."
|
placeholder="Enter a valid Name..."
|
||||||
onChange={setColorwaySourceName}
|
onInput={e => setColorwaySourceName(e.currentTarget.value)}
|
||||||
value={colorwaySourceName}
|
value={colorwaySourceName}
|
||||||
error={nameError}
|
|
||||||
readOnly={nameReadOnly}
|
readOnly={nameReadOnly}
|
||||||
disabled={nameReadOnly}
|
disabled={nameReadOnly}
|
||||||
/>
|
/>
|
||||||
<Forms.FormTitle style={{ marginTop: "8px" }}>URL:</Forms.FormTitle>
|
<span className="colorwaysModalSectionHeader" style={{ marginTop: "8px" }}>URL:</span>
|
||||||
<TextInput
|
<input
|
||||||
|
type="text"
|
||||||
|
className="colorwaySelector-search"
|
||||||
placeholder="Enter a valid URL..."
|
placeholder="Enter a valid URL..."
|
||||||
onChange={value => {
|
onChange={({ currentTarget: { value } }) => {
|
||||||
setColorwaySourceURL(value);
|
setColorwaySourceURL(value);
|
||||||
if (value === defaultColorwaySource) {
|
if (value === defaultColorwaySource) {
|
||||||
setNameReadOnly(true);
|
setNameReadOnly(true);
|
||||||
|
@ -94,16 +101,12 @@ function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={colorwaySourceURL}
|
value={colorwaySourceURL}
|
||||||
error={URLError}
|
|
||||||
style={{ marginBottom: "16px" }}
|
style={{ marginBottom: "16px" }}
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</div>
|
||||||
<ModalFooter>
|
<div className="colorwaysModalFooter">
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
color={Button.Colors.BRAND}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.FILLED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const sourcesArr: { name: string, url: string; }[] = (await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
const sourcesArr: { name: string, url: string; }[] = (await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
||||||
if (!colorwaySourceName) {
|
if (!colorwaySourceName) {
|
||||||
|
@ -124,197 +127,104 @@ function AddOnlineStoreModal({ modalProps, onFinish }: { modalProps: ModalProps,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Finish
|
Finish
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
style={{ marginLeft: 8 }}
|
className="colorwaysPillButton"
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={() => modalProps.onClose()}
|
onClick={() => modalProps.onClose()}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</button>
|
||||||
</ModalFooter>
|
</div>
|
||||||
</ModalRoot>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function ({
|
||||||
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>([]);
|
hasTheme = false
|
||||||
const [customColorwayStores, setCustomColorwayStores] = useState<{ name: string, colorways: Colorway[]; }[]>([]);
|
}: {
|
||||||
|
hasTheme?: boolean;
|
||||||
const { item: radioBarItem, itemFilled: radioBarItemFilled } = findByProps("radioBar");
|
}) {
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function Container({ children }: { children: ReactNode; }) {
|
||||||
|
if (hasTheme) return <div className="colorwaysModalTab" data-theme={theme}>{children}</div>;
|
||||||
|
else return <div className="colorwaysModalTab">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Container>
|
||||||
|
<TabBar items={[
|
||||||
|
{
|
||||||
|
name: "Online",
|
||||||
|
component: OnlineTab
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Offline",
|
||||||
|
component: OfflineTab
|
||||||
|
}
|
||||||
|
]} />
|
||||||
|
</Container >;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OfflineTab() {
|
||||||
|
const [customColorwayStores, setCustomColorwayStores] = useState<{ name: string, colorways: Colorway[]; }[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async function () {
|
(async function () {
|
||||||
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
|
||||||
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
||||||
|
updateRemoteSources();
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
return <SettingsTab title="Sources">
|
return <div className="colorwaySourceTab">
|
||||||
<Flex style={{ gap: "0", marginBottom: "8px", alignItems: "center" }}>
|
<div style={{
|
||||||
<Forms.FormTitle tag="h5" style={{ marginBottom: 0, flexGrow: 1 }}>Online</Forms.FormTitle>
|
display: "flex",
|
||||||
<Button
|
gap: "8px"
|
||||||
className="colorwaysSettings-colorwaySourceAction"
|
}}>
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
style={{ flexShrink: "0" }}
|
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 () => {
|
onClick={async () => {
|
||||||
if (IS_DISCORD_DESKTOP) {
|
const file = await chooseFile("application/json");
|
||||||
const [file] = await DiscordNative.fileManager.openFiles({
|
if (!file) return;
|
||||||
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();
|
const reader = new FileReader();
|
||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
try {
|
try {
|
||||||
if ((await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]).map(store => store.name).includes(JSON.parse(reader.result as string).name)) {
|
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 => {
|
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 }]);
|
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[]; }[]);
|
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
||||||
}
|
updateRemoteSources();
|
||||||
} catch (err) {
|
}} />);
|
||||||
new Logger("DiscordColorways").error(err);
|
} 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[]; }[]);
|
||||||
|
updateRemoteSources();
|
||||||
}
|
}
|
||||||
};
|
} catch (err) {
|
||||||
reader.readAsText(file);
|
console.error("DiscordColorways: " + err);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
updateRemoteSources();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ImportIcon width={14} height={14} />
|
<ImportIcon width={14} height={14} />
|
||||||
Import...
|
Import...
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
className="colorwaysSettings-colorwaySourceAction"
|
className="colorwaysPillButton"
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
style={{ flexShrink: "0" }}
|
||||||
style={{ flexShrink: "0", marginLeft: "8px" }}
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
color={Button.Colors.TRANSPARENT}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openModal(props => <StoreNameModal conflicting={false} modalProps={props} originalName="" onFinish={async e => {
|
openModal(props => <StoreNameModal conflicting={false} modalProps={props} originalName="" onFinish={async e => {
|
||||||
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
|
await DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: [] }]);
|
||||||
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
setCustomColorwayStores(await DataStore.get("customColorways") as { name: string, colorways: Colorway[]; }[]);
|
||||||
props.onClose();
|
props.onClose();
|
||||||
|
updateRemoteSources();
|
||||||
}} />);
|
}} />);
|
||||||
}}>
|
}}>
|
||||||
<svg
|
<svg
|
||||||
|
@ -330,41 +240,31 @@ export default function () {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
New...
|
New...
|
||||||
</Button>
|
</button>
|
||||||
</Flex>
|
</div>
|
||||||
<ScrollerThin orientation="vertical" style={{ maxHeight: "50%" }} className="colorwaysSettings-sourceScroller">
|
<div className="colorwaysSettings-sourceScroller">
|
||||||
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") ? <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
{getComputedStyle(document.body).getPropertyValue("--os-accent-color") ? <div className={"colorwaysSettings-colorwaySource"} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
<Flex style={{ gap: 0, alignItems: "center", width: "100%", height: "30px" }}>
|
<div style={{ alignItems: "center", width: "100%", height: "30px", display: "flex" }}>
|
||||||
<Text className="colorwaysSettings-colorwaySourceLabel">OS Accent Color{" "}
|
<span className="colorwaysSettings-colorwaySourceLabel">OS Accent Color{" "}
|
||||||
<div className="colorways-badge">Built-In</div>
|
<div className="colorways-badge">Built-In</div>
|
||||||
</Text>
|
</span>
|
||||||
</Flex>
|
</div>
|
||||||
</div> : <></>}
|
</div> : <></>}
|
||||||
{customColorwayStores.map(({ name: customColorwaySourceName, colorways: offlineStoreColorways }) => <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
{customColorwayStores.map(({ name: customColorwaySourceName, colorways: offlineStoreColorways }) => <div className={"colorwaysSettings-colorwaySource"} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
<Text className="colorwaysSettings-colorwaySourceLabel">
|
<span className="colorwaysSettings-colorwaySourceLabel">
|
||||||
{customColorwaySourceName}
|
{customColorwaySourceName}
|
||||||
</Text>
|
</span>
|
||||||
<Flex style={{ marginLeft: "auto", gap: "8px" }}>
|
<div style={{ marginLeft: "auto", gap: "8px", display: "flex" }}>
|
||||||
<Button
|
<button
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (IS_DISCORD_DESKTOP) {
|
saveFile(new File([JSON.stringify({ "name": customColorwaySourceName, "colorways": [...offlineStoreColorways] })], `${customColorwaySourceName.replaceAll(" ", "-").toLowerCase()}.colorways.json`, { type: "application/json" }));
|
||||||
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...
|
<DownloadIcon width={14} height={14} /> Export as...
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
color={Button.Colors.RED}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
var sourcesArr: { name: string, colorways: Colorway[]; }[] = [];
|
var sourcesArr: { name: string, colorways: Colorway[]; }[] = [];
|
||||||
const customColorwaySources = await DataStore.get("customColorways");
|
const customColorwaySources = await DataStore.get("customColorways");
|
||||||
|
@ -375,13 +275,141 @@ export default function () {
|
||||||
});
|
});
|
||||||
DataStore.set("customColorways", sourcesArr);
|
DataStore.set("customColorways", sourcesArr);
|
||||||
setCustomColorwayStores(sourcesArr);
|
setCustomColorwayStores(sourcesArr);
|
||||||
|
updateRemoteSources();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon width={20} height={20} /> Remove
|
<DeleteIcon width={20} height={20} /> Remove
|
||||||
</Button>
|
</button>
|
||||||
</Flex>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ScrollerThin>
|
</div>
|
||||||
</SettingsTab>;
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnlineTab() {
|
||||||
|
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
(async function () {
|
||||||
|
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
||||||
|
updateRemoteSources();
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
return <div className="colorwaySourceTab">
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "8px"
|
||||||
|
}}>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
style={{ flexShrink: "0" }}
|
||||||
|
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 }]);
|
||||||
|
updateRemoteSources();
|
||||||
|
}} />);
|
||||||
|
}}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="colorwaysSettings-sourceScroller">
|
||||||
|
{!colorwaySourceFiles.length && <div className={"colorwaysSettings-colorwaySource"} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }} onClick={async () => {
|
||||||
|
DataStore.set("colorwaySourceFiles", [{ name: "Project Colorway", url: defaultColorwaySource }, ...(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter(i => i.name !== "Project Colorway")]);
|
||||||
|
setColorwaySourceFiles([{ name: "Project Colorway", url: defaultColorwaySource }, ...(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter(i => i.name !== "Project Colorway")]);
|
||||||
|
}}>
|
||||||
|
<PlusIcon width={24} height={24} />
|
||||||
|
<span className="colorwaysSettings-colorwaySourceLabel">
|
||||||
|
Add Project Colorway Source
|
||||||
|
</span>
|
||||||
|
</div>}
|
||||||
|
{colorwaySourceFiles.map((colorwaySourceFile: { name: string, url: string; }, i: number) => <div className={"colorwaysSettings-colorwaySource"} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
|
<div className="hoverRoll">
|
||||||
|
<span className="colorwaysSettings-colorwaySourceLabel hoverRoll_normal">
|
||||||
|
{colorwaySourceFile.name} {colorwaySourceFile.url === defaultColorwaySource && <div className="colorways-badge">Built-In</div>} {colorwaySourceFile.url === "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json" && <div className="colorways-badge">Built-In | Outdated</div>}
|
||||||
|
</span>
|
||||||
|
<span className="colorwaysSettings-colorwaySourceLabel hoverRoll_hovered">
|
||||||
|
{colorwaySourceFile.url}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: "auto", gap: "8px", display: "flex" }}>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
|
onClick={() => { navigator.clipboard.writeText(colorwaySourceFile.url); }}
|
||||||
|
>
|
||||||
|
<CopyIcon width={14} height={14} /> Copy URL
|
||||||
|
</button>
|
||||||
|
{colorwaySourceFile.url === "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json" && <button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
|
onClick={async () => {
|
||||||
|
DataStore.set("colorwaySourceFiles", [{ name: "Project Colorway", url: defaultColorwaySource }, ...(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter(i => i.name !== "Project Colorway")]);
|
||||||
|
setColorwaySourceFiles([{ name: "Project Colorway", url: defaultColorwaySource }, ...(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter(i => i.name !== "Project Colorway")]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
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> Update source...
|
||||||
|
</button>}
|
||||||
|
{(colorwaySourceFile.url !== defaultColorwaySource && colorwaySourceFile.url !== "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json")
|
||||||
|
&& <>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
|
onClick={async () => {
|
||||||
|
openModal(props => <StoreNameModal conflicting={false} modalProps={props} originalName={colorwaySourceFile.name || ""} onFinish={async e => {
|
||||||
|
const res = await fetch(colorwaySourceFile.url);
|
||||||
|
const data = await res.json();
|
||||||
|
DataStore.set("customColorways", [...await DataStore.get("customColorways"), { name: e, colorways: data.colorways || [] }]);
|
||||||
|
updateRemoteSources();
|
||||||
|
}} />);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadIcon width={14} height={14} /> Download...
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
|
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));
|
||||||
|
updateRemoteSources();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon width={14} height={14} /> Remove
|
||||||
|
</button>
|
||||||
|
</>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,32 +4,27 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore, openModal, ReactNode, useEffect, useState } from "../../";
|
||||||
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 { StoreItem } from "../../types";
|
||||||
import { DownloadIcon, PalleteIcon } from "../Icons";
|
import { DeleteIcon, DownloadIcon, PalleteIcon } from "../Icons";
|
||||||
import Selector from "../Selector";
|
import Selector from "../Selector";
|
||||||
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
export default function ({
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
hasTheme = false
|
||||||
|
}: {
|
||||||
function GithubIcon() {
|
hasTheme?: boolean;
|
||||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
}) {
|
||||||
return <img src={src} alt="GitHub" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
const [storeObject, setStoreObject] = useState<StoreItem[]>([]);
|
const [storeObject, setStoreObject] = useState<StoreItem[]>([]);
|
||||||
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>([]);
|
const [colorwaySourceFiles, setColorwaySourceFiles] = useState<{ name: string, url: string; }[]>([]);
|
||||||
const [searchValue, setSearchValue] = useState<string>("");
|
const [searchValue, setSearchValue] = useState<string>("");
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchValue) {
|
if (!searchValue) {
|
||||||
|
@ -42,79 +37,76 @@ export default function () {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { item: radioBarItem, itemFilled: radioBarItemFilled } = findByProps("radioBar");
|
function Container({ children }: { children: ReactNode; }) {
|
||||||
|
if (hasTheme) return <div className="colorwaysModalTab" data-theme={theme}>{children}</div>;
|
||||||
|
else return <div className="colorwaysModalTab">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return <SettingsTab title="Colorway Store">
|
return <Container>
|
||||||
<Flex style={{ gap: "0", marginBottom: "8px" }}>
|
<div style={{ display: "flex", marginBottom: "8px" }}>
|
||||||
<TextInput
|
<input
|
||||||
|
type="text"
|
||||||
className="colorwaySelector-search"
|
className="colorwaySelector-search"
|
||||||
placeholder="Search for sources..."
|
placeholder="Search for sources..."
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={setSearchValue}
|
onChange={e => setSearchValue(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<Tooltip text="Refresh...">
|
<button
|
||||||
{({ onMouseEnter, onMouseLeave }) => <Button
|
className="colorwaysPillButton"
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
style={{ marginLeft: "8px", marginTop: "auto", marginBottom: "auto" }}
|
||||||
size={Button.Sizes.ICON}
|
onClick={async function () {
|
||||||
color={Button.Colors.PRIMARY}
|
const res: Response = await fetch("https://dablulite.vercel.app/");
|
||||||
look={Button.Looks.OUTLINED}
|
const data = await res.json();
|
||||||
style={{ marginLeft: "8px" }}
|
setStoreObject(data.sources);
|
||||||
onMouseEnter={onMouseEnter}
|
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
||||||
onMouseLeave={onMouseLeave}
|
}}
|
||||||
onClick={async function () {
|
>
|
||||||
const res: Response = await fetch("https://dablulite.vercel.app/");
|
<svg
|
||||||
const data = await res.json();
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
setStoreObject(data.sources);
|
x="0px"
|
||||||
setColorwaySourceFiles(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]);
|
y="0px"
|
||||||
}}
|
width="14"
|
||||||
|
height="14"
|
||||||
|
style={{ boxSizing: "content-box", flexShrink: 0 }}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
<svg
|
<rect
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
y="0"
|
||||||
x="0px"
|
fill="none"
|
||||||
y="0px"
|
width="24"
|
||||||
width="20"
|
height="24"
|
||||||
height="20"
|
/>
|
||||||
style={{ padding: "6px", boxSizing: "content-box" }}
|
<path
|
||||||
viewBox="0 0 24 24"
|
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"
|
||||||
fill="currentColor"
|
/>
|
||||||
>
|
<path
|
||||||
<rect
|
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"
|
||||||
y="0"
|
/>
|
||||||
fill="none"
|
</svg>
|
||||||
width="24"
|
Refresh
|
||||||
height="24"
|
</button>
|
||||||
/>
|
</div>
|
||||||
<path
|
<div className="colorwaysSettings-sourceScroller">
|
||||||
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) =>
|
{storeObject.map((item: StoreItem) =>
|
||||||
item.name.toLowerCase().includes(searchValue.toLowerCase()) ? <div className={`${radioBarItem} ${radioBarItemFilled} colorwaysSettings-colorwaySource`} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
item.name.toLowerCase().includes(searchValue.toLowerCase()) ? <div className={"colorwaysSettings-colorwaySource"} style={{ flexDirection: "column", padding: "16px", alignItems: "start" }}>
|
||||||
<Flex flexDirection="column" style={{ gap: ".5rem", marginBottom: "8px" }}>
|
<div style={{ gap: ".5rem", display: "flex", marginBottom: "8px", flexDirection: "column" }}>
|
||||||
<Text className="colorwaysSettings-colorwaySourceLabelHeader">
|
<span className="colorwaysSettings-colorwaySourceLabelHeader">
|
||||||
{item.name}
|
{item.name}
|
||||||
</Text>
|
</span>
|
||||||
<Text className="colorwaysSettings-colorwaySourceDesc">
|
<span className="colorwaysSettings-colorwaySourceDesc">
|
||||||
{item.description}
|
{item.description}
|
||||||
</Text>
|
</span>
|
||||||
<Text className="colorwaysSettings-colorwaySourceDesc" style={{ opacity: ".8" }}>
|
<span className="colorwaysSettings-colorwaySourceDesc" style={{ opacity: ".8" }}>
|
||||||
by {item.authorGh}
|
by {item.authorGh}
|
||||||
</Text>
|
</span>
|
||||||
</Flex>
|
</div>
|
||||||
<Flex style={{ gap: "8px", alignItems: "center", width: "100%" }}>
|
<div style={{ gap: "8px", alignItems: "center", width: "100%", display: "flex" }}>
|
||||||
<Link href={"https://github.com/" + item.authorGh}><GithubIcon /></Link>
|
<a role="link" target="_blank" href={"https://github.com/" + item.authorGh}>
|
||||||
<Button
|
<img src="/assets/6a853b4c87fce386cbfef4a2efbacb09.svg" alt="GitHub" />
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
</a>
|
||||||
size={Button.Sizes.SMALL}
|
<button
|
||||||
color={colorwaySourceFiles.map(source => source.name).includes(item.name) ? Button.Colors.RED : Button.Colors.PRIMARY}
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
style={{ marginLeft: "auto" }}
|
style={{ marginLeft: "auto" }}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (colorwaySourceFiles.map(source => source.name).includes(item.name)) {
|
if (colorwaySourceFiles.map(source => source.name).includes(item.name)) {
|
||||||
|
@ -129,21 +121,26 @@ export default function () {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{colorwaySourceFiles.map(source => source.name).includes(item.name) ? <><DeleteIcon width={14} height={14} /> Remove</> : <><DownloadIcon width={14} height={14} /> Add to Sources</>}
|
{colorwaySourceFiles.map(source => source.name).includes(item.name) ? <><DeleteIcon width={14} height={14} /> Remove</> : <><DownloadIcon width={14} height={14} /> Add to Sources</>}
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
<button
|
||||||
innerClassName="colorwaysSettings-iconButtonInner"
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
look={Button.Looks.OUTLINED}
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
openModal(props => <Selector modalProps={props} settings={{ selectorType: "preview", previewSource: item.url }} />);
|
openModal(props => <div className={`colorwaysModal ${props.transitionState === 2 ? "closing" : ""} ${props.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">
|
||||||
|
Previewing colorways for {item.name}
|
||||||
|
</h2>
|
||||||
|
<div className="colorwaysModalContent colorwaysModalContent-sourcePreview">
|
||||||
|
<Selector settings={{ selectorType: "preview", previewSource: item.url }} />
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PalleteIcon width={14} height={14} />{" "}Preview
|
<PalleteIcon width={14} height={14} />
|
||||||
</Button>
|
Preview
|
||||||
</Flex>
|
</button>
|
||||||
|
</div>
|
||||||
</div> : <></>
|
</div> : <></>
|
||||||
)}
|
)}
|
||||||
</ScrollerThin>
|
</div>
|
||||||
</SettingsTab>;
|
</Container>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "..";
|
||||||
|
|
||||||
|
export default function ({ source, sources, onSourceChange }: { source: { name: string, id: string; }, sources: { name: string, id: string; }[], onSourceChange: (sourceId: string) => void; }) {
|
||||||
|
const menuProps = useRef(null);
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
const [current, setCurrent] = useState(source);
|
||||||
|
|
||||||
|
function rightClickContextMenu(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.dispatchEvent(new Event("click"));
|
||||||
|
setShowMenu(!showMenu);
|
||||||
|
setPos({
|
||||||
|
x: e.currentTarget.getBoundingClientRect().x,
|
||||||
|
y: e.currentTarget.getBoundingClientRect().y + e.currentTarget.offsetHeight + 8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onPageClick() {
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("click", onPageClick);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("click", onPageClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function onSourceChange_internal(newSort: { name: string, id: string; }) {
|
||||||
|
onSourceChange(newSort.id);
|
||||||
|
setCurrent(newSort);
|
||||||
|
setShowMenu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{showMenu ? <nav className="colorwaysContextMenu" ref={menuProps} style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: `${pos.y}px`,
|
||||||
|
left: `${pos.x}px`
|
||||||
|
}}>
|
||||||
|
{sources.map(({ name, id }) => {
|
||||||
|
return <button onClick={() => onSourceChange_internal({ name, id })} className="colorwaysContextMenuItm">
|
||||||
|
{name}
|
||||||
|
<svg aria-hidden="true" role="img" width="18" height="18" viewBox="0 0 24 24" style={{
|
||||||
|
marginLeft: "8px"
|
||||||
|
}}>
|
||||||
|
<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" />
|
||||||
|
{source.id === id ? <circle className="colorwaysRadioSelected" cx="12" cy="12" r="5" /> : null}
|
||||||
|
</svg>
|
||||||
|
</button>;
|
||||||
|
})}
|
||||||
|
</nav> : null}
|
||||||
|
<button className="colorwaysPillButton" onClick={rightClickContextMenu}>Source: {current.name}</button>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -4,9 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties } from "react";
|
export default function ({ className, style }: { className?: string, style?: any; }) {
|
||||||
|
|
||||||
export default function ({ className, style }: { className?: string, style?: CSSProperties; }) {
|
|
||||||
return <div className={"colorwaysBtn-spinner" + (className ? ` ${className}` : "")} role="img" aria-label="Loading" style={style}>
|
return <div className={"colorwaysBtn-spinner" + (className ? ` ${className}` : "")} role="img" aria-label="Loading" style={style}>
|
||||||
<div className="colorwaysBtn-spinnerInner">
|
<div className="colorwaysBtn-spinnerInner">
|
||||||
<svg className="colorwaysBtn-spinnerCircular" viewBox="25 25 50 50" fill="currentColor">
|
<svg className="colorwaysBtn-spinnerCircular" viewBox="25 25 50 50" fill="currentColor">
|
||||||
|
|
78
src/equicordplugins/discordColorways/components/Switch.tsx
Normal file
78
src/equicordplugins/discordColorways/components/Switch.tsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
id,
|
||||||
|
label
|
||||||
|
}: {
|
||||||
|
id?: string,
|
||||||
|
value: boolean,
|
||||||
|
label?: string,
|
||||||
|
onChange: (checked: boolean) => void;
|
||||||
|
}) {
|
||||||
|
return label ? <div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}>
|
||||||
|
<label className="colorwaySwitch-label" htmlFor={id}>{label}</label>
|
||||||
|
<div className={`colorwaysSettings-switch ${value ? "checked" : ""}`}>
|
||||||
|
<svg viewBox="0 0 28 20" preserveAspectRatio="xMinYMid meet" aria-hidden="true" style={{
|
||||||
|
left: value ? "12px" : "-3px",
|
||||||
|
transition: ".2s ease",
|
||||||
|
display: "block",
|
||||||
|
position: "absolute",
|
||||||
|
width: "28px",
|
||||||
|
height: "18px",
|
||||||
|
margin: "3px"
|
||||||
|
}}>
|
||||||
|
<rect className="colorwaysSettings-switchCircle" fill="#000" x="4" y="0" height="20" width="20" rx="10" />
|
||||||
|
</svg>
|
||||||
|
<input checked={value} id={id} type="checkbox" style={{
|
||||||
|
position: "absolute",
|
||||||
|
opacity: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "14px",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
margin: 0
|
||||||
|
}} tabIndex={0} onChange={e => {
|
||||||
|
onChange(e.currentTarget.checked);
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
</div> : <div className={`colorwaysSettings-switch ${value ? "checked" : ""}`}>
|
||||||
|
<svg viewBox="0 0 28 20" preserveAspectRatio="xMinYMid meet" aria-hidden="true" style={{
|
||||||
|
left: value ? "12px" : "-3px",
|
||||||
|
transition: ".2s ease",
|
||||||
|
display: "block",
|
||||||
|
position: "absolute",
|
||||||
|
width: "28px",
|
||||||
|
height: "18px",
|
||||||
|
margin: "3px"
|
||||||
|
}}>
|
||||||
|
<rect className="colorwaysSettings-switchCircle" fill="#000" x="4" y="0" height="20" width="20" rx="10" />
|
||||||
|
</svg>
|
||||||
|
<input checked={value} id={id} type="checkbox" style={{
|
||||||
|
position: "absolute",
|
||||||
|
opacity: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "14px",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
margin: 0
|
||||||
|
}} tabIndex={0} onChange={e => {
|
||||||
|
onChange(e.currentTarget.checked);
|
||||||
|
}} />
|
||||||
|
</div>;
|
||||||
|
}
|
28
src/equicordplugins/discordColorways/components/TabBar.tsx
Normal file
28
src/equicordplugins/discordColorways/components/TabBar.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from "..";
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
items = []
|
||||||
|
}: {
|
||||||
|
items: { name: string, component: () => JSX.Element; }[];
|
||||||
|
}) {
|
||||||
|
const [active, setActive] = useState(items[0].name);
|
||||||
|
return <>
|
||||||
|
<div className="colorwaysMenuTabs">
|
||||||
|
{items.map(item => {
|
||||||
|
return <div className={`colorwaysMenuTab ${active === item.name ? "active" : ""}`} onClick={() => {
|
||||||
|
setActive(item.name);
|
||||||
|
}}>{item.name}</div>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{items.map(item => {
|
||||||
|
const Component = item.component;
|
||||||
|
return active === item.name ? <Component /> : null;
|
||||||
|
})}
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* Vencord, a Discord client mod
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
|
import { openModal } from "..";
|
||||||
import { Text } from "@webpack/common";
|
import { ModalProps } from "../types";
|
||||||
|
|
||||||
import { HexToHSL } from "../utils";
|
import { HexToHSL } from "../utils";
|
||||||
import { CloseIcon } from "./Icons";
|
import { CloseIcon } from "./Icons";
|
||||||
|
|
||||||
|
@ -48,12 +47,12 @@ export default function ThemePreview({
|
||||||
if (isModal) {
|
if (isModal) {
|
||||||
modalProps?.onClose();
|
modalProps?.onClose();
|
||||||
} else {
|
} else {
|
||||||
openModal((props: ModalProps) => <ModalRoot className="colorwaysPreview-modal" {...props}>
|
openModal((props: ModalProps) => <div className={`colorwaysPreview-modal ${props.transitionState === 2 ? "closing" : ""} ${props.transitionState === 4 ? "hidden" : ""}`}>
|
||||||
<style>
|
<style>
|
||||||
{previewCSS}
|
{previewCSS}
|
||||||
</style>
|
</style>
|
||||||
<ThemePreview accent={accent} primary={primary} secondary={secondary} tertiary={tertiary} isModal modalProps={props} />
|
<ThemePreview accent={accent} primary={primary} secondary={secondary} tertiary={tertiary} isModal modalProps={props} />
|
||||||
</ModalRoot>);
|
</div>);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -122,14 +121,12 @@ export default function ThemePreview({
|
||||||
"--primary-500-hsl": `${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + (3.6 * 3), 100)}%`
|
"--primary-500-hsl": `${HexToHSL(primary)[0]} ${HexToHSL(primary)[1]}% ${Math.min(HexToHSL(primary)[2] + (3.6 * 3), 100)}%`
|
||||||
} as React.CSSProperties}
|
} as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<Text
|
<span style={{
|
||||||
tag="div"
|
fontWeight: 700,
|
||||||
variant="text-md/semibold"
|
color: "var(--text-normal)"
|
||||||
lineClamp={1}
|
}}>
|
||||||
selectable={false}
|
|
||||||
>
|
|
||||||
Preview
|
Preview
|
||||||
</Text>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="colorwayPreview-chat" style={{ background: `var(--dc-overlay-chat, ${primary})` }}>
|
<div className="colorwayPreview-chat" style={{ background: `var(--dc-overlay-chat, ${primary})` }}>
|
||||||
|
|
63
src/equicordplugins/discordColorways/components/Tooltip.tsx
Normal file
63
src/equicordplugins/discordColorways/components/Tooltip.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "..";
|
||||||
|
|
||||||
|
export default function ({
|
||||||
|
children,
|
||||||
|
text,
|
||||||
|
position = "top"
|
||||||
|
}: {
|
||||||
|
children: (props: { onMouseEnter: () => void; onMouseLeave: () => void; onClick: () => void; }) => JSX.Element,
|
||||||
|
text: JSX.Element,
|
||||||
|
position?: "top" | "bottom" | "left" | "right";
|
||||||
|
}) {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||||
|
const btn = useRef(null);
|
||||||
|
|
||||||
|
function showTooltip() {
|
||||||
|
setPos({
|
||||||
|
x: (btn.current as unknown as HTMLElement).children[0].getBoundingClientRect().x + ((btn.current as unknown as HTMLElement).children[0] as HTMLElement).offsetWidth + 8,
|
||||||
|
y: (btn.current as unknown as HTMLElement).children[0].getBoundingClientRect().y
|
||||||
|
});
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowUnfocused(e) {
|
||||||
|
e = e || window.event;
|
||||||
|
var from = e.relatedTarget || e.toElement;
|
||||||
|
if (!from || from.nodeName === "HTML") {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mouseout", onWindowUnfocused);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mouseout", onWindowUnfocused);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div ref={btn} style={{
|
||||||
|
display: "contents"
|
||||||
|
}}>
|
||||||
|
{children({
|
||||||
|
onMouseEnter: () => showTooltip(),
|
||||||
|
onMouseLeave: () => setVisible(false),
|
||||||
|
onClick: () => setVisible(false)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={`colorwaysTooltip colorwaysTooltip-${position} ${!visible ? "colorwaysTooltip-hidden" : ""}`} style={{
|
||||||
|
top: `${pos.y}px`,
|
||||||
|
left: `${pos.x}px`
|
||||||
|
}}>
|
||||||
|
<div className="colorwaysTooltipPointer" />
|
||||||
|
<div className="colorwaysTooltipContent">{text}</div>
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore, useEffect, useState } from "..";
|
||||||
|
import { ModalProps } from "../types";
|
||||||
|
import { getRepainterTheme } from "../utils";
|
||||||
|
|
||||||
|
export default function ({ modalProps, onFinish }: { modalProps: ModalProps, onFinish: ({ id, colors }: { id: string, colors: string[]; }) => void; }) {
|
||||||
|
const [colorwaySourceURL, setColorwaySourceURL] = useState<string>("");
|
||||||
|
const [URLError, setURLError] = useState<string>("");
|
||||||
|
const [theme, setTheme] = useState("discord");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function load() {
|
||||||
|
setTheme(await DataStore.get("colorwaysPluginTheme") as string);
|
||||||
|
}
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div className={`colorwaysModal ${modalProps.transitionState === 2 ? "closing" : ""} ${modalProps.transitionState === 4 ? "hidden" : ""}`} data-theme={theme}>
|
||||||
|
<h2 className="colorwaysModalHeader">Use Repainter theme</h2>
|
||||||
|
<div className="colorwaysModalContent">
|
||||||
|
<span className="colorwaysModalSectionHeader">URL: {URLError ? <span className="colorwaysModalSectionError">{URLError}</span> : <></>}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter a valid URL..."
|
||||||
|
onInput={e => {
|
||||||
|
setColorwaySourceURL(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
value={colorwaySourceURL}
|
||||||
|
className="colorwaySelector-search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="colorwaysModalFooter">
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton colorwaysPillButton-onSurface"
|
||||||
|
onClick={async () => {
|
||||||
|
getRepainterTheme(colorwaySourceURL).then(data => {
|
||||||
|
onFinish({ id: data.id as any, colors: data.colors as any });
|
||||||
|
modalProps.onClose();
|
||||||
|
}).catch(e => setURLError("Error: " + e));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="colorwaysPillButton"
|
||||||
|
onClick={() => modalProps.onClose()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
|
@ -4,7 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const defaultColorwaySource = "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json";
|
import { ColorwayObject } from "./types";
|
||||||
|
|
||||||
|
export const defaultColorwaySource = "https://raw.githubusercontent.com/ProjectColorway/ProjectColorway/master/index.json";
|
||||||
|
|
||||||
export const fallbackColorways = [
|
export const fallbackColorways = [
|
||||||
{
|
{
|
||||||
|
@ -311,3 +313,5 @@ export const mainColors = [
|
||||||
{ name: "secondary", title: "Secondary", var: "--background-secondary" },
|
{ name: "secondary", title: "Secondary", var: "--background-secondary" },
|
||||||
{ name: "tertiary", title: "Tertiary", var: "--background-tertiary" }
|
{ name: "tertiary", title: "Tertiary", var: "--background-tertiary" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const nullColorwayObj: ColorwayObject = { id: null, css: null, sourceType: null, source: null };
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserStore } from "@webpack/common";
|
import { PluginProps, UserStore } from "./";
|
||||||
import { Plugins } from "Vencord";
|
|
||||||
|
|
||||||
import { HexToHSL } from "./utils";
|
import { HexToHSL } from "./utils";
|
||||||
|
|
||||||
export const colorVariables: string[] = [
|
export const colorVariables: string[] = [
|
||||||
|
@ -398,31 +396,31 @@ export function gradientBase(accentColor?: string, discordSaturation = false) {
|
||||||
--bg-overlay-opacity-home-card: 0.9;
|
--bg-overlay-opacity-home-card: 0.9;
|
||||||
--bg-overlay-opacity-app-frame: var(--bg-overlay-opacity-5);
|
--bg-overlay-opacity-app-frame: var(--bg-overlay-opacity-5);
|
||||||
}
|
}
|
||||||
.children_cde9af:after, .form_d8a4a1:before {
|
.children_fc4f04:after, .form_a7d72e:before {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
.scroller_de945b {
|
.scroller_fea3ef {
|
||||||
background: var(--bg-overlay-app-frame,var(--background-tertiary));
|
background: var(--bg-overlay-app-frame,var(--background-tertiary));
|
||||||
}
|
}
|
||||||
.expandedFolderBackground_b1385f {
|
.expandedFolderBackground_bc7085 {
|
||||||
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
||||||
}
|
}
|
||||||
.wrapper__8436d:not(:hover):not(.selected_ae80f7) .childWrapper_a6ce15 {
|
.wrapper__8436d:not(:hover):not(.selected_ae80f7) .childWrapper_a6ce15 {
|
||||||
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
||||||
}
|
}
|
||||||
.folder__17546:has(.expandedFolderIconWrapper__324c1) {
|
.folder_bc7085:has(.expandedFolderIconWrapper_bc7085) {
|
||||||
background: var(--bg-overlay-6,var(--background-secondary));
|
background: var(--bg-overlay-6,var(--background-secondary));
|
||||||
}
|
}
|
||||||
.circleIconButton__05cf2:not(.selected_aded59) {
|
.circleIconButton_db6521:not(.selected_db6521) {
|
||||||
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
background: rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-6));
|
||||||
}
|
}
|
||||||
.auto_a3c0bd::-webkit-scrollbar-thumb,
|
.auto_eed6a8::-webkit-scrollbar-thumb,
|
||||||
.thin_b1c063::-webkit-scrollbar-thumb {
|
.thin_eed6a8::-webkit-scrollbar-thumb {
|
||||||
background-size: 200vh;
|
background-size: 200vh;
|
||||||
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),to(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)))),var(--custom-theme-background);
|
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),to(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)))),var(--custom-theme-background);
|
||||||
background-image: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),var(--custom-theme-background);
|
background-image: linear-gradient(rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4)),rgb(var(--bg-overlay-color-inverse)/var(--bg-overlay-opacity-4))),var(--custom-theme-background);
|
||||||
}
|
}
|
||||||
.auto_a3c0bd::-webkit-scrollbar-track {
|
.auto_eed6a8::-webkit-scrollbar-track {
|
||||||
background-size: 200vh;
|
background-size: 200vh;
|
||||||
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color)/.4)),to(rgb(var(--bg-overlay-color)/.4))),var(--custom-theme-background);
|
background-image: -webkit-gradient(linear,left top,left bottom,from(rgb(var(--bg-overlay-color)/.4)),to(rgb(var(--bg-overlay-color)/.4))),var(--custom-theme-background);
|
||||||
background-image: linear-gradient(rgb(var(--bg-overlay-color)/.4),rgb(var(--bg-overlay-color)/.4)),var(--custom-theme-background);
|
background-image: linear-gradient(rgb(var(--bg-overlay-color)/.4),rgb(var(--bg-overlay-color)/.4)),var(--custom-theme-background);
|
||||||
|
@ -470,10 +468,10 @@ export function gradientBase(accentColor?: string, discordSaturation = false) {
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateCss(primaryColor: string, secondaryColor: string, tertiaryColor: string, accentColor: string, tintedText: boolean, discordSaturation: boolean, mutedTextBrightness?: number, name?: string) {
|
export function generateCss(primaryColor: string, secondaryColor: string, tertiaryColor: string, accentColor: string, tintedText: boolean = true, discordSaturation: boolean = true, mutedTextBrightness?: number, name?: string) {
|
||||||
return `/**
|
return `/**
|
||||||
* @name ${name}
|
* @name ${name}
|
||||||
* @version ${(Plugins.plugins.DiscordColorways as any).creatorVersion}
|
* @version ${PluginProps.creatorVersion}
|
||||||
* @description Automatically generated Colorway.
|
* @description Automatically generated Colorway.
|
||||||
* @author ${UserStore.getCurrentUser().username}
|
* @author ${UserStore.getCurrentUser().username}
|
||||||
* @authorId ${UserStore.getCurrentUser().id}
|
* @authorId ${UserStore.getCurrentUser().id}
|
||||||
|
@ -529,30 +527,30 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
|
||||||
--primary-160-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + secondaryColor)[1] / 100) * (100 + PrimarySatDiffs[660])) * 10) / 10 : HexToHSL("#" + secondaryColor)[1]}%) ${Math.min(HexToHSL("#" + secondaryColor)[2] + 76.4, 82.5)}%;
|
--primary-160-hsl: ${HexToHSL("#" + secondaryColor)[0]} calc(var(--saturation-factor, 1)*${discordSaturation ? Math.round(((HexToHSL("#" + secondaryColor)[1] / 100) * (100 + PrimarySatDiffs[660])) * 10) / 10 : HexToHSL("#" + secondaryColor)[1]}%) ${Math.min(HexToHSL("#" + secondaryColor)[2] + 76.4, 82.5)}%;
|
||||||
--primary-200-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min(HexToHSL("#" + tertiaryColor)[2] + 80, 80)}%;
|
--primary-200-hsl: ${HexToHSL("#" + tertiaryColor)[0]} calc(var(--saturation-factor, 1)*${HexToHSL("#" + tertiaryColor)[1]}%) ${Math.min(HexToHSL("#" + tertiaryColor)[2] + 80, 80)}%;
|
||||||
}
|
}
|
||||||
.emptyPage_feb902,
|
.emptyPage_c6b11b,
|
||||||
.scrollerContainer_dda72c,
|
.scrollerContainer_c6b11b,
|
||||||
.container__03ec9,
|
.container_f1fd9c,
|
||||||
.header__71942 {
|
.header_f1fd9c {
|
||||||
background-color: unset !important;
|
background-color: unset !important;
|
||||||
}
|
}
|
||||||
.container__6b2e5,
|
.container_c2efea,
|
||||||
.container__03ec9,
|
.container_f1fd9c,
|
||||||
.header__71942 {
|
.header_f1fd9c {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}${(Math.round(HexToHSL("#" + primaryColor)[2]) > 80) ? `\n\n/*Primary*/
|
}${(Math.round(HexToHSL("#" + primaryColor)[2]) > 80) ? `\n\n/*Primary*/
|
||||||
.theme-dark .container_bd15da,
|
.theme-dark .container_c2739c,
|
||||||
.theme-dark .body__616e6,
|
.theme-dark .body_cd82a7,
|
||||||
.theme-dark .toolbar__62fb5,
|
.theme-dark .toolbar_fc4f04,
|
||||||
.theme-dark .container_e1387b,
|
.theme-dark .container_f0fccd,
|
||||||
.theme-dark .messageContent_abea64,
|
.theme-dark .messageContent_f9f2ca,
|
||||||
.theme-dark .attachButtonPlus_fd0021,
|
.theme-dark .attachButtonPlus_f298d4,
|
||||||
.theme-dark .username__0b0e7:not([style]),
|
.theme-dark .username_f9f2ca:not([style]),
|
||||||
.theme-dark .children_cde9af,
|
.theme-dark .children_fc4f04,
|
||||||
.theme-dark .buttonContainer__6de7e,
|
.theme-dark .buttonContainer_f9f2ca,
|
||||||
.theme-dark .listItem__48528,
|
.theme-dark .listItem_c96c45,
|
||||||
.theme-dark .body__616e6 .caret__33d19,
|
.theme-dark .body_cd82a7 .caret_fc4f04,
|
||||||
.theme-dark .body__616e6 .titleWrapper_d6133e > h1,
|
.theme-dark .body_cd82a7 .titleWrapper_fc4f04 > h1,
|
||||||
.theme-dark .body__616e6 .icon_ae0b42 {
|
.theme-dark .body_cd82a7 .icon_fc4f04 {
|
||||||
--white-500: black !important;
|
--white-500: black !important;
|
||||||
--interactive-normal: black !important;
|
--interactive-normal: black !important;
|
||||||
--text-normal: black !important;
|
--text-normal: black !important;
|
||||||
|
@ -561,23 +559,23 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
|
||||||
--header-secondary: black !important;
|
--header-secondary: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .contentRegionScroller__9ae20 :not(.mtk1,.mtk2,.mtk3,.mtk4,.mtk5,.mtk6,.mtk7,.mtk8,.mtk9,.monaco-editor .line-numbers) {
|
.theme-dark .contentRegionScroller_c25c6d :not(.mtk1,.mtk2,.mtk3,.mtk4,.mtk5,.mtk6,.mtk7,.mtk8,.mtk9,.monaco-editor .line-numbers) {
|
||||||
--white-500: black !important;
|
--white-500: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .container__26baa {
|
.theme-dark .container_fc4f04 {
|
||||||
--channel-icon: black;
|
--channel-icon: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .callContainer__1477d {
|
.theme-dark .callContainer_d880dc {
|
||||||
--white-500: ${(HexToHSL("#" + tertiaryColor)[2] > 80) ? "black" : "white"} !important;
|
--white-500: ${(HexToHSL("#" + tertiaryColor)[2] > 80) ? "black" : "white"} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .channelTextArea_c2094b {
|
.theme-dark .channelTextArea_a7d72e {
|
||||||
--text-normal: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
|
--text-normal: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .placeholder_dec8c7 {
|
.theme-dark .placeholder_a552a6 {
|
||||||
--channel-text-area-placeholder: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
|
--channel-text-area-placeholder: ${(HexToHSL("#" + primaryColor)[2] + 3.6 > 80) ? "black" : "white"};
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
}
|
}
|
||||||
|
@ -586,16 +584,16 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .root_a28985 > .header__5e5a6 > h1 {
|
.theme-dark .root_f9a4c9 > .header_f9a4c9 > h1 {
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
/*End Primary*/`: ""}${(HexToHSL("#" + secondaryColor)[2] > 80) ? `\n\n/*Secondary*/
|
/*End Primary*/`: ""}${(HexToHSL("#" + secondaryColor)[2] > 80) ? `\n\n/*Secondary*/
|
||||||
.theme-dark .wrapper__3c6d5 *,
|
.theme-dark .wrapper_cd82a7 *,
|
||||||
.theme-dark .sidebar_e031be *:not(.hasBanner__04337 *),
|
.theme-dark .sidebar_a4d4d9 *:not(.hasBanner_fd6364 *),
|
||||||
.theme-dark .members__573eb *:not([style]),
|
.theme-dark .members_cbd271 *:not([style]),
|
||||||
.theme-dark .sidebarRegionScroller__8113e *,
|
.theme-dark .sidebarRegionScroller_c25c6d *,
|
||||||
.theme-dark .header__8e271,
|
.theme-dark .header_e06857,
|
||||||
.theme-dark .lookFilled__950dd.colorPrimary_ebe632 {
|
.theme-dark .lookFilled_dd4f85.colorPrimary_dd4f85 {
|
||||||
--white-500: black !important;
|
--white-500: black !important;
|
||||||
--channels-default: black !important;
|
--channels-default: black !important;
|
||||||
--channel-icon: black !important;
|
--channel-icon: black !important;
|
||||||
|
@ -604,36 +602,36 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
|
||||||
--interactive-active: var(--white-500);
|
--interactive-active: var(--white-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .channelRow__538ef {
|
.theme-dark .channelRow_f04d06 {
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .channelRow__538ef * {
|
.theme-dark .channelRow_f04d06 * {
|
||||||
--channel-icon: black;
|
--channel-icon: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark #app-mount .activity_bafb94 {
|
.theme-dark #app-mount .activity_a31c43 {
|
||||||
--channels-default: var(--white-500) !important;
|
--channels-default: var(--white-500) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .nameTag__77ab2 {
|
.theme-dark .nameTag_b2ca13 {
|
||||||
--header-primary: black !important;
|
--header-primary: black !important;
|
||||||
--header-secondary: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 90%)" : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")} !important;
|
--header-secondary: ${HexToHSL("#" + secondaryColor)[0] === 0 ? "gray" : ((HexToHSL("#" + secondaryColor)[2] < 80) ? "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 90%)" : "hsl(" + HexToHSL("#" + secondaryColor)[0] + ", calc(var(--saturation-factor, 1)*100%), 20%)")} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .bannerVisible_ef30fe .headerContent__6fcc7 {
|
.theme-dark .bannerVisible_fd6364 .headerContent_fd6364 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .embedFull__14919 {
|
.theme-dark .embedFull_b0068a {
|
||||||
--text-normal: black;
|
--text-normal: black;
|
||||||
}
|
}
|
||||||
/*End Secondary*/`: ""}${HexToHSL("#" + tertiaryColor)[2] > 80 ? `\n\n/*Tertiary*/
|
/*End Secondary*/`: ""}${HexToHSL("#" + tertiaryColor)[2] > 80 ? `\n\n/*Tertiary*/
|
||||||
.theme-dark .winButton_f17fb6,
|
.theme-dark .winButton_a934d8,
|
||||||
.theme-dark .searchBar__310d8 *,
|
.theme-dark .searchBar_e0840f *,
|
||||||
.theme-dark .wordmarkWindows_ffbc5e,
|
.theme-dark .wordmarkWindows_a934d8,
|
||||||
.theme-dark .searchBar__5a20a *,
|
.theme-dark .searchBar_a46bef *,
|
||||||
.theme-dark .searchBarComponent__8f95f {
|
.theme-dark .searchBarComponent_f0963d {
|
||||||
--white-500: black !important;
|
--white-500: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,25 +639,25 @@ export function generateCss(primaryColor: string, secondaryColor: string, tertia
|
||||||
color: ${HexToHSL("#" + secondaryColor)[2] > 80 ? "black" : "white"};
|
color: ${HexToHSL("#" + secondaryColor)[2] > 80 ? "black" : "white"};
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .popout__24e32 > * {
|
.theme-dark .popout_c5b389 > * {
|
||||||
--interactive-normal: black !important;
|
--interactive-normal: black !important;
|
||||||
--header-secondary: black !important;
|
--header-secondary: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .tooltip__7b090 {
|
.theme-dark .tooltip_b6c360 {
|
||||||
--text-normal: black !important;
|
--text-normal: black !important;
|
||||||
}
|
}
|
||||||
.theme-dark .children_cde9af .icon_ae0b42 {
|
.theme-dark .children_fc4f04 .icon_fc4f04 {
|
||||||
color: var(--interactive-active) !important;
|
color: var(--interactive-active) !important;
|
||||||
}
|
}
|
||||||
/*End Tertiary*/`: ""}${HexToHSL("#" + accentColor)[2] > 80 ? `\n\n/*Accent*/
|
/*End Tertiary*/`: ""}${HexToHSL("#" + accentColor)[2] > 80 ? `\n\n/*Accent*/
|
||||||
.selected_aded59 *,
|
.selected_db6521 *,
|
||||||
.selected_ae80f7 *,
|
.selected_ae80f7 *,
|
||||||
#app-mount .lookFilled__950dd.colorBrand__27d57:not(.buttonColor__7bad9),
|
#app-mount .lookFilled_dd4f85.colorBrand_dd4f85:not(.buttonColor_adcaac),
|
||||||
.colorDefault_e361cf.focused_dcafb9,
|
.colorDefault_d90b3d.focused_d90b3d,
|
||||||
.row__9e25f:hover,
|
.row_c5b389:hover,
|
||||||
.colorwayInfoIcon,
|
.colorwayInfoIcon,
|
||||||
.checkmarkCircle_b1b1cc > circle {
|
.checkmarkCircle_cb7c27 > circle {
|
||||||
--white-500: black !important;
|
--white-500: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -789,10 +787,10 @@ export function getAutoPresets(accentColor?: string) {
|
||||||
--primary-400: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
|
--primary-400: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
|
||||||
--primary-360: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
|
--primary-360: hsl(${HexToHSL("#" + accentColor)[0]}, calc(var(--saturation-factor, 1)*12%), 90%);
|
||||||
}
|
}
|
||||||
.emptyPage_feb902,
|
.emptyPage_c6b11b,
|
||||||
.scrollerContainer_dda72c,
|
.scrollerContainer_c6b11b,
|
||||||
.container__03ec9,
|
.container_f1fd9c,
|
||||||
.header__71942 {
|
.header_f1fd9c {
|
||||||
background-color: unset !important;
|
background-color: unset !important;
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
@ -816,7 +814,25 @@ export function getAutoPresets(accentColor?: string) {
|
||||||
} as { [key: string]: { name: string, id: string, preset: () => string; }; };
|
} as { [key: string]: { name: string, id: string, preset: () => string; }; };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreset(primaryColor?: string, secondaryColor?: string, tertiaryColor?: string, accentColor?: string): { [preset: string]: { name: string, preset: (...args: any) => string | { full: string, base: string; }, id: string, colors: string[]; }; } {
|
export function getPreset(
|
||||||
|
primaryColor?: string,
|
||||||
|
secondaryColor?: string,
|
||||||
|
tertiaryColor?: string,
|
||||||
|
accentColor?: string
|
||||||
|
): {
|
||||||
|
[preset: string]: {
|
||||||
|
name: string,
|
||||||
|
preset: (...args: any) => string | { full: string, base: string; },
|
||||||
|
id: string,
|
||||||
|
colors: string[],
|
||||||
|
calculated?: {
|
||||||
|
accent?: string,
|
||||||
|
primary?: string,
|
||||||
|
secondary?: string,
|
||||||
|
tertiary?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} {
|
||||||
function cyanLegacy(discordSaturation = false) {
|
function cyanLegacy(discordSaturation = false) {
|
||||||
return `:root:root {
|
return `:root:root {
|
||||||
--cyan-accent-color: #${accentColor};
|
--cyan-accent-color: #${accentColor};
|
||||||
|
@ -979,7 +995,12 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
|
||||||
name: "Hue Rotation",
|
name: "Hue Rotation",
|
||||||
preset: getAutoPresets(accentColor).hueRotation.preset,
|
preset: getAutoPresets(accentColor).hueRotation.preset,
|
||||||
id: "hueRotation",
|
id: "hueRotation",
|
||||||
colors: ["accent"]
|
colors: ["accent"],
|
||||||
|
calculated: {
|
||||||
|
primary: `hsl(${HexToHSL("#" + accentColor)[0]} 11% 21%)`,
|
||||||
|
secondary: `hsl(${HexToHSL("#" + accentColor)[0]} 11% 18%)`,
|
||||||
|
tertiary: `hsl(${HexToHSL("#" + accentColor)[0]} 10% 13%)`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
accentSwap: {
|
accentSwap: {
|
||||||
name: "Accent Swap",
|
name: "Accent Swap",
|
||||||
|
@ -991,7 +1012,12 @@ export function getPreset(primaryColor?: string, secondaryColor?: string, tertia
|
||||||
name: "Material You",
|
name: "Material You",
|
||||||
preset: getAutoPresets(accentColor).materialYou.preset,
|
preset: getAutoPresets(accentColor).materialYou.preset,
|
||||||
id: "materialYou",
|
id: "materialYou",
|
||||||
colors: ["accent"]
|
colors: ["accent"],
|
||||||
|
calculated: {
|
||||||
|
primary: `hsl(${HexToHSL("#" + accentColor)[0]} 12% 12%)`,
|
||||||
|
secondary: `hsl(${HexToHSL("#" + accentColor)[0]} 12% 16%)`,
|
||||||
|
tertiary: `hsl(${HexToHSL("#" + accentColor)[0]} 16% 18%)`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
117
src/equicordplugins/discordColorways/defaultsLoader.tsx
Normal file
117
src/equicordplugins/discordColorways/defaultsLoader.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore } from ".";
|
||||||
|
import { defaultColorwaySource, nullColorwayObj } from "./constants";
|
||||||
|
|
||||||
|
export default async function () {
|
||||||
|
const [
|
||||||
|
customColorways,
|
||||||
|
colorwaySourceFiles,
|
||||||
|
showColorwaysButton,
|
||||||
|
onDemandWays,
|
||||||
|
onDemandWaysTintedText,
|
||||||
|
onDemandWaysDiscordSaturation,
|
||||||
|
onDemandWaysOsAccentColor,
|
||||||
|
activeColorwayObject,
|
||||||
|
colorwaysPluginTheme,
|
||||||
|
colorwaysBoundManagers,
|
||||||
|
colorwaysManagerAutoconnectPeriod,
|
||||||
|
colorwaysManagerDoAutoconnect
|
||||||
|
] = await DataStore.getMany([
|
||||||
|
"customColorways",
|
||||||
|
"colorwaySourceFiles",
|
||||||
|
"showColorwaysButton",
|
||||||
|
"onDemandWays",
|
||||||
|
"onDemandWaysTintedText",
|
||||||
|
"onDemandWaysDiscordSaturation",
|
||||||
|
"onDemandWaysOsAccentColor",
|
||||||
|
"activeColorwayObject",
|
||||||
|
"colorwaysPluginTheme",
|
||||||
|
"colorwaysBoundManagers",
|
||||||
|
"colorwaysManagerAutoconnectPeriod",
|
||||||
|
"colorwaysManagerDoAutoconnect"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const defaults = [
|
||||||
|
{
|
||||||
|
name: "colorwaysManagerAutoconnectPeriod",
|
||||||
|
value: colorwaysManagerAutoconnectPeriod,
|
||||||
|
default: 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colorwaysManagerDoAutoconnect",
|
||||||
|
value: colorwaysManagerDoAutoconnect,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "showColorwaysButton",
|
||||||
|
value: showColorwaysButton,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "onDemandWays",
|
||||||
|
value: onDemandWays,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "onDemandWaysTintedText",
|
||||||
|
value: onDemandWaysTintedText,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "onDemandWaysDiscordSaturation",
|
||||||
|
value: onDemandWaysDiscordSaturation,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "onDemandWaysOsAccentColor",
|
||||||
|
value: onDemandWaysOsAccentColor,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colorwaysBoundManagers",
|
||||||
|
value: colorwaysBoundManagers,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "activeColorwayObject",
|
||||||
|
value: activeColorwayObject,
|
||||||
|
default: nullColorwayObj
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "colorwaysPluginTheme",
|
||||||
|
value: colorwaysPluginTheme,
|
||||||
|
default: "discord"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
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 === "https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json" ? defaultColorwaySource : sourceURL };
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DataStore.set("colorwaySourceFiles", [{
|
||||||
|
name: "Project Colorway",
|
||||||
|
url: defaultColorwaySource
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,27 +4,30 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
// Plugin Imports
|
||||||
|
import * as $DataStore from "@api/DataStore";
|
||||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
|
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Devs, EquicordDevs } from "@utils/constants";
|
import { Devs, EquicordDevs } from "@utils/constants";
|
||||||
import { ModalProps, openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Clipboard,
|
|
||||||
Forms,
|
|
||||||
i18n,
|
i18n,
|
||||||
SettingsRouter,
|
SettingsRouter
|
||||||
Toasts
|
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
|
import { FluxEvents as $FluxEvents } from "@webpack/types";
|
||||||
|
// Mod-specific imports
|
||||||
|
import {
|
||||||
|
CSSProperties as $CSSProperties,
|
||||||
|
ReactNode as $ReactNode
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import AutoColorwaySelector from "./components/AutoColorwaySelector";
|
import { ColorwayCSS } from "./colorwaysAPI";
|
||||||
import ColorPickerModal from "./components/ColorPicker";
|
import ColorwayID from "./components/ColorwayID";
|
||||||
import ColorwaysButton from "./components/ColorwaysButton";
|
import ColorwaysButton from "./components/ColorwaysButton";
|
||||||
import CreatorModal from "./components/CreatorModal";
|
import CreatorModal from "./components/CreatorModal";
|
||||||
|
import PCSMigrationModal from "./components/PCSMigrationModal";
|
||||||
import Selector from "./components/Selector";
|
import Selector from "./components/Selector";
|
||||||
import OnDemandWaysPage from "./components/SettingsTabs/OnDemandPage";
|
import OnDemandWaysPage from "./components/SettingsTabs/OnDemandPage";
|
||||||
import SettingsPage from "./components/SettingsTabs/SettingsPage";
|
import SettingsPage from "./components/SettingsTabs/SettingsPage";
|
||||||
|
@ -32,97 +35,45 @@ import SourceManager from "./components/SettingsTabs/SourceManager";
|
||||||
import Store from "./components/SettingsTabs/Store";
|
import Store from "./components/SettingsTabs/Store";
|
||||||
import Spinner from "./components/Spinner";
|
import Spinner from "./components/Spinner";
|
||||||
import { defaultColorwaySource } from "./constants";
|
import { defaultColorwaySource } from "./constants";
|
||||||
import { generateCss, getAutoPresets } from "./css";
|
import defaultsLoader from "./defaultsLoader";
|
||||||
import style from "./style.css?managed";
|
import style from "./style.css?managed";
|
||||||
|
import discordTheme from "./theme.discord.css?managed";
|
||||||
import { ColorPickerProps, ColorwayObject } from "./types";
|
import { ColorPickerProps, ColorwayObject } from "./types";
|
||||||
import { colorToHex, hexToString } from "./utils";
|
import { connect } from "./wsClient";
|
||||||
|
|
||||||
|
export const DataStore = $DataStore;
|
||||||
|
export type ReactNode = $ReactNode;
|
||||||
|
export type CSSProperties = $CSSProperties;
|
||||||
|
export type FluxEvents = $FluxEvents;
|
||||||
|
export { closeModal, openModal } from "@utils/modal";
|
||||||
|
export {
|
||||||
|
Clipboard,
|
||||||
|
FluxDispatcher,
|
||||||
|
i18n,
|
||||||
|
ReactDOM,
|
||||||
|
SettingsRouter,
|
||||||
|
Slider,
|
||||||
|
Toasts,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useReducer,
|
||||||
|
useRef,
|
||||||
|
UserStore,
|
||||||
|
useState,
|
||||||
|
useStateFromStores
|
||||||
|
} from "@webpack/common";
|
||||||
|
|
||||||
export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
|
export let ColorPicker: React.FunctionComponent<ColorPickerProps> = () => {
|
||||||
return <Spinner className="colorways-creator-module-warning" />;
|
return <Spinner className="colorways-creator-module-warning" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
(async function () {
|
defaultsLoader();
|
||||||
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 = [
|
export const PluginProps = {
|
||||||
{ name: "showColorwaysButton", value: showColorwaysButton, default: false },
|
pluginVersion: "6.1.0",
|
||||||
{ name: "onDemandWays", value: onDemandWays, default: false },
|
clientMod: "Vencord User Plugin",
|
||||||
{ name: "onDemandWaysTintedText", value: onDemandWaysTintedText, default: true },
|
UIVersion: "2.0.0",
|
||||||
{ name: "useThinMenuButton", value: useThinMenuButton, default: false },
|
creatorVersion: "1.20"
|
||||||
{ 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({
|
export default definePlugin({
|
||||||
|
@ -130,38 +81,17 @@ export default definePlugin({
|
||||||
description: "A plugin that offers easy access to simple color schemes/themes for Discord, also known as Colorways",
|
description: "A plugin that offers easy access to simple color schemes/themes for Discord, also known as Colorways",
|
||||||
authors: [EquicordDevs.DaBluLite, Devs.ImLvna],
|
authors: [EquicordDevs.DaBluLite, Devs.ImLvna],
|
||||||
dependencies: ["ServerListAPI", "MessageAccessoriesAPI"],
|
dependencies: ["ServerListAPI", "MessageAccessoriesAPI"],
|
||||||
pluginVersion: versionData.pluginVersion,
|
pluginVersion: PluginProps.pluginVersion,
|
||||||
creatorVersion: versionData.creatorVersion,
|
|
||||||
toolboxActions: {
|
toolboxActions: {
|
||||||
"Change Colorway": () => openModal(props => <Selector modalProps={props} />),
|
|
||||||
"Open Colorway Creator": () => openModal(props => <CreatorModal modalProps={props} />),
|
"Open Colorway Creator": () => openModal(props => <CreatorModal modalProps={props} />),
|
||||||
"Open Color Stealer": () => openModal(props => <ColorPickerModal modalProps={props} />),
|
|
||||||
"Open Settings": () => SettingsRouter.open("ColorwaysSettings"),
|
"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: [
|
patches: [
|
||||||
// Credits to Kyuuhachi for the BetterSettings plugin patches
|
// Credits to Kyuuhachi for the BetterSettings plugin patches
|
||||||
{
|
{
|
||||||
find: "this.renderArtisanalHack()",
|
find: "this.renderArtisanalHack()",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /createPromise:\(\)=>([^:}]*?),webpackId:"?\d+"?,name:(?!="CollectiblesShop")"[^"]+"/g,
|
match: /createPromise:\(\)=>([^:}]*?),webpackId:"\d+",name:(?!="CollectiblesShop")"[^"]+"/g,
|
||||||
replace: "$&,_:$1",
|
replace: "$&,_:$1",
|
||||||
predicate: () => true
|
predicate: () => true
|
||||||
}
|
}
|
||||||
|
@ -170,8 +100,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
match: /(?<=(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,)(?=\1\(this)/,
|
||||||
replace: "$&(async ()=>$2)(),"
|
replace: "(async ()=>$2)(),"
|
||||||
},
|
},
|
||||||
predicate: () => true
|
predicate: () => true
|
||||||
},
|
},
|
||||||
|
@ -208,6 +138,57 @@ export default definePlugin({
|
||||||
|
|
||||||
patchedSettings: new WeakSet(),
|
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>) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
section: SectionTypes.HEADER,
|
||||||
|
label: "Discord Colorways",
|
||||||
|
className: "vc-settings-header"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysSelector",
|
||||||
|
label: "Colorways",
|
||||||
|
element: () => <Selector hasTheme />,
|
||||||
|
className: "dc-colorway-selector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysSettings",
|
||||||
|
label: "Settings",
|
||||||
|
element: () => <SettingsPage hasTheme />,
|
||||||
|
className: "dc-colorway-settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysSourceManager",
|
||||||
|
label: "Sources",
|
||||||
|
element: () => <SourceManager hasTheme />,
|
||||||
|
className: "dc-colorway-sources-manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysOnDemand",
|
||||||
|
label: "On-Demand",
|
||||||
|
element: () => <OnDemandWaysPage hasTheme />,
|
||||||
|
className: "dc-colorway-ondemand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: "ColorwaysStore",
|
||||||
|
label: "Store",
|
||||||
|
element: () => <Store hasTheme />,
|
||||||
|
className: "dc-colorway-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: SectionTypes.DIVIDER
|
||||||
|
}
|
||||||
|
].filter(Boolean);
|
||||||
|
},
|
||||||
|
|
||||||
ColorwaysButton: () => <ColorwaysButton />,
|
ColorwaysButton: () => <ColorwaysButton />,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
|
@ -220,31 +201,31 @@ export default definePlugin({
|
||||||
const ColorwaysSelector = () => ({
|
const ColorwaysSelector = () => ({
|
||||||
section: "ColorwaysSelector",
|
section: "ColorwaysSelector",
|
||||||
label: "Colorways Selector",
|
label: "Colorways Selector",
|
||||||
element: () => <Selector isSettings modalProps={{ onClose: () => new Promise(() => true), transitionState: 1 }} />,
|
element: () => <Selector hasTheme />,
|
||||||
className: "dc-colorway-selector"
|
className: "dc-colorway-selector"
|
||||||
});
|
});
|
||||||
const ColorwaysSettings = () => ({
|
const ColorwaysSettings = () => ({
|
||||||
section: "ColorwaysSettings",
|
section: "ColorwaysSettings",
|
||||||
label: "Colorways Settings",
|
label: "Colorways Settings",
|
||||||
element: SettingsPage,
|
element: () => <SettingsPage hasTheme />,
|
||||||
className: "dc-colorway-settings"
|
className: "dc-colorway-settings"
|
||||||
});
|
});
|
||||||
const ColorwaysSourceManager = () => ({
|
const ColorwaysSourceManager = () => ({
|
||||||
section: "ColorwaysSourceManager",
|
section: "ColorwaysSourceManager",
|
||||||
label: "Colorways Sources",
|
label: "Colorways Sources",
|
||||||
element: SourceManager,
|
element: () => <SourceManager hasTheme />,
|
||||||
className: "dc-colorway-sources-manager"
|
className: "dc-colorway-sources-manager"
|
||||||
});
|
});
|
||||||
const ColorwaysOnDemand = () => ({
|
const ColorwaysOnDemand = () => ({
|
||||||
section: "ColorwaysOnDemand",
|
section: "ColorwaysOnDemand",
|
||||||
label: "Colorways On-Demand",
|
label: "Colorways On-Demand",
|
||||||
element: OnDemandWaysPage,
|
element: () => <OnDemandWaysPage hasTheme />,
|
||||||
className: "dc-colorway-ondemand"
|
className: "dc-colorway-ondemand"
|
||||||
});
|
});
|
||||||
const ColorwaysStore = () => ({
|
const ColorwaysStore = () => ({
|
||||||
section: "ColorwaysStore",
|
section: "ColorwaysStore",
|
||||||
label: "Colorways Store",
|
label: "Colorways Store",
|
||||||
element: Store,
|
element: () => <Store hasTheme />,
|
||||||
className: "dc-colorway-store"
|
className: "dc-colorway-store"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -252,109 +233,25 @@ export default definePlugin({
|
||||||
|
|
||||||
addServerListElement(ServerListRenderPosition.Above, this.ColorwaysButton);
|
addServerListElement(ServerListRenderPosition.Above, this.ColorwaysButton);
|
||||||
|
|
||||||
|
connect();
|
||||||
|
|
||||||
enableStyle(style);
|
enableStyle(style);
|
||||||
|
enableStyle(discordTheme);
|
||||||
ColorwayCSS.set((await DataStore.get("activeColorwayObject") as ColorwayObject).css || "");
|
ColorwayCSS.set((await DataStore.get("activeColorwayObject") as ColorwayObject).css || "");
|
||||||
|
|
||||||
addAccessory("colorways-btn", props => {
|
if ((await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).map(i => i.url).includes("https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json") || (!(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).map(i => i.url).includes("https://raw.githubusercontent.com/DaBluLite/ProjectColorway/master/index.json") && !(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).map(i => i.url).includes("https://raw.githubusercontent.com/ProjectColorway/ProjectColorway/master/index.json"))) {
|
||||||
if (String(props.message.content).match(/colorway:[0-9a-f]{0,100}/)) {
|
DataStore.set("colorwaySourceFiles", [{ name: "Project Colorway", url: defaultColorwaySource }, ...(await DataStore.get("colorwaySourceFiles") as { name: string, url: string; }[]).filter(i => i.name !== "Project Colorway")]);
|
||||||
return <Flex flexDirection="column">
|
openModal(props => <PCSMigrationModal modalProps={props} />);
|
||||||
{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">
|
addAccessory("colorway-id-card", props => <ColorwayID props={props} />);
|
||||||
<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() {
|
stop() {
|
||||||
removeServerListElement(ServerListRenderPosition.In, this.ColorwaysButton);
|
removeServerListElement(ServerListRenderPosition.Above, this.ColorwaysButton);
|
||||||
disableStyle(style);
|
disableStyle(style);
|
||||||
|
disableStyle(discordTheme);
|
||||||
ColorwayCSS.remove();
|
ColorwayCSS.remove();
|
||||||
removeAccessory("colorways-btn");
|
removeAccessory("colorway-id-card");
|
||||||
const customSettingsSections = (
|
const customSettingsSections = (
|
||||||
Vencord.Plugins.plugins.Settings as any as {
|
Vencord.Plugins.plugins.Settings as any as {
|
||||||
customSections: ((ID: Record<string, unknown>) => any)[];
|
customSections: ((ID: Record<string, unknown>) => any)[];
|
||||||
|
|
File diff suppressed because it is too large
Load diff
333
src/equicordplugins/discordColorways/theme.discord.css
Normal file
333
src/equicordplugins/discordColorways/theme.discord.css
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
/* stylelint-disable color-function-notation */
|
||||||
|
.colorwaySelectorModal[data-theme="discord"],
|
||||||
|
.colorwayModal[data-theme="discord"] {
|
||||||
|
border: none;
|
||||||
|
box-shadow: var(--legacy-elevation-border), var(--legacy-elevation-high);
|
||||||
|
background-color: var(--modal-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettingsDivider {
|
||||||
|
border-color: var(--background-modifier-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaySwitch-label,
|
||||||
|
[data-theme="discord"] .colorwaysNote {
|
||||||
|
color: var(--header-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-switchCircle {
|
||||||
|
fill: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-switch {
|
||||||
|
background-color: rgb(128, 132, 142);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-switch.checked {
|
||||||
|
background-color: #23a55a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] > .colorwaySelectorSidebar > .colorwaySelectorSidebar-tab {
|
||||||
|
transition: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] > .colorwaySelectorSidebar > .colorwaySelectorSidebar-tab.active {
|
||||||
|
background-color: var(--background-modifier-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] > .colorwaySelectorSidebar > .colorwaySelectorSidebar-tab:hover {
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysPillButton {
|
||||||
|
color: var(--white-500);
|
||||||
|
background-color: var(--button-secondary-background);
|
||||||
|
height: var(--custom-button-button-sm-height);
|
||||||
|
min-width: var(--custom-button-button-sm-width);
|
||||||
|
min-height: var(--custom-button-button-sm-height);
|
||||||
|
width: auto;
|
||||||
|
transition:
|
||||||
|
background-color var(--custom-button-transition-duration) ease,
|
||||||
|
color var(--custom-button-transition-duration) ease;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 2px 16px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysPillButton:hover {
|
||||||
|
background-color: var(--button-secondary-background-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] > .colorwaySelectorSidebar {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
background-color: var(--modal-footer-background);
|
||||||
|
box-shadow: inset 0 1px 0 hsl(var(--primary-630-hsl) / 60%);
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaySelector-search {
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--text-normal);
|
||||||
|
background-color: var(--input-background) !important;
|
||||||
|
height: 40px;
|
||||||
|
padding: 10px;
|
||||||
|
transition: none;
|
||||||
|
font-size: 16px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySource {
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--interactive-normal);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySource:hover {
|
||||||
|
color: var(--interactive-active);
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .discordColorway {
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: none;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .discordColorway:hover {
|
||||||
|
filter: none;
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .discordColorway[aria-checked="true"] {
|
||||||
|
background-color: var(--background-modifier-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySourceLabelHeader,
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySourceDesc {
|
||||||
|
color: var(--header-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorways-badge {
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: var(--bg-brand);
|
||||||
|
color: var(--white);
|
||||||
|
text-transform: uppercase;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
text-indent: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorwaysModal[data-theme="discord"] {
|
||||||
|
box-shadow: var(--legacy-elevation-border), var(--legacy-elevation-high);
|
||||||
|
background-color: var(--modal-background);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysMenuTabs {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysMenuTab {
|
||||||
|
padding: 0;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
margin-right: 32px;
|
||||||
|
margin-bottom: -2px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysMenuTab:hover {
|
||||||
|
color: var(--interactive-hover);
|
||||||
|
border-bottom-color: var(--brand-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysMenuTab.active {
|
||||||
|
cursor: default;
|
||||||
|
color: var(--interactive-active);
|
||||||
|
border-bottom-color: var(--control-brand-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter {
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
background-color: var(--modal-footer-background);
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: inset 0 1px 0 hsl(var(--primary-630-hsl) / 60%);
|
||||||
|
gap: 0;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton {
|
||||||
|
width: auto;
|
||||||
|
height: var(--custom-button-button-md-height);
|
||||||
|
min-width: var(--custom-button-button-md-width);
|
||||||
|
min-height: var(--custom-button-button-md-height);
|
||||||
|
transition:
|
||||||
|
color var(--custom-button-transition-duration) ease,
|
||||||
|
background-color var(--custom-button-transition-duration) ease,
|
||||||
|
border-color var(--custom-button-transition-duration) ease;
|
||||||
|
border: 1px solid var(--button-outline-primary-border);
|
||||||
|
color: var(--button-outline-primary-text);
|
||||||
|
margin-left: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton:hover {
|
||||||
|
background-color: var(--button-outline-primary-background-hover);
|
||||||
|
border-color: var(--button-outline-primary-border-hover);
|
||||||
|
color: var(--button-outline-primary-text-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton:active {
|
||||||
|
background-color: var(--button-outline-primary-background-active);
|
||||||
|
border-color: var(--button-outline-primary-border-active);
|
||||||
|
color: var(--button-outline-primary-text-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton.colorwaysPillButton-onSurface {
|
||||||
|
color: var(--white-500);
|
||||||
|
background-color: var(--brand-500);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton.colorwaysPillButton-onSurface:hover {
|
||||||
|
background-color: var(--brand-560);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalFooter > .colorwaysPillButton.colorwaysPillButton-onSurface:active {
|
||||||
|
background-color: var(--brand-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalHeader {
|
||||||
|
box-shadow:
|
||||||
|
0 1px 0 0 hsl(var(--primary-800-hsl) / 30%),
|
||||||
|
0 1px 2px 0 hsl(var(--primary-800-hsl) / 30%);
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
transition: box-shadow 0.1s ease-out;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysModalSectionHeader,
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySourceLabel,
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySourceLabelHeader,
|
||||||
|
[data-theme="discord"] .colorwaysSettings-colorwaySourceDesc {
|
||||||
|
color: var(--header-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysCreator-setting,
|
||||||
|
[data-theme="discord"] .colorwaysCreator-settingCat {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysCreator-setting:hover {
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysContextMenu {
|
||||||
|
background: var(--background-floating);
|
||||||
|
box-shadow: var(--shadow-high);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: none;
|
||||||
|
gap: 0;
|
||||||
|
min-width: 188px;
|
||||||
|
max-width: 320px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysContextMenuItm {
|
||||||
|
border: none;
|
||||||
|
transition: none;
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px;
|
||||||
|
color: var(--interactive-normal);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysContextMenuItm:hover {
|
||||||
|
background-color: var(--menu-item-default-hover-bg);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysContextMenuItm:active {
|
||||||
|
background-color: var(--menu-item-default-active-bg);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysRadioSelected {
|
||||||
|
fill: var(--control-brand-foreground-new);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysConflictingColors-warning {
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysManagerConnectionMenu {
|
||||||
|
transition:
|
||||||
|
transform 0.1s ease,
|
||||||
|
opacity 0.1s ease;
|
||||||
|
transform: scale(0.95);
|
||||||
|
transform-origin: 0% 50%;
|
||||||
|
background-color: var(--background-floating);
|
||||||
|
box-shadow: var(--shadow-high);
|
||||||
|
color: var(--text-normal);
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorwayIDCard[data-theme="discord"] > .colorwayMessage {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .colorwayIDCard[data-theme="discord"] .colorwayMessage {
|
||||||
|
background: hsl(var(--primary-630-hsl) / 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .colorwayIDCard[data-theme="discord"] .colorwayMessage {
|
||||||
|
background: hsl(var(--primary-100-hsl) / 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysManagerConnectionValue {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="discord"] .colorwaysManagerConnectionValue > b {
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
|
@ -21,7 +21,8 @@ export interface Colorway {
|
||||||
source?: string,
|
source?: string,
|
||||||
linearGradient?: string,
|
linearGradient?: string,
|
||||||
preset?: string,
|
preset?: string,
|
||||||
creatorVersion: string;
|
creatorVersion: string,
|
||||||
|
colorObj?: { accent?: string, primary?: string, secondary?: string, tertiary?: string; };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColorPickerProps {
|
export interface ColorPickerProps {
|
||||||
|
@ -34,9 +35,15 @@ export interface ColorPickerProps {
|
||||||
|
|
||||||
export interface ColorwayObject {
|
export interface ColorwayObject {
|
||||||
id: string | null,
|
id: string | null,
|
||||||
css: string | null,
|
css?: string | null,
|
||||||
sourceType: "online" | "offline" | "temporary" | null,
|
sourceType: "online" | "offline" | "temporary" | null,
|
||||||
source: string | null | undefined;
|
source: string | null | undefined,
|
||||||
|
colors?: {
|
||||||
|
accent?: string | undefined,
|
||||||
|
primary?: string | undefined,
|
||||||
|
secondary?: string | undefined,
|
||||||
|
tertiary?: string | undefined;
|
||||||
|
} | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SourceObject {
|
export interface SourceObject {
|
||||||
|
@ -63,3 +70,8 @@ export interface StoreItem {
|
||||||
url: string,
|
url: string,
|
||||||
authorGh: string;
|
authorGh: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModalProps {
|
||||||
|
transitionState: 0 | 1 | 2 | 3 | 4;
|
||||||
|
onClose(): void;
|
||||||
|
}
|
||||||
|
|
|
@ -148,3 +148,84 @@ export function colorToHex(color: string) {
|
||||||
}
|
}
|
||||||
return color.replace("#", "");
|
return color.replace("#", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parseClr = (clr: number) => (clr & 0x00ffffff).toString(16).padStart(6, "0");
|
||||||
|
|
||||||
|
export async function getRepainterTheme(link: string): Promise<{ status: "success" | "fail", id?: string, colors?: string[], errorCode?: number, errorMsg?: string; }> {
|
||||||
|
const linkCheck: string | undefined = link.match(/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&//=]*)/g)!.filter(x => x.startsWith("https://repainter.app/themes/"))[0];
|
||||||
|
|
||||||
|
if (!linkCheck) return { status: "fail", errorCode: 0, errorMsg: "Invalid URL" };
|
||||||
|
|
||||||
|
// const res = await (
|
||||||
|
// await fetch(
|
||||||
|
// `https://repainter.app/_next/data/Z0BCpVYZyrdkss0k0zqLC/themes/${link.match(/themes\/([a-z0-9]+)/i)?.[1] ?? ""
|
||||||
|
// }.json`,
|
||||||
|
// {
|
||||||
|
// "headers": {
|
||||||
|
// "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||||
|
// "accept-language": "en-US,en;q=0.9",
|
||||||
|
// "if-none-match": "W/\"4b2-Wsw1gFTK1l04ijqMn5s6ZUnH6hM\"",
|
||||||
|
// "priority": "u=0, i",
|
||||||
|
// "sec-ch-ua": "\"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
|
||||||
|
// "sec-ch-ua-mobile": "?0",
|
||||||
|
// "sec-ch-ua-platform": "\"Linux\"",
|
||||||
|
// "sec-fetch-dest": "document",
|
||||||
|
// "sec-fetch-mode": "navigate",
|
||||||
|
// "sec-fetch-site": "none",
|
||||||
|
// "sec-fetch-user": "?1",
|
||||||
|
// "upgrade-insecure-requests": "1"
|
||||||
|
// },
|
||||||
|
// "referrerPolicy": "strict-origin-when-cross-origin",
|
||||||
|
// "body": null,
|
||||||
|
// "method": "GET",
|
||||||
|
// "mode": "cors",
|
||||||
|
// "credentials": "omit",
|
||||||
|
// "cache": "no-store"
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
const { pageProps: { fallback: { a: { name, colors } } } } = { "pageProps": { "initialId": "01G5PMR5G9H76H1R2RET4A0ZHY", "fallback": { a: { "id": "01G5PMR5G9H76H1R2RET4A0ZHY", "name": "Midwinter Fire", "description": "Very red", "createdAt": "2022-06-16T16:15:11.881Z", "updatedAt": "2022-07-12T08:37:13.141Z", "settingsLines": ["Colorful", "Bright", "Vibrant style"], "voteCount": 309, "colors": [-1426063361, 4294901760, 4294901760, -1426071591, -1426080078, -1426089335, 4294901760, -1426119398, -1428615936, -1431629312, -1434644480, 4294901760, 4294901760, 4294901760, 4294901760, -1426067223, -1426071086, -1426079070, -1426088082, 4294901760, -1428201216, -1430761216, -1433255936, 4294901760, 4294901760, 4294901760, 4294901760, 4294901760, 4294901760, -1426070330, 4294901760, -1426086346, 4294901760, -1430030080, 4294901760, -1434431744, 4294901760, 4294901760, 4294901760, 4294901760, -1426064133, 4294901760, -1426071591, 4294901760, -1426874223, 4294901760, -1430359452, 4294901760, -1433845194, 4294901760, -1437922816, 4294901760, 4294901760, 4294901760, 4294901760, -1426071591, -1426080078, -1426089335, -1427799438, -1429640356, 4294901760, -1433191891, 4294901760, 4294901760, 4294901760] } } }, "__N_SSP": true } as any;
|
||||||
|
return { status: "success", id: name, colors: colors.filter(c => c !== 4294901760).map(c => "#" + parseClr(c)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to choose a file from their system
|
||||||
|
* @param mimeTypes A comma separated list of mime types to accept, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
|
||||||
|
* @returns A promise that resolves to the chosen file or null if the user cancels
|
||||||
|
*/
|
||||||
|
export function chooseFile(mimeTypes: string) {
|
||||||
|
return new Promise<File | null>(resolve => {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.style.display = "none";
|
||||||
|
input.accept = mimeTypes;
|
||||||
|
input.onchange = async () => {
|
||||||
|
resolve(input.files?.[0] ?? null);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.click();
|
||||||
|
setImmediate(() => document.body.removeChild(input));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to save a file to their system
|
||||||
|
* @param file The file to save
|
||||||
|
*/
|
||||||
|
export function saveFile(file: File) {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL(file);
|
||||||
|
a.download = file.name;
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setImmediate(() => {
|
||||||
|
URL.revokeObjectURL(a.href);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classes(...classes: Array<string | null | undefined | false>) {
|
||||||
|
return classes.filter(Boolean).join(" ");
|
||||||
|
}
|
||||||
|
|
229
src/equicordplugins/discordColorways/wsClient.ts
Normal file
229
src/equicordplugins/discordColorways/wsClient.ts
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DataStore } from ".";
|
||||||
|
import { ColorwayCSS } from "./colorwaysAPI";
|
||||||
|
import { updateBoundKeyMain, updateWSMain } from "./components/MainModal";
|
||||||
|
import { updateActiveColorway, updateManagerRole, updateWS as updateWSSelector } from "./components/Selector";
|
||||||
|
import { nullColorwayObj } from "./constants";
|
||||||
|
import { generateCss } from "./css";
|
||||||
|
import { ColorwayObject } from "./types";
|
||||||
|
import { colorToHex } from "./utils";
|
||||||
|
|
||||||
|
export let wsOpen = false;
|
||||||
|
export let boundKey: { [managerKey: string]: string; } | null = null;
|
||||||
|
export let hasManagerRole: boolean = false;
|
||||||
|
|
||||||
|
export let sendColorway: (obj: ColorwayObject) => void = () => { };
|
||||||
|
export let requestManagerRole: () => void = () => { };
|
||||||
|
export let updateRemoteSources: () => void = () => { };
|
||||||
|
export let closeWS: () => void = () => { };
|
||||||
|
export let restartWS: () => void = () => connect();
|
||||||
|
export let updateShouldAutoconnect: (shouldAutoconnect: boolean) => void = () => connect();
|
||||||
|
|
||||||
|
function updateWS(status: boolean) {
|
||||||
|
updateWSSelector(status);
|
||||||
|
updateWSMain(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBoundKey(bound: { [managerKey: string]: string; }) {
|
||||||
|
updateBoundKeyMain(bound);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connect() {
|
||||||
|
var ws: WebSocket | null = new WebSocket("ws://localhost:6124");
|
||||||
|
|
||||||
|
updateShouldAutoconnect = shouldAutoconnect => {
|
||||||
|
if (shouldAutoconnect && ws?.readyState === ws?.CLOSED) connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onopen = function () {
|
||||||
|
wsOpen = true;
|
||||||
|
hasManagerRole = false;
|
||||||
|
updateWS(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
restartWS = () => {
|
||||||
|
ws?.close();
|
||||||
|
connect();
|
||||||
|
};
|
||||||
|
closeWS = () => ws?.close();
|
||||||
|
|
||||||
|
ws.onmessage = function (e) {
|
||||||
|
const data: {
|
||||||
|
type: "change-colorway" | "remove-colorway" | "manager-connection-established" | "complication:remote-sources:received" | "complication:remote-sources:update-request" | "complication:manager-role:granted" | "complication:manager-role:revoked",
|
||||||
|
[key: string]: any;
|
||||||
|
} = JSON.parse(e.data);
|
||||||
|
|
||||||
|
function typeSwitch(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "change-colorway":
|
||||||
|
if (data.active.id == null) {
|
||||||
|
DataStore.set("activeColorwayObject", nullColorwayObj);
|
||||||
|
ColorwayCSS.remove();
|
||||||
|
updateActiveColorway(nullColorwayObj);
|
||||||
|
} else {
|
||||||
|
const demandedColorway = generateCss(
|
||||||
|
colorToHex("#" + data.active.colors.primary || "#313338").replace("#", ""),
|
||||||
|
colorToHex("#" + data.active.colors.secondary || "#2b2d31").replace("#", ""),
|
||||||
|
colorToHex("#" + data.active.colors.tertiary || "#1e1f22").replace("#", ""),
|
||||||
|
colorToHex("#" + data.active.colors.accent || "#5865f2").replace("#", "")
|
||||||
|
);
|
||||||
|
ColorwayCSS.set(demandedColorway);
|
||||||
|
DataStore.set("activeColorwayObject", { ...data.active, css: demandedColorway });
|
||||||
|
updateActiveColorway({ ...data.active, css: demandedColorway });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case "remove-colorway":
|
||||||
|
DataStore.set("activeColorwayObject", nullColorwayObj);
|
||||||
|
ColorwayCSS.remove();
|
||||||
|
updateActiveColorway(nullColorwayObj);
|
||||||
|
return;
|
||||||
|
case "manager-connection-established":
|
||||||
|
DataStore.get("colorwaysBoundManagers").then((boundManagers: { [managerKey: string]: string; }[]) => {
|
||||||
|
if (data.MID) {
|
||||||
|
const boundSearch = boundManagers.filter(boundManager => {
|
||||||
|
if (Object.keys(boundManager)[0] === data.MID) return boundManager;
|
||||||
|
});
|
||||||
|
if (boundSearch.length) {
|
||||||
|
boundKey = boundSearch[0];
|
||||||
|
} else {
|
||||||
|
const id = { [data.MID]: `vencord.${Math.random().toString(16).slice(2)}.${new Date().getUTCMilliseconds()}` };
|
||||||
|
DataStore.set("colorwaysBoundManagers", [...boundManagers, id]);
|
||||||
|
boundKey = id;
|
||||||
|
}
|
||||||
|
updateBoundKey(typeof boundKey === "string" ? JSON.parse(boundKey) : boundKey);
|
||||||
|
ws?.send(JSON.stringify({
|
||||||
|
type: "client-sync-established",
|
||||||
|
boundKey,
|
||||||
|
complications: [
|
||||||
|
"remote-sources",
|
||||||
|
"manager-role"
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
DataStore.getMany([
|
||||||
|
"colorwaySourceFiles",
|
||||||
|
"customColorways"
|
||||||
|
]).then(([
|
||||||
|
colorwaySourceFiles,
|
||||||
|
customColorways
|
||||||
|
]) => {
|
||||||
|
ws?.send(JSON.stringify({
|
||||||
|
type: "complication:remote-sources:init",
|
||||||
|
boundKey,
|
||||||
|
online: colorwaySourceFiles,
|
||||||
|
offline: customColorways
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
sendColorway = obj => ws?.send(JSON.stringify({
|
||||||
|
type: "complication:manager-role:send-colorway",
|
||||||
|
active: obj,
|
||||||
|
boundKey
|
||||||
|
}));
|
||||||
|
requestManagerRole = () => ws?.send(JSON.stringify({
|
||||||
|
type: "complication:manager-role:request",
|
||||||
|
boundKey
|
||||||
|
}));
|
||||||
|
updateRemoteSources = () => DataStore.getMany([
|
||||||
|
"colorwaySourceFiles",
|
||||||
|
"customColorways"
|
||||||
|
]).then(([
|
||||||
|
colorwaySourceFiles,
|
||||||
|
customColorways
|
||||||
|
]) => {
|
||||||
|
ws?.send(JSON.stringify({
|
||||||
|
type: "complication:remote-sources:init",
|
||||||
|
boundKey,
|
||||||
|
online: colorwaySourceFiles,
|
||||||
|
offline: customColorways
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
case "complication:manager-role:granted":
|
||||||
|
hasManagerRole = true;
|
||||||
|
updateManagerRole(true);
|
||||||
|
return;
|
||||||
|
case "complication:manager-role:revoked":
|
||||||
|
hasManagerRole = false;
|
||||||
|
updateManagerRole(false);
|
||||||
|
return;
|
||||||
|
case "complication:remote-sources:update-request":
|
||||||
|
DataStore.getMany([
|
||||||
|
"colorwaySourceFiles",
|
||||||
|
"customColorways"
|
||||||
|
]).then(([
|
||||||
|
colorwaySourceFiles,
|
||||||
|
customColorways
|
||||||
|
]) => {
|
||||||
|
ws?.send(JSON.stringify({
|
||||||
|
type: "complication:remote-sources:init",
|
||||||
|
boundKey,
|
||||||
|
online: colorwaySourceFiles,
|
||||||
|
offline: customColorways
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeSwitch(data.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function (e) {
|
||||||
|
boundKey = null;
|
||||||
|
hasManagerRole = false;
|
||||||
|
sendColorway = () => { };
|
||||||
|
requestManagerRole = () => { };
|
||||||
|
updateRemoteSources = () => { };
|
||||||
|
restartWS = () => connect();
|
||||||
|
closeWS = () => { };
|
||||||
|
try {
|
||||||
|
ws?.close();
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ws = null;
|
||||||
|
wsOpen = false;
|
||||||
|
updateWS(false);
|
||||||
|
DataStore.getMany([
|
||||||
|
"colorwaysManagerAutoconnectPeriod",
|
||||||
|
"colorwaysManagerDoAutoconnect"
|
||||||
|
]).then(([
|
||||||
|
colorwaysManagerAutoconnectPeriod,
|
||||||
|
colorwaysManagerDoAutoconnect
|
||||||
|
]) => {
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
if (colorwaysManagerDoAutoconnect || true) setTimeout(() => connect(), colorwaysManagerAutoconnectPeriod || 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
boundKey = null;
|
||||||
|
sendColorway = () => { };
|
||||||
|
requestManagerRole = () => { };
|
||||||
|
updateRemoteSources = () => { };
|
||||||
|
restartWS = () => connect();
|
||||||
|
closeWS = () => { };
|
||||||
|
hasManagerRole = false;
|
||||||
|
ws?.close();
|
||||||
|
ws = null;
|
||||||
|
wsOpen = false;
|
||||||
|
updateWS(false);
|
||||||
|
DataStore.getMany([
|
||||||
|
"colorwaysManagerAutoconnectPeriod",
|
||||||
|
"colorwaysManagerDoAutoconnect"
|
||||||
|
]).then(([
|
||||||
|
colorwaysManagerAutoconnectPeriod,
|
||||||
|
colorwaysManagerDoAutoconnect
|
||||||
|
]) => {
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
if (colorwaysManagerDoAutoconnect || true) setTimeout(() => connect(), colorwaysManagerAutoconnectPeriod || 3000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -47,13 +47,13 @@ export default definePlugin({
|
||||||
RUNNING_GAMES_CHANGE(event) {
|
RUNNING_GAMES_CHANGE(event) {
|
||||||
const status = PresenceStore.getStatus(UserStore.getCurrentUser().id);
|
const status = PresenceStore.getStatus(UserStore.getCurrentUser().id);
|
||||||
if (event.games.length > 0) {
|
if (event.games.length > 0) {
|
||||||
if (savedStatus !== "" && savedStatus !== settings.store.statusToSet)
|
|
||||||
updateAsync(savedStatus);
|
|
||||||
} else {
|
|
||||||
if (status !== settings.store.statusToSet) {
|
if (status !== settings.store.statusToSet) {
|
||||||
savedStatus = status;
|
savedStatus = status;
|
||||||
updateAsync(settings.store.statusToSet);
|
updateAsync(settings.store.statusToSet);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (savedStatus !== "" && savedStatus !== settings.store.statusToSet)
|
||||||
|
updateAsync(savedStatus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue