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.
126 lines
3.7 KiB
JavaScript
126 lines
3.7 KiB
JavaScript
(function () {
|
|
function getBootstrapToast(toast) {
|
|
if (!toast || !window.bootstrap || !window.bootstrap.Toast) {
|
|
return null;
|
|
}
|
|
|
|
return window.bootstrap.Toast.getOrCreateInstance(toast, {
|
|
autohide: true,
|
|
delay: 4000
|
|
});
|
|
}
|
|
|
|
function removeToast(toast) {
|
|
if (toast && toast.parentNode) {
|
|
toast.parentNode.removeChild(toast);
|
|
}
|
|
}
|
|
|
|
function dismissToast(toast) {
|
|
var instance = getBootstrapToast(toast);
|
|
if (instance) {
|
|
instance.hide();
|
|
return;
|
|
}
|
|
removeToast(toast);
|
|
}
|
|
|
|
function setToastVariant(toast, variant) {
|
|
if (!toast || !toast.classList) {
|
|
return;
|
|
}
|
|
|
|
var nextVariant = String(variant || 'success').trim().toLowerCase();
|
|
var variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'];
|
|
variants.forEach(function (value) {
|
|
toast.classList.remove('text-bg-' + value);
|
|
});
|
|
toast.classList.add('text-bg-' + (variants.indexOf(nextVariant) === -1 ? 'success' : nextVariant));
|
|
}
|
|
|
|
function getMessageVariant(message, fallbackVariant) {
|
|
var text = String(message || '').trim();
|
|
if (/^(unable to delete|cannot delete|can't delete)/i.test(text)) {
|
|
return 'danger';
|
|
}
|
|
return String(fallbackVariant || 'success').trim().toLowerCase() || 'success';
|
|
}
|
|
|
|
function showToast(message, variant) {
|
|
var text = String(message || '').trim();
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
var container = document.getElementById('app-toast-container');
|
|
if (!container) {
|
|
return;
|
|
}
|
|
|
|
var existingToast = document.getElementById('app-toast');
|
|
if (existingToast) {
|
|
var existingBody = existingToast.querySelector('.toast-body');
|
|
if (existingBody) {
|
|
existingBody.textContent = text;
|
|
}
|
|
setToastVariant(existingToast, getMessageVariant(text, variant));
|
|
var existingInstance = getBootstrapToast(existingToast);
|
|
if (existingInstance) {
|
|
existingInstance.show();
|
|
}
|
|
return;
|
|
}
|
|
|
|
var toast = document.createElement('div');
|
|
toast.className = 'toast align-items-center border-0';
|
|
toast.id = 'app-toast';
|
|
toast.setAttribute('role', 'status');
|
|
toast.setAttribute('aria-live', 'polite');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
toast.setAttribute('data-bs-autohide', 'true');
|
|
toast.setAttribute('data-bs-delay', '4000');
|
|
toast.innerHTML = '<div class="d-flex"><div class="toast-body"></div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Dismiss notification"></button></div>';
|
|
setToastVariant(toast, getMessageVariant(text, variant));
|
|
toast.querySelector('.toast-body').textContent = text;
|
|
|
|
toast.addEventListener('hidden.bs.toast', function () {
|
|
removeToast(toast);
|
|
});
|
|
|
|
container.appendChild(toast);
|
|
var instance = getBootstrapToast(toast);
|
|
if (instance) {
|
|
instance.show();
|
|
}
|
|
}
|
|
|
|
function initToast() {
|
|
var toast = document.getElementById('app-toast');
|
|
if (!toast) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var url = new URL(window.location.href);
|
|
if (url.searchParams.has('message')) {
|
|
url.searchParams.delete('message');
|
|
window.history.replaceState({}, document.title, url.pathname + url.search + url.hash);
|
|
}
|
|
} catch (_error) {
|
|
// ignore URL cleanup failures
|
|
}
|
|
|
|
setToastVariant(toast, getMessageVariant((toast.querySelector('.toast-body') && toast.querySelector('.toast-body').textContent) || '', 'success'));
|
|
|
|
var instance = getBootstrapToast(toast);
|
|
if (instance) {
|
|
instance.show();
|
|
}
|
|
}
|
|
|
|
window.dismissToast = dismissToast;
|
|
window.showToast = showToast;
|
|
window.initToast = initToast;
|
|
|
|
initToast();
|
|
}()); |