From cfe41ef656085e3b3ed2e0ac68020b546045b55b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 12 Apr 2023 03:27:31 +0200 Subject: [PATCH] ViewIcons: Add format setting & user context menu + cleanup --- src/plugins/viewIcons.tsx | 206 +++++++++++++++++++++++++++----------- 1 file changed, 146 insertions(+), 60 deletions(-) diff --git a/src/plugins/viewIcons.tsx b/src/plugins/viewIcons.tsx index b60fb759..8b3d31d3 100644 --- a/src/plugins/viewIcons.tsx +++ b/src/plugins/viewIcons.tsx @@ -16,40 +16,161 @@ * along with this program. If not, see . */ +import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/settings"; import { Devs } from "@utils/constants"; import { LazyComponent } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { find, findByCode, findByPropsLazy } from "@webpack"; -import { Menu } from "@webpack/common"; -import type { Guild } from "discord-types/general"; +import { GuildMemberStore, Menu } from "@webpack/common"; +import type { Channel, Guild, User } from "discord-types/general"; const ImageModal = LazyComponent(() => findByCode(".MEDIA_MODAL_CLOSE,")); const MaskedLink = LazyComponent(() => find(m => m.type?.toString().includes("MASKED_LINK)"))); +const BannerStore = findByPropsLazy("getGuildBannerURL"); -const GuildBannerStore = findByPropsLazy("getGuildBannerURL"); +interface UserContextProps { + channel: Channel; + guildId?: string; + user: User; +} + +interface GuildContextProps { + guild: Guild; +} + +const settings = definePluginSettings({ + format: { + type: OptionType.SELECT, + description: "Choose the image format to use for non animated images. Animated images will always use .gif", + options: [ + { + label: "webp", + value: "webp", + default: true + }, + { + label: "png", + value: "png", + }, + { + label: "jpg", + value: "jpg", + } + ] + } +}); + +function openImage(url: string) { + const u = new URL(url); + u.searchParams.set("size", "512"); + u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${settings.store.format}`); + url = u.toString(); + + openModal(modalProps => ( + + + + )); +} + +const seen = new WeakSet(); + +const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => { + if (seen.has(children)) return; + seen.add(children); + + const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null; + + children.splice(1, 0, ( + + openImage(BannerStore.getUserAvatarURL(user, true, 512))} + /> + {memberAvatar && ( + openImage(BannerStore.getGuildMemberAvatarURLSimple({ + userId: user.id, + avatar: memberAvatar, + guildId + }, true))} + /> + )} + + )); +}; + +const GuildContext: NavContextMenuPatchCallback = (children, { guild: { id, icon, banner } }: GuildContextProps) => { + if (seen.has(children)) return; + seen.add(children); + + if (!banner && !icon) return; + + // before copy id (if it exists) + const idx = children.length + + children[children.length - 1]?.props?.children?.props?.id === "devmode-copy-id" + ? -2 + : -1; + + children.splice(idx, 0, ( + + {icon ? ( + + openImage(BannerStore.getGuildIconURL({ + id, + icon, + size: 512, + canAnimate: true + })) + } + /> + ) : null} + {banner ? ( + + openImage(BannerStore.getGuildBannerURL({ + id, + banner, + }, true)) + } + /> + ) : null} + + )); +}; -const OPEN_URL = "Vencord.Plugins.plugins.ViewIcons.openImage("; export default definePlugin({ name: "ViewIcons", authors: [Devs.Ven], - description: "Makes Avatars/Banners in user profiles clickable, and adds Guild Context Menu Entries to View Banner/Icon.", + description: "Makes Avatars/Banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu", - openImage(url: string) { - const u = new URL(url); - u.searchParams.set("size", "512"); - url = u.toString(); + settings, - openModal(modalProps => ( - - - - )); + openImage, + + start() { + addContextMenuPatch("user-context", UserContext); + addContextMenuPatch("guild-context", GuildContext); + }, + + stop() { + removeContextMenuPatch("user-context", UserContext); + removeContextMenuPatch("guild-context", GuildContext); }, patches: [ @@ -57,52 +178,17 @@ export default definePlugin({ find: "onAddFriend:", replacement: { // global because Discord has two components that are 99% identical with one small change ._. - match: /\{src:(.{1,2}),avatarDecoration/g, - replace: (_, src) => `{src:${src},onClick:()=>${OPEN_URL}${src}),avatarDecoration` + match: /\{src:(\i),avatarDecoration/g, + replace: (_, src) => `{src:${src},onClick:()=>$self.openImage(${src}),avatarDecoration` } }, { find: ".popoutNoBannerPremium", replacement: { - match: /style:.{0,10}\{\},(.{1,2})\)/, + match: /style:.{0,10}\{\},(\i)\)/, replace: (m, style) => `onClick:${style}.backgroundImage&&(${style}.cursor="pointer",` + - `()=>${OPEN_URL}${style}.backgroundImage.replace("url(", ""))),${m}` + `()=>$self.openImage(${style}.backgroundImage.replace("url(", ""))),${m}` } - }, { - find: '"GuildContextMenu:', - replacement: [ - { - match: /\w=(\w)\.id/, - replace: "_guild=$1,$&" - }, - { - match: /(id:"leave-guild".{0,200}),(\(0,.{1,3}\.jsxs?\).{0,200}function)/, - replace: "$1,$self.buildGuildContextMenuEntries(_guild),$2" - } - ] } - ], - - buildGuildContextMenuEntries(guild: Guild) { - return ( - - {guild.banner && ( - this.openImage(GuildBannerStore.getGuildBannerURL(guild))} - /> - )} - {guild.icon && ( - this.openImage(guild.getIconURL(0, true))} - /> - )} - - ); - } + ] });