feat(web+android): Warteschlange bearbeiten — ziehen und entfernen

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-22 20:31:08 +02:00
1 parent 77a3bb33f0
commit d5b5c8cf84
9 files changed
+330 -7

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
import { queueEntfernen, queueVerschieben } from './player.js';
// ─── WARTESCHLANGE BEARBEITEN (Web) ─────────────────────────────────────────
// Ziehen und Entfernen fuer beide Queue-Listen: die Seitenleiste und die im Now-Playing.
//
// Bewusst ueber Zeiger-Ereignisse statt HTML5-Drag-and-Drop: dessen `dragstart` gibt es auf
// Touch-Geraeten gar nicht, die Warteschlange waere am Handy also unbearbeitbar geblieben.
// `setPointerCapture` liefert dafuer Maus und Finger ueber denselben Weg.
const ZIEH_SCHWELLE = 4; // px, bevor aus einem Tippen ein Ziehen wird
export function macheQueueBearbeitbar(listeId) {
const liste = document.getElementById(listeId);
if (!liste || liste.dataset.bearbeitbar) return;
liste.dataset.bearbeitbar = '1';
liste.addEventListener('click', (e) => {
const weg = e.target.closest('.q-del');
if (!weg) return;
e.stopPropagation();
const zeile = weg.closest('[data-qidx]');
if (zeile) queueEntfernen(Number(zeile.dataset.qidx));
});
liste.addEventListener('pointerdown', (e) => {
const griff = e.target.closest('.q-grip');
if (!griff || e.button > 0) return;
const zeile = griff.closest('[data-qidx]');
if (!zeile) return;
e.preventDefault();
e.stopPropagation();
ziehen(liste, zeile, e);
});
}
function ziehen(liste, zeile, start) {
const zeilen = [...liste.querySelectorAll('[data-qidx]')];
const vonIdx = Number(zeile.dataset.qidx);
const hoehe = zeile.offsetHeight || 1;
const startY = start.clientY;
let zielIdx = vonIdx;
let aktiv = false;
const bewegen = (e) => {
const dy = e.clientY - startY;
if (!aktiv) {
if (Math.abs(dy) < ZIEH_SCHWELLE) return;
aktiv = true;
zeile.classList.add('q-dragging');
liste.classList.add('q-dragging-aktiv');
}
zeile.style.transform = `translateY(${dy}px)`;
zielIdx = Math.max(0, Math.min(zeilen.length - 1, vonIdx + Math.round(dy / hoehe)));
// Die Zeilen zwischen Start und Ziel weichen um eine Zeilenhoehe aus, damit die Luecke
// dorthin wandert, wo losgelassen wird.
zeilen.forEach((z, i) => {
if (z === zeile) return;
let v = 0;
if (vonIdx < zielIdx && i > vonIdx && i <= zielIdx) v = -hoehe;
else if (vonIdx > zielIdx && i >= zielIdx && i < vonIdx) v = hoehe;
z.style.transform = v ? `translateY(${v}px)` : '';
});
};
const beenden = () => {
liste.releasePointerCapture?.(start.pointerId);
window.removeEventListener('pointermove', bewegen);
window.removeEventListener('pointerup', beenden);
window.removeEventListener('pointercancel', beenden);
zeilen.forEach(z => { z.style.transform = ''; z.classList.remove('q-dragging'); });
liste.classList.remove('q-dragging-aktiv');
if (aktiv && zielIdx !== vonIdx) queueVerschieben(vonIdx, zielIdx);
};
try { liste.setPointerCapture(start.pointerId); } catch {}
window.addEventListener('pointermove', bewegen);
window.addEventListener('pointerup', beenden);
window.addEventListener('pointercancel', beenden);
}
/** Griff und Entfernen-Knopf als HTML — in beiden Listen identisch. */
export const QUEUE_WERKZEUGE =
'<button class="q-del" title="Aus der Warteschlange entfernen" aria-label="Entfernen">' +
'<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
'<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>' +
'</button>' +
'<span class="q-grip" title="Zum Verschieben ziehen" aria-label="Verschieben">' +
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
'<line x1="4" y1="9" x2="20" y2="9"/><line x1="4" y1="15" x2="20" y2="15"/></svg>' +
'</span>';