Release 2.8.0
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m49s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
2026-08-16 17:15:31 +01:00
parent 1bdc122995
commit 203d0bfc01
105 changed files with 4185 additions and 677 deletions
@@ -23,6 +23,7 @@ function buildAnnouncementFormViewModel(announcement, message, currentUser, opti
const announcementTypes = Array.isArray(options && options.announcementTypes) ? options.announcementTypes : [];
const announcementColors = Array.isArray(options && options.announcementColors) ? options.announcementColors : [];
const announcementIcons = Array.isArray(options && options.announcementIcons) ? options.announcementIcons : [];
const announcementIconCatalog = Array.isArray(options && options.announcementIconCatalog) ? options.announcementIconCatalog : announcementIcons;
const announcementScreens = Array.isArray(options && options.announcementScreens) ? options.announcementScreens : [];
const viewOptions = options || {};
const viewAnnouncement = Object.assign({}, buildDefaultAnnouncement(), announcement || {});
@@ -68,6 +69,7 @@ function buildAnnouncementFormViewModel(announcement, message, currentUser, opti
announcementTypes: announcementTypes,
announcementColors: announcementColors,
announcementIcons: announcementIcons,
announcementIconCatalog: announcementIconCatalog,
announcementScreens: announcementScreens
};
}
+31 -6
View File
@@ -11,6 +11,7 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
const { buildPagination } = require('../../../lib/pagination');
const announcementData = require('#src/data/announcements');
const announcementIcons = require('#src/data/announcement-icons');
const { fetchAppSettings } = require('#src/data/app-settings');
const { buildDuplicateAnnouncementName, buildDuplicateAnnouncement } = require('./duplicate');
const LIST_PAGE_SIZE = 25;
@@ -30,6 +31,21 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
{ value: 'light', label: 'Light' }
];
const ANNOUNCEMENT_ICONS = announcementIcons.ANNOUNCEMENT_ICON_OPTIONS || [];
const ANNOUNCEMENT_ICON_CATALOG = announcementIcons.ANNOUNCEMENT_ICON_CATALOG || ANNOUNCEMENT_ICONS;
async function fetchSuggestedAnnouncementIcons() {
const settings = await fetchAppSettings(pool);
const catalogByKey = new Map(ANNOUNCEMENT_ICON_CATALOG.map(function (option) {
return [option.value, option];
}));
const configuredKeys = Array.isArray(settings['announcements.suggested_icons'])
? settings['announcements.suggested_icons']
: [];
const configuredOptions = configuredKeys.map(function (key) {
return catalogByKey.get(String(key || '').trim().toLowerCase()) || null;
}).filter(Boolean);
return configuredOptions.length ? configuredOptions : ANNOUNCEMENT_ICONS;
}
function normalizeDurationUnit(value) {
return String(value || 'minutes').trim().toLowerCase() === 'seconds' ? 'seconds' : 'minutes';
@@ -269,21 +285,28 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
app.get('/announcements/new', requirePermission('announcements.create'), async function (req, res, next) {
try {
const screens = await fetchAnnouncementScreens();
const suggestedAnnouncementIcons = await fetchSuggestedAnnouncementIcons();
const settings = await fetchAppSettings(pool);
const configuredIcon = String(settings['announcements.default_icon'] || '').trim().toLowerCase();
const defaultIcon = ANNOUNCEMENT_ICON_CATALOG.some(function (option) { return option.value === configuredIcon; })
? configuredIcon
: (suggestedAnnouncementIcons[0] && suggestedAnnouncementIcons[0].value ? suggestedAnnouncementIcons[0].value : announcementIcons.DEFAULT_ANNOUNCEMENT_ICON);
res.send(pages.renderAnnouncementAddPage(buildAnnouncementFormData({
message: '',
short_label: '',
announcement_type: 'lower-third',
color_key: 'primary',
icon_key: announcementIcons.DEFAULT_ANNOUNCEMENT_ICON,
icon_key: defaultIcon,
durationMode: 'duration',
duration_seconds: null,
durationValue: 5,
durationUnit: 'minutes',
durationValue: Number(settings['announcements.default_duration_value']) || 10,
durationUnit: String(settings['announcements.default_duration_unit'] || 'seconds'),
screen_ids: []
}), req.query.message ? String(req.query.message) : '', req.currentUser, {
announcementTypes: ANNOUNCEMENT_TYPES,
announcementColors: ANNOUNCEMENT_COLORS,
announcementIcons: ANNOUNCEMENT_ICONS,
announcementIcons: suggestedAnnouncementIcons,
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
announcementScreens: buildAnnouncementScreenOptions(screens, [])
}));
} catch (error) {
@@ -301,7 +324,8 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
res.send(pages.renderAnnouncementEditPage(buildAnnouncementFormData(announcement), null, req.query.message ? String(req.query.message) : '', req.currentUser, {
announcementTypes: ANNOUNCEMENT_TYPES,
announcementColors: ANNOUNCEMENT_COLORS,
announcementIcons: ANNOUNCEMENT_ICONS,
announcementIcons: await fetchSuggestedAnnouncementIcons(),
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
announcementScreens: buildAnnouncementScreenOptions(screens, announcement.screen_ids || [])
}));
} catch (error) {
@@ -327,7 +351,8 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
res.send(pages.renderAnnouncementAddPage(buildAnnouncementFormData(buildDuplicateAnnouncement(announcement, duplicateName)), req.query.message ? String(req.query.message) : 'Review the copied values and save when ready.', req.currentUser, {
announcementTypes: ANNOUNCEMENT_TYPES,
announcementColors: ANNOUNCEMENT_COLORS,
announcementIcons: ANNOUNCEMENT_ICONS,
announcementIcons: await fetchSuggestedAnnouncementIcons(),
announcementIconCatalog: ANNOUNCEMENT_ICON_CATALOG,
announcementScreens: buildAnnouncementScreenOptions(screens, announcement.screen_ids || []),
messageVariant: 'info'
}));
@@ -175,6 +175,7 @@ function formatCanvasSizeLabel(size) {
function buildPlaylistFormViewModel(playlist, data, message, currentUser, options) {
const isEdit = Boolean(options && options.isEdit);
const defaultSlideDurationSeconds = Number(data && data.defaultSlideDurationSeconds) || 10;
const viewPlaylist = Object.assign({
id: null,
name: '',
@@ -245,7 +246,7 @@ function buildPlaylistFormViewModel(playlist, data, message, currentUser, option
videoDurationSeconds: '',
useVideoDuration: false,
disableAudio: true,
durationSeconds: 10,
durationSeconds: defaultSlideDurationSeconds,
scheduleRules: [],
scheduleSummary: 'Always visible',
displayOrder: ''
@@ -280,7 +281,8 @@ function buildPlaylistFormViewModel(playlist, data, message, currentUser, option
selectableSlides: selectableSlides,
playlistCanvasId: playlistCanvasId,
playlistCanvasMismatch: playlistCanvasMismatch,
scripts: ['js/lib/modal.js?v=20260726.7', 'js/playlists/playlist-table-drag.js?v=20260802.5', 'js/playlists/playlist-schedule.js?v=20260803.1'],
defaultSlideDurationSeconds: defaultSlideDurationSeconds,
scripts: ['js/lib/modal.js?v=20260726.7', 'js/playlists/playlist-table-drag.js?v=20260802.5', 'js/playlists/playlist-schedule.js?v=20260816.1'],
message: message,
currentUser: currentUser || null,
inUse: Boolean(viewPlaylist.inUse)
+11 -1
View File
@@ -8,6 +8,7 @@ module.exports = function registerPlaylistsRoutes(app, deps) {
const { buildPagination } = require('../../../lib/pagination');
const requirePermission = deps.requirePermission;
const { buildDuplicatePlaylistName, buildDuplicatePlaylist } = require('./duplicate');
const { fetchAppSettings } = require('../../../../data/app-settings');
const LIST_PAGE_SIZE = 25;
@@ -31,6 +32,8 @@ module.exports = function registerPlaylistsRoutes(app, deps) {
return res.status(404).send('Playlist not found');
}
playlist.inUse = (await fetchScreensByPlaylistId(pool, playlist.id)).length > 0;
const settings = await fetchAppSettings(pool);
data.defaultSlideDurationSeconds = Number(settings['player.default_slide_duration_seconds']) || 10;
return res.send(pages.renderPlaylistEditPage(playlist, data, req.query.message ? String(req.query.message) : '', req.currentUser));
}
const data = await common.fetchPlaylistsPage(pool, page, LIST_PAGE_SIZE, search, sort, direction);
@@ -46,7 +49,12 @@ module.exports = function registerPlaylistsRoutes(app, deps) {
app.get('/playlists/new', requirePermission('playlists.create'), function (req, res) {
(async function () {
const data = await common.fetchAdminData(pool);
res.send(await pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser, data, pool));
const settings = await fetchAppSettings(pool);
data.defaultSlideDurationSeconds = Number(settings['player.default_slide_duration_seconds']) || 10;
res.send(await pages.renderPlaylistFormPage({
fade_between_slides: Boolean(settings['player.default_fade_between_slides']),
skip_unavailable_rtmp: Boolean(settings['player.skip_unavailable_rtmp'])
}, req.query.message ? String(req.query.message) : '', req.currentUser, data, pool));
})().catch(function (error) {
res.status(500).send(error.message || 'Unable to load page.');
});
@@ -81,6 +89,8 @@ module.exports = function registerPlaylistsRoutes(app, deps) {
return res.status(404).send('Playlist not found');
}
const data = await common.fetchAdminData(pool);
const settings = await fetchAppSettings(pool);
data.defaultSlideDurationSeconds = Number(settings['player.default_slide_duration_seconds']) || 10;
playlist.inUse = (await fetchScreensByPlaylistId(pool, playlist.id)).length > 0;
res.send(pages.renderPlaylistEditPage(playlist, data, req.query.message ? String(req.query.message) : '', req.currentUser));
} catch (error) {
@@ -26,6 +26,7 @@ function buildSlideFormViewModel(data, slide, message, currentUser, isEdit) {
const apiSources = data && data.apiSources ? data.apiSources : [];
const timetableGroups = data && data.timetableGroups ? data.timetableGroups : [];
const fontLibrary = data && data.fontLibrary ? data.fontLibrary : null;
const uploadLimits = data && data.uploadLimits ? data.uploadLimits : {};
const templates = buildTemplates(data && data.templates ? data.templates : [], templateRegions);
const viewSlide = slide || buildDefaultSlide();
@@ -50,7 +51,8 @@ function buildSlideFormViewModel(data, slide, message, currentUser, isEdit) {
fontStylesheetHref: fontLibrary && fontLibrary.stylesheetHref ? fontLibrary.stylesheetHref : '',
fontFamilyFormats: fontLibrary && fontLibrary.fontFamilyFormats ? fontLibrary.fontFamilyFormats : '',
existingTemplateId: viewSlide && viewSlide.template_id ? viewSlide.template_id : null,
existingContent: viewSlide && viewSlide.content ? viewSlide.content : {}
existingContent: viewSlide && viewSlide.content ? viewSlide.content : {},
uploadLimits: uploadLimits
},
assetVersion: assetVersion,
slideEditorScripts: ['vendor/qr-code-styling/qr-code-styling-loader.js?v=' + assetVersion].concat(getRegionEditorScripts(assetVersion)),