[web] Allow removing playlists
This commit is contained in:
@ -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="count">${p.tracks} track${p.tracks === 1 ? "" : "s"}</div>
|
<div class="name">${esc(p.name)}</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>
|
||||||
|
|||||||
@ -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, {}),
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user