diff --git a/src/equicordplugins/themeLibrary/components/ThemeInfoModal.tsx b/src/equicordplugins/themeLibrary/components/ThemeInfoModal.tsx index 1c424ee2..b0a2dbdb 100644 --- a/src/equicordplugins/themeLibrary/components/ThemeInfoModal.tsx +++ b/src/equicordplugins/themeLibrary/components/ThemeInfoModal.tsx @@ -12,10 +12,10 @@ import { classes } from "@utils/misc"; import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import type { PluginNative } from "@utils/types"; 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 { DownloadIcon } from "../utils/Icons"; +import { ClockIcon, DownloadIcon, WarningIcon } from "../utils/Icons"; import { logger } from "./LikesComponent"; const Native = VencordNative.pluginHelpers.ThemeLibrary as PluginNative; @@ -32,22 +32,23 @@ async function downloadTheme(themesDir: string, theme: Theme) { } export const ThemeInfoModal: React.FC = ({ 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 metadata = themeContent.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || ""; const donate = metadata.match(/@donate\s+(.+)/)?.[1] || ""; const version = metadata.match(/@version\s+(.+)/)?.[1] || ""; - + const invite = metadata.match(/@invite\s+(.+)/)?.[1] || ""; const authors = Array.isArray(author) ? author : [author]; + const lastUpdated = Math.floor(new Date(last_updated ?? 0).getTime() / 1000); + return ( {type} Details - {authors.length > 1 ? "Authors" : "Author"}
@@ -94,12 +95,14 @@ export const ThemeInfoModal: React.FC = ({ author, theme, . )} - {guild && ( + {(guild || invite) && ( <> Support Server - - {guild.name} - + {guild && ( + + {guild.name} + + )}
@@ -184,7 +198,6 @@ export const ThemeInfoModal: React.FC = ({ author, theme, . const validThemesDir = await Native.getThemesDir(themesDir, theme); // check if theme exists, and ask if they want to overwrite if (exists) { - showToast("A file with the same name already exists!", Toasts.Type.FAILURE); openModal(modalProps => ( diff --git a/src/equicordplugins/themeLibrary/components/ThemeTab.tsx b/src/equicordplugins/themeLibrary/components/ThemeTab.tsx index 310faf65..b1917b9f 100644 --- a/src/equicordplugins/themeLibrary/components/ThemeTab.tsx +++ b/src/equicordplugins/themeLibrary/components/ThemeTab.tsx @@ -17,9 +17,9 @@ import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; 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 { 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 { Constructor } from "type-fest"; @@ -108,22 +108,22 @@ function ThemeTab() { const themeFilter = (theme: Theme) => { 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; 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 (!searchValue.value.length) return true; - const v = searchValue.value.toLowerCase(); + const v = searchValue.value?.toLowerCase(); return ( - theme.name.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)) || + theme.name?.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)) || tags.has(v) ); }; @@ -232,7 +232,7 @@ function ThemeTab() { {theme.name} - {theme.description} + {Parser.parse(theme.description)}
{ 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 => ( + + + Hold on! + + + +
+

This theme requires the ThemeAttributes plugin to work properly!

+

+ Do you want to enable it? +

+
+
+
+ + + + +
+ )); + } else { + setThemeLinks(onlineThemeLinks); + Vencord.Settings.themeLinks = onlineThemeLinks; + } }} size={Button.Sizes.MEDIUM} color={Button.Colors.GREEN} @@ -364,7 +410,7 @@ function ThemeTab() { {theme.name} - {theme.description} + {Parser.parse(theme.description)} { 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 => ( + + + Hold on! + + + +
+

This theme requires the ThemeAttributes plugin to work properly!

+

+ Do you want to enable it? +

+
+
+
+ + + + +
+ )); + } else { + setThemeLinks(onlineThemeLinks); + Vencord.Settings.themeLinks = onlineThemeLinks; + } }} size={Button.Sizes.MEDIUM} color={Button.Colors.GREEN} diff --git a/src/equicordplugins/themeLibrary/components/styles.css b/src/equicordplugins/themeLibrary/components/styles.css index c72ab412..d03ef093 100644 --- a/src/equicordplugins/themeLibrary/components/styles.css +++ b/src/equicordplugins/themeLibrary/components/styles.css @@ -1,4 +1,3 @@ - [data-tab-id="ThemeLibrary"]::before { /* stylelint-disable-next-line property-no-vendor-prefix */ -webkit-mask: var(--si-widget) center/contain no-repeat !important; diff --git a/src/equicordplugins/themeLibrary/types.ts b/src/equicordplugins/themeLibrary/types.ts index 87c22c93..ebcbf584 100644 --- a/src/equicordplugins/themeLibrary/types.ts +++ b/src/equicordplugins/themeLibrary/types.ts @@ -24,13 +24,15 @@ export interface Theme { likes: number; tags: string[]; thumbnail_url: string; - release_date: string; + release_date: Date; + last_updated?: Date; guild?: { name: string; snowflake: string; invite_link: string; }; source?: string; + requiresThemeAttributes?: boolean; } export interface ThemeInfoModalProps extends ModalProps { diff --git a/src/equicordplugins/themeLibrary/utils/Icons.tsx b/src/equicordplugins/themeLibrary/utils/Icons.tsx index 791b92bb..2ef1cdad 100644 --- a/src/equicordplugins/themeLibrary/utils/Icons.tsx +++ b/src/equicordplugins/themeLibrary/utils/Icons.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -export const LikeIcon = (isLiked: boolean) => ( +export const LikeIcon = (isLiked: boolean = false) => ( ( ); -export const DownloadIcon = (props: any) => ( +export const DownloadIcon = (props?: any) => ( ); + +export const ClockIcon = (props?: any) => { + return ( + + + ); +}; + +export const WarningIcon = (props?: any) => { + return ( + + ); +};