diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts
new file mode 100644
index 000000000..2ce51400d
--- /dev/null
+++ b/src/plugins/_api/dynamicImageModalApi.ts
@@ -0,0 +1,24 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+
+export default definePlugin({
+ name: "DynamicImageModalAPI",
+ authors: [Devs.sadan, Devs.Nuckyz],
+ description: "Allows you to omit either width or height when opening an image modal",
+ patches: [
+ {
+ find: "SCALE_DOWN:",
+ replacement: {
+ match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
+ replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
+ }
+ }
+ ]
+});
diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx
index bf4cf0f37..1029c07e2 100644
--- a/src/plugins/betterRoleContext/index.tsx
+++ b/src/plugins/betterRoleContext/index.tsx
@@ -99,7 +99,11 @@ export default definePlugin({
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
- openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
+ openImageModal({
+ url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
+ height: 128,
+ width: 128
+ });
}}
icon={ImageIcon}
/>
diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx
index 8cca912bc..92b6f57fd 100644
--- a/src/plugins/biggerStreamPreview/index.tsx
+++ b/src/plugins/biggerStreamPreview/index.tsx
@@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
if (!previewUrl) return;
- openImageModal(previewUrl);
+ openImageModal({
+ url: previewUrl,
+ height: 720,
+ width: 1280
+ });
};
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx
index fb8df2ce1..a0d138cda 100644
--- a/src/plugins/serverInfo/GuildInfoModal.tsx
+++ b/src/plugins/serverInfo/GuildInfoModal.tsx
@@ -80,7 +80,10 @@ function GuildInfoModal({ guild }: GuildProps) {
className={cl("banner")}
src={bannerUrl}
alt=""
- onClick={() => openImageModal(bannerUrl)}
+ onClick={() => openImageModal({
+ url: bannerUrl,
+ width: 1024
+ })}
/>
)}
@@ -89,7 +92,11 @@ function GuildInfoModal({ guild }: GuildProps) {
? openImageModal(iconUrl)}
+ onClick={() => openImageModal({
+ url: iconUrl,
+ height: 512,
+ width: 512,
+ })}
/>
:
{guild.acronym}
}
@@ -151,7 +158,15 @@ function Owner(guildId: string, owner: User) {
return (
-
openImageModal(ownerAvatarUrl)} />
+
openImageModal({
+ url: ownerAvatarUrl,
+ height: 512,
+ width: 512
+ })}
+ />
{Parser.parse(`<@${owner.id}>`)}
);
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index aef0c7362..6eb901b54 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -229,7 +229,11 @@ function AlbumContextMenu({ track }: { track: Track; }) {
id="view-cover"
label="View Album Cover"
// trolley
- action={() => openImageModal(track.album.image.url)}
+ action={() => openImageModal({
+ url: track.album.image.url,
+ height: 512,
+ width: 512
+ })}
icon={ImageIcon}
/>
openImage(url, 512, 512);
+const openBanner = (url: string) => openImage(url, 1024);
+
+function openImage(url: string, width: number, height?: number) {
const format = url.startsWith("/") ? "png" : settings.store.format;
const u = new URL(url, window.location.href);
@@ -76,11 +79,13 @@ function openImage(url: string) {
url = u.toString();
u.searchParams.set("size", "4096");
- const originalUrl = u.toString();
+ const original = u.toString();
- openImageModal(url, {
- original: originalUrl,
- height: 256
+ openImageModal({
+ url,
+ original,
+ width,
+ height
});
}
@@ -93,14 +98,14 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
openImage(IconUtils.getUserAvatarURL(user, true))}
+ action={() => openAvatar(IconUtils.getUserAvatarURL(user, true))}
icon={ImageIcon}
/>
{memberAvatar && (
openImage(IconUtils.getGuildMemberAvatarURLSimple({
+ action={() => openAvatar(IconUtils.getGuildMemberAvatarURLSimple({
userId: user.id,
avatar: memberAvatar,
guildId: guildId!,
@@ -126,7 +131,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
id="view-icon"
label="View Icon"
action={() =>
- openImage(IconUtils.getGuildIconURL({
+ openAvatar(IconUtils.getGuildIconURL({
id,
icon,
canAnimate: true
@@ -140,7 +145,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
id="view-banner"
label="View Banner"
action={() =>
- openImage(IconUtils.getGuildBannerURL(guild, true)!)
+ openBanner(IconUtils.getGuildBannerURL(guild, true)!)
}
icon={ImageIcon}
/>
@@ -158,7 +163,7 @@ const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: Grou
id="view-group-channel-icon"
label="View Icon"
action={() =>
- openImage(IconUtils.getChannelIconURL(channel)!)
+ openAvatar(IconUtils.getChannelIconURL(channel)!)
}
icon={ImageIcon}
/>
@@ -171,10 +176,12 @@ export default definePlugin({
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.",
tags: ["ImageUtilities"],
+ dependencies: ["DynamicImageModalAPI"],
settings,
- openImage,
+ openAvatar,
+ openBanner,
contextMenus: {
"user-context": UserContext,
@@ -188,7 +195,7 @@ export default definePlugin({
find: ".overlay:void 0,status:",
replacement: {
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
- replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)},"
+ replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},"
},
all: true
},
@@ -197,7 +204,7 @@ export default definePlugin({
find: 'backgroundColor:"COMPLETE"',
replacement: {
match: /(\.banner,.+?),style:{(?=.+?backgroundImage:null!=(\i)\?"url\("\.concat\(\2,)/,
- replace: (_, rest, bannerSrc) => `${rest},onClick:()=>${bannerSrc}!=null&&$self.openImage(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,`
+ replace: (_, rest, bannerSrc) => `${rest},onClick:()=>${bannerSrc}!=null&&$self.openBanner(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,`
}
},
// Group DMs top small & large icon
@@ -205,7 +212,7 @@ export default definePlugin({
find: /\.recipients\.length>=2(?! `${m},onClick:()=>$self.openImage(${iconUrl})`
+ replace: (m, iconUrl) => `${m},onClick:()=>$self.openAvatar(${iconUrl})`
}
},
// User DMs top small icon
@@ -213,7 +220,7 @@ export default definePlugin({
find: ".cursorPointer:null,children",
replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
- replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
+ replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
}
},
// User Dms top large icon
@@ -221,7 +228,7 @@ export default definePlugin({
find: 'experimentLocation:"empty_messages"',
replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
- replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
+ replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
}
}
]
diff --git a/src/utils/discord.css b/src/utils/discord.css
new file mode 100644
index 000000000..9b03bf7b7
--- /dev/null
+++ b/src/utils/discord.css
@@ -0,0 +1,3 @@
+.vc-position-inherit {
+ position: inherit;
+}
diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx
index 4c7cc38a0..099a1e53e 100644
--- a/src/utils/discord.tsx
+++ b/src/utils/discord.tsx
@@ -16,11 +16,13 @@
* along with this program. If not, see .
*/
+import "./discord.css";
+
import { MessageObject } from "@api/MessageEvents";
-import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
+import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Channel, Guild, Message, User } from "discord-types/general";
-import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
+import { ImageModal, ImageModalItem, openModal } from "./modal";
/**
* Open the invite modal
@@ -108,25 +110,23 @@ export function sendMessage(
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
}
-export function openImageModal(url: string, props?: Partial>): string {
+/**
+ * You must specify either height or width
+ */
+export function openImageModal(props: Omit): string {
return openModal(modalProps => (
-
- }
- // Don't render forward message button
- renderForwardComponent={() => null}
- shouldHideMediaOptions={false}
- shouldAnimate
- {...props}
- />
-
+ fit="vc-position-inherit"
+ items={[{
+ type: "IMAGE",
+ original: props.url,
+ ...props,
+ }]}
+ onClose={modalProps.onClose}
+ shouldHideMediaOptions={false}
+ shouldAnimate
+ />
));
}
diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx
index 79f777088..7459379ea 100644
--- a/src/utils/modal.tsx
+++ b/src/utils/modal.tsx
@@ -101,25 +101,24 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
}>;
};
-export type ImageModal = ComponentType<{
- className?: string;
- src: string;
- placeholder: string;
- original: string;
+export interface ImageModalItem {
+ type: "IMAGE" | "VIDEO";
+ url: string;
width?: number;
height?: number;
- animated?: boolean;
- responsive?: boolean;
- renderLinkComponent(props: any): ReactNode;
- renderForwardComponent(props: any): ReactNode;
- maxWidth?: number;
- maxHeight?: number;
- shouldAnimate?: boolean;
+ original?: string;
+}
+
+export type ImageModal = ComponentType<{
+ className?: string;
+ fit?: string;
onClose?(): void;
shouldHideMediaOptions?: boolean;
+ shouldAnimate?: boolean;
+ items: ImageModalItem[];
}>;
-export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE", "responsive") as ImageModal;
+export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE") as ImageModal;
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);