(function () { var DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; function formatDays(daysValue) { var days = []; if (Array.isArray(daysValue)) { days = daysValue; } else if (daysValue) { try { var parsedDays = JSON.parse(daysValue); days = Array.isArray(parsedDays) ? parsedDays : []; } catch (_error) { days = []; } } return days .map(function (day) { return DAY_NAMES[Number(day)] || ''; }) .filter(Boolean) .join(', '); } function initPlaylistScheduleModal() { var dialog = document.getElementById('slide-schedule-dialog'); var content = document.getElementById('slide-schedule-content'); var triggers = document.querySelectorAll('[data-schedule-config]'); if (!dialog || !content || !triggers.length) { return; } function initInjectedContent() { if (typeof window.initPlaylistScheduleForm === 'function') { window.initPlaylistScheduleForm(content); } } function collectScheduleParams(row, rowKey) { var params = new URLSearchParams(); var scheduleFields = [ 'schedule_mode[]', 'schedule_start_datetime[]', 'schedule_end_datetime[]', 'schedule_start_time[]', 'schedule_end_time[]', 'schedule_days_json[]' ]; params.set('row_key', String(rowKey || '')); scheduleFields.forEach(function (fieldName) { var field = row && row.querySelector ? row.querySelector('[name="' + fieldName + '"]') : null; var value = field && typeof field.value !== 'undefined' ? String(field.value || '') : ''; if (value) { params.set(fieldName.replace(/\[\]$/, ''), value); } }); return params; } function openScheduleModal(url) { content.innerHTML = '
Loading schedule...
'; if (typeof dialog.showModal === 'function') { dialog.showModal(); } else { dialog.setAttribute('open', 'open'); } fetch(url, { credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { throw new Error(text || 'Unable to open schedule editor.'); }); } return response.text(); }).then(function (html) { content.innerHTML = html; initInjectedContent(); }).catch(function (error) { content.innerHTML = '
' + String(error && error.message ? error.message : 'Unable to open schedule editor.') + '
'; }); } function closeScheduleModal() { content.innerHTML = ''; if (typeof dialog.close === 'function') { dialog.close(); } else { dialog.removeAttribute('open'); } } window.openScheduleModal = openScheduleModal; window.closeScheduleModal = closeScheduleModal; Array.prototype.forEach.call(triggers, function (trigger) { trigger.addEventListener('click', function () { var url = trigger.getAttribute('data-schedule-config'); var rowKey = trigger.getAttribute('data-schedule-config-row') || ''; var row = trigger.closest('tr[data-playlist-slide-row]'); if (row) { var params = collectScheduleParams(row, rowKey || row.getAttribute('data-row-key') || ''); url += (url.indexOf('?') === -1 ? '?' : '&') + params.toString(); } else if (rowKey && url.indexOf('row_key=') === -1) { url += (url.indexOf('?') === -1 ? '?' : '&') + 'row_key=' + encodeURIComponent(rowKey); } openScheduleModal(url); }); }); } function scheduleSummary(values) { var mode = String(values.schedule_mode || 'always'); if (mode === 'dates') { if (values.schedule_start_datetime && values.schedule_end_datetime) { return 'Dates: ' + values.schedule_start_datetime.replace('T', ' ') + ' to ' + values.schedule_end_datetime.replace('T', ' '); } return 'Dates: not set'; } if (mode === 'times') { var days = formatDays(values.schedule_days_json); if (days && values.schedule_start_time && values.schedule_end_time) { return 'Times: ' + days + ' ' + values.schedule_start_time.slice(0, 5) + '-' + values.schedule_end_time.slice(0, 5); } return 'Times: not set'; } return 'Always visible'; } function initPlaylistScheduleForm(root) { var scope = root || document; var form = scope.querySelector('form[action*="/config"]'); var select = scope.querySelector('#schedule-mode-select'); if (!form || !select) { return; } var DEFAULT_START_TIME = '00:00'; var DEFAULT_END_TIME = '23:59'; var datesPanel = scope.querySelector('#schedule-dates-panel'); var timesPanel = scope.querySelector('#schedule-times-panel'); var startDateInput = form.querySelector('[name="schedule_start_datetime"]'); var endDateInput = form.querySelector('[name="schedule_end_datetime"]'); var startTimeInput = form.querySelector('[name="schedule_start_time"]'); var endTimeInput = form.querySelector('[name="schedule_end_time"]'); var rowKeyInput = form.querySelector('[name="row_key"]'); var dayCheckboxes = form.querySelectorAll('[name="schedule_days"]'); function clearScheduleValidity() { [startDateInput, endDateInput, startTimeInput, endTimeInput].forEach(function (input) { if (input) { input.setCustomValidity(''); } }); } function setScheduleError(input, message) { if (!input) { return; } input.setCustomValidity(message); } function validateScheduleForm() { var mode = select.value; var firstDayCheckbox = dayCheckboxes.length ? dayCheckboxes[0] : null; clearScheduleValidity(); if (mode === 'dates') { if (!startDateInput.value) { setScheduleError(startDateInput, 'Start datetime is required for this schedule mode.'); } if (!endDateInput.value) { setScheduleError(endDateInput, 'End datetime is required for this schedule mode.'); } if (startDateInput.value && endDateInput.value && new Date(endDateInput.value) < new Date(startDateInput.value)) { setScheduleError(endDateInput, 'End datetime must be after start datetime.'); } } if (mode === 'times') { if (!startTimeInput.value) { setScheduleError(startTimeInput, 'Start time is required for this schedule mode.'); } if (!endTimeInput.value) { setScheduleError(endTimeInput, 'End time is required for this schedule mode.'); } if (startTimeInput.value && endTimeInput.value && endTimeInput.value < startTimeInput.value) { setScheduleError(endTimeInput, 'End time must be after start time.'); } if (!Array.prototype.some.call(dayCheckboxes, function (checkbox) { return checkbox.checked; })) { setScheduleError(firstDayCheckbox, 'Select at least one day.'); } } return form.checkValidity(); } [startDateInput, endDateInput, startTimeInput, endTimeInput].forEach(function (input) { if (input) { input.addEventListener('input', clearScheduleValidity); } }); Array.prototype.forEach.call(dayCheckboxes, function (checkbox) { checkbox.addEventListener('change', clearScheduleValidity); }); select.addEventListener('change', clearScheduleValidity); function notifyParentResize() { return; } function updateVisibility() { var mode = select.value; if (datesPanel) { datesPanel.classList.toggle('is-hidden', mode !== 'dates'); } if (timesPanel) { timesPanel.classList.toggle('is-hidden', mode !== 'times'); } if (mode === 'times') { if (!startTimeInput.value) { startTimeInput.value = DEFAULT_START_TIME; } if (!endTimeInput.value) { endTimeInput.value = DEFAULT_END_TIME; } } window.requestAnimationFrame(function () { notifyParentResize(); }); } select.addEventListener('change', updateVisibility); updateVisibility(); form.addEventListener('submit', function (event) { event.preventDefault(); if (!validateScheduleForm()) { form.reportValidity(); return; } var mode = select.value; var selectedDays = []; Array.prototype.forEach.call(dayCheckboxes, function (checkbox) { if (checkbox.checked) { selectedDays.push(Number(checkbox.value)); } }); var values = { row_key: String(rowKeyInput && rowKeyInput.value ? rowKeyInput.value : ''), schedule_mode: mode, schedule_start_datetime: startDateInput.value || '', schedule_end_datetime: endDateInput.value || '', schedule_start_time: startTimeInput.value || '', schedule_end_time: endTimeInput.value || '', schedule_days_json: JSON.stringify(selectedDays.sort()), summary: '' }; values.summary = scheduleSummary(values); if (window.parent && typeof window.parent.applyPlaylistScheduleConfig === 'function') { window.parent.applyPlaylistScheduleConfig(values); if (typeof window.parent.closeScheduleModal === 'function') { window.parent.closeScheduleModal(); } return; } var payload = new URLSearchParams(new FormData(form)); fetch(form.action, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: payload.toString(), credentials: 'same-origin' }).then(function (response) { if (!response.ok) { return response.text().then(function (text) { throw new Error(text || 'Unable to save schedule.'); }); } if (window.parent && typeof window.parent.closeScheduleModal === 'function') { window.parent.closeScheduleModal(); } }).catch(function (error) { alert(error.message || 'Unable to save schedule.'); }); }); } function initPlaylistEditStaging() { var tbody = document.getElementById('playlist-items-body'); var form = document.getElementById('playlist-edit-form'); var addSection = document.getElementById('playlist-add-section'); var addSlideForm = document.getElementById('playlist-add-slide-form'); var addSlideButton = document.getElementById('playlist-add-slide-button'); var addSlideSelect = document.getElementById('playlist-add-slide-select'); var addSlideEmpty = document.getElementById('playlist-add-slide-empty'); if (!tbody || !form) { return; } function getRows() { return Array.prototype.slice.call(tbody.querySelectorAll('tr[data-playlist-slide-row]')); } function findRowByKey(rowKey) { var rows = getRows(); for (var i = 0; i < rows.length; i += 1) { if (rows[i].getAttribute('data-row-key') === rowKey) { return rows[i]; } } return null; } function updateEmptyState() { var rows = getRows(); var placeholder = tbody.querySelector('.playlist-empty-row'); if (rows.length) { if (placeholder) { placeholder.remove(); } return; } if (!placeholder) { tbody.innerHTML = 'No slides assigned yet.'; } } function markPlaylistDirty() { if (form && form.dataset) { form.dataset.dirty = 'true'; } } function scheduleSummaryForRow(row) { var modeInput = row.querySelector('[name="schedule_mode[]"]'); var startDatetime = row.querySelector('[name="schedule_start_datetime[]"]'); var endDatetime = row.querySelector('[name="schedule_end_datetime[]"]'); var startTime = row.querySelector('[name="schedule_start_time[]"]'); var endTime = row.querySelector('[name="schedule_end_time[]"]'); var daysJson = row.querySelector('[name="schedule_days_json[]"]'); var mode = String(modeInput ? modeInput.value : 'always'); var days = formatDays(daysJson && daysJson.value ? daysJson.value : '[]'); if (mode === 'dates') { if (startDatetime && endDatetime && startDatetime.value && endDatetime.value) { return 'Dates: ' + startDatetime.value.replace('T', ' ') + ' to ' + endDatetime.value.replace('T', ' '); } return 'Dates: not set'; } if (mode === 'times') { if (days && startTime && endTime && startTime.value && endTime.value) { return 'Times: ' + days + ' ' + startTime.value.slice(0, 5) + '-' + endTime.value.slice(0, 5); } return 'Times: not set'; } return 'Always visible'; } function syncAddSlideOptions() { if (!addSlideSelect) { return; } var activeSlideIds = {}; var activeCanvasSignatures = {}; getRows().forEach(function (row) { var slideId = String(row.getAttribute('data-slide-id') || ''); var canvasSignature = String(row.getAttribute('data-canvas-signature') || ''); if (slideId) { activeSlideIds[slideId] = true; } if (canvasSignature) { activeCanvasSignatures[canvasSignature] = true; } }); var allowedCanvasSignature = ''; var canvasKeys = Object.keys(activeCanvasSignatures); if (canvasKeys.length === 1) { allowedCanvasSignature = canvasKeys[0]; } Array.prototype.forEach.call(addSlideSelect.options, function (option) { if (!option.value) { return; } var optionCanvasSignature = String(option.getAttribute('data-canvas-signature') || ''); var isActive = Boolean(activeSlideIds[String(option.value)]); var isCanvasMismatch = Boolean(allowedCanvasSignature && optionCanvasSignature && optionCanvasSignature !== allowedCanvasSignature); option.disabled = isActive || isCanvasMismatch; option.hidden = isActive || isCanvasMismatch; }); if ( addSlideSelect.options[addSlideSelect.selectedIndex] && (addSlideSelect.options[addSlideSelect.selectedIndex].disabled || addSlideSelect.options[addSlideSelect.selectedIndex].hidden) ) { for (var i = 0; i < addSlideSelect.options.length; i += 1) { if (!addSlideSelect.options[i].disabled && !addSlideSelect.options[i].hidden) { addSlideSelect.selectedIndex = i; break; } } } var availableCount = 0; Array.prototype.forEach.call(addSlideSelect.options, function (option) { if (!option.value) { return; } if (!option.disabled && !option.hidden) { availableCount += 1; } }); if (addSlideButton) { addSlideButton.disabled = availableCount === 0; } if (addSlideSelect) { addSlideSelect.disabled = availableCount === 0; } if (addSlideEmpty) { addSlideEmpty.classList.toggle('is-hidden', availableCount !== 0); } if (addSection && addSlideForm && addSlideSelect && addSlideButton) { var controlsVisible = availableCount > 0; addSlideForm.classList.toggle('is-hidden', !controlsVisible); } } function updateRowOrder(markDirty) { var rows = getRows(); rows.forEach(function (row, index) { var orderNumber = row.querySelector('.playlist-order-number'); if (orderNumber) { orderNumber.textContent = String(index + 1); } }); syncAddSlideOptions(); updateEmptyState(); if (markDirty) { markPlaylistDirty(); } } function lockDraggedRowWidths(row) { if (!row) { return; } var rowRect = row.getBoundingClientRect(); row.style.width = rowRect.width + 'px'; row.style.height = rowRect.height + 'px'; row.style.boxSizing = 'border-box'; Array.prototype.forEach.call(row.children, function (cell) { var cellRect = cell.getBoundingClientRect(); cell.style.width = cellRect.width + 'px'; cell.style.height = cellRect.height + 'px'; cell.style.boxSizing = 'border-box'; }); } function unlockDraggedRowWidths(row) { if (!row) { return; } row.style.width = ''; row.style.height = ''; row.style.boxSizing = ''; Array.prototype.forEach.call(row.children, function (cell) { cell.style.width = ''; cell.style.height = ''; cell.style.boxSizing = ''; }); } function setRowSchedule(row, values) { var modeInput = row.querySelector('[name="schedule_mode[]"]'); var startDatetime = row.querySelector('[name="schedule_start_datetime[]"]'); var endDatetime = row.querySelector('[name="schedule_end_datetime[]"]'); var startTime = row.querySelector('[name="schedule_start_time[]"]'); var endTime = row.querySelector('[name="schedule_end_time[]"]'); var daysJson = row.querySelector('[name="schedule_days_json[]"]'); var summary = row.querySelector('.playlist-schedule-summary'); if (modeInput) { modeInput.value = values.schedule_mode || 'always'; } if (startDatetime) { startDatetime.value = values.schedule_start_datetime || ''; } if (endDatetime) { endDatetime.value = values.schedule_end_datetime || ''; } if (startTime) { startTime.value = values.schedule_start_time || ''; } if (endTime) { endTime.value = values.schedule_end_time || ''; } if (daysJson) { daysJson.value = values.schedule_days_json || '[]'; } if (summary) { summary.textContent = values.summary || scheduleSummaryForRow(row); } markPlaylistDirty(); } function createRow(values) { var row = document.createElement('tr'); var rowKey = values.row_key || ('new-' + Date.now() + '-' + Math.random().toString(36).slice(2)); var playlistId = tbody.getAttribute('data-playlist-id') || ''; row.setAttribute('data-playlist-slide-row', ''); row.setAttribute('data-row-key', rowKey); row.setAttribute('data-slide-id', String(values.slide_id)); row.setAttribute('data-canvas-signature', String(values.canvas_signature || '')); row.innerHTML = '' + '' + '
' + '' + '' + '
' + '' + '' + values.title + '' + '' + '
' + values.summary + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '' + '' + '
'; return row; } tbody.addEventListener('click', function (event) { var removeButton = event.target.closest('[data-playlist-remove-row]'); var scheduleButton = event.target.closest('[data-schedule-config]'); var row = event.target.closest('tr[data-playlist-slide-row]'); if (!row) { return; } if (removeButton) { event.preventDefault(); row.remove(); updateRowOrder(true); return; } if (scheduleButton) { event.preventDefault(); if (typeof window.openScheduleModal === 'function') { window.openScheduleModal(scheduleButton.getAttribute('data-schedule-config') + '?row_key=' + encodeURIComponent(row.getAttribute('data-row-key') || '')); } } }); if (window.Sortable) { Sortable.create(tbody, { animation: 180, handle: '[data-playlist-drag-handle]', draggable: 'tr[data-playlist-slide-row]', ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', forceFallback: true, fallbackOnBody: true, fallbackTolerance: 3, swapThreshold: 0.65, invertedSwapThreshold: 0.65, onChoose: function (event) { lockDraggedRowWidths(event && event.item); }, onUnchoose: function (event) { unlockDraggedRowWidths(event && event.item); }, onEnd: function () { unlockDraggedRowWidths(tbody.querySelector('.sortable-drag')); updateRowOrder(true); } }); } if (addSlideButton && addSlideSelect) { addSlideButton.addEventListener('click', function () { var selectedOption = addSlideSelect.options[addSlideSelect.selectedIndex]; if (!selectedOption || selectedOption.disabled) { return; } var placeholder = tbody.querySelector('.playlist-empty-row'); if (placeholder) { placeholder.remove(); } var row = createRow({ row_key: 'new-' + Date.now(), slide_id: String(selectedOption.value), canvas_signature: String(selectedOption.getAttribute('data-canvas-signature') || ''), title: selectedOption.textContent || 'Slide', duration_seconds: 10, schedule_mode: 'always', schedule_start_datetime: '', schedule_end_datetime: '', schedule_start_time: '', schedule_end_time: '', schedule_days_json: '[]', summary: 'Always visible' }); tbody.appendChild(row); updateRowOrder(true); }); } window.applyPlaylistScheduleConfig = function (values) { var row = findRowByKey(String(values.row_key || '')); if (!row) { return; } setRowSchedule(row, values); }; updateRowOrder(); } window.initPlaylistScheduleForm = initPlaylistScheduleForm; initPlaylistScheduleModal(); initPlaylistScheduleForm(); initPlaylistEditStaging(); }());