mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-05-23 04:17:34 +00:00
Add user-configurable timezone and 12/24-hour preferences, then wire them through settings, runtime snapshots, scheduler payloads, wait handling, notifications, backups, memory, plugin metadata, and frontend formatters. Keep UTC as the boundary for absolute instants while serializing user-facing dates in the configured or browser-resolved timezone. Preserve scheduler wall-clock inputs in the selected timezone, propagate TZ into desktop/runtime process environments, and restart active desktop sessions when the runtime timezone changes. Cover the risky paths with timezone regression tests for settings normalization, auto and fixed timezone resolution, scheduler round-trips, memory timestamp conversion, and desktop timezone sync.
872 lines
23 KiB
JavaScript
872 lines
23 KiB
JavaScript
import { createStore } from "/js/AlpineStore.js";
|
|
import * as API from "/js/api.js";
|
|
import { openModal } from "/js/modals.js";
|
|
import { formatDateTime, getCurrentUserISOString } from "/js/time-utils.js";
|
|
|
|
export const NotificationType = {
|
|
INFO: "info",
|
|
SUCCESS: "success",
|
|
WARNING: "warning",
|
|
ERROR: "error",
|
|
PROGRESS: "progress",
|
|
};
|
|
|
|
export const NotificationPriority = {
|
|
NORMAL: 10,
|
|
HIGH: 20,
|
|
};
|
|
|
|
export const defaultPriority = NotificationPriority.NORMAL;
|
|
|
|
const maxNotifications = 100;
|
|
const maxToasts = 5;
|
|
|
|
const model = {
|
|
notifications: Array(),
|
|
loading: false,
|
|
lastNotificationVersion: 0,
|
|
lastNotificationGuid: "",
|
|
unreadCount: 0,
|
|
unreadPrioCount: 0,
|
|
|
|
// NEW: Toast stack management
|
|
toastStack: Array(),
|
|
|
|
init() {
|
|
this.initialize();
|
|
},
|
|
|
|
// Initialize the notification store
|
|
initialize() {
|
|
this.loading = true;
|
|
this.updateUnreadCount();
|
|
// this.removeOldNotifications();
|
|
this.toastStack = [];
|
|
|
|
// // Auto-cleanup old notifications and toasts
|
|
// setInterval(() => {
|
|
// this.removeOldNotifications();
|
|
// this.cleanupExpiredToasts();
|
|
// }, 5 * 60 * 1000); // Every 5 minutes
|
|
},
|
|
|
|
// Update notifications from polling data
|
|
updateFromPoll(pollData) {
|
|
if (!pollData) return;
|
|
|
|
// Check if GUID changed (system restart)
|
|
if (pollData.notifications_guid !== this.lastNotificationGuid) {
|
|
this.lastNotificationVersion = 0;
|
|
this.notifications = [];
|
|
this.toastStack = []; // Clear toast stack on restart
|
|
this.lastNotificationGuid = pollData.notifications_guid || "";
|
|
}
|
|
|
|
// Process new notifications and add to toast stack
|
|
if (pollData.notifications && pollData.notifications.length > 0) {
|
|
pollData.notifications.forEach((notification) => {
|
|
// should we toast the notification?
|
|
const shouldToast = !notification.read;
|
|
|
|
// adjust notification data before adding
|
|
this.adjustNotificationData(notification);
|
|
|
|
const existingNotificationIndex = this.notifications.findIndex(
|
|
(n) => n.id === notification.id
|
|
);
|
|
const existingNotification =
|
|
existingNotificationIndex >= 0
|
|
? this.notifications[existingNotificationIndex]
|
|
: null;
|
|
const isNew = !existingNotification;
|
|
const shouldRetoast =
|
|
!!existingNotification &&
|
|
shouldToast &&
|
|
(existingNotification.timestamp !== notification.timestamp ||
|
|
existingNotification.title !== notification.title ||
|
|
existingNotification.message !== notification.message ||
|
|
existingNotification.detail !== notification.detail);
|
|
|
|
this.addOrUpdateNotification(notification);
|
|
|
|
// Add new unread notifications to toast stack, and also re-toast updated unread notifications
|
|
if ((isNew && shouldToast) || shouldRetoast) {
|
|
this.addToToastStack(notification);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update version tracking
|
|
this.lastNotificationVersion = pollData.notifications_version || 0;
|
|
this.lastNotificationGuid = pollData.notifications_guid || "";
|
|
|
|
// Update UI state
|
|
this.updateUnreadCount();
|
|
// this.removeOldNotifications();
|
|
},
|
|
|
|
adjustNotificationData(notification) {
|
|
// set default priority if not set
|
|
if (!notification.priority) {
|
|
notification.priority = defaultPriority;
|
|
}
|
|
},
|
|
|
|
getToastDisplayTime(toast) {
|
|
const displayTime = Number(toast?.display_time);
|
|
return Number.isFinite(displayTime) ? displayTime : 3;
|
|
},
|
|
|
|
isPersistentToast(toast) {
|
|
return this.getToastDisplayTime(toast) <= 0;
|
|
},
|
|
|
|
// NEW: Add notification to toast stack
|
|
addToToastStack(notification) {
|
|
// If notification has a group, remove any existing toasts with the same group
|
|
if (notification.group && notification.group.trim() !== "") {
|
|
const existingToast = this.toastStack.find(
|
|
(t) => t.group === notification.group
|
|
);
|
|
if (existingToast && existingToast.toastId)
|
|
this.removeFromToastStack(existingToast.toastId);
|
|
}
|
|
|
|
// Create toast object with auto-dismiss timer
|
|
const toast = {
|
|
...notification,
|
|
toastId: `toast-${notification.id}`,
|
|
addedAt: Date.now(),
|
|
autoRemoveTimer: null,
|
|
};
|
|
|
|
// Add to bottom of stack (newest at bottom)
|
|
this.toastStack.push(toast);
|
|
|
|
// Enforce max stack limit (remove oldest)
|
|
while (this.toastStack.length > maxToasts) {
|
|
const oldest = this.toastStack[0];
|
|
if (oldest && oldest.toastId) this.removeFromToastStack(oldest.toastId);
|
|
}
|
|
|
|
// Set auto-dismiss timer
|
|
this.restartToastTimer(toast.toastId);
|
|
},
|
|
|
|
clearToastTimer(toastId) {
|
|
const toastIndex = this.toastStack.findIndex((t) => t.toastId === toastId);
|
|
if (toastIndex < 0) return;
|
|
|
|
const toast = this.toastStack[toastIndex];
|
|
if (this.isPersistentToast(toast)) return;
|
|
|
|
if (toast.autoRemoveTimer) {
|
|
clearTimeout(toast.autoRemoveTimer);
|
|
toast.autoRemoveTimer = null;
|
|
}
|
|
},
|
|
|
|
restartToastTimer(toastId) {
|
|
const toastIndex = this.toastStack.findIndex((t) => t.toastId === toastId);
|
|
if (toastIndex < 0) return;
|
|
|
|
const toast = this.toastStack[toastIndex];
|
|
if (this.isPersistentToast(toast)) return;
|
|
|
|
this.clearToastTimer(toastId);
|
|
toast.autoRemoveTimer = setTimeout(() => {
|
|
this.removeFromToastStack(toast.toastId);
|
|
}, this.getToastDisplayTime(toast) * 1000);
|
|
},
|
|
|
|
// NEW: Remove toast from stack
|
|
removeFromToastStack(toastId, removedByUser = false) {
|
|
const index = this.toastStack.findIndex((t) => t.toastId === toastId);
|
|
if (index >= 0) {
|
|
const toast = this.toastStack[index];
|
|
if (toast.autoRemoveTimer) {
|
|
clearTimeout(toast.autoRemoveTimer);
|
|
toast.autoRemoveTimer = null;
|
|
}
|
|
this.toastStack.splice(index, 1);
|
|
|
|
// execute after toast removed callback
|
|
this.afterToastRemoved(toast, removedByUser);
|
|
}
|
|
},
|
|
|
|
// called by UI
|
|
dismissToast(toastId) {
|
|
this.removeFromToastStack(toastId, true);
|
|
},
|
|
|
|
async afterToastRemoved(toast, removedByUser = false) {
|
|
// if the toast is closed by the user OR timed out with normal priority, mark it as read
|
|
if (removedByUser || toast.priority <= NotificationPriority.NORMAL) {
|
|
this.markAsRead(toast.id);
|
|
}
|
|
},
|
|
|
|
// NEW: Clear entire toast stack
|
|
clearToastStack(withCallback = true, removedByUser = false) {
|
|
this.toastStack.forEach((toast) => {
|
|
if (toast.autoRemoveTimer) {
|
|
clearTimeout(toast.autoRemoveTimer);
|
|
toast.autoRemoveTimer = null;
|
|
}
|
|
if (withCallback) this.afterToastRemoved(toast, removedByUser);
|
|
});
|
|
this.toastStack = [];
|
|
},
|
|
|
|
// NEW: Clean up expired toasts (backup cleanup)
|
|
cleanupExpiredToasts() {
|
|
const now = Date.now();
|
|
this.toastStack = this.toastStack.filter((toast) => {
|
|
if (this.isPersistentToast(toast)) {
|
|
return true;
|
|
}
|
|
const age = now - toast.addedAt;
|
|
const maxAge = this.getToastDisplayTime(toast) * 1000;
|
|
|
|
if (age > maxAge) {
|
|
if (toast.autoRemoveTimer) {
|
|
clearTimeout(toast.autoRemoveTimer);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
},
|
|
|
|
// NEW: Handle toast click (opens modal)
|
|
async handleToastClick(toastId, event) {
|
|
const target = event?.target;
|
|
const toast = this.toastStack.find((t) => t.toastId === toastId);
|
|
if (
|
|
target instanceof Element &&
|
|
target.closest(
|
|
'button, a, input, select, textarea, summary, label, [role="button"], [data-toast-interactive]'
|
|
)
|
|
) {
|
|
if (toast?.id) {
|
|
this.markAsRead(toast.id);
|
|
}
|
|
return;
|
|
}
|
|
|
|
await this.openModal();
|
|
// Modal opening will clear toast stack via markAllAsRead
|
|
},
|
|
|
|
// Add or update a notification
|
|
addOrUpdateNotification(notification) {
|
|
const existingIndex = this.notifications.findIndex(
|
|
(n) => n.id === notification.id
|
|
);
|
|
|
|
if (existingIndex >= 0) {
|
|
// Update existing notification
|
|
this.notifications[existingIndex] = notification;
|
|
} else {
|
|
// Add new notification at the beginning (most recent first)
|
|
this.notifications.unshift(notification);
|
|
}
|
|
|
|
// Limit notifications to prevent memory issues (keep most recent)
|
|
if (this.notifications.length > maxNotifications) {
|
|
this.notifications = this.notifications.slice(0, maxNotifications);
|
|
}
|
|
},
|
|
|
|
// Update unread count
|
|
updateUnreadCount() {
|
|
const unread = this.notifications.filter((n) => !n.read).length;
|
|
const unreadPrio = this.notifications.filter(
|
|
(n) => !n.read && n.priority > NotificationPriority.NORMAL
|
|
).length;
|
|
if (this.unreadCount !== unread) this.unreadCount = unread;
|
|
if (this.unreadPrioCount !== unreadPrio) this.unreadPrioCount = unreadPrio;
|
|
},
|
|
|
|
// Mark notification as read
|
|
async markAsRead(notificationId) {
|
|
const notification = this.notifications.find(
|
|
(n) => n.id === notificationId
|
|
);
|
|
if (notification && !notification.read) {
|
|
notification.read = true;
|
|
this.updateUnreadCount();
|
|
|
|
// Sync with backend (non-blocking)
|
|
try {
|
|
await API.callJsonApi("notifications_mark_read", {
|
|
notification_ids: [notificationId],
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to sync notification read status:", error);
|
|
// Don't revert the UI change - user experience should not be affected
|
|
}
|
|
}
|
|
},
|
|
|
|
// Enhanced: Mark all as read and clear toast stack
|
|
async markAllAsRead() {
|
|
const unreadNotifications = this.notifications.filter((n) => !n.read);
|
|
if (unreadNotifications.length === 0) return;
|
|
|
|
// Update UI immediately
|
|
this.notifications.forEach((notification) => {
|
|
notification.read = true;
|
|
});
|
|
this.updateUnreadCount();
|
|
|
|
// Clear toast stack when marking all as read
|
|
this.clearToastStack(false);
|
|
|
|
// Sync with backend (non-blocking)
|
|
try {
|
|
await API.callJsonApi("notifications_mark_read", {
|
|
mark_all: true,
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to sync mark all as read:", error);
|
|
}
|
|
},
|
|
|
|
// Clear all notifications
|
|
async clearAll(syncBackend = true) {
|
|
this.notifications = [];
|
|
this.unreadCount = 0;
|
|
this.clearToastStack(false); // Also clear toast stack
|
|
this.clearBackendNotifications();
|
|
},
|
|
|
|
async clearBackendNotifications() {
|
|
try {
|
|
await API.callJsonApi("notifications_clear", null);
|
|
} catch (error) {
|
|
console.error("Failed to clear notifications:", error);
|
|
}
|
|
},
|
|
|
|
// Get notifications by type
|
|
getNotificationsByType(type) {
|
|
return this.notifications.filter((n) => n.type === type);
|
|
},
|
|
|
|
// Get notifications for display: ALL unread + read from last 5 minutes
|
|
getDisplayNotifications() {
|
|
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
|
|
|
return this.notifications.filter((notification) => {
|
|
// Always show unread notifications
|
|
if (!notification.read) {
|
|
return true;
|
|
}
|
|
|
|
// Show read notifications only if they're from the last 5 minutes
|
|
const notificationDate = new Date(notification.timestamp);
|
|
return notificationDate > fiveMinutesAgo;
|
|
});
|
|
},
|
|
|
|
// Get recent notifications (last 5) - kept for backwards compatibility
|
|
getRecentNotifications() {
|
|
return this.notifications.slice(0, 5);
|
|
},
|
|
|
|
// Get notification by ID
|
|
getNotificationById(id) {
|
|
return this.notifications.find((n) => n.id === id);
|
|
},
|
|
|
|
// Remove old notifications (older than 1 hour)
|
|
removeOldNotifications() {
|
|
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
|
const initialCount = this.notifications.length;
|
|
this.notifications = this.notifications.filter(
|
|
(n) => new Date(n.timestamp) > oneHourAgo
|
|
);
|
|
|
|
if (this.notifications.length !== initialCount) {
|
|
this.updateUnreadCount();
|
|
}
|
|
},
|
|
|
|
// Format timestamp for display
|
|
formatTimestamp(timestamp) {
|
|
const date = new Date(timestamp);
|
|
const now = new Date();
|
|
const diffMs = now - date;
|
|
const diffMins = diffMs / 60000;
|
|
const diffHours = diffMs / 3600000;
|
|
const diffDays = diffMs / 86400000;
|
|
|
|
if (diffMins < 0.15) return "Just now";
|
|
else if (diffMins < 1) return "Less than a minute ago";
|
|
else if (diffMins < 60) return `${Math.round(diffMins)}m ago`;
|
|
else if (diffHours < 24) return `${Math.round(diffHours)}h ago`;
|
|
else if (diffDays < 7) return `${Math.round(diffDays)}d ago`;
|
|
|
|
return formatDateTime(timestamp, "date");
|
|
},
|
|
|
|
// Get CSS class for notification type
|
|
getNotificationClass(type) {
|
|
const classes = {
|
|
info: "notification-info",
|
|
success: "notification-success",
|
|
warning: "notification-warning",
|
|
error: "notification-error",
|
|
progress: "notification-progress",
|
|
};
|
|
return classes[type] || "notification-info";
|
|
},
|
|
|
|
// Get CSS class for notification item including read state
|
|
getNotificationItemClass(notification) {
|
|
const typeClass = this.getNotificationClass(notification.type);
|
|
const readClass = notification.read ? "read" : "unread";
|
|
return `notification-item ${typeClass} ${readClass}`;
|
|
},
|
|
|
|
// Get icon for notification type (Google Material Icons)
|
|
getNotificationIcon(type) {
|
|
const icons = {
|
|
info: "info",
|
|
success: "check_circle",
|
|
warning: "warning",
|
|
error: "error",
|
|
progress: "hourglass_empty",
|
|
};
|
|
const iconName = icons[type] || "info";
|
|
return `<span class="material-symbols-outlined">${iconName}</span>`;
|
|
},
|
|
|
|
// Create notification via backend (will appear via polling)
|
|
async createNotification(
|
|
type,
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
try {
|
|
const response = await globalThis.sendJsonData("/notification_create", {
|
|
type: type,
|
|
message: message,
|
|
title: title,
|
|
detail: detail,
|
|
display_time: display_time,
|
|
group: group,
|
|
priority: priority,
|
|
});
|
|
|
|
if (response.success) {
|
|
return response.notification_id;
|
|
} else {
|
|
console.error("Failed to create notification:", response.error);
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error creating notification:", error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
// Convenience methods for different notification types
|
|
async info(
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
return await this.createNotification(
|
|
NotificationType.INFO,
|
|
message,
|
|
title,
|
|
detail,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
async success(
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
return await this.createNotification(
|
|
NotificationType.SUCCESS,
|
|
message,
|
|
title,
|
|
detail,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
async warning(
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
return await this.createNotification(
|
|
NotificationType.WARNING,
|
|
message,
|
|
title,
|
|
detail,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
async error(
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
return await this.createNotification(
|
|
NotificationType.ERROR,
|
|
message,
|
|
title,
|
|
detail,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
async progress(
|
|
message,
|
|
title = "",
|
|
detail = "",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
return await this.createNotification(
|
|
NotificationType.PROGRESS,
|
|
message,
|
|
title,
|
|
detail,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
// Enhanced: Open modal and clear toast stack
|
|
async openModal() {
|
|
// Clear toast stack when modal opens
|
|
this.clearToastStack(false);
|
|
// open modal
|
|
await openModal("notifications/notification-modal.html");
|
|
// mark all as read when modal closes
|
|
this.markAllAsRead();
|
|
},
|
|
|
|
// Legacy method for backward compatibility
|
|
toggleNotifications() {
|
|
this.openModal();
|
|
},
|
|
|
|
// NEW: Check if backend connection is available
|
|
isConnected() {
|
|
// Use the global connection status from index.js, but default to true if undefined
|
|
// This handles the case where polling hasn't run yet but backend is actually available
|
|
const pollingStatus =
|
|
typeof globalThis.getConnectionStatus === "function"
|
|
? globalThis.getConnectionStatus()
|
|
: undefined;
|
|
|
|
// If polling status is explicitly false, respect that
|
|
if (pollingStatus === false) {
|
|
return false;
|
|
}
|
|
|
|
// If polling status is undefined/true, assume backend is available
|
|
// (since the page loaded successfully, backend must be working)
|
|
return true;
|
|
},
|
|
|
|
// NEW: Add frontend-only toast directly to stack (renamed from original addFrontendToast)
|
|
addFrontendToastOnly(
|
|
type,
|
|
message,
|
|
title = "",
|
|
display_time = 5,
|
|
group = "",
|
|
priority = defaultPriority
|
|
) {
|
|
const timestamp = getCurrentUserISOString();
|
|
const notification = {
|
|
id: `frontend-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
type: type,
|
|
title: title,
|
|
message: message,
|
|
detail: "",
|
|
timestamp: timestamp,
|
|
display_time: display_time,
|
|
read: false,
|
|
frontend: true, // Mark as frontend-only
|
|
group: group,
|
|
priority: priority,
|
|
};
|
|
|
|
//adjust data before using
|
|
this.adjustNotificationData(notification);
|
|
|
|
// If notification has a group, remove any existing toasts with the same group
|
|
if (group && String(group).trim() !== "") {
|
|
const existingToastIndex = this.toastStack.findIndex(
|
|
(t) => t.group === group
|
|
);
|
|
|
|
if (existingToastIndex >= 0) {
|
|
const existingToast = this.toastStack[existingToastIndex];
|
|
this.removeFromToastStack(existingToast.toastId);
|
|
}
|
|
}
|
|
|
|
// Create toast object with auto-dismiss timer
|
|
const toast = {
|
|
...notification,
|
|
toastId: `toast-${notification.id}`,
|
|
addedAt: Date.now(),
|
|
autoRemoveTimer: null,
|
|
hoverTimer: null,
|
|
isHovered: false,
|
|
};
|
|
|
|
// Add to bottom of stack (newest at bottom)
|
|
this.toastStack.push(toast);
|
|
|
|
// Enforce max stack limit (remove oldest).
|
|
while (this.toastStack.length > maxToasts) {
|
|
const removed = this.toastStack.shift();
|
|
if (removed?.autoRemoveTimer) {
|
|
clearTimeout(removed.autoRemoveTimer);
|
|
}
|
|
}
|
|
|
|
// Set auto-dismiss timer
|
|
this.restartToastTimer(toast.toastId);
|
|
|
|
return notification.id;
|
|
},
|
|
|
|
// NEW: Enhanced frontend toast that tries backend first, falls back to frontend-only
|
|
async addFrontendToast(
|
|
type,
|
|
message,
|
|
title = "",
|
|
display_time = 5,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
// Try to send to backend first if connected
|
|
if (!frontendOnly) {
|
|
if (this.isConnected()) {
|
|
try {
|
|
const notificationId = await this.createNotification(
|
|
type,
|
|
message,
|
|
title,
|
|
"",
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
if (notificationId) {
|
|
// Backend handled it, notification will arrive via polling
|
|
return notificationId;
|
|
}
|
|
} catch (error) {
|
|
console.log(
|
|
`Backend unavailable for notification, showing as frontend-only: ${
|
|
error.message || error
|
|
}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("Backend disconnected, showing as frontend-only toast");
|
|
}
|
|
}
|
|
|
|
// Fallback to frontend-only toast
|
|
return this.addFrontendToastOnly(
|
|
type,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority
|
|
);
|
|
},
|
|
|
|
// NEW: Convenience methods for frontend notifications (updated to use new backend-first logic)
|
|
async frontendError(
|
|
message,
|
|
title = "Connection Error",
|
|
display_time = 8,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
return await this.addFrontendToast(
|
|
NotificationType.ERROR,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority,
|
|
frontendOnly
|
|
);
|
|
},
|
|
|
|
async frontendWarning(
|
|
message,
|
|
title = "Warning",
|
|
display_time = 5,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
return await this.addFrontendToast(
|
|
NotificationType.WARNING,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority,
|
|
frontendOnly
|
|
);
|
|
},
|
|
|
|
async frontendInfo(
|
|
message,
|
|
title = "Info",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
return await this.addFrontendToast(
|
|
NotificationType.INFO,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority,
|
|
frontendOnly
|
|
);
|
|
},
|
|
|
|
async frontendSuccess(
|
|
message,
|
|
title = "Success",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
return await this.addFrontendToast(
|
|
NotificationType.SUCCESS,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority,
|
|
frontendOnly
|
|
);
|
|
},
|
|
|
|
async frontendProgress(
|
|
message,
|
|
title = "Progress",
|
|
display_time = 3,
|
|
group = "",
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
) {
|
|
return await this.addFrontendToast(
|
|
NotificationType.PROGRESS,
|
|
message,
|
|
title,
|
|
display_time,
|
|
group,
|
|
priority,
|
|
frontendOnly
|
|
);
|
|
},
|
|
|
|
// NEW: Enhanced frontend toast with object parameters and type annotations
|
|
/**
|
|
* Adds a frontend toast notification with object parameters.
|
|
* @param {Object} options - The options for the toast notification.
|
|
* @param {string} options.type - The type of notification (e.g., info, success, error).
|
|
* @param {string} options.message - The message content of the notification.
|
|
* @param {string} [options.title=''] - The title of the notification.
|
|
* @param {number} [options.displayTime=5] - The display duration in seconds.
|
|
* @param {string} [options.group=''] - The group identifier for the notification.
|
|
* @param {string} [options.priority='medium'] - The priority of the notification.
|
|
* @param {boolean} [options.frontendOnly=false] - Whether to show only on frontend.
|
|
* @returns {Promise<string>} The ID of the added notification.
|
|
*/
|
|
async frontendNotification({
|
|
type,
|
|
message,
|
|
title = '',
|
|
displayTime = 5,
|
|
group = '',
|
|
priority = defaultPriority,
|
|
frontendOnly = false
|
|
}) {
|
|
return await this.addFrontendToast(type, message, title, displayTime, group, priority, frontendOnly);
|
|
},
|
|
};
|
|
|
|
// Create and export the store
|
|
const store = createStore("notificationStore", model);
|
|
export { store };
|
|
|
|
// export toast functions
|
|
const toastFrontendInfo = store.frontendInfo.bind(store);
|
|
const toastFrontendSuccess = store.frontendSuccess.bind(store);
|
|
const toastFrontendWarning = store.frontendWarning.bind(store);
|
|
const toastFrontendError = store.frontendError.bind(store);
|
|
const toastFrontendProgress = store.frontendProgress.bind(store);
|
|
|
|
export {
|
|
toastFrontendInfo,
|
|
toastFrontendSuccess,
|
|
toastFrontendWarning,
|
|
toastFrontendError,
|
|
toastFrontendProgress,
|
|
};
|
|
|
|
// add toasts to global for backward compatibility with older scripts
|
|
globalThis.toastFrontendInfo = toastFrontendInfo;
|
|
globalThis.toastFrontendSuccess = toastFrontendSuccess;
|
|
globalThis.toastFrontendWarning = toastFrontendWarning;
|
|
globalThis.toastFrontendError = toastFrontendError;
|
|
globalThis.toastFrontendProgress = toastFrontendProgress;
|