| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| class ToastManager {
|
| constructor() {
|
| this.toasts = [];
|
| this.container = null;
|
| this.maxToasts = 5;
|
| this.defaultDuration = 5000;
|
| this.init();
|
| }
|
|
|
| |
| |
|
|
| init() {
|
|
|
| if (!document.getElementById('toast-container')) {
|
| this.container = document.createElement('div');
|
| this.container.id = 'toast-container';
|
| this.container.className = 'toast-container';
|
| this.container.setAttribute('role', 'region');
|
| this.container.setAttribute('aria-label', 'Notifications');
|
| this.container.setAttribute('aria-live', 'polite');
|
| document.body.appendChild(this.container);
|
| } else {
|
| this.container = document.getElementById('toast-container');
|
| }
|
|
|
| console.log('[Toast] Toast manager initialized');
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| show(message, type = 'info', options = {}) {
|
| const {
|
| duration = this.defaultDuration,
|
| title = null,
|
| icon = null,
|
| dismissible = true,
|
| action = null
|
| } = options;
|
|
|
|
|
| if (this.toasts.length >= this.maxToasts) {
|
| this.dismiss(this.toasts[0].id);
|
| }
|
|
|
| const toast = {
|
| id: this.generateId(),
|
| message,
|
| type,
|
| title,
|
| icon: icon || this.getDefaultIcon(type),
|
| dismissible,
|
| action,
|
| duration,
|
| createdAt: Date.now()
|
| };
|
|
|
| this.toasts.push(toast);
|
| this.render(toast);
|
|
|
|
|
| if (duration > 0) {
|
| setTimeout(() => this.dismiss(toast.id), duration);
|
| }
|
|
|
| return toast.id;
|
| }
|
|
|
| |
| |
|
|
| success(message, options = {}) {
|
| return this.show(message, 'success', options);
|
| }
|
|
|
| |
| |
|
|
| error(message, options = {}) {
|
| return this.show(message, 'error', { ...options, duration: options.duration || 7000 });
|
| }
|
|
|
| |
| |
|
|
| warning(message, options = {}) {
|
| return this.show(message, 'warning', options);
|
| }
|
|
|
| |
| |
|
|
| info(message, options = {}) {
|
| return this.show(message, 'info', options);
|
| }
|
|
|
| |
| |
|
|
| dismiss(toastId) {
|
| const toastElement = document.getElementById(`toast-${toastId}`);
|
| if (!toastElement) return;
|
|
|
|
|
| toastElement.classList.add('toast-exit');
|
|
|
| setTimeout(() => {
|
| toastElement.remove();
|
| this.toasts = this.toasts.filter(t => t.id !== toastId);
|
| }, 300);
|
| }
|
|
|
| |
| |
|
|
| dismissAll() {
|
| const toastIds = this.toasts.map(t => t.id);
|
| toastIds.forEach(id => this.dismiss(id));
|
| }
|
|
|
| |
| |
|
|
| render(toast) {
|
| const toastElement = document.createElement('div');
|
| toastElement.id = `toast-${toast.id}`;
|
| toastElement.className = `toast toast-${toast.type} glass-effect`;
|
| toastElement.setAttribute('role', 'alert');
|
| toastElement.setAttribute('aria-atomic', 'true');
|
|
|
| const iconHtml = window.getIcon
|
| ? window.getIcon(toast.icon, 24)
|
| : '';
|
|
|
| const titleHtml = toast.title
|
| ? `<div class="toast-title">${toast.title}</div>`
|
| : '';
|
|
|
| const actionHtml = toast.action
|
| ? `<button class="toast-action" onclick="${toast.action.onClick}">${toast.action.label}</button>`
|
| : '';
|
|
|
| const closeButton = toast.dismissible
|
| ? `<button class="toast-close" onclick="window.toastManager.dismiss('${toast.id}')" aria-label="Close notification">
|
| ${window.getIcon ? window.getIcon('close', 20) : '×'}
|
| </button>`
|
| : '';
|
|
|
| const progressBar = toast.duration > 0
|
| ? `<div class="toast-progress" style="animation-duration: ${toast.duration}ms"></div>`
|
| : '';
|
|
|
| toastElement.innerHTML = `
|
| <div class="toast-icon">
|
| ${iconHtml}
|
| </div>
|
| <div class="toast-content">
|
| ${titleHtml}
|
| <div class="toast-message">${toast.message}</div>
|
| ${actionHtml}
|
| </div>
|
| ${closeButton}
|
| ${progressBar}
|
| `;
|
|
|
| this.container.appendChild(toastElement);
|
|
|
|
|
| setTimeout(() => toastElement.classList.add('toast-enter'), 10);
|
| }
|
|
|
| |
| |
|
|
| getDefaultIcon(type) {
|
| const icons = {
|
| success: 'checkCircle',
|
| error: 'alertCircle',
|
| warning: 'alertCircle',
|
| info: 'info'
|
| };
|
| return icons[type] || 'info';
|
| }
|
|
|
| |
| |
|
|
| generateId() {
|
| return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
| }
|
|
|
| |
| |
|
|
| showProviderError(providerName, error) {
|
| return this.error(
|
| `Failed to connect to ${providerName}`,
|
| {
|
| title: 'Provider Error',
|
| duration: 7000,
|
| action: {
|
| label: 'Retry',
|
| onClick: `window.providerDiscovery.checkProviderHealth('${providerName}')`
|
| }
|
| }
|
| );
|
| }
|
|
|
| |
| |
|
|
| showProviderSuccess(providerName) {
|
| return this.success(
|
| `Successfully connected to ${providerName}`,
|
| {
|
| title: 'Provider Online',
|
| duration: 3000
|
| }
|
| );
|
| }
|
|
|
| |
| |
|
|
| showRateLimitWarning(providerName, retryAfter) {
|
| return this.warning(
|
| `Rate limit reached for ${providerName}. Retry after ${retryAfter}s`,
|
| {
|
| title: 'Rate Limit',
|
| duration: 6000
|
| }
|
| );
|
| }
|
| }
|
|
|
|
|
| window.toastManager = new ToastManager();
|
|
|
|
|
| window.showToast = (message, type, options) => window.toastManager.show(message, type, options);
|
| window.toast = {
|
| success: (msg, opts) => window.toastManager.success(msg, opts),
|
| error: (msg, opts) => window.toastManager.error(msg, opts),
|
| warning: (msg, opts) => window.toastManager.warning(msg, opts),
|
| info: (msg, opts) => window.toastManager.info(msg, opts)
|
| };
|
|
|
| console.log('[Toast] Toast notification system ready');
|
|
|