Web changes: Split shared helpers, bootstrap logic, route groups, and upload-sync behavior out of web.js. Kept web.js focused on wiring and server startup. Fixed screen playlist reassignment so changing a screen’s playlist now triggers a refresh. Fixed single-slide playlist refresh behavior so updates do not get stuck behind the current slide. Player changes: Split websocket/runtime handling into runtime.js. Split playlist assembly and revision hashing into playlist.js. Split onboarding and player HTTP routes into dedicated modules. Split render utilities and template loading into render-helpers.js. Kept player.js mostly as startup/orchestration. Validation: Rebuilt both services with Docker Compose. Smoke-checked web and player routes after the refactor. Verified get_errors was clean on the touched modules.
118 lines
4.6 KiB
JavaScript
118 lines
4.6 KiB
JavaScript
(function () {
|
|
function escapeHtml(value) {
|
|
return String(value === undefined || value === null ? '' : value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function valueOrDefault(value, fallback) {
|
|
return value === undefined || value === null || value === '' ? fallback : value;
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return Math.max(min, Math.min(max, value));
|
|
}
|
|
|
|
function getRegionName(card) {
|
|
return String(card.querySelector('[name="region_name[]"]').value || '').trim();
|
|
}
|
|
|
|
function syncRegionIdentity(card, value) {
|
|
var next = String(value || '').trim();
|
|
card.querySelector('[name="region_name[]"]').value = next;
|
|
card.querySelector('[name="region_key[]"]').value = next;
|
|
card.querySelector('[name="region_label[]"]').value = next;
|
|
}
|
|
|
|
function readCard(card) {
|
|
var name = getRegionName(card);
|
|
return {
|
|
region_key: name,
|
|
label: name,
|
|
region_type: card.querySelector('[name="region_type[]"]').value,
|
|
font_family: card.querySelector('[name="font_family[]"]').value,
|
|
x: Number(card.querySelector('[name="region_x[]"]').value || 0),
|
|
y: Number(card.querySelector('[name="region_y[]"]').value || 0),
|
|
width: Number(card.querySelector('[name="region_width[]"]').value || 0),
|
|
height: Number(card.querySelector('[name="region_height[]"]').value || 0),
|
|
z_index: Number(card.querySelector('[name="region_z[]"]').value || 0)
|
|
};
|
|
}
|
|
|
|
function writeCard(card, values) {
|
|
if (values.region_name !== undefined) {
|
|
syncRegionIdentity(card, values.region_name);
|
|
} else if (values.region_key !== undefined) {
|
|
syncRegionIdentity(card, values.region_key);
|
|
} else if (values.label !== undefined) {
|
|
syncRegionIdentity(card, values.label);
|
|
}
|
|
if (values.region_type !== undefined) { card.querySelector('[name="region_type[]"]').value = values.region_type; }
|
|
if (values.font_family !== undefined) { card.querySelector('[name="font_family[]"]').value = values.font_family; }
|
|
if (values.x !== undefined) { card.querySelector('[name="region_x[]"]').value = Math.round(values.x); }
|
|
if (values.y !== undefined) { card.querySelector('[name="region_y[]"]').value = Math.round(values.y); }
|
|
if (values.width !== undefined) { card.querySelector('[name="region_width[]"]').value = Math.round(values.width); }
|
|
if (values.height !== undefined) { card.querySelector('[name="region_height[]"]').value = Math.round(values.height); }
|
|
if (values.z_index !== undefined) { card.querySelector('[name="region_z[]"]').value = Math.round(values.z_index); }
|
|
}
|
|
|
|
function clampRegion(region, size, minSize) {
|
|
var bounds = size || { width: 1920, height: 1080 };
|
|
var minimum = minSize || 12;
|
|
var x = clamp(region.x, 0, bounds.width - minimum);
|
|
var y = clamp(region.y, 0, bounds.height - minimum);
|
|
var width = Math.max(minimum, region.width);
|
|
var height = Math.max(minimum, region.height);
|
|
if (x + width > bounds.width) {
|
|
width = bounds.width - x;
|
|
}
|
|
if (y + height > bounds.height) {
|
|
height = bounds.height - y;
|
|
}
|
|
return { x: Math.round(x), y: Math.round(y), width: Math.round(Math.max(minimum, width)), height: Math.round(Math.max(minimum, height)) };
|
|
}
|
|
|
|
function getOverlayRect(overlay) {
|
|
return overlay.getBoundingClientRect();
|
|
}
|
|
|
|
function toCanvasPoint(event, overlay, size) {
|
|
var rect = getOverlayRect(overlay);
|
|
var canvasSize = size || { width: 1920, height: 1080 };
|
|
var x = clamp(event.clientX - rect.left, 0, rect.width);
|
|
var y = clamp(event.clientY - rect.top, 0, rect.height);
|
|
return {
|
|
x: Math.round((x / Math.max(rect.width, 1)) * canvasSize.width),
|
|
y: Math.round((y / Math.max(rect.height, 1)) * canvasSize.height)
|
|
};
|
|
}
|
|
|
|
function canvasRectToPixels(region, overlay, size) {
|
|
var rect = getOverlayRect(overlay);
|
|
var canvasSize = size || { width: 1920, height: 1080 };
|
|
return {
|
|
left: (region.x / canvasSize.width) * rect.width,
|
|
top: (region.y / canvasSize.height) * rect.height,
|
|
width: (region.width / canvasSize.width) * rect.width,
|
|
height: (region.height / canvasSize.height) * rect.height
|
|
};
|
|
}
|
|
|
|
window.templateDesignerUtils = {
|
|
escapeHtml: escapeHtml,
|
|
valueOrDefault: valueOrDefault,
|
|
clamp: clamp,
|
|
getRegionName: getRegionName,
|
|
syncRegionIdentity: syncRegionIdentity,
|
|
readCard: readCard,
|
|
writeCard: writeCard,
|
|
clampRegion: clampRegion,
|
|
getOverlayRect: getOverlayRect,
|
|
toCanvasPoint: toCanvasPoint,
|
|
canvasRectToPixels: canvasRectToPixels
|
|
};
|
|
}());
|