Files
arcum42-ComfyUI_SageUtils/js/shared/notifications.js
T

364 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Modern Notification System - Toast notifications and inline messages
* Replaces legacy alert() dialogs with better UX
*/
// Notification types with consistent styling
export const NOTIFICATION_TYPES = {
SUCCESS: 'success',
ERROR: 'error',
WARNING: 'warning',
INFO: 'info'
};
// Toast notification configuration
const TOAST_CONFIG = {
duration: 5000, // 5 seconds default
position: 'top-right',
maxToasts: 5
};
let toastContainer = null;
let toastIdCounter = 0;
/**
* Initialize the toast container if it doesn't exist
*/
function ensureToastContainer() {
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'sage-toast-container';
toastContainer.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
`;
document.body.appendChild(toastContainer);
}
return toastContainer;
}
/**
* Get the CSS classes for a notification type
*/
function getNotificationStyles(type) {
const baseStyles = `
padding: 12px 16px;
border-radius: 6px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
font-size: 14px;
line-height: 1.4;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
pointer-events: auto;
cursor: pointer;
transition: all 0.3s ease;
max-width: 400px;
word-wrap: break-word;
`;
const typeStyles = {
success: `
background: #10B981;
color: white;
border-left: 4px solid #059669;
`,
error: `
background: #EF4444;
color: white;
border-left: 4px solid #DC2626;
`,
warning: `
background: #F59E0B;
color: white;
border-left: 4px solid #D97706;
`,
info: `
background: #3B82F6;
color: white;
border-left: 4px solid #2563EB;
`
};
return baseStyles + (typeStyles[type] || typeStyles.info);
}
/**
* Show a toast notification
* @param {string} message - The message to display
* @param {string} type - Notification type (success, error, warning, info)
* @param {number} duration - How long to show the toast in milliseconds (0 = permanent)
* @param {Function} onClick - Optional click handler
* @returns {string} - Toast ID for manual dismissal
*/
export function showToast(message, type = NOTIFICATION_TYPES.INFO, duration = TOAST_CONFIG.duration, onClick = null) {
const container = ensureToastContainer();
const toastId = `toast-${++toastIdCounter}`;
// Limit number of toasts
const existingToasts = container.querySelectorAll('.sage-toast');
if (existingToasts.length >= TOAST_CONFIG.maxToasts) {
existingToasts[0].remove();
}
// Create toast element
const toast = document.createElement('div');
toast.id = toastId;
toast.className = 'sage-toast';
toast.style.cssText = getNotificationStyles(type) + `
transform: translateX(100%);
animation: slideIn 0.3s ease forwards;
`;
// Add close button
const closeButton = document.createElement('span');
closeButton.innerHTML = '×';
closeButton.style.cssText = `
float: right;
margin-left: 10px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
opacity: 0.7;
`;
closeButton.onclick = (e) => {
e.stopPropagation();
dismissToast(toastId);
};
// Add content
const content = document.createElement('span');
content.textContent = message;
toast.appendChild(content);
toast.appendChild(closeButton);
// Add click handler if provided
if (onClick) {
toast.onclick = (e) => {
if (e.target !== closeButton) {
onClick();
dismissToast(toastId);
}
};
} else {
// Default: click to dismiss
toast.onclick = (e) => {
if (e.target !== closeButton) {
dismissToast(toastId);
}
};
}
container.appendChild(toast);
// Auto-dismiss if duration is specified
if (duration > 0) {
setTimeout(() => dismissToast(toastId), duration);
}
return toastId;
}
/**
* Dismiss a specific toast by ID
*/
export function dismissToast(toastId) {
const toast = document.getElementById(toastId);
if (toast) {
toast.style.cssText += `
transform: translateX(100%);
opacity: 0;
transition: all 0.3s ease;
`;
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}
}
/**
* Clear all toasts
*/
export function clearAllToasts() {
if (toastContainer) {
toastContainer.innerHTML = '';
}
}
// Convenience methods for different notification types
export const notifications = {
success: (message, duration, onClick) => showToast(message, NOTIFICATION_TYPES.SUCCESS, duration, onClick),
error: (message, duration, onClick) => showToast(message, NOTIFICATION_TYPES.ERROR, duration, onClick),
warning: (message, duration, onClick) => showToast(message, NOTIFICATION_TYPES.WARNING, duration, onClick),
info: (message, duration, onClick) => showToast(message, NOTIFICATION_TYPES.INFO, duration, onClick)
};
/**
* Show a confirmation dialog (replacement for confirm())
* @param {string} message - The confirmation message
* @param {string} title - Optional title for the dialog
* @returns {Promise<boolean>} - Promise that resolves to user's choice
*/
export function showConfirmation(message, title = 'Confirm') {
return new Promise((resolve) => {
// Create modal backdrop
const backdrop = document.createElement('div');
backdrop.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
`;
// Create dialog
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
max-width: 400px;
width: 90%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
`;
// Add title
const titleEl = document.createElement('h3');
titleEl.textContent = title;
titleEl.style.cssText = `
margin: 0 0 16px 0;
font-size: 18px;
font-weight: 600;
color: #374151;
`;
// Add message
const messageEl = document.createElement('p');
messageEl.textContent = message;
messageEl.style.cssText = `
margin: 0 0 24px 0;
font-size: 14px;
line-height: 1.5;
color: #6B7280;
`;
// Add buttons
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
gap: 12px;
justify-content: flex-end;
`;
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.style.cssText = `
padding: 8px 16px;
border: 1px solid #D1D5DB;
border-radius: 6px;
background: white;
color: #374151;
cursor: pointer;
font-size: 14px;
`;
const confirmButton = document.createElement('button');
confirmButton.textContent = 'OK';
confirmButton.style.cssText = `
padding: 8px 16px;
border: none;
border-radius: 6px;
background: #3B82F6;
color: white;
cursor: pointer;
font-size: 14px;
`;
// Event handlers
const cleanup = () => {
if (backdrop.parentNode) {
backdrop.parentNode.removeChild(backdrop);
}
};
cancelButton.onclick = () => {
resolve(false);
cleanup();
};
confirmButton.onclick = () => {
resolve(true);
cleanup();
};
backdrop.onclick = (e) => {
if (e.target === backdrop) {
resolve(false);
cleanup();
}
};
// Assemble dialog
buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(confirmButton);
dialog.appendChild(titleEl);
dialog.appendChild(messageEl);
dialog.appendChild(buttonContainer);
backdrop.appendChild(dialog);
document.body.appendChild(backdrop);
// Focus the confirm button
confirmButton.focus();
});
}
// Add CSS animations
if (!document.querySelector('#sage-notifications-styles')) {
const styles = document.createElement('style');
styles.id = 'sage-notifications-styles';
styles.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.sage-toast:hover {
transform: scale(1.02);
}
.sage-toast:active {
transform: scale(0.98);
}
`;
document.head.appendChild(styles);
}
// Legacy alert() replacement - shows as error toast
export function modernAlert(message) {
return showToast(message, NOTIFICATION_TYPES.ERROR, 7000); // Longer duration for errors
}
// Legacy confirm() replacement
export function modernConfirm(message) {
return showConfirmation(message);
}