Equicord/src/equicordplugins/themeLibrary/components/ThemeCard.tsx
thororen1234 91d3a67657
Fixes
2025-07-10 19:40:07 -04:00

180 lines
8 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { generateId } from "@api/Commands";
import { Settings } from "@api/Settings";
import { OpenExternalIcon } from "@components/Icons";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { User } from "@vencord/discord-types";
import { Button, Card, FluxDispatcher, Forms, Parser, React, UserStore, UserUtils } from "@webpack/common";
import { Constructor } from "type-fest";
import type { Theme, ThemeLikeProps } from "../types";
import { LikesComponent } from "./LikesComponent";
import { ThemeInfoModal } from "./ThemeInfoModal";
import { apiUrl } from "./ThemeTab";
interface ThemeCardProps {
theme: Theme;
themeLinks: string[];
likedThemes?: ThemeLikeProps;
setThemeLinks: (links: string[]) => void;
removePreview?: boolean;
removeButtons?: boolean;
}
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
function makeDummyUser(user: { username: string; id?: string; avatar?: string; }) {
const newUser = new UserRecord({
username: user.username,
id: user.id ?? generateId(),
avatar: user.avatar,
bot: true,
});
FluxDispatcher.dispatch({
type: "USER_UPDATE",
user: newUser,
});
return newUser;
}
export const ThemeCard: React.FC<ThemeCardProps> = ({ theme, themeLinks, likedThemes, setThemeLinks, removeButtons, removePreview }) => {
const getUser = (id: string, username: string) => UserUtils.getUser(id) ?? makeDummyUser({ username, id });
const handleAddRemoveTheme = () => {
const onlineThemeLinks = themeLinks.includes(`${apiUrl}/${theme.id}`)
? themeLinks.filter(link => link !== `${apiUrl}/${theme.id}`)
: [...themeLinks, `${apiUrl}/${theme.id}`];
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
};
const handleThemeAttributesCheck = () => {
const requiresThemeAttributes = theme.requiresThemeAttributes ?? false;
if (requiresThemeAttributes && !Settings.plugins.ThemeAttributes.enabled) {
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<Forms.FormTitle tag="h4">Hold on!</Forms.FormTitle>
</ModalHeader>
<ModalContent>
<Forms.FormText style={{ padding: "8px" }}>
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
<p>Do you want to enable it?</p>
</Forms.FormText>
</ModalContent>
<ModalFooter>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
onClick={() => {
Settings.plugins.ThemeAttributes.enabled = true;
modalProps.onClose();
handleAddRemoveTheme();
}}
>
Enable Plugin
</Button>
<Button
color={Button.Colors.RED}
look={Button.Looks.FILLED}
className={Margins.right8}
onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
));
} else {
handleAddRemoveTheme();
}
};
const handleViewSource = () => {
const content = window.atob(theme.content);
const metadata = content.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || "";
const source = metadata.match(/@source\s+(.+)/)?.[1] || "";
if (source) {
VencordNative.native.openExternal(source);
} else {
VencordNative.native.openExternal(`${apiUrl}/${theme.id}`);
}
};
return (
<Card style={{ padding: ".5rem", marginBottom: ".5em", marginTop: ".5em", display: "flex", flexDirection: "column", backgroundColor: "var(--background-base-lower-alt)" }} key={theme.id}>
<Forms.FormTitle tag="h2" style={{ overflowWrap: "break-word", marginTop: 8 }} className="vce-theme-text">
{theme.name}
</Forms.FormTitle>
<Forms.FormText className="vce-theme-text">
{Parser.parse(theme.description)}
</Forms.FormText>
{!removePreview && (
<img role="presentation" src={theme.thumbnail_url} loading="lazy" alt={theme.name} className="vce-theme-info-preview" />
)}
<div className="vce-theme-info">
<div style={{ justifyContent: "flex-start", flexDirection: "column" }}>
{theme.tags && (
<Forms.FormText>
{theme.tags.map(tag => (
<span className="vce-theme-info-tag" key={tag}>
{tag}
</span>
))}
</Forms.FormText>
)}
{!removeButtons && (
<div style={{ marginTop: "8px", display: "flex", flexDirection: "row" }}>
<Button
onClick={handleThemeAttributesCheck}
size={Button.Sizes.MEDIUM}
color={themeLinks.includes(`${apiUrl}/${theme.id}`) ? Button.Colors.RED : Button.Colors.GREEN}
look={Button.Looks.FILLED}
className={Margins.right8}
disabled={!theme.content || theme.id === "preview"}
>
{themeLinks.includes(`${apiUrl}/${theme.id}`) ? "Remove Theme" : "Add Theme"}
</Button>
<Button
onClick={async () => {
const authors = Array.isArray(theme.author)
? await Promise.all(theme.author.map(author => getUser(author.discord_snowflake, author.discord_name)))
: [await getUser(theme.author.discord_snowflake, theme.author.discord_name)];
openModal(props => <ThemeInfoModal {...props} author={authors} theme={theme} />);
}}
size={Button.Sizes.MEDIUM}
color={Button.Colors.BRAND}
look={Button.Looks.FILLED}
>
Theme Info
</Button>
<LikesComponent themeId={theme.id} likedThemes={likedThemes} />
<Button
onClick={handleViewSource}
size={Button.Sizes.MEDIUM}
color={Button.Colors.LINK}
look={Button.Looks.LINK}
disabled={!theme.content || theme.id === "preview"}
style={{ display: "flex", alignItems: "center", justifyContent: "center" }}
>
View Source <OpenExternalIcon height={16} width={16} />
</Button>
</div>
)}
</div>
</div>
</Card >
);
};