added way to delete channels

This commit is contained in:
peterino2 2026-02-03 20:41:17 -08:00
parent a90a9f0a0d
commit 4ae6004239
4 changed files with 67 additions and 10 deletions

23
db.ts
View File

@ -386,15 +386,20 @@ export function deleteChannelFromDb(id: string): void {
// Queue persistence functions // Queue persistence functions
export function saveChannelQueue(channelId: string, trackIds: string[]): void { export function saveChannelQueue(channelId: string, trackIds: string[]): void {
// Delete existing queue db.query("BEGIN").run();
db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId); try {
db.query("DELETE FROM channel_queue WHERE channel_id = ?").run(channelId);
// Insert new queue
const insert = db.query( const insert = db.query(
"INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)" "INSERT INTO channel_queue (channel_id, track_id, position) VALUES (?, ?, ?)"
); );
for (let i = 0; i < trackIds.length; i++) { for (let i = 0; i < trackIds.length; i++) {
insert.run(channelId, trackIds[i], i); insert.run(channelId, trackIds[i], i);
}
db.query("COMMIT").run();
} catch (e) {
db.query("ROLLBACK").run();
throw e;
} }
} }

View File

@ -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 // New channel creation with slideout input
M.createNewChannel = async function() { M.createNewChannel = async function() {
const header = M.$("#channels-panel .panel-header"); const header = M.$("#channels-panel .panel-header");
@ -129,14 +151,29 @@
const listenersHtml = Object.entries(counts).map(([name, count]) => const listenersHtml = Object.entries(counts).map(([name, count]) =>
`<div class="listener">${name}${count > 1 ? ` <span class="listener-mult">x${count}</span>` : ""}</div>` `<div class="listener">${name}${count > 1 ? ` <span class="listener-mult">x${count}</span>` : ""}</div>`
).join(""); ).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 ? `<button class="btn-delete-channel" title="Delete channel">×</button>` : "";
div.innerHTML = ` div.innerHTML = `
<div class="channel-header"> <div class="channel-header">
<span class="channel-name">${ch.name}</span> <span class="channel-name">${ch.name}</span>
${deleteBtn}
<span class="listener-count">${ch.listenerCount}</span> <span class="listener-count">${ch.listenerCount}</span>
</div> </div>
<div class="channel-listeners">${listenersHtml}</div> <div class="channel-listeners">${listenersHtml}</div>
`; `;
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); container.appendChild(div);
} }
}; };

View File

@ -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-header:hover { background: #222; }
#channels-list .channel-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.8rem; } #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 .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 .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 { 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; } #channels-list .listener::before { content: ""; position: absolute; left: -0.3rem; top: 50%; width: 0.2rem; height: 1px; background: #333; }

View File

@ -475,6 +475,18 @@ serve({
if (!user.is_admin && channel.createdBy !== user.id) { if (!user.is_admin && channel.createdBy !== user.id) {
return Response.json({ error: "Access denied" }, { status: 403 }); 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); channels.delete(channelId);
deleteChannelFromDb(channelId); deleteChannelFromDb(channelId);
broadcastChannelList(); broadcastChannelList();