diff --git a/packages/discord-types/src/stores/MediaEngineStore.d.ts b/packages/discord-types/src/stores/MediaEngineStore.d.ts index 2452a6056..d44f5aa58 100644 --- a/packages/discord-types/src/stores/MediaEngineStore.d.ts +++ b/packages/discord-types/src/stores/MediaEngineStore.d.ts @@ -3,6 +3,8 @@ import { FluxStore } from ".."; export class MediaEngineStore extends FluxStore { isSelfMute(): boolean; isSelfDeaf(): boolean; + isLocalMute(userId): boolean; + isLocalVideoDisabled(userId): boolean; setAv1Enabled(AV1: boolean): void; setH265Enabled(H265: boolean): void; diff --git a/src/equicordplugins/imagePreview/index.ts b/src/equicordplugins/imagePreview/index.ts index 065d8a392..948dd870d 100644 --- a/src/equicordplugins/imagePreview/index.ts +++ b/src/equicordplugins/imagePreview/index.ts @@ -376,6 +376,24 @@ function updatePreviewPosition(mouseEvent: MouseEvent, element: HTMLElement) { } } +let previewCheckInterval; + +function watchSourceElement(el: Element) { + if (previewCheckInterval) { + clearInterval(previewCheckInterval); + previewCheckInterval = null; + } + + previewCheckInterval = setInterval(() => { + const node = el as HTMLElement; + if (!document.body.contains(node) || !node.offsetParent) { + deleteCurrentPreview(); + clearInterval(previewCheckInterval); + previewCheckInterval = null; + } + }, 300); +} + function addHoverListener(element: Element, sticker: boolean = false) { element.setAttribute("data-processed", "true"); @@ -406,6 +424,7 @@ function addHoverListener(element: Element, sticker: boolean = false) { hoverDelayTimeout = setTimeout(() => { loadImagePreview(strippedURL, sticker); + watchSourceElement(element); if (lastMouseEvent) { updatePreviewPosition(lastMouseEvent, element as HTMLElement); } diff --git a/src/equicordplugins/voiceButtons/index.tsx b/src/equicordplugins/voiceButtons/index.tsx new file mode 100644 index 000000000..dfe0bfca7 --- /dev/null +++ b/src/equicordplugins/voiceButtons/index.tsx @@ -0,0 +1,74 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { User } from "@vencord/discord-types"; +import { React } from "@webpack/common"; + +import { UserChatButton, UserDeafenButton, UserMuteButton } from "./utils"; + +export const settings = definePluginSettings({ + showChatButton: { + type: OptionType.BOOLEAN, + default: true, + description: "Whether you want to display the chat button", + restartNeeded: true, + }, + showMuteButton: { + type: OptionType.BOOLEAN, + default: true, + description: "Whether you want to display the mute button", + restartNeeded: true, + }, + showDeafenButton: { + type: OptionType.BOOLEAN, + default: true, + description: "Whether you want to display the deafen button", + restartNeeded: true, + }, + muteSoundboard: { + type: OptionType.BOOLEAN, + default: true, + description: "Toggles their soundboard upon clicking deafen button.", + restartNeeded: false, + }, + disableVideo: { + type: OptionType.BOOLEAN, + default: true, + description: "Toggles their video upon clicking deafen button.", + restartNeeded: false, + }, +}); + +export default definePlugin({ + name: "VoiceButtons", + description: "Quickly DM, mute, or deafen any user right from the voice-call panel.", + authors: [EquicordDevs.nicola02nb, EquicordDevs.omaw], + settings, + patches: [ + { + find: "\"avatarContainerClass\",\"userNameClassName\"", + replacement: [ + { + match: /:(\i)\.username.*?flipped\]:\i\}\),children:\[/, + replace: "$&$self.renderButtons($1)," + } + ] + } + ], + renderPing(user?: User) { + if (!user) return null; + return ( +
+ {settings.store.showChatButton && } + {settings.store.showMuteButton && } + {settings.store.showDeafenButton && } +
+ ); + } +}); diff --git a/src/equicordplugins/voiceButtons/styles.css b/src/equicordplugins/voiceButtons/styles.css new file mode 100644 index 000000000..4dcda501c --- /dev/null +++ b/src/equicordplugins/voiceButtons/styles.css @@ -0,0 +1,3 @@ +div[class^='list_'][class*='collapsed_'] .click-to-chat-btn { + display: none; +} \ No newline at end of file diff --git a/src/equicordplugins/voiceButtons/utils.tsx b/src/equicordplugins/voiceButtons/utils.tsx new file mode 100644 index 000000000..63f5e41bf --- /dev/null +++ b/src/equicordplugins/voiceButtons/utils.tsx @@ -0,0 +1,175 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { openPrivateChannel } from "@utils/discord"; +import { User } from "@vencord/discord-types"; +import { findStoreLazy } from "@webpack"; +import { Button, MediaEngineStore, Tooltip, UserStore, VoiceActions } from "@webpack/common"; + +import { settings } from "."; + +const SoundboardStore = findStoreLazy("SoundboardStore"); + +export function UserChatButton({ user }: { user: User; }) { + if (user.id === UserStore.getCurrentUser().id) return null; + return ( +
+ + {({ onMouseEnter, onMouseLeave }) => ( + + )} + +
+ ); +} + +export function UserMuteButton({ user }: { user: User; }) { + if (user.id === UserStore.getCurrentUser().id) return null; + return ( +
+ + {({ onMouseEnter, onMouseLeave }) => ( + + )} + +
+ ); +} + +function DeafenIcon({ user, ...props }) { + const isDeafened = + MediaEngineStore.isLocalMute(user.id) && + SoundboardStore.isLocalSoundboardMuted(user.id) && + MediaEngineStore.isLocalVideoDisabled(user.id); + + return ( + + + + {isDeafened && ( + + )} + + ); +} + +export function UserDeafenButton({ user }: { user: User; }) { + if (user.id === UserStore.getCurrentUser().id) return null; + const isDeafened = + MediaEngineStore.isLocalMute(user.id) && + SoundboardStore.isLocalSoundboardMuted(user.id) && + MediaEngineStore.isLocalVideoDisabled(user.id); + return ( +
+ + {({ onMouseEnter, onMouseLeave }) => ( + + )} + +
+ ); +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index bfb5a4bc3..cec3b5004 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1148,6 +1148,10 @@ export const EquicordDevs = Object.freeze({ justjxke: { name: "justjxke", id: 852558183087472640n + }, + nicola02nb: { + name: "nicola02nb", + id: 257900031351193600n } } satisfies Record);