From 23c102a3cb4ad62d6618c92566e35de1b01311cc Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:34:37 +0100 Subject: [PATCH] feat: add channel/category handler factories and container handlers --- packages/web/src/hooks/useDragManager.ts | 83 ++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/packages/web/src/hooks/useDragManager.ts b/packages/web/src/hooks/useDragManager.ts index 1b227731..cb886d7e 100644 --- a/packages/web/src/hooks/useDragManager.ts +++ b/packages/web/src/hooks/useDragManager.ts @@ -36,19 +36,92 @@ export function useDragManager(opts: UseDragManagerOpts) { setVoiceHoverChannelId(null); }, []); - // Handler factories added in subsequent stages... + // --- Channel/Category drag handlers --- + + const handleChannelDragStart = useCallback((e: React.DragEvent, channelId: string) => { + if (!canManage || activeDrag !== null) return; + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', channelId); // Firefox requires setData for drag to work + setActiveDrag({ type: 'channel', dragId: channelId }); + }, [canManage, activeDrag]); + + const handleCategoryDragStart = useCallback((e: React.DragEvent, categoryId: string) => { + if (!canManage || activeDrag !== null) return; + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', categoryId); // Firefox requires setData for drag to work + setActiveDrag({ type: 'category', dragId: categoryId }); + }, [canManage, activeDrag]); + + const handleLayoutDragOver = useCallback((e: React.DragEvent, targetId: string, targetType: 'channel' | 'category') => { + if (!activeDrag || activeDrag.type === 'voiceUser') return; + e.preventDefault(); + e.dataTransfer.dropEffect = 'move'; + const rect = e.currentTarget.getBoundingClientRect(); + const midY = rect.top + rect.height / 2; + const position: 'before' | 'after' = e.clientY < midY ? 'before' : 'after'; + setDropTarget({ targetId, position, targetType }); + }, [activeDrag]); + + const handleDragEnd = useCallback(() => { + clearState(); + }, [clearState]); + + const channelHandlers = useCallback((channelId: string) => ({ + draggable: canManage, + onDragStart: (e: React.DragEvent) => handleChannelDragStart(e, channelId), + onDragOver: (e: React.DragEvent) => handleLayoutDragOver(e, channelId, 'channel'), + onDragEnd: handleDragEnd, + }), [canManage, handleChannelDragStart, handleLayoutDragOver, handleDragEnd]); + + const categoryHandlers = useCallback((categoryId: string) => ({ + draggable: canManage, + onDragStart: (e: React.DragEvent) => handleCategoryDragStart(e, categoryId), + onDragOver: (e: React.DragEvent) => handleLayoutDragOver(e, categoryId, 'category'), + onDragEnd: handleDragEnd, + }), [canManage, handleCategoryDragStart, handleLayoutDragOver, handleDragEnd]); + + // --- Container handlers (scrollable sidebar div) --- + + const handleContainerDragOver = useCallback((e: React.DragEvent) => { + if (activeDrag && (activeDrag.type === 'channel' || activeDrag.type === 'category')) { + e.preventDefault(); + } + }, [activeDrag]); + + const handleContainerDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + if (!activeDrag || !dropTarget) { + clearState(); + return; + } + // Self-drop no-op (fixes bug 5) + if (activeDrag.dragId === dropTarget.targetId) { + clearState(); + return; + } + if (activeDrag.type === 'channel') { + onChannelDrop(activeDrag.dragId, dropTarget); + } else if (activeDrag.type === 'category') { + onCategoryDrop(activeDrag.dragId, dropTarget); + } + clearState(); + }, [activeDrag, dropTarget, onChannelDrop, onCategoryDrop, clearState]); + + const containerHandlers = { + onDrop: handleContainerDrop, + onDragOver: handleContainerDragOver, + }; // Suppress unused variable warnings for incremental build void scrollContainerRef; - void canManage; void canMoveMembers; - void onChannelDrop; - void onCategoryDrop; void onVoiceUserDrop; - void clearState; return { activeDrag, dropTarget, + channelHandlers, + categoryHandlers, + containerHandlers, }; }