feat: add announced checkbox to track rows
Add a persistent "announced" checkbox after each track's Announce button. The state is stored in a new `announced` column on `show_tracks` and is auto-set when the Announce button is pressed. The checkbox is also freely togglable, and announced tracks have their Announce button disabled. Also fixes .env leakage in test_config.py (pass _env_file=None) and adds tests for the new DB method, API endpoint, and announce side-effect. Made-with: Cursor
This commit is contained in:
@@ -66,7 +66,8 @@
|
||||
border-bottom-color: #4caf50;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-group { display: flex; gap: 4px; }
|
||||
.btn-group { display: flex; gap: 8px; align-items: center; }
|
||||
.announced-check { accent-color: #4caf50; cursor: pointer; width: 28px; height: 28px; margin: 0; }
|
||||
.ping-section {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1.5rem;
|
||||
@@ -149,7 +150,10 @@
|
||||
html += '<th class="track-num">#</th><th>Title</th><th>Artist</th><th></th>';
|
||||
html += '</tr></thead><tbody>';
|
||||
for (const t of tracks) {
|
||||
const disabled = subscriberCount === 0 ? 'disabled title="No bots connected"' : "";
|
||||
const noBot = subscriberCount === 0;
|
||||
const announced = !!t.announced;
|
||||
const annDisabled = (noBot || announced) ? 'disabled' : '';
|
||||
const annTitle = noBot ? 'title="No bots connected"' : announced ? 'title="Already announced"' : '';
|
||||
const copyText = `${t.title} by ${t.artist} - ${t.permalink_url}`;
|
||||
html += `<tr>
|
||||
<td class="track-num">${t.position}</td>
|
||||
@@ -159,8 +163,11 @@
|
||||
<div class="btn-group">
|
||||
<button class="btn-sm copy-btn outline"
|
||||
onclick="copyTrack(this, '${esc(copyText).replace(/'/g, "\\'")}')">Copy</button>
|
||||
<button class="btn-sm announce-btn" ${disabled}
|
||||
<button class="btn-sm announce-btn" ${annDisabled} ${annTitle}
|
||||
onclick="announce(${showId}, ${t.position}, this)">Announce</button>
|
||||
<input type="checkbox" class="announced-check"
|
||||
${announced ? 'checked' : ''}
|
||||
onchange="toggleAnnounced(${showId}, ${t.position}, this)">
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
@@ -275,10 +282,18 @@
|
||||
}
|
||||
btn.textContent = "\u2713";
|
||||
btn.classList.add("success");
|
||||
const cb = btn.closest("tr").querySelector(".announced-check");
|
||||
if (cb) cb.checked = true;
|
||||
const cached = showCache[showId];
|
||||
if (cached) {
|
||||
const track = cached.tracks.find(t => t.position === position);
|
||||
if (track) track.announced = 1;
|
||||
}
|
||||
setTimeout(() => {
|
||||
btn.textContent = "Announce";
|
||||
btn.classList.remove("success");
|
||||
btn.disabled = false;
|
||||
btn.disabled = true;
|
||||
btn.title = "Already announced";
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
showToast(e.message, true);
|
||||
@@ -287,6 +302,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleAnnounced(showId, position, cb) {
|
||||
const newVal = cb.checked;
|
||||
try {
|
||||
const resp = await fetch("/admin/announced", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({show_id: showId, position: position, announced: newVal}),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
throw new Error(data.detail || "Failed to update");
|
||||
}
|
||||
const cached = showCache[showId];
|
||||
if (cached) {
|
||||
const track = cached.tracks.find(t => t.position === position);
|
||||
if (track) track.announced = newVal ? 1 : 0;
|
||||
}
|
||||
const annBtn = cb.closest("tr").querySelector(".announce-btn");
|
||||
if (annBtn) {
|
||||
if (newVal) {
|
||||
annBtn.disabled = true;
|
||||
annBtn.title = "Already announced";
|
||||
} else if (subscriberCount > 0) {
|
||||
annBtn.disabled = false;
|
||||
annBtn.title = "";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
cb.checked = !newVal;
|
||||
showToast(e.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPing(btn) {
|
||||
const target = document.getElementById("ping-target").value.trim();
|
||||
const message = document.getElementById("ping-message").value.trim();
|
||||
@@ -344,9 +392,12 @@
|
||||
detail.style.display = "none";
|
||||
}
|
||||
document.querySelectorAll(".announce-btn").forEach(btn => {
|
||||
if (count === 0) {
|
||||
const row = btn.closest("tr");
|
||||
const cb = row ? row.querySelector(".announced-check") : null;
|
||||
const isAnnounced = cb && cb.checked;
|
||||
if (count === 0 || isAnnounced) {
|
||||
btn.disabled = true;
|
||||
btn.title = "No bots connected";
|
||||
btn.title = isAnnounced ? "Already announced" : "No bots connected";
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.title = "";
|
||||
|
||||
Reference in New Issue
Block a user