add plugin favourites

This commit is contained in:
Vendicated 2026-07-27 20:57:31 +02:00
parent 83b74e2305
commit 67ed651e28
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
9 changed files with 134 additions and 59 deletions

View file

@ -68,6 +68,7 @@ export interface Settings {
plugins: {
[plugin: string]: {
enabled: boolean;
isFavorite?: boolean;
[setting: string]: any;
};
};

View file

@ -639,3 +639,31 @@ export function RightArrow(props: IconProps) {
</Icon>
);
}
export function StarFilled(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M10.81 2.86c.38-1.15 2-1.15 2.38 0l1.89 5.83h6.12c1.2 0 1.71 1.54.73 2.25l-4.95 3.6 1.9 5.82a1.25 1.25 0 0 1-1.93 1.4L12 18.16l-4.95 3.6c-.98.7-2.3-.25-1.92-1.4l1.89-5.82-4.95-3.6a1.25 1.25 0 0 1 .73-2.25h6.12l1.9-5.83Z"
/>
</Icon>
);
}
export function StarOutlined(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
fillRule="evenodd"
d="M2.07 10.94a1.25 1.25 0 0 1 .73-2.25h6.12l1.9-5.83c.37-1.15 2-1.15 2.37 0l1.89 5.83h6.12c1.2 0 1.71 1.54.73 2.25l-4.95 3.6 1.9 5.82a1.25 1.25 0 0 1-1.93 1.4L12 18.16l-4.95 3.6c-.98.7-2.3-.25-1.92-1.4l1.89-5.82-4.95-3.6Zm11.55-.25h5.26l-4.25 3.09 1.62 5-4.25-3.1-4.25 3.1 1.62-5-4.25-3.1h5.26l1.62-5 1.62 5Z" />
</Icon>
);
}

View file

@ -13,12 +13,12 @@ import { classNameFactory } from "@utils/css";
import { fetchUserProfile } from "@utils/discord";
import { classes, pluralise } from "@utils/misc";
import { RenderModalProps, User } from "@vencord/discord-types";
import { Forms, Modal,openModal, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { Forms, Modal, openModal, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import Plugins from "~plugins";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
import { PluginCard } from "./PluginCard";
import { GithubButton, WebsiteButton } from "./PluginModalButtons";
const cl = classNameFactory("vc-author-modal-");

View file

@ -1,13 +0,0 @@
.vc-settings-modal-link-icon {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-base-lowest);
box-sizing: border-box
}
.vc-settings-modal-links {
display: flex;
gap: 0.2em;
margin-left: auto;
}

View file

@ -1,41 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./LinkIconButton.css";
import { GithubIcon, WebsiteIcon } from "@components/Icons";
import { getTheme, Theme } from "@utils/discord";
import { MaskedLink, Tooltip } from "@webpack/common";
export function GithubLinkIcon() {
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
export function WebsiteLinkIcon() {
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
interface Props {
text: string;
href: string;
}
function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
return (
<Tooltip text={text}>
{props => (
<MaskedLink {...props} href={href}>
<Icon />
</MaskedLink>
)}
</Tooltip>
);
}
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;

View file

@ -32,14 +32,14 @@ import { classes } from "@utils/misc";
import { OptionType, Plugin, PluginTag } from "@utils/types";
import { RenderModalProps, User } from "@vencord/discord-types";
import { findCssClassesLazy } from "@webpack";
import { Clickable, FluxDispatcher, Forms, Modal,openModal, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Clickable, FluxDispatcher, Forms, Modal, openModal, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins";
import { OptionComponentMap } from "./components";
import { openContributorModal } from "./ContributorModal";
import { GithubButton, WebsiteButton } from "./LinkIconButton";
import { FavoriteButton, GithubButton, WebsiteButton } from "./PluginModalButtons";
const cl = classNameFactory("vc-plugin-modal-");
@ -176,6 +176,10 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<BaseText tag="h1" weight="semibold" size="lg">{plugin.name}</BaseText>
{!pluginMeta.userPlugin && (
<div className="vc-settings-modal-links">
<FavoriteButton
isFavorite={pluginSettings.isFavorite ?? false}
onClick={() => pluginSettings.isFavorite = !pluginSettings.isFavorite}
/>
<WebsiteButton
text="View more info"
href={`https://vencord.dev/plugins/${plugin.name}`}

View file

@ -0,0 +1,37 @@
.vc-settings-modal-light {
color: black;
}
.vc-settings-modal-dark {
color: white;
}
.vc-settings-modal-favorite-button {
cursor: pointer;
transition: color 0.2s ease-in-out;
}
.vc-settings-modal-favorite-button-active,
.vc-settings-modal-favorite-button:hover {
color: var(--yellow-300);
}
.vc-settings-modal-link-icon {
height: 32px;
width: 32px;
border-radius: 50%;
border: 4px solid var(--background-base-lowest);
box-sizing: border-box
}
.vc-settings-modal-favorite-icon {
background: var(--background-base-lowest);
padding: 1px;
}
.vc-settings-modal-links {
display: flex;
gap: 0.2em;
margin-left: auto;
}

View file

@ -0,0 +1,53 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./PluginModalButtons.css";
import { GithubIcon, StarFilled, StarOutlined, WebsiteIcon } from "@components/Icons";
import { classNameFactory } from "@utils/css";
import { getTheme, Theme } from "@utils/discord";
import { Clickable, MaskedLink, Tooltip } from "@webpack/common";
const cl = classNameFactory("vc-settings-modal-");
const getThemeClass = () => getTheme() !== Theme.Light ? "dark" : "light";
interface Props {
text: string;
href: string;
}
function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
return (
<Tooltip text={text}>
{props =>
<MaskedLink {...props} href={href} className={cl("link-button", getThemeClass())}>
<Icon />
</MaskedLink>
}
</Tooltip>
);
}
const GithubLinkIcon = () => <GithubIcon aria-hidden className={cl("link-icon")} />;
const WebsiteLinkIcon = () => <WebsiteIcon aria-hidden className={cl("link-icon")} />;
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
export function FavoriteButton({ isFavorite, onClick }: { isFavorite: boolean; onClick: () => void; }) {
const Icon = isFavorite ? StarFilled : StarOutlined;
return (
<Tooltip text={isFavorite ? "Unfavorite plugin" : "Favorite plugin - pins it to the top of the plugin list"}>
{props =>
<Clickable {...props} onClick={onClick} className={cl("favorite-button", isFavorite && "favorite-button-active", getThemeClass())}>
<Icon aria-hidden className={cl("link-icon", "favorite-icon")} />
</Clickable>
}
</Tooltip>
);
}

View file

@ -74,6 +74,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
const enum SearchStatus {
ALL,
FAVORITES,
ENABLED,
DISABLED,
NEW,
@ -160,7 +161,8 @@ function PluginSettings() {
const sortedPlugins = useMemo(() =>
Object.values(Plugins).sort((a, b) => a.name.localeCompare(b.name)),
[]
);
)
.toSorted((a, b) => Number(settings.plugins[b.name]?.isFavorite ?? false) - Number(settings.plugins[a.name]?.isFavorite ?? false));
const hasUserPlugins = useMemo(() => !IS_STANDALONE && Object.values(PluginMeta).some(m => m.userPlugin), []);
@ -173,6 +175,9 @@ function PluginSettings() {
const { status, tags } = searchValue;
switch (status) {
case SearchStatus.FAVORITES:
if (!settings.plugins[plugin.name]?.isFavorite) return false;
break;
case SearchStatus.DISABLED:
if (isPluginEnabled(plugin.name)) return false;
break;
@ -288,6 +293,7 @@ function PluginSettings() {
<Select
options={[
{ label: "Show All", value: SearchStatus.ALL, default: true },
{ label: "Show Favorites", value: SearchStatus.FAVORITES },
{ label: "Show Enabled", value: SearchStatus.ENABLED },
{ label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show New", value: SearchStatus.NEW },