feat(themes): Online Themes Redesign
This commit is contained in:
parent
18df66a4b4
commit
506aab14c9
|
@ -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,
|
||||
|
|
|
@ -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}</Forms.FormText>;
|
||||
}
|
||||
|
||||
function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||
if (!themeLinks.length) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||
<div>
|
||||
{themeLinks.map(link => (
|
||||
<Card style={{
|
||||
padding: ".5em",
|
||||
marginBottom: ".5em",
|
||||
marginTop: ".5em"
|
||||
}} key={link}>
|
||||
<Forms.FormTitle tag="h5" style={{
|
||||
overflowWrap: "break-word"
|
||||
}}>
|
||||
{link}
|
||||
</Forms.FormTitle>
|
||||
<Validator link={link} />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<AddonCard
|
||||
name={theme.name}
|
||||
|
@ -113,7 +88,7 @@ function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
|
|||
enabled={enabled}
|
||||
setEnabled={onChange}
|
||||
infoButton={
|
||||
IS_WEB && (
|
||||
(IS_WEB || showDeleteButton) && (
|
||||
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
|
@ -146,16 +121,19 @@ enum ThemeTab {
|
|||
}
|
||||
|
||||
function ThemesTab() {
|
||||
const settings = useSettings(["themeLinks", "enabledThemes"]);
|
||||
const settings = useSettings(["themeLinks", "enabledThemeLinks", "enabledThemes"]);
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(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<UserThemeHeader[] | null>(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 (
|
||||
<>
|
||||
<Card className="vc-settings-card">
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Card className="vc-settings-card vc-text-selectable">
|
||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
||||
<Forms.FormText>One link per line</Forms.FormText>
|
||||
<Forms.FormSection title="Online Themes" tag="h5">
|
||||
<Card className="vc-settings-theme-add-card">
|
||||
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
||||
<Flex flexDirection="row">
|
||||
<TextInput placeholder="Theme Link" className="vc-settings-theme-link-input" value={currentThemeLink} onChange={setCurrentThemeLink} />
|
||||
<Button onClick={() => addThemeLink(currentThemeLink)} disabled={!themeLinkValid}>Add</Button>
|
||||
</Flex>
|
||||
{currentThemeLink && <Validator link={currentThemeLink} onValidate={setThemeLinkValid} />}
|
||||
</Card>
|
||||
|
||||
<Forms.FormSection title="Online Themes" tag="h5">
|
||||
<TextArea
|
||||
value={themeText}
|
||||
onChange={setThemeText}
|
||||
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")}
|
||||
placeholder="Theme Links"
|
||||
spellCheck={false}
|
||||
onBlur={onBlur}
|
||||
rows={10}
|
||||
/>
|
||||
<Validators themeLinks={settings.themeLinks} />
|
||||
<div className={cl("grid")}>
|
||||
{onlineThemes?.map(theme => {
|
||||
return <ThemeCard
|
||||
key={theme.fileName}
|
||||
enabled={settings.enabledThemeLinks.includes(theme.link)}
|
||||
onChange={enabled => onThemeLinkEnabledChange(theme.link, enabled)}
|
||||
onDelete={() => deleteThemeLink(theme.link)}
|
||||
showDeleteButton
|
||||
theme={theme}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
</>
|
||||
);
|
||||
|
@ -347,8 +351,8 @@ function ThemesTab() {
|
|||
</TabBar.Item>
|
||||
</TabBar>
|
||||
|
||||
{currentTab === ThemeTab.LOCAL && renderLocalThemes()}
|
||||
{currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
|
||||
{currentTab === ThemeTab.LOCAL && <LocalThemes />}
|
||||
{currentTab === ThemeTab.ONLINE && <OnlineThemes />}
|
||||
</SettingsTab>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue