Release v2.2.0

This commit is contained in:
2026-08-01 21:41:44 +01:00
parent c643d2fb07
commit d6417b667c
673 changed files with 146752 additions and 7389 deletions
+125 -20
View File
@@ -80,6 +80,37 @@
}
function initAsyncCommandForms() {
function getAnnouncementActionState(actionPath) {
var isPlay = /\/play$/.test(actionPath);
return {
nextActionPath: actionPath.replace(/\/(?:play|stop)$/, isPlay ? '/stop' : '/play'),
nextLabel: isPlay ? 'Stop' : 'Play',
nextIcon: isPlay ? 'bi-stop-fill' : 'bi-play-fill',
nextClassName: isPlay ? 'btn-outline-warning' : 'btn-outline-success',
nextConfirmMessage: isPlay
? 'Stop this announcement on the selected screens now?'
: 'Send this announcement to the selected screens now?'
};
}
function updateAnnouncementActionButton(form, actionPath) {
if (!form) {
return false;
}
var state = getAnnouncementActionState(actionPath);
var visibleButton = form.querySelector('button[type="submit"]') || (form.id ? document.querySelector('button[form="' + form.id + '"]') : null);
if (!visibleButton) {
return false;
}
form.action = state.nextActionPath;
form.setAttribute('data-confirm-message', state.nextConfirmMessage);
visibleButton.className = visibleButton.className.replace(/btn-outline-(success|warning)/g, state.nextClassName);
visibleButton.innerHTML = '<i class="bi ' + state.nextIcon + ' me-1" aria-hidden="true"></i>' + state.nextLabel;
return true;
}
document.addEventListener('submit', function (event) {
var form = event.target;
if (!form || !form.hasAttribute || !form.hasAttribute('data-async-command')) {
@@ -97,6 +128,14 @@
return;
}
var actionPath = '';
try {
actionPath = new URL(form.action, window.location.href).pathname;
} catch (_error) {
actionPath = String(form.action || '');
}
var shouldReloadAfterSuccess = /^\/announcements\/\d+\/(?:play|stop)$/.test(actionPath);
event.preventDefault();
form.dataset.busy = 'true';
@@ -116,6 +155,11 @@
},
body: body.toString(),
credentials: 'same-origin'
}).then(function (response) {
if (shouldReloadAfterSuccess && response && response.ok) {
window.location.reload();
return;
}
}).finally(function () {
delete form.dataset.busy;
});
@@ -426,6 +470,52 @@
});
}
function renderJsonPrimitive(value) {
if (value === null) {
return '<span class="text-body-secondary">null</span>';
}
if (value === true || value === false) {
return '<span class="text-success">' + String(value) + '</span>';
}
if (typeof value === 'number') {
return '<span class="text-info">' + escapeHtml(String(value)) + '</span>';
}
return '<span class="text-body">"' + escapeHtml(String(value)) + '"</span>';
}
function renderJsonNode(key, value, isRoot) {
var isArray = Array.isArray(value);
var isObject = value && typeof value === 'object' && !isArray;
if (!isArray && !isObject) {
var primitiveLabel = isRoot ? 'value' : String(key);
return '<div class="json-tree-leaf"><span class="json-tree-key">' + escapeHtml(primitiveLabel) + '</span><span class="mx-1">:</span>' + renderJsonPrimitive(value) + '</div>';
}
var entries = isArray
? value.map(function (entry, index) {
return renderJsonNode('[' + index + ']', entry, false);
}).join('')
: Object.keys(value).map(function (childKey) {
return renderJsonNode(childKey, value[childKey], false);
}).join('');
var summaryLabel = isRoot ? (isArray ? 'Array' : 'Object') : String(key);
var summaryMeta = isArray ? '[' + value.length + ']' : '{' + Object.keys(value).length + '}';
return '' +
'<details class="json-tree-node" open>' +
'<summary><span class="json-tree-key">' + escapeHtml(summaryLabel) + '</span><span class="mx-1">:</span><span class="text-body-secondary">' + escapeHtml(summaryMeta) + '</span></summary>' +
'<div class="ms-3 ps-3 border-start">' + entries + '</div>' +
'</details>';
}
function renderJsonTree(value) {
if (Array.isArray(value) || (value && typeof value === 'object')) {
return renderJsonNode('', value, true);
}
return renderJsonNode('value', value, true);
}
function initJsonTogglePanels(root) {
var scope = root && root.querySelectorAll ? root : document;
var panels = scope.querySelectorAll('[data-json-toggle-panel]');
@@ -436,8 +526,8 @@
}
var card = panel.closest ? panel.closest('.card') : null;
var button = card ? card.querySelector('[data-json-toggle]') : null;
var label = button ? button.querySelector('[data-json-toggle-label]') : null;
var collapseButton = card ? card.querySelector('[data-json-toggle-collapse-all]') : null;
var expandButton = card ? card.querySelector('[data-json-toggle-expand-all]') : null;
var sourceNode = panel.querySelector('[data-json-toggle-source]');
var rawJson = '';
try {
@@ -463,32 +553,47 @@
return;
}
var compactJson = JSON.stringify(parsedJson);
var formattedJson = JSON.stringify(parsedJson, null, 2);
var isFormatted = true;
var formattedTree = renderJsonTree(parsedJson);
function syncButtonLabel() {
if (!button || !label) {
return;
function setAllSectionsExpanded(expanded) {
var details = output.querySelectorAll('details');
Array.prototype.forEach.call(details, function (detail) {
detail.open = expanded;
});
if (collapseButton) {
collapseButton.disabled = !details.length || !expanded;
}
if (expandButton) {
expandButton.disabled = !details.length || expanded;
}
label.textContent = isFormatted
? String(button.getAttribute('data-json-toggle-label-compact') || 'Unformat JSON')
: String(button.getAttribute('data-json-toggle-label-formatted') || 'Format JSON');
button.setAttribute('aria-pressed', isFormatted ? 'true' : 'false');
}
function syncOutput() {
output.textContent = isFormatted ? formattedJson : compactJson;
syncButtonLabel();
function syncButtons() {
if (collapseButton) {
collapseButton.setAttribute('aria-pressed', 'false');
}
if (expandButton) {
expandButton.setAttribute('aria-pressed', 'true');
}
}
output.textContent = formattedJson;
syncButtonLabel();
output.innerHTML = formattedTree;
syncButtons();
setAllSectionsExpanded(true);
if (button) {
button.addEventListener('click', function () {
isFormatted = !isFormatted;
syncOutput();
if (collapseButton) {
collapseButton.addEventListener('click', function () {
setAllSectionsExpanded(false);
syncButtons();
});
}
if (expandButton) {
expandButton.addEventListener('click', function () {
setAllSectionsExpanded(true);
syncButtons();
});
}
});