[web] Allow removing playlists

This commit is contained in:
2026-06-26 13:12:46 -04:00
parent 7827558cf1
commit 195e067b0f
2 changed files with 64 additions and 2 deletions

View File

@ -46,11 +46,35 @@
border-radius: var(--radius); border-radius: var(--radius);
background: var(--card); background: var(--card);
font-size: 0.875rem; font-size: 0.875rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar .playlist-item .info {
overflow: hidden;
}
.sidebar .playlist-item .info .name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.sidebar .playlist-item .count { .sidebar .playlist-item .count {
color: var(--muted); color: var(--muted);
font-size: 0.75rem; font-size: 0.75rem;
} }
.sidebar .playlist-item .del-btn {
background: none;
border: none;
color: var(--muted);
cursor: pointer;
font-size: 1rem;
line-height: 1;
padding: 0.25rem;
transition: color 0.2s;
}
.sidebar .playlist-item .del-btn:hover {
color: #f87171;
}
.sidebar .empty { .sidebar .empty {
color: var(--muted); color: var(--muted);
font-size: 0.875rem; font-size: 0.875rem;
@ -249,10 +273,16 @@ async function loadStatus() {
} }
list.innerHTML = data.smart_playlists.map(p => list.innerHTML = data.smart_playlists.map(p =>
`<div class="playlist-item"> `<div class="playlist-item">
<div>${esc(p.name)}</div> <div class="info">
<div class="name">${esc(p.name)}</div>
<div class="count">${p.tracks} track${p.tracks === 1 ? "" : "s"}</div> <div class="count">${p.tracks} track${p.tracks === 1 ? "" : "s"}</div>
</div>
<button class="del-btn" data-uri="${esc(p.uri)}" title="Delete playlist">✕</button>
</div>` </div>`
).join(""); ).join("");
list.querySelectorAll(".del-btn").forEach(btn => {
btn.onclick = () => deletePlaylist(btn.dataset.uri);
});
} catch { } catch {
list.innerHTML = '<div class="empty">Could not load playlists</div>'; list.innerHTML = '<div class="empty">Could not load playlists</div>';
} }
@ -405,6 +435,17 @@ async function refreshPlaylists() {
} }
} }
async function deletePlaylist(uri) {
if (!confirm('Delete "' + uri + '"?')) return;
try {
await api("POST", "/delete", { uri });
showToast("Deleted", "success");
loadStatus();
} catch (e) {
showToast("Delete failed: " + e, "error");
}
}
document.addEventListener("DOMContentLoaded", () => { loadStatus(); loadSqStatus(); }); document.addEventListener("DOMContentLoaded", () => { loadStatus(); loadSqStatus(); });
</script> </script>
</body> </body>

View File

@ -326,6 +326,26 @@ class StatusHandler(tornado.web.RequestHandler):
self.write({"smart_playlists": smart, "count": len(smart)}) self.write({"smart_playlists": smart, "count": len(smart)})
class PlaylistDeleteHandler(tornado.web.RequestHandler):
def initialize(self, core: CoreProxy) -> None:
self.core = core
def post(self) -> None:
data = json.loads(self.request.body)
uri = data.get("uri", "")
if not uri:
self.set_status(400)
self.write({"error": "Missing 'uri' in request body"})
return
try:
self.core.playlists.delete(uri).get()
self.write({"status": "ok"})
except Exception as e:
logger.exception("Failed to delete playlist %s", uri)
self.set_status(500)
self.write({"error": str(e)})
class SmartQueueControlHandler(tornado.web.RequestHandler): class SmartQueueControlHandler(tornado.web.RequestHandler):
def initialize(self) -> None: def initialize(self) -> None:
pass pass
@ -395,5 +415,6 @@ def app_factory(config: Config, core: CoreProxy) -> list[tuple]:
{"core": core, "uris": uris, "variety_chance": 0.15}), {"core": core, "uris": uris, "variety_chance": 0.15}),
(r"/refresh", RefreshHandler, {"core": core, "config": config}), (r"/refresh", RefreshHandler, {"core": core, "config": config}),
(r"/status", StatusHandler, {"core": core, "prefix": prefix}), (r"/status", StatusHandler, {"core": core, "prefix": prefix}),
(r"/delete", PlaylistDeleteHandler, {"core": core}),
(r"/smart-queue", SmartQueueControlHandler, {}), (r"/smart-queue", SmartQueueControlHandler, {}),
] ]