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.
607 lines
23 KiB
JavaScript
607 lines
23 KiB
JavaScript
(function () {
|
|
var dataElement = document.getElementById('template-editor-data');
|
|
if (!dataElement) {
|
|
return;
|
|
}
|
|
|
|
var utils = window.templateDesignerUtils || {};
|
|
|
|
var templateData = {};
|
|
try {
|
|
templateData = JSON.parse(dataElement.getAttribute('data-json') || dataElement.textContent || '{}') || {};
|
|
} catch (_error) {
|
|
templateData = {};
|
|
}
|
|
|
|
var existingRegions = Array.isArray(templateData.regions)
|
|
? templateData.regions
|
|
: (Array.isArray(templateData) ? templateData : []);
|
|
var stage = document.getElementById('designer-stage');
|
|
var overlay = document.getElementById('designer-overlay');
|
|
var regionList = document.getElementById('region-list');
|
|
var regionSelect = document.getElementById('region-select');
|
|
var canvasSizeSelect = document.getElementById('canvas-size-select');
|
|
var canvasSizeIdInput = document.getElementById('canvas-size-id');
|
|
var canvasSizeSummary = document.getElementById('canvas-size-summary');
|
|
var canvasWidthInput = document.getElementById('canvas-width');
|
|
var canvasHeightInput = document.getElementById('canvas-height');
|
|
var backgroundInput = document.getElementById('background-image');
|
|
var backgroundColorInput = document.getElementById('background-color');
|
|
var backgroundPreview = document.getElementById('background-preview');
|
|
var backgroundEmpty = document.getElementById('background-empty');
|
|
var removeBackgroundButton = document.getElementById('remove-background-image');
|
|
var removeBackgroundFlag = document.getElementById('remove-background-image-flag');
|
|
var addRegionButton = document.getElementById('add-region-button');
|
|
var regionAddModal = document.getElementById('region-add-modal');
|
|
var regionCardTemplate = document.getElementById('region-card-template');
|
|
var regionsJsonInput = document.getElementById('regions-json');
|
|
var templateForm = document.getElementById('template-form');
|
|
var draft = null;
|
|
var selectedIndex = -1;
|
|
var overlayRenderFrame = 0;
|
|
|
|
function escapeHtml(value) {
|
|
return utils.escapeHtml ? utils.escapeHtml(value) : String(value === undefined || value === null ? '' : value);
|
|
}
|
|
|
|
function valueOrDefault(value, fallback) {
|
|
return utils.valueOrDefault ? utils.valueOrDefault(value, fallback) : (value === undefined || value === null || value === '' ? fallback : value);
|
|
}
|
|
|
|
function getCanvasSize() {
|
|
return {
|
|
width: Math.max(1, Number(canvasWidthInput.value || 1920)),
|
|
height: Math.max(1, Number(canvasHeightInput.value || 1080))
|
|
};
|
|
}
|
|
|
|
function clamp(value, min, max) {
|
|
return utils.clamp ? utils.clamp(value, min, max) : Math.max(min, Math.min(max, value));
|
|
}
|
|
|
|
function getCards() {
|
|
return Array.prototype.slice.call(regionList.querySelectorAll('.region-item'));
|
|
}
|
|
|
|
function cardAt(index) {
|
|
return getCards()[index] || null;
|
|
}
|
|
|
|
function getRegionName(card) {
|
|
return utils.getRegionName ? utils.getRegionName(card) : String(card.querySelector('[name="region_name[]"]').value || '').trim();
|
|
}
|
|
|
|
function syncRegionIdentity(card, value) {
|
|
if (utils.syncRegionIdentity) {
|
|
utils.syncRegionIdentity(card, value);
|
|
return;
|
|
}
|
|
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 validateRegionNames() {
|
|
var cards = getCards();
|
|
var names = {};
|
|
var hasDuplicate = false;
|
|
|
|
cards.forEach(function (card) {
|
|
var input = card.querySelector('[name="region_name[]"]');
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
var normalized = String(input.value || '').trim().toLowerCase();
|
|
if (!normalized) {
|
|
input.setCustomValidity('Region name is required.');
|
|
return;
|
|
}
|
|
|
|
if (!names[normalized]) {
|
|
names[normalized] = [];
|
|
}
|
|
names[normalized].push(input);
|
|
});
|
|
|
|
Object.keys(names).forEach(function (key) {
|
|
var inputs = names[key];
|
|
if (inputs.length > 1) {
|
|
hasDuplicate = true;
|
|
inputs.forEach(function (input) {
|
|
input.setCustomValidity('Region names must be unique on this template.');
|
|
});
|
|
} else {
|
|
inputs[0].setCustomValidity('');
|
|
}
|
|
});
|
|
|
|
return !hasDuplicate;
|
|
}
|
|
|
|
function readCard(card) {
|
|
return utils.readCard ? utils.readCard(card) : {
|
|
region_key: getRegionName(card),
|
|
label: getRegionName(card),
|
|
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 (utils.writeCard) {
|
|
utils.writeCard(card, values);
|
|
return;
|
|
}
|
|
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 getOverlayRect() {
|
|
return utils.getOverlayRect ? utils.getOverlayRect(overlay) : overlay.getBoundingClientRect();
|
|
}
|
|
|
|
function toCanvasPoint(event) {
|
|
return utils.toCanvasPoint ? utils.toCanvasPoint(event, overlay, getCanvasSize()) : {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
}
|
|
|
|
function canvasRectToPixels(region) {
|
|
return utils.canvasRectToPixels ? utils.canvasRectToPixels(region, overlay, getCanvasSize()) : {
|
|
left: 0,
|
|
top: 0,
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
}
|
|
|
|
function updateAspectRatio() {
|
|
var size = getCanvasSize();
|
|
stage.style.aspectRatio = size.width + ' / ' + size.height;
|
|
}
|
|
|
|
function updateCanvasSizeSummary() {
|
|
var option = canvasSizeSelect.options[canvasSizeSelect.selectedIndex];
|
|
canvasSizeSummary.textContent = option ? option.textContent : '';
|
|
}
|
|
|
|
function updateCanvasSizeLock() {
|
|
var lockOnExistingTemplate = canvasSizeSelect.dataset.lockOnExistingTemplate === 'true';
|
|
var locked = lockOnExistingTemplate && getCards().length > 0;
|
|
canvasSizeSelect.disabled = locked;
|
|
canvasSizeSummary.classList.toggle('is-locked', locked);
|
|
}
|
|
|
|
function syncCanvasSizeSelection() {
|
|
var option = canvasSizeSelect.options[canvasSizeSelect.selectedIndex];
|
|
if (!option) {
|
|
return;
|
|
}
|
|
if (canvasSizeIdInput) {
|
|
canvasSizeIdInput.value = option.value;
|
|
}
|
|
canvasWidthInput.value = Math.max(1, Number(option.dataset.width || canvasWidthInput.value || 1920));
|
|
canvasHeightInput.value = Math.max(1, Number(option.dataset.height || canvasHeightInput.value || 1080));
|
|
updateAspectRatio();
|
|
updateCanvasSizeSummary();
|
|
}
|
|
|
|
function updateBackgroundPreview(file) {
|
|
if (!file) {
|
|
return;
|
|
}
|
|
if (removeBackgroundFlag) {
|
|
removeBackgroundFlag.checked = false;
|
|
}
|
|
var reader = new FileReader();
|
|
reader.onload = function () {
|
|
backgroundPreview.src = reader.result;
|
|
backgroundPreview.style.display = 'block';
|
|
backgroundEmpty.style.display = 'none';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
function updateStageBackgroundColor() {
|
|
if (!stage) {
|
|
return;
|
|
}
|
|
stage.style.backgroundColor = backgroundColorInput && backgroundColorInput.value ? backgroundColorInput.value : '#111111';
|
|
}
|
|
|
|
function getRegionChipLabel(regionType) {
|
|
return regionType === 'image' ? 'Image' : regionType === 'webpage' ? 'Webpage' : regionType === 'html' ? 'HTML' : 'Text';
|
|
}
|
|
|
|
function populateRegionCard(card, region) {
|
|
var chip = card.querySelector('[data-region-chip]');
|
|
var title = card.querySelector('[data-region-title]');
|
|
var nameInput = card.querySelector('[name="region_name[]"]');
|
|
var fontFamilyInput = card.querySelector('[name="font_family[]"]');
|
|
var regionTypeInput = card.querySelector('[name="region_type[]"]');
|
|
var regionKeyInput = card.querySelector('[name="region_key[]"]');
|
|
var regionLabelInput = card.querySelector('[name="region_label[]"]');
|
|
|
|
if (title) {
|
|
title.textContent = region.label || region.region_key || 'Region';
|
|
}
|
|
if (chip) {
|
|
chip.textContent = getRegionChipLabel(region.region_type);
|
|
}
|
|
if (nameInput) {
|
|
nameInput.value = region.region_key || region.label || '';
|
|
}
|
|
if (fontFamilyInput) {
|
|
fontFamilyInput.value = region.region_type === 'image' ? '' : (region.font_family || 'Arial');
|
|
}
|
|
if (regionTypeInput) {
|
|
regionTypeInput.value = region.region_type || 'text';
|
|
}
|
|
if (regionKeyInput) {
|
|
regionKeyInput.value = region.region_key || region.label || '';
|
|
}
|
|
if (regionLabelInput) {
|
|
regionLabelInput.value = region.label || region.region_key || '';
|
|
}
|
|
card.querySelector('[name="region_x[]"]').value = valueOrDefault(region.x, 80);
|
|
card.querySelector('[name="region_y[]"]').value = valueOrDefault(region.y, 80);
|
|
card.querySelector('[name="region_z[]"]').value = valueOrDefault(region.z_index, 1);
|
|
card.querySelector('[name="region_width[]"]').value = valueOrDefault(region.width, 300);
|
|
card.querySelector('[name="region_height[]"]').value = valueOrDefault(region.height, 120);
|
|
}
|
|
|
|
function updateRegionLabel(card) {
|
|
var label = getRegionName(card) || 'Region';
|
|
var cards = getCards();
|
|
var index = cards.indexOf(card);
|
|
var title = card.querySelector('.template-field-head strong');
|
|
if (title) {
|
|
title.textContent = label;
|
|
}
|
|
if (index >= 0 && regionSelect.options[index]) {
|
|
regionSelect.options[index].textContent = label;
|
|
}
|
|
}
|
|
|
|
function makeRegionCard(region) {
|
|
var card;
|
|
if (regionCardTemplate && regionCardTemplate.content) {
|
|
card = regionCardTemplate.content.firstElementChild.cloneNode(true);
|
|
} else {
|
|
card = document.createElement('div');
|
|
card.className = 'card card-outline card-secondary admin-form-card region-item mb-3';
|
|
}
|
|
populateRegionCard(card, region);
|
|
var nameInput = card.querySelector('[name="region_name[]"]');
|
|
nameInput.addEventListener('input', function () {
|
|
syncRegionIdentity(card, nameInput.value);
|
|
updateRegionLabel(card);
|
|
validateRegionNames();
|
|
renderRegionSidebar();
|
|
renderOverlay();
|
|
});
|
|
card.addEventListener('click', function (event) {
|
|
if (event.target && event.target.classList && event.target.classList.contains('remove-region')) {
|
|
return;
|
|
}
|
|
setSelected(getCards().indexOf(card));
|
|
});
|
|
card.querySelector('.remove-region').addEventListener('click', function (event) {
|
|
event.preventDefault();
|
|
card.remove();
|
|
if (!getCards().length) {
|
|
selectedIndex = -1;
|
|
} else if (selectedIndex >= getCards().length) {
|
|
selectedIndex = getCards().length - 1;
|
|
}
|
|
renderRegionSidebar();
|
|
renderOverlay();
|
|
});
|
|
return card;
|
|
}
|
|
|
|
function renderRegionList(initialRegions) {
|
|
regionList.innerHTML = '';
|
|
initialRegions.forEach(function (region) {
|
|
regionList.appendChild(makeRegionCard(region));
|
|
});
|
|
}
|
|
|
|
function renderRegionSidebar() {
|
|
var cards = getCards();
|
|
regionSelect.innerHTML = '';
|
|
if (!cards.length) {
|
|
regionSelect.disabled = true;
|
|
regionList.innerHTML = '';
|
|
updateCanvasSizeLock();
|
|
return;
|
|
}
|
|
regionSelect.disabled = false;
|
|
if (selectedIndex < 0 || selectedIndex >= cards.length) {
|
|
selectedIndex = 0;
|
|
}
|
|
cards.forEach(function (card, index) {
|
|
var option = document.createElement('option');
|
|
option.value = String(index);
|
|
option.textContent = getRegionName(card) || ('Region ' + (index + 1));
|
|
if (index === selectedIndex) {
|
|
option.selected = true;
|
|
}
|
|
regionSelect.appendChild(option);
|
|
card.hidden = index !== selectedIndex;
|
|
});
|
|
regionSelect.value = String(selectedIndex);
|
|
updateCanvasSizeLock();
|
|
validateRegionNames();
|
|
}
|
|
|
|
function renderOverlay() {
|
|
var cards = getCards();
|
|
var selectedCard = selectedIndex >= 0 ? cards[selectedIndex] : null;
|
|
var selectedNow = selectedCard ? cards.indexOf(selectedCard) : -1;
|
|
var regions = cards.map(readCard);
|
|
regionsJsonInput.value = JSON.stringify(regions);
|
|
overlay.innerHTML = regions.map(function (region, index) {
|
|
var box = canvasRectToPixels(region);
|
|
var selected = index === selectedNow ? ' selected' : '';
|
|
return '<div class="designer-rect' + selected + '" data-index="' + index + '" style="left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px;"><div class="designer-rect-label">' + escapeHtml(region.label || region.region_key || 'Region') + '</div><span class="resize-handle nw" data-dir="nw"></span><span class="resize-handle ne" data-dir="ne"></span><span class="resize-handle sw" data-dir="sw"></span><span class="resize-handle se" data-dir="se"></span></div>';
|
|
}).join('');
|
|
if (draft) {
|
|
var rect = getOverlayRect();
|
|
var size = getCanvasSize();
|
|
var draftBox = { x: Math.min(draft.start.x, draft.end.x), y: Math.min(draft.start.y, draft.end.y), width: Math.abs(draft.end.x - draft.start.x), height: Math.abs(draft.end.y - draft.start.y) };
|
|
overlay.innerHTML += '<div class="designer-rect designer-draft" style="left:' + ((draftBox.x / size.width) * rect.width) + 'px;top:' + ((draftBox.y / size.height) * rect.height) + 'px;width:' + ((draftBox.width / size.width) * rect.width) + 'px;height:' + ((draftBox.height / size.height) * rect.height) + 'px;"></div>';
|
|
}
|
|
}
|
|
|
|
function requestOverlayRender() {
|
|
if (overlayRenderFrame) {
|
|
return;
|
|
}
|
|
|
|
overlayRenderFrame = window.requestAnimationFrame(function () {
|
|
overlayRenderFrame = 0;
|
|
renderOverlay();
|
|
});
|
|
}
|
|
|
|
function render() {
|
|
updateAspectRatio();
|
|
renderRegionSidebar();
|
|
renderOverlay();
|
|
}
|
|
|
|
function setSelected(index) {
|
|
var cards = getCards();
|
|
if (!cards.length) {
|
|
selectedIndex = -1;
|
|
} else if (index < 0) {
|
|
selectedIndex = 0;
|
|
} else {
|
|
selectedIndex = clamp(index, 0, cards.length - 1);
|
|
}
|
|
renderRegionSidebar();
|
|
renderOverlay();
|
|
}
|
|
|
|
function addRegion(region) {
|
|
var hint = regionList.querySelector('.muted');
|
|
if (hint) {
|
|
hint.remove();
|
|
}
|
|
regionList.appendChild(makeRegionCard(region));
|
|
setSelected(getCards().length - 1);
|
|
}
|
|
|
|
function openAddRegionModal() {
|
|
if (!regionAddModal || !window.bootstrap || !window.bootstrap.Modal) {
|
|
return;
|
|
}
|
|
window.bootstrap.Modal.getOrCreateInstance(regionAddModal).show();
|
|
}
|
|
|
|
function createDefaultRegion(type) {
|
|
var count = getCards().length + 1;
|
|
var name = 'region_' + count;
|
|
return {
|
|
region_key: name,
|
|
label: name,
|
|
region_type: type,
|
|
font_family: type === 'text' || type === 'html' ? 'Arial' : '',
|
|
x: 80,
|
|
y: 80,
|
|
width: type === 'image' || type === 'webpage' || type === 'html' ? 420 : 300,
|
|
height: type === 'image' || type === 'webpage' || type === 'html' ? 240 : 120,
|
|
z_index: 1
|
|
};
|
|
}
|
|
|
|
function clampRegion(region) {
|
|
var size = getCanvasSize();
|
|
var minSize = 12;
|
|
var x = clamp(region.x, 0, size.width - minSize);
|
|
var y = clamp(region.y, 0, size.height - minSize);
|
|
var width = Math.max(minSize, region.width);
|
|
var height = Math.max(minSize, region.height);
|
|
if (x + width > size.width) {
|
|
width = size.width - x;
|
|
}
|
|
if (y + height > size.height) {
|
|
height = size.height - y;
|
|
}
|
|
return { x: Math.round(x), y: Math.round(y), width: Math.round(Math.max(minSize, width)), height: Math.round(Math.max(minSize, height)) };
|
|
}
|
|
|
|
function startDraw(event) {
|
|
var start = toCanvasPoint(event);
|
|
draft = { start: start, end: start };
|
|
renderOverlay();
|
|
function moveHandler(moveEvent) {
|
|
draft.end = toCanvasPoint(moveEvent);
|
|
requestOverlayRender();
|
|
}
|
|
function upHandler(upEvent) {
|
|
draft.end = toCanvasPoint(upEvent);
|
|
var width = Math.abs(draft.end.x - draft.start.x);
|
|
var height = Math.abs(draft.end.y - draft.start.y);
|
|
if (width >= 8 && height >= 8) {
|
|
var region = clampRegion({ x: Math.min(draft.start.x, draft.end.x), y: Math.min(draft.start.y, draft.end.y), width: width, height: height });
|
|
addRegion({ region_key: 'region_' + (getCards().length + 1), label: 'Region ' + (getCards().length + 1), region_type: 'text', x: region.x, y: region.y, width: region.width, height: region.height, z_index: 1 });
|
|
}
|
|
draft = null;
|
|
requestOverlayRender();
|
|
document.removeEventListener('mousemove', moveHandler);
|
|
document.removeEventListener('mouseup', upHandler);
|
|
}
|
|
document.addEventListener('mousemove', moveHandler);
|
|
document.addEventListener('mouseup', upHandler);
|
|
}
|
|
|
|
function startMove(index, event) {
|
|
var startPoint = toCanvasPoint(event);
|
|
var startRegion = readCard(cardAt(index));
|
|
function moveHandler(moveEvent) {
|
|
var currentPoint = toCanvasPoint(moveEvent);
|
|
var dx = currentPoint.x - startPoint.x;
|
|
var dy = currentPoint.y - startPoint.y;
|
|
var next = clampRegion({ x: startRegion.x + dx, y: startRegion.y + dy, width: startRegion.width, height: startRegion.height });
|
|
writeCard(cardAt(index), { x: next.x, y: next.y });
|
|
requestOverlayRender();
|
|
}
|
|
function upHandler() {
|
|
document.removeEventListener('mousemove', moveHandler);
|
|
document.removeEventListener('mouseup', upHandler);
|
|
}
|
|
document.addEventListener('mousemove', moveHandler);
|
|
document.addEventListener('mouseup', upHandler);
|
|
}
|
|
|
|
function resizeFromHandle(index, dir, event) {
|
|
var startPoint = toCanvasPoint(event);
|
|
var startRegion = readCard(cardAt(index));
|
|
function moveHandler(moveEvent) {
|
|
var currentPoint = toCanvasPoint(moveEvent);
|
|
var dx = currentPoint.x - startPoint.x;
|
|
var dy = currentPoint.y - startPoint.y;
|
|
var next = { x: startRegion.x, y: startRegion.y, width: startRegion.width, height: startRegion.height };
|
|
if (dir.indexOf('w') !== -1) { next.x = startRegion.x + dx; next.width = startRegion.width - dx; }
|
|
if (dir.indexOf('e') !== -1) { next.width = startRegion.width + dx; }
|
|
if (dir.indexOf('n') !== -1) { next.y = startRegion.y + dy; next.height = startRegion.height - dy; }
|
|
if (dir.indexOf('s') !== -1) { next.height = startRegion.height + dy; }
|
|
if (next.width < 12) { if (dir.indexOf('w') !== -1) { next.x -= 12 - next.width; } next.width = 12; }
|
|
if (next.height < 12) { if (dir.indexOf('n') !== -1) { next.y -= 12 - next.height; } next.height = 12; }
|
|
next = clampRegion(next);
|
|
writeCard(cardAt(index), { x: next.x, y: next.y, width: next.width, height: next.height });
|
|
requestOverlayRender();
|
|
}
|
|
function upHandler() {
|
|
document.removeEventListener('mousemove', moveHandler);
|
|
document.removeEventListener('mouseup', upHandler);
|
|
}
|
|
document.addEventListener('mousemove', moveHandler);
|
|
document.addEventListener('mouseup', upHandler);
|
|
}
|
|
|
|
if (addRegionButton && regionAddModal) {
|
|
var addRegionTypeButtons = regionAddModal.querySelectorAll('[data-add-region-type]');
|
|
addRegionButton.addEventListener('click', function () {
|
|
openAddRegionModal();
|
|
});
|
|
Array.prototype.forEach.call(addRegionTypeButtons, function (button) {
|
|
button.addEventListener('click', function () {
|
|
var regionType = button.getAttribute('data-add-region-type');
|
|
addRegion(createDefaultRegion(regionType));
|
|
if (window.bootstrap && window.bootstrap.Modal) {
|
|
window.bootstrap.Modal.getOrCreateInstance(regionAddModal).hide();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
backgroundInput.addEventListener('change', function () {
|
|
var file = backgroundInput.files && backgroundInput.files[0];
|
|
if (file) {
|
|
updateBackgroundPreview(file);
|
|
}
|
|
});
|
|
if (removeBackgroundButton && removeBackgroundFlag) {
|
|
removeBackgroundButton.addEventListener('click', function () {
|
|
removeBackgroundFlag.checked = true;
|
|
backgroundInput.value = '';
|
|
backgroundPreview.removeAttribute('src');
|
|
backgroundPreview.style.display = 'none';
|
|
backgroundEmpty.style.display = 'block';
|
|
});
|
|
}
|
|
if (backgroundColorInput) {
|
|
backgroundColorInput.addEventListener('input', updateStageBackgroundColor);
|
|
}
|
|
canvasSizeSelect.addEventListener('change', function () { syncCanvasSizeSelection(); render(); });
|
|
canvasWidthInput.addEventListener('input', render);
|
|
canvasHeightInput.addEventListener('input', render);
|
|
regionSelect.addEventListener('change', function () { setSelected(Number(regionSelect.value || 0)); });
|
|
overlay.addEventListener('mousedown', function (event) {
|
|
var rect = event.target.closest('.designer-rect');
|
|
if (rect) {
|
|
var index = Number(rect.getAttribute('data-index'));
|
|
var handle = event.target.closest('.resize-handle');
|
|
event.preventDefault();
|
|
setSelected(index);
|
|
if (handle) {
|
|
resizeFromHandle(index, handle.getAttribute('data-dir'), event);
|
|
} else {
|
|
startMove(index, event);
|
|
}
|
|
return;
|
|
}
|
|
if (event.target !== overlay && !event.target.classList.contains('designer-overlay')) {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
setSelected(-1);
|
|
startDraw(event);
|
|
});
|
|
if (templateForm) {
|
|
templateForm.addEventListener('formdata', function (event) {
|
|
if (!validateRegionNames()) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
syncCanvasSizeSelection();
|
|
event.formData.set('regions_json', JSON.stringify(getCards().map(readCard)));
|
|
});
|
|
|
|
templateForm.addEventListener('submit', function () {
|
|
if (!validateRegionNames()) {
|
|
return;
|
|
}
|
|
syncCanvasSizeSelection();
|
|
regionsJsonInput.value = JSON.stringify(getCards().map(readCard));
|
|
});
|
|
}
|
|
|
|
renderRegionList(existingRegions);
|
|
syncCanvasSizeSelection();
|
|
updateStageBackgroundColor();
|
|
render();
|
|
})();
|