Fix ViewIcons & plugins that use image modals
Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com>
This commit is contained in:
parent
58c3032bb2
commit
a11ccde40f
9 changed files with 114 additions and 54 deletions
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
24
src/plugins/_api/dynamicImageModalApi.ts
Normal file
|
@ -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}))`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -99,7 +99,11 @@ export default definePlugin({
|
||||||
id="vc-view-role-icon"
|
id="vc-view-role-icon"
|
||||||
label="View Role Icon"
|
label="View Role Icon"
|
||||||
action={() => {
|
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}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
|
||||||
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
||||||
if (!previewUrl) return;
|
if (!previewUrl) return;
|
||||||
|
|
||||||
openImageModal(previewUrl);
|
openImageModal({
|
||||||
|
url: previewUrl,
|
||||||
|
height: 720,
|
||||||
|
width: 1280
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
||||||
|
|
|
@ -80,7 +80,10 @@ function GuildInfoModal({ guild }: GuildProps) {
|
||||||
className={cl("banner")}
|
className={cl("banner")}
|
||||||
src={bannerUrl}
|
src={bannerUrl}
|
||||||
alt=""
|
alt=""
|
||||||
onClick={() => openImageModal(bannerUrl)}
|
onClick={() => openImageModal({
|
||||||
|
url: bannerUrl,
|
||||||
|
width: 1024
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -89,7 +92,11 @@ function GuildInfoModal({ guild }: GuildProps) {
|
||||||
? <img
|
? <img
|
||||||
src={iconUrl}
|
src={iconUrl}
|
||||||
alt=""
|
alt=""
|
||||||
onClick={() => openImageModal(iconUrl)}
|
onClick={() => openImageModal({
|
||||||
|
url: iconUrl,
|
||||||
|
height: 512,
|
||||||
|
width: 512,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
: <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{guild.acronym}</div>
|
: <div aria-hidden className={classes(IconClasses.childWrapper, IconClasses.acronym)}>{guild.acronym}</div>
|
||||||
}
|
}
|
||||||
|
@ -151,7 +158,15 @@ function Owner(guildId: string, owner: User) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cl("owner")}>
|
<div className={cl("owner")}>
|
||||||
<img src={ownerAvatarUrl} alt="" onClick={() => openImageModal(ownerAvatarUrl)} />
|
<img
|
||||||
|
src={ownerAvatarUrl}
|
||||||
|
alt=""
|
||||||
|
onClick={() => openImageModal({
|
||||||
|
url: ownerAvatarUrl,
|
||||||
|
height: 512,
|
||||||
|
width: 512
|
||||||
|
})}
|
||||||
|
/>
|
||||||
{Parser.parse(`<@${owner.id}>`)}
|
{Parser.parse(`<@${owner.id}>`)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -229,7 +229,11 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
||||||
id="view-cover"
|
id="view-cover"
|
||||||
label="View Album Cover"
|
label="View Album Cover"
|
||||||
// trolley
|
// trolley
|
||||||
action={() => openImageModal(track.album.image.url)}
|
action={() => openImageModal({
|
||||||
|
url: track.album.image.url,
|
||||||
|
height: 512,
|
||||||
|
width: 512
|
||||||
|
})}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
<Menu.MenuControlItem
|
<Menu.MenuControlItem
|
||||||
|
|
|
@ -67,7 +67,10 @@ const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function openImage(url: string) {
|
const openAvatar = (url: string) => 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 format = url.startsWith("/") ? "png" : settings.store.format;
|
||||||
|
|
||||||
const u = new URL(url, window.location.href);
|
const u = new URL(url, window.location.href);
|
||||||
|
@ -76,11 +79,13 @@ function openImage(url: string) {
|
||||||
url = u.toString();
|
url = u.toString();
|
||||||
|
|
||||||
u.searchParams.set("size", "4096");
|
u.searchParams.set("size", "4096");
|
||||||
const originalUrl = u.toString();
|
const original = u.toString();
|
||||||
|
|
||||||
openImageModal(url, {
|
openImageModal({
|
||||||
original: originalUrl,
|
url,
|
||||||
height: 256
|
original,
|
||||||
|
width,
|
||||||
|
height
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,14 +98,14 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-avatar"
|
id="view-avatar"
|
||||||
label="View Avatar"
|
label="View Avatar"
|
||||||
action={() => openImage(IconUtils.getUserAvatarURL(user, true))}
|
action={() => openAvatar(IconUtils.getUserAvatarURL(user, true))}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
{memberAvatar && (
|
{memberAvatar && (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="view-server-avatar"
|
id="view-server-avatar"
|
||||||
label="View Server Avatar"
|
label="View Server Avatar"
|
||||||
action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({
|
action={() => openAvatar(IconUtils.getGuildMemberAvatarURLSimple({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
avatar: memberAvatar,
|
avatar: memberAvatar,
|
||||||
guildId: guildId!,
|
guildId: guildId!,
|
||||||
|
@ -126,7 +131,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-icon"
|
id="view-icon"
|
||||||
label="View Icon"
|
label="View Icon"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(IconUtils.getGuildIconURL({
|
openAvatar(IconUtils.getGuildIconURL({
|
||||||
id,
|
id,
|
||||||
icon,
|
icon,
|
||||||
canAnimate: true
|
canAnimate: true
|
||||||
|
@ -140,7 +145,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
||||||
id="view-banner"
|
id="view-banner"
|
||||||
label="View Banner"
|
label="View Banner"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(IconUtils.getGuildBannerURL(guild, true)!)
|
openBanner(IconUtils.getGuildBannerURL(guild, true)!)
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
@ -158,7 +163,7 @@ const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: Grou
|
||||||
id="view-group-channel-icon"
|
id="view-group-channel-icon"
|
||||||
label="View Icon"
|
label="View Icon"
|
||||||
action={() =>
|
action={() =>
|
||||||
openImage(IconUtils.getChannelIconURL(channel)!)
|
openAvatar(IconUtils.getChannelIconURL(channel)!)
|
||||||
}
|
}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
@ -171,10 +176,12 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
|
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.",
|
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"],
|
tags: ["ImageUtilities"],
|
||||||
|
dependencies: ["DynamicImageModalAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
openImage,
|
openAvatar,
|
||||||
|
openBanner,
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"user-context": UserContext,
|
"user-context": UserContext,
|
||||||
|
@ -188,7 +195,7 @@ export default definePlugin({
|
||||||
find: ".overlay:void 0,status:",
|
find: ".overlay:void 0,status:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
|
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
|
all: true
|
||||||
},
|
},
|
||||||
|
@ -197,7 +204,7 @@ export default definePlugin({
|
||||||
find: 'backgroundColor:"COMPLETE"',
|
find: 'backgroundColor:"COMPLETE"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\.banner,.+?),style:{(?=.+?backgroundImage:null!=(\i)\?"url\("\.concat\(\2,)/,
|
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
|
// Group DMs top small & large icon
|
||||||
|
@ -205,7 +212,7 @@ export default definePlugin({
|
||||||
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
|
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
|
match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
|
||||||
replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})`
|
replace: (m, iconUrl) => `${m},onClick:()=>$self.openAvatar(${iconUrl})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User DMs top small icon
|
// User DMs top small icon
|
||||||
|
@ -213,7 +220,7 @@ export default definePlugin({
|
||||||
find: ".cursorPointer:null,children",
|
find: ".cursorPointer:null,children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
||||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
|
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User Dms top large icon
|
// User Dms top large icon
|
||||||
|
@ -221,7 +228,7 @@ export default definePlugin({
|
||||||
find: 'experimentLocation:"empty_messages"',
|
find: 'experimentLocation:"empty_messages"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
||||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
|
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
3
src/utils/discord.css
Normal file
3
src/utils/discord.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.vc-position-inherit {
|
||||||
|
position: inherit;
|
||||||
|
}
|
|
@ -16,11 +16,13 @@
|
||||||
* 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 "./discord.css";
|
||||||
|
|
||||||
import { MessageObject } from "@api/MessageEvents";
|
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 { 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
|
* Open the invite modal
|
||||||
|
@ -108,25 +110,23 @@ export function sendMessage(
|
||||||
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
|
return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openImageModal(url: string, props?: Partial<React.ComponentProps<ImageModal>>): string {
|
/**
|
||||||
|
* You must specify either height or width
|
||||||
|
*/
|
||||||
|
export function openImageModal(props: Omit<ImageModalItem, "type">): string {
|
||||||
return openModal(modalProps => (
|
return openModal(modalProps => (
|
||||||
<ModalRoot
|
<ImageModal
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
className={ModalImageClasses.modal}
|
fit="vc-position-inherit"
|
||||||
size={ModalSize.DYNAMIC}>
|
items={[{
|
||||||
<ImageModal
|
type: "IMAGE",
|
||||||
className={ModalImageClasses.image}
|
original: props.url,
|
||||||
original={url}
|
...props,
|
||||||
placeholder={url}
|
}]}
|
||||||
src={url}
|
onClose={modalProps.onClose}
|
||||||
renderLinkComponent={props => <MaskedLink {...props} />}
|
shouldHideMediaOptions={false}
|
||||||
// Don't render forward message button
|
shouldAnimate
|
||||||
renderForwardComponent={() => null}
|
/>
|
||||||
shouldHideMediaOptions={false}
|
|
||||||
shouldAnimate
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</ModalRoot>
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,25 +101,24 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImageModal = ComponentType<{
|
export interface ImageModalItem {
|
||||||
className?: string;
|
type: "IMAGE" | "VIDEO";
|
||||||
src: string;
|
url: string;
|
||||||
placeholder: string;
|
|
||||||
original: string;
|
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
animated?: boolean;
|
original?: string;
|
||||||
responsive?: boolean;
|
}
|
||||||
renderLinkComponent(props: any): ReactNode;
|
|
||||||
renderForwardComponent(props: any): ReactNode;
|
export type ImageModal = ComponentType<{
|
||||||
maxWidth?: number;
|
className?: string;
|
||||||
maxHeight?: number;
|
fit?: string;
|
||||||
shouldAnimate?: boolean;
|
|
||||||
onClose?(): void;
|
onClose?(): void;
|
||||||
shouldHideMediaOptions?: boolean;
|
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 ModalRoot = LazyComponent(() => Modals.ModalRoot);
|
||||||
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
|
||||||
|
|
Loading…
Reference in a new issue