156 lines
7.5 KiB
JavaScript
156 lines
7.5 KiB
JavaScript
// Pure thumbnail preview payload helpers shared by routes and screenshot capture.
|
|
|
|
const {
|
|
escapeHtml,
|
|
renderEditorJsContent,
|
|
sanitizeFontFamily,
|
|
sanitizeFontSize,
|
|
sanitizeTextColor
|
|
} = require('#src/player/render-helpers');
|
|
|
|
function normalizeBaseUrl(baseUrl) {
|
|
return String(baseUrl || '').trim().replace(/\/$/, '');
|
|
}
|
|
|
|
function resolveAssetUrl(baseUrl, value) {
|
|
const raw = String(value || '').trim();
|
|
if (!raw) {
|
|
return '';
|
|
}
|
|
if (/^(?:https?:)?\/\//i.test(raw) || raw.startsWith('data:')) {
|
|
return raw;
|
|
}
|
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
if (!normalizedBaseUrl) {
|
|
return raw;
|
|
}
|
|
if (raw.startsWith('/')) {
|
|
return normalizedBaseUrl + raw;
|
|
}
|
|
return normalizedBaseUrl + '/' + raw.replace(/^\/+/, '');
|
|
}
|
|
|
|
function getThumbnailCanvasSize(slide) {
|
|
const template = slide && slide.template ? slide.template : null;
|
|
return {
|
|
width: Math.max(1, Number(template && template.canvas_size_width ? template.canvas_size_width : 1920)),
|
|
height: Math.max(1, Number(template && template.canvas_size_height ? template.canvas_size_height : 1080))
|
|
};
|
|
}
|
|
|
|
function getRegionContent(slide, region) {
|
|
const content = slide && slide.content && slide.content[region.region_key] ? slide.content[region.region_key] : {};
|
|
return content && typeof content === 'object' ? content : { value: content };
|
|
}
|
|
|
|
function buildThumbnailRegionStyle(region, canvasWidth, canvasHeight) {
|
|
const left = Number.isFinite(Number(region && region.x)) && canvasWidth > 0 ? (Number(region.x) / canvasWidth) * 100 : 0;
|
|
const top = Number.isFinite(Number(region && region.y)) && canvasHeight > 0 ? (Number(region.y) / canvasHeight) * 100 : 0;
|
|
const width = Number.isFinite(Number(region && region.width)) && canvasWidth > 0 ? (Number(region.width) / canvasWidth) * 100 : 0;
|
|
const height = Number.isFinite(Number(region && region.height)) && canvasHeight > 0 ? (Number(region.height) / canvasHeight) * 100 : 0;
|
|
|
|
return 'left:' + left + '%;top:' + top + '%;width:' + width + '%;height:' + height + '%;z-index:' + Number(region && region.z_index || 0) + ';';
|
|
}
|
|
|
|
function hasVisibleContent(html) {
|
|
return Boolean(String(html || '').replace(/<[^>]+>/g, '').trim());
|
|
}
|
|
|
|
function buildTextRegionMarkup(region, regionContent) {
|
|
const fontFamily = sanitizeFontFamily(regionContent.font_family || region.font_family);
|
|
const fontSize = sanitizeFontSize(regionContent.font_size || region.font_size);
|
|
const fontColor = sanitizeTextColor(regionContent.font_color || region.font_color);
|
|
const renderedBody = renderEditorJsContent(regionContent.value || '');
|
|
if (!hasVisibleContent(renderedBody)) {
|
|
return '';
|
|
}
|
|
|
|
return '<div class="slide-preview-region slide-preview-text-region" style="' + region.baseStyle + '"><div class="slide-preview-text-content" style="width:' + region.pixelWidth + 'px;height:' + region.pixelHeight + 'px;overflow:hidden;' + (fontFamily ? 'font-family:' + escapeHtml(fontFamily) + ';' : '') + 'font-size:' + fontSize + 'px;color:' + escapeHtml(fontColor || '#000000') + ';">' + renderedBody + '</div></div>';
|
|
}
|
|
|
|
function buildRegionInnerHtml(region, regionContent, baseUrl) {
|
|
const regionType = String(regionContent.type || region.region_type || 'text').trim().toLowerCase();
|
|
const rawValue = regionContent && regionContent.value !== undefined ? regionContent.value : '';
|
|
|
|
if (regionType === 'image') {
|
|
const src = resolveAssetUrl(baseUrl, rawValue);
|
|
return src
|
|
? '<div class="slide-preview-region slide-preview-image-region" style="' + region.baseStyle + '"><img class="slide-preview-image" src="' + escapeHtml(src) + '" alt="' + escapeHtml(region.label || region.region_key || 'image') + '" /></div>'
|
|
: '';
|
|
}
|
|
|
|
if (regionType === 'video') {
|
|
const src = resolveAssetUrl(baseUrl, rawValue);
|
|
return src
|
|
? '<div class="slide-preview-region slide-preview-video-region" style="' + region.baseStyle + '"><video class="slide-preview-video" src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'video') + '" muted playsinline preload="metadata"></video></div>'
|
|
: '';
|
|
}
|
|
|
|
if (regionType === 'webpage') {
|
|
const src = resolveAssetUrl(baseUrl, rawValue);
|
|
return src
|
|
? '<div class="slide-preview-region slide-preview-webpage-region" style="' + region.baseStyle + '"><iframe src="' + escapeHtml(src) + '" title="' + escapeHtml(region.label || region.region_key || 'webpage') + '" loading="eager" referrerpolicy="no-referrer" scrolling="no"></iframe></div>'
|
|
: '';
|
|
}
|
|
|
|
if (regionType === 'qr-code') {
|
|
const src = String(regionContent.qr_preview || '').trim();
|
|
const borderRadius = Math.max(0, Math.round(Number(regionContent.qr_border_radius || 0)));
|
|
const radiusStyle = borderRadius > 0 ? ' style="border-radius:' + borderRadius + 'px;overflow:hidden;"' : '';
|
|
return src
|
|
? '<div class="slide-preview-region slide-preview-qr-code-region" style="' + region.baseStyle + radiusStyle + '"><img class="slide-preview-image" src="' + escapeHtml(src) + '" alt="' + escapeHtml(region.label || region.region_key || 'qr code') + '" /></div>'
|
|
: '';
|
|
}
|
|
|
|
if (regionType === 'html') {
|
|
const html = String(rawValue || '').trim();
|
|
return html
|
|
? '<div class="slide-preview-region slide-preview-html-region" style="' + region.baseStyle + '"><iframe sandbox="" srcdoc="<!doctype html><html><head><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent;}</style></head><body>' + escapeHtml(html) + '</body></html>" title="' + escapeHtml(region.label || region.region_key || 'html') + '" loading="eager" scrolling="no"></iframe></div>'
|
|
: '';
|
|
}
|
|
|
|
if (regionType === 'rtmp') {
|
|
const label = String(rawValue || '').trim() || 'RTMP source';
|
|
return '<div class="slide-preview-region slide-preview-rtmp-region" style="' + region.baseStyle + '"><div class="slide-preview-rtmp-placeholder">' + escapeHtml(label) + '</div></div>';
|
|
}
|
|
|
|
return buildTextRegionMarkup(region, regionContent);
|
|
}
|
|
|
|
function buildThumbnailPreviewMarkup(slide, baseUrl) {
|
|
const template = slide && slide.template ? slide.template : null;
|
|
if (!template) {
|
|
return '';
|
|
}
|
|
|
|
const canvasSize = getThumbnailCanvasSize(slide);
|
|
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
return (Array.isArray(template.regions) ? template.regions : []).map(function (region) {
|
|
const regionContent = getRegionContent(slide, region);
|
|
const previewRegion = Object.assign({}, region, {
|
|
baseStyle: buildThumbnailRegionStyle(region, canvasSize.width, canvasSize.height),
|
|
pixelWidth: Math.max(1, Math.round(Number(region && region.width || 0) || 1)),
|
|
pixelHeight: Math.max(1, Math.round(Number(region && region.height || 0) || 1))
|
|
});
|
|
return buildRegionInnerHtml(previewRegion, regionContent, normalizedBaseUrl);
|
|
}).join('');
|
|
}
|
|
|
|
function buildThumbnailPreviewPayload(slide, options) {
|
|
const template = slide && slide.template ? slide.template : null;
|
|
const canvasSize = getThumbnailCanvasSize(slide);
|
|
return {
|
|
thumbnailPreview: true,
|
|
canvasWidth: canvasSize.width,
|
|
canvasHeight: canvasSize.height,
|
|
backgroundColor: template && template.background_color ? String(template.background_color) : '#111111',
|
|
backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '',
|
|
fontStylesheetHref: String(options && options.fontStylesheetHref || '').trim(),
|
|
html: buildThumbnailPreviewMarkup(slide, options && options.baseUrl)
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
buildThumbnailPreviewPayload: buildThumbnailPreviewPayload,
|
|
buildThumbnailPreviewMarkup: buildThumbnailPreviewMarkup
|
|
}; |