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)!
-
-
-
-
+
+ Make sure to use direct links to files (raw or github.io)!
+
+
+
+
+ {currentThemeLink && }
+
+
+
+ {onlineThemes?.map(theme => {
+ return onThemeLinkEnabledChange(theme.link, enabled)}
+ onDelete={() => deleteThemeLink(theme.link)}
+ showDeleteButton
+ theme={theme}
+ />;
+ })}
+
>
);
@@ -347,8 +351,8 @@ function ThemesTab() {
- {currentTab === ThemeTab.LOCAL && renderLocalThemes()}
- {currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
+ {currentTab === ThemeTab.LOCAL && }
+ {currentTab === ThemeTab.ONLINE && }
);
}
diff --git a/src/components/VencordSettings/themesStyles.css b/src/components/VencordSettings/themesStyles.css
index 6038274f8..90aa9e813 100644
--- a/src/components/VencordSettings/themesStyles.css
+++ b/src/components/VencordSettings/themesStyles.css
@@ -27,3 +27,12 @@
.vc-settings-theme-author::before {
content: "by ";
}
+
+.vc-settings-theme-link-input {
+ width: 100%;
+}
+
+.vc-settings-theme-add-card {
+ padding: 1em;
+ margin-bottom: 16px;
+}
\ No newline at end of file
diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts
index 99f06004c..c80a2f434 100644
--- a/src/utils/quickCss.ts
+++ b/src/utils/quickCss.ts
@@ -57,9 +57,9 @@ export async function toggle(isEnabled: boolean) {
async function initThemes() {
themesStyle ??= createStyle("vencord-themes");
- const { themeLinks, enabledThemes } = Settings;
+ const { enabledThemeLinks, enabledThemes } = Settings;
- const links: string[] = [...themeLinks];
+ const links: string[] = [...enabledThemeLinks];
if (IS_WEB) {
for (const theme of enabledThemes) {
@@ -83,7 +83,7 @@ document.addEventListener("DOMContentLoaded", () => {
toggle(Settings.useQuickCss);
SettingsStore.addChangeListener("useQuickCss", toggle);
- SettingsStore.addChangeListener("themeLinks", initThemes);
+ SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
SettingsStore.addChangeListener("enabledThemes", initThemes);
if (!IS_WEB)