From 700278e7fbe56f22d99baa087000297548bd6acd Mon Sep 17 00:00:00 2001 From: Mark Rapson Date: Thu, 22 Oct 2026 15:54:19 +0100 Subject: [PATCH] Fix mobile slide tables and thumbnails --- CHANGELOG.md | 5 ++++ src/web/public/css/theme-custom.css | 32 ++++++++++++++++++++++--- test/table-pagination-height.test.js | 36 ++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c24aa20..75bf0d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. - Updated web and player container images to Node.js 26.12.1 to address the October 2026 security releases. +### Fixed + +- Slide thumbnails and empty placeholders on Slides and playlist pages preserve the canvas aspect ratio and display the full image without cropping on smaller screens; standalone thumbnails are centered within their column. +- Responsive tables keep descriptive text columns wide enough for multi-word lines on mobile, using horizontal scrolling instead of squeezing text into narrow columns. + ## 2.16.3 - 2026-10-21 ### Changed diff --git a/src/web/public/css/theme-custom.css b/src/web/public/css/theme-custom.css index a8bcbe6..4c184f3 100644 --- a/src/web/public/css/theme-custom.css +++ b/src/web/public/css/theme-custom.css @@ -2120,6 +2120,22 @@ td[data-label="Slides"] { overflow-wrap: anywhere; } +@media (max-width: 767.98px) { + .table-responsive > table.table > tbody > tr > td:is( + [data-label="Name"], + [data-label="Title"], + [data-label="Template"], + [data-label="Description"], + [data-label="Client"], + [data-label="Current Screen"], + [data-label="Current Slide"], + [data-label="Slides"] + ) { + min-width: 14rem; + overflow-wrap: break-word; + } +} + .slide-editor-shell { display: grid; grid-template-columns: minmax(0, 1fr) minmax(18rem, 24rem); @@ -4032,6 +4048,8 @@ table.table > :not(caption) > * > * { .playlist-slide-thumb { flex: 0 0 auto; + width: min(5.5rem, calc(5.5rem * (var(--playlist-slide-thumb-aspect-ratio, 16 / 9)))); + min-height: 0; max-width: 5.5rem; max-height: 5.5rem; aspect-ratio: var(--playlist-slide-thumb-aspect-ratio, 16 / 9); @@ -4043,10 +4061,18 @@ table.table > :not(caption) > * > * { justify-content: center; } +.card .table td[data-label="Thumbnail"] { + padding-inline: 1.5rem; +} + +td[data-label="Thumbnail"] > .playlist-slide-thumb { + margin-inline: auto; +} + .playlist-slide-thumb-image { width: 100%; height: 100%; - object-fit: cover; + object-fit: contain; display: block; } @@ -4056,8 +4082,8 @@ table.table > :not(caption) > * > * { justify-content: center; width: 100%; height: 100%; - min-width: 5.5rem; - min-height: 3.5rem; + min-width: 0; + min-height: 0; color: var(--bs-secondary-color, #6c757d); background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.02) 100%), diff --git a/test/table-pagination-height.test.js b/test/table-pagination-height.test.js index 8b89a0c..8fd4085 100644 --- a/test/table-pagination-height.test.js +++ b/test/table-pagination-height.test.js @@ -4,6 +4,42 @@ const vm = require('node:vm'); const test = require('node:test'); const assert = require('node:assert/strict'); +test('slide table thumbnails size to the canvas ratio and show the whole image', () => { + const stylesheet = fs.readFileSync(path.join(__dirname, '..', 'src', 'web', 'public', 'css', 'theme-custom.css'), 'utf8'); + const thumbRule = stylesheet.match(/^\.playlist-slide-thumb\s*\{([^}]+)\}/m)[1]; + const imageRule = stylesheet.match(/^\.playlist-slide-thumb-image\s*\{([^}]+)\}/m)[1]; + const placeholderRule = stylesheet.match(/^\.playlist-slide-thumb-placeholder\s*\{([^}]+)\}/m)[1]; + + assert.match(thumbRule, /width:\s*min\(5\.5rem, calc\(5\.5rem \* \(var\(--playlist-slide-thumb-aspect-ratio, 16 \/ 9\)\)\)\);/); + assert.match(thumbRule, /aspect-ratio:\s*var\(--playlist-slide-thumb-aspect-ratio, 16 \/ 9\);/); + assert.match(thumbRule, /min-height:\s*0;/); + assert.match(thumbRule, /flex:\s*0 0 auto;/); + assert.match(imageRule, /object-fit:\s*contain;/); + assert.match(placeholderRule, /min-width:\s*0;/); + assert.match(placeholderRule, /min-height:\s*0;/); +}); + +test('standalone slide thumbnails are centered within their table column', () => { + const stylesheet = fs.readFileSync(path.join(__dirname, '..', 'src', 'web', 'public', 'css', 'theme-custom.css'), 'utf8'); + const columnRule = stylesheet.match(/^td\[data-label="Thumbnail"\] > \.playlist-slide-thumb\s*\{([^}]+)\}/m)[1]; + const cellRule = stylesheet.match(/^\.card \.table td\[data-label="Thumbnail"\]\s*\{([^}]+)\}/m)[1]; + + assert.match(columnRule, /margin-inline:\s*auto;/); + assert.match(cellRule, /padding-inline:\s*1\.5rem;/); +}); + +test('mobile responsive tables keep descriptive columns wide enough for phrases', () => { + const stylesheet = fs.readFileSync(path.join(__dirname, '..', 'src', 'web', 'public', 'css', 'theme-custom.css'), 'utf8'); + const mobileRule = stylesheet.match(/@media \(max-width: 767\.98px\)\s*\{\s*\.table-responsive > table\.table > tbody > tr > td:is\(([\s\S]*?)\)\s*\{([^}]+)\}/); + + assert.ok(mobileRule); + for (const label of ['Name', 'Title', 'Template', 'Description', 'Client', 'Current Screen', 'Current Slide', 'Slides']) { + assert.ok(mobileRule[1].includes('[data-label="' + label + '"]')); + } + assert.match(mobileRule[2], /min-width:\s*14rem;/); + assert.match(mobileRule[2], /overflow-wrap:\s*break-word;/); +}); + test('table pagination cards skip the minimum height for short content', () => { const cardStyle = { properties: {},