528 lines
19 KiB
JavaScript
528 lines
19 KiB
JavaScript
(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 frame = document.getElementById('slide-schedule-frame');
|
|
var closeButton = document.getElementById('slide-schedule-close');
|
|
var triggers = document.querySelectorAll('[data-schedule-config]');
|
|
|
|
if (!dialog || !frame || !closeButton || !triggers.length) {
|
|
return;
|
|
}
|
|
|
|
function openScheduleModal(url) {
|
|
frame.src = url;
|
|
if (typeof dialog.showModal === 'function') {
|
|
dialog.showModal();
|
|
} else {
|
|
dialog.setAttribute('open', 'open');
|
|
}
|
|
}
|
|
|
|
function resizeScheduleFrame() {
|
|
try {
|
|
if (!frame.contentWindow || !frame.contentWindow.document) {
|
|
return;
|
|
}
|
|
var doc = frame.contentWindow.document;
|
|
var height = Math.max(
|
|
doc.body.scrollHeight,
|
|
doc.documentElement.scrollHeight,
|
|
doc.body.offsetHeight,
|
|
doc.documentElement.offsetHeight
|
|
);
|
|
frame.style.height = height + 'px';
|
|
} catch (_error) {
|
|
frame.style.height = '70vh';
|
|
}
|
|
}
|
|
|
|
function closeScheduleModal() {
|
|
frame.src = 'about:blank';
|
|
if (typeof dialog.close === 'function') {
|
|
dialog.close();
|
|
} else {
|
|
dialog.removeAttribute('open');
|
|
}
|
|
}
|
|
|
|
window.openScheduleModal = openScheduleModal;
|
|
window.resizeScheduleFrame = resizeScheduleFrame;
|
|
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') || '';
|
|
if (rowKey && url.indexOf('row_key=') === -1) {
|
|
url += (url.indexOf('?') === -1 ? '?' : '&') + 'row_key=' + encodeURIComponent(rowKey);
|
|
}
|
|
openScheduleModal(url);
|
|
});
|
|
});
|
|
|
|
frame.addEventListener('load', resizeScheduleFrame);
|
|
closeButton.addEventListener('click', closeScheduleModal);
|
|
}
|
|
|
|
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() {
|
|
var form = document.querySelector('form[action*="/config"]');
|
|
var select = document.getElementById('schedule-mode-select');
|
|
if (!form || !select) {
|
|
return;
|
|
}
|
|
|
|
var datesPanel = document.getElementById('schedule-dates-panel');
|
|
var timesPanel = document.getElementById('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 notifyParentResize() {
|
|
if (window.parent && typeof window.parent.resizeScheduleFrame === 'function') {
|
|
window.parent.resizeScheduleFrame();
|
|
}
|
|
}
|
|
|
|
function updateVisibility() {
|
|
var mode = select.value;
|
|
datesPanel.style.display = mode === 'dates' ? '' : 'none';
|
|
timesPanel.style.display = mode === 'times' ? '' : 'none';
|
|
window.requestAnimationFrame(function () {
|
|
notifyParentResize();
|
|
});
|
|
}
|
|
|
|
select.addEventListener('change', updateVisibility);
|
|
updateVisibility();
|
|
|
|
form.addEventListener('submit', function (event) {
|
|
event.preventDefault();
|
|
|
|
var mode = select.value;
|
|
var datesIncomplete = mode === 'dates' && (!startDateInput.value || !endDateInput.value);
|
|
var timesIncomplete = mode === 'times' && (!startTimeInput.value || !endTimeInput.value || !Array.prototype.some.call(dayCheckboxes, function (checkbox) {
|
|
return checkbox.checked;
|
|
}));
|
|
|
|
if (mode === 'dates' && !datesIncomplete) {
|
|
if (new Date(endDateInput.value) < new Date(startDateInput.value)) {
|
|
alert('End datetime must be after start datetime.');
|
|
return;
|
|
}
|
|
}
|
|
if (mode === 'times' && !timesIncomplete) {
|
|
if (endTimeInput.value < startTimeInput.value) {
|
|
alert('End time must be after start time.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (datesIncomplete || timesIncomplete) {
|
|
mode = 'always';
|
|
select.value = 'always';
|
|
startDateInput.value = '';
|
|
endDateInput.value = '';
|
|
startTimeInput.value = '';
|
|
endTimeInput.value = '';
|
|
Array.prototype.forEach.call(dayCheckboxes, function (checkbox) {
|
|
checkbox.checked = false;
|
|
});
|
|
updateVisibility();
|
|
}
|
|
|
|
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 addDurationInput = document.getElementById('playlist-add-duration');
|
|
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 = '<tr class="playlist-empty-row"><td colspan="5" class="empty">No slides assigned yet.</td></tr>';
|
|
}
|
|
}
|
|
|
|
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 (addDurationInput) {
|
|
addDurationInput.disabled = availableCount === 0;
|
|
}
|
|
if (addSlideEmpty) {
|
|
addSlideEmpty.style.display = availableCount === 0 ? '' : 'none';
|
|
}
|
|
if (addSection && addSlideForm && addSlideSelect && addSlideButton) {
|
|
var controlsVisible = availableCount > 0;
|
|
addSlideForm.style.display = controlsVisible ? '' : 'none';
|
|
}
|
|
}
|
|
|
|
function updateRowOrder() {
|
|
var rows = getRows();
|
|
rows.forEach(function (row, index) {
|
|
var orderCell = row.querySelector('.playlist-order-cell');
|
|
var moveUp = row.querySelector('[data-playlist-move="up"]');
|
|
var moveDown = row.querySelector('[data-playlist-move="down"]');
|
|
if (orderCell) {
|
|
orderCell.textContent = String(index + 1);
|
|
}
|
|
if (moveUp) {
|
|
moveUp.disabled = index === 0;
|
|
}
|
|
if (moveDown) {
|
|
moveDown.disabled = index === rows.length - 1;
|
|
}
|
|
});
|
|
syncAddSlideOptions();
|
|
updateEmptyState();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
function createRow(values) {
|
|
var row = document.createElement('tr');
|
|
var rowKey = values.row_key || ('new-' + Date.now() + '-' + Math.random().toString(36).slice(2));
|
|
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 = '' +
|
|
'<td class="playlist-order-cell"></td>' +
|
|
'<td>' + values.title + '<input type="hidden" name="slide_id[]" value="' + values.slide_id + '" /></td>' +
|
|
'<td><input name="duration_seconds[]" type="number" min="1" value="' + values.duration_seconds + '" required /></td>' +
|
|
'<td>' +
|
|
'<div class="playlist-schedule-summary">' + values.summary + '</div>' +
|
|
'<input type="hidden" name="schedule_mode[]" value="' + values.schedule_mode + '" />' +
|
|
'<input type="hidden" name="schedule_start_datetime[]" value="' + values.schedule_start_datetime + '" />' +
|
|
'<input type="hidden" name="schedule_end_datetime[]" value="' + values.schedule_end_datetime + '" />' +
|
|
'<input type="hidden" name="schedule_start_time[]" value="' + values.schedule_start_time + '" />' +
|
|
'<input type="hidden" name="schedule_end_time[]" value="' + values.schedule_end_time + '" />' +
|
|
'<input type="hidden" name="schedule_days_json[]" value="' + values.schedule_days_json + '" />' +
|
|
'</td>' +
|
|
'<td><div class="actions playlist-item-actions">' +
|
|
'<button type="button" class="secondary" data-playlist-move="up">Up</button>' +
|
|
'<button type="button" class="secondary" data-playlist-move="down">Down</button>' +
|
|
'<button type="button" class="schedule-config-button" disabled>Schedule</button>' +
|
|
'<button type="button" class="danger" data-playlist-remove-row>Remove</button>' +
|
|
'</div></td>';
|
|
return row;
|
|
}
|
|
|
|
tbody.addEventListener('click', function (event) {
|
|
var moveButton = event.target.closest('[data-playlist-move]');
|
|
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 (moveButton) {
|
|
event.preventDefault();
|
|
var direction = moveButton.getAttribute('data-playlist-move');
|
|
if (direction === 'up' && row.previousElementSibling && row.previousElementSibling.matches('[data-playlist-slide-row]')) {
|
|
tbody.insertBefore(row, row.previousElementSibling);
|
|
} else if (direction === 'down' && row.nextElementSibling && row.nextElementSibling.matches('[data-playlist-slide-row]')) {
|
|
tbody.insertBefore(row.nextElementSibling, row);
|
|
}
|
|
updateRowOrder();
|
|
return;
|
|
}
|
|
|
|
if (removeButton) {
|
|
event.preventDefault();
|
|
row.remove();
|
|
updateRowOrder();
|
|
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 (addSlideButton && addSlideSelect && addDurationInput) {
|
|
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: Math.max(1, Number(addDurationInput.value || 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();
|
|
});
|
|
}
|
|
|
|
window.applyPlaylistScheduleConfig = function (values) {
|
|
var row = findRowByKey(String(values.row_key || ''));
|
|
if (!row) {
|
|
return;
|
|
}
|
|
setRowSchedule(row, values);
|
|
};
|
|
|
|
updateRowOrder();
|
|
}
|
|
|
|
initPlaylistScheduleModal();
|
|
initPlaylistScheduleForm();
|
|
initPlaylistEditStaging();
|
|
}());
|