Release 1.5.3

This commit is contained in:
2026-07-25 15:11:41 +01:00
parent 9ee938fd2f
commit 0e89892c94
89 changed files with 7660 additions and 542 deletions
+291 -72
View File
@@ -305,16 +305,239 @@
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');
var addSlideModal = document.getElementById('playlist-add-slide-modal');
var addSlideGrid = document.getElementById('playlist-slide-picker-grid');
var addSlideSearch = document.getElementById('playlist-slide-picker-search');
var addSlideEmpty = document.getElementById('playlist-slide-picker-empty');
var addSlideConfirm = document.getElementById('playlist-confirm-add-slides');
var addSlideCount = document.getElementById('playlist-slide-picker-selected-count');
var addSlideData = document.getElementById('playlist-add-slide-data');
var addSlideOpenButton = document.getElementById('playlist-open-slide-modal');
var slidePickerCards = [];
var slidePickerSelection = new Set();
var slidePickerSlides = [];
if (!tbody || !form) {
return;
}
if (addSlideData) {
try {
var parsedSlides = JSON.parse(addSlideData.textContent || '[]');
slidePickerSlides = Array.isArray(parsedSlides) ? parsedSlides : [];
} catch (_error) {
slidePickerSlides = [];
}
}
function getModalInstance() {
if (!window.pulseModal) {
return null;
}
return window.pulseModal.getOrCreate(addSlideModal);
}
function setCardSelected(card, selected) {
if (!card) {
return;
}
card.classList.toggle('is-selected', Boolean(selected));
card.setAttribute('aria-pressed', Boolean(selected) ? 'true' : 'false');
}
function setCardVisible(card, visible) {
if (!card) {
return;
}
card.classList.toggle('is-hidden', !visible);
}
function clearModalSelection() {
slidePickerSelection.clear();
slidePickerCards.forEach(function (card) {
setCardSelected(card, false);
});
if (addSlideSearch) {
addSlideSearch.value = '';
}
syncSlidePickerState();
}
function attachSlideThumbFallbacks(root) {
if (typeof window.attachSlideThumbFallbacks === 'function') {
window.attachSlideThumbFallbacks(root || tbody);
}
}
function createPickerThumb(slide) {
function createPlaceholder() {
var placeholder = document.createElement('div');
var icon = document.createElement('i');
var label = document.createElement('span');
placeholder.className = 'playlist-slide-picker-thumb-placeholder';
icon.className = 'bi bi-image playlist-slide-picker-thumb-placeholder-icon';
icon.setAttribute('aria-hidden', 'true');
label.className = 'playlist-slide-picker-thumb-placeholder-label';
label.textContent = 'No thumbnail';
placeholder.appendChild(icon);
placeholder.appendChild(label);
return placeholder;
}
if (slide && slide.thumbnail_path) {
var image = document.createElement('img');
image.className = 'playlist-slide-picker-thumb-image';
image.loading = 'lazy';
image.alt = String(slide.title || 'Slide');
image.src = String(slide.thumbnail_path || '');
image.addEventListener('error', function () {
if (image.parentNode) {
image.parentNode.replaceChild(createPlaceholder(), image);
}
});
return image;
}
return createPlaceholder();
}
function createPickerCard(slide) {
var button = document.createElement('button');
var media = document.createElement('div');
var title = document.createElement('div');
var check = document.createElement('span');
var searchText = String(slide && slide.title ? slide.title : '').toLowerCase();
button.type = 'button';
button.className = 'playlist-slide-picker-card';
button.setAttribute('aria-pressed', 'false');
button.setAttribute('data-slide-id', String(slide.id || ''));
button.setAttribute('data-search-text', searchText);
if (slide && slide.isAssigned) {
button.disabled = true;
button.classList.add('is-assigned');
}
media.className = 'playlist-slide-picker-media';
media.appendChild(createPickerThumb(slide));
check.className = 'playlist-slide-picker-check bi bi-check2-circle';
check.setAttribute('aria-hidden', 'true');
media.appendChild(check);
title.className = 'playlist-slide-picker-title';
title.textContent = String(slide && slide.title ? slide.title : 'Slide');
button.appendChild(media);
button.appendChild(title);
return button;
}
function renderSlidePicker() {
if (!addSlideGrid) {
return;
}
addSlideGrid.innerHTML = '';
slidePickerCards = slidePickerSlides.map(function (slide) {
var card = createPickerCard(slide);
addSlideGrid.appendChild(card);
return card;
});
syncSlidePickerState();
}
function syncSlidePickerState() {
var query = String(addSlideSearch && addSlideSearch.value ? addSlideSearch.value : '').trim().toLowerCase();
var visibleCount = 0;
slidePickerCards.forEach(function (card) {
var searchText = String(card.getAttribute('data-search-text') || '');
var matches = !query || searchText.indexOf(query) !== -1;
setCardVisible(card, matches);
if (matches) {
visibleCount += 1;
}
});
if (addSlideEmpty) {
addSlideEmpty.textContent = query ? 'No slides match your search.' : 'No slides are currently available for this playlist.';
addSlideEmpty.classList.toggle('is-hidden', visibleCount !== 0);
}
if (addSlideCount) {
addSlideCount.textContent = String(slidePickerSelection.size);
}
if (addSlideConfirm) {
addSlideConfirm.disabled = slidePickerSelection.size === 0;
}
}
function toggleCardSelection(card) {
var slideId = String(card && card.getAttribute('data-slide-id') ? card.getAttribute('data-slide-id') : '');
var selected;
if (!slideId || card.disabled) {
return;
}
selected = !slidePickerSelection.has(slideId);
if (selected) {
slidePickerSelection.add(slideId);
} else {
slidePickerSelection.delete(slideId);
}
setCardSelected(card, selected);
syncSlidePickerState();
}
function addSelectedSlides() {
var selectedSlides = slidePickerSlides.filter(function (slide) {
return slidePickerSelection.has(String(slide.id || '')) && !slide.isAssigned;
});
if (!selectedSlides.length) {
return;
}
if (tbody.querySelector('.playlist-empty-row')) {
tbody.querySelector('.playlist-empty-row').remove();
}
selectedSlides.forEach(function (slide) {
var row = createRow({
row_key: 'new-' + Date.now() + '-' + slide.id,
slide_id: String(slide.id),
thumbnail_path: String(slide.thumbnail_path || ''),
canvas_signature: String(slide.canvasSignature || ''),
title: slide.title || '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);
});
attachSlideThumbFallbacks(tbody);
updateRowOrder(true);
if (!window.pulseModal || !window.pulseModal.hide(addSlideModal)) {
addSlideModal.classList.remove('show');
addSlideModal.setAttribute('aria-hidden', 'true');
}
}
attachSlideThumbFallbacks(tbody);
function getRows() {
return Array.prototype.slice.call(tbody.querySelectorAll('tr[data-playlist-slide-row]'));
}
@@ -375,9 +598,6 @@
}
function syncAddSlideOptions() {
if (!addSlideSelect) {
return;
}
var activeSlideIds = {};
var activeCanvasSignatures = {};
getRows().forEach(function (row) {
@@ -397,51 +617,37 @@
allowedCanvasSignature = canvasKeys[0];
}
Array.prototype.forEach.call(addSlideSelect.options, function (option) {
if (!option.value) {
return;
slidePickerSlides.forEach(function (slide) {
var slideId = String(slide.id || '');
var card = slidePickerCards.find(function (item) {
return String(item.getAttribute('data-slide-id') || '') === slideId;
});
var canvasSignature = String(slide.canvasSignature || '');
var isAssigned = Boolean(activeSlideIds[slideId]);
var isCanvasMismatch = Boolean(allowedCanvasSignature && canvasSignature && canvasSignature !== allowedCanvasSignature);
if (card) {
card.disabled = isAssigned || isCanvasMismatch;
card.classList.toggle('is-assigned', isAssigned);
card.classList.toggle('is-mismatch', isCanvasMismatch);
card.classList.toggle('is-hidden', false);
card.setAttribute('aria-disabled', card.disabled ? 'true' : 'false');
}
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;
if (isAssigned) {
slidePickerSelection.delete(slideId);
if (card) {
setCardSelected(card, false);
}
}
}
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);
if (addSlideOpenButton) {
addSlideOpenButton.disabled = slidePickerCards.filter(function (card) {
return !card.disabled;
}).length === 0;
}
syncSlidePickerState();
}
function updateRowOrder(markDirty) {
@@ -530,6 +736,9 @@
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') || '';
var thumbnailMarkup = values.thumbnail_path
? '<img class="playlist-slide-thumb-image" src="' + values.thumbnail_path + '" alt="" loading="lazy" />'
: '<span class="playlist-slide-thumb-placeholder"><i class="bi bi-image" aria-hidden="true"></i></span>';
row.setAttribute('data-playlist-slide-row', '');
row.setAttribute('data-row-key', rowKey);
row.setAttribute('data-slide-id', String(values.slide_id));
@@ -541,7 +750,12 @@
'<span class="playlist-order-number"></span>' +
'</div>' +
'</td>' +
'<td>' + values.title + '<input type="hidden" name="slide_id[]" value="' + values.slide_id + '" form="playlist-edit-form" /></td>' +
'<td data-label="Slide">' +
'<div class="playlist-slide-cell">' +
'<div class="playlist-slide-thumb" aria-hidden="true">' + thumbnailMarkup + '</div>' +
'<div class="playlist-slide-cell-content"><span class="playlist-slide-title">' + values.title + '</span></div>' +
'</div>' +
'<input type="hidden" name="slide_id[]" value="' + values.slide_id + '" form="playlist-edit-form" /></td>' +
'<td>' +
'<div class="playlist-schedule-summary">' + values.summary + '</div>' +
'<input type="hidden" name="schedule_mode[]" value="' + values.schedule_mode + '" form="playlist-edit-form" />' +
@@ -551,7 +765,7 @@
'<input type="hidden" name="schedule_end_time[]" value="' + values.schedule_end_time + '" form="playlist-edit-form" />' +
'<input type="hidden" name="schedule_days_json[]" value="' + values.schedule_days_json + '" form="playlist-edit-form" />' +
'</td>' +
'<td><input name="duration_seconds[]" type="number" min="1" value="' + values.duration_seconds + '" required form="playlist-edit-form" /></td>' +
'<td><input name="duration_seconds[]" type="number" min="1" value="' + values.duration_seconds + '" required form="playlist-edit-form" class="form-control form-control-sm playlist-duration-input text-end" /></td>' +
'<td><div class="actions playlist-item-actions">' +
'<button type="button" class="btn btn-sm btn-primary" data-schedule-config="/playlists/' + encodeURIComponent(playlistId) + '/slides/0/config" data-schedule-config-row="' + rowKey + '">Schedule</button>' +
'<button type="button" class="btn btn-sm btn-danger" data-playlist-remove-row>Remove</button>' +
@@ -609,32 +823,37 @@
});
}
if (addSlideButton && addSlideSelect) {
addSlideButton.addEventListener('click', function () {
var selectedOption = addSlideSelect.options[addSlideSelect.selectedIndex];
if (!selectedOption || selectedOption.disabled) {
if (addSlideModal && addSlideGrid) {
renderSlidePicker();
addSlideGrid.addEventListener('click', function (event) {
var card = event.target.closest('.playlist-slide-picker-card');
if (!card) {
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'
event.preventDefault();
toggleCardSelection(card);
});
if (addSlideSearch) {
addSlideSearch.addEventListener('input', syncSlidePickerState);
}
if (addSlideConfirm) {
addSlideConfirm.addEventListener('click', function () {
addSelectedSlides();
});
tbody.appendChild(row);
updateRowOrder(true);
}
addSlideModal.addEventListener('shown.bs.modal', function () {
syncSlidePickerState();
if (addSlideSearch) {
addSlideSearch.focus();
}
});
addSlideModal.addEventListener('hidden.bs.modal', function () {
clearModalSelection();
});
}