Release v2.5.6
This commit is contained in:
@@ -99,7 +99,6 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2.5rem;
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
backface-visibility: hidden;
|
||||
will-change: transform;
|
||||
@@ -148,7 +147,7 @@
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(calc(-1 * var(--announcement-scroll-distance, 0px)), 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -178,21 +177,10 @@
|
||||
}
|
||||
|
||||
var track = root.querySelector('.player-announcement__message-track');
|
||||
if (!track) {
|
||||
if (!track || typeof window.initPlayerAnnouncementMarquee !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (track.dataset.marqueeMeasured === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
var rate = Number(track.getAttribute('data-announcement-marquee-rate') || 44) || 44;
|
||||
var singlePassWidth = Math.max(1, track.scrollWidth / 2);
|
||||
var durationSeconds = Math.max(8, singlePassWidth / rate);
|
||||
var durationValue = durationSeconds.toFixed(2) + 's';
|
||||
|
||||
track.dataset.marqueeMeasured = 'true';
|
||||
track.style.setProperty('--announcement-scroll-duration', durationValue);
|
||||
track.style.animationDuration = durationValue;
|
||||
window.initPlayerAnnouncementMarquee(track, { container: root.querySelector('.player-announcement__message') || root });
|
||||
}());
|
||||
</script>
|
||||
@@ -101,7 +101,6 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2.5rem;
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
backface-visibility: hidden;
|
||||
will-change: transform;
|
||||
@@ -150,7 +149,7 @@
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(calc(-1 * var(--announcement-scroll-distance, 0px)), 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -180,21 +179,10 @@
|
||||
}
|
||||
|
||||
var track = root.querySelector('.player-announcement__message-track');
|
||||
if (!track) {
|
||||
if (!track || typeof window.initPlayerAnnouncementMarquee !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (track.dataset.marqueeMeasured === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
var rate = Number(track.getAttribute('data-announcement-marquee-rate') || 44) || 44;
|
||||
var singlePassWidth = Math.max(1, track.scrollWidth / 2);
|
||||
var durationSeconds = Math.max(8, singlePassWidth / rate);
|
||||
var durationValue = durationSeconds.toFixed(2) + 's';
|
||||
|
||||
track.dataset.marqueeMeasured = 'true';
|
||||
track.style.setProperty('--announcement-scroll-duration', durationValue);
|
||||
track.style.animationDuration = durationValue;
|
||||
window.initPlayerAnnouncementMarquee(track, { container: root.querySelector('.player-announcement__message') || root });
|
||||
}());
|
||||
</script>
|
||||
@@ -1,6 +1,7 @@
|
||||
// Player onboarding routes and signup flow helpers.
|
||||
|
||||
const { isClientNameAvailable, withClientNameReservation } = require('#src/data/client-name-check');
|
||||
const { createStyledQrCodeSvg } = require('#src/data/qr-code');
|
||||
const { getSharedSecret, verifyPageAuthToken } = require('#src/request-auth');
|
||||
const { isTransientDbError } = require('./store');
|
||||
|
||||
@@ -222,11 +223,10 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
const pool = options && options.pool ? options.pool : null;
|
||||
const common = options && options.common ? options.common : null;
|
||||
const playerRuntime = options && options.playerRuntime ? options.playerRuntime : null;
|
||||
const QRCode = options && options.QRCode ? options.QRCode : null;
|
||||
const onboardingStore = options && options.onboardingStore ? options.onboardingStore : null;
|
||||
|
||||
if (!app || !pool || !common || !playerRuntime || !QRCode) {
|
||||
throw new Error('registerPlayerOnboardingRoutes requires app, pool, common, playerRuntime, and QRCode.');
|
||||
if (!app || !pool || !common || !playerRuntime) {
|
||||
throw new Error('registerPlayerOnboardingRoutes requires app, pool, common, and playerRuntime.');
|
||||
}
|
||||
|
||||
const sharedSecret = getSharedSecret();
|
||||
@@ -303,7 +303,7 @@ function registerPlayerOnboardingRoutes(app, options) {
|
||||
return res.status(400).json({ error: 'Device ID is required' });
|
||||
}
|
||||
const onboardingUrl = `${getPublicBaseUrl(req, options && options.playerPublicBaseUrl)}/onboard?deviceId=${encodeURIComponent(deviceId)}`;
|
||||
const svg = await QRCode.toString(onboardingUrl, { type: 'svg', margin: 1, errorCorrectionLevel: 'M' });
|
||||
const svg = await createStyledQrCodeSvg({ value: onboardingUrl, qr_margin: 20 });
|
||||
res.set('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.set('Cache-Control', 'no-store');
|
||||
res.send(svg);
|
||||
|
||||
+29
-2
@@ -3,6 +3,7 @@
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { createStyledQrCodeDataUrl } = require('../data/qr-code');
|
||||
|
||||
function createPlayerPlaylistService(options) {
|
||||
const pool = options && options.pool ? options.pool : null;
|
||||
@@ -56,6 +57,10 @@ function createPlayerPlaylistService(options) {
|
||||
await fs.promises.writeFile(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
async function createQrPreviewDataUrl(value) {
|
||||
return createStyledQrCodeDataUrl(value);
|
||||
}
|
||||
|
||||
async function buildScreenPlaylist(slug) {
|
||||
try {
|
||||
const [screenRows] = await pool.query('SELECT id, name, slug, playlist_id, created_at, modified_at, created_by, modified_by FROM d_screens WHERE slug = ?', [slug]);
|
||||
@@ -150,12 +155,13 @@ function createPlayerPlaylistService(options) {
|
||||
}
|
||||
}
|
||||
|
||||
const slides = slideRows.map(function (slide) {
|
||||
const slides = await Promise.all(slideRows.map(async function (slide) {
|
||||
const storedDuration = Number(slide.duration_seconds || 0);
|
||||
const videoDuration = slide.use_video_duration ? getVideoRegionDurationSeconds(slide.content_json) : null;
|
||||
const disableAudio = slide.disable_audio === undefined || slide.disable_audio === null ? true : Boolean(slide.disable_audio);
|
||||
const videoCacheBust = String(slide.modified_at || slide.content_json || slide.id || '');
|
||||
const content = common.parseJsonSafe(slide.content_json) || {};
|
||||
const qrBackfillTasks = [];
|
||||
Object.keys(content).forEach(function (key) {
|
||||
const region = content[key];
|
||||
if (region && typeof region === 'object') {
|
||||
@@ -168,6 +174,27 @@ function createPlayerPlaylistService(options) {
|
||||
region.cache_bust = videoCacheBust;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(content).forEach(function (key) {
|
||||
const region = content[key];
|
||||
if (!region || typeof region !== 'object' || String(region.type || '').trim().toLowerCase() !== 'qr-code') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (region.qr_preview && /^data:image\//i.test(String(region.qr_preview || '').trim())) {
|
||||
return;
|
||||
}
|
||||
|
||||
qrBackfillTasks.push(createQrPreviewDataUrl(region.value).then(function (preview) {
|
||||
if (preview) {
|
||||
region.qr_preview = preview;
|
||||
}
|
||||
return null;
|
||||
}).catch(function () {
|
||||
return null;
|
||||
}));
|
||||
});
|
||||
await Promise.all(qrBackfillTasks);
|
||||
return {
|
||||
id: slide.id,
|
||||
title: slide.title,
|
||||
@@ -180,7 +207,7 @@ function createPlayerPlaylistService(options) {
|
||||
template: slide.template_id ? templatesById[slide.template_id] || null : null,
|
||||
content: content
|
||||
};
|
||||
});
|
||||
}));
|
||||
|
||||
let rssFeeds = [];
|
||||
if (typeof common.fetchRssFeedsData === 'function' && typeof common.fetchRssFeedItemsByFeedId === 'function') {
|
||||
|
||||
@@ -116,6 +116,8 @@ body.thumbnail-preview .player-offline-banner {
|
||||
display: block;
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.onboarding-form {
|
||||
|
||||
@@ -54,12 +54,68 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getComputedGapWidth(track) {
|
||||
if (!track || !window.getComputedStyle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var computedStyle = window.getComputedStyle(track);
|
||||
var gapValue = String(computedStyle.columnGap || computedStyle.gap || '0').trim();
|
||||
var gap = Number.parseFloat(gapValue);
|
||||
return Number.isFinite(gap) ? gap : 0;
|
||||
}
|
||||
|
||||
function getElementWidth(element) {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.max(0, element.getBoundingClientRect().width || 0);
|
||||
}
|
||||
|
||||
function initPlayerAnnouncementMarquee(track, options) {
|
||||
if (!track || track.dataset.marqueeMeasured === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = options || {};
|
||||
var rate = Number(settings.rate || track.getAttribute('data-announcement-marquee-rate') || 44) || 44;
|
||||
var textSelector = String(settings.textSelector || '.player-announcement__message-text').trim() || '.player-announcement__message-text';
|
||||
var textNodes = Array.prototype.slice.call(track.querySelectorAll(textSelector));
|
||||
if (!textNodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var container = settings.container || track.parentElement || track;
|
||||
var containerWidth = Math.max(1, getElementWidth(container) || 0);
|
||||
var gapWidth = getComputedGapWidth(track);
|
||||
var firstItemWidth = Math.max(1, getElementWidth(textNodes[0]) || 0);
|
||||
var cycleWidth = Math.max(1, firstItemWidth + gapWidth);
|
||||
var minimumTrackWidth = Math.max(containerWidth + cycleWidth, cycleWidth * 2);
|
||||
var cloneIndex = 0;
|
||||
|
||||
while (getElementWidth(track) < minimumTrackWidth && cloneIndex < 8) {
|
||||
var clone = textNodes[cloneIndex % textNodes.length].cloneNode(true);
|
||||
clone.setAttribute('aria-hidden', 'true');
|
||||
track.appendChild(clone);
|
||||
cloneIndex += 1;
|
||||
}
|
||||
|
||||
var durationSeconds = Math.max(8, cycleWidth / rate);
|
||||
var durationValue = durationSeconds.toFixed(2) + 's';
|
||||
|
||||
track.dataset.marqueeMeasured = 'true';
|
||||
track.style.setProperty('--announcement-scroll-distance', cycleWidth.toFixed(2) + 'px');
|
||||
track.style.setProperty('--announcement-scroll-duration', durationValue);
|
||||
track.style.animationDuration = durationValue;
|
||||
}
|
||||
|
||||
function renderPlayerAnnouncementMarkup(announcement) {
|
||||
var normalizedType = normalizeAnnouncementType(announcement && announcement.announcement_type);
|
||||
var colorKey = normalizeAnnouncementColor(announcement && announcement.color_key);
|
||||
var colorTokens = getAnnouncementColorTokens(colorKey);
|
||||
var iconKey = normalizeAnnouncementIcon(announcement && announcement.icon_key);
|
||||
var text = escapeHtml(String(announcement && announcement.message || '').trim()).replace(/\n/g, '<br />');
|
||||
var text = escapeHtml(String(announcement && announcement.message || '').trim()).replace(/[\r\n]+/g, ' ');
|
||||
var titleText = escapeHtml(String(announcement && announcement.short_label || 'Announcement').trim() || 'Announcement');
|
||||
var templates = window.playerAnnouncementTemplates || {};
|
||||
var templateName = normalizedType === 'fullscreen' ? 'fullscreen' : normalizedType === 'top-banner' ? 'topBanner' : 'lowerThird';
|
||||
@@ -77,4 +133,5 @@
|
||||
}
|
||||
|
||||
window.renderPlayerAnnouncementMarkup = renderPlayerAnnouncementMarkup;
|
||||
window.initPlayerAnnouncementMarquee = initPlayerAnnouncementMarquee;
|
||||
})();
|
||||
@@ -3,14 +3,18 @@
|
||||
var registry = window.pulsePlayerRegionTypes;
|
||||
|
||||
function renderQrCodeRegion(region, regionContent) {
|
||||
var svg = String(regionContent && regionContent.qr_svg || '').trim();
|
||||
if (!svg) {
|
||||
var borderRadius = Math.max(0, Math.round(Number(regionContent && regionContent.qr_border_radius || 0)));
|
||||
var radiusStyle = borderRadius > 0 ? 'border-radius:' + borderRadius + 'px;overflow:hidden;' : '';
|
||||
var preview = String(regionContent && regionContent.qr_preview || '').trim();
|
||||
|
||||
if (!preview) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<div class="template-region qr-code" style="' + region.baseStyle + '"><img class="template-region-qr-code-image" src="data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) + '" alt="QR code" /></div>';
|
||||
return '<div class="template-region qr-code" style="' + region.baseStyle + radiusStyle + '"><img class="template-region-qr-code-image" src="' + escapeHtml(preview) + '" alt="QR code" role="img" draggable="false" style="background:transparent;display:block;width:100%;height:100%;object-fit:contain;' + radiusStyle + '" /></div>';
|
||||
}
|
||||
|
||||
registry.register('qr-code', {
|
||||
renderRegion: renderQrCodeRegion
|
||||
renderRegion: renderQrCodeRegion,
|
||||
initRegion: function () {}
|
||||
});
|
||||
@@ -40,7 +40,7 @@ function renderTextRegion(region, regionContent) {
|
||||
var fontFamily = sanitizeFontFamily(regionContent.font_family || region.fontFamily || defaultStyle.font_family);
|
||||
var fontSize = sanitizeFontSize(regionContent.font_size || region.fontSize || defaultStyle.font_size);
|
||||
var fontColor = sanitizeTextColor(regionContent.font_color || region.fontColor || defaultStyle.font_color);
|
||||
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
|
||||
var contentStyle = 'width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;transform:scale(' + region.canvasScale + ');transform-origin:top left;line-height:1.5;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor) + ';';
|
||||
var renderedBody = renderEditorJsContent(rawValue);
|
||||
return renderedBody ? '<div class="template-region text" style="' + region.baseStyle + '"><div class="template-region-text-scale" style="' + contentStyle + '">' + renderedBody + '</div></div>' : '';
|
||||
}
|
||||
|
||||
@@ -442,8 +442,9 @@ function getPlayerRegionScriptPaths() {
|
||||
|
||||
function getPlayerRegionScripts() {
|
||||
const sharedPlaceholderUtilsPath = path.join(__dirname, '..', 'web', 'public', 'js', 'shared', 'placeholder-utils.js');
|
||||
const sharedQrSvgUtilsPath = path.join(__dirname, '..', 'web', 'public', 'js', 'shared', 'qr-code-svg.js');
|
||||
const playerRegionScriptPaths = getPlayerRegionScriptPaths();
|
||||
const scriptPaths = [sharedPlaceholderUtilsPath].concat(playerRegionScriptPaths);
|
||||
const scriptPaths = [sharedPlaceholderUtilsPath, sharedQrSvgUtilsPath].concat(playerRegionScriptPaths);
|
||||
const statSignature = scriptPaths.map(function (filePath) {
|
||||
return fs.statSync(filePath).mtimeMs;
|
||||
}).join('|');
|
||||
|
||||
Reference in New Issue
Block a user