refactor: update VoiceChannel to use useDragManager hook-provided handlers
This commit is contained in:
@@ -1,20 +1,13 @@
|
|||||||
import React, { useState, useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { useVoiceStore } from '../../stores/voiceStore';
|
import { useVoiceStore } from '../../stores/voiceStore';
|
||||||
import { useSpaceStore, getChannelOrigin } from '../../stores/spaceStore';
|
import { useSpaceStore } from '../../stores/spaceStore';
|
||||||
import { useAuthStore } from '../../stores/authStore';
|
import { useAuthStore } from '../../stores/authStore';
|
||||||
import { Avatar } from '../ui/Avatar';
|
import { Avatar } from '../ui/Avatar';
|
||||||
import { useContextMenuStore, type ContextMenuItem } from '../../stores/contextMenuStore';
|
import { useContextMenuStore, type ContextMenuItem } from '../../stores/contextMenuStore';
|
||||||
import { buildVoiceModMenuItems, VolumeSliderItem } from './voiceMenuItems';
|
import { buildVoiceModMenuItems, VolumeSliderItem } from './voiceMenuItems';
|
||||||
import { wsSend } from '../../hooks/useWebSocket';
|
|
||||||
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
|
|
||||||
|
|
||||||
const EMPTY_VOICE_USERS: string[] = [];
|
const EMPTY_VOICE_USERS: string[] = [];
|
||||||
|
|
||||||
interface VoiceChannelDragState {
|
|
||||||
userId: string;
|
|
||||||
fromChannelId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface VoiceChannelProps {
|
interface VoiceChannelProps {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
channelName: string;
|
channelName: string;
|
||||||
@@ -22,13 +15,24 @@ interface VoiceChannelProps {
|
|||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
canManage?: boolean;
|
canManage?: boolean;
|
||||||
onSettingsClick?: () => void;
|
onSettingsClick?: () => void;
|
||||||
dragState?: VoiceChannelDragState | null;
|
voiceUserHandlers?: (userId: string, channelId: string) => {
|
||||||
onDragStart?: (userId: string) => void;
|
draggable: boolean;
|
||||||
onDragEnd?: () => void;
|
isBeingDragged: boolean;
|
||||||
|
onDragStart: (e: React.DragEvent) => void;
|
||||||
|
onDragEnd: (e: React.DragEvent) => void;
|
||||||
|
};
|
||||||
|
dropZone?: {
|
||||||
|
onDragOver: (e: React.DragEvent) => void;
|
||||||
|
onDragEnter: (e: React.DragEvent) => void;
|
||||||
|
onDragLeave: (e: React.DragEvent) => void;
|
||||||
|
onDrop: (e: React.DragEvent) => void;
|
||||||
|
isDragOver: boolean;
|
||||||
|
isValidTarget: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wrapper component for the volume slider so it can use hooks (useState). */
|
/** Wrapper component for the volume slider so it can use hooks (useState). */
|
||||||
export function VoiceChannel({ channelId, channelName, onClick, locked, canManage, onSettingsClick, dragState, onDragStart, onDragEnd }: VoiceChannelProps) {
|
export function VoiceChannel({ channelId, channelName, onClick, locked, canManage, onSettingsClick, voiceUserHandlers, dropZone }: VoiceChannelProps) {
|
||||||
const serverVoiceUsers = useVoiceStore((s) => s.voiceUsers.get(channelId)) ?? EMPTY_VOICE_USERS;
|
const serverVoiceUsers = useVoiceStore((s) => s.voiceUsers.get(channelId)) ?? EMPTY_VOICE_USERS;
|
||||||
const currentVoiceChannel = useVoiceStore((s) => s.currentVoiceChannelId);
|
const currentVoiceChannel = useVoiceStore((s) => s.currentVoiceChannelId);
|
||||||
const participants = useVoiceStore((s) => s.participants);
|
const participants = useVoiceStore((s) => s.participants);
|
||||||
@@ -60,16 +64,6 @@ export function VoiceChannel({ channelId, channelName, onClick, locked, canManag
|
|||||||
const myUser = useAuthStore((s) => s.user);
|
const myUser = useAuthStore((s) => s.user);
|
||||||
const isActive = currentVoiceChannel === channelId;
|
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;
|
|
||||||
|
|
||||||
const openContextMenu = useContextMenuStore((s) => s.open);
|
const openContextMenu = useContextMenuStore((s) => s.open);
|
||||||
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
@@ -114,35 +108,11 @@ export function VoiceChannel({ channelId, channelName, onClick, locked, canManag
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onDragOver={(e) => {
|
onDragOver={dropZone?.onDragOver}
|
||||||
if (isValidDropTarget) {
|
onDragEnter={dropZone?.onDragEnter}
|
||||||
e.preventDefault();
|
onDragLeave={dropZone?.onDragLeave}
|
||||||
e.dataTransfer.dropEffect = 'move';
|
onDrop={dropZone?.onDrop}
|
||||||
setIsDragOver(true);
|
className={dropZone?.isDragOver && dropZone?.isValidTarget ? 'rounded-[8px] ring-1 ring-accent-mint/40' : ''}
|
||||||
}
|
|
||||||
}}
|
|
||||||
onDragEnter={(e) => {
|
|
||||||
if (isValidDropTarget) {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsDragOver(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onDragLeave={(e) => {
|
|
||||||
// Only clear when leaving the container (not entering a child)
|
|
||||||
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
|
|
||||||
setIsDragOver(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onDrop={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsDragOver(false);
|
|
||||||
if (dragState && dragState.fromChannelId !== channelId) {
|
|
||||||
const voiceOrigin = getChannelOrigin(dragState.fromChannelId);
|
|
||||||
wsSend({ type: 'voice_move', userId: dragState.userId, targetChannelId: channelId }, voiceOrigin);
|
|
||||||
onDragEnd?.();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={isDragOver && isValidDropTarget ? 'rounded-[8px] ring-1 ring-accent-mint/40' : ''}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@@ -211,8 +181,9 @@ export function VoiceChannel({ channelId, channelName, onClick, locked, canManag
|
|||||||
const isSpaceDeafened = spaceDeafenedUserIds.has(`${spaceId}:${userId}`);
|
const isSpaceDeafened = spaceDeafenedUserIds.has(`${spaceId}:${userId}`);
|
||||||
const isPermissionMuted = permissionMutedUserIds.has(`${spaceId}:${userId}`);
|
const isPermissionMuted = permissionMutedUserIds.has(`${spaceId}:${userId}`);
|
||||||
|
|
||||||
const isDraggable = canMoveMembers && userId !== myUser?.id;
|
const userDrag = voiceUserHandlers?.(userId, channelId);
|
||||||
const isBeingDragged = dragState?.userId === userId && dragState?.fromChannelId === channelId;
|
const isDraggable = !!(userDrag?.draggable && userId !== myUser?.id);
|
||||||
|
const isBeingDragged = userDrag?.isBeingDragged ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -221,13 +192,8 @@ export function VoiceChannel({ channelId, channelName, onClick, locked, canManag
|
|||||||
isDraggable ? 'cursor-grab active:cursor-grabbing' : ''
|
isDraggable ? 'cursor-grab active:cursor-grabbing' : ''
|
||||||
} ${isBeingDragged ? 'opacity-50' : ''}`}
|
} ${isBeingDragged ? 'opacity-50' : ''}`}
|
||||||
draggable={isDraggable}
|
draggable={isDraggable}
|
||||||
onDragStart={(e) => {
|
onDragStart={isDraggable ? userDrag!.onDragStart : undefined}
|
||||||
if (!isDraggable) return;
|
onDragEnd={isDraggable ? userDrag!.onDragEnd : undefined}
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
|
||||||
e.dataTransfer.setData('text/plain', userId);
|
|
||||||
onDragStart?.(userId);
|
|
||||||
}}
|
|
||||||
onDragEnd={() => onDragEnd?.()}
|
|
||||||
onContextMenu={(e) => handleContextMenu(e, userId)}
|
onContextMenu={(e) => handleContextMenu(e, userId)}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|||||||
Reference in New Issue
Block a user