stream: let listeners remove a track from the queue
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
493e55ed18
commit
976f009297
5 changed files with 125 additions and 2 deletions
|
|
@ -157,6 +157,13 @@
|
|||
.queue .q-num { color: #6b6480; font-size: .8rem; font-variant-numeric: tabular-nums;
|
||||
min-width: 1.2em; text-align: right; }
|
||||
.queue .q-meta { flex: 1; min-width: 0; }
|
||||
/* Retrait d'un morceau à venir : croix discrète, virant au rouge tamisé au
|
||||
survol pour signaler l'action destructive. Bouton nu, aligné sur le titre. */
|
||||
.queue .q-act { color: #7d768f; font-size: .95rem; line-height: 1;
|
||||
background: none; border: 0; padding: 0; cursor: pointer;
|
||||
transition: color .15s; }
|
||||
.queue .q-act:hover { color: #e08b9b; }
|
||||
.queue .q-act:disabled { opacity: .5; cursor: default; }
|
||||
/* Ajout à la file : un input d'URL + bouton, calqués sur le bloc « share ».
|
||||
Le message de retour s'affiche discrètement sous le formulaire, en rouge
|
||||
tamisé quand c'est une erreur. */
|
||||
|
|
@ -473,11 +480,33 @@
|
|||
? `<a href="${escapeHtml(u)}" target="_blank" rel="noopener">${t}</a>`
|
||||
: t;
|
||||
const meta = `<div class="q-meta"><div class="h-title">${titleHtml}</div>${artist}</div>`;
|
||||
return `<li><div class="q-item"><span class="q-num">${i + 1}</span>${meta}</div></li>`;
|
||||
// Croix de retrait, portant l'id opaque de l'entrée (fourni par
|
||||
// /queue). Absent (ancien ingest sans id) → pas de bouton.
|
||||
const id = (m.id || "").toString();
|
||||
const rm = id
|
||||
? `<button class="q-act" type="button" data-id="${escapeHtml(id)}" title="Retirer de la file" aria-label="Retirer de la file">✕</button>`
|
||||
: "";
|
||||
return `<li><div class="q-item"><span class="q-num">${i + 1}</span>${meta}${rm}</div></li>`;
|
||||
}).join("");
|
||||
} catch (e) { /* keep last known values */ }
|
||||
}
|
||||
|
||||
// Retrait d'un morceau de la file : délégation de clic sur la liste. On POST
|
||||
// l'id au stream (relayé à l'ingest), puis on rafraîchit la file. Un échec
|
||||
// (entrée déjà passée entre-temps) est simplement ignoré : le prochain
|
||||
// rafraîchissement remettra l'affichage d'aplomb.
|
||||
queueList.addEventListener("click", async (e) => {
|
||||
const btn = e.target.closest(".q-act");
|
||||
if (!btn) return;
|
||||
const id = btn.dataset.id;
|
||||
if (!id) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await fetch("/dequeue?id=" + encodeURIComponent(id), { method: "POST" });
|
||||
} catch (err) { /* ignore */ }
|
||||
pollQueue();
|
||||
});
|
||||
|
||||
// Ajout à la file : on POST l'URL au stream, qui la relaie à l'ingest. Ce
|
||||
// dernier résout l'URL (piste seule, ou playlist/album entier) et la place
|
||||
// en file prioritaire — le prochain morceau diffusé sera la demande. On
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue