import { useCallback, useRef, useState, type DragEvent } from 'react'; import { useTranslation } from 'react-i18next'; import { Avatar } from '@/components/ui/Avatar'; import { VoiceFlags, VoiceQualityDot } from '@/components/voice/VoiceIndicators'; import { VoiceOccupantMenu } from '@/components/voice/VoiceOccupantMenu'; import { useVoiceChannels, useVoiceModeration, type VoiceOccupant, } from '@/components/voice/useVoiceOccupants'; import { VOICE_DRAG_MIME } from '@/components/voice/voiceDrag'; import { speakingRingClass } from '@/components/voice/voiceStyles'; import { useVoiceConnectionStore } from '@/stores/voiceConnection'; /** * Строка участника голосовой комнаты в сайдбаре: аватар с кольцом речи, * имя, флаги и точка качества связи. Клик открывает меню с громкостью и * действиями модератора; при праве MOVE_MEMBERS строку можно перетащить * на другую голосовую комнату. */ export function VoiceOccupantRow({ occupant, guildId, channelId, }: { occupant: VoiceOccupant; guildId: string; channelId: string; }) { const { t } = useTranslation(); const permissions = useVoiceModeration(guildId); const moveTargets = useVoiceChannels(guildId).filter((channel) => channel.id !== channelId); // Подписка именно на карту пингов: селектор-функция не вызывает перерисовку. const latencies = useVoiceConnectionStore((state) => state.latencies); const setParticipantMuted = useVoiceConnectionStore((state) => state.setParticipantMuted); const setParticipantDeafened = useVoiceConnectionStore((state) => state.setParticipantDeafened); const moveParticipant = useVoiceConnectionStore((state) => state.moveParticipant); const [open, setOpen] = useState(false); const anchorRef = useRef(null); const close = useCallback(() => setOpen(false), []); const label = t('voice.menu.label', { name: occupant.name }); const canDrag = permissions.move && !occupant.isLocal; const onDragStart = (event: DragEvent): void => { event.dataTransfer.setData(VOICE_DRAG_MIME, occupant.userId); event.dataTransfer.effectAllowed = 'move'; }; return (
  • void setParticipantMuted(guildId, userId, muted)} onDeafen={(userId, deafened) => void setParticipantDeafened(guildId, userId, deafened)} onMove={(userId, targetId) => void moveParticipant(guildId, userId, targetId)} />
  • ); }