Release 2.19.3
This commit is contained in:
1 parent
55ee5dc366
commit
2e603caeb6
19 files changed
+351
-28
No files matched your search
@@ -15,6 +15,7 @@ const {
|
||||
} = require('#src/player/render-helpers');
|
||||
const { createRequestAuthHeaders, createPageAuthToken } = require('#src/request-auth');
|
||||
const { convertWeatherSnapshot } = require('#src/data/weather-units');
|
||||
const { applyLayout } = require('#src/web/public/js/shared/region-layout');
|
||||
|
||||
const placeholderUtils = require('#src/web/public/js/shared/placeholder-utils');
|
||||
const weatherData = require('#src/web/public/js/shared/weather-data');
|
||||
@@ -335,6 +336,10 @@ function buildThumbnailPreviewMarkup(slide, baseUrl, options) {
|
||||
function buildThumbnailPreviewPayload(slide, options) {
|
||||
const template = slide && slide.template ? slide.template : null;
|
||||
const canvasSize = getThumbnailCanvasSize(slide);
|
||||
const regions = (Array.isArray(template && template.regions) ? template.regions : []).map(function (region) {
|
||||
const content = getRegionContent(slide, region);
|
||||
return Object.assign({}, applyLayout(region, content, canvasSize), { content: content });
|
||||
});
|
||||
return {
|
||||
thumbnailPreview: true,
|
||||
canvasWidth: canvasSize.width,
|
||||
@@ -342,6 +347,13 @@ function buildThumbnailPreviewPayload(slide, options) {
|
||||
backgroundColor: template && template.background_color ? String(template.background_color) : '#111111',
|
||||
backgroundGradient: template && template.background_gradient ? String(template.background_gradient) : '',
|
||||
backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '',
|
||||
regions: regions,
|
||||
existingContent: slide && slide.content && typeof slide.content === 'object' ? slide.content : {},
|
||||
apiSources: Array.isArray(options && options.apiSources) ? options.apiSources : [],
|
||||
rssFeeds: Array.isArray(options && options.rssFeeds) ? options.rssFeeds : [],
|
||||
weatherLocations: Array.isArray(options && options.weatherLocations) ? options.weatherLocations : [],
|
||||
timetableGroups: Array.isArray(options && options.timetableGroups) ? options.timetableGroups : [],
|
||||
weatherIconSets: Array.isArray(options && options.weatherIconSets) ? options.weatherIconSets : [],
|
||||
fontStylesheetHref: String(options && options.fontStylesheetHref || '').trim(),
|
||||
html: buildThumbnailPreviewMarkup(slide, options && options.baseUrl, options)
|
||||
};
|
||||
|
||||
@@ -3146,8 +3146,8 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
color: #fff;
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
@@ -3156,7 +3156,7 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.slide-preview-popup-canvas {
|
||||
@@ -4577,6 +4577,36 @@ table.table > :not(caption) > * > * {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.slide-list-thumbnail-button {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slide-list-thumbnail-button:focus-visible {
|
||||
outline: 2px solid var(--bs-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.slide-list-preview-frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: var(--slide-preview-aspect-ratio, 16 / 9);
|
||||
border: 0;
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
#slide-preview-modal .modal-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#slide-preview-modal .modal-dialog {
|
||||
width: var(--slide-preview-modal-width, min(90vw, 1140px));
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.card .table td[data-label="Thumbnail"] {
|
||||
width: 1%;
|
||||
padding-inline: 0.5rem;
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
var hasPreviewBounds = options && options.preview && imageConfig.width && imageConfig.height;
|
||||
if (hasPreviewBounds || imageConfig.verticalAlign) {
|
||||
var wrapperStyle = 'display:inline-flex;align-items:center;justify-content:center;';
|
||||
var wrapperClass = hasPreviewBounds ? ' class="api-image-placeholder-bounds"' : '';
|
||||
var wrapperClass = hasPreviewBounds ? ' class="api-image-placeholder-bounds slide-preview-image-placeholder-bounds"' : '';
|
||||
if (hasPreviewBounds) {
|
||||
wrapperStyle += 'box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;border:1px dashed rgba(120,120,120,.75);';
|
||||
}
|
||||
|
||||
@@ -141,13 +141,14 @@
|
||||
var hasPreviewBounds = options && options.preview && imageConfig.width && imageConfig.height;
|
||||
if (hasPreviewBounds || imageConfig.verticalAlign) {
|
||||
var wrapperStyle = 'display:inline-flex;align-items:center;justify-content:center;';
|
||||
var wrapperClass = hasPreviewBounds ? ' class="slide-preview-image-placeholder-bounds"' : '';
|
||||
if (hasPreviewBounds) {
|
||||
wrapperStyle += 'box-sizing:border-box;width:' + imageConfig.width + 'px;height:' + imageConfig.height + 'px;border:1px dashed rgba(120,120,120,.75);';
|
||||
}
|
||||
if (imageConfig.verticalAlign) {
|
||||
wrapperStyle += 'vertical-align:' + imageConfig.verticalAlign + ';';
|
||||
}
|
||||
return '<span style="' + wrapperStyle + '">' + image + '</span>';
|
||||
return '<span' + wrapperClass + ' style="' + wrapperStyle + '">' + image + '</span>';
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
var style = width ? ' style="display:block;font-size:' + width + 'px;line-height:1;width:' + width + 'px;height:' + height + 'px;"' : '';
|
||||
var icon = '<i class="bi ' + iconForCode(weatherCode) + '"' + style + ' aria-hidden="true"></i>';
|
||||
var verticalAlign = alignment === 'top' || alignment === 'bottom' ? alignment : 'middle';
|
||||
return width ? '<span style="display:inline-flex;vertical-align:' + verticalAlign + ';align-items:center;justify-content:center;box-sizing:border-box;line-height:0;width:' + width + 'px;height:' + height + 'px;border:1px dashed rgba(120,120,120,.75);">' + icon + '</span>' : icon;
|
||||
return width ? '<span class="slide-preview-image-placeholder-bounds" style="display:inline-flex;vertical-align:' + verticalAlign + ';align-items:center;justify-content:center;box-sizing:border-box;line-height:0;width:' + width + 'px;height:' + height + 'px;border:1px dashed rgba(120,120,120,.75);">' + icon + '</span>' : icon;
|
||||
}
|
||||
return escapeHtml(placeholderUtils.formatPlaceholderValue(placeholderUtils.resolvePlaceholderExpression(value, normalizeWeatherExpression(expression), { timeZone: value.timezone })));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
(function (root, factory) {
|
||||
const renderer = factory(root);
|
||||
if (typeof module === 'object' && module.exports) {
|
||||
module.exports = renderer;
|
||||
} else {
|
||||
root.pulseSlidePreviewRenderer = renderer;
|
||||
}
|
||||
}(typeof window !== 'undefined' ? window : globalThis, function (root) {
|
||||
function renderRegionContent(region, content, context, registry) {
|
||||
const regionType = String(region.region_type || content.type || 'text').trim().toLowerCase();
|
||||
const definition = registry && typeof registry.get === 'function' ? registry.get(regionType) : null;
|
||||
if (!definition || typeof definition.renderPreview !== 'function') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (regionType === 'rtmp') {
|
||||
return definition.renderPreview(content, content.disable_audio);
|
||||
}
|
||||
if (regionType === 'time-date') {
|
||||
return definition.renderPreview(region, content, content, 1);
|
||||
}
|
||||
if (regionType === 'text') {
|
||||
return definition.renderPreview(region, content, content);
|
||||
}
|
||||
return definition.renderPreview(region, content, context);
|
||||
}
|
||||
|
||||
function render(payload, registry) {
|
||||
const settings = payload || {};
|
||||
const activeRegistry = registry || root.pulseRegionTypes;
|
||||
const context = {
|
||||
existingContent: settings.existingContent || {},
|
||||
sources: Array.isArray(settings.apiSources) ? settings.apiSources : [],
|
||||
feeds: Array.isArray(settings.rssFeeds) ? settings.rssFeeds : [],
|
||||
weatherLocations: Array.isArray(settings.weatherLocations) ? settings.weatherLocations : [],
|
||||
timetableGroups: Array.isArray(settings.timetableGroups) ? settings.timetableGroups : []
|
||||
};
|
||||
|
||||
return (Array.isArray(settings.regions) ? settings.regions : []).map(function (region) {
|
||||
const content = region.content && typeof region.content === 'object' ? region.content : { value: region.content || '' };
|
||||
const regionType = String(region.region_type || content.type || 'text').trim().toLowerCase();
|
||||
const left = Math.max(0, Math.round(Number(region.x) || 0));
|
||||
const top = Math.max(0, Math.round(Number(region.y) || 0));
|
||||
const width = Math.max(1, Math.round(Number(region.width) || 1));
|
||||
const height = Math.max(1, Math.round(Number(region.height) || 1));
|
||||
const zIndex = Math.max(1, Number(region.z_index) || 1);
|
||||
const regionKey = root.pulseRegionUtils && root.pulseRegionUtils.escapeHtml
|
||||
? root.pulseRegionUtils.escapeHtml(region.region_key || '')
|
||||
: String(region.region_key || '').replace(/[&<>"']/g, function (character) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character];
|
||||
});
|
||||
const html = renderRegionContent(region, content, context, activeRegistry);
|
||||
return '<div class="slide-preview-region slide-preview-' + regionType + '-region" data-region-key="' + regionKey + '" style="left:' + left + 'px;top:' + top + 'px;width:' + width + 'px;height:' + height + 'px;z-index:' + zIndex + ';">' + html + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
return { render: render };
|
||||
}));
|
||||
@@ -26,7 +26,7 @@
|
||||
var iconWidth = width ? width + 'px' : '1em';
|
||||
var iconHeight = height ? height + 'px' : '1em';
|
||||
var verticalAlign = alignment === 'top' || alignment === 'bottom' ? alignment : 'middle';
|
||||
return '<span style="display:inline-flex;vertical-align:' + verticalAlign + ';align-items:center;justify-content:center;box-sizing:border-box;line-height:0;width:' + iconWidth + ';height:' + iconHeight + ';"><img src="' + source + '" alt="" style="display:block;width:100%;height:100%;object-fit:contain;" /></span>';
|
||||
return '<span class="slide-preview-image-placeholder-bounds" style="display:inline-flex;vertical-align:' + verticalAlign + ';align-items:center;justify-content:center;box-sizing:border-box;line-height:0;width:' + iconWidth + ';height:' + iconHeight + ';"><img src="' + source + '" alt="" style="display:block;width:100%;height:100%;object-fit:contain;" /></span>';
|
||||
}
|
||||
var api = { conditionForCode: conditionForCode, imageForCode: imageForCode, isNightForWeather: isNightForWeather, renderImage: renderImage };
|
||||
if (typeof module === 'object' && module.exports) module.exports = api;
|
||||
|
||||
@@ -9,6 +9,7 @@ const { getFontStylesheetHref } = require('#src/web/lib/media/font-library');
|
||||
const { convertWeatherSnapshot } = require('#src/data/weather-units');
|
||||
const { buildThumbnailPreviewPayload } = require('#src/web/lib/media/slide-thumbnails');
|
||||
const { resolveApiItems } = require('#src/web/public/js/shared/placeholder-utils');
|
||||
const { getRegionEditorScripts } = require('#src/web/lib/region-scripts');
|
||||
|
||||
function safeJsonForScript(value) {
|
||||
return JSON.stringify(value === undefined ? null : value).replace(/</g, '\\u003c');
|
||||
@@ -22,6 +23,7 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
const webBaseUrl = String(deps.webBaseUrl || '').trim().replace(/\/$/, '');
|
||||
const { buildPagination } = require('#src/web/lib/pagination');
|
||||
const requirePermission = deps.requirePermission;
|
||||
const slideEditorScripts = getRegionEditorScripts();
|
||||
|
||||
function requireRequestAuth(req, res, next) {
|
||||
if (!verifyRequestAuth(req)) {
|
||||
@@ -62,11 +64,12 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
title: 'Slide preview',
|
||||
framePopupCard: true,
|
||||
hideFrameHeader: true,
|
||||
frameBodyClass: 'slide-preview-popup-shell slide-preview-popup-body'
|
||||
frameBodyClass: 'slide-preview-popup-shell slide-preview-popup-body',
|
||||
slideEditorScripts: slideEditorScripts
|
||||
}));
|
||||
});
|
||||
|
||||
app.get('/api/internal/slide-thumbnails/:id/popup-preview', async function (req, res, next) {
|
||||
app.get('/api/internal/slide-thumbnails/:id/popup-preview', requirePopupPreviewAccess, async function (req, res, next) {
|
||||
try {
|
||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||
if (!slide) {
|
||||
@@ -95,7 +98,7 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
item.current = Object.assign({}, item.current || {}, { temperature_unit: location.temperature_unit === 'fahrenheit' ? '°F' : '°C', wind_speed_unit: location.wind_unit === 'mph' ? 'mph' : location.wind_unit === 'ms' ? 'm/s' : 'km/h', precipitation_unit: location.precipitation_unit === 'inch' ? 'in' : 'mm' });
|
||||
item.daily = Object.assign({}, item.daily || {}, { temperature_unit: location.temperature_unit === 'fahrenheit' ? '°F' : '°C' });
|
||||
item.hourly = Object.assign({}, item.hourly || {}, { temperature_unit: location.temperature_unit === 'fahrenheit' ? '°F' : '°C' });
|
||||
return { id: location.id, data: item };
|
||||
return Object.assign({}, location, { id: location.id, data: item, responseJson: item });
|
||||
}).filter(Boolean);
|
||||
const getItem = function (type, regionContent) {
|
||||
const index = Math.max(0, Math.max(1, Number(regionContent && regionContent.item_number || 1)) - 1);
|
||||
@@ -125,6 +128,8 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
fontStylesheetHref: getFontStylesheetHref(mediaDir),
|
||||
weatherIconSets: require('#src/web/lib/media/weather-icon-library').readWeatherIconSets(mediaDir),
|
||||
apiSources: apiSources,
|
||||
rssFeeds: rssFeeds,
|
||||
weatherLocations: weatherLocations,
|
||||
timetableGroups: timetableData.timetableGroups,
|
||||
getItem: getItem,
|
||||
getCachedImagePath: getCachedImagePath
|
||||
@@ -136,7 +141,8 @@ module.exports = function registerSlidesRoutes(app, deps) {
|
||||
framePopupCard: true,
|
||||
hideFrameHeader: true,
|
||||
frameBodyClass: 'slide-preview-popup-shell slide-preview-popup-body',
|
||||
popupPreviewPayloadJson: safeJsonForScript(payload)
|
||||
popupPreviewPayloadJson: safeJsonForScript(payload),
|
||||
slideEditorScripts: slideEditorScripts
|
||||
}));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
{{#each slides}}
|
||||
<tr data-table-search-row data-search-text="{{title}} {{template_name}}">
|
||||
<td data-label="Thumbnail">
|
||||
<div class="playlist-slide-thumb" aria-hidden="true" {{#if canvas_width}}style="--playlist-slide-thumb-aspect-ratio: {{canvas_width}} / {{canvas_height}};"{{/if}}>
|
||||
<button type="button" class="playlist-slide-thumb slide-list-thumbnail-button" data-bs-toggle="modal" data-bs-target="#slide-preview-modal" data-slide-preview-url="/api/internal/slide-thumbnails/{{id}}/popup-preview" data-slide-preview-title="{{title}}" data-canvas-width="{{canvas_width}}" data-canvas-height="{{canvas_height}}" aria-label="Preview slide: {{title}}" {{#if canvas_width}}style="--playlist-slide-thumb-aspect-ratio: {{canvas_width}} / {{canvas_height}};"{{/if}}>
|
||||
{{#if thumbnail_path}}
|
||||
<img class="playlist-slide-thumb-image" src="{{thumbnail_path}}" alt="" loading="lazy" />
|
||||
{{else}}
|
||||
<span class="playlist-slide-thumb-placeholder"><i class="bi bi-image" aria-hidden="true"></i></span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</button>
|
||||
</td>
|
||||
<td data-label="Title">{{title}}</td>
|
||||
<td data-label="Template">{{template_name}}</td>
|
||||
@@ -79,7 +79,62 @@
|
||||
{{> table-pagination pagination=pagination basePath="/slides" alwaysShow=true}}
|
||||
</div>
|
||||
|
||||
{{#> modal-shell modalId="slide-preview-modal" modalLabelId="slide-preview-modal-label" modalDialogClass="modal-xl modal-dialog-centered"}}
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title fs-5" id="slide-preview-modal-label">Slide preview</h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<iframe class="slide-list-preview-frame" title="Slide preview" data-slide-preview-frame src="about:blank"></iframe>
|
||||
</div>
|
||||
{{/modal-shell}}
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var modal = document.getElementById('slide-preview-modal');
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
var frame = modal.querySelector('[data-slide-preview-frame]');
|
||||
var dialog = modal.querySelector('.modal-dialog');
|
||||
var title = modal.querySelector('#slide-preview-modal-label');
|
||||
var currentAspectRatio = 16 / 9;
|
||||
|
||||
function updateModalSize() {
|
||||
var maxWidth = Math.min(window.innerWidth * 0.9, 1140);
|
||||
var maxHeight = window.innerHeight * 0.72;
|
||||
var width = Math.min(maxWidth, maxHeight * currentAspectRatio);
|
||||
modal.style.setProperty('--slide-preview-aspect-ratio', String(currentAspectRatio));
|
||||
modal.style.setProperty('--slide-preview-modal-width', Math.round(width) + 'px');
|
||||
}
|
||||
|
||||
modal.addEventListener('show.bs.modal', function (event) {
|
||||
var trigger = event.relatedTarget;
|
||||
if (!trigger || !frame) {
|
||||
return;
|
||||
}
|
||||
var canvasWidth = Number(trigger.getAttribute('data-canvas-width'));
|
||||
var canvasHeight = Number(trigger.getAttribute('data-canvas-height'));
|
||||
currentAspectRatio = canvasWidth > 0 && canvasHeight > 0 ? canvasWidth / canvasHeight : 16 / 9;
|
||||
updateModalSize();
|
||||
frame.src = trigger.getAttribute('data-slide-preview-url') || 'about:blank';
|
||||
if (title) {
|
||||
title.textContent = 'Slide preview: ' + (trigger.getAttribute('data-slide-preview-title') || '');
|
||||
}
|
||||
});
|
||||
modal.addEventListener('hidden.bs.modal', function () {
|
||||
if (frame) {
|
||||
frame.src = 'about:blank';
|
||||
}
|
||||
});
|
||||
window.addEventListener('resize', function () {
|
||||
if (modal.classList.contains('show')) {
|
||||
updateModalSize();
|
||||
}
|
||||
});
|
||||
}());
|
||||
|
||||
if (typeof window.attachSlideThumbFallbacks === 'function') {
|
||||
window.attachSlideThumbFallbacks(document);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
background: #111;
|
||||
color: #fff;
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
#popup-preview-stage {
|
||||
@@ -44,8 +44,11 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#popup-preview-canvas .slide-preview-image-placeholder-bounds,
|
||||
#popup-preview-canvas .api-image-placeholder-bounds {
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#popup-preview-canvas .template-region.chart,
|
||||
@@ -71,9 +74,10 @@
|
||||
<div class="slide-preview-popup-canvas" id="popup-preview-canvas"></div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/vendor/chart.js/chart.umd.js"></script>
|
||||
<script src="/assets/js/shared/placeholder-utils.js"></script>
|
||||
<script src="/assets/js/regions/chart-data.js"></script>
|
||||
{{#each slideEditorScripts}}
|
||||
<script src="/assets/{{this}}"></script>
|
||||
{{/each}}
|
||||
<script src="/assets/js/shared/slide-preview-renderer.js"></script>
|
||||
|
||||
{{#if popupPreviewPayloadJson}}
|
||||
<script>
|
||||
@@ -237,7 +241,10 @@
|
||||
canvas.style.transformOrigin = 'center center';
|
||||
canvas.style.width = canvasWidth + 'px';
|
||||
canvas.style.height = canvasHeight + 'px';
|
||||
canvas.innerHTML = String(payload.html || '');
|
||||
var html = payload.thumbnailPreview && window.pulseSlidePreviewRenderer
|
||||
? window.pulseSlidePreviewRenderer.render(payload)
|
||||
: payload.html;
|
||||
canvas.innerHTML = String(html || '');
|
||||
startPreviewVideoPlayback(canvas);
|
||||
|
||||
var stageRect = stage.getBoundingClientRect();
|
||||
|
||||
Reference in new issue
Block a user