feat: Add day & night toggles for online and local themes (#949)
This commit is contained in:
parent
4058c9ed82
commit
fb5d32ba91
3 changed files with 251 additions and 71 deletions
|
|
@ -26,6 +26,8 @@ import plugins from "~plugins";
|
|||
|
||||
const logger = new Logger("Settings");
|
||||
|
||||
export type ThemeActivationMode = "always" | "light" | "dark";
|
||||
|
||||
export interface SettingsPluginUiElement {
|
||||
enabled: boolean;
|
||||
// TODO
|
||||
|
|
@ -47,6 +49,7 @@ export interface Settings {
|
|||
enableOnlineThemes: boolean;
|
||||
pinnedThemes: string[];
|
||||
themeNames: Record<string, string>;
|
||||
themeActivationModes: Partial<Record<string, ThemeActivationMode>>;
|
||||
enableReactDevtools: boolean;
|
||||
themeLinks: string[];
|
||||
mainWindowFrameless: boolean;
|
||||
|
|
@ -116,6 +119,7 @@ const DefaultSettings: Settings = {
|
|||
enableOnlineThemes: true,
|
||||
pinnedThemes: [],
|
||||
themeNames: {},
|
||||
themeActivationModes: {},
|
||||
enableReactDevtools: false,
|
||||
mainWindowFrameless: false,
|
||||
frameless: false,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { Settings, SettingsStore, type ThemeActivationMode } from "@api/Settings";
|
||||
import { createAndAppendStyle } from "@utils/css";
|
||||
import { ThemeStore } from "@vencord/discord-types";
|
||||
import { PopoutWindowStore } from "@webpack/common";
|
||||
|
|
@ -26,6 +26,16 @@ import { coreStyleRootNode, managedStyleRootNode, userStyleRootNode, vencordRoot
|
|||
let style: HTMLStyleElement;
|
||||
let themesStyle: HTMLStyleElement;
|
||||
|
||||
function getThemeActivationMode(themeId: string) {
|
||||
return Settings.themeActivationModes?.[themeId] ?? "always";
|
||||
}
|
||||
|
||||
function shouldApplyTheme(mode: ThemeActivationMode, activeTheme?: "light" | "dark") {
|
||||
if (mode === "always") return true;
|
||||
if (!activeTheme) return false;
|
||||
return mode === activeTheme;
|
||||
}
|
||||
|
||||
async function toggle(isEnabled: boolean) {
|
||||
if (!style) {
|
||||
if (isEnabled) {
|
||||
|
|
@ -55,29 +65,38 @@ async function initThemes() {
|
|||
? undefined
|
||||
: ThemeStore.theme === "light" ? "light" : "dark";
|
||||
|
||||
const links = enabledThemeLinks
|
||||
.map(rawLink => {
|
||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
||||
if (!match) return rawLink;
|
||||
const links = new Set<string>();
|
||||
|
||||
const [, mode, link] = match;
|
||||
return mode === activeTheme ? link : null;
|
||||
})
|
||||
.filter(link => link !== null);
|
||||
for (const rawLink of enabledThemeLinks) {
|
||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
||||
const link = match?.[2] ?? rawLink;
|
||||
const mode = getThemeActivationMode(rawLink);
|
||||
|
||||
if (shouldApplyTheme(mode, activeTheme)) {
|
||||
links.add(link);
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_WEB) {
|
||||
for (const theme of enabledThemes) {
|
||||
const mode = getThemeActivationMode(theme);
|
||||
if (!shouldApplyTheme(mode, activeTheme)) continue;
|
||||
|
||||
const themeData = await VencordNative.themes.getThemeData(theme);
|
||||
if (!themeData) continue;
|
||||
const blob = new Blob([themeData], { type: "text/css" });
|
||||
links.push(URL.createObjectURL(blob));
|
||||
links.add(URL.createObjectURL(blob));
|
||||
}
|
||||
} else {
|
||||
const localThemes = enabledThemes.map(theme => `vencord:///themes/${theme}?v=${Date.now()}`);
|
||||
links.push(...localThemes);
|
||||
const version = Date.now();
|
||||
for (const theme of enabledThemes) {
|
||||
const mode = getThemeActivationMode(theme);
|
||||
if (!shouldApplyTheme(mode, activeTheme)) continue;
|
||||
links.add(`vencord:///themes/${theme}?v=${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
themesStyle.textContent = links.map(link => `@import url("${link.trim()}");`).join("\n");
|
||||
themesStyle.textContent = Array.from(links).map(link => `@import url("${link.trim()}");`).join("\n");
|
||||
updatePopoutWindows();
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +139,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
|
||||
SettingsStore.addChangeListener("enabledThemeLinks", initThemes);
|
||||
SettingsStore.addChangeListener("enabledThemes", initThemes);
|
||||
SettingsStore.addChangeListener("themeActivationModes", initThemes);
|
||||
|
||||
window.addEventListener("message", event => {
|
||||
const { discordPopoutEvent } = event.data || {};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
import "./themesStyles.css";
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { Settings, type ThemeActivationMode, useSettings } from "@api/Settings";
|
||||
import { Button } from "@components/Button";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
|
|
@ -59,6 +59,74 @@ const LinkIcon = findComponentByCodeLazy("M16.32 14.72a1 1 0 0 1 0-1.41l2.51-2.5
|
|||
const DiscordIcon = findComponentByCodeLazy("1.6 5.64-2.87");
|
||||
const DownloadIcon = findComponentByCodeLazy("1.42l3.3 3.3V3a1");
|
||||
|
||||
const themeActivationModeOptions: { value: ThemeActivationMode; label: string; }[] = [
|
||||
{ value: "always", label: "Always on" },
|
||||
{ value: "light", label: "Light only" },
|
||||
{ value: "dark", label: "Dark only" }
|
||||
];
|
||||
|
||||
function getThemeActivationModeLabel(mode: ThemeActivationMode) {
|
||||
return themeActivationModeOptions.find(option => option.value === mode)?.label ?? "Always on";
|
||||
}
|
||||
|
||||
function inferThemeActivationMode(css: string) {
|
||||
let text = css.replace(/^\uFEFF/, "");
|
||||
|
||||
while (true) {
|
||||
const trimmed = text.trimStart();
|
||||
if (trimmed !== text) text = trimmed;
|
||||
|
||||
const comment = /^\/\*[\s\S]*?\*\/\s*/.exec(text);
|
||||
if (!comment) break;
|
||||
text = text.slice(comment[0].length);
|
||||
}
|
||||
|
||||
const match = /^@(light|dark)\b/i.exec(text);
|
||||
return match?.[1].toLowerCase() as ThemeActivationMode | undefined;
|
||||
}
|
||||
|
||||
function inferAndStoreThemeActivationMode(themeId: string, css: string) {
|
||||
const activationMode = Settings.themeActivationModes?.[themeId] ?? inferThemeActivationMode(css);
|
||||
if (!activationMode || themeId in (Settings.themeActivationModes ?? {})) return;
|
||||
|
||||
Settings.themeActivationModes = {
|
||||
...(Settings.themeActivationModes ?? {}),
|
||||
[themeId]: activationMode,
|
||||
};
|
||||
}
|
||||
|
||||
function ThemeActivationMenu({ themeId, activationMode, onActivationModeChange, children }: {
|
||||
themeId: string;
|
||||
activationMode: ThemeActivationMode;
|
||||
onActivationModeChange?: (mode: ThemeActivationMode) => void;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const [selectedMode, setSelectedMode] = useState(activationMode);
|
||||
|
||||
return (
|
||||
<Menu.Menu navId={`theme-card-menu-${themeId}`} onClose={ContextMenuApi.closeContextMenu}>
|
||||
{onActivationModeChange && (
|
||||
<Menu.MenuItem id={`theme-activation-${themeId}`} label="Theme activation">
|
||||
{themeActivationModeOptions.map(option => (
|
||||
<Menu.MenuRadioItem
|
||||
key={option.value}
|
||||
id={`theme-activation-${themeId}-${option.value}`}
|
||||
group={`theme-activation-${themeId}`}
|
||||
label={option.label}
|
||||
checked={selectedMode === option.value}
|
||||
action={() => {
|
||||
setSelectedMode(option.value);
|
||||
onActivationModeChange(option.value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Menu.MenuItem>
|
||||
)}
|
||||
{children}
|
||||
</Menu.Menu>
|
||||
);
|
||||
}
|
||||
|
||||
function LocalThemeIcon({ size }: { size?: string; }) {
|
||||
const sizeVal = size === "sm" ? 16 : 24;
|
||||
return (
|
||||
|
|
@ -145,6 +213,8 @@ interface OtherThemeCardProps {
|
|||
onDownload?: () => void;
|
||||
themeLink?: string;
|
||||
isLocal?: boolean;
|
||||
activationMode?: ThemeActivationMode;
|
||||
onActivationModeChange?: (mode: ThemeActivationMode) => void;
|
||||
}
|
||||
|
||||
interface UserCSSCardProps {
|
||||
|
|
@ -153,12 +223,48 @@ interface UserCSSCardProps {
|
|||
onChange: (enabled: boolean) => void;
|
||||
onDelete: () => void;
|
||||
onSettingsReset: () => void;
|
||||
activationMode?: ThemeActivationMode;
|
||||
onActivationModeChange?: (mode: ThemeActivationMode) => void;
|
||||
}
|
||||
|
||||
function UserCSSThemeCard({ theme, enabled, onChange, onDelete, onSettingsReset }: UserCSSCardProps) {
|
||||
function UserCSSThemeCard({ theme, enabled, onChange, onDelete, onSettingsReset, activationMode = "always", onActivationModeChange }: UserCSSCardProps) {
|
||||
const missingPlugins = useMemo(() =>
|
||||
theme.requiredPlugins?.filter(p => !isPluginEnabled(p)), [theme]);
|
||||
|
||||
const openThemeMenu = (e: React.MouseEvent) => {
|
||||
ContextMenuApi.openContextMenu(e, () => (
|
||||
<ThemeActivationMenu
|
||||
themeId={theme.id}
|
||||
activationMode={activationMode}
|
||||
onActivationModeChange={onActivationModeChange}
|
||||
>
|
||||
{theme.vars && (
|
||||
<Menu.MenuItem
|
||||
id="edit-theme-settings"
|
||||
label="Edit Settings"
|
||||
action={() => openModal(modalProps =>
|
||||
<UserCSSSettingsModal modalProps={modalProps} theme={theme} onSettingsReset={onSettingsReset} />)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{IS_WEB && (
|
||||
<>
|
||||
<Menu.MenuSeparator />
|
||||
<Menu.MenuItem
|
||||
id="delete-theme"
|
||||
label="Delete"
|
||||
color="danger"
|
||||
icon={DeleteIcon}
|
||||
action={onDelete}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ThemeActivationMenu>
|
||||
));
|
||||
};
|
||||
|
||||
const canOpenThemeMenu = !!theme.vars || !!onActivationModeChange || IS_WEB;
|
||||
|
||||
return (
|
||||
<AddonCard
|
||||
name={theme.name ?? "Unknown"}
|
||||
|
|
@ -182,19 +288,11 @@ function UserCSSThemeCard({ theme, enabled, onChange, onDelete, onSettingsReset
|
|||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
{theme.vars && (
|
||||
<div style={{ cursor: "pointer" }} onClick={
|
||||
() => openModal(modalProps =>
|
||||
<UserCSSSettingsModal modalProps={modalProps} theme={theme} onSettingsReset={onSettingsReset} />)
|
||||
}>
|
||||
{canOpenThemeMenu && (
|
||||
<div style={{ cursor: "pointer" }} onClick={openThemeMenu}>
|
||||
<CogWheel />
|
||||
</div>
|
||||
)}
|
||||
{IS_WEB && (
|
||||
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
|
|
@ -204,16 +302,26 @@ function UserCSSThemeCard({ theme, enabled, onChange, onDelete, onSettingsReset
|
|||
<span style={{ color: "var(--text-muted)" }}>•</span>
|
||||
)}
|
||||
{!!theme.supportURL && <Link href={theme.supportURL}>Support</Link>}
|
||||
{activationMode !== "always" && (
|
||||
<>
|
||||
{!!(theme.homepageURL || theme.supportURL) && <span style={{ color: "var(--text-muted)" }}>•</span>}
|
||||
<span style={{ color: "var(--text-muted)" }}>{getThemeActivationModeLabel(activationMode)}</span>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function OtherThemeCard({ theme, enabled, onChange, onDelete, showDeleteButton, onEditName, disabled, onPin, isPinned, onRefresh, onOpenFolder, onCopyUrl, onDownload, themeLink, isLocal }: OtherThemeCardProps) {
|
||||
function OtherThemeCard({ theme, enabled, onChange, onDelete, showDeleteButton, onEditName, disabled, onPin, isPinned, onRefresh, onOpenFolder, onCopyUrl, onDownload, themeLink, isLocal, activationMode = "always", onActivationModeChange }: OtherThemeCardProps) {
|
||||
const openThemeMenu = (e: React.MouseEvent) => {
|
||||
ContextMenuApi.openContextMenu(e, () => (
|
||||
<Menu.Menu navId="theme-card-menu" onClose={ContextMenuApi.closeContextMenu}>
|
||||
<ThemeActivationMenu
|
||||
themeId={themeLink ?? theme.fileName}
|
||||
activationMode={activationMode}
|
||||
onActivationModeChange={onActivationModeChange}
|
||||
>
|
||||
{onPin && (
|
||||
<Menu.MenuItem
|
||||
id="pin-theme"
|
||||
|
|
@ -286,7 +394,7 @@ function OtherThemeCard({ theme, enabled, onChange, onDelete, showDeleteButton,
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
</Menu.Menu>
|
||||
</ThemeActivationMenu>
|
||||
));
|
||||
};
|
||||
|
||||
|
|
@ -353,6 +461,12 @@ function OtherThemeCard({ theme, enabled, onChange, onDelete, showDeleteButton,
|
|||
Discord Server
|
||||
</Link>
|
||||
)}
|
||||
{activationMode !== "always" && (
|
||||
<>
|
||||
{!!(theme.website || theme.invite) && <span style={{ color: "var(--text-muted)" }}>•</span>}
|
||||
<span style={{ color: "var(--text-muted)" }}>{getThemeActivationModeLabel(activationMode)}</span>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
|
||||
|
|
@ -368,10 +482,11 @@ interface UnifiedTheme {
|
|||
enabled: boolean;
|
||||
header: UserThemeHeader | UserstyleHeader;
|
||||
link?: string;
|
||||
activationMode: ThemeActivationMode;
|
||||
}
|
||||
|
||||
function ThemesTab() {
|
||||
const settings = useSettings(["themeLinks", "enabledThemeLinks", "enabledThemes", "enableOnlineThemes", "pinnedThemes"]);
|
||||
const settings = useSettings(["themeLinks", "enabledThemeLinks", "enabledThemes", "enableOnlineThemes", "pinnedThemes", "themeActivationModes.*"]);
|
||||
|
||||
const fileInputRef = useState<HTMLInputElement | null>(null)[1];
|
||||
const [currentThemeLink, setCurrentThemeLink] = useState("");
|
||||
|
|
@ -386,16 +501,15 @@ function ThemesTab() {
|
|||
const [filter, setFilter] = useState(ThemeFilter.All);
|
||||
|
||||
useEffect(() => {
|
||||
updateThemes();
|
||||
void updateThemes();
|
||||
}, []);
|
||||
|
||||
async function updateThemes() {
|
||||
await changeThemeLibraryURLs();
|
||||
refreshLocalThemes();
|
||||
refreshOnlineThemes();
|
||||
changeThemeLibraryURLs();
|
||||
await Promise.allSettled([refreshLocalThemes(), refreshOnlineThemes()]);
|
||||
}
|
||||
|
||||
async function changeThemeLibraryURLs() {
|
||||
function changeThemeLibraryURLs() {
|
||||
settings.themeLinks = settings.themeLinks.map(link => {
|
||||
if (link.startsWith("https://discord-themes.com/api")) {
|
||||
return link.replace("https://discord-themes.com/api", "https://themes.equicord.org/api");
|
||||
|
|
@ -411,43 +525,49 @@ function ThemesTab() {
|
|||
for (const { fileName, content } of themes) {
|
||||
if (!fileName.endsWith(".css")) continue;
|
||||
|
||||
if ((!IS_WEB || "legcord" in window) && fileName.endsWith(".user.css")) {
|
||||
const header = await usercssParse(content, fileName);
|
||||
try {
|
||||
inferAndStoreThemeActivationMode(fileName, content);
|
||||
|
||||
themeInfo.push({
|
||||
type: "usercss",
|
||||
header
|
||||
});
|
||||
if ((!IS_WEB || "legcord" in window) && fileName.endsWith(".user.css")) {
|
||||
const header = await usercssParse(content, fileName);
|
||||
|
||||
Settings.userCssVars[header.id] ??= {};
|
||||
themeInfo.push({
|
||||
type: "usercss",
|
||||
header
|
||||
});
|
||||
|
||||
for (const [name, varInfo] of Object.entries(header.vars ?? {})) {
|
||||
let normalizedValue = "";
|
||||
Settings.userCssVars[header.id] ??= {};
|
||||
|
||||
switch (varInfo.type) {
|
||||
case "text":
|
||||
case "color":
|
||||
case "checkbox":
|
||||
normalizedValue = varInfo.default;
|
||||
break;
|
||||
case "select":
|
||||
normalizedValue = varInfo.options.find(v => v.name === varInfo.default)!.value;
|
||||
break;
|
||||
case "range":
|
||||
normalizedValue = `${varInfo.default}${varInfo.units}`;
|
||||
break;
|
||||
case "number":
|
||||
normalizedValue = String(varInfo.default);
|
||||
break;
|
||||
for (const [name, varInfo] of Object.entries(header.vars ?? {})) {
|
||||
let normalizedValue = "";
|
||||
|
||||
switch (varInfo.type) {
|
||||
case "text":
|
||||
case "color":
|
||||
case "checkbox":
|
||||
normalizedValue = varInfo.default;
|
||||
break;
|
||||
case "select":
|
||||
normalizedValue = varInfo.options.find(v => v.name === varInfo.default)!.value;
|
||||
break;
|
||||
case "range":
|
||||
normalizedValue = `${varInfo.default}${varInfo.units}`;
|
||||
break;
|
||||
case "number":
|
||||
normalizedValue = String(varInfo.default);
|
||||
break;
|
||||
}
|
||||
|
||||
Settings.userCssVars[header.id][name] ??= normalizedValue;
|
||||
}
|
||||
|
||||
Settings.userCssVars[header.id][name] ??= normalizedValue;
|
||||
} else {
|
||||
themeInfo.push({
|
||||
type: "other",
|
||||
header: getThemeInfo(stripBOM(content), fileName)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
themeInfo.push({
|
||||
type: "other",
|
||||
header: getThemeInfo(stripBOM(content), fileName)
|
||||
});
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -504,9 +624,10 @@ function ThemesTab() {
|
|||
const res = await fetch(link);
|
||||
if (!res.ok) throw new Error(`Failed to fetch ${link}`);
|
||||
const css = await res.text();
|
||||
inferAndStoreThemeActivationMode(link, css);
|
||||
|
||||
return { ...getThemeInfo(css, link), link };
|
||||
} catch (err) {
|
||||
console.warn(`Theme fetch failed for ${link}:`, err);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
|
|
@ -523,12 +644,35 @@ function ThemesTab() {
|
|||
}
|
||||
}
|
||||
|
||||
function clearThemeState(themeId: string) {
|
||||
settings.pinnedThemes = settings.pinnedThemes.filter(f => f !== themeId);
|
||||
settings.enabledThemes = settings.enabledThemes.filter(f => f !== themeId);
|
||||
settings.enabledThemeLinks = settings.enabledThemeLinks.filter(f => f !== themeId);
|
||||
settings.themeNames = Object.fromEntries(Object.entries(settings.themeNames).filter(([key]) => key !== themeId));
|
||||
|
||||
const themeActivationModes = { ...(settings.themeActivationModes ?? {}) };
|
||||
delete themeActivationModes[themeId];
|
||||
settings.themeActivationModes = themeActivationModes;
|
||||
}
|
||||
|
||||
function deleteThemeLink(link: string) {
|
||||
settings.themeLinks = settings.themeLinks.filter(f => f !== link);
|
||||
settings.pinnedThemes = settings.pinnedThemes.filter(f => f !== link);
|
||||
clearThemeState(link);
|
||||
refreshOnlineThemes();
|
||||
}
|
||||
|
||||
function setThemeActivationMode(themeId: string, mode: ThemeActivationMode) {
|
||||
const themeActivationModes = { ...(settings.themeActivationModes ?? {}) };
|
||||
|
||||
if (mode === "always") {
|
||||
delete themeActivationModes[themeId];
|
||||
} else {
|
||||
themeActivationModes[themeId] = mode;
|
||||
}
|
||||
|
||||
settings.themeActivationModes = themeActivationModes;
|
||||
}
|
||||
|
||||
function togglePinTheme(themeId: string) {
|
||||
if (settings.pinnedThemes.includes(themeId)) {
|
||||
settings.pinnedThemes = settings.pinnedThemes.filter(f => f !== themeId);
|
||||
|
|
@ -542,6 +686,8 @@ function ThemesTab() {
|
|||
const res = await fetch(link);
|
||||
if (!res.ok) throw new Error(`Failed to fetch ${link}`);
|
||||
const css = await res.text();
|
||||
inferAndStoreThemeActivationMode(link, css);
|
||||
|
||||
const updatedTheme = { ...getThemeInfo(css, link), link };
|
||||
|
||||
setOnlineThemes(prev =>
|
||||
|
|
@ -587,7 +733,8 @@ function ThemesTab() {
|
|||
name: customName ?? theme.name ?? theme.fileName,
|
||||
enabled: settings.enabledThemeLinks.includes(theme.link),
|
||||
header: { ...theme, customName },
|
||||
link: theme.link
|
||||
link: theme.link,
|
||||
activationMode: settings.themeActivationModes?.[theme.link] ?? "always",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -601,12 +748,13 @@ function ThemesTab() {
|
|||
themeType: type,
|
||||
name,
|
||||
enabled: settings.enabledThemes.includes(header.fileName),
|
||||
header
|
||||
header,
|
||||
activationMode: settings.themeActivationModes?.[header.fileName] ?? "always",
|
||||
});
|
||||
}
|
||||
|
||||
return themes;
|
||||
}, [onlineThemes, userThemes, themeNames, settings.enabledThemeLinks, settings.enabledThemes]);
|
||||
}, [onlineThemes, userThemes, themeNames, settings.enabledThemeLinks, settings.enabledThemes, settings.themeActivationModes]);
|
||||
|
||||
const filteredThemes = useMemo(() => {
|
||||
let themes = allThemes;
|
||||
|
|
@ -808,6 +956,8 @@ function ThemesTab() {
|
|||
onRefresh={() => refreshOnlineTheme(onlineTheme.link)}
|
||||
onDownload={() => downloadTheme(onlineTheme.link, onlineTheme.name ?? "theme")}
|
||||
isLocal={false}
|
||||
activationMode={theme.activationMode}
|
||||
onActivationModeChange={mode => setThemeActivationMode(onlineTheme.link, mode)}
|
||||
onEditName={newName => {
|
||||
const updatedNames = { ...themeNames, [onlineTheme.link]: newName };
|
||||
setThemeNames(updatedNames);
|
||||
|
|
@ -829,11 +979,14 @@ function ThemesTab() {
|
|||
onChange={enabled => onLocalThemeChange(usercssTheme.fileName, enabled)}
|
||||
onDelete={async () => {
|
||||
onLocalThemeChange(usercssTheme.fileName, false);
|
||||
clearThemeState(usercssTheme.fileName);
|
||||
await VencordNative.themes.deleteTheme(usercssTheme.fileName);
|
||||
refreshLocalThemes();
|
||||
}}
|
||||
onSettingsReset={refreshLocalThemes}
|
||||
theme={usercssTheme}
|
||||
activationMode={theme.activationMode}
|
||||
onActivationModeChange={mode => setThemeActivationMode(usercssTheme.fileName, mode)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -846,6 +999,7 @@ function ThemesTab() {
|
|||
onChange={enabled => onLocalThemeChange(localTheme.fileName, enabled)}
|
||||
onDelete={async () => {
|
||||
onLocalThemeChange(localTheme.fileName, false);
|
||||
clearThemeState(localTheme.fileName);
|
||||
await VencordNative.themes.deleteTheme(localTheme.fileName);
|
||||
refreshLocalThemes();
|
||||
}}
|
||||
|
|
@ -856,6 +1010,8 @@ function ThemesTab() {
|
|||
onRefresh={refreshLocalThemes}
|
||||
isLocal
|
||||
theme={localTheme}
|
||||
activationMode={theme.activationMode}
|
||||
onActivationModeChange={mode => setThemeActivationMode(localTheme.fileName, mode)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue