mirror of
https://github.com/Equicord/Equicord.git
synced 2025-03-04 08:20:02 -05:00
Update ThemeLibrary
This commit is contained in:
parent
09c7176036
commit
9201d94404
5 changed files with 167 additions and 29 deletions
|
@ -12,10 +12,10 @@ import { classes } from "@utils/misc";
|
||||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import type { PluginNative } from "@utils/types";
|
import type { PluginNative } from "@utils/types";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clipboard, Forms, React, showToast, Toasts } from "@webpack/common";
|
import { Button, Clipboard, Forms, Parser, React, showToast, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
import { Theme, ThemeInfoModalProps } from "../types";
|
import { Theme, ThemeInfoModalProps } from "../types";
|
||||||
import { DownloadIcon } from "../utils/Icons";
|
import { ClockIcon, DownloadIcon, WarningIcon } from "../utils/Icons";
|
||||||
import { logger } from "./LikesComponent";
|
import { logger } from "./LikesComponent";
|
||||||
|
|
||||||
const Native = VencordNative.pluginHelpers.ThemeLibrary as PluginNative<typeof import("../native")>;
|
const Native = VencordNative.pluginHelpers.ThemeLibrary as PluginNative<typeof import("../native")>;
|
||||||
|
@ -32,22 +32,23 @@ async function downloadTheme(themesDir: string, theme: Theme) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, ...props }) => {
|
export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, ...props }) => {
|
||||||
const { type, content, likes, guild, tags } = theme;
|
const { type, content, likes, guild, tags, last_updated, requiresThemeAttributes } = theme;
|
||||||
|
|
||||||
const themeContent = window.atob(content);
|
const themeContent = window.atob(content);
|
||||||
const metadata = themeContent.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || "";
|
const metadata = themeContent.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || "";
|
||||||
const donate = metadata.match(/@donate\s+(.+)/)?.[1] || "";
|
const donate = metadata.match(/@donate\s+(.+)/)?.[1] || "";
|
||||||
const version = metadata.match(/@version\s+(.+)/)?.[1] || "";
|
const version = metadata.match(/@version\s+(.+)/)?.[1] || "";
|
||||||
|
const invite = metadata.match(/@invite\s+(.+)/)?.[1] || "";
|
||||||
|
|
||||||
const authors = Array.isArray(author) ? author : [author];
|
const authors = Array.isArray(author) ? author : [author];
|
||||||
|
|
||||||
|
const lastUpdated = Math.floor(new Date(last_updated ?? 0).getTime() / 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<Forms.FormTitle tag="h4">{type} Details</Forms.FormTitle>
|
<Forms.FormTitle tag="h4">{type} Details</Forms.FormTitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>{authors.length > 1 ? "Authors" : "Author"}</Forms.FormTitle>
|
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>{authors.length > 1 ? "Authors" : "Author"}</Forms.FormTitle>
|
||||||
<div style={{ display: "flex", alignItems: "center", marginBottom: "10px" }}>
|
<div style={{ display: "flex", alignItems: "center", marginBottom: "10px" }}>
|
||||||
|
@ -94,12 +95,14 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{guild && (
|
{(guild || invite) && (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Support Server</Forms.FormTitle>
|
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Support Server</Forms.FormTitle>
|
||||||
<Forms.FormText>
|
{guild && (
|
||||||
{guild.name}
|
<Forms.FormText>
|
||||||
</Forms.FormText>
|
{guild.name}
|
||||||
|
</Forms.FormText>
|
||||||
|
)}
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
<Button
|
<Button
|
||||||
color={Button.Colors.BRAND_NEW}
|
color={Button.Colors.BRAND_NEW}
|
||||||
|
@ -107,7 +110,8 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
||||||
className={Margins.top8}
|
className={Margins.top8}
|
||||||
onClick={async e => {
|
onClick={async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
guild.invite_link != null && openInviteModal(guild.invite_link.split("discord.gg/")[1]).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
|
const useInvite = guild ? guild.invite_link?.split("discord.gg/")[1] : invite;
|
||||||
|
useInvite != null && openInviteModal(useInvite).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Join Discord Server
|
Join Discord Server
|
||||||
|
@ -161,6 +165,16 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{requiresThemeAttributes && (
|
||||||
|
<Forms.FormText style={{ marginTop: "10px" }}>
|
||||||
|
<WarningIcon /> This theme requires the <b>ThemeAttributes</b> plugin!
|
||||||
|
</Forms.FormText>
|
||||||
|
)}
|
||||||
|
{last_updated && (
|
||||||
|
<Forms.FormText style={{ marginTop: "10px" }}>
|
||||||
|
<ClockIcon /> This theme was last updated {Parser.parse("<t:" + lastUpdated + ":F>")} ({Parser.parse("<t:" + lastUpdated + ":R>")})
|
||||||
|
</Forms.FormText>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
@ -184,7 +198,6 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
||||||
const validThemesDir = await Native.getThemesDir(themesDir, theme);
|
const validThemesDir = await Native.getThemesDir(themesDir, theme);
|
||||||
// check if theme exists, and ask if they want to overwrite
|
// check if theme exists, and ask if they want to overwrite
|
||||||
if (exists) {
|
if (exists) {
|
||||||
showToast("A file with the same name already exists!", Toasts.Type.FAILURE);
|
|
||||||
openModal(modalProps => (
|
openModal(modalProps => (
|
||||||
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
|
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
|
|
|
@ -17,9 +17,9 @@ import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, Card, FluxDispatcher, Forms, React, SearchableSelect, TabBar, TextArea, TextInput, Toasts, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
|
import { Button, Card, FluxDispatcher, Forms, Parser, React, SearchableSelect, TabBar, TextArea, TextInput, Toasts, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
|
@ -108,22 +108,22 @@ function ThemeTab() {
|
||||||
|
|
||||||
const themeFilter = (theme: Theme) => {
|
const themeFilter = (theme: Theme) => {
|
||||||
const enabled = themeLinks.includes(`${API_URL}/${theme.name}`);
|
const enabled = themeLinks.includes(`${API_URL}/${theme.name}`);
|
||||||
const tags = new Set(theme.tags.map(tag => tag.toLowerCase()));
|
const tags = new Set(theme.tags.map(tag => tag?.toLowerCase()));
|
||||||
|
|
||||||
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
||||||
|
|
||||||
const anyTags = SearchTags[searchValue.status];
|
const anyTags = SearchTags[searchValue.status];
|
||||||
if (anyTags && !tags.has(anyTags.toLowerCase())) return false;
|
if (anyTags && !tags.has(anyTags?.toLowerCase())) return false;
|
||||||
|
|
||||||
if ((enabled && searchValue.status === SearchStatus.DISABLED) || (!enabled && searchValue.status === SearchStatus.ENABLED)) return false;
|
if ((enabled && searchValue.status === SearchStatus.DISABLED) || (!enabled && searchValue.status === SearchStatus.ENABLED)) return false;
|
||||||
|
|
||||||
if (!searchValue.value.length) return true;
|
if (!searchValue.value.length) return true;
|
||||||
|
|
||||||
const v = searchValue.value.toLowerCase();
|
const v = searchValue.value?.toLowerCase();
|
||||||
return (
|
return (
|
||||||
theme.name.toLowerCase().includes(v) ||
|
theme.name?.toLowerCase().includes(v) ||
|
||||||
theme.description.toLowerCase().includes(v) ||
|
theme.description?.toLowerCase().includes(v) ||
|
||||||
(Array.isArray(theme.author) ? theme.author.some(author => author.discord_name.toLowerCase().includes(v)) : theme.author.discord_name.toLowerCase().includes(v)) ||
|
(Array.isArray(theme.author) ? theme.author.some(author => author.discord_name?.toLowerCase()?.includes(v)) : theme.author.discord_name?.toLowerCase()?.includes(v)) ||
|
||||||
tags.has(v)
|
tags.has(v)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -232,7 +232,7 @@ function ThemeTab() {
|
||||||
{theme.name}
|
{theme.name}
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<Forms.FormText className="vce-theme-text">
|
<Forms.FormText className="vce-theme-text">
|
||||||
{theme.description}
|
{Parser.parse(theme.description)}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<div className="vce-theme-info">
|
<div className="vce-theme-info">
|
||||||
<div style={{
|
<div style={{
|
||||||
|
@ -267,8 +267,54 @@ function ThemeTab() {
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
|
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
|
||||||
setThemeLinks(onlineThemeLinks);
|
|
||||||
Vencord.Settings.themeLinks = onlineThemeLinks;
|
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",
|
||||||
|
}}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
|
||||||
|
<p>
|
||||||
|
Do you want to enable it?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Forms.FormText>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
color={Button.Colors.GREEN}
|
||||||
|
onClick={async () => {
|
||||||
|
Settings.plugins.ThemeAttributes.enabled = true;
|
||||||
|
modalProps.onClose();
|
||||||
|
setThemeLinks(onlineThemeLinks);
|
||||||
|
Vencord.Settings.themeLinks = onlineThemeLinks;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable Plugin
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
className={Margins.right8} onClick={() => modalProps.onClose()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
setThemeLinks(onlineThemeLinks);
|
||||||
|
Vencord.Settings.themeLinks = onlineThemeLinks;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
size={Button.Sizes.MEDIUM}
|
size={Button.Sizes.MEDIUM}
|
||||||
color={Button.Colors.GREEN}
|
color={Button.Colors.GREEN}
|
||||||
|
@ -364,7 +410,7 @@ function ThemeTab() {
|
||||||
{theme.name}
|
{theme.name}
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<Forms.FormText className="vce-theme-text">
|
<Forms.FormText className="vce-theme-text">
|
||||||
{theme.description}
|
{Parser.parse(theme.description)}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -406,8 +452,54 @@ function ThemeTab() {
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
|
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
|
||||||
setThemeLinks(onlineThemeLinks);
|
|
||||||
Vencord.Settings.themeLinks = onlineThemeLinks;
|
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",
|
||||||
|
}}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
|
||||||
|
<p>
|
||||||
|
Do you want to enable it?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Forms.FormText>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
color={Button.Colors.GREEN}
|
||||||
|
onClick={async () => {
|
||||||
|
Settings.plugins.ThemeAttributes.enabled = true;
|
||||||
|
modalProps.onClose();
|
||||||
|
setThemeLinks(onlineThemeLinks);
|
||||||
|
Vencord.Settings.themeLinks = onlineThemeLinks;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable Plugin
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
look={Button.Looks.FILLED}
|
||||||
|
className={Margins.right8} onClick={() => modalProps.onClose()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
setThemeLinks(onlineThemeLinks);
|
||||||
|
Vencord.Settings.themeLinks = onlineThemeLinks;
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
size={Button.Sizes.MEDIUM}
|
size={Button.Sizes.MEDIUM}
|
||||||
color={Button.Colors.GREEN}
|
color={Button.Colors.GREEN}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
[data-tab-id="ThemeLibrary"]::before {
|
[data-tab-id="ThemeLibrary"]::before {
|
||||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||||
-webkit-mask: var(--si-widget) center/contain no-repeat !important;
|
-webkit-mask: var(--si-widget) center/contain no-repeat !important;
|
||||||
|
|
|
@ -24,13 +24,15 @@ export interface Theme {
|
||||||
likes: number;
|
likes: number;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
thumbnail_url: string;
|
thumbnail_url: string;
|
||||||
release_date: string;
|
release_date: Date;
|
||||||
|
last_updated?: Date;
|
||||||
guild?: {
|
guild?: {
|
||||||
name: string;
|
name: string;
|
||||||
snowflake: string;
|
snowflake: string;
|
||||||
invite_link: string;
|
invite_link: string;
|
||||||
};
|
};
|
||||||
source?: string;
|
source?: string;
|
||||||
|
requiresThemeAttributes?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThemeInfoModalProps extends ModalProps {
|
export interface ThemeInfoModalProps extends ModalProps {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const LikeIcon = (isLiked: boolean) => (
|
export const LikeIcon = (isLiked: boolean = false) => (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill={isLiked ? "red" : "currentColor"}
|
fill={isLiked ? "red" : "currentColor"}
|
||||||
|
@ -17,7 +17,7 @@ export const LikeIcon = (isLiked: boolean) => (
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const DownloadIcon = (props: any) => (
|
export const DownloadIcon = (props?: any) => (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
|
@ -30,3 +30,35 @@ export const DownloadIcon = (props: any) => (
|
||||||
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path>
|
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ClockIcon = (props?: any) => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path fill-rule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm1-18a1 1 0 1 0-2 0v7c0 .27.1.52.3.7l3 3a1 1 0 0 0 1.4-1.4L13 11.58V5Z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WarningIcon = (props?: any) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
fill="currentColor"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" fill="transparent"></circle>
|
||||||
|
<path fill-rule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm1.44-15.94L13.06 14a1.06 1.06 0 0 1-2.12 0l-.38-6.94a1 1 0 0 1 1-1.06h.88a1 1 0 0 1 1 1.06Zm-.19 10.69a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue