Update Theme Library (#7)

* UpdateThemeLiberry

* Delete old index

* Update Authors

* Update index.tsx

---------

Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
KrystalSkull💖 2024-06-24 14:53:58 -04:00 committed by GitHub
parent 35b5cd1182
commit 970bfb757a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 208 additions and 29 deletions

View file

@ -0,0 +1,100 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Logger } from "@utils/Logger";
import { Button, useEffect, UserStore, useState } from "@webpack/common";
import type { User } from "discord-types/general";
import type { Theme, ThemeLikeProps } from "../types";
import { themeRequest } from "./ThemeTab";
const logger = new Logger("ThemeLibrary", "#e5c890");
const fetchLikes = async () => {
try {
const response = await themeRequest("/likes/get");
const data = await response.json();
return data;
} catch (err) {
logger.error(err);
}
};
export const LikesComponent = ({ themeId, likedThemes: initialLikedThemes }: { themeId: Theme["id"], likedThemes: ThemeLikeProps | undefined; }) => {
const [likesCount, setLikesCount] = useState(0);
const [likedThemes, setLikedThemes] = useState(initialLikedThemes);
useEffect(() => {
const likes = getThemeLikes(themeId);
logger.debug("likes", likes, "for:", themeId);
setLikesCount(likes);
}, [likedThemes, themeId]);
function getThemeLikes(themeId: Theme["id"]): number {
const themeLike = likedThemes?.likes.find(like => like.themeId === themeId as unknown as Number);
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"]) => {
const theme = likedThemes?.likes.find(like => like.themeId === themeId as unknown as Number);
const currentUser: User = UserStore.getCurrentUser();
const hasLiked: boolean = theme?.userIds.includes(currentUser.id) ?? false;
const endpoint = hasLiked ? "/likes/remove" : "/likes/add";
try {
const response = await themeRequest(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
cache: "no-store",
body: JSON.stringify({
userId: currentUser.id,
themeId: themeId,
}),
});
if (!response.ok) return logger.error("Couldnt update likes, res:", response.statusText);
const fetchLikes = async () => {
try {
const response = await themeRequest("/likes/get");
const data = await response.json();
setLikedThemes(data);
} catch (err) {
logger.error(err);
}
};
fetchLikes();
// doing it locally isnt the best way probably, but it does the same
setLikesCount(likesCount + (hasLiked ? -1 : 1));
} catch (err) {
logger.error(err);
}
};
const hasLiked = likedThemes?.likes.some(like => like.themeId === themeId as unknown as Number && like.userIds.includes(UserStore.getCurrentUser().id)) ?? false;
return (
<div>
<Button onClick={() => handleLikeClick(themeId)}
size={Button.Sizes.MEDIUM}
color={Button.Colors.PRIMARY}
look={Button.Looks.OUTLINED}
style={{ marginLeft: "8px" }}
>
{likeIcon(hasLiked)} {likesCount}
</Button>
</div>
);
};

View file

@ -15,6 +15,7 @@ import { ErrorCard } from "@components/ErrorCard";
import { OpenExternalIcon } from "@components/Icons";
import { SettingsTab, wrapTab } from "@components/VencordSettings/shared";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
@ -23,7 +24,8 @@ import { Button, Card, FluxDispatcher, Forms, React, Select, showToast, TabBar,
import { User } from "discord-types/general";
import { Constructor } from "type-fest";
import { SearchStatus, TabItem, Theme } from "../types";
import { SearchStatus, TabItem, Theme, ThemeLikeProps } from "../types";
import { LikesComponent } from "./LikesComponent";
import { ThemeInfoModal } from "./ThemeInfoModal";
const cl = classNameFactory("vc-plugins-");
@ -33,6 +35,8 @@ const TextAreaProps = findLazy(m => typeof m.textarea === "string");
const API_URL = "https://themes-delta.vercel.app/api";
const logger = new Logger("ThemeLibrary", "#e5c890");
async function fetchThemes(url: string): Promise<Theme[]> {
const response = await fetch(url);
const data = await response.json();
@ -47,7 +51,7 @@ async function fetchThemes(url: string): Promise<Theme[]> {
return themes.sort((a, b) => new Date(b.release_date).getTime() - new Date(a.release_date).getTime());
}
function API_TYPE(theme, returnAll?: boolean) {
function API_TYPE(theme: Theme | Object, returnAll?: boolean) {
if (!theme) return;
const settings = Settings.plugins.ThemeLibrary.domain ?? false;
@ -55,11 +59,12 @@ function API_TYPE(theme, returnAll?: boolean) {
const url = settings ? "https://raw.githubusercontent.com/Faf4a/plugins/main/assets/meta.json" : `${API_URL}/themes`;
return fetchThemes(url);
} else {
// @ts-ignore
return settings ? theme.source : `${API_URL}/${theme.name}`;
}
}
async function themeRequest(path: string, options: RequestInit = {}) {
export async function themeRequest(path: string, options: RequestInit = {}) {
return fetch(API_URL + path, {
...options,
headers: {
@ -100,7 +105,9 @@ function ThemeTab() {
const [themes, setThemes] = useState<Theme[]>([]);
const [filteredThemes, setFilteredThemes] = useState<Theme[]>([]);
const [themeLinks, setThemeLinks] = useState(Vencord.Settings.themeLinks);
const [likedThemes, setLikedThemes] = useState<ThemeLikeProps>();
const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL });
const [hideWarningCard, setHideWarningCard] = useState(Settings.plugins.ThemeLibrary.hideWarningCard);
const [loading, setLoading] = useState(true);
const getUser = (id: string, username: string) => UserUtils.getUser(id) ?? makeDummyUser({ username, id });
@ -126,22 +133,38 @@ function ThemeTab() {
);
};
const fetchLikes = async () => {
try {
const response = await themeRequest("/likes/get");
const data = await response.json();
return data;
} catch (err) {
logger.error(err);
}
};
useEffect(() => {
Promise.resolve(API_TYPE({}, true)).then(themes => {
setThemes(themes);
setFilteredThemes(themes);
setLoading(false);
}).catch(err => {
console.error("Failed to load 'ThemeLibrary'", err);
setLoading(true);
});
const fetchThemes = async () => {
try {
const themes = await API_TYPE({}, true);
// fetch likes
setThemes(themes);
const likes = await fetchLikes();
setLikedThemes(likes);
setFilteredThemes(themes);
} catch (err) {
logger.error(err);
} finally {
setLoading(false);
}
};
fetchThemes();
}, []);
useEffect(() => {
setThemeLinks(Vencord.Settings.themeLinks);
}, [Vencord.Settings.themeLinks]);
useEffect(() => {
const filteredThemes = themes.filter(themeFilter);
setFilteredThemes(filteredThemes);
@ -160,15 +183,27 @@ function ThemeTab() {
height: "100%",
fontSize: "1.5em",
color: "var(--text-muted)"
}}>Loading Themes...</div>
}}>Loading themes...</div>
) : (
<>
<ErrorCard id="vc-themetab-warning">
<Forms.FormTitle tag="h4">Want your theme removed?</Forms.FormTitle>
<Forms.FormText className={Margins.top8}>
If you want your theme(s) permanently removed, please open an issue on <a href="https://github.com/Faf4a/plugins/issues/new?labels=removal&projects=&template=request_removal.yml&title=Theme+Removal">GitHub <OpenExternalIcon height={16} width={16} /></a>
</Forms.FormText>
</ErrorCard >
{hideWarningCard ? null : (
<ErrorCard id="vc-themetab-warning">
<Forms.FormTitle tag="h4">Want your theme removed?</Forms.FormTitle>
<Forms.FormText className={Margins.top8}>
If you want your theme(s) permanently removed, please open an issue on <a href="https://github.com/Faf4a/plugins/issues/new?labels=removal&projects=&template=request_removal.yml&title=Theme+Removal">GitHub <OpenExternalIcon height={16} width={16} /></a>
</Forms.FormText>
<Button
onClick={() => {
Settings.plugins.ThemeLibrary.hideWarningCard = true;
setHideWarningCard(true);
}}
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
look={Button.Looks.FILLED}
className={Margins.top8}
>Hide</Button>
</ErrorCard >
)}
<div className={`${Margins.bottom8} ${Margins.top16}`}>
<Forms.FormTitle tag="h2"
style={{
@ -291,10 +326,12 @@ function ThemeTab() {
{ label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Themes", value: SearchStatus.THEME },
{ label: "Show Snippets", value: SearchStatus.SNIPPET },
{ label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
// TODO: filter for most liked themes
// { 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 },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
]}
serialize={String}
select={onStatusChange}
@ -385,6 +422,7 @@ function ThemeTab() {
>
Theme Info
</Button>
<LikesComponent themeId={theme.id} likedThemes={likedThemes} />
<Button
onClick={() => {
const content = atob(theme.content);
@ -445,6 +483,9 @@ function SubmitThemes() {
}}>
Submit Themes
</Forms.FormTitle>
<Forms.FormText>
If you plan on updating your theme / snippet frequently, consider using an <code>@import</code> instead!
</Forms.FormText>
<Forms.FormText>
<TextArea
content={themeTemplate}
@ -546,4 +587,4 @@ function ThemeLibrary() {
);
}
export default wrapTab(ThemeLibrary, "Theme Library");
export default wrapTab(ThemeLibrary, "Theme Library");

View file

@ -1,5 +1,10 @@
/* stylelint-disable property-no-vendor-prefix */
@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 */
-webkit-mask: var(--si-widget) center/contain no-repeat !important;
mask: var(--si-widget) center/contain no-repeat !important;
}
@ -52,3 +57,9 @@
border: 2px solid var(--background-tertiary);
max-height: unset;
}
.vce-likes-icon {
overflow: visible;
margin-right: 0.5rem;
transition: fill 0.3s ease;
}

View file

@ -10,11 +10,17 @@ import definePlugin, { OptionType } from "@utils/types";
import { SettingsRouter } from "@webpack/common";
const settings = definePluginSettings({
hideWarningCard: {
type: OptionType.BOOLEAN,
default: false,
description: "Hide the warning card displayed at the top of the theme library tab",
restartNeeded: false,
},
domain: {
type: OptionType.BOOLEAN,
default: false,
description: "Use Github instead of the default domain for themes",
restartNeeded: false
restartNeeded: false,
},
});
@ -26,19 +32,21 @@ export default definePlugin({
toolboxActions: {
"Open Theme Library": () => {
SettingsRouter.open("ThemeLibrary");
}
},
},
start() {
const customSettingsSections = (
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[]; }
Vencord.Plugins.plugins.Settings as any as {
customSections: ((ID: Record<string, unknown>) => any)[];
}
).customSections;
const ThemeSection = () => ({
section: "ThemeLibrary",
label: "Theme Library",
element: require("./components/ThemeTab").default,
id: "ThemeSection"
id: "ThemeSection",
});
customSettingsSections.push(ThemeSection);
@ -46,10 +54,14 @@ export default definePlugin({
stop() {
const customSettingsSections = (
Vencord.Plugins.plugins.Settings as any as { customSections: ((ID: Record<string, unknown>) => any)[]; }
Vencord.Plugins.plugins.Settings as any as {
customSections: ((ID: Record<string, unknown>) => any)[];
}
).customSections;
const i = customSettingsSections.findIndex(section => section({}).id === "ThemeSection");
const i = customSettingsSections.findIndex(
section => section({}).id === "ThemeSection"
);
if (i !== -1) customSettingsSections.splice(i, 1);
},

View file

@ -46,6 +46,11 @@ export const enum TabItem {
SUBMIT_THEMES,
}
export interface LikesComponentProps {
theme: Theme;
userId: User["id"];
}
export const enum SearchStatus {
ALL,
ENABLED,
@ -54,4 +59,14 @@ export const enum SearchStatus {
SNIPPET,
DARK,
LIGHT,
LIKED,
}
export type ThemeLikeProps = {
status: number;
likes: [{
_id?: string;
themeId: number;
userIds: User["id"][];
}];
};