new notification system test

This commit is contained in:
Raven Scott
2025-11-24 15:58:47 -05:00
parent 05fa6b8622
commit 6469f620e6
4 changed files with 790 additions and 22 deletions
+203
View File
@@ -0,0 +1,203 @@
/**
* Notification Manager
* Centralized notification system with persistence and tray support
*/
const STORAGE_KEY = 'peardock_notifications';
const MAX_NOTIFICATIONS = 100; // Limit stored notifications
class NotificationManager {
constructor() {
this.notifications = [];
this.listeners = [];
this.loadFromStorage();
}
/**
* Load notifications from localStorage
*/
loadFromStorage() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
// Convert date strings back to Date objects
this.notifications = parsed.map(n => ({
...n,
timestamp: new Date(n.timestamp)
}));
// Limit to most recent notifications
this.notifications = this.notifications.slice(-MAX_NOTIFICATIONS);
}
} catch (err) {
console.error('[ERROR] Failed to load notifications from storage:', err);
this.notifications = [];
}
}
/**
* Save notifications to localStorage
*/
saveToStorage() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.notifications));
} catch (err) {
console.error('[ERROR] Failed to save notifications to storage:', err);
}
}
/**
* Subscribe to notification changes
* @param {Function} callback - Callback function
*/
subscribe(callback) {
this.listeners.push(callback);
// Immediately notify with current state
callback(this.notifications, this.getUnreadCount());
}
/**
* Unsubscribe from notification changes
* @param {Function} callback - Callback function to remove
*/
unsubscribe(callback) {
this.listeners = this.listeners.filter(listener => listener !== callback);
}
/**
* Notify all subscribers
*/
notify() {
this.listeners.forEach(callback => {
try {
callback(this.notifications, this.getUnreadCount());
} catch (err) {
console.error('[ERROR] Notification listener error:', err);
}
});
}
/**
* Add a new notification
* @param {string} type - Notification type (success, danger, warning, info)
* @param {string} message - Notification message
* @param {Object} options - Additional options (autoDismiss, duration, etc.)
* @returns {string} Notification ID
*/
add(type, message, options = {}) {
const id = this.generateId();
const notification = {
id,
type,
message,
timestamp: new Date(),
read: false,
autoDismiss: options.autoDismiss !== false, // Default to true
duration: options.duration || 5000
};
this.notifications.unshift(notification); // Add to beginning
this.notifications = this.notifications.slice(0, MAX_NOTIFICATIONS); // Limit size
this.saveToStorage();
this.notify();
// Auto-dismiss if enabled
if (notification.autoDismiss) {
setTimeout(() => {
this.remove(id);
}, notification.duration);
}
return id;
}
/**
* Remove a notification
* @param {string} id - Notification ID
*/
remove(id) {
this.notifications = this.notifications.filter(n => n.id !== id);
this.saveToStorage();
this.notify();
}
/**
* Mark notification as read
* @param {string} id - Notification ID
*/
markAsRead(id) {
const notification = this.notifications.find(n => n.id === id);
if (notification && !notification.read) {
notification.read = true;
this.saveToStorage();
this.notify();
}
}
/**
* Mark all notifications as read
*/
markAllAsRead() {
let changed = false;
this.notifications.forEach(n => {
if (!n.read) {
n.read = true;
changed = true;
}
});
if (changed) {
this.saveToStorage();
this.notify();
}
}
/**
* Clear all notifications
*/
clearAll() {
this.notifications = [];
this.saveToStorage();
this.notify();
}
/**
* Get unread count
* @returns {number} Number of unread notifications
*/
getUnreadCount() {
return this.notifications.filter(n => !n.read).length;
}
/**
* Get notifications with optional filtering
* @param {Object} filters - Filter options (type, read)
* @returns {Array} Filtered notifications
*/
getNotifications(filters = {}) {
let filtered = [...this.notifications];
if (filters.type && filters.type !== 'all') {
filtered = filtered.filter(n => n.type === filters.type);
}
if (filters.read !== undefined) {
filtered = filtered.filter(n => n.read === filters.read);
}
return filtered;
}
/**
* Generate unique ID for notification
* @returns {string} Unique ID
*/
generateId() {
return `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
// Create singleton instance
const notificationManager = new NotificationManager();
export default notificationManager;
+7 -19
View File
@@ -3,6 +3,8 @@
* Used by both app.js and templateDeploy.js to avoid code duplication
*/
import notificationManager from './notifications.js';
/**
* Close all open Bootstrap modals
*/
@@ -54,27 +56,13 @@ export function hideStatusIndicator() {
}
/**
* Show alert message
* Show alert message (now uses notification system)
* @param {string} type - Alert type (success, danger, warning, info)
* @param {string} message - Message to display
* @param {Object} options - Additional options (autoDismiss, duration)
*/
export function showAlert(type, message) {
const alertBox = document.createElement('div');
alertBox.className = `alert alert-${type}`;
alertBox.textContent = message;
// Get alert container dynamically to avoid dependency on module-specific variables
const container = document.getElementById('alert-container') || document.querySelector('#alert-container');
if (container) {
container.appendChild(alertBox);
setTimeout(() => {
if (container.contains(alertBox)) {
container.removeChild(alertBox);
}
}, 5000);
} else {
console.warn('[WARN] Alert container not found.');
}
export function showAlert(type, message, options = {}) {
// Use the new notification system
notificationManager.add(type, message, options);
}