Release v2.20.3
This commit is contained in:
1 parent
9dec1818a5
commit
7228406929
9 files changed
+99
-5
No files matched your search
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## 2.20.3 - 2026-10-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Playlist schedule summaries show up to two numbered rules on separate lines, with a 60-character description limit per rule. Longer summaries and schedules with three or more rules show the rule count instead. Time windows without selected weekdays explicitly show "Any day".
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Standardized the Screen groups navigation label.
|
||||||
|
|
||||||
## 2.20.2 - 2026-10-25
|
## 2.20.2 - 2026-10-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage-player",
|
"name": "pulse-signage-player",
|
||||||
"version": "2.20.2",
|
"version": "2.20.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage player application bundle",
|
"description": "Pulse Signage player application bundle",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage-web",
|
"name": "pulse-signage-web",
|
||||||
"version": "2.20.2",
|
"version": "2.20.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage web and bridge application bundle",
|
"description": "Pulse Signage web and bridge application bundle",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.20.2",
|
"version": "2.20.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.20.2",
|
"version": "2.20.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-auth/passkey": "^1.7.13",
|
"@better-auth/passkey": "^1.7.13",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.20.2",
|
"version": "2.20.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage application with MySQL and media storage",
|
"description": "Pulse Signage application with MySQL and media storage",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ function formatScheduleRuleSummary(rule) {
|
|||||||
const days = formatDays(normalized.days || []);
|
const days = formatDays(normalized.days || []);
|
||||||
if (days) {
|
if (days) {
|
||||||
rightParts.push(days);
|
rightParts.push(days);
|
||||||
|
} else if (normalized.start_time && normalized.end_time) {
|
||||||
|
rightParts.push('Any day');
|
||||||
}
|
}
|
||||||
if (normalized.start_time && normalized.end_time) {
|
if (normalized.start_time && normalized.end_time) {
|
||||||
rightParts.push(formatTime(normalized.start_time) + '-' + formatTime(normalized.end_time));
|
rightParts.push(formatTime(normalized.start_time) + '-' + formatTime(normalized.end_time));
|
||||||
@@ -200,8 +202,20 @@ function formatScheduleRulesSummary(value) {
|
|||||||
return 'Always visible';
|
return 'Always visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rules.length >= 3) {
|
||||||
|
return String(rules.length) + ' rules';
|
||||||
|
}
|
||||||
|
|
||||||
|
const summaries = rules.map(function (rule) {
|
||||||
|
return formatScheduleRuleSummary(rule).replace(/^Any date: /, '');
|
||||||
|
});
|
||||||
|
if (summaries.some(function (summary) { return summary.length > 60; })) {
|
||||||
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
|
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
|
||||||
}
|
}
|
||||||
|
return summaries.map(function (summary, index) {
|
||||||
|
return 'Rule ' + String(index + 1) + ': ' + summary;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
normalizeDateTimeValue: normalizeDateTimeValue,
|
normalizeDateTimeValue: normalizeDateTimeValue,
|
||||||
|
|||||||
@@ -2396,6 +2396,14 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlist-schedule-summary .chip {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: min(30rem, 100%);
|
||||||
|
white-space: pre-line;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
color: var(--bs-secondary-color);
|
color: var(--bs-secondary-color);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|||||||
@@ -146,6 +146,8 @@
|
|||||||
var days = formatDays(normalized.days || []);
|
var days = formatDays(normalized.days || []);
|
||||||
if (days) {
|
if (days) {
|
||||||
right.push(days);
|
right.push(days);
|
||||||
|
} else if (normalized.start_time && normalized.end_time) {
|
||||||
|
right.push('Any day');
|
||||||
}
|
}
|
||||||
if (normalized.start_time && normalized.end_time) {
|
if (normalized.start_time && normalized.end_time) {
|
||||||
right.push(normalized.start_time.slice(0, 5) + '-' + normalized.end_time.slice(0, 5));
|
right.push(normalized.start_time.slice(0, 5) + '-' + normalized.end_time.slice(0, 5));
|
||||||
@@ -165,8 +167,20 @@
|
|||||||
return 'Always visible';
|
return 'Always visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rules.length >= 3) {
|
||||||
|
return String(rules.length) + ' rules';
|
||||||
|
}
|
||||||
|
|
||||||
|
var summaries = rules.map(function (rule) {
|
||||||
|
return formatScheduleRuleSummary(rule).replace(/^Any date: /, '');
|
||||||
|
});
|
||||||
|
if (summaries.some(function (summary) { return summary.length > 60; })) {
|
||||||
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
|
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
|
||||||
}
|
}
|
||||||
|
return summaries.map(function (summary, index) {
|
||||||
|
return 'Rule ' + String(index + 1) + ': ' + summary;
|
||||||
|
}).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
function parseScheduleBoundary(value) {
|
function parseScheduleBoundary(value) {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
|||||||
@@ -5,6 +5,54 @@ require('../src/common');
|
|||||||
|
|
||||||
const { buildPlaylistFormViewModel } = require('../src/web/routes/signage/playlists/form-view-model');
|
const { buildPlaylistFormViewModel } = require('../src/web/routes/signage/playlists/form-view-model');
|
||||||
|
|
||||||
|
test('playlist schedule summaries show up to two short rules on separate lines and count longer summaries', () => {
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
const { formatScheduleRulesSummary } = require('../src/web/lib/playlist-schedule');
|
||||||
|
const source = fs.readFileSync(path.join(__dirname, '../src/web/public/js/playlists/playlist-schedule.js'), 'utf8');
|
||||||
|
const context = {};
|
||||||
|
vm.runInNewContext(source.slice(0, source.indexOf(' function parseScheduleBoundary('))
|
||||||
|
+ 'globalThis.formatSummary = formatScheduleRulesSummary; })();', context);
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
[[], 'Always visible'],
|
||||||
|
[[{ days: [1, 3], start_time: '09:00', end_time: '17:00' }], 'Rule 1: Mon, Wed 09:00-17:00'],
|
||||||
|
[[{ start_time: '09:00', end_time: '17:00' }], 'Rule 1: Any day 09:00-17:00'],
|
||||||
|
[[{ start_time: '09:00', end_time: '17:00' }, { days: [5] }],
|
||||||
|
'Rule 1: Any day 09:00-17:00\nRule 2: Fri'],
|
||||||
|
[[{ days: [1] }, { days: [5] }], 'Rule 1: Mon\nRule 2: Fri'],
|
||||||
|
[[{ days: [1] }, { days: [3] }, { days: [5] }], '3 rules'],
|
||||||
|
[[{ days: [1] }, { days: [2] }, { days: [3] }, { days: [4] }], '4 rules'],
|
||||||
|
[[{ start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00', days: [1] }],
|
||||||
|
'Rule 1: Dates 2026-10-25 09:00 to 2026-10-26 17:00: Mon'],
|
||||||
|
[[{ start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00',
|
||||||
|
days: [1, 2], start_time: '09:00', end_time: '17:00' }],
|
||||||
|
'1 rule'],
|
||||||
|
[[{ days: [0, 1, 2, 3, 4, 5, 6], start_time: '09:00', end_time: '17:00' },
|
||||||
|
{ days: [1, 2, 3, 4, 5], start_time: '18:00', end_time: '19:00' }],
|
||||||
|
'Rule 1: Sun, Mon, Tue, Wed, Thu, Fri, Sat 09:00-17:00\nRule 2: Mon, Tue, Wed, Thu, Fri 18:00-19:00'],
|
||||||
|
[[{ start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00',
|
||||||
|
days: [1, 2, 3] }], 'Rule 1: Dates 2026-10-25 09:00 to 2026-10-26 17:00: Mon, Tue, Wed'],
|
||||||
|
[[{ start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00',
|
||||||
|
days: [1, 2, 3, 4, 5] }], '1 rule'],
|
||||||
|
[[{ days: [1] }, { start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00',
|
||||||
|
days: [1, 2, 3, 4, 5] }], '2 rules'],
|
||||||
|
[[{ start_datetime: '2026-10-25T09:00', end_datetime: '2026-10-26T17:00',
|
||||||
|
days: [1, 2], start_time: '09:00', end_time: '17:00' }, { days: [5] }], '2 rules']
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [rules, expected] of cases) {
|
||||||
|
assert.equal(formatScheduleRulesSummary(rules), expected);
|
||||||
|
assert.equal(context.formatSummary(rules), expected);
|
||||||
|
assert.equal(formatScheduleRulesSummary(JSON.stringify(rules)), expected);
|
||||||
|
assert.equal(context.formatSummary(JSON.stringify(rules)), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
const css = fs.readFileSync(path.join(__dirname, '../src/web/public/css/theme-custom.css'), 'utf8');
|
||||||
|
assert.match(css, /\.playlist-schedule-summary \.chip\s*\{[^}]*display:\s*inline-block;[^}]*white-space:\s*pre-line;[^}]*overflow-wrap:\s*anywhere;/);
|
||||||
|
});
|
||||||
|
|
||||||
test('playlist rows show mute controls for RTMP slides', () => {
|
test('playlist rows show mute controls for RTMP slides', () => {
|
||||||
const model = buildPlaylistFormViewModel(
|
const model = buildPlaylistFormViewModel(
|
||||||
{ id: 22, name: 'Playlist 22' },
|
{ id: 22, name: 'Playlist 22' },
|
||||||
|
|||||||
Reference in new issue
Block a user