import React, { useState, useCallback } from 'react'; import { useVoiceStore } from '../../stores/voiceStore'; import { useSpaceStore, getChannelOrigin } from '../../stores/spaceStore'; import { useAuthStore } from '../../stores/authStore'; import { Avatar } from '../ui/Avatar'; import { VoiceUserContextMenu } from './VoiceUserContextMenu'; import { wsSend } from '../../hooks/useWebSocket'; import { hasPermissionBit, PermissionBits } from '../../utils/permissions'; const EMPTY_VOICE_USERS: string[] = []; interface VoiceChannelDragState { userId: string; fromChannelId: string; } interface VoiceChannelProps { channelId: string; channelName: string; onClick: () => void; locked?: boolean; dragState?: VoiceChannelDragState | null; onDragStart?: (userId: string) => void; onDragEnd?: () => void; } export function VoiceChannel({ channelId, channelName, onClick, locked, dragState, onDragStart, onDragEnd }: VoiceChannelProps) { const voiceUsers = useVoiceStore((s) => s.voiceUsers.get(channelId)) ?? EMPTY_VOICE_USERS; const currentVoiceChannel = useVoiceStore((s) => s.currentVoiceChannelId); const participants = useVoiceStore((s) => s.participants); const localIsDeafened = useVoiceStore((s) => s.isDeafened); const localIsMuted = useVoiceStore((s) => s.isMuted); const voiceUserStates = useVoiceStore((s) => s.voiceUserStates); const spaceMutedUserIds = useVoiceStore((s) => s.spaceMutedUserIds); const spaceDeafenedUserIds = useVoiceStore((s) => s.spaceDeafenedUserIds); const permissionMutedUserIds = useVoiceStore((s) => s.permissionMutedUserIds); const participantMutes = useVoiceStore((s) => s.participantMutes); const unwatchedCameras = useVoiceStore((s) => s.unwatchedCameras); const currentUserId = useVoiceStore((s) => { const local = s.participants.find(p => p.isLocal); return local?.userId ?? null; }); const members = useSpaceStore((s) => s.members); const channelToSpaceMap = useSpaceStore((s) => s.channelToSpaceMap); const myUser = useAuthStore((s) => s.user); const isActive = currentVoiceChannel === channelId; // Drag-and-drop permission check const spacePermissions = useSpaceStore((s) => s.spacePermissions); const currentSpaceId = useSpaceStore((s) => s.currentSpaceId); const myPerms = currentSpaceId ? spacePermissions.get(currentSpaceId) : undefined; const canMoveMembers = hasPermissionBit(myPerms, PermissionBits.MOVE_MEMBERS); // Drop target highlight state const [isDragOver, setIsDragOver] = useState(false); const isValidDropTarget = dragState !== null && dragState !== undefined && dragState.fromChannelId !== channelId; // Context menu state const [contextMenu, setContextMenu] = useState<{ x: number; y: number; userId: string } | null>(null); const handleContextMenu = useCallback( (e: React.MouseEvent, userId: string) => { if (userId === myUser?.id) return; e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY, userId }); }, [myUser?.id], ); return (