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

This commit is contained in:
lzstealth committed 2026-10-20 18:52:36 +01:00
1 parent 821593dd8f
commit 3514d514a3
36 files changed
+2329 -346

No files matched your search

+27
View File
@@ -2,6 +2,7 @@
const { fetchTemplateById } = require('#src/data/templates');
const { parseJsonSafe, validateMaxLength } = require('#src/data/utils');
const { normalizeLayout } = require('#src/web/public/js/shared/region-layout');
const TITLE_MAX_LENGTH = 255;
const { buildQrCodeContent } = require('#src/data/qr-code');
@@ -622,6 +623,30 @@ async function buildSlidePayload(pool, req, existingSlide) {
if (template) {
const content = await buildTemplateContent(pool, template, req.body, filesByField, existingContent);
const submittedLayouts = req.body.region_layouts_json;
let layouts = null;
if (submittedLayouts !== undefined) {
layouts = parseJsonSafe(submittedLayouts);
if (!layouts || typeof layouts !== 'object' || Array.isArray(layouts)) {
const error = new Error('Invalid slide layout.');
error.statusCode = 400;
throw error;
}
}
const canvas = { width: template.canvas_size_width, height: template.canvas_size_height };
for (const region of template.regions) {
const key = region.region_key;
const value = layouts ? (Object.hasOwn(layouts, key) ? layouts[key] : null)
: Number(existingSlide && existingSlide.template_id) === template.id && existingContent[key] && existingContent[key].layout;
if (!value) continue;
const layout = normalizeLayout(value, canvas);
if (!layout) {
const error = new Error('Invalid position or size for region ' + key + '.');
error.statusCode = 400;
throw error;
}
if (content[key]) content[key].layout = layout;
}
await Promise.all(Object.keys(content).map(async function (regionKey) {
const region = template.regions.find(function (item) {
return String(item.region_key || '').trim() === regionKey;
@@ -630,7 +655,9 @@ async function buildSlidePayload(pool, req, existingSlide) {
return;
}
const layout = content[regionKey].layout;
content[regionKey] = await buildQrCodeContent(content[regionKey]);
if (layout) content[regionKey].layout = layout;
}));
return {
+54
View File
@@ -2,6 +2,7 @@
const { parseJsonSafe, readFormArray, validateMaxLength } = require('#src/data/utils');
const animationPresets = require('#src/web/public/js/templates/animation-presets');
const { normalizeLayout, getTemplateLayoutSnapshot, getTemplateAnimationSnapshot } = require('#src/web/public/js/shared/region-layout');
const TEMPLATE_NAME_MAX_LENGTH = 255;
const REGION_NAME_MAX_LENGTH = 255;
@@ -309,11 +310,64 @@ async function buildTemplatePayload(pool, req, existingTemplate) {
};
}
function buildTemplateLayoutUpdate(template, body) {
function fail(message, statusCode) {
const error = new Error(message);
error.statusCode = statusCode;
throw error;
}
if (JSON.stringify(body.baseline) !== JSON.stringify(getTemplateLayoutSnapshot(template))) {
fail('The shared template layout has changed. Reload the slide before saving to the template.', 409);
}
const submitted = body.regions;
if (!Array.isArray(submitted) || submitted.length !== template.regions.length) {
fail('The layout must contain every existing template region.', 400);
}
const seen = new Set();
const canvas = { width: template.canvas_size_width, height: template.canvas_size_height };
return submitted.map(function (value) {
const region = value && template.regions.find(function (item) { return item.region_key === value.region_key; });
if (!region || seen.has(region.region_key)) fail('Invalid or duplicate template region.', 400);
seen.add(region.region_key);
if (typeof value.z_index !== 'number' || typeof value.lock_ratio !== 'string') fail('Invalid stacking order or ratio.', 400);
const layout = normalizeLayout({
x: value.x, y: value.y, width: value.width, height: value.height,
z_index: value.z_index, lock_ratio: value.lock_ratio
}, canvas);
if (!layout) fail('Invalid region position, size, stacking order, or ratio.', 400);
return Object.assign({}, region, layout);
}).sort(function (first, second) { return first.z_index - second.z_index || Number(first.id) - Number(second.id); });
}
function buildTemplateAnimationUpdate(template, body) {
function fail(message, statusCode) {
const error = new Error(message);
error.statusCode = statusCode;
throw error;
}
if (JSON.stringify(body.baseline) !== JSON.stringify(getTemplateAnimationSnapshot(template))) {
fail('The shared template animations have changed. Reload the slide before saving.', 409);
}
if (!Array.isArray(body.animations) || body.animations.length !== template.regions.length) {
fail('Animations must be provided for every existing template region.', 400);
}
const seen = new Set();
return body.animations.map(function (value) {
const region = value && template.regions.find(function (item) { return item.region_key === value.region_key; });
if (!region || seen.has(region.region_key)) fail('Invalid or duplicate template region.', 400);
seen.add(region.region_key);
return Object.assign({}, region, { animation_json: normalizeTemplateRegionAnimationConfig(value.animation_json) });
}).sort(function (first, second) { return Number(first.z_index) - Number(second.z_index) || Number(first.id) - Number(second.id); });
}
module.exports = {
fetchTemplateById,
fetchTemplatesData,
extractTemplateRegions,
buildTemplatePayload,
normalizeBackgroundGradient,
buildTemplateLayoutUpdate,
buildTemplateAnimationUpdate,
normalizeTemplateRegionAnimationConfig,
parseJsonSafe
};