// drag and drop support for reordering queue items // setup drag and drop for queue reordering function setupDragAndDrop() { const isDropBelowCenter = (e, row) => e.clientY - row.getBoundingClientRect().top > row.offsetHeight / 2; let lastDragOverRow = null; ui.queueList.addEventListener("dragstart", (e) => { const row = getClosestRow(e.target); if ( !row || row.classList.contains(CLASSES.DRAGGING) || selectionManager.isSelected(getRowIndex(row, DATA_ATTRS.INDEX)) ) return; selectionManager.clear(); selectionManager.select(getRowIndex(row, DATA_ATTRS.INDEX)); updateRowClass( ui.queueList, selectionManager.getSelected(), CLASSES.DRAGGING, true, ); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setDragImage(row, 0, 0); }); // handle drag over - show drop indicator ui.queueList.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; const row = getClosestRow(e.target); if (!row || row.classList.contains(CLASSES.DRAGGING)) return; const isBelow = isDropBelowCenter(e, row); // only update if row changed or position within row changed if (lastDragOverRow !== row) { if (lastDragOverRow) { lastDragOverRow.classList.remove( CLASSES.DRAG_OVER_ABOVE, CLASSES.DRAG_OVER_BELOW, ); } row.classList.add( isBelow ? CLASSES.DRAG_OVER_BELOW : CLASSES.DRAG_OVER_ABOVE, ); lastDragOverRow = row; } else { const hasAbove = row.classList.contains(CLASSES.DRAG_OVER_ABOVE); const hasBelow = row.classList.contains(CLASSES.DRAG_OVER_BELOW); if ((isBelow && hasAbove) || (!isBelow && hasBelow)) { row.classList.remove(CLASSES.DRAG_OVER_ABOVE, CLASSES.DRAG_OVER_BELOW); row.classList.add( isBelow ? CLASSES.DRAG_OVER_BELOW : CLASSES.DRAG_OVER_ABOVE, ); } } }); // handle drop and complete the move ui.queueList.addEventListener("drop", (e) => { e.preventDefault(); const row = getClosestRow(e.target); if (!row || row.classList.contains(CLASSES.DRAGGING)) return; if (lastDragOverRow) { lastDragOverRow.classList.remove( CLASSES.DRAG_OVER_ABOVE, CLASSES.DRAG_OVER_BELOW, CLASSES.DRAGGING, ); lastDragOverRow = null; } clearRowClasses(ui.queueList, "tr", CLASSES.DRAGGING); const draggedIdx = getRowIndex(row, DATA_ATTRS.INDEX); moveQueueItems( state.queue, selectionManager.getSelected(), isDropBelowCenter(e, row) ? draggedIdx + 1 : draggedIdx, queueCallbacks, ); // refocus after DOM render cycle completes requestAnimationFrame(() => { const mainEl = document.getElementById("queue"); if (mainEl) mainEl.focus(); }); }); // cleanup on drag end ui.queueList.addEventListener("dragend", () => { if (lastDragOverRow) { lastDragOverRow.classList.remove( CLASSES.DRAG_OVER_ABOVE, CLASSES.DRAG_OVER_BELOW, CLASSES.DRAGGING, ); lastDragOverRow = null; } clearRowClasses(ui.queueList, "tr", CLASSES.DRAGGING); }); }