Add onboarding weather and template gradients
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 34s

This commit is contained in:
lzstealth committed 2026-08-28 20:05:23 +01:00
1 parent aa07a78912
commit 3960931ebe
80 files changed
+12750 -519

No files matched your search

+71 -2
View File
@@ -440,6 +440,34 @@ function isEditableTarget(target) {
return ['INPUT', 'TEXTAREA', 'SELECT', 'OPTION'].indexOf(tagName) !== -1;
}
var keyboardFeedbackTimer = null;
function showKeyboardFeedback(message) {
if (typeof document === 'undefined' || !document.body) {
return;
}
var feedback = document.querySelector('.player-keyboard-feedback');
if (!feedback) {
feedback = document.createElement('div');
feedback.className = 'player-keyboard-feedback';
feedback.setAttribute('aria-live', 'polite');
document.body.appendChild(feedback);
}
feedback.textContent = String(message || '');
feedback.classList.remove('is-visible');
void feedback.offsetWidth;
feedback.classList.add('is-visible');
if (keyboardFeedbackTimer) {
window.clearTimeout(keyboardFeedbackTimer);
}
keyboardFeedbackTimer = window.setTimeout(function () {
feedback.classList.remove('is-visible');
keyboardFeedbackTimer = null;
}, 900);
}
function handlePlayerKeydown(event) {
if (!event || event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) {
return;
@@ -452,12 +480,28 @@ function handlePlayerKeydown(event) {
if (event.key === 'ArrowLeft') {
event.preventDefault();
navigateSlides(-1);
showKeyboardFeedback('Previous slide');
return;
}
if (event.key === 'ArrowRight') {
event.preventDefault();
navigateSlides(1);
showKeyboardFeedback('Next slide');
return;
}
if (String(event.key || '').toLowerCase() === 'p') {
event.preventDefault();
setPaused(!isPaused);
showKeyboardFeedback(isPaused ? 'Paused' : 'Playing');
return;
}
if (String(event.key || '').toLowerCase() === 'b') {
event.preventDefault();
setBlackout(!isBlackout);
showKeyboardFeedback(isBlackout ? 'Blackout on' : 'Blackout off');
}
}
@@ -489,6 +533,10 @@ function handleCommandMessage(rawMessage) {
} catch (_error) {
return;
}
if (payload && payload.type === 'client-id-conflict') {
handleClientIdConflict();
return;
}
if (!payload || payload.type !== 'command') {
return;
@@ -505,7 +553,17 @@ function handleCommandMessage(rawMessage) {
return;
case 'redirect':
if (payload.url) {
window.location.replace(String(payload.url));
var redirectUrl = String(payload.url);
var authorizeMove = payload.moveToken
? fetch('/api/screen-move-authorize', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ moveToken: String(payload.moveToken) })
})
: Promise.resolve();
authorizeMove.finally(function () {
window.location.replace(redirectUrl);
});
}
return;
case 'pause':
@@ -541,6 +599,13 @@ function handleCommandMessage(rawMessage) {
}
}
function handleClientIdConflict() {
var replacementClientId = regenerateCommandClientId();
var onboardingUrl = new URL('/', window.location.origin);
onboardingUrl.searchParams.set('clientId', replacementClientId);
window.location.replace(onboardingUrl.toString());
}
// Retry the command websocket after a disconnect.
function scheduleCommandReconnect() {
if (commandReconnectTimer) {
@@ -577,8 +642,12 @@ function connectCommandSocket() {
handleCommandMessage(event.data);
};
socket.onclose = function () {
socket.onclose = function (event) {
commandSocket = null;
if (event && event.code === 4009) {
handleClientIdConflict();
return;
}
scheduleCommandReconnect();
};
+21 -9
View File
@@ -156,6 +156,9 @@ function refresh() {
if (window.__pulsePageAuthToken) {
request.setRequestHeader('x-pulse-page-auth', window.__pulsePageAuthToken);
}
if (typeof getCommandClientId === 'function') {
request.setRequestHeader('x-pulse-client-id', getCommandClientId());
}
if (currentPlaylistEtag) {
request.setRequestHeader('If-None-Match', currentPlaylistEtag);
}
@@ -172,6 +175,10 @@ function refresh() {
return;
}
if (request.status < 200 || request.status >= 300) {
if (request.status === 401 || request.status === 403) {
window.location.replace('/');
return;
}
setOfflineBannerVisible(true);
scheduleRefreshRetry();
logDebug(
@@ -189,16 +196,21 @@ function refresh() {
const nextActiveSlides = getActiveSlidesFrom(nextSlides);
const nextFadeBetweenSlides = Boolean(data && data.playlist && data.playlist.fade_between_slides);
const nextSkipUnavailableRtmp = Boolean(data && data.playlist && data.playlist.skip_unavailable_rtmp);
if (window.initialData && typeof window.initialData === 'object') {
window.initialData.screen = data.screen || window.initialData.screen || null;
window.initialData.playlist = data.playlist || null;
window.initialData.slides = nextSlides;
window.initialData.rssFeeds = Array.isArray(data.rssFeeds) ? data.rssFeeds : [];
window.initialData.apiSources = Array.isArray(data.apiSources) ? data.apiSources : [];
window.initialData.timetableGroups = Array.isArray(data.timetableGroups) ? data.timetableGroups : [];
window.initialData.weatherLocations = Array.isArray(data.weatherLocations) ? data.weatherLocations : [];
window.initialData.revision = nextSignature;
var refreshedInitialData = Object.assign({},
typeof initialData !== 'undefined' && initialData ? initialData : (window.initialData || {}), {
screen: data.screen || null,
playlist: data.playlist || null,
slides: nextSlides,
rssFeeds: Array.isArray(data.rssFeeds) ? data.rssFeeds : [],
apiSources: Array.isArray(data.apiSources) ? data.apiSources : [],
timetableGroups: Array.isArray(data.timetableGroups) ? data.timetableGroups : [],
weatherLocations: Array.isArray(data.weatherLocations) ? data.weatherLocations : [],
revision: nextSignature
});
if (typeof initialData !== 'undefined') {
initialData = refreshedInitialData;
}
window.initialData = refreshedInitialData;
savePlaylistSnapshot({
slides: nextSlides,
signature: nextSignature,
@@ -211,6 +211,16 @@ function getCommandClientId() {
return commandClientId;
}
function regenerateCommandClientId() {
commandClientId = null;
try {
window.sessionStorage.removeItem(commandClientStorageKey);
} catch (_error) {
// ignore storage errors
}
return getCommandClientId();
}
// Load the most recent playlist snapshot from browser storage.
function loadPlaylistSnapshot() {
try {
+20 -5
View File
@@ -782,6 +782,7 @@ function getTemplateLayout(template) {
canvasHeight: canvasSize.height,
background: template.background_image_path ? '<img class="template-background" src="' + escapeHtml(template.background_image_path) + '" alt="" />' : '',
backgroundColor: template.background_color || '#111111',
backgroundGradient: template.background_gradient || '',
regions: regions
};
@@ -790,17 +791,31 @@ function getTemplateLayout(template) {
}
// Build a dark backdrop style for template and media canvases.
function buildBackdropStyle(backgroundColor, backgroundImagePath) {
function buildBackdropStyle(backgroundColor, backgroundImagePath, backgroundGradient) {
var color = String(backgroundColor || '#111111').trim() || '#111111';
var style = 'background-color:' + escapeHtml(color) + ';';
var gradient = '';
try {
var gradientData = typeof backgroundGradient === 'string' ? JSON.parse(backgroundGradient) : backgroundGradient;
if (gradientData && gradientData.type === 'linear' && Array.isArray(gradientData.colors) && gradientData.colors.length >= 2) {
var stops = Array.isArray(gradientData.stops) ? gradientData.stops : (gradientData.colors || []).map(function (color, index, colors) { return { color: color, position: colors.length > 1 ? Math.round(index * 100 / (colors.length - 1)) : 0 }; });
stops = stops.filter(function (stop) { return stop && /^#[0-9a-fA-F]{3,8}$/.test(String(stop.color || '').trim()); }).slice(0, 12);
if (stops.length >= 2) {
var angle = Number(gradientData.angle);
gradient = 'linear-gradient(' + (Number.isFinite(angle) ? Math.max(0, Math.min(360, angle)) : 90) + 'deg,' + stops.map(function (stop) { return stop.color + ' ' + Math.max(0, Math.min(100, Number(stop.position) || 0)) + '%'; }).join(',') + ')';
}
}
} catch (_error) {
gradient = '';
}
if (backgroundImagePath) {
style += 'background-image:url("' + escapeHtml(backgroundImagePath) + '");';
style += 'background-position:center;background-size:contain;background-repeat:no-repeat;';
style += 'background-image:url("' + escapeHtml(backgroundImagePath) + '")' + (gradient ? ',' + gradient : '') + ';';
style += 'background-position:center,center;background-size:contain,cover;background-repeat:no-repeat,no-repeat;';
return style;
}
style += 'background-image:none;background-position:center;background-size:cover;background-repeat:no-repeat;';
style += 'background-image:' + (gradient || 'none') + ';background-position:center;background-size:cover;background-repeat:no-repeat;';
return style;
}
@@ -876,7 +891,7 @@ function renderTemplateSlideMarkup(slide) {
animationConfig: normalizePlayerAnimationConfig(region.animationJson)
});
}).join('') : '';
const stageStyle = layout ? buildBackdropStyle(layout.backgroundColor || '#111111', template.background_image_path) : '';
const stageStyle = layout ? buildBackdropStyle(layout.backgroundColor || '#111111', template.background_image_path, layout.backgroundGradient) : '';
if (layout) {
setPlayerCanvasDimensions(layout.canvasWidth, layout.canvasHeight);
}