/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ /* eslint-disable indent */ import "./styles.css"; import { generateId } from "@api/Commands"; import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { CodeBlock } from "@components/CodeBlock"; import { OpenExternalIcon } from "@components/Icons"; import { SettingsTab, wrapTab } from "@components/VencordSettings/shared"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; import { findByPropsLazy, findLazy } from "@webpack"; import { Button, Card, FluxDispatcher, Forms, React, Select, showToast, TabBar, TextArea, TextInput, Toasts, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; import { User } from "discord-types/general"; import { Constructor } from "type-fest"; import { SearchStatus, TabItem, Theme } from "../types"; import { ThemeInfoModal } from "./ThemeInfoModal"; const cl = classNameFactory("vc-plugins-"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const API_URL = "https://themes-delta.vercel.app/api"; async function fetchThemes(url: string): Promise { const response = await fetch(url); const data = await response.json(); const themes: Theme[] = Object.values(data); themes.forEach(theme => { if (!theme.source) { theme.source = `${API_URL}/${theme.name}`; } else { theme.source = theme.source.replace("?raw=true", "") + "?raw=true"; } }); return themes.sort((a, b) => new Date(b.release_date).getTime() - new Date(a.release_date).getTime()); } function API_TYPE(theme, returnAll?: boolean) { if (!theme) return; const settings = Settings.plugins.ThemeLibrary.domain ?? false; if (returnAll) { const url = settings ? "https://raw.githubusercontent.com/Faf4a/plugins/main/assets/meta.json" : `${API_URL}/themes`; return fetchThemes(url); } else { return settings ? theme.source : `${API_URL}/${theme.name}`; } } async function themeRequest(path: string, options: RequestInit = {}) { return fetch(API_URL + path, { ...options, headers: { ...options.headers, } }); } 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; } const themeTemplate = `/** * @name [Theme name] * @author [Your name] * @description [Your Theme Description] * @version [Your Theme Version] * @donate [Optionally, your Donation Link] * @tags [Optionally, tags that apply to your theme] * @invite [Optionally, your Support Server Invite] * @source [Optionally, your source code link] */ /* Your CSS goes here */ `; function ThemeTab() { const [themes, setThemes] = useState([]); const [filteredThemes, setFilteredThemes] = useState([]); const [themeLinks, setThemeLinks] = useState(Vencord.Settings.themeLinks); const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL }); const [loading, setLoading] = useState(true); const getUser = (id: string, username: string) => UserUtils.getUser(id) ?? makeDummyUser({ username, id }); const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query })); const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status })); const themeFilter = (theme: Theme) => { const enabled = themeLinks.includes(API_TYPE(theme)); if (enabled && searchValue.status === SearchStatus.DISABLED) return false; if (!theme.tags.includes("theme") && searchValue.status === SearchStatus.THEME) return false; if (!theme.tags.includes("snippet") && searchValue.status === SearchStatus.SNIPPET) return false; if (!theme.tags.includes("dark") && searchValue.status === SearchStatus.DARK) return false; if (!theme.tags.includes("light") && searchValue.status === SearchStatus.LIGHT) return false; if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (!searchValue.value.length) return true; const v = searchValue.value.toLowerCase(); return ( theme.name.toLowerCase().includes(v) || theme.description.toLowerCase().includes(v) || theme.author.discord_name.toLowerCase().includes(v) || theme.tags?.some(t => t.toLowerCase().includes(v)) ); }; useEffect(() => { Promise.resolve(API_TYPE({}, true)).then(themes => { setThemes(themes); setFilteredThemes(themes); setLoading(false); }).catch(err => { console.error("Failed to load 'ThemeLibrary'", err); setLoading(true); }); }, []); useEffect(() => { setThemeLinks(Vencord.Settings.themeLinks); }, [Vencord.Settings.themeLinks]); useEffect(() => { const filteredThemes = themes.filter(themeFilter); setFilteredThemes(filteredThemes); }, [searchValue]); return (
<> {loading ? (
Loading Themes...
) : (<>
Newest Additions {themes.slice(0, 2).map((theme: Theme) => ( {theme.name} {theme.description}
{theme.tags && ( {theme.tags.map(tag => ( {tag} ))} )}
{themeLinks.includes(API_TYPE(theme)) ? ( ) : ( )}
))}
Themes