+
+
Description
+
+
0 /200
+
+
+
+
Slide transition
+
+ None
+ Fade
+ Slide
+
+
Applied on the display when switching between playlist items.
+
+
Tick the playlists that should be active on this display.
@@ -285,66 +291,6 @@
return data.display;
}
- // Description modal
- const modalEl = document.getElementById('editDescModal');
- const modal = modalEl ? new bootstrap.Modal(modalEl) : null;
- const titleEl = document.getElementById('editDescModalTitle');
- const inputEl = document.getElementById('editDescInput');
- const countEl = document.getElementById('editDescCount');
- const saveBtn = document.getElementById('editDescSaveBtn');
-
- let activeDisplayId = null;
-
- function updateCount() {
- if (!inputEl || !countEl) return;
- countEl.textContent = String((inputEl.value || '').length);
- }
- if (inputEl) inputEl.addEventListener('input', updateCount);
-
- document.querySelectorAll('.js-edit-desc').forEach((btn) => {
- btn.addEventListener('click', () => {
- activeDisplayId = btn.dataset.displayId;
- const displayName = btn.dataset.displayName || 'Display';
- const currentDesc = btn.dataset.currentDesc || '';
- if (titleEl) titleEl.textContent = `Edit description — ${displayName}`;
- if (inputEl) inputEl.value = currentDesc;
- updateCount();
- if (modal) modal.show();
- });
- });
-
- async function saveDescription() {
- if (!activeDisplayId || !inputEl) return;
- const desc = (inputEl.value || '').trim();
- saveBtn.disabled = true;
- try {
- const updated = await postDisplayUpdate(activeDisplayId, { description: desc });
- // Update visible description
- const descEl = document.querySelector(`.js-display-desc[data-display-id="${activeDisplayId}"]`);
- if (descEl) descEl.textContent = updated.description ? updated.description : '—';
- // Update button's stored value
- const btn = document.querySelector(`.js-edit-desc[data-display-id="${activeDisplayId}"]`);
- if (btn) btn.dataset.currentDesc = updated.description || '';
- showToast('Description saved', 'text-bg-success');
- if (modal) modal.hide();
- } catch (e) {
- showToast(e && e.message ? e.message : 'Save failed', 'text-bg-danger');
- } finally {
- saveBtn.disabled = false;
- }
- }
- if (saveBtn) {
- saveBtn.addEventListener('click', saveDescription);
- }
- if (modalEl) {
- modalEl.addEventListener('keydown', (e) => {
- if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- saveDescription();
- }
- });
- }
-
// Playlists modal
const plModalEl = document.getElementById('editPlaylistsModal');
const plModal = plModalEl ? new bootstrap.Modal(plModalEl) : null;
@@ -352,9 +298,18 @@
const plListEl = document.getElementById('editPlaylistsList');
const plHintEl = document.getElementById('editPlaylistsHint');
const plSaveBtn = document.getElementById('editPlaylistsSaveBtn');
+ const plDescInputEl = document.getElementById('editPlaylistsDescInput');
+ const plDescCountEl = document.getElementById('editPlaylistsDescCount');
+ const plTransitionEl = document.getElementById('editPlaylistsTransitionSelect');
let activePlDisplayId = null;
let activePlButton = null;
+ function updatePlDescCount() {
+ if (!plDescInputEl || !plDescCountEl) return;
+ plDescCountEl.textContent = String((plDescInputEl.value || '').length);
+ }
+ if (plDescInputEl) plDescInputEl.addEventListener('input', updatePlDescCount);
+
function renderPlaylistCheckboxes(selectedIds) {
if (!plListEl) return;
plListEl.innerHTML = '';
@@ -401,7 +356,15 @@
activePlDisplayId = btn.dataset.displayId;
activePlButton = btn;
const displayName = btn.dataset.displayName || 'Display';
- if (plTitleEl) plTitleEl.textContent = `Select playlists — ${displayName}`;
+ if (plTitleEl) plTitleEl.textContent = `Configure display — ${displayName}`;
+
+ const currentDesc = btn.dataset.currentDesc || '';
+ if (plDescInputEl) plDescInputEl.value = currentDesc;
+ updatePlDescCount();
+
+ const currentTransition = (btn.dataset.currentTransition || 'none').toLowerCase();
+ if (plTransitionEl) plTransitionEl.value = ['none','fade','slide'].includes(currentTransition) ? currentTransition : 'none';
+
const selected = computeActiveIdsFromDataset(btn);
renderPlaylistCheckboxes(selected);
if (plHintEl) {
@@ -414,13 +377,31 @@
async function savePlaylists() {
if (!activePlDisplayId || !activePlButton || !plSaveBtn) return;
const ids = getSelectedPlaylistIdsFromModal();
+ const desc = plDescInputEl ? (plDescInputEl.value || '').trim() : '';
+ const transition = plTransitionEl ? (plTransitionEl.value || 'none') : 'none';
plSaveBtn.disabled = true;
try {
- const updated = await postDisplayPlaylists(activePlDisplayId, ids);
- const newIds = (updated && updated.active_playlist_ids) ? updated.active_playlist_ids : ids;
+ const [updatedPlaylists, updatedDesc] = await Promise.all([
+ postDisplayPlaylists(activePlDisplayId, ids),
+ postDisplayUpdate(activePlDisplayId, { description: desc, transition })
+ ]);
+
+ const newIds = (updatedPlaylists && updatedPlaylists.active_playlist_ids)
+ ? updatedPlaylists.active_playlist_ids
+ : ids;
setActiveIdsOnButton(activePlButton, newIds);
refreshActivePlaylistSummary(activePlDisplayId, newIds);
- showToast('Playlists saved', 'text-bg-success');
+
+ const descEl = document.querySelector(`.js-display-desc[data-display-id="${activePlDisplayId}"]`);
+ const newDesc = updatedDesc && typeof updatedDesc.description === 'string' ? updatedDesc.description : desc;
+ if (descEl) descEl.textContent = newDesc ? newDesc : '—';
+ activePlButton.dataset.currentDesc = newDesc || '';
+
+ // Keep button dataset in sync so reopening modal shows correct value.
+ const newTransition = updatedDesc && typeof updatedDesc.transition === 'string' ? updatedDesc.transition : transition;
+ activePlButton.dataset.currentTransition = newTransition || 'none';
+
+ showToast('Display updated', 'text-bg-success');
refreshPreviewIframe(activePlDisplayId);
if (plModal) plModal.hide();
} catch (e) {
diff --git a/app/templates/company/playlist_detail.html b/app/templates/company/playlist_detail.html
index a9d623f..92dae52 100644
--- a/app/templates/company/playlist_detail.html
+++ b/app/templates/company/playlist_detail.html
@@ -61,6 +61,12 @@
/* Modal step visibility */
.step { display: none; }
.step.active { display: block; }
+
+ /* Tiny status pills/icons */
+ .priority-pill { color: #dc3545; font-weight: 700; }
+ .schedule-pill { font-weight: 700; }
+ .schedule-pill.active { color: #198754; }
+ .schedule-pill.inactive { color: #dc3545; }
Playlist: {{ playlist.name }}
@@ -80,6 +86,113 @@
+ {# Priority + schedule indicators #}
+ {% set has_schedule = (playlist.schedule_start is not none) or (playlist.schedule_end is not none) %}
+ {% set schedule_active = (not playlist.schedule_start or playlist.schedule_start <= now_utc) and (not playlist.schedule_end or now_utc <= playlist.schedule_end) %}
+
+
+
+ {% if playlist.is_priority %}
+ âť— Priority
+ {% else %}
+ Not priority
+ {% endif %}
+
+ {% if has_schedule %}
+
+ đź“… Scheduled
+ (… )
+
+ {% else %}
+ Not scheduled
+ {% endif %}
+
+
+
+ {# Priority toggle: auto-saves (no Save button) #}
+
+
+ {# Schedule button moved to where Save button used to be #}
+
+ Schedule
+
+
+