/* * 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 { Channel } from "@vencord/discord-types"; import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; import { PropsWithChildren } from "react"; const cl = classNameFactory("vc-uvs-"); const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChannel"); const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", { useChannelName: filters.byCode("()=>null==") }); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); type IconProps = Omit, "children"> & { size?: number; iconClassName?: string; }; function Icon(props: PropsWithChildren) { const { size = 16, className, iconClassName, ...restProps } = props; return (
{props.children}
); } function SpeakerIcon(props: IconProps) { return ( ); } function LockedSpeakerIcon(props: IconProps) { return ( ); } function MutedIcon(props: IconProps) { return ( ); } function DeafIcon(props: IconProps) { 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 Icon = isLocked ? LockedSpeakerIcon : SpeakerIcon; return ( <> In Voice Chat {Parser.parse(`<#${channel.id}>`)}
); } export interface VoiceChannelIndicatorProps { userId: string; isProfile?: boolean; isActionButton?: boolean; shouldHighlight?: boolean; } const clickTimers = new Map(); export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => { const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId); const { isMuted, isDeaf } = useStateFromStores([VoiceStateStore], () => { const voiceState = VoiceStateStore.getVoiceStateForUser(userId); return { isMuted: voiceState?.mute || voiceState?.selfMute || false, isDeaf: voiceState?.deaf || voiceState?.selfDeaf || false }; }); 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.get(channelId)); clickTimers.delete(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 { const timeoutId = setTimeout(() => { ChannelRouter.transitionToChannel(channelId); clickTimers.delete(channelId); }, 250); clickTimers.set(channelId, timeoutId); } } const IconComponent = isLocked ? LockedSpeakerIcon : isDeaf ? DeafIcon : isMuted ? MutedIcon : SpeakerIcon; return ( } tooltipClassName={cl("tooltip-container")} tooltipContentClassName={cl("tooltip-content")} > {props => ( )} ); }, { noop: true });