From 506aab14c908e497d1ecc29f389712ce8d79810f Mon Sep 17 00:00:00 2001 From: MrDiamondDog <84212701+MrDiamondDog@users.noreply.github.com> Date: Thu, 20 Jun 2024 18:11:01 -0600 Subject: [PATCH] feat(themes): Online Themes Redesign --- src/api/Settings.ts | 2 + src/components/VencordSettings/ThemesTab.tsx | 136 +++++++++--------- .../VencordSettings/themesStyles.css | 9 ++ src/utils/quickCss.ts | 6 +- 4 files changed, 84 insertions(+), 69 deletions(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 70ba0bd4a..7fb5ae251 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -34,6 +34,7 @@ export interface Settings { useQuickCss: boolean; enableReactDevtools: boolean; themeLinks: string[]; + enabledThemeLinks: string[]; enabledThemes: string[]; frameless: boolean; transparent: boolean; @@ -81,6 +82,7 @@ const DefaultSettings: Settings = { autoUpdateNotification: true, useQuickCss: true, themeLinks: [], + enabledThemeLinks: [], enabledThemes: [], enableReactDevtools: false, frameless: false, diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 2eb91cb82..781bb01b9 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -22,15 +22,13 @@ import { Flex } from "@components/Flex"; import { DeleteIcon } from "@components/Icons"; import { Link } from "@components/Link"; import PluginModal from "@components/PluginSettings/PluginModal"; -import type { UserThemeHeader } from "@main/themes"; +import { getThemeInfo, type UserThemeHeader } from "@main/themes"; import { openInviteModal } from "@utils/discord"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; import { showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; import { findByPropsLazy, findLazy } from "@webpack"; -import { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; +import { Button, Card, Forms, React, showToast, TabBar, TextInput, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; import { AddonCard } from "./AddonCard"; @@ -49,13 +47,16 @@ const TextAreaProps = findLazy(m => typeof m.textarea === "string"); const cl = classNameFactory("vc-settings-theme-"); -function Validator({ link }: { link: string; }) { +function Validator({ link, onValidate }: { link: string; onValidate: (valid: boolean) => void; }) { const [res, err, pending] = useAwaiter(() => fetch(link).then(res => { if (res.status > 300) throw `${res.status} ${res.statusText}`; const contentType = res.headers.get("Content-Type"); - if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain")) + if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain")) { + onValidate(false); throw "Not a CSS file. Remember to use the raw link!"; + } + onValidate(true); return "Okay!"; })); @@ -70,41 +71,15 @@ function Validator({ link }: { link: string; }) { }}>{text}; } -function Validators({ themeLinks }: { themeLinks: string[]; }) { - if (!themeLinks.length) return null; - - return ( - <> - Validator - This section will tell you whether your themes can successfully be loaded -
- {themeLinks.map(link => ( - - - {link} - - - - ))} -
- - ); -} - interface ThemeCardProps { theme: UserThemeHeader; enabled: boolean; onChange: (enabled: boolean) => void; onDelete: () => void; + showDeleteButton?: boolean; } -function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) { +function ThemeCard({ theme, enabled, onChange, onDelete, showDeleteButton }: ThemeCardProps) { return ( @@ -146,16 +121,19 @@ enum ThemeTab { } function ThemesTab() { - const settings = useSettings(["themeLinks", "enabledThemes"]); + const settings = useSettings(["themeLinks", "enabledThemeLinks", "enabledThemes"]); const fileInputRef = useRef(null); const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); - const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); + const [currentThemeLink, setCurrentThemeLink] = useState(""); + const [themeLinkValid, setThemeLinkValid] = useState(false); const [userThemes, setUserThemes] = useState(null); + const [onlineThemes, setOnlineThemes] = useState<(UserThemeHeader & { link: string; })[] | null>(null); const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir); useEffect(() => { refreshLocalThemes(); + refreshOnlineThemes(); }, []); async function refreshLocalThemes() { @@ -198,7 +176,7 @@ function ThemesTab() { refreshLocalThemes(); } - function renderLocalThemes() { + function LocalThemes() { return ( <> @@ -288,37 +266,63 @@ function ThemesTab() { ); } - // When the user leaves the online theme textbox, update the settings - function onBlur() { - settings.themeLinks = [...new Set( - themeText - .trim() - .split(/\n+/) - .map(s => s.trim()) - .filter(Boolean) - )]; + function addThemeLink(link: string) { + if (!themeLinkValid) return; + if (settings.themeLinks.includes(link)) return; + + settings.themeLinks = [...settings.themeLinks, link]; + setCurrentThemeLink(""); + refreshOnlineThemes(); } - function renderOnlineThemes() { + async function refreshOnlineThemes() { + const themes = await Promise.all(settings.themeLinks.map(async link => { + const css = await fetch(link).then(res => res.text()); + return { ...getThemeInfo(css, link), link }; + })); + setOnlineThemes(themes); + } + + function onThemeLinkEnabledChange(link: string, enabled: boolean) { + if (enabled) { + if (settings.enabledThemeLinks.includes(link)) return; + settings.enabledThemeLinks = [...settings.enabledThemeLinks, link]; + } else { + settings.enabledThemeLinks = settings.enabledThemeLinks.filter(f => f !== link); + } + } + + function deleteThemeLink(link: string) { + settings.themeLinks = settings.themeLinks.filter(f => f !== link); + + refreshOnlineThemes(); + } + + function OnlineThemes() { return ( <> - - Paste links to css files here - One link per line - Make sure to use direct links to files (raw or github.io)! - - -