Release v2.18.0: template default content
This commit is contained in:
1 parent
48e8f3eb4b
commit
24f1603b43
54 files changed
+1421
-151
No files matched your search
@@ -2626,7 +2626,8 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
}
|
||||
|
||||
#slide-layout-modal,
|
||||
#slide-animation-modal {
|
||||
#slide-animation-modal,
|
||||
#region-default-content-modal {
|
||||
left: var(--slide-layout-left, 0px);
|
||||
width: calc(100% - var(--slide-layout-left, 0px));
|
||||
}
|
||||
@@ -2640,6 +2641,189 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
.template-picker-current {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: .5rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-picker-current strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.template-picker-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(14rem, 1fr) minmax(12rem, 20rem);
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.template-picker-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.template-picker-option {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
color: var(--bs-body-color);
|
||||
background: var(--bs-body-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.template-picker-option:hover,
|
||||
.template-picker-option:focus-visible {
|
||||
border-color: var(--bs-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.template-picker-option[aria-pressed="true"] {
|
||||
border: 2px solid var(--bs-primary);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--bs-primary) 22%, transparent);
|
||||
}
|
||||
|
||||
.template-picker-preview {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #111;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.template-picker-preview-none {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--bs-secondary-color);
|
||||
background-image: repeating-linear-gradient(135deg, transparent 0 12px, rgba(128, 128, 128, .12) 12px 13px);
|
||||
}
|
||||
|
||||
.template-picker-preview-background,
|
||||
.template-picker-region-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.template-picker-preview-background {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.template-picker-region-layer {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.template-picker-region {
|
||||
position: absolute;
|
||||
border: 1px solid rgba(255, 255, 255, .82);
|
||||
background: rgba(50, 150, 170, .16);
|
||||
}
|
||||
|
||||
.template-picker-details {
|
||||
display: grid;
|
||||
gap: .2rem;
|
||||
padding: .65rem .75rem;
|
||||
}
|
||||
|
||||
.template-picker-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.template-default-content-dialog {
|
||||
width: calc(100% - 2rem);
|
||||
max-width: none;
|
||||
height: calc(100dvh - 4rem);
|
||||
max-height: 860px;
|
||||
min-height: 0;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
.template-default-content-dialog .modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.slide-animation-body.template-default-content-body {
|
||||
grid-template-columns: minmax(0, 1fr) 21rem;
|
||||
}
|
||||
|
||||
.template-default-content-workspace {
|
||||
container-type: size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.template-default-content-preview-stage {
|
||||
position: relative;
|
||||
width: min(100%, calc(100cqh * var(--template-preview-ratio, 1.777778)));
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
outline: 1px solid var(--bs-border-color);
|
||||
background: #111;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.template-default-content-preview-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.template-default-content-preview-background,
|
||||
.template-default-content-preview-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.template-default-content-preview-background {
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.template-default-content-preview-label {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
max-width: calc(100% - 8px);
|
||||
overflow: hidden;
|
||||
padding: 2px 4px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, .65);
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.template-default-content-properties {
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.template-default-content-properties .template-field-card {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.slide-layout-dialog .modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -2831,12 +3015,26 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.template-picker-toolbar {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.template-picker-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
}
|
||||
|
||||
.slide-layout-dialog {
|
||||
width: calc(100% - 1rem);
|
||||
height: calc(100dvh - 2rem);
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.template-default-content-dialog {
|
||||
width: calc(100% - 1rem);
|
||||
height: calc(100dvh - 2rem);
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.slide-layout-body {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: minmax(14rem, 1fr) auto;
|
||||
@@ -2846,6 +3044,16 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
.slide-layout-properties {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.slide-animation-body.template-default-content-body {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto minmax(14rem, 1fr);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.template-default-content-properties {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slide preview canvas and popup */
|
||||
@@ -4199,6 +4407,10 @@ html[data-bs-theme='dark'] .template-field-card .tox .tox-collection {
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
|
||||
.slide-schedule-card > form > .card-footer::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.slide-schedule-content .schedule-rule-card .card-header {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
}
|
||||
|
||||
var activeButton = null;
|
||||
var nestedParentModal = null;
|
||||
var nestedParentFocusTrap = null;
|
||||
var nestedReturnFocus = null;
|
||||
var items = Array.prototype.slice.call(modal.querySelectorAll('[data-media-picker-item]'));
|
||||
var grid = modal.querySelector('[data-media-picker-grid]');
|
||||
var search = modal.querySelector('[data-media-picker-search]');
|
||||
@@ -90,6 +93,27 @@
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
var openModals = Array.prototype.slice.call(document.querySelectorAll('.modal.show'))
|
||||
.filter(function (openModal) { return openModal !== modal; });
|
||||
nestedParentModal = openModals.length ? openModals[openModals.length - 1] : null;
|
||||
if (nestedParentModal) {
|
||||
nestedReturnFocus = activeButton;
|
||||
var parentInstance = window.pulseModal && typeof window.pulseModal.getOrCreate === 'function'
|
||||
? window.pulseModal.getOrCreate(nestedParentModal)
|
||||
: window.bootstrap && window.bootstrap.Modal
|
||||
? window.bootstrap.Modal.getOrCreateInstance(nestedParentModal)
|
||||
: null;
|
||||
nestedParentFocusTrap = parentInstance && parentInstance._focustrap || null;
|
||||
if (nestedParentFocusTrap && typeof nestedParentFocusTrap.deactivate === 'function') nestedParentFocusTrap.deactivate();
|
||||
var parentZIndex = Number.parseInt(window.getComputedStyle(nestedParentModal).zIndex, 10) || 1055;
|
||||
var pickerZIndex = parentZIndex + 20;
|
||||
modal.style.zIndex = String(pickerZIndex);
|
||||
modal.addEventListener('shown.bs.modal', function () {
|
||||
var backdrops = document.querySelectorAll('.modal-backdrop');
|
||||
var backdrop = backdrops[backdrops.length - 1];
|
||||
if (backdrop) backdrop.style.zIndex = String(pickerZIndex - 5);
|
||||
}, { once: true });
|
||||
}
|
||||
if (window.pulseModal && typeof window.pulseModal.show === 'function') {
|
||||
window.pulseModal.show(modal);
|
||||
} else if (window.bootstrap && window.bootstrap.Modal) {
|
||||
@@ -431,5 +455,13 @@
|
||||
currentPath = '';
|
||||
pendingPath = '';
|
||||
pendingType = '';
|
||||
modal.style.removeProperty('z-index');
|
||||
if (nestedParentModal && nestedParentModal.classList.contains('show')) {
|
||||
if (nestedParentFocusTrap && typeof nestedParentFocusTrap.activate === 'function') nestedParentFocusTrap.activate();
|
||||
if (nestedReturnFocus && document.contains(nestedReturnFocus)) nestedReturnFocus.focus();
|
||||
}
|
||||
nestedParentModal = null;
|
||||
nestedParentFocusTrap = null;
|
||||
nestedReturnFocus = null;
|
||||
});
|
||||
})();
|
||||
@@ -1127,7 +1127,8 @@
|
||||
'<div class="d-flex align-items-center gap-4 ms-auto flex-shrink-0">' +
|
||||
'<button type="button" class="btn btn-sm btn-outline-danger" data-remove-schedule-rule data-confirm-message="Remove this rule?">Remove</button>' +
|
||||
'<button type="button" class="btn btn-tool p-0 text-body-secondary" data-lte-toggle="card-collapse" aria-label="Collapse rule ' + String(ruleIndex) + '" title="Collapse rule ' + String(ruleIndex) + '">' +
|
||||
'<i class="bi ' + (isCollapsed ? 'bi-plus-lg' : 'bi-dash-lg') + '" aria-hidden="true" data-schedule-rule-collapse-icon></i>' +
|
||||
'<i class="bi bi-dash-lg" aria-hidden="true" data-lte-icon="collapse"></i>' +
|
||||
'<i class="bi bi-plus-lg" aria-hidden="true" data-lte-icon="expand"></i>' +
|
||||
'<span class="visually-hidden">Collapse rule ' + String(ruleIndex) + '</span>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
@@ -1330,13 +1331,8 @@
|
||||
return card ? card.querySelector('[data-schedule-rule-collapse], [data-lte-toggle="card-collapse"]') : null;
|
||||
}
|
||||
|
||||
function getCollapseRuleIcon(card) {
|
||||
return card ? card.querySelector('[data-schedule-rule-collapse-icon]') : null;
|
||||
}
|
||||
|
||||
function syncRuleCollapseButtonState(card) {
|
||||
var button = getCollapseRuleButton(card);
|
||||
var icon = getCollapseRuleIcon(card);
|
||||
var numberNode = getRuleNumberNode(card);
|
||||
var ruleIndex = numberNode ? String(numberNode.textContent || '') : '';
|
||||
var isCollapsed = Boolean(card && card.classList && card.classList.contains('collapsed-card'));
|
||||
@@ -1350,11 +1346,6 @@
|
||||
button.setAttribute('aria-label', label);
|
||||
button.setAttribute('title', label);
|
||||
|
||||
if (icon) {
|
||||
icon.classList.remove('bi-dash-lg', 'bi-plus-lg');
|
||||
icon.classList.add(isCollapsed ? 'bi-plus-lg' : 'bi-dash-lg');
|
||||
}
|
||||
|
||||
var visibleLabel = button.querySelector('.visually-hidden');
|
||||
if (visibleLabel) {
|
||||
visibleLabel.textContent = label;
|
||||
@@ -1742,15 +1733,23 @@
|
||||
handleCardChange(card);
|
||||
});
|
||||
|
||||
function handleRuleCollapseComplete(event) {
|
||||
var card = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-card]') : null;
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
syncRuleCollapseButtonState(card);
|
||||
persistScheduleDraft();
|
||||
}
|
||||
|
||||
scheduleRuleList.addEventListener('collapsed.lte.card-widget', handleRuleCollapseComplete);
|
||||
scheduleRuleList.addEventListener('expanded.lte.card-widget', handleRuleCollapseComplete);
|
||||
|
||||
scheduleRuleList.addEventListener('click', function (event) {
|
||||
var removeButton = event.target && event.target.closest ? event.target.closest('[data-remove-schedule-rule]') : null;
|
||||
var collapseButton = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-collapse], [data-lte-toggle="card-collapse"]') : null;
|
||||
|
||||
if (collapseButton) {
|
||||
window.setTimeout(function () {
|
||||
syncRuleCollapseButtonState(collapseButton.closest('[data-schedule-rule-card]'));
|
||||
persistScheduleDraft();
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ export function createSlideAnimationEditor(options) {
|
||||
var nextSibling = null;
|
||||
|
||||
function template() { return options.getTemplate(); }
|
||||
function regions() { return template() && template().regions || []; }
|
||||
function allRegions() { return template() && template().regions || []; }
|
||||
function regions() { return allRegions().filter(function (region) { return !(region.read_only === true || Number(region.read_only) === 1); }); }
|
||||
function selectedKey() { return regionSelect.value; }
|
||||
function getConfig(regionKey) {
|
||||
var region = regions().find(function (item) { return item.region_key === regionKey; });
|
||||
var region = allRegions().find(function (item) { return item.region_key === regionKey; });
|
||||
return Object.prototype.hasOwnProperty.call(draft, regionKey)
|
||||
? draft[regionKey]
|
||||
: region && region.animation_json || '{}';
|
||||
@@ -49,7 +50,17 @@ export function createSlideAnimationEditor(options) {
|
||||
function renderRegion() {
|
||||
var key = selectedKey();
|
||||
var region = regions().find(function (item) { return item.region_key === key; });
|
||||
if (!region) return;
|
||||
if (!region) {
|
||||
card.querySelectorAll('input, select, button, textarea').forEach(function (element) {
|
||||
element.disabled = true;
|
||||
});
|
||||
updatePreviewButtons();
|
||||
updateResetButtons();
|
||||
return;
|
||||
}
|
||||
card.querySelectorAll('input, select, button, textarea').forEach(function (element) {
|
||||
element.disabled = false;
|
||||
});
|
||||
controls.write(card, getConfig(key));
|
||||
updatePreviewButtons();
|
||||
updateResetButtons();
|
||||
@@ -87,8 +98,8 @@ export function createSlideAnimationEditor(options) {
|
||||
return;
|
||||
}
|
||||
saveStatus.textContent = 'Saving shared animations...';
|
||||
var animations = regions().map(function (region) {
|
||||
return { region_key: region.region_key, animation_json: getConfig(region.region_key) };
|
||||
var animations = allRegions().map(function (region) {
|
||||
return { region_key: region.region_key, animation_json: region.read_only === true || Number(region.read_only) === 1 ? region.animation_json || '{}' : getConfig(region.region_key) };
|
||||
});
|
||||
var result = await readResponse(await fetch(url, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
@@ -138,7 +149,7 @@ export function createSlideAnimationEditor(options) {
|
||||
var modeKeys = { intro: 'intro', outro: 'outro', loop: 'loop' };
|
||||
previewButtons.forEach(function (button) {
|
||||
var mode = button.dataset.slideAnimationPreview;
|
||||
var available = regions().some(function (region) {
|
||||
var available = allRegions().some(function (region) {
|
||||
var config = window.templateDesignerUtils.normalizeAnimationConfig(getConfig(region.region_key));
|
||||
return window.templateAnimationPresets.normalize(config[modeKeys[mode]].preset) !== 'none';
|
||||
});
|
||||
@@ -184,7 +195,7 @@ export function createSlideAnimationEditor(options) {
|
||||
});
|
||||
|
||||
openButton.addEventListener('click', function () {
|
||||
if (!regions().length || !window.pulseModal) return;
|
||||
if (!allRegions().length || !window.pulseModal) return;
|
||||
regionSelect.replaceChildren();
|
||||
regions().forEach(function (region) {
|
||||
var option = document.createElement('option');
|
||||
@@ -296,5 +307,5 @@ export function createSlideAnimationEditor(options) {
|
||||
window.pulseModal.hide(modal);
|
||||
});
|
||||
|
||||
return { refresh: function () { openButton.disabled = active || !regions().length; } };
|
||||
return { refresh: function () { openButton.disabled = active || !allRegions().length; } };
|
||||
}
|
||||
@@ -28,8 +28,16 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
window.pulseWeatherIconSets = Array.isArray(slideEditorData.weatherIconSets) ? slideEditorData.weatherIconSets : [];
|
||||
var fontStylesheetHref = String(slideEditorData.fontStylesheetHref || '').trim();
|
||||
var existingTemplateId = slideEditorData.existingTemplateId !== undefined ? slideEditorData.existingTemplateId : null;
|
||||
var existingContent = slideEditorData.existingContent || {};
|
||||
var savedExistingContent = slideEditorData.existingContent || {};
|
||||
var existingContent = Object.assign({}, savedExistingContent);
|
||||
var templateSelect = document.getElementById('template-select');
|
||||
var templatePickerModal = document.getElementById('template-picker-modal');
|
||||
var templatePickerOpenButton = document.getElementById('template-picker-open');
|
||||
var templatePickerSelectedName = document.getElementById('template-picker-selected-name');
|
||||
var templatePickerSearch = document.getElementById('template-picker-search');
|
||||
var templatePickerCanvasSize = document.getElementById('template-picker-canvas-size');
|
||||
var templatePickerGrid = document.getElementById('template-picker-grid');
|
||||
var templatePickerEmpty = document.getElementById('template-picker-empty');
|
||||
var templateFields = document.getElementById('template-fields');
|
||||
var slideForm = document.getElementById('slide-form');
|
||||
var slideEditorShell = document.querySelector('.slide-editor-shell');
|
||||
@@ -113,6 +121,134 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
return templates.find(function (item) { return Number(item.id) === Number(id); }) || null;
|
||||
}
|
||||
|
||||
function getTemplateCanvasSignature(template) {
|
||||
return (Number(template && template.canvas_size_width) || 1920) + 'x' + (Number(template && template.canvas_size_height) || 1080);
|
||||
}
|
||||
|
||||
function populateTemplateCanvasSizes() {
|
||||
if (!templatePickerCanvasSize) return;
|
||||
var canvasSizes = new Map();
|
||||
templates.forEach(function (template) {
|
||||
var width = Number(template && template.canvas_size_width) || 1920;
|
||||
var height = Number(template && template.canvas_size_height) || 1080;
|
||||
var signature = width + 'x' + height;
|
||||
if (!canvasSizes.has(signature)) {
|
||||
var name = String(template && template.canvas_size_name || '').trim();
|
||||
canvasSizes.set(signature, { signature: signature, label: (name ? name + ' (' : '') + width + ' x ' + height + (name ? ')' : '') });
|
||||
}
|
||||
});
|
||||
Array.from(canvasSizes.values()).sort(function (left, right) {
|
||||
return left.label.localeCompare(right.label, undefined, { numeric: true, sensitivity: 'base' });
|
||||
}).forEach(function (size) {
|
||||
var option = document.createElement('option');
|
||||
option.value = size.signature;
|
||||
option.textContent = size.label;
|
||||
templatePickerCanvasSize.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function createTemplatePickerCard(template) {
|
||||
var card = document.createElement('button');
|
||||
card.type = 'button';
|
||||
card.className = 'template-picker-option';
|
||||
card.setAttribute('role', 'option');
|
||||
card.dataset.templatePickerId = String(template.id);
|
||||
card.setAttribute('aria-pressed', String(Number(template.id) === Number(templateSelect.value)));
|
||||
|
||||
var width = Math.max(1, Number(template.canvas_size_width) || 1920);
|
||||
var height = Math.max(1, Number(template.canvas_size_height) || 1080);
|
||||
var preview = document.createElement('div');
|
||||
preview.className = 'template-picker-preview';
|
||||
preview.style.aspectRatio = width + ' / ' + height;
|
||||
preview.style.backgroundColor = /^#[0-9a-f]{3,8}$/i.test(String(template.background_color || '')) ? template.background_color : '#111111';
|
||||
if (template.background_image_path) {
|
||||
var backgroundImage = document.createElement('img');
|
||||
backgroundImage.className = 'template-picker-preview-background';
|
||||
backgroundImage.src = String(template.background_image_path);
|
||||
backgroundImage.alt = '';
|
||||
backgroundImage.loading = 'lazy';
|
||||
preview.appendChild(backgroundImage);
|
||||
}
|
||||
var regionLayer = document.createElement('span');
|
||||
regionLayer.className = 'template-picker-region-layer';
|
||||
(Array.isArray(template.regions) ? template.regions : []).forEach(function (region) {
|
||||
var box = document.createElement('span');
|
||||
box.className = 'template-picker-region';
|
||||
box.title = String(region.label || region.region_key || 'Region');
|
||||
box.style.left = Math.max(0, Number(region.x) || 0) / width * 100 + '%';
|
||||
box.style.top = Math.max(0, Number(region.y) || 0) / height * 100 + '%';
|
||||
box.style.width = Math.min(100, Math.max(0, Number(region.width) || 0) / width * 100) + '%';
|
||||
box.style.height = Math.min(100, Math.max(0, Number(region.height) || 0) / height * 100) + '%';
|
||||
regionLayer.appendChild(box);
|
||||
});
|
||||
preview.appendChild(regionLayer);
|
||||
card.appendChild(preview);
|
||||
|
||||
var details = document.createElement('span');
|
||||
details.className = 'template-picker-details';
|
||||
var name = document.createElement('strong');
|
||||
name.className = 'template-picker-name';
|
||||
name.textContent = String(template.name || 'Untitled template');
|
||||
var size = document.createElement('small');
|
||||
size.className = 'text-body-secondary';
|
||||
size.textContent = width + ' x ' + height;
|
||||
details.appendChild(name);
|
||||
details.appendChild(size);
|
||||
card.appendChild(details);
|
||||
card.addEventListener('click', function () { selectTemplate(template.id); });
|
||||
return card;
|
||||
}
|
||||
|
||||
function renderTemplatePicker() {
|
||||
if (!templatePickerGrid) return;
|
||||
var query = String(templatePickerSearch && templatePickerSearch.value || '').trim().toLowerCase();
|
||||
var selectedCanvasSize = String(templatePickerCanvasSize && templatePickerCanvasSize.value || '');
|
||||
var matches = templates.filter(function (template) {
|
||||
var canvasMatches = !selectedCanvasSize || getTemplateCanvasSignature(template) === selectedCanvasSize;
|
||||
return canvasMatches && String(template.name || '').toLowerCase().includes(query);
|
||||
});
|
||||
templatePickerGrid.replaceChildren();
|
||||
matches.forEach(function (template) { templatePickerGrid.appendChild(createTemplatePickerCard(template)); });
|
||||
if (templatePickerEmpty) templatePickerEmpty.hidden = matches.length > 0;
|
||||
}
|
||||
|
||||
function syncTemplatePickerSummary() {
|
||||
if (!templateSelect) return;
|
||||
var selectedTemplate = getSelectedTemplate();
|
||||
if (templatePickerSelectedName) templatePickerSelectedName.textContent = selectedTemplate ? String(selectedTemplate.name || 'Untitled template') : 'No template selected';
|
||||
if (templatePickerOpenButton) {
|
||||
templatePickerOpenButton.disabled = Boolean(templateSelect.disabled);
|
||||
templatePickerOpenButton.title = templateSelect.disabled ? templateSelect.title || 'Template is locked after you edit its content.' : 'Choose a template';
|
||||
}
|
||||
}
|
||||
|
||||
function selectTemplate(templateId) {
|
||||
if (!templateSelect || templateSelect.disabled) return;
|
||||
templateSelect.value = templateId === null || templateId === undefined ? '' : String(templateId);
|
||||
templateSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
if (window.pulseModal && templatePickerModal) window.pulseModal.hide(templatePickerModal);
|
||||
}
|
||||
|
||||
if (templatePickerOpenButton && templatePickerModal) {
|
||||
populateTemplateCanvasSizes();
|
||||
templatePickerOpenButton.addEventListener('click', function () {
|
||||
if (templateSelect.disabled) return;
|
||||
renderTemplatePicker();
|
||||
if (window.pulseModal) window.pulseModal.show(templatePickerModal);
|
||||
});
|
||||
templatePickerModal.addEventListener('shown.bs.modal', function () {
|
||||
if (templatePickerSearch) templatePickerSearch.focus();
|
||||
});
|
||||
templatePickerModal.addEventListener('hidden.bs.modal', function () {
|
||||
templatePickerOpenButton.focus();
|
||||
});
|
||||
if (templatePickerSearch) templatePickerSearch.addEventListener('input', renderTemplatePicker);
|
||||
if (templatePickerCanvasSize) templatePickerCanvasSize.addEventListener('change', renderTemplatePicker);
|
||||
if (window.MutationObserver) {
|
||||
new MutationObserver(syncTemplatePickerSummary).observe(templateSelect, { attributes: true, attributeFilter: ['disabled', 'title'] });
|
||||
}
|
||||
}
|
||||
|
||||
function applyBackdropStyle(element, backgroundColor, backgroundImagePath, backgroundGradient) {
|
||||
if (!element) {
|
||||
return;
|
||||
@@ -249,7 +385,24 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
}
|
||||
|
||||
function hasExistingSlideContent() {
|
||||
return hasMeaningfulValue(existingContent);
|
||||
return hasMeaningfulValue(savedExistingContent);
|
||||
}
|
||||
|
||||
function applyTemplateDefaults(template) {
|
||||
Object.keys(existingContent).forEach(function (key) { delete existingContent[key]; });
|
||||
if (template && Number(template.id) === Number(existingTemplateId)) {
|
||||
Object.assign(existingContent, savedExistingContent);
|
||||
}
|
||||
(template && Array.isArray(template.regions) ? template.regions : []).forEach(function (region) {
|
||||
var content = region.default_content;
|
||||
if (typeof content === 'string') {
|
||||
try { content = JSON.parse(content); } catch (_error) { content = {}; }
|
||||
}
|
||||
if (!content || typeof content !== 'object' || Array.isArray(content)) content = {};
|
||||
if (region.read_only === true || Number(region.read_only) === 1 || !Object.prototype.hasOwnProperty.call(existingContent, region.region_key)) {
|
||||
existingContent[region.region_key] = content;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearImagePreviewUrls() {
|
||||
@@ -557,6 +710,9 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
}
|
||||
|
||||
function openPreviewPopup() {
|
||||
if (!getSelectedTemplate()) {
|
||||
return;
|
||||
}
|
||||
var popupWidth = 1280;
|
||||
var popupHeight = 900;
|
||||
var left = Math.max(0, Math.round((window.screen.width - popupWidth) / 2));
|
||||
@@ -625,6 +781,7 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
|
||||
function renderPreview() {
|
||||
var template = getSelectedTemplate();
|
||||
if (openPreviewPopupButton) openPreviewPopupButton.disabled = !template;
|
||||
if (!template) {
|
||||
slidePreviewMeta.textContent = '--';
|
||||
slidePreviewBackground.style.display = 'none';
|
||||
@@ -758,16 +915,18 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
clearImagePreviewUrls();
|
||||
slideFormEditorController.destroyEditors();
|
||||
var template = getTemplateById(templateSelect.value);
|
||||
slideLayoutEditor.reset(Number(templateSelect.value) === Number(existingTemplateId) ? existingContent : {});
|
||||
applyTemplateDefaults(template);
|
||||
slideLayoutEditor.reset(Number(templateSelect.value) === Number(existingTemplateId) ? savedExistingContent : {});
|
||||
if (!template) {
|
||||
templateFields.innerHTML = '';
|
||||
requestPreviewRender();
|
||||
return;
|
||||
}
|
||||
|
||||
templateFields.innerHTML = template.regions.length ? template.regions.map(function (region) {
|
||||
var editableRegions = template.regions.filter(function (region) { return !(region.read_only === true || Number(region.read_only) === 1); });
|
||||
templateFields.innerHTML = editableRegions.length ? editableRegions.map(function (region) {
|
||||
return getRegionEditorCardHtml(region) || '<div class="muted">Region editor unavailable.</div>';
|
||||
}).join('') : '<div class="muted">This template has no regions.</div>';
|
||||
}).join('') : '<div class="muted">This template has no editable regions.</div>';
|
||||
if (typeof window.initJsonTogglePanels === 'function') {
|
||||
window.initJsonTogglePanels(templateFields);
|
||||
}
|
||||
@@ -978,7 +1137,10 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
});
|
||||
}
|
||||
|
||||
templateSelect.addEventListener('change', renderTemplate);
|
||||
templateSelect.addEventListener('change', function () {
|
||||
renderTemplate();
|
||||
syncTemplatePickerSummary();
|
||||
});
|
||||
templateFields.addEventListener('change', function (event) {
|
||||
var webpageInput = event.target && typeof event.target.matches === 'function' && event.target.matches('input[type="url"][name^="region_webpage_"]');
|
||||
if (webpageInput) {
|
||||
@@ -1062,6 +1224,7 @@ import { createSlideAnimationEditor } from '/assets/js/slides/slide-animation-ed
|
||||
templateSelect.value = String(existingTemplateId);
|
||||
}
|
||||
renderTemplate();
|
||||
syncTemplatePickerSummary();
|
||||
if (window.ResizeObserver && slidePreviewStage) {
|
||||
var previewResizeObserver = new ResizeObserver(function () {
|
||||
requestPreviewRender();
|
||||
|
||||
@@ -69,7 +69,7 @@ export function createSlideLayoutEditor(options) {
|
||||
saveStatus.textContent = 'Saving shared layout...';
|
||||
var result = await readResponse(await fetch(url, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ slideId: options.slideId || null, baseline: sharedBaseline, regions: helpers.getTemplateLayoutSnapshot(Object.assign({}, template(), { regions: regions().map(effective) })).regions })
|
||||
body: JSON.stringify({ slideId: options.slideId || null, baseline: sharedBaseline, regions: helpers.getTemplateLayoutSnapshot(Object.assign({}, template(), { regions: allRegions().map(effective) })).regions })
|
||||
}));
|
||||
options.updateTemplate(result.template);
|
||||
var inheritedLayouts = Object.create(null);
|
||||
@@ -109,7 +109,8 @@ export function createSlideLayoutEditor(options) {
|
||||
var current = template();
|
||||
return { width: Number(current && current.canvas_size_width) || 1920, height: Number(current && current.canvas_size_height) || 1080 };
|
||||
}
|
||||
function regions() { return template() && template().regions || []; }
|
||||
function allRegions() { return template() && template().regions || []; }
|
||||
function regions() { return allRegions().filter(function (region) { return !(region.read_only === true || Number(region.read_only) === 1); }); }
|
||||
function selected() {
|
||||
return regions().find(function (region) { return region.region_key === selectedKey; });
|
||||
}
|
||||
@@ -147,7 +148,7 @@ export function createSlideLayoutEditor(options) {
|
||||
}
|
||||
function render() {
|
||||
openButton.disabled = active || !regions().length;
|
||||
animationButton.disabled = active || !regions().length;
|
||||
animationButton.disabled = active || !allRegions().length;
|
||||
if (!active || sharedSaving) return;
|
||||
if (!selected()) selectedKey = regions().length ? regions()[0].region_key : '';
|
||||
var region = selected();
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import { createSlideFormEditorController } from '/assets/js/slides/slide-form-editor.js';
|
||||
import { createSlideFormRegionHelpers } from '/assets/js/slides/slide-form-regions.js';
|
||||
|
||||
(function () {
|
||||
var host = document.getElementById('region-default-content-editor-host');
|
||||
if (!host) return;
|
||||
var previewStage = document.getElementById('region-default-content-preview-stage');
|
||||
var previewWorkspace = document.querySelector('.template-default-content-workspace');
|
||||
var previewFrame = document.getElementById('region-default-content-preview-frame');
|
||||
var previewCanvas = document.getElementById('region-default-content-preview-canvas');
|
||||
var previewBackground = document.getElementById('region-default-content-preview-background');
|
||||
var previewEmpty = document.getElementById('region-default-content-preview-empty');
|
||||
var templateRegions = [];
|
||||
var previewResizeObserver = null;
|
||||
|
||||
var editorData = {};
|
||||
try {
|
||||
var dataElement = document.getElementById('template-region-editor-data');
|
||||
editorData = JSON.parse((dataElement && dataElement.textContent) || '{}') || {};
|
||||
} catch (_error) {
|
||||
editorData = {};
|
||||
}
|
||||
try {
|
||||
var templateDataElement = document.getElementById('template-editor-data');
|
||||
templateRegions = JSON.parse((templateDataElement && templateDataElement.textContent) || '[]') || [];
|
||||
if (!Array.isArray(templateRegions)) templateRegions = [];
|
||||
} catch (_error) {
|
||||
templateRegions = [];
|
||||
}
|
||||
window.pulseWeatherIconSets = Array.isArray(editorData.weatherIconSets) ? editorData.weatherIconSets : [];
|
||||
|
||||
var existingContent = {};
|
||||
var activeRegion = null;
|
||||
var activeContent = {};
|
||||
var editorId = 900000000;
|
||||
var getRegionTypeModule = function (type) {
|
||||
return window.pulseRegionTypes && typeof window.pulseRegionTypes.get === 'function'
|
||||
? window.pulseRegionTypes.get(type)
|
||||
: null;
|
||||
};
|
||||
var helpers = createSlideFormRegionHelpers({
|
||||
templateFields: host,
|
||||
existingContent: existingContent,
|
||||
getRegionTypeModule: getRegionTypeModule,
|
||||
defaultFontSize: 32,
|
||||
fontStylesheetHref: editorData.fontLibrary && editorData.fontLibrary.stylesheetHref || '',
|
||||
fontFamilyFormats: editorData.fontLibrary && editorData.fontLibrary.fontFamilyFormats || '',
|
||||
rssFeeds: editorData.rssFeeds || [],
|
||||
apiSources: editorData.apiSources || [],
|
||||
timetableGroups: editorData.timetableGroups || [],
|
||||
weatherLocations: editorData.weatherLocations || [],
|
||||
mediaAssets: editorData.mediaAssets || [],
|
||||
uploadMaxBytes: editorData.uploadLimits && editorData.uploadLimits.imageMaxBytes,
|
||||
uploadVideoMaxBytes: editorData.uploadLimits && editorData.uploadLimits.videoMaxBytes,
|
||||
uploadMaxLabel: editorData.uploadMaxLabel,
|
||||
uploadVideoMaxLabel: editorData.uploadVideoMaxLabel,
|
||||
uploadMimeTypes: editorData.uploadLimits && editorData.uploadLimits.allowedMimeTypes || []
|
||||
});
|
||||
var editor = createSlideFormEditorController({
|
||||
templateFields: host,
|
||||
getRegionTypeModule: getRegionTypeModule,
|
||||
defaultFontSize: 32,
|
||||
fontStylesheetHref: editorData.fontLibrary && editorData.fontLibrary.stylesheetHref || '',
|
||||
fontFamilyFormats: editorData.fontLibrary && editorData.fontLibrary.fontFamilyFormats || '',
|
||||
imageUploadMaxBytes: editorData.uploadLimits && editorData.uploadLimits.wysiwygImageMaxBytes,
|
||||
imageUploadContext: 'wysiwyg',
|
||||
markFormDirty: function () {},
|
||||
requestPreviewRender: renderPreview,
|
||||
getEditorBackgroundColor: function () {
|
||||
var backgroundColorInput = document.getElementById('background-color');
|
||||
return String(backgroundColorInput && backgroundColorInput.value || '#111111');
|
||||
}
|
||||
});
|
||||
|
||||
function cloneContent(value) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function normalizeDefaultContent(value) {
|
||||
var content = value;
|
||||
if (typeof content === 'string') {
|
||||
try { content = JSON.parse(content); } catch (_error) { content = {}; }
|
||||
}
|
||||
return cloneContent(content);
|
||||
}
|
||||
|
||||
function cleanContent(value) {
|
||||
var result = Object.assign({}, activeContent);
|
||||
var transientFields = new Set([
|
||||
'existingContent', 'sources', 'feeds', 'timetableGroups', 'weatherLocations', 'locations',
|
||||
'style', 'sampleDataPreview', 'placeholderChips', 'placeholderFields', 'sourceOptions', 'feedOptions'
|
||||
]);
|
||||
Object.keys(value && typeof value === 'object' ? value : {}).forEach(function (key) {
|
||||
if (!transientFields.has(key)) result[key] = value[key];
|
||||
});
|
||||
if (value && value.style && typeof value.style === 'object') {
|
||||
result.font_family = value.style.font_family || result.font_family;
|
||||
result.font_size = value.style.font_size || result.font_size;
|
||||
result.font_color = value.style.font_color || result.font_color;
|
||||
}
|
||||
result.type = activeRegion.region_type;
|
||||
return result;
|
||||
}
|
||||
|
||||
function renderPreview() {
|
||||
if (!activeRegion || !previewStage || !previewFrame || !previewCanvas) return;
|
||||
var card = host.querySelector('[data-region-id="' + activeRegion.id + '"]');
|
||||
if (!card) return;
|
||||
var sourceWidth = Math.max(1, Number((document.getElementById('canvas-width') || {}).value) || 1920);
|
||||
var sourceHeight = Math.max(1, Number((document.getElementById('canvas-height') || {}).value) || 1080);
|
||||
var workspaceBounds = (previewWorkspace || previewStage).getBoundingClientRect();
|
||||
if (workspaceBounds.width < 2 || workspaceBounds.height < 2) return;
|
||||
var scale = Math.min((workspaceBounds.width - 2) / sourceWidth, (workspaceBounds.height - 2) / sourceHeight);
|
||||
if (!Number.isFinite(scale) || scale <= 0) return;
|
||||
var previewWidth = Math.max(1, Math.round(sourceWidth * scale));
|
||||
var previewHeight = Math.max(1, Math.round(sourceHeight * scale));
|
||||
previewStage.style.setProperty('--template-preview-ratio', String(sourceWidth / sourceHeight));
|
||||
previewStage.style.aspectRatio = sourceWidth + ' / ' + sourceHeight;
|
||||
previewStage.style.width = previewWidth + 'px';
|
||||
previewStage.style.height = previewHeight + 'px';
|
||||
previewCanvas.style.width = sourceWidth + 'px';
|
||||
previewCanvas.style.height = sourceHeight + 'px';
|
||||
previewCanvas.style.transform = 'scale(' + scale + ')';
|
||||
previewCanvas.style.transformOrigin = 'top left';
|
||||
var backgroundColorInput = document.getElementById('background-color');
|
||||
previewCanvas.style.backgroundColor = backgroundColorInput && backgroundColorInput.value || '#111111';
|
||||
var gradientInput = document.getElementById('background-gradient');
|
||||
try {
|
||||
var gradient = JSON.parse(gradientInput && gradientInput.value || 'null');
|
||||
if (gradient && Array.isArray(gradient.stops) && gradient.stops.length > 1) {
|
||||
previewCanvas.style.backgroundImage = 'linear-gradient(' + (Number(gradient.angle) || 90) + 'deg,' + gradient.stops.map(function (stop) {
|
||||
return String(stop.color || '#111111') + ' ' + Math.max(0, Math.min(100, Number(stop.position) || 0)) + '%';
|
||||
}).join(',') + ')';
|
||||
} else {
|
||||
previewCanvas.style.backgroundImage = '';
|
||||
}
|
||||
} catch (_error) {
|
||||
previewCanvas.style.backgroundImage = '';
|
||||
}
|
||||
var imagePathInput = document.getElementById('existing-background-image-path');
|
||||
var backgroundPreview = document.getElementById('background-preview');
|
||||
var imagePath = String(backgroundPreview && backgroundPreview.src && backgroundPreview.style.display !== 'none'
|
||||
? backgroundPreview.src
|
||||
: imagePathInput && imagePathInput.value || '').trim();
|
||||
if (previewBackground) {
|
||||
previewBackground.hidden = !imagePath;
|
||||
previewBackground.src = imagePath;
|
||||
previewBackground.style.width = sourceWidth + 'px';
|
||||
previewBackground.style.height = sourceHeight + 'px';
|
||||
previewBackground.style.transform = 'scale(' + scale + ')';
|
||||
}
|
||||
|
||||
previewCanvas.replaceChildren();
|
||||
var hasContent = false;
|
||||
templateRegions.forEach(function (region) {
|
||||
var isActiveRegion = region.region_key === activeRegion.region_key;
|
||||
var displayRegion = isActiveRegion ? activeRegion : region;
|
||||
var content = isActiveRegion
|
||||
? helpers.getPreviewRegionContent(card, activeRegion)
|
||||
: normalizeDefaultContent(region.default_content);
|
||||
existingContent[region.region_key] = content;
|
||||
var context = helpers.buildPreviewRenderContext(displayRegion, isActiveRegion ? card : null);
|
||||
var module = getRegionTypeModule(displayRegion.region_type);
|
||||
var html = module && typeof module.renderPreview === 'function'
|
||||
? module.renderPreview(displayRegion, context, context)
|
||||
: helpers.renderPreviewTextRegion(displayRegion, context.value, context.style || {}, 1);
|
||||
var regionElement = document.createElement('div');
|
||||
regionElement.className = 'template-default-content-preview-region';
|
||||
regionElement.dataset.regionKey = String(displayRegion.region_key || '');
|
||||
regionElement.style.position = 'absolute';
|
||||
regionElement.style.left = Math.max(0, Number(displayRegion.x) || 0) + 'px';
|
||||
regionElement.style.top = Math.max(0, Number(displayRegion.y) || 0) + 'px';
|
||||
regionElement.style.width = Math.max(1, Number(displayRegion.width) || 1) + 'px';
|
||||
regionElement.style.height = Math.max(1, Number(displayRegion.height) || 1) + 'px';
|
||||
regionElement.style.zIndex = Math.max(1, Number(displayRegion.z_index) || 1);
|
||||
regionElement.style.overflow = 'hidden';
|
||||
regionElement.innerHTML = html || '';
|
||||
if (!html) {
|
||||
regionElement.style.outline = isActiveRegion ? '2px dashed rgba(255,255,255,.9)' : '1px dashed rgba(255,255,255,.45)';
|
||||
if (Number(displayRegion.height) >= 24 && Number(displayRegion.width) >= 80) {
|
||||
var label = document.createElement('span');
|
||||
label.className = 'template-default-content-preview-label';
|
||||
label.textContent = String(displayRegion.label || displayRegion.region_key || 'Region');
|
||||
regionElement.appendChild(label);
|
||||
}
|
||||
}
|
||||
if (html) hasContent = true;
|
||||
previewCanvas.appendChild(regionElement);
|
||||
});
|
||||
previewFrame.hidden = false;
|
||||
if (previewEmpty) previewEmpty.hidden = hasContent;
|
||||
}
|
||||
|
||||
window.pulseTemplateDefaultContentEditor = {
|
||||
async open(region, content) {
|
||||
await editor.destroyEditors();
|
||||
host.replaceChildren();
|
||||
activeContent = cloneContent(content);
|
||||
activeRegion = Object.assign({}, region, { id: editorId++ });
|
||||
templateRegions.forEach(function (templateRegion) {
|
||||
existingContent[templateRegion.region_key] = normalizeDefaultContent(templateRegion.default_content);
|
||||
});
|
||||
existingContent[activeRegion.region_key] = activeContent;
|
||||
host.innerHTML = helpers.getRegionEditorCardHtml(activeRegion);
|
||||
helpers.bindRegionCards();
|
||||
await editor.renderEditors();
|
||||
renderPreview();
|
||||
},
|
||||
async read() {
|
||||
if (!activeRegion) throw new Error('No region editor is open.');
|
||||
await editor.syncEditors();
|
||||
var card = host.querySelector('[data-region-id="' + activeRegion.id + '"]');
|
||||
return cleanContent(helpers.getPreviewRegionContent(card, activeRegion));
|
||||
},
|
||||
async destroy() {
|
||||
await editor.destroyEditors();
|
||||
host.replaceChildren();
|
||||
if (activeRegion) delete existingContent[activeRegion.region_key];
|
||||
activeRegion = null;
|
||||
activeContent = {};
|
||||
if (previewCanvas) previewCanvas.replaceChildren();
|
||||
if (previewEmpty) previewEmpty.hidden = true;
|
||||
}
|
||||
};
|
||||
host.addEventListener('input', renderPreview);
|
||||
host.addEventListener('change', renderPreview);
|
||||
window.addEventListener('resize', renderPreview);
|
||||
if (window.ResizeObserver && previewWorkspace) {
|
||||
previewResizeObserver = new ResizeObserver(renderPreview);
|
||||
previewResizeObserver.observe(previewWorkspace);
|
||||
}
|
||||
}());
|
||||
@@ -263,11 +263,19 @@
|
||||
|
||||
function readCard(card) {
|
||||
var name = getRegionName(card);
|
||||
var defaultContentInput = card.querySelector('[data-region-default-content]');
|
||||
var readOnlyInput = card.querySelector('[data-region-read-only]');
|
||||
var defaultContent = {};
|
||||
if (defaultContentInput) {
|
||||
try { defaultContent = JSON.parse(defaultContentInput.value || '{}'); } catch (_error) { defaultContent = {}; }
|
||||
}
|
||||
return {
|
||||
region_key: name,
|
||||
label: name,
|
||||
region_type: card.querySelector('[name="region_type[]"]').value,
|
||||
lock_ratio: normalizeLockRatio(card.querySelector('[name="region_lock_ratio[]"]').value),
|
||||
default_content: defaultContent && typeof defaultContent === 'object' && !Array.isArray(defaultContent) ? defaultContent : {},
|
||||
read_only: Boolean(readOnlyInput && readOnlyInput.checked),
|
||||
animation_json: getAnimationConfig(card),
|
||||
x: Number(card.querySelector('[name="region_x[]"]').value || 0),
|
||||
y: Number(card.querySelector('[name="region_y[]"]').value || 0),
|
||||
@@ -287,6 +295,10 @@
|
||||
}
|
||||
if (values.region_type !== undefined) { card.querySelector('[name="region_type[]"]').value = values.region_type; }
|
||||
if (values.lock_ratio !== undefined) { card.querySelector('[name="region_lock_ratio[]"]').value = normalizeLockRatio(values.lock_ratio); }
|
||||
var defaultContentInput = card.querySelector('[data-region-default-content]');
|
||||
var readOnlyInput = card.querySelector('[data-region-read-only]');
|
||||
if (defaultContentInput && values.default_content !== undefined) { defaultContentInput.value = JSON.stringify(values.default_content || {}, null, 2); }
|
||||
if (readOnlyInput && values.read_only !== undefined) { readOnlyInput.checked = values.read_only === true || values.read_only === 1; }
|
||||
if (values.animation_json !== undefined) { writeAnimationConfig(card, values.animation_json); }
|
||||
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); }
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
var regionAddModal = document.getElementById('region-add-modal');
|
||||
var regionAddOptions = document.getElementById('region-add-options');
|
||||
var animationAdvancedModal = document.getElementById('animation-advanced-modal');
|
||||
var defaultContentModal = document.getElementById('region-default-content-modal');
|
||||
var defaultContentModalTitle = document.getElementById('region-default-content-modal-label');
|
||||
var defaultContentEditorHost = document.getElementById('region-default-content-editor-host');
|
||||
var defaultContentSaveButton = document.getElementById('region-default-content-save');
|
||||
var regionCardTemplate = document.getElementById('region-card-template');
|
||||
var regionsJsonInput = document.getElementById('regions-json');
|
||||
var templateForm = document.getElementById('template-form');
|
||||
@@ -63,6 +67,7 @@
|
||||
var draft = null;
|
||||
var selectedIndex = -1;
|
||||
var activeAnimationCard = null;
|
||||
var activeDefaultContentCard = null;
|
||||
var overlayRenderFrame = 0;
|
||||
var stageResizeObserver = null;
|
||||
var regionNameValidationToastShown = false;
|
||||
@@ -830,6 +835,65 @@
|
||||
if (values.z_index !== undefined) { card.querySelector('[name="region_z[]"]').value = Math.max(1, Math.round(Number(values.z_index) || 1)); }
|
||||
}
|
||||
|
||||
function readDefaultContent(card) {
|
||||
var input = card && card.querySelector('[data-region-default-content]');
|
||||
if (!input) return {};
|
||||
try {
|
||||
var content = JSON.parse(input.value || '{}');
|
||||
return content && typeof content === 'object' && !Array.isArray(content) ? content : {};
|
||||
} catch (_error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function openDefaultContentEditor(card) {
|
||||
if (!defaultContentModal || !card || !window.pulseModal) return;
|
||||
activeDefaultContentCard = card;
|
||||
var main = document.querySelector('.app-main');
|
||||
var sidebarEdge = window.innerWidth >= 992 && main ? Math.max(0, main.getBoundingClientRect().left) : 0;
|
||||
defaultContentModal.style.setProperty('--slide-layout-left', sidebarEdge + 'px');
|
||||
var region = readCard(card);
|
||||
var content = readDefaultContent(card);
|
||||
if (defaultContentModalTitle) defaultContentModalTitle.textContent = 'Default content: ' + (region.region_name || region.region_key || 'Region');
|
||||
if (defaultContentEditorHost) defaultContentEditorHost.replaceChildren();
|
||||
if (defaultContentSaveButton) defaultContentSaveButton.disabled = true;
|
||||
if (defaultContentModal) defaultContentModal.dataset.regionType = region.region_type;
|
||||
window.pulseTemplateDefaultContentEditorContent = { region: region, content: content };
|
||||
window.pulseModal.show(defaultContentModal);
|
||||
}
|
||||
|
||||
if (defaultContentModal) {
|
||||
defaultContentModal.addEventListener('shown.bs.modal', function () {
|
||||
var state = window.pulseTemplateDefaultContentEditorContent;
|
||||
if (!state || !window.pulseTemplateDefaultContentEditor || !defaultContentEditorHost) return;
|
||||
window.pulseTemplateDefaultContentEditor.open(state.region, state.content, defaultContentEditorHost).then(function () {
|
||||
if (defaultContentSaveButton) defaultContentSaveButton.disabled = false;
|
||||
}).catch(function () {
|
||||
if (typeof showToast === 'function') showToast('The region editor could not be loaded.', 'danger');
|
||||
});
|
||||
});
|
||||
defaultContentModal.addEventListener('hidden.bs.modal', function () {
|
||||
if (window.pulseTemplateDefaultContentEditor) window.pulseTemplateDefaultContentEditor.destroy();
|
||||
window.pulseTemplateDefaultContentEditorContent = null;
|
||||
});
|
||||
}
|
||||
if (defaultContentSaveButton) {
|
||||
defaultContentSaveButton.addEventListener('click', async function () {
|
||||
if (!activeDefaultContentCard || !window.pulseTemplateDefaultContentEditor) return;
|
||||
try {
|
||||
var region = readCard(activeDefaultContentCard);
|
||||
var content = await window.pulseTemplateDefaultContentEditor.read();
|
||||
content.type = region.region_type;
|
||||
writeCard(activeDefaultContentCard, { default_content: content });
|
||||
markTemplateFormDirty();
|
||||
activeDefaultContentCard = null;
|
||||
window.pulseModal.hide(defaultContentModal);
|
||||
} catch (_error) {
|
||||
if (typeof showToast === 'function') showToast('Unable to read region content.', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getOverlayRect() {
|
||||
return utils.getOverlayRect ? utils.getOverlayRect(overlay) : overlay.getBoundingClientRect();
|
||||
}
|
||||
@@ -1154,6 +1218,14 @@
|
||||
if (regionLabelInput) {
|
||||
regionLabelInput.value = region.label || region.region_key || '';
|
||||
}
|
||||
var defaultContentInput = card.querySelector('[data-region-default-content]');
|
||||
if (defaultContentInput) {
|
||||
defaultContentInput.value = JSON.stringify(region.default_content || {});
|
||||
}
|
||||
var readOnlyInput = card.querySelector('[data-region-read-only]');
|
||||
if (readOnlyInput) {
|
||||
readOnlyInput.checked = region.read_only === true || Number(region.read_only) === 1;
|
||||
}
|
||||
var regionLockRatioInput = card.querySelector('[name="region_lock_ratio[]"]');
|
||||
if (regionLockRatioInput) {
|
||||
regionLockRatioInput.value = normalizeLockRatio(region.lock_ratio || (String(region.region_type || '').trim() === 'qr-code' ? '1:1' : ''));
|
||||
@@ -1211,6 +1283,7 @@
|
||||
var animationPresetInputs = card.querySelectorAll('[data-animation-preset]');
|
||||
var animationResetButtons = card.querySelectorAll('[data-animation-reset-button]');
|
||||
var animationAdvancedButton = card.querySelector('[data-animation-advanced-button]');
|
||||
var defaultContentButton = card.querySelector('[data-region-default-content-button]');
|
||||
nameInput.addEventListener('input', function () {
|
||||
regionNameValidationToastShown = false;
|
||||
syncRegionIdentity(card, nameInput.value);
|
||||
@@ -1219,6 +1292,9 @@
|
||||
renderRegionSidebar();
|
||||
renderOverlay();
|
||||
});
|
||||
var readOnlyInput = card.querySelector('[data-region-read-only]');
|
||||
if (defaultContentButton) defaultContentButton.addEventListener('click', function () { openDefaultContentEditor(card); });
|
||||
if (readOnlyInput) readOnlyInput.addEventListener('change', markTemplateFormDirty);
|
||||
Array.prototype.forEach.call(animationPresetInputs, function (input) {
|
||||
input.addEventListener('change', function () {
|
||||
setAnimationPreset(card, input.getAttribute('data-animation-preset'), input.value);
|
||||
|
||||
Reference in new issue
Block a user