Files
pulse-signage/src/web/public/js/toast.js
T
2026-08-05 19:38:16 +01:00

329 lines
9.6 KiB
JavaScript

(function (root) {
var pendingToastStorageKey = 'pulse:pending-toast';
function readPendingToast() {
try {
if (!root.sessionStorage) {
return null;
}
var rawValue = root.sessionStorage.getItem(pendingToastStorageKey);
if (!rawValue) {
return null;
}
root.sessionStorage.removeItem(pendingToastStorageKey);
var payload = JSON.parse(rawValue);
if (!payload || !payload.message) {
return null;
}
return {
message: String(payload.message || '').trim(),
variant: String(payload.variant || 'info').trim().toLowerCase() || 'info'
};
} catch (_error) {
return null;
}
}
function writePendingToast(message, variant) {
try {
if (!root.sessionStorage) {
return false;
}
var text = String(message || '').trim();
if (!text) {
return false;
}
root.sessionStorage.setItem(pendingToastStorageKey, JSON.stringify({
message: text,
variant: String(variant || 'info').trim().toLowerCase() || 'info'
}));
return true;
} catch (_error) {
return false;
}
}
function clearPendingToast() {
try {
if (root.sessionStorage) {
root.sessionStorage.removeItem(pendingToastStorageKey);
}
} catch (_error) {
// ignore storage cleanup failures
}
}
function getToastTitle(variant, message) {
var text = String(message || '').trim();
if (/\b(?:deleted|removed)\.?$/i.test(text)) {
return 'Deleted';
}
var textVariant = String(variant || '').trim().toLowerCase();
if (textVariant === 'danger') {
return 'Error';
}
if (textVariant === 'warning') {
return 'Warning';
}
if (textVariant === 'success') {
return 'Success';
}
return 'Pulse';
}
function getToastTimeLabel(createdAt) {
var timestamp = Number(createdAt);
if (!timestamp || Number.isNaN(timestamp)) {
return 'just now';
}
var elapsed = Date.now() - timestamp;
if (elapsed < 60 * 1000) {
return 'just now';
}
var minutes = Math.floor(elapsed / (60 * 1000));
if (minutes < 60) {
return minutes + ' min' + (minutes === 1 ? '' : 's') + ' ago';
}
var hours = Math.floor(minutes / 60);
if (hours < 24) {
return hours + ' hour' + (hours === 1 ? '' : 's') + ' ago';
}
var days = Math.floor(hours / 24);
if (days < 7) {
return days + ' day' + (days === 1 ? '' : 's') + ' ago';
}
return new Date(timestamp).toLocaleString();
}
function getToastButtonClass(variant) {
var textVariant = String(variant || '').trim().toLowerCase();
return textVariant === 'light' || textVariant === 'warning' ? 'btn-close' : 'btn-close-white';
}
function getToastIconClass(variant) {
var textVariant = String(variant || '').trim().toLowerCase();
if (textVariant === 'success') {
return 'bi-check-circle-fill';
}
if (textVariant === 'warning') {
return 'bi-exclamation-triangle-fill';
}
if (textVariant === 'danger') {
return 'bi-x-circle-fill';
}
if (textVariant === 'primary') {
return 'bi-bell-fill';
}
return 'bi-info-circle-fill';
}
function buildToastMarkup(title, timeLabel, buttonClass, iconClass) {
return '<div class="toast-header border-0">' +
'<i class="bi ' + iconClass + ' me-2"></i>' +
'<strong class="me-auto">' + title + '</strong>' +
'<small class="toast-time text-nowrap">' + timeLabel + '</small>' +
'<button type="button" class="btn-close ' + buttonClass + ' ms-2" data-bs-dismiss="toast" aria-label="Dismiss notification"></button>' +
'</div>' +
'<div class="toast-body"></div>';
}
function setToastHeader(toast, variant, createdAt) {
if (!toast) {
return;
}
var header = toast.querySelector('.toast-header');
var icon = toast.querySelector('.toast-header i');
var title = toast.querySelector('.toast-header .me-auto');
var time = toast.querySelector('.toast-time');
var closeButton = toast.querySelector('.toast-header .btn-close');
var nextVariant = getMessageVariant('', variant);
if (!header) {
return;
}
header.className = 'toast-header border-0 text-bg-' + nextVariant;
if (icon) {
icon.className = 'bi ' + getToastIconClass(nextVariant) + ' me-2';
}
if (title) {
title.textContent = getToastTitle(nextVariant);
}
if (time) {
time.textContent = getToastTimeLabel(createdAt);
}
if (closeButton) {
closeButton.className = 'btn-close ' + getToastButtonClass(nextVariant) + ' ms-2';
}
}
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;
}
setToastHeader(toast, variant, toast.getAttribute('data-toast-created-at') || Date.now());
}
function getMessageVariant(message, fallbackVariant) {
var text = String(message || '').trim();
var rules = [
{ variant: 'danger', patterns: [/\b(?:deleted|removed)\.?\s*$/i, /\b(?:deleted|removed)\b/i] },
{ variant: 'danger', patterns: [/\b(?:unable to|cannot|can't|could not|failed to)\s+delete\b/i, /\bdelete\b.*\b(?:before|first)\b/i, /\bstill (?:in use|linked|assigned|used)\b/i] },
{ variant: 'warning', patterns: [/\b(?:already exists|already exist|already taken|duplicate|must be unique|name already exists)\b/i] }
];
for (var i = 0; i < rules.length; i += 1) {
if (rules[i].patterns.some(function (pattern) { return pattern.test(text); })) {
return rules[i].variant;
}
}
return String(fallbackVariant || 'info').trim().toLowerCase() || 'info';
}
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;
}
var nextVariant = getMessageVariant(text, variant);
existingToast.setAttribute('data-toast-variant', nextVariant);
existingToast.setAttribute('data-toast-created-at', String(Date.now()));
setToastVariant(existingToast, nextVariant);
setToastHeader(existingToast, nextVariant, Date.now());
var existingInstance = getBootstrapToast(existingToast);
if (existingInstance) {
existingInstance.show();
}
return;
}
var toast = document.createElement('div');
toast.className = 'toast shadow-lg 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.setAttribute('data-toast-created-at', String(Date.now()));
var toastVariant = getMessageVariant(text, variant);
toast.setAttribute('data-toast-variant', toastVariant);
toast.innerHTML = buildToastMarkup(getToastTitle(toastVariant, text), getToastTimeLabel(Date.now()), getToastButtonClass(toastVariant), getToastIconClass(toastVariant));
toast.querySelector('.toast-body').textContent = text;
setToastVariant(toast, toastVariant);
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) {
var pendingToast = readPendingToast();
if (pendingToast) {
showToast(pendingToast.message, pendingToast.variant);
}
return;
}
clearPendingToast();
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
}
var bodyText = (toast.querySelector('.toast-body') && toast.querySelector('.toast-body').textContent) || '';
var existingVariant = String(toast.getAttribute('data-toast-variant') || '').trim().toLowerCase() || getMessageVariant(bodyText, 'info');
setToastVariant(toast, existingVariant);
var title = toast.querySelector('.toast-header .me-auto');
if (title) {
title.textContent = getToastTitle(existingVariant, bodyText);
}
var instance = getBootstrapToast(toast);
if (instance) {
instance.show();
}
}
root.dismissToast = dismissToast;
root.showToast = showToast;
root.initToast = initToast;
root.getMessageVariant = getMessageVariant;
root.queueToastAfterRefresh = writePendingToast;
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
getMessageVariant: getMessageVariant
};
}
if (root.document) {
initToast();
}
}(typeof window !== 'undefined' ? window : globalThis));