/* * 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 { ChannelStore, GuildStore, IconUtils, match, NavigationRouter, 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"); 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; } function VoiceChannelTooltip({ channel }: 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}
); } interface VoiceChannelIndicatorProps { userId: string; size?: number; isActionButton?: boolean; } const clickTimers = {} as Record; export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, size, isActionButton }: 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(); 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; if (!isDM && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel)) { showToast("You cannot view the user's Voice Channel", Toasts.Type.FAILURE); 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(() => { NavigationRouter.transitionTo(`/channels/${channel.getGuildId() ?? "@me"}/${channelId}`); delete clickTimers[channelId]; }, 250); } } return ( } tooltipClassName={cl("tooltip-container")} tooltipContentClassName={cl("tooltip-content")} > {props => { const iconProps = { ...props, onClick, size, className: isActionButton ? cl("indicator-action-button") : cl("speaker-padding") }; return isLocked ? : ; }} ); }, { noop: true });