Release 1.5.3
This commit is contained in:
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user