a simple web player for subsonic
tinysub.devins.page
subsonic
navidrome
javascript
1// drag and drop support for reordering queue items
2
3// setup drag and drop for queue reordering
4function setupDragAndDrop() {
5 const isDropBelowCenter = (e, row) =>
6 e.clientY - row.getBoundingClientRect().top > row.offsetHeight / 2;
7
8 let lastDragOverRow = null;
9
10 ui.queueList.addEventListener("dragstart", (e) => {
11 const row = getClosestRow(e.target);
12 if (
13 !row ||
14 row.classList.contains(CLASSES.DRAGGING) ||
15 selectionManager.isSelected(getRowIndex(row, DATA_ATTRS.INDEX))
16 )
17 return;
18
19 selectionManager.clear();
20 selectionManager.select(getRowIndex(row, DATA_ATTRS.INDEX));
21 updateRowClass(
22 ui.queueList,
23 selectionManager.getSelected(),
24 CLASSES.DRAGGING,
25 true,
26 );
27 e.dataTransfer.effectAllowed = "move";
28 e.dataTransfer.setDragImage(row, 0, 0);
29 });
30
31 // handle drag over - show drop indicator
32 ui.queueList.addEventListener("dragover", (e) => {
33 e.preventDefault();
34 e.dataTransfer.dropEffect = "move";
35 const row = getClosestRow(e.target);
36 if (!row || row.classList.contains(CLASSES.DRAGGING)) return;
37
38 const isBelow = isDropBelowCenter(e, row);
39
40 // only update if row changed or position within row changed
41 if (lastDragOverRow !== row) {
42 if (lastDragOverRow) {
43 lastDragOverRow.classList.remove(
44 CLASSES.DRAG_OVER_ABOVE,
45 CLASSES.DRAG_OVER_BELOW,
46 );
47 }
48 row.classList.add(
49 isBelow ? CLASSES.DRAG_OVER_BELOW : CLASSES.DRAG_OVER_ABOVE,
50 );
51 lastDragOverRow = row;
52 } else {
53 const hasAbove = row.classList.contains(CLASSES.DRAG_OVER_ABOVE);
54 const hasBelow = row.classList.contains(CLASSES.DRAG_OVER_BELOW);
55
56 if ((isBelow && hasAbove) || (!isBelow && hasBelow)) {
57 row.classList.remove(CLASSES.DRAG_OVER_ABOVE, CLASSES.DRAG_OVER_BELOW);
58 row.classList.add(
59 isBelow ? CLASSES.DRAG_OVER_BELOW : CLASSES.DRAG_OVER_ABOVE,
60 );
61 }
62 }
63 });
64
65 // handle drop and complete the move
66 ui.queueList.addEventListener("drop", (e) => {
67 e.preventDefault();
68 const row = getClosestRow(e.target);
69 if (!row || row.classList.contains(CLASSES.DRAGGING)) return;
70
71 if (lastDragOverRow) {
72 lastDragOverRow.classList.remove(
73 CLASSES.DRAG_OVER_ABOVE,
74 CLASSES.DRAG_OVER_BELOW,
75 CLASSES.DRAGGING,
76 );
77 lastDragOverRow = null;
78 }
79 clearRowClasses(ui.queueList, "tr", CLASSES.DRAGGING);
80
81 const draggedIdx = getRowIndex(row, DATA_ATTRS.INDEX);
82 moveQueueItems(
83 state.queue,
84 selectionManager.getSelected(),
85 isDropBelowCenter(e, row) ? draggedIdx + 1 : draggedIdx,
86 queueCallbacks,
87 );
88
89 // refocus after DOM render cycle completes
90 requestAnimationFrame(() => {
91 const mainEl = document.getElementById("queue");
92 if (mainEl) mainEl.focus();
93 });
94 });
95
96 // cleanup on drag end
97 ui.queueList.addEventListener("dragend", () => {
98 if (lastDragOverRow) {
99 lastDragOverRow.classList.remove(
100 CLASSES.DRAG_OVER_ABOVE,
101 CLASSES.DRAG_OVER_BELOW,
102 CLASSES.DRAGGING,
103 );
104 lastDragOverRow = null;
105 }
106 clearRowClasses(ui.queueList, "tr", CLASSES.DRAGGING);
107 });
108}