mirror of
https://github.com/Equicord/Equicord.git
synced 2025-01-18 21:33:35 -05:00
Update ThemeLibrary
This commit is contained in:
parent
910274c357
commit
1e079a70e9
10 changed files with 256 additions and 95 deletions
|
@ -9,8 +9,9 @@ import { Logger } from "@utils/Logger";
|
|||
import { Button, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
||||
import type { User } from "discord-types/general";
|
||||
|
||||
import { isAuthorized } from "../auth";
|
||||
import type { Theme, ThemeLikeProps } from "../types";
|
||||
import { isAuthorized } from "../utils/auth";
|
||||
import { LikeIcon } from "../utils/Icons";
|
||||
import { themeRequest } from "./ThemeTab";
|
||||
|
||||
export const logger = new Logger("ThemeLibrary", "#e5c890");
|
||||
|
@ -30,12 +31,6 @@ export const LikesComponent = ({ themeId, likedThemes: initialLikedThemes }: { t
|
|||
return themeLike ? themeLike.userIds.length : 0;
|
||||
}
|
||||
|
||||
const likeIcon = (isLiked: boolean) => (
|
||||
<svg viewBox="0 0 20 20" fill={isLiked ? "red" : "currentColor"} aria-hidden="true" width="18" height="18" className="vce-likes-icon vce-likes-icon-animation">
|
||||
<path d="M16.44 3.10156C14.63 3.10156 13.01 3.98156 12 5.33156C10.99 3.98156 9.37 3.10156 7.56 3.10156C4.49 3.10156 2 5.60156 2 8.69156C2 9.88156 2.19 10.9816 2.52 12.0016C4.1 17.0016 8.97 19.9916 11.38 20.8116C11.72 20.9316 12.28 20.9316 12.62 20.8116C15.03 19.9916 19.9 17.0016 21.48 12.0016C21.81 10.9816 22 9.88156 22 8.69156C22 5.60156 19.51 3.10156 16.44 3.10156Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const handleLikeClick = async (themeId: Theme["id"]) => {
|
||||
if (!isAuthorized()) return;
|
||||
const theme = likedThemes?.likes.find(like => like.themeId === themeId as unknown as Number);
|
||||
|
@ -90,7 +85,7 @@ export const LikesComponent = ({ themeId, likedThemes: initialLikedThemes }: { t
|
|||
look={Button.Looks.OUTLINED}
|
||||
style={{ marginLeft: "8px" }}
|
||||
>
|
||||
{likeIcon(hasLiked)} {likesCount}
|
||||
{LikeIcon(hasLiked)} {likesCount}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -8,21 +8,37 @@ import { CodeBlock } from "@components/CodeBlock";
|
|||
import { Heart } from "@components/Heart";
|
||||
import { openInviteModal } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import type { PluginNative } from "@utils/types";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clipboard, Forms, React, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
import { ThemeInfoModalProps } from "../types";
|
||||
import { Theme, ThemeInfoModalProps } from "../types";
|
||||
import { DownloadIcon } from "../utils/Icons";
|
||||
import { logger } from "./LikesComponent";
|
||||
|
||||
const Native = VencordNative.pluginHelpers.ThemeLibrary as PluginNative<typeof import("../native")>;
|
||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||
|
||||
export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, ...props }) => {
|
||||
async function downloadTheme(themesDir: string, theme: Theme) {
|
||||
try {
|
||||
await Native.downloadTheme(themesDir, theme);
|
||||
showToast(`Downloaded ${theme.name}!`, Toasts.Type.SUCCESS);
|
||||
} catch (err: any) {
|
||||
logger.error(err);
|
||||
showToast(`Failed to download ${theme.name}! (check console)`, Toasts.Type.FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, ...props }) => {
|
||||
const content = window.atob(theme.content);
|
||||
const metadata = content.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || "";
|
||||
const donate = metadata.match(/@donate\s+(.+)/)?.[1] || "";
|
||||
const version = metadata.match(/@version\s+(.+)/)?.[1] || "";
|
||||
|
||||
const { likes, guild, tags } = theme;
|
||||
|
||||
return (
|
||||
<ModalRoot {...props}>
|
||||
<ModalHeader>
|
||||
|
@ -48,6 +64,53 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
|||
{author.username}
|
||||
</Forms.FormText>
|
||||
</div>
|
||||
{version && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Version</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{version}
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Likes</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{likes === 0 ? `Nobody liked this ${theme.type} yet.` : `${likes} users liked this ${theme.type}!`}
|
||||
</Forms.FormText>
|
||||
{donate && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Donate</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
You can support the author by donating below!
|
||||
</Forms.FormText>
|
||||
<Forms.FormText style={{ marginTop: "10px" }}>
|
||||
<Button onClick={() => VencordNative.native.openExternal(donate)}>
|
||||
<Heart />
|
||||
Donate
|
||||
</Button>
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
{guild && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Support Server</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{guild.name}
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
<Button
|
||||
color={Button.Colors.BRAND_NEW}
|
||||
look={Button.Looks.FILLED}
|
||||
className={Margins.top8}
|
||||
onClick={async e => {
|
||||
e.preventDefault();
|
||||
guild.invite_link != null && openInviteModal(guild.invite_link.split("discord.gg/")[1]).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
|
||||
}}
|
||||
>
|
||||
Join Discord Server
|
||||
</Button>
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Source</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
<Button onClick={() => openModal(modalProps => (
|
||||
|
@ -57,7 +120,7 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
|||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Forms.FormText style={{
|
||||
padding: "5px",
|
||||
padding: "8px",
|
||||
}}>
|
||||
<CodeBlock lang="css" content={content} />
|
||||
</Forms.FormText>
|
||||
|
@ -82,54 +145,11 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
|||
View Theme Source
|
||||
</Button>
|
||||
</Forms.FormText>
|
||||
{version && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Version</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{version}
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
{donate && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Donate</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
You can support the author by donating below.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText style={{ marginTop: "10px" }}>
|
||||
<Button onClick={() => VencordNative.native.openExternal(donate)}>
|
||||
<Heart />
|
||||
Donate
|
||||
</Button>
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
{theme.guild && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Support Server</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{theme.guild.name}
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
<Button
|
||||
color={Button.Colors.BRAND_NEW}
|
||||
look={Button.Looks.FILLED}
|
||||
className={Margins.top8}
|
||||
onClick={async e => {
|
||||
e.preventDefault();
|
||||
theme.guild?.invite_link != null && openInviteModal(theme.guild?.invite_link.split("discord.gg/")[1]).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
|
||||
}}
|
||||
>
|
||||
Join Discord Server
|
||||
</Button>
|
||||
</Forms.FormText>
|
||||
</>
|
||||
)}
|
||||
{theme.tags && (
|
||||
{tags && (
|
||||
<>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Tags</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
{theme.tags.map(tag => (
|
||||
{tags.map(tag => (
|
||||
<span className="vce-theme-info-tag">
|
||||
{tag}
|
||||
</span>
|
||||
|
@ -148,6 +168,68 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
|
|||
>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
color={Button.Colors.GREEN}
|
||||
look={Button.Looks.OUTLINED}
|
||||
className={classes("vce-button", Margins.right8)}
|
||||
onClick={async () => {
|
||||
const themesDir = await VencordNative.themes.getThemesDir();
|
||||
const exists = await Native.themeExists(themesDir, theme);
|
||||
// using another function so we get the proper file path instead of just guessing
|
||||
// which slash to use (im looking at you windows)
|
||||
const validThemesDir = await Native.getThemesDir(themesDir, theme);
|
||||
// check if theme exists, and ask if they want to overwrite
|
||||
if (exists) {
|
||||
showToast("A file with the same name already exists!", Toasts.Type.FAILURE);
|
||||
openModal(modalProps => (
|
||||
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
|
||||
<ModalHeader>
|
||||
<Forms.FormTitle tag="h4">Conflict!</Forms.FormTitle>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Forms.FormText style={{
|
||||
padding: "8px",
|
||||
}}>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<p>A theme with the same name <b>already exist</b> in your themes directory! Do you want to overwrite it?</p>
|
||||
<div className="vce-overwrite-modal">
|
||||
<code style={{ wordWrap: "break-word" }}>
|
||||
{validThemesDir}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</Forms.FormText>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
look={Button.Looks.FILLED}
|
||||
color={Button.Colors.RED}
|
||||
onClick={async () => {
|
||||
await downloadTheme(themesDir, theme);
|
||||
modalProps.onClose();
|
||||
}}
|
||||
>
|
||||
Overwrite
|
||||
</Button>
|
||||
<Button
|
||||
color={Button.Colors.GREEN}
|
||||
look={Button.Looks.FILLED}
|
||||
className={Margins.right8} onClick={() => modalProps.onClose()}
|
||||
>
|
||||
Keep my file
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
));
|
||||
} else {
|
||||
await downloadTheme(themesDir, theme);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
Download <DownloadIcon style={{ marginLeft: "5px" }} />
|
||||
</div>
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,6 @@ import "./styles.css";
|
|||
import { generateId } from "@api/Commands";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { OpenExternalIcon } from "@components/Icons";
|
||||
|
@ -25,12 +24,11 @@ import { Button, Card, FluxDispatcher, Forms, React, SearchableSelect, TabBar, T
|
|||
import { User } from "discord-types/general";
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
import { isAuthorized } from "../auth";
|
||||
import { SearchStatus, TabItem, Theme, ThemeLikeProps } from "../types";
|
||||
import { isAuthorized } from "../utils/auth";
|
||||
import { LikesComponent } from "./LikesComponent";
|
||||
import { ThemeInfoModal } from "./ThemeInfoModal";
|
||||
|
||||
const cl = classNameFactory("vc-plugins-");
|
||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper", "error");
|
||||
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
|
||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||
|
@ -88,6 +86,15 @@ const themeTemplate = `/**
|
|||
/* Your CSS goes here */
|
||||
`;
|
||||
|
||||
const SearchTags = {
|
||||
[SearchStatus.THEME]: "THEME",
|
||||
[SearchStatus.SNIPPET]: "SNIPPET",
|
||||
[SearchStatus.LIKED]: "LIKED",
|
||||
[SearchStatus.DARK]: "DARK",
|
||||
[SearchStatus.LIGHT]: "LIGHT",
|
||||
};
|
||||
|
||||
|
||||
function ThemeTab() {
|
||||
const [themes, setThemes] = useState<Theme[]>([]);
|
||||
const [filteredThemes, setFilteredThemes] = useState<Theme[]>([]);
|
||||
|
@ -103,12 +110,15 @@ function ThemeTab() {
|
|||
|
||||
const themeFilter = (theme: Theme) => {
|
||||
const enabled = themeLinks.includes(`${API_URL}/${theme.name}`);
|
||||
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;
|
||||
const tags = new Set(theme.tags.map(tag => tag.toLowerCase()));
|
||||
|
||||
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
||||
|
||||
const anyTags = SearchTags[searchValue.status];
|
||||
if (anyTags && !tags.has(anyTags.toLowerCase())) return false;
|
||||
|
||||
if ((enabled && searchValue.status === SearchStatus.DISABLED) || (!enabled && searchValue.status === SearchStatus.ENABLED)) return false;
|
||||
|
||||
if (!searchValue.value.length) return true;
|
||||
|
||||
const v = searchValue.value.toLowerCase();
|
||||
|
@ -116,7 +126,7 @@ function ThemeTab() {
|
|||
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))
|
||||
tags.has(v)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -131,12 +141,10 @@ function ThemeTab() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchThemes = async () => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const themes = await fetchAllThemes();
|
||||
// fetch likes
|
||||
const [themes, likes] = await Promise.all([fetchAllThemes(), fetchLikes()]);
|
||||
setThemes(themes);
|
||||
const likes = await fetchLikes();
|
||||
setLikedThemes(likes);
|
||||
setFilteredThemes(themes);
|
||||
} catch (err) {
|
||||
|
@ -145,7 +153,7 @@ function ThemeTab() {
|
|||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchThemes();
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -153,9 +161,18 @@ function ThemeTab() {
|
|||
}, [Vencord.Settings.themeLinks]);
|
||||
|
||||
useEffect(() => {
|
||||
const filteredThemes = themes.filter(themeFilter);
|
||||
setFilteredThemes(filteredThemes);
|
||||
}, [searchValue]);
|
||||
// likes only update after 12_000 due to cache
|
||||
if (searchValue.status === SearchStatus.LIKED) {
|
||||
const likedThemes = themes.sort((a, b) => b.likes - a.likes);
|
||||
// replacement of themeFilter which wont work with SearchStatus.LIKED
|
||||
const filteredLikedThemes = likedThemes.filter(x => x.name.includes(searchValue.value));
|
||||
setFilteredThemes(filteredLikedThemes);
|
||||
} else {
|
||||
const sortedThemes = themes.sort((a, b) => new Date(b.release_date).getTime() - new Date(a.release_date).getTime());
|
||||
const filteredThemes = sortedThemes.filter(themeFilter);
|
||||
setFilteredThemes(filteredThemes);
|
||||
}
|
||||
}, [searchValue, themes]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -191,13 +208,13 @@ function ThemeTab() {
|
|||
>Hide</Button>
|
||||
</ErrorCard >
|
||||
)}
|
||||
<div className={`${Margins.bottom8} ${Margins.top16}`}>
|
||||
<div className={classes(Margins.bottom8, Margins.top16)}>
|
||||
<Forms.FormTitle tag="h2"
|
||||
style={{
|
||||
overflowWrap: "break-word",
|
||||
marginTop: 8,
|
||||
}}>
|
||||
Newest Additions
|
||||
{searchValue.status === SearchStatus.LIKED ? "Most Liked" : "Newest Additions"}
|
||||
</Forms.FormTitle>
|
||||
|
||||
{themes.slice(0, 2).map((theme: Theme) => (
|
||||
|
@ -305,7 +322,7 @@ function ThemeTab() {
|
|||
}}>
|
||||
Themes
|
||||
</Forms.FormTitle>
|
||||
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
||||
<div className={classes(Margins.bottom20, "vce-search-grid")}>
|
||||
<TextInput value={searchValue.value} placeholder="Search for a theme..." onChange={onSearch} />
|
||||
<div className={InputStyles.inputWrapper}>
|
||||
<SearchableSelect
|
||||
|
@ -313,8 +330,7 @@ function ThemeTab() {
|
|||
{ label: "Show All", value: SearchStatus.ALL, default: true },
|
||||
{ label: "Show Themes", value: SearchStatus.THEME },
|
||||
{ label: "Show Snippets", value: SearchStatus.SNIPPET },
|
||||
// TODO: filter for most liked themes
|
||||
// { label: "Show Most Liked", value: SearchStatus.LIKED },
|
||||
{ label: "Show Most Liked", value: SearchStatus.LIKED },
|
||||
{ label: "Show Dark", value: SearchStatus.DARK },
|
||||
{ label: "Show Light", value: SearchStatus.LIGHT },
|
||||
{ label: "Show Enabled", value: SearchStatus.ENABLED },
|
||||
|
@ -448,7 +464,7 @@ function SubmitThemes() {
|
|||
const handleChange = (v: string) => setContent(v);
|
||||
|
||||
return (
|
||||
<div className={`${Margins.bottom8} ${Margins.top16}`}>
|
||||
<div className={classes(Margins.bottom8, Margins.top16)}>
|
||||
<Forms.FormTitle tag="h2" style={{
|
||||
overflowWrap: "break-word",
|
||||
marginTop: 8,
|
||||
|
@ -461,7 +477,7 @@ function SubmitThemes() {
|
|||
<Forms.FormText>
|
||||
(your theme will be reviewed and can take up to 24 hours to be approved)
|
||||
</Forms.FormText>
|
||||
<Forms.FormText className={`${Margins.bottom16} ${Margins.top8}`}>
|
||||
<Forms.FormText className={classes(Margins.bottom16, Margins.top8)}>
|
||||
<CodeBlock lang="css" content={themeTemplate} />
|
||||
</Forms.FormText>
|
||||
<Forms.FormTitle tag="h2" style={{
|
||||
|
@ -478,7 +494,7 @@ function SubmitThemes() {
|
|||
content={themeTemplate}
|
||||
onChange={handleChange}
|
||||
className={classes(TextAreaProps.textarea, "vce-text-input")}
|
||||
placeholder="Theme CSS goes here..."
|
||||
placeholder={themeTemplate}
|
||||
spellCheck={false}
|
||||
rows={35}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
@keyframes bounce {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); }
|
||||
}
|
||||
|
||||
[data-tab-id="ThemeLibrary"]::before {
|
||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||
|
@ -51,10 +47,10 @@
|
|||
.vce-text-input {
|
||||
display: inline-block !important;
|
||||
color: var(--text-normal) !important;
|
||||
font-family: var(--font-code) !important;
|
||||
font-size: 16px !important;
|
||||
padding: 0.5em;
|
||||
border: 2px solid var(--background-tertiary);
|
||||
line-height: 1.2;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
|
@ -74,3 +70,23 @@
|
|||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vce-search-grid {
|
||||
display: grid;
|
||||
height: 40px;
|
||||
gap: 10px;
|
||||
grid-template-columns: 1fr 200px;
|
||||
}
|
||||
|
||||
.vce-button {
|
||||
white-space: normal;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.vce-overwrite-modal {
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 8px;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EquicordDevs } from "@utils/constants";
|
|||
import definePlugin from "@utils/types";
|
||||
import { SettingsRouter } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { settings } from "./utils/settings";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ThemeLibrary",
|
||||
|
|
25
src/equicordplugins/themeLibrary/native.ts
Normal file
25
src/equicordplugins/themeLibrary/native.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { IpcMainInvokeEvent } from "electron";
|
||||
import { existsSync, type PathLike, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
import type { Theme } from "./types";
|
||||
|
||||
export async function themeExists(_: IpcMainInvokeEvent, dir: PathLike, theme: Theme) {
|
||||
return existsSync(join(dir.toString(), `${theme.name}.theme.css`));
|
||||
}
|
||||
|
||||
export function getThemesDir(_: IpcMainInvokeEvent, dir: PathLike, theme: Theme) {
|
||||
return join(dir.toString(), `${theme.name}.theme.css`);
|
||||
}
|
||||
|
||||
export async function downloadTheme(_: IpcMainInvokeEvent, dir: PathLike, theme: Theme) {
|
||||
if (!theme.content || !theme.name) return;
|
||||
const path = join(dir.toString(), `${theme.name}.theme.css`);
|
||||
writeFileSync(path, Buffer.from(theme.content, "base64"));
|
||||
}
|
|
@ -10,20 +10,16 @@ import { User } from "discord-types/general";
|
|||
export interface Theme {
|
||||
id: string;
|
||||
name: string;
|
||||
file_name: string;
|
||||
content: string;
|
||||
type: string | "theme" | "snippet";
|
||||
description: string;
|
||||
external_url?: string;
|
||||
download_url: string;
|
||||
version?: string;
|
||||
version: string;
|
||||
author: {
|
||||
github_name?: string;
|
||||
discord_name: string;
|
||||
discord_snowflake: string;
|
||||
};
|
||||
likes?: number;
|
||||
downloads?: number;
|
||||
likes: number;
|
||||
tags: string[];
|
||||
thumbnail_url: string;
|
||||
release_date: string;
|
||||
|
@ -31,7 +27,6 @@ export interface Theme {
|
|||
name: string;
|
||||
snowflake: string;
|
||||
invite_link: string;
|
||||
avatar_hash: string;
|
||||
};
|
||||
source?: string;
|
||||
}
|
||||
|
|
32
src/equicordplugins/themeLibrary/utils/Icons.tsx
Normal file
32
src/equicordplugins/themeLibrary/utils/Icons.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export const LikeIcon = (isLiked: boolean) => (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill={isLiked ? "red" : "currentColor"}
|
||||
aria-hidden="true"
|
||||
width="18"
|
||||
height="18"
|
||||
className="vce-likes-icon"
|
||||
>
|
||||
<path d="M16.44 3.10156C14.63 3.10156 13.01 3.98156 12 5.33156C10.99 3.98156 9.37 3.10156 7.56 3.10156C4.49 3.10156 2 5.60156 2 8.69156C2 9.88156 2.19 10.9816 2.52 12.0016C4.1 17.0016 8.97 19.9916 11.38 20.8116C11.72 20.9316 12.28 20.9316 12.62 20.8116C15.03 19.9916 19.9 17.0016 21.48 12.0016C21.81 10.9816 22 9.88156 22 8.69156C22 5.60156 19.51 3.10156 16.44 3.10156Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DownloadIcon = (props: any) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path>
|
||||
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path>
|
||||
</svg>
|
||||
);
|
|
@ -9,7 +9,7 @@ import { showNotification } from "@api/Notifications";
|
|||
import { openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
import { logger } from "./components/LikesComponent";
|
||||
import { logger } from "../components/LikesComponent";
|
||||
|
||||
export async function authorizeUser(triggerModal: boolean = true) {
|
||||
const isAuthorized = await getAuthorization();
|
Loading…
Reference in a new issue