PluginModals: add plugin website & source code links
This commit is contained in:
parent
76f6912511
commit
3ce241021f
|
@ -89,15 +89,20 @@ export const globPlugins = kind => ({
|
||||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||||
let code = "";
|
let code = "";
|
||||||
let plugins = "\n";
|
let plugins = "\n";
|
||||||
|
let meta = "\n";
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
if (!await exists(`./src/${dir}`)) continue;
|
const userPlugin = dir === "userplugins";
|
||||||
const files = await readdir(`./src/${dir}`);
|
|
||||||
for (const file of files) {
|
if (!await exists(`./src/${dir}`)) continue;
|
||||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
const files = await readdir(`./src/${dir}`, { withFileTypes: true });
|
||||||
if (file === "index.ts") continue;
|
for (const file of files) {
|
||||||
|
const fileName = file.name;
|
||||||
|
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
|
||||||
|
if (fileName === "index.ts") continue;
|
||||||
|
|
||||||
|
const target = getPluginTarget(fileName);
|
||||||
|
|
||||||
const target = getPluginTarget(file);
|
|
||||||
if (target && !IS_REPORTER) {
|
if (target && !IS_REPORTER) {
|
||||||
if (target === "dev" && !watch) continue;
|
if (target === "dev" && !watch) continue;
|
||||||
if (target === "web" && kind === "discordDesktop") continue;
|
if (target === "web" && kind === "discordDesktop") continue;
|
||||||
|
@ -106,13 +111,16 @@ export const globPlugins = kind => ({
|
||||||
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
|
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
|
||||||
|
|
||||||
const mod = `p${i}`;
|
const mod = `p${i}`;
|
||||||
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
|
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
||||||
plugins += `[${mod}.name]:${mod},\n`;
|
plugins += `[${mod}.name]:${mod},\n`;
|
||||||
|
meta += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code += `export default {${plugins}};`;
|
code += `export default {${plugins}};export const PluginMeta={${meta}};`;
|
||||||
return {
|
return {
|
||||||
contents: code,
|
contents: code,
|
||||||
resolveDir: "./src"
|
resolveDir: "./src"
|
||||||
|
|
|
@ -11,20 +11,16 @@ import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { DevsById } from "@utils/constants";
|
import { DevsById } from "@utils/constants";
|
||||||
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
|
import { fetchUserProfile } from "@utils/discord";
|
||||||
import { pluralise } from "@utils/misc";
|
import { classes, pluralise } from "@utils/misc";
|
||||||
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||||
import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
import { PluginCard } from ".";
|
import { PluginCard } from ".";
|
||||||
|
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-author-modal-");
|
const cl = classNameFactory("vc-author-modal-");
|
||||||
|
|
||||||
|
@ -40,16 +36,6 @@ export function openContributorModal(user: User) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GithubIcon() {
|
|
||||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
|
||||||
return <img src={src} alt="GitHub" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function WebsiteIcon() {
|
|
||||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
|
||||||
return <img src={src} alt="Website" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContributorModal({ user }: { user: User; }) {
|
function ContributorModal({ user }: { user: User; }) {
|
||||||
useSettings();
|
useSettings();
|
||||||
|
|
||||||
|
@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
|
||||||
/>
|
/>
|
||||||
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("links")}>
|
<div className={classes("vc-settings-modal-links", cl("links"))}>
|
||||||
{website && (
|
{website && (
|
||||||
<Tooltip text={website}>
|
<WebsiteButton
|
||||||
{props => (
|
text={website}
|
||||||
<MaskedLink {...props} href={"https://" + website}>
|
href={`https://${website}`}
|
||||||
<WebsiteIcon />
|
/>
|
||||||
</MaskedLink>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
{githubName && (
|
{githubName && (
|
||||||
<Tooltip text={githubName}>
|
<GithubButton
|
||||||
{props => (
|
text={githubName}
|
||||||
<MaskedLink {...props} href={`https://github.com/${githubName}`}>
|
href={`https://github.com/${githubName}`}
|
||||||
<GithubIcon />
|
/>
|
||||||
</MaskedLink>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
12
src/components/PluginSettings/LinkIconButton.css
Normal file
12
src/components/PluginSettings/LinkIconButton.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.vc-settings-modal-link-icon {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid var(--background-tertiary);
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-modal-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
45
src/components/PluginSettings/LinkIconButton.tsx
Normal file
45
src/components/PluginSettings/LinkIconButton.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
|
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||||
|
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
|
export function GithubIcon() {
|
||||||
|
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||||
|
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebsiteIcon() {
|
||||||
|
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
||||||
|
return <img src={src} aria-hidden 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={WebsiteIcon} />;
|
||||||
|
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubIcon} />;
|
7
src/components/PluginSettings/PluginModal.css
Normal file
7
src/components/PluginSettings/PluginModal.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.vc-plugin-modal-info {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-plugin-modal-description {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
|
@ -16,10 +16,14 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./PluginModal.css";
|
||||||
|
|
||||||
import { generateId } from "@api/Commands";
|
import { generateId } from "@api/Commands";
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
|
@ -30,6 +34,8 @@ import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserSto
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
|
@ -40,6 +46,9 @@ import {
|
||||||
SettingTextComponent
|
SettingTextComponent
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import { openContributorModal } from "./ContributorModal";
|
import { openContributorModal } from "./ContributorModal";
|
||||||
|
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-plugin-modal-");
|
||||||
|
|
||||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
|
@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function switchToPopout() {
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
|
||||||
|
PopoutActions.open(
|
||||||
|
PopoutKey,
|
||||||
|
() => <PluginModal
|
||||||
|
transitionState={transitionState}
|
||||||
|
plugin={plugin}
|
||||||
|
onRestartNeeded={onRestartNeeded}
|
||||||
|
onClose={() => PopoutActions.close(PopoutKey)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pluginMeta = PluginMeta[plugin.name];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
||||||
<ModalHeader separator={false}>
|
<ModalHeader separator={false}>
|
||||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
<Button look={Button.Looks.BLANK} onClick={switchToPopout}>
|
||||||
|
<OpenExternalIcon aria-label="Open in Popout" />
|
||||||
|
</Button>
|
||||||
|
*/}
|
||||||
<ModalCloseButton onClick={onClose} />
|
<ModalCloseButton onClick={onClose} />
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
<Flex className={cl("info")}>
|
||||||
<Forms.FormText>{plugin.description}</Forms.FormText>
|
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
|
||||||
|
{!pluginMeta.userPlugin && (
|
||||||
|
<div className="vc-settings-modal-links">
|
||||||
|
<WebsiteButton
|
||||||
|
text="View more info"
|
||||||
|
href={`https://vencord.dev/plugins/${plugin.name}`}
|
||||||
|
/>
|
||||||
|
<GithubButton
|
||||||
|
text="View source code"
|
||||||
|
href={`https://github.com/${gitRemote}/tree/main/src/plugins/${pluginMeta.folderName}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
||||||
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
||||||
<UserSummaryItem
|
<UserSummaryItem
|
||||||
|
|
|
@ -42,16 +42,6 @@
|
||||||
|
|
||||||
.vc-author-modal-links {
|
.vc-author-modal-links {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: flex;
|
|
||||||
gap: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-author-modal-links img {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 4px solid var(--background-tertiary);
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-plugins {
|
.vc-author-modal-plugins {
|
||||||
|
|
4
src/modules.d.ts
vendored
4
src/modules.d.ts
vendored
|
@ -22,6 +22,10 @@
|
||||||
declare module "~plugins" {
|
declare module "~plugins" {
|
||||||
const plugins: Record<string, import("./utils/types").Plugin>;
|
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||||
export default plugins;
|
export default plugins;
|
||||||
|
export const PluginMeta: Record<string, {
|
||||||
|
folderName: string;
|
||||||
|
userPlugin: boolean;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "~pluginNatives" {
|
declare module "~pluginNatives" {
|
||||||
|
|
|
@ -38,7 +38,7 @@ const enum ModalTransitionState {
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
transitionState: ModalTransitionState;
|
transitionState: ModalTransitionState;
|
||||||
onClose(): Promise<void>;
|
onClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModalOptions {
|
export interface ModalOptions {
|
||||||
|
|
35
src/webpack/common/types/utils.d.ts
vendored
35
src/webpack/common/types/utils.d.ts
vendored
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import { Guild, GuildMember } from "discord-types/general";
|
import { Guild, GuildMember } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
import type { FluxEvents } from "./fluxEvents";
|
import type { FluxEvents } from "./fluxEvents";
|
||||||
import { i18nMessages } from "./i18nMessages";
|
import { i18nMessages } from "./i18nMessages";
|
||||||
|
@ -221,3 +222,37 @@ export interface Constants {
|
||||||
UserFlags: Record<string, number>;
|
UserFlags: Record<string, number>;
|
||||||
FriendsSections: Record<string, string>;
|
FriendsSections: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExpressionPickerStore {
|
||||||
|
closeExpressionPicker(activeViewType?: any): void;
|
||||||
|
openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrowserWindowFeatures {
|
||||||
|
toolbar?: boolean;
|
||||||
|
menubar?: boolean;
|
||||||
|
location?: boolean;
|
||||||
|
directories?: boolean;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
defaultWidth?: number;
|
||||||
|
defaultHeight?: number;
|
||||||
|
left?: number;
|
||||||
|
top?: number;
|
||||||
|
defaultAlwaysOnTop?: boolean;
|
||||||
|
movable?: boolean;
|
||||||
|
resizable?: boolean;
|
||||||
|
frame?: boolean;
|
||||||
|
alwaysOnTop?: boolean;
|
||||||
|
hasShadow?: boolean;
|
||||||
|
transparent?: boolean;
|
||||||
|
skipTaskbar?: boolean;
|
||||||
|
titleBarStyle?: string | null;
|
||||||
|
backgroundColor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopoutActions {
|
||||||
|
open(key: string, render: (windowKey: string) => ReactNode, features?: BrowserWindowFeatures);
|
||||||
|
close(key: string): void;
|
||||||
|
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
||||||
|
}
|
||||||
|
|
|
@ -160,9 +160,15 @@ export const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
|
|
||||||
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
||||||
|
|
||||||
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i/);
|
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/);
|
||||||
// TODO: type
|
// TODO: type
|
||||||
export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
||||||
closeExpressionPicker: filters.byCode("setState({activeView:null"),
|
closeExpressionPicker: filters.byCode("setState({activeView:null"),
|
||||||
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
|
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {
|
||||||
|
open: filters.byCode('type:"POPOUT_WINDOW_OPEN"'),
|
||||||
|
close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
|
||||||
|
setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue