From 4ae6004239d2f420f81b2a4ba7db9b2ef3826bdc Mon Sep 17 00:00:00 2001 From: peterino2 Date: Tue, 3 Feb 2026 20:41:17 -0800 Subject: [PATCH] added way to delete channels --- db.ts | 23 ++++++++++++++--------- public/channelSync.js | 39 ++++++++++++++++++++++++++++++++++++++- public/styles.css | 3 +++ server.ts | 12 ++++++++++++ 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/db.ts b/db.ts index 2d14310..775addc 100644 --- a/db.ts +++ b/db.ts @@ -386,15 +386,20 @@ export function deleteChannelFromDb(id: string): void { // Queue persistence functions export function saveChannelQueue(channelId: string, trackIds: string[]): void { - // Delete existing queue - db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId); - - // Insert new queue - const insert = db.query( - "INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)" - ); - for (let i = 0; i < trackIds.length; i++) { - insert.run(channelId, trackIds[i], i); + db.query("BEGIN").run(); + try { + db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId); + + const insert = db.query( + "INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)" + ); + for (let i = 0; i < trackIds.length; i++) { + insert.run(channelId, trackIds[i], i); + } + db.query("COMMIT").run(); + } catch (e) { + db.query("ROLLBACK").run(); + throw e; } } diff --git a/public/channelSync.js b/public/channelSync.js index b9ebbe9..9dff468 100644 --- a/public/channelSync.js +++ b/public/channelSync.js @@ -50,6 +50,28 @@ } }; + // Delete a channel + M.deleteChannel = async function(channelId) { + const channel = M.channels?.find(c => c.id === channelId); + if (!channel) return; + if (channel.isDefault) { + M.showToast("Cannot delete default channel"); + return; + } + if (!confirm(`Delete channel "${channel.name}"?`)) return; + try { + const res = await fetch(`/api/channels/${channelId}`, { method: "DELETE" }); + if (!res.ok) { + const err = await res.json(); + M.showToast(err.error || "Failed to delete channel"); + return; + } + M.showToast(`Channel "${channel.name}" deleted`); + } catch (e) { + M.showToast("Failed to delete channel"); + } + }; + // New channel creation with slideout input M.createNewChannel = async function() { const header = M.$("#channels-panel .panel-header"); @@ -129,14 +151,29 @@ const listenersHtml = Object.entries(counts).map(([name, count]) => `
${name}${count > 1 ? ` x${count}` : ""}
` ).join(""); + + // Show delete button for non-default channels if user is admin or creator + const canDelete = !ch.isDefault && M.currentUser && + (M.currentUser.isAdmin || ch.createdBy === M.currentUser.id); + const deleteBtn = canDelete ? `` : ""; + div.innerHTML = `
${ch.name} + ${deleteBtn} ${ch.listenerCount}
${listenersHtml}
`; - div.querySelector(".channel-header").onclick = () => M.switchChannel(ch.id); + const headerEl = div.querySelector(".channel-header"); + headerEl.querySelector(".channel-name").onclick = () => M.switchChannel(ch.id); + const delBtn = headerEl.querySelector(".btn-delete-channel"); + if (delBtn) { + delBtn.onclick = (e) => { + e.stopPropagation(); + M.deleteChannel(ch.id); + }; + } container.appendChild(div); } }; diff --git a/public/styles.css b/public/styles.css index 85d29af..4dc1675 100644 --- a/public/styles.css +++ b/public/styles.css @@ -29,6 +29,9 @@ h3 { font-size: 0.8rem; color: #666; margin-bottom: 0.3rem; text-transform: uppe #channels-list .channel-header:hover { background: #222; } #channels-list .channel-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; } #channels-list .listener-count { font-size: 0.65rem; color: #666; flex-shrink: 0; margin-left: 0.3rem; } +#channels-list .btn-delete-channel { background: none; border: none; color: #666; font-size: 0.9rem; cursor: pointer; padding: 0 0.2rem; line-height: 1; opacity: 0; transition: opacity 0.15s; } +#channels-list .channel-header:hover .btn-delete-channel { opacity: 1; } +#channels-list .btn-delete-channel:hover { color: #e44; } #channels-list .channel-listeners { display: flex; flex-direction: column; margin-left: 0.5rem; border-left: 1px solid #333; padding-left: 0.3rem; } #channels-list .listener { font-size: 0.65rem; color: #aaa; padding: 0.05rem 0; position: relative; } #channels-list .listener::before { content: ""; position: absolute; left: -0.3rem; top: 50%; width: 0.2rem; height: 1px; background: #333; } diff --git a/server.ts b/server.ts index 156aab7..0d56eed 100644 --- a/server.ts +++ b/server.ts @@ -475,6 +475,18 @@ serve({ if (!user.is_admin && channel.createdBy !== user.id) { return Response.json({ error: "Access denied" }, { status: 403 }); } + + // Move connected clients to default channel before deleting + const defaultChannel = [...channels.values()].find(c => c.isDefault); + if (defaultChannel && channel.clients.size > 0) { + for (const ws of channel.clients) { + channel.removeClient(ws); + ws.data.channelId = defaultChannel.id; + defaultChannel.addClient(ws); + ws.send(JSON.stringify({ type: "switched", channelId: defaultChannel.id })); + } + } + channels.delete(channelId); deleteChannelFromDb(channelId); broadcastChannelList();