From 195e067b0f23393273cf2cf0baecf85703a8b6be Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Fri, 26 Jun 2026 13:12:46 -0400 Subject: [PATCH] [web] Allow removing playlists --- src/mopidy_smartplaylists/static/index.html | 45 ++++++++++++++++++++- src/mopidy_smartplaylists/web.py | 21 ++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/mopidy_smartplaylists/static/index.html b/src/mopidy_smartplaylists/static/index.html index a8cfb55..c0e715f 100644 --- a/src/mopidy_smartplaylists/static/index.html +++ b/src/mopidy_smartplaylists/static/index.html @@ -46,11 +46,35 @@ border-radius: var(--radius); background: var(--card); 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 { color: var(--muted); 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 { color: var(--muted); font-size: 0.875rem; @@ -249,10 +273,16 @@ async function loadStatus() { } list.innerHTML = data.smart_playlists.map(p => `
-
${esc(p.name)}
-
${p.tracks} track${p.tracks === 1 ? "" : "s"}
+
+
${esc(p.name)}
+
${p.tracks} track${p.tracks === 1 ? "" : "s"}
+
+
` ).join(""); + list.querySelectorAll(".del-btn").forEach(btn => { + btn.onclick = () => deletePlaylist(btn.dataset.uri); + }); } catch { list.innerHTML = '
Could not load playlists
'; } @@ -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(); }); diff --git a/src/mopidy_smartplaylists/web.py b/src/mopidy_smartplaylists/web.py index ef9e173..c3bd200 100644 --- a/src/mopidy_smartplaylists/web.py +++ b/src/mopidy_smartplaylists/web.py @@ -326,6 +326,26 @@ class StatusHandler(tornado.web.RequestHandler): 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): def initialize(self) -> None: pass @@ -395,5 +415,6 @@ def app_factory(config: Config, core: CoreProxy) -> list[tuple]: {"core": core, "uris": uris, "variety_chance": 0.15}), (r"/refresh", RefreshHandler, {"core": core, "config": config}), (r"/status", StatusHandler, {"core": core, "prefix": prefix}), + (r"/delete", PlaylistDeleteHandler, {"core": core}), (r"/smart-queue", SmartQueueControlHandler, {}), ]