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);