Bump version to 1.5.6
This commit is contained in:
@@ -6,6 +6,18 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
- No unreleased changes recorded yet.
|
||||
|
||||
## 1.5.6 - 2026-07-25
|
||||
|
||||
### Changed
|
||||
|
||||
- Playlist slide picker now uses a button to reveal already added slides, while still defaulting to showing only available slides.
|
||||
- Already-added slides in the picker are now labeled and visually muted to make them easier to spot.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Thumbnails in slide and playlist tables now follow the slide canvas ratio and cap at 5.5rem tall.
|
||||
- Thumbnail-only table columns are no longer sortable.
|
||||
|
||||
## 1.5.5 - 2026-07-25
|
||||
|
||||
### Fixed
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "1.5.5",
|
||||
"version": "1.5.6",
|
||||
"private": false,
|
||||
"description": "Pulse Signage application with MySQL and media storage",
|
||||
"repository": {
|
||||
|
||||
@@ -1265,6 +1265,19 @@ html[data-bs-theme='dark'] .ck-body-wrapper .ck.ck-color-grid__tile {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.playlist-slide-picker-card.is-assigned {
|
||||
border-color: rgba(var(--bs-primary-rgb), 0.35);
|
||||
background: rgba(var(--bs-primary-rgb), 0.08);
|
||||
}
|
||||
|
||||
.playlist-slide-picker-card.is-assigned .playlist-slide-picker-media {
|
||||
filter: saturate(0.85) grayscale(0.25);
|
||||
}
|
||||
|
||||
.playlist-slide-picker-card.is-assigned .playlist-slide-picker-title {
|
||||
color: var(--bs-body-secondary);
|
||||
}
|
||||
|
||||
.playlist-slide-picker-card.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1322,6 +1335,10 @@ html[data-bs-theme='dark'] .ck-body-wrapper .ck.ck-color-grid__tile {
|
||||
padding: 0 0.9rem 0.6rem;
|
||||
}
|
||||
|
||||
.playlist-slide-picker-filter-button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.playlist-slide-picker-meta {
|
||||
display: none;
|
||||
}
|
||||
@@ -1335,7 +1352,7 @@ html[data-bs-theme='dark'] .ck-body-wrapper .ck.ck-color-grid__tile {
|
||||
opacity: 0;
|
||||
background: rgba(var(--bs-body-bg-rgb), 0.88);
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.3rem;
|
||||
padding: 0.2rem 0.45rem;
|
||||
box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
@@ -1345,6 +1362,17 @@ html[data-bs-theme='dark'] .ck-body-wrapper .ck.ck-color-grid__tile {
|
||||
|
||||
.playlist-slide-picker-badge {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
top: 0.5rem;
|
||||
z-index: 1;
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.playlist-slide-picker-card.is-assigned .playlist-slide-picker-badge {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.playlist-slide-picker-empty.is-hidden {
|
||||
@@ -1438,8 +1466,9 @@ table.table > :not(caption) > * > * {
|
||||
|
||||
.playlist-slide-thumb {
|
||||
flex: 0 0 auto;
|
||||
width: 5.5rem;
|
||||
height: 3.5rem;
|
||||
max-width: 5.5rem;
|
||||
max-height: 5.5rem;
|
||||
aspect-ratio: var(--playlist-slide-thumb-aspect-ratio, 16 / 9);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
background: rgba(15, 23, 42, 0.08);
|
||||
|
||||
@@ -308,6 +308,7 @@
|
||||
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 addSlideShowAssigned = document.getElementById('playlist-slide-picker-show-assigned');
|
||||
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');
|
||||
@@ -408,6 +409,7 @@
|
||||
var media = document.createElement('div');
|
||||
var title = document.createElement('div');
|
||||
var check = document.createElement('span');
|
||||
var badge = document.createElement('span');
|
||||
var searchText = String(slide && slide.title ? slide.title : '').toLowerCase();
|
||||
|
||||
button.type = 'button';
|
||||
@@ -415,6 +417,7 @@
|
||||
button.setAttribute('aria-pressed', 'false');
|
||||
button.setAttribute('data-slide-id', String(slide.id || ''));
|
||||
button.setAttribute('data-search-text', searchText);
|
||||
button.setAttribute('data-is-assigned', slide && slide.isAssigned ? 'true' : 'false');
|
||||
|
||||
if (slide && slide.isAssigned) {
|
||||
button.disabled = true;
|
||||
@@ -427,6 +430,11 @@
|
||||
check.setAttribute('aria-hidden', 'true');
|
||||
media.appendChild(check);
|
||||
|
||||
badge.className = 'playlist-slide-picker-badge badge text-bg-primary';
|
||||
badge.textContent = 'Already in playlist';
|
||||
badge.setAttribute('aria-hidden', 'true');
|
||||
media.appendChild(badge);
|
||||
|
||||
title.className = 'playlist-slide-picker-title';
|
||||
title.textContent = String(slide && slide.title ? slide.title : 'Slide');
|
||||
|
||||
@@ -436,6 +444,16 @@
|
||||
return button;
|
||||
}
|
||||
|
||||
function setShowAssignedState(showAssignedSlides) {
|
||||
if (!addSlideShowAssigned) {
|
||||
return;
|
||||
}
|
||||
|
||||
addSlideShowAssigned.setAttribute('aria-pressed', showAssignedSlides ? 'true' : 'false');
|
||||
addSlideShowAssigned.classList.toggle('btn-secondary', showAssignedSlides);
|
||||
addSlideShowAssigned.classList.toggle('btn-outline-secondary', !showAssignedSlides);
|
||||
}
|
||||
|
||||
function renderSlidePicker() {
|
||||
if (!addSlideGrid) {
|
||||
return;
|
||||
@@ -451,19 +469,31 @@
|
||||
|
||||
function syncSlidePickerState() {
|
||||
var query = String(addSlideSearch && addSlideSearch.value ? addSlideSearch.value : '').trim().toLowerCase();
|
||||
var showAssignedSlides = Boolean(addSlideShowAssigned && addSlideShowAssigned.getAttribute('aria-pressed') === 'true');
|
||||
var visibleCount = 0;
|
||||
var assignedCount = 0;
|
||||
|
||||
slidePickerCards.forEach(function (card) {
|
||||
var searchText = String(card.getAttribute('data-search-text') || '');
|
||||
var matches = !query || searchText.indexOf(query) !== -1;
|
||||
var isAssigned = card.getAttribute('data-is-assigned') === 'true';
|
||||
var matchesSearch = !query || searchText.indexOf(query) !== -1;
|
||||
var matchesAssignment = showAssignedSlides || !isAssigned;
|
||||
var matches = matchesSearch && matchesAssignment;
|
||||
setCardVisible(card, matches);
|
||||
if (isAssigned) {
|
||||
assignedCount += 1;
|
||||
}
|
||||
if (matches) {
|
||||
visibleCount += 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (addSlideEmpty) {
|
||||
addSlideEmpty.textContent = query ? 'No slides match your search.' : 'No slides are currently available for this playlist.';
|
||||
if (!showAssignedSlides && assignedCount && visibleCount === 0) {
|
||||
addSlideEmpty.textContent = query ? 'No available slides match your search.' : 'No available slides are currently left for this playlist.';
|
||||
} else {
|
||||
addSlideEmpty.textContent = query ? 'No slides match your search.' : 'No slides are currently available for this playlist.';
|
||||
}
|
||||
addSlideEmpty.classList.toggle('is-hidden', visibleCount !== 0);
|
||||
}
|
||||
|
||||
@@ -474,6 +504,8 @@
|
||||
if (addSlideConfirm) {
|
||||
addSlideConfirm.disabled = slidePickerSelection.size === 0;
|
||||
}
|
||||
|
||||
setShowAssignedState(showAssignedSlides);
|
||||
}
|
||||
|
||||
function toggleCardSelection(card) {
|
||||
@@ -512,6 +544,8 @@
|
||||
row_key: 'new-' + Date.now() + '-' + slide.id,
|
||||
slide_id: String(slide.id),
|
||||
thumbnail_path: String(slide.thumbnail_path || ''),
|
||||
canvas_width: slide.canvas_width,
|
||||
canvas_height: slide.canvas_height,
|
||||
canvas_signature: String(slide.canvasSignature || ''),
|
||||
title: slide.title || 'Slide',
|
||||
duration_seconds: 10,
|
||||
@@ -626,7 +660,10 @@
|
||||
var isAssigned = Boolean(activeSlideIds[slideId]);
|
||||
var isCanvasMismatch = Boolean(allowedCanvasSignature && canvasSignature && canvasSignature !== allowedCanvasSignature);
|
||||
|
||||
slide.isAssigned = isAssigned;
|
||||
|
||||
if (card) {
|
||||
card.setAttribute('data-is-assigned', isAssigned ? 'true' : 'false');
|
||||
card.disabled = isAssigned || isCanvasMismatch;
|
||||
card.classList.toggle('is-assigned', isAssigned);
|
||||
card.classList.toggle('is-mismatch', isCanvasMismatch);
|
||||
@@ -736,6 +773,12 @@
|
||||
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 canvasWidth = Number(values.canvas_width);
|
||||
var canvasHeight = Number(values.canvas_height);
|
||||
var thumbnailStyle = '';
|
||||
if (Number.isFinite(canvasWidth) && Number.isFinite(canvasHeight) && canvasWidth > 0 && canvasHeight > 0) {
|
||||
thumbnailStyle = ' style="--playlist-slide-thumb-aspect-ratio: ' + canvasWidth + ' / ' + canvasHeight + ';"';
|
||||
}
|
||||
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>';
|
||||
@@ -752,7 +795,7 @@
|
||||
'</td>' +
|
||||
'<td data-label="Slide">' +
|
||||
'<div class="playlist-slide-cell">' +
|
||||
'<div class="playlist-slide-thumb" aria-hidden="true">' + thumbnailMarkup + '</div>' +
|
||||
'<div class="playlist-slide-thumb" aria-hidden="true"' + thumbnailStyle + '>' + 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>' +
|
||||
@@ -839,6 +882,14 @@
|
||||
addSlideSearch.addEventListener('input', syncSlidePickerState);
|
||||
}
|
||||
|
||||
if (addSlideShowAssigned) {
|
||||
addSlideShowAssigned.addEventListener('click', function () {
|
||||
var nextState = addSlideShowAssigned.getAttribute('aria-pressed') !== 'true';
|
||||
setShowAssignedState(nextState);
|
||||
syncSlidePickerState();
|
||||
});
|
||||
}
|
||||
|
||||
if (addSlideConfirm) {
|
||||
addSlideConfirm.addEventListener('click', function () {
|
||||
addSelectedSlides();
|
||||
|
||||
@@ -114,6 +114,10 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
if (headerCell.getAttribute && headerCell.getAttribute('data-sortable') === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/actions?|buttons?/i.test(getTableHeaderText(headerCell))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</td>
|
||||
<td data-label="Slide">
|
||||
<div class="playlist-slide-cell">
|
||||
<div class="playlist-slide-thumb" aria-hidden="true">
|
||||
<div class="playlist-slide-thumb" aria-hidden="true" {{#if canvas_width}}style="--playlist-slide-thumb-aspect-ratio: {{canvas_width}} / {{canvas_height}};"{{/if}}>
|
||||
{{#if thumbnail_path}}
|
||||
<img class="playlist-slide-thumb-image" src="{{thumbnail_path}}" alt="" loading="lazy" />
|
||||
{{else}}
|
||||
@@ -153,11 +153,16 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body playlist-slide-picker-modal-body">
|
||||
<div class="playlist-slide-picker-toolbar">
|
||||
<label class="form-label mb-1" for="playlist-slide-picker-search">Search</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" aria-hidden="true"><i class="bi bi-search"></i></span>
|
||||
<input type="search" class="form-control" id="playlist-slide-picker-search" placeholder="Search slide titles" autocomplete="off" />
|
||||
<div class="playlist-slide-picker-toolbar d-flex flex-column flex-lg-row gap-3 align-items-stretch align-items-lg-end">
|
||||
<div class="flex-grow-1">
|
||||
<label class="form-label mb-1" for="playlist-slide-picker-search">Search</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" aria-hidden="true"><i class="bi bi-search"></i></span>
|
||||
<input type="search" class="form-control" id="playlist-slide-picker-search" placeholder="Search slide titles" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="playlist-slide-picker-filter mb-0">
|
||||
<button type="button" class="btn btn-outline-secondary playlist-slide-picker-filter-button" id="playlist-slide-picker-show-assigned" aria-pressed="false">Show already added slides</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="playlist-slide-picker-empty empty is-hidden" id="playlist-slide-picker-empty">No slides match your search.</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<table class="table table-striped w-100 mb-0" data-table-searchable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Thumbnail</th>
|
||||
<th data-sortable="false">Thumbnail</th>
|
||||
<th>Title</th>
|
||||
<th>Template</th>
|
||||
<th>Actions</th>
|
||||
@@ -33,7 +33,7 @@
|
||||
{{#each slides}}
|
||||
<tr data-table-search-row data-search-text="{{title}} {{template_name}}">
|
||||
<td data-label="Thumbnail">
|
||||
<div class="playlist-slide-thumb" aria-hidden="true">
|
||||
<div class="playlist-slide-thumb" aria-hidden="true" {{#if canvas_width}}style="--playlist-slide-thumb-aspect-ratio: {{canvas_width}} / {{canvas_height}};"{{/if}}>
|
||||
{{#if thumbnail_path}}
|
||||
<img class="playlist-slide-thumb-image" src="{{thumbnail_path}}" alt="" loading="lazy" />
|
||||
{{else}}
|
||||
|
||||
Reference in New Issue
Block a user