// Notification and Progress System
class NotificationManager {
constructor() {
this.notifications = [];
this.notificationId = 0;
}
show(title, message, type = 'info', duration = 5000) {
const id = this.notificationId++;
// Create notification element
const notification = document.createElement('div');
notification.className = `notification-toast ${type}`;
notification.id = `notification-${id}`;
// Icon based on type
let icon = '';
if (type === 'success') {
icon = '';
} else if (type === 'error') {
icon = '';
} else {
icon = '';
}
notification.innerHTML = `
${icon}
`;
document.body.appendChild(notification);
this.notifications.push({ id, element: notification });
// Auto-close after duration
if (duration > 0) {
setTimeout(() => this.close(id), duration);
}
// DO NOT show browser notifications - they overlap with our in-app notifications
return id;
}
close(id) {
const notification = this.notifications.find(n => n.id === id);
if (notification) {
notification.element.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
notification.element.remove();
this.notifications = this.notifications.filter(n => n.id !== id);
}, 300);
}
}
success(title, message, duration = 5000) {
return this.show(title, message, 'success', duration);
}
error(title, message, duration = 7000) {
return this.show(title, message, 'error', duration);
}
info(title, message, duration = 5000) {
return this.show(title, message, 'info', duration);
}
}
class ProgressTracker {
constructor() {
this.startTime = null;
this.stages = [];
this.currentStage = 0;
}
start(stages = ['Processing...']) {
this.startTime = Date.now();
this.stages = stages;
this.currentStage = 0;
this.updateProgress(0, stages[0]);
}
nextStage() {
this.currentStage++;
if (this.currentStage < this.stages.length) {
const progress = (this.currentStage / this.stages.length) * 100;
this.updateProgress(progress, this.stages[this.currentStage]);
}
}
updateProgress(percent, message = null) {
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const loadingMessage = document.getElementById('loadingMessage');
const etaText = document.getElementById('etaText');
if (progressFill) {
progressFill.style.width = `${percent}%`;
}
if (progressText) {
progressText.textContent = `${Math.round(percent)}%`;
}
if (message && loadingMessage) {
loadingMessage.textContent = message;
}
// Calculate ETA
if (this.startTime && percent > 0 && percent < 100) {
const elapsed = Date.now() - this.startTime;
const total = (elapsed / percent) * 100;
const remaining = total - elapsed;
if (etaText && remaining > 0) {
const seconds = Math.ceil(remaining / 1000);
if (seconds < 60) {
etaText.textContent = `ETA: ${seconds}s`;
} else {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
etaText.textContent = `ETA: ${minutes}m ${secs}s`;
}
}
} else if (etaText) {
etaText.textContent = '';
}
}
complete(message = 'Complete!') {
this.updateProgress(100, message);
// Clear ETA
const etaText = document.getElementById('etaText');
if (etaText) {
etaText.textContent = '';
}
}
reset() {
this.startTime = null;
this.stages = [];
this.currentStage = 0;
this.updateProgress(0, '');
}
}
// Global instances
const notificationManager = new NotificationManager();
const progressTracker = new ProgressTracker();
// DO NOT request notification permission - we only use in-app notifications
// Add slideOut animation
const style = document.createElement('style');
style.textContent = `
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(style);