Revert to vencord fakeprofilethemes

This commit is contained in:
thororen1234 2024-05-22 09:31:25 -04:00
parent 736c0856f5
commit 7d919d3d62
5 changed files with 0 additions and 902 deletions

View file

@ -1,62 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Text, Tooltip } from "@webpack/common";
import type { CSSProperties } from "react";
interface BuilderButtonProps {
label?: string;
tooltip?: string;
selectedStyle?: CSSProperties;
onClick?: () => void;
}
export function BuilderButton({ label, tooltip, selectedStyle, onClick }: BuilderButtonProps) {
return (
<Tooltip text={tooltip} shouldShow={!!tooltip}>
{({ onMouseLeave, onMouseEnter }) => (
<div style={{ width: "60px" }}>
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
role="button"
tabIndex={0}
style={{
...selectedStyle || { border: "2px dashed var(--header-secondary)" },
borderRadius: "4px",
cursor: "pointer",
display: "grid",
height: "60px",
placeItems: "center"
}}
onClick={onClick}
>
{!selectedStyle && (
<svg
fill="var(--header-secondary)"
width="40%"
height="40%"
viewBox="0 0 144 144"
>
<path d="M144 64H80V0H64v64H0v16h64v64h16V80h64Z" />
</svg>
)}
</div>
{!!label && (
<Text
color="header-secondary"
variant="text-xs/normal"
tag="div"
style={{ textAlign: "center" }}
>
{label}
</Text>
)}
</div>
)}
</Tooltip>
);
}

View file

@ -1,95 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Button, Flex, Text, useRef, useState } from "@webpack/common";
import type { ColorPicker } from "../types";
interface ColorPickerModalProps {
modalProps: ModalProps;
ColorPicker: ColorPicker;
onClose: () => void;
onSubmit: (v: number) => void;
initialColor: number;
suggestedColors: string[];
}
export function ColorPickerModal({ modalProps, ColorPicker, onClose, onSubmit, initialColor = 0, suggestedColors = [] }: ColorPickerModalProps) {
const [color, setColor] = useState(initialColor);
const [pos, setPos] = useState<[number, number]>([-1, -1]);
const header = useRef<HTMLDivElement>(null);
return (
<div
style={{
position: pos[0] === -1 || pos[1] === -1 ? "revert" : "fixed",
left: `clamp(0px, ${pos[0]}px, calc(100vw - ${header.current?.getBoundingClientRect().width ?? 0}px))`,
top: `clamp(22px, ${pos[1]}px, calc(100vh - ${header.current?.getBoundingClientRect().height ?? 0}px))`
}}
>
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
<style>{":has(>:not([class*=hidden__]) [class*=customColorPicker__])>[class*=backdrop__]{display:none!important}[class*=root_] [class*=customColorPicker__]{border:none!important;box-shadow:none!important}"}</style>
<div
ref={header}
style={{ cursor: "move" }}
onMouseDown={e => {
const ref = header.current;
if (ref === null) return;
const rect = ref.getBoundingClientRect();
const offsetX = e.pageX - rect.left;
const offsetY = e.pageY - rect.top;
const onDrag = (e: MouseEvent) => setPos([e.pageX - offsetX, e.pageY - offsetY]);
document.addEventListener("mousemove", onDrag);
document.addEventListener("mouseup",
() => { document.removeEventListener("mousemove", onDrag); },
{ once: true }
);
}}
>
<ModalHeader justify={Flex.Justify.BETWEEN}>
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
Color Picker
</Text>
<div onMouseDown={e => e.stopPropagation()}>
<ModalCloseButton onClick={onClose} />
</div>
</ModalHeader>
</div>
<ColorPicker
value={color}
showEyeDropper={true}
suggestedColors={suggestedColors}
onChange={(e: number) => setColor(e)}
/>
<ModalFooter>
<Button onClick={() => onSubmit(color)}>
Apply
</Button>
</ModalFooter>
</ModalRoot>
</div>
);
}
export function openColorPickerModal(
ColorPicker: ColorPicker,
onSubmit: (v: number) => void,
initialColor: number = 0,
suggestedColors: string[] = []
) {
const key = openModal(modalProps =>
<ColorPickerModal
modalProps={modalProps}
ColorPicker={ColorPicker}
onClose={() => closeModal(key)}
onSubmit={onSubmit}
initialColor={initialColor}
suggestedColors={suggestedColors}
/>
);
return key;
}

View file

@ -1,103 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Button, Flex, showToast, Text, useState } from "@webpack/common";
import type { ProfileEffect } from "../types";
interface ProfileEffectModalProps {
modalProps: ModalProps;
onClose: () => void;
onSubmit: (v: ProfileEffect) => void;
classNames: { [k: string]: string; };
profileEffects: ProfileEffect[];
initialEffectID?: string;
}
export function ProfileEffectModal({ modalProps, onClose, onSubmit, profileEffects, classNames = {}, initialEffectID }: ProfileEffectModalProps) {
const [selected, setSelected] = useState(initialEffectID ? profileEffects.findIndex(e => e.id === initialEffectID) : -1);
return (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader justify={Flex.Justify.BETWEEN}>
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
Add Profile Effect
</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent
paddingFix={false}
style={{
display: "flex",
flexWrap: "wrap",
gap: "12px",
justifyContent: "center",
padding: "16px 8px 16px 16px"
}}
>
{profileEffects.map((e, i) => (
<div
className={classNames.effectGridItem + (i === selected ? " " + classNames.selected : "")}
role="button"
tabIndex={0}
style={{ width: "80px", height: "80px" }}
onClick={() => setSelected(i)}
>
<img
className={classNames.presetEffectBackground}
src="/assets/f328a6f8209d4f1f5022.png"
alt={e.accessibilityLabel}
/>
<img
className={classNames.presetEffectImg}
src={e.thumbnailPreviewSrc}
alt={e.title}
/>
</div>
))}
</ModalContent>
<ModalFooter
justify={Flex.Justify.BETWEEN}
direction={Flex.Direction.HORIZONTAL}
align={Flex.Align.CENTER}
>
<Text color="header-primary" variant="heading-lg/semibold" tag="h1">
{selected === -1 ? "" : profileEffects[selected].title}
</Text>
<Button
onClick={() => {
if (selected !== -1)
onSubmit(profileEffects[selected]);
else
showToast("No effect selected!");
}}
>
Apply
</Button>
</ModalFooter>
</ModalRoot>
);
}
export function openProfileEffectModal(
onSubmit: (v: ProfileEffect) => void,
profileEffects: ProfileEffect[],
classNames: { [k: string]: string; } = {},
initialEffectID?: string
) {
const key = openModal(modalProps =>
<ProfileEffectModal
modalProps={modalProps}
onClose={() => closeModal(key)}
onSubmit={onSubmit}
profileEffects={profileEffects}
classNames={classNames}
initialEffectID={initialEffectID}
/>
);
return key;
}

View file

@ -1,574 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Button, FluxDispatcher, Forms, RestAPI, showToast, Switch, Toasts, useEffect, useRef, UserStore, useState } from "@webpack/common";
import { BuilderButton } from "./components/BuilderButton";
import { openColorPickerModal } from "./components/ColorPickerModal";
import { openProfileEffectModal } from "./components/ProfileEffectModal";
import type { ColorPicker, CustomizationSection, ProfileEffect, RGBColor, UserProfile } from "./types";
let CustomizationSection: CustomizationSection = () => null;
let ColorPicker: ColorPicker = () => null;
let getPaletteForAvatar = (v: string) => Promise.resolve<RGBColor[]>([]);
let getComplimentaryPaletteForColor = (v: RGBColor): RGBColor[] => [];
const profileEffectModalClassNames: { [k: string]: string; } = {};
let [primaryColor, setPrimaryColor] = [-1, (v: number) => { }];
let [accentColor, setAccentColor] = [-1, (v: number) => { }];
let [effect, setEffect]: [ProfileEffect | null, (v: ProfileEffect | null) => void] = [null, () => { }];
let [preview, setPreview] = [true, (v: boolean) => { }];
/**
* Builds a profile theme color string in the legacy format, [#primary,#accent] where
* primary and accent are base-16 24-bit colors, with each code point offset by +0xE0000
* @param primary The base-10 24-bit primary color to be encoded
* @param accent The base-10 24-bit accent color to be encoded
* @returns The legacy encoded profile theme color string
*/
function encodeColorsLegacy(primary: number, accent: number) {
return String.fromCodePoint(...[...`[#${primary.toString(16)},#${accent.toString(16)}]`]
.map(c => c.codePointAt(0)! + 0xE0000));
}
/**
* Extracts profile theme colors from given legacy-format string
* @param str The legacy-format string to extract profile theme colors from
* @returns The profile theme colors. Colors will be -1 if not found.
*/
function decodeColorsLegacy(str: string): [number, number] {
const colors = str.matchAll(/(?<=#)[\dA-Fa-f]{1,6}/g);
return [parseInt(colors.next().value?.[0], 16) || -1, parseInt(colors.next().value?.[0], 16) || -1];
}
/**
* Converts the given base-10 24-bit color to a base-4096 string with each code point offset by +0xE0000
* @param color The base-10 24-bit color to be converted
* @returns The converted base-4096 string with +0xE0000 offset
*/
function encodeColor(color: number) {
if (color === 0) return "\u{e0000}";
let str = "";
for (; color > 0; color = Math.trunc(color / 4096))
str = String.fromCodePoint(color % 4096 + 0xE0000) + str;
return str;
}
/**
* Converts the given no-offset base-4096 string to a base-10 24-bit color
* @param str The no-offset base-4096 string to be converted
* @returns The converted base-10 24-bit color
* Will be -1 if the given string is empty and -2 if greater than the maximum 24-bit color, 16,777,215
*/
function decodeColor(str: string) {
if (str === "") return -1;
let color = 0;
for (let i = 0; i < str.length; i++) {
if (color > 16_777_215) return -2;
color += str.codePointAt(i)! * 4096 ** (str.length - 1 - i);
}
return color;
}
/**
* Converts the given base-10 profile effect ID to a base-4096 string with each code point offset by +0xE0000
* @param id The base-10 profile effect ID to be converted
* @returns The converted base-4096 string with +0xE0000 offset
*/
function encodeEffect(id: bigint) {
if (id === 0n) return "\u{e0000}";
let str = "";
for (; id > 0n; id /= 4096n)
str = String.fromCodePoint(Number(id % 4096n) + 0xE0000) + str;
return str;
}
/**
* Converts the given no-offset base-4096 string to a base-10 profile effect ID
* @param str The no-offset base-4096 string to be converted
* @returns The converted base-10 profile effect ID
* Will be -1n if the given string is empty and -2n if greater than the maximum profile effect ID, 1.2 quintillion
*/
function decodeEffect(str: string) {
if (str === "") return -1n;
let id = 0n;
for (let i = 0; i < str.length; i++) {
if (id > 1_200_000_000_000_000_000n) return -2n;
id += BigInt(str.codePointAt(i)!) * 4096n ** BigInt(str.length - 1 - i);
}
return id;
}
/**
* Builds a FPTE string containing the given primary / accent colors and effect ID. If the FPTE Builder is NOT set to
* backwards compatibility mode, the primary and accent colors will be converted to base-4096 before they are encoded.
* @param primary The primary profile theme color. Must be -1 if unset.
* @param accent The accent profile theme color. Must be -1 if unset.
* @param effect The profile effect ID. Must be empty if unset.
* @param legacy Whether the primary and accent colors should be legacy encoded
* @returns The built FPTE string. Will be empty if the given colors and effect are all unset.
*/
function buildFPTE(primary: number, accent: number, effect: string, legacy: boolean) {
const DELIM = "\u200b"; // The FPTE delimiter (zero-width space)
let fpte = ""; // The FPTE string to be returned
// If the FPTE Builder is set to backwards compatibility mode,
// the primary and accent colors, if set, will be legacy encoded.
if (legacy) {
// Legacy FPTE strings must include both the primary and accent colors even if they are the same.
if (primary !== -1) {
// If both the primary and accent colors are set, they will be legacy encoded and added to the
// string; otherwise, if the accent color is unset, the primary color will be used in its place.
if (accent !== -1)
fpte = encodeColorsLegacy(primary, accent);
else
fpte = encodeColorsLegacy(primary, primary);
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
if (effect !== "")
fpte += DELIM + encodeEffect(BigInt(effect));
return fpte;
}
// Since the primary color is unset, the accent color, if set, will be used in its place.
if (accent !== -1) {
fpte = encodeColorsLegacy(accent, accent);
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
if (effect !== "")
fpte += DELIM + encodeEffect(BigInt(effect));
return fpte;
}
}
// If the primary color is set, it will be encoded and added to the string.
else if (primary !== -1) {
fpte = encodeColor(primary);
// If the accent color is set and different from the primary color, it
// will be encoded and added to the string prefixed by one delimiter.
if (accent !== -1 && primary !== accent) {
fpte += DELIM + encodeColor(accent);
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
if (effect !== "")
fpte += DELIM + encodeEffect(BigInt(effect));
return fpte;
}
}
// If only the accent color is set, it will be encoded and added to the string.
else if (accent !== -1)
fpte = encodeColor(accent);
// Since either the primary / accent colors are the same, both are unset, or just one is set, only one color will be added
// to the string; therefore, the effect ID, if set, will be encoded and added to the string prefixed by two delimiters.
if (effect !== "")
fpte += DELIM + DELIM + encodeEffect(BigInt(effect));
return fpte;
}
/**
* Extracts the delimiter-separated values of the first FPTE string found in the given string
* @param str The string to be searched for a FPTE string
* @returns An array of the extracted FPTE string's values. Values will be empty if not found.
*/
function extractFPTE(str: string) {
const fpte: [string, string, string] = ["", "", ""]; // The array containing extracted FPTE values
let i = 0; // The current index of fpte getting extracted
for (const char of str) {
const cp = char.codePointAt(0)!; // The current character's code point
// If the current character is a delimiter, then the current index of fpte has been completed.
if (cp === 0x200B) {
// If the current index of fpte is the last, then the extraction is done.
if (i >= 2) break;
i++; // Start extracting the next index of fpte
}
// If the current character is not a delimiter but a valid FPTE
// character, it will be added to the current index of fpte.
else if (cp >= 0xE0000 && cp <= 0xE0FFF)
fpte[i] += String.fromCodePoint(cp - 0xE0000);
// If an FPTE string has been found and its end has been reached, then the extraction is done.
else if (i > 0 || fpte[0] !== "") break;
}
return fpte;
}
/**
* Converts the given RGB color to a hexadecimal string
* @param rgb The RGB color to be converted
* @returns The converted hexadecimal string
* @example
* // returns #ff0000
* RGBtoHex([255, 0, 0])
*/
function RGBtoHex(rgb: RGBColor) {
return "#" + ((rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).padStart(6, "0");
}
function getSuggestedColors(callback: (v: string[]) => void) {
const user = UserStore.getCurrentUser();
getPaletteForAvatar(`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp?size=80`)
.then(avatarColors => {
callback([
...avatarColors.slice(0, 2),
...getComplimentaryPaletteForColor(avatarColors[0]).slice(0, 3)
].map(e => RGBtoHex(e)));
})
.catch(e => {
console.error(e);
showToast("Unable to retrieve suggested colors.", Toasts.Type.FAILURE);
callback([]);
});
}
function fetchProfileEffects(callback: (v: ProfileEffect[]) => void) {
RestAPI.get({ url: "/user-profile-effects" })
.then(res => callback(res.body.profile_effect_configs))
.catch(e => {
console.error(e);
showToast("Unable to retrieve the list of profile effects.", Toasts.Type.FAILURE);
});
}
function updateUserThemeColors(user: UserProfile, primary: number, accent: number) {
if (primary > -1) {
user.themeColors = [primary, accent > -1 ? accent : primary];
user.premiumType = 2;
} else if (accent > -1) {
user.themeColors = [accent, accent];
user.premiumType = 2;
}
}
function updateUserEffectId(user: UserProfile, id: bigint) {
if (id > -1n) {
user.profileEffectId = id.toString();
user.premiumType = 2;
}
}
function updatePreview() {
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" });
}
const settings = definePluginSettings({
prioritizeNitro: {
description: "Source to use if profile theme colors / effects are set by both Nitro and About Me",
type: OptionType.SELECT,
options: [
{ label: "Nitro", value: true },
{ label: "About Me", value: false, default: true },
]
},
hideBuilder: {
description: "Hide the FPTE Builder in the profiles settings page",
type: OptionType.BOOLEAN,
default: false
}
});
export default definePlugin({
name: "FakeProfileThemes",
description: "Allows profile theming and the usage of profile effects by hiding the colors and effect ID in your About Me using invisible, zero-width characters",
authors: [EquicordDevs.ryan],
patches: [
{
find: '"UserProfileStore"',
replacement: {
match: /(?<=getUserProfile\(\i\){return )\i\[\i](?=})/,
replace: "$self.decodeUserBioFPTEHook($&)"
}
},
{
find: '"DefaultCustomizationSections"',
replacement: {
match: /\.sectionsContainer,children:\[/,
replace: "$&$self.addFPTEBuilder(),"
}
},
{
find: ".customizationSectionBackground",
replacement: {
match: /default:function\(\){return (\i)}.*?;/,
replace: "$&$self.CustomizationSection=$1;"
}
},
{
find: "CustomColorPicker:function(){",
replacement: {
match: /CustomColorPicker:function\(\){return (\i)}.*? \1=(?=[^=])/,
replace: "$&$self.ColorPicker="
}
},
{
find: "getPaletteForAvatar:function(){",
replacement: {
match: /getPaletteForAvatar:function\(\){return (\i)}.*? \1=(?=[^=])/,
replace: "$&$self.getPaletteForAvatar="
}
},
{
find: "getComplimentaryPaletteForColor:function(){",
replacement: {
match: /getComplimentaryPaletteForColor:function\(\){return (\i)}.*?;/,
replace: "$&$self.getComplimentaryPaletteForColor=$1;"
}
},
{
find: 'effectGridItem:"',
noWarn: true,
replacement: {
match: /(\i):"(.+?)"/g,
replace: (m, k, v) => { profileEffectModalClassNames[k] = v; return m; }
}
},
{
find: '"ProfileCustomizationPreview"',
replacement: {
match: /let{(?=(?:[^}]+,)?pendingThemeColors:)(?=(?:[^}]+,)?pendingProfileEffectId:)[^}]+}=(\i)[,;]/,
replace: "$self.profilePreviewHook($1);$&"
}
}
],
set CustomizationSection(c: CustomizationSection) {
CustomizationSection = c;
},
set ColorPicker(c: ColorPicker) {
ColorPicker = c;
},
set getPaletteForAvatar(f: (v: string) => Promise<RGBColor[]>) {
getPaletteForAvatar = f;
},
set getComplimentaryPaletteForColor(f: (v: RGBColor) => RGBColor[]) {
getComplimentaryPaletteForColor = f;
},
settingsAboutComponent: () => {
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText>
After enabling this plugin, you will see custom theme colors and effects in the profiles of other people using this plugin.
<div className={Margins.top8}>
<b>To set your own profile theme colors and effect:</b>
</div>
<ol
className={Margins.bottom8}
style={{ listStyle: "decimal", paddingLeft: "40px" }}
>
<li>Go to your profile settings</li>
<li>Use the FPTE Builder to choose your profile theme colors and effect</li>
<li>Click the "Copy FPTE" button</li>
<li>Paste the invisible text anywhere in your About Me</li>
</ol>
</Forms.FormText>
</Forms.FormSection>
);
},
settings,
decodeUserBioFPTEHook(user: UserProfile | undefined) {
if (user === undefined) return user;
if (settings.store.prioritizeNitro) {
if (user.themeColors !== undefined) {
if (user.profileEffectId === undefined) {
const fpte = extractFPTE(user.bio);
if (decodeColor(fpte[0]) === -2)
updateUserEffectId(user, decodeEffect(fpte[1]));
else
updateUserEffectId(user, decodeEffect(fpte[2]));
}
return user;
} else if (user.profileEffectId !== undefined) {
const fpte = extractFPTE(user.bio);
const primaryColor = decodeColor(fpte[0]);
if (primaryColor === -2)
updateUserThemeColors(user, ...decodeColorsLegacy(fpte[0]));
else
updateUserThemeColors(user, primaryColor, decodeColor(fpte[1]));
return user;
}
}
const fpte = extractFPTE(user.bio);
const primaryColor = decodeColor(fpte[0]);
if (primaryColor === -2) {
updateUserThemeColors(user, ...decodeColorsLegacy(fpte[0]));
updateUserEffectId(user, decodeEffect(fpte[1]));
} else {
updateUserThemeColors(user, primaryColor, decodeColor(fpte[1]));
updateUserEffectId(user, decodeEffect(fpte[2]));
}
return user;
},
profilePreviewHook(props: any) {
if (preview) {
if (primaryColor !== -1) {
props.pendingThemeColors = [primaryColor, accentColor === -1 ? primaryColor : accentColor];
props.canUsePremiumCustomization = true;
} else if (accentColor !== -1) {
props.pendingThemeColors = [accentColor, accentColor];
props.canUsePremiumCustomization = true;
}
if (effect) {
props.pendingProfileEffectId = effect.id;
props.canUsePremiumCustomization = true;
}
}
},
addFPTEBuilder() {
if (settings.store.hideBuilder) return null;
[primaryColor, setPrimaryColor] = useState(-1);
[accentColor, setAccentColor] = useState(-1);
[effect, setEffect] = useState<ProfileEffect | null>(null);
[preview, setPreview] = useState(true);
const [buildLegacy, setBuildLegacy] = useState(false);
const currModal = useRef("");
useEffect(() => () => closeModal(currModal.current), []);
return (
<>
<CustomizationSection title="FPTE Builder">
<div style={{ display: "flex", justifyContent: "space-between" }}>
<BuilderButton
label="Primary"
{...primaryColor !== -1 ? (c => ({
tooltip: c,
selectedStyle: { background: c }
}))("#" + primaryColor.toString(16).padStart(6, "0")) : {}}
onClick={() => {
getSuggestedColors(colors => {
closeModal(currModal.current);
currModal.current = openColorPickerModal(
ColorPicker,
c => {
setPrimaryColor(c);
if (preview) updatePreview();
},
primaryColor === -1 ? parseInt(colors[0]?.slice(1), 16) || 0 : primaryColor,
colors
);
});
}}
/>
<BuilderButton
label="Accent"
{...accentColor !== -1 ? (c => ({
tooltip: c,
selectedStyle: { background: c }
}))("#" + accentColor.toString(16).padStart(6, "0")) : {}}
onClick={() => {
getSuggestedColors(colors => {
closeModal(currModal.current);
currModal.current = openColorPickerModal(
ColorPicker,
c => {
setAccentColor(c);
if (preview) updatePreview();
},
accentColor === -1 ? parseInt(colors[1]?.slice(1), 16) || 0 : accentColor,
colors
);
});
}}
/>
<BuilderButton
label="Effect"
{...effect && {
tooltip: effect.title,
selectedStyle: {
background: `top / cover url(${effect.thumbnailPreviewSrc}), top / cover url(/assets/f328a6f8209d4f1f5022.png)`
}
}}
onClick={() => {
fetchProfileEffects(effects => {
if (effects) {
closeModal(currModal.current);
currModal.current = openProfileEffectModal(
e => {
setEffect(e);
if (preview) updatePreview();
},
effects,
profileEffectModalClassNames,
effect?.id
);
} else
showToast("The retrieved data did not match the expected format.", Toasts.Type.FAILURE);
});
}}
/>
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: "column",
}}
>
<Button
size={Button.Sizes.SMALL}
onClick={() => {
const strToCopy = buildFPTE(primaryColor, accentColor, effect?.id ?? "", buildLegacy);
if (strToCopy === "")
showToast("FPTE Builder is empty; nothing to copy!");
else
copyWithToast(strToCopy, "FPTE copied to clipboard!");
}}
>
Copy FPTE
</Button>
<Button
look={Button.Looks.LINK}
color={Button.Colors.PRIMARY}
size={Button.Sizes.SMALL}
style={{ display: primaryColor === -1 && accentColor === -1 && !effect ? "none" : "revert" }}
onClick={() => {
setPrimaryColor(-1);
setAccentColor(-1);
setEffect(null);
if (preview) updatePreview();
}}
>
Reset
</Button>
</div>
</div>
</CustomizationSection>
<Switch
value={preview}
onChange={value => {
setPreview(value);
updatePreview();
}}
>
FPTE Builder Preview
</Switch>
<Switch
value={buildLegacy}
note="Will use more characters"
onChange={value => setBuildLegacy(value)}
>
Build backwards compatible FPTE
</Switch>
</>
);
}
});

View file

@ -1,68 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import type { User } from "discord-types/general";
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
export interface UserProfile extends User {
themeColors: [number, number] | undefined;
profileEffectId: string | undefined;
}
export interface ProfileEffect {
accessibilityLabel: string;
animationType: number;
description: string;
effects: {
duartion: number;
height: number;
loop: boolean;
loopDelay: number;
position: {
x: number;
y: number;
};
src: string;
start: number;
width: number;
zIndex: number;
}[];
id: string;
reducedMotionSrc: string;
sku_id: string;
staticFrameSrc?: string;
thumbnailPreviewSrc: string;
title: string;
type: number;
}
export type CustomizationSection = ComponentType<PropsWithChildren<{
title?: ReactNode;
titleIcon?: ReactNode;
titleId?: string;
description?: ReactNode;
className?: string;
errors?: string[];
disabled?: boolean;
hideDivider?: boolean;
showBorder?: boolean;
borderType?: "limited" | "premium";
hasBackground?: boolean;
forcedDivider?: boolean;
showPremiumIcon?: boolean;
}>>;
export type ColorPicker = ComponentType<{
value?: number | null;
onChange: (v: number) => void;
onClose?: () => void;
suggestedColors?: string[];
middle?: ReactNode;
footer?: ReactNode;
showEyeDropper?: boolean;
}>;
export type RGBColor = [number, number, number];