/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { classes } from "@utils/misc"; import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, useStateFromStores } from "@webpack/common"; import { Channel } from "discord-types/general"; const cl = classNameFactory("vc-uvs-"); const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChannel"); const { useChannelName } = mapMangledModuleLazy(".Messages.GROUP_DM_ALONE", { useChannelName: filters.byCode("()=>null==") }); const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({"); const VoiceStateStore = findStoreLazy("VoiceStateStore"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const Avatar = findComponentByCodeLazy(".AVATAR_STATUS_TYPING_16;"); const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL"); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); interface IconProps extends React.ComponentPropsWithoutRef<"div"> { size?: number; } function SpeakerIcon(props: IconProps) { props.size ??= 16; return (
); } function LockedSpeakerIcon(props: IconProps) { props.size ??= 16; return (
); } interface VoiceChannelTooltipProps { channel: Channel; isLocked: boolean; } function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) { const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id)); const users = useMemo( () => Object.values(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null), [voiceStates] ); const guild = channel.getGuildId() == null ? undefined : GuildStore.getGuild(channel.getGuildId()); const guildIcon = guild?.icon == null ? undefined : IconUtils.getGuildIconURL({ id: guild.id, icon: guild.icon, size: 30 }); const channelIcon = match(channel.type) .with(P.union(1, 3), () => { return channel.recipients.length >= 2 && channel.icon == null ? : ; }) .otherwise(() => null); const channelName = useChannelName(channel); return ( <> {guild != null && (
{guildIcon != null && } {guild.name}
)}
{channelIcon} {channelName}
{isLocked ? : }
); } interface VoiceChannelIndicatorProps { userId: string; isMessageIndicator?: boolean; isProfile?: boolean; isActionButton?: boolean; shouldHighlight?: boolean; } const clickTimers = {} as Record; export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined); const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId); if (channel == null) return null; const isDM = channel.isDM() || channel.isMultiUserDM(); if (!isDM && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) && !Vencord.Plugins.isPluginEnabled("ShowHiddenChannels")) return null; const isLocked = !isDM && (!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || !PermissionStore.can(PermissionsBits.CONNECT, channel)); function onClick(e: React.MouseEvent) { e.preventDefault(); e.stopPropagation(); if (channel == null || channelId == null) return; clearTimeout(clickTimers[channelId]); delete clickTimers[channelId]; if (e.detail > 1) { if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) { showToast("You cannot join the user's Voice Channel", Toasts.Type.FAILURE); return; } selectVoiceChannel(channelId); } else { clickTimers[channelId] = setTimeout(() => { ChannelRouter.transitionToChannel(channelId); delete clickTimers[channelId]; }, 250); } } return ( } tooltipClassName={cl("tooltip-container")} tooltipContentClassName={cl("tooltip-content")} > {props => { const iconProps: IconProps = { ...props, className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight), size: isActionButton ? 20 : undefined, onClick }; return isLocked ? : ; }} ); }, { noop: true });