From bfea0cdbab19907a4bfa260f785a5334e8d8db32 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Mon, 28 Jul 2025 00:28:15 +0200 Subject: [PATCH] Enhance notification system and auto-correct node_id Adds a modern, type-based notification UI with support for success, error, info, warning, and alert styles, including a new showAlertNotification function. CanvasState now auto-corrects the node_id widget before saving state and notifies the user if a correction occurs. CanvasView centering logic now uses the actual canvas container for more accurate viewport adjustments. --- js/CanvasState.js | 15 ++ js/CanvasView.js | 12 +- js/utils/NotificationUtils.js | 241 ++++++++++++++++++++++++++--- src/CanvasState.ts | 18 +++ src/utils/NotificationUtils.ts | 269 ++++++++++++++++++++++++++++++--- 5 files changed, 515 insertions(+), 40 deletions(-) diff --git a/js/CanvasState.js b/js/CanvasState.js index e717510..0b7745b 100644 --- a/js/CanvasState.js +++ b/js/CanvasState.js @@ -1,5 +1,6 @@ import { getCanvasState, setCanvasState, saveImage, getImage } from "./db.js"; import { createModuleLogger } from "./utils/LoggerUtils.js"; +import { showAlertNotification } from "./utils/NotificationUtils.js"; import { generateUUID, cloneLayers, getStateSignature, debounce, createCanvas } from "./utils/CommonUtils.js"; const log = createModuleLogger('CanvasState'); export class CanvasState { @@ -237,6 +238,20 @@ export class CanvasState { log.error("Node ID is not available for saving state to DB."); return; } + // Auto-correct node_id widget if needed before saving state + if (this.canvas.node && this.canvas.node.widgets) { + const nodeIdWidget = this.canvas.node.widgets.find((w) => w.name === "node_id"); + if (nodeIdWidget) { + const correctId = String(this.canvas.node.id); + if (nodeIdWidget.value !== correctId) { + const prevValue = nodeIdWidget.value; + nodeIdWidget.value = correctId; + log.warn(`[CanvasState] node_id widget value (${prevValue}) did not match node.id (${correctId}) - auto-corrected (saveStateToDB).`); + showAlertNotification(`The value of node_id (${prevValue}) did not match the node number (${correctId}) and was automatically corrected. +If you see dark images or masks in the output, make sure node_id is set to ${correctId}.`); + } + } + } log.info("Preparing state to be sent to worker..."); const layers = await this._prepareLayers(); const state = { diff --git a/js/CanvasView.js b/js/CanvasView.js index 88fc5a1..d807b2e 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -774,6 +774,11 @@ async function createCanvasWidget(node, widget, app) { // Remove ESC key listener when editor closes document.removeEventListener('keydown', handleEscKey); setTimeout(() => { + // Use the actual canvas container for centering calculation + const currentCanvasContainer = originalParent.querySelector('.painterCanvasContainer.painter-container'); + const fullscreenCanvasContainer = backdrop.querySelector('.painterCanvasContainer.painter-container'); + const currentRect = currentCanvasContainer.getBoundingClientRect(); + const fullscreenRect = fullscreenCanvasContainer.getBoundingClientRect(); adjustViewportForCentering(currentRect, fullscreenRect, -1); canvas.render(); if (node.onResize) { @@ -811,8 +816,11 @@ async function createCanvasWidget(node, widget, app) { // Add ESC key listener when editor opens document.addEventListener('keydown', handleEscKey); setTimeout(() => { - const originalRect = originalParent.getBoundingClientRect(); - const fullscreenRect = modalContent.getBoundingClientRect(); + // Use the actual canvas container for centering calculation + const originalCanvasContainer = originalParent.querySelector('.painterCanvasContainer.painter-container'); + const fullscreenCanvasContainer = modalContent.querySelector('.painterCanvasContainer.painter-container'); + const originalRect = originalCanvasContainer.getBoundingClientRect(); + const fullscreenRect = fullscreenCanvasContainer.getBoundingClientRect(); adjustViewportForCentering(originalRect, fullscreenRect, 1); canvas.render(); if (node.onResize) { diff --git a/js/utils/NotificationUtils.js b/js/utils/NotificationUtils.js index 01ba03d..7172010 100644 --- a/js/utils/NotificationUtils.js +++ b/js/utils/NotificationUtils.js @@ -9,28 +9,188 @@ const log = createModuleLogger('NotificationUtils'); * @param backgroundColor - Background color (default: #4a6cd4) * @param duration - Duration in milliseconds (default: 3000) */ -export function showNotification(message, backgroundColor = "#4a6cd4", duration = 3000) { +export function showNotification(message, backgroundColor = "#4a6cd4", duration = 3000, type = "info") { + // Remove any existing prefix to avoid double prefixing + message = message.replace(/^\[Layer Forge\]\s*/, ""); + // Type-specific config + const config = { + success: { + icon: "✔️", + title: "Success", + bg: "#1fd18b", + color: "#155c3b" + }, + error: { + icon: "❌", + title: "Error", + bg: "#ff6f6f", + color: "#7a2323" + }, + info: { + icon: "ℹ️", + title: "Info", + bg: "#4a6cd4", + color: "#fff" + }, + warning: { + icon: "⚠️", + title: "Warning", + bg: "#ffd43b", + color: "#7a5c00" + }, + alert: { + icon: "⚠️", + title: "Alert", + bg: "#fff7cc", + color: "#7a5c00" + } + }[type]; + // --- Dark, modern notification style with sticky header --- const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; - top: 20px; - right: 20px; - background: ${backgroundColor}; - color: white; - padding: 12px 16px; - border-radius: 4px; - box-shadow: 0 2px 10px rgba(0,0,0,0.3); + top: 24px; + right: 24px; + min-width: 380px; + max-width: 440px; + max-height: 80vh; + background: rgba(30, 32, 41, 0.9); + color: #fff; + border-radius: 12px; + box-shadow: 0 4px 32px rgba(0,0,0,0.25); z-index: 10001; font-size: 14px; + display: flex; + flex-direction: column; + padding: 0; + margin-bottom: 18px; + font-family: 'Segoe UI', 'Arial', sans-serif; + overflow: hidden; + border: 1px solid rgba(80, 80, 80, 0.5); + backdrop-filter: blur(8px); + animation: lf-fadein 0.2s; `; - notification.textContent = message; + // --- Header (non-scrollable) --- + const header = document.createElement('div'); + header.style.cssText = ` + display: flex; + align-items: flex-start; + padding: 16px 20px; + position: relative; + flex-shrink: 0; + `; + const leftBar = document.createElement('div'); + leftBar.style.cssText = ` + position: absolute; + left: 0; top: 0; bottom: 0; + width: 6px; + background: ${config.bg}; + box-shadow: 0 0 12px ${config.bg}; + border-radius: 3px 0 0 3px; + `; + const iconContainer = document.createElement('div'); + iconContainer.style.cssText = ` + width: 48px; height: 48px; + min-width: 48px; min-height: 48px; + display: flex; align-items: center; justify-content: center; + margin-left: 18px; margin-right: 18px; + `; + iconContainer.innerHTML = { + success: ``, + error: ``, + info: ``, + warning: ``, + alert: `` + }[type]; + const headerTextContent = document.createElement('div'); + headerTextContent.style.cssText = `display: flex; flex-direction: column; justify-content: center; flex: 1; min-width: 0;`; + const titleSpan = document.createElement('div'); + titleSpan.style.cssText = `font-weight: 700; font-size: 16px; margin-bottom: 4px; color: #fff; text-transform: uppercase; letter-spacing: 0.5px;`; + titleSpan.textContent = config.title; + headerTextContent.appendChild(titleSpan); + const topRightContainer = document.createElement('div'); + topRightContainer.style.cssText = `position: absolute; top: 14px; right: 18px; display: flex; align-items: center; gap: 12px;`; + const tag = document.createElement('span'); + tag.style.cssText = `font-size: 11px; font-weight: 600; color: #fff; background: ${config.bg}; border-radius: 4px; padding: 2px 8px; box-shadow: 0 0 8px ${config.bg};`; + tag.innerHTML = '🎨 Layer Forge'; + const getTextColorForBg = (hexColor) => { + const r = parseInt(hexColor.slice(1, 3), 16), g = parseInt(hexColor.slice(3, 5), 16), b = parseInt(hexColor.slice(5, 7), 16); + return ((0.299 * r + 0.587 * g + 0.114 * b) / 255) > 0.5 ? '#000' : '#fff'; + }; + tag.style.color = getTextColorForBg(config.bg); + const closeBtn = document.createElement('button'); + closeBtn.innerHTML = '×'; + closeBtn.setAttribute("aria-label", "Close notification"); + closeBtn.style.cssText = `background: none; border: none; color: #ccc; font-size: 22px; font-weight: bold; cursor: pointer; padding: 0; opacity: 0.7; transition: opacity 0.15s; line-height: 1;`; + closeBtn.onclick = () => { if (notification.parentNode) + notification.parentNode.removeChild(notification); }; + topRightContainer.appendChild(tag); + topRightContainer.appendChild(closeBtn); + header.appendChild(iconContainer); + header.appendChild(headerTextContent); + header.appendChild(topRightContainer); + // --- Scrollable Body --- + const body = document.createElement('div'); + body.style.cssText = ` + padding: 0px 20px 16px 20px; /* Adjusted left padding */ + overflow-y: auto; + flex: 1; + `; + const msgSpan = document.createElement('div'); + msgSpan.style.cssText = `font-size: 14px; color: #ccc; line-height: 1.5; white-space: pre-wrap; word-break: break-word;`; + msgSpan.textContent = message; + body.appendChild(msgSpan); + // --- Progress Bar (non-scrollable) --- + const progressBar = document.createElement('div'); + progressBar.style.cssText = ` + height: 4px; + width: 100%; + background: ${config.bg}; + box-shadow: 0 0 12px ${config.bg}; + transform-origin: left; + animation: lf-progress ${duration / 1000}s linear; + flex-shrink: 0; + `; + notification.appendChild(leftBar); // Add bar to main container + notification.appendChild(header); + notification.appendChild(body); + notification.appendChild(progressBar); document.body.appendChild(notification); - setTimeout(() => { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }, duration); - log.debug(`Notification shown: ${message}`); + // --- Keyframes and Timer Logic --- + const styleSheet = document.createElement("style"); + styleSheet.type = "text/css"; + styleSheet.innerText = ` + @keyframes lf-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } } + @keyframes lf-progress-rewind { to { transform: scaleX(1); } } + @keyframes lf-fadein { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } + .notification-scrollbar::-webkit-scrollbar { width: 8px; } + .notification-scrollbar::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; } + .notification-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.25); border-radius: 4px; } + .notification-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); } + `; + body.classList.add('notification-scrollbar'); + document.head.appendChild(styleSheet); + let dismissTimeout = null; + const startDismissTimer = () => { + dismissTimeout = window.setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, duration); + progressBar.style.animation = `lf-progress ${duration / 1000}s linear`; + }; + const pauseAndRewindTimer = () => { + if (dismissTimeout !== null) + clearTimeout(dismissTimeout); + dismissTimeout = null; + const computedStyle = window.getComputedStyle(progressBar); + progressBar.style.transform = computedStyle.transform; + progressBar.style.animation = 'lf-progress-rewind 0.5s ease-out forwards'; + }; + notification.addEventListener('mouseenter', pauseAndRewindTimer); + notification.addEventListener('mouseleave', startDismissTimer); + startDismissTimer(); + log.debug(`Notification shown: [Layer Forge] ${message}`); } /** * Shows a success notification @@ -38,7 +198,7 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration * @param duration - Duration in milliseconds (default: 3000) */ export function showSuccessNotification(message, duration = 3000) { - showNotification(message, "#4a7c59", duration); + showNotification(message, undefined, duration, "success"); } /** * Shows an error notification @@ -46,7 +206,7 @@ export function showSuccessNotification(message, duration = 3000) { * @param duration - Duration in milliseconds (default: 5000) */ export function showErrorNotification(message, duration = 5000) { - showNotification(message, "#c54747", duration); + showNotification(message, undefined, duration, "error"); } /** * Shows an info notification @@ -54,5 +214,50 @@ export function showErrorNotification(message, duration = 5000) { * @param duration - Duration in milliseconds (default: 3000) */ export function showInfoNotification(message, duration = 3000) { - showNotification(message, "#4a6cd4", duration); + showNotification(message, undefined, duration, "info"); +} +/** + * Shows a warning notification + * @param message - The message to show + * @param duration - Duration in milliseconds (default: 3000) + */ +export function showWarningNotification(message, duration = 3000) { + showNotification(message, undefined, duration, "warning"); +} +/** + * Shows an alert notification + * @param message - The message to show + * @param duration - Duration in milliseconds (default: 3000) + */ +export function showAlertNotification(message, duration = 3000) { + showNotification(message, undefined, duration, "alert"); +} +/** + * Shows a sequence of all notification types for debugging purposes. + * @param message - An optional message to display in all notification types. + */ +export function showAllNotificationTypes(message) { + const types = ["success", "error", "info", "warning", "alert"]; + types.forEach((type, index) => { + const notificationMessage = message || `This is a '${type}' notification.`; + setTimeout(() => { + switch (type) { + case "success": + showSuccessNotification(notificationMessage); + break; + case "error": + showErrorNotification(notificationMessage); + break; + case "info": + showInfoNotification(notificationMessage); + break; + case "warning": + showWarningNotification(notificationMessage); + break; + case "alert": + showAlertNotification(notificationMessage); + break; + } + }, index * 400); // Stagger the notifications + }); } diff --git a/src/CanvasState.ts b/src/CanvasState.ts index c58f0e7..82461d2 100644 --- a/src/CanvasState.ts +++ b/src/CanvasState.ts @@ -1,5 +1,6 @@ import {getCanvasState, setCanvasState, saveImage, getImage} from "./db.js"; import {createModuleLogger} from "./utils/LoggerUtils.js"; +import {showAlertNotification} from "./utils/NotificationUtils.js"; import {generateUUID, cloneLayers, getStateSignature, debounce, createCanvas} from "./utils/CommonUtils.js"; import {withErrorHandling} from "./ErrorHandler.js"; import type { Canvas } from './Canvas'; @@ -272,6 +273,23 @@ export class CanvasState { return; } + // Auto-correct node_id widget if needed before saving state + if (this.canvas.node && this.canvas.node.widgets) { + const nodeIdWidget = this.canvas.node.widgets.find((w: any) => w.name === "node_id"); + if (nodeIdWidget) { + const correctId = String(this.canvas.node.id); + if (nodeIdWidget.value !== correctId) { + const prevValue = nodeIdWidget.value; + nodeIdWidget.value = correctId; + log.warn(`[CanvasState] node_id widget value (${prevValue}) did not match node.id (${correctId}) - auto-corrected (saveStateToDB).`); + showAlertNotification( + `The value of node_id (${prevValue}) did not match the node number (${correctId}) and was automatically corrected. +If you see dark images or masks in the output, make sure node_id is set to ${correctId}.` + ); + } + } + } + log.info("Preparing state to be sent to worker..."); const layers = await this._prepareLayers(); const state = { diff --git a/src/utils/NotificationUtils.ts b/src/utils/NotificationUtils.ts index 466a2ff..10ac062 100644 --- a/src/utils/NotificationUtils.ts +++ b/src/utils/NotificationUtils.ts @@ -12,30 +12,210 @@ const log = createModuleLogger('NotificationUtils'); * @param backgroundColor - Background color (default: #4a6cd4) * @param duration - Duration in milliseconds (default: 3000) */ -export function showNotification(message: string, backgroundColor: string = "#4a6cd4", duration: number = 3000): void { +export function showNotification( + message: string, + backgroundColor: string = "#4a6cd4", + duration: number = 3000, + type: "success" | "error" | "info" | "warning" | "alert" = "info" +): void { + // Remove any existing prefix to avoid double prefixing + message = message.replace(/^\[Layer Forge\]\s*/, ""); + + // Type-specific config + const config = { + success: { + icon: "✔️", + title: "Success", + bg: "#1fd18b", + color: "#155c3b" + }, + error: { + icon: "❌", + title: "Error", + bg: "#ff6f6f", + color: "#7a2323" + }, + info: { + icon: "ℹ️", + title: "Info", + bg: "#4a6cd4", + color: "#fff" + }, + warning: { + icon: "⚠️", + title: "Warning", + bg: "#ffd43b", + color: "#7a5c00" + }, + alert: { + icon: "⚠️", + title: "Alert", + bg: "#fff7cc", + color: "#7a5c00" + } + }[type]; + + // --- Dark, modern notification style with sticky header --- const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; - top: 20px; - right: 20px; - background: ${backgroundColor}; - color: white; - padding: 12px 16px; - border-radius: 4px; - box-shadow: 0 2px 10px rgba(0,0,0,0.3); + top: 24px; + right: 24px; + min-width: 380px; + max-width: 440px; + max-height: 80vh; + background: rgba(30, 32, 41, 0.9); + color: #fff; + border-radius: 12px; + box-shadow: 0 4px 32px rgba(0,0,0,0.25); z-index: 10001; font-size: 14px; + display: flex; + flex-direction: column; + padding: 0; + margin-bottom: 18px; + font-family: 'Segoe UI', 'Arial', sans-serif; + overflow: hidden; + border: 1px solid rgba(80, 80, 80, 0.5); + backdrop-filter: blur(8px); + animation: lf-fadein 0.2s; `; - notification.textContent = message; + + // --- Header (non-scrollable) --- + const header = document.createElement('div'); + header.style.cssText = ` + display: flex; + align-items: flex-start; + padding: 16px 20px; + position: relative; + flex-shrink: 0; + `; + + const leftBar = document.createElement('div'); + leftBar.style.cssText = ` + position: absolute; + left: 0; top: 0; bottom: 0; + width: 6px; + background: ${config.bg}; + box-shadow: 0 0 12px ${config.bg}; + border-radius: 3px 0 0 3px; + `; + + const iconContainer = document.createElement('div'); + iconContainer.style.cssText = ` + width: 48px; height: 48px; + min-width: 48px; min-height: 48px; + display: flex; align-items: center; justify-content: center; + margin-left: 18px; margin-right: 18px; + `; + iconContainer.innerHTML = { + success: ``, + error: ``, + info: ``, + warning: ``, + alert: `` + }[type]; + + const headerTextContent = document.createElement('div'); + headerTextContent.style.cssText = `display: flex; flex-direction: column; justify-content: center; flex: 1; min-width: 0;`; + + const titleSpan = document.createElement('div'); + titleSpan.style.cssText = `font-weight: 700; font-size: 16px; margin-bottom: 4px; color: #fff; text-transform: uppercase; letter-spacing: 0.5px;`; + titleSpan.textContent = config.title; + headerTextContent.appendChild(titleSpan); + + const topRightContainer = document.createElement('div'); + topRightContainer.style.cssText = `position: absolute; top: 14px; right: 18px; display: flex; align-items: center; gap: 12px;`; + + const tag = document.createElement('span'); + tag.style.cssText = `font-size: 11px; font-weight: 600; color: #fff; background: ${config.bg}; border-radius: 4px; padding: 2px 8px; box-shadow: 0 0 8px ${config.bg};`; + tag.innerHTML = '🎨 Layer Forge'; + const getTextColorForBg = (hexColor: string): string => { + const r = parseInt(hexColor.slice(1, 3), 16), g = parseInt(hexColor.slice(3, 5), 16), b = parseInt(hexColor.slice(5, 7), 16); + return ((0.299 * r + 0.587 * g + 0.114 * b) / 255) > 0.5 ? '#000' : '#fff'; + }; + tag.style.color = getTextColorForBg(config.bg); + + const closeBtn = document.createElement('button'); + closeBtn.innerHTML = '×'; + closeBtn.setAttribute("aria-label", "Close notification"); + closeBtn.style.cssText = `background: none; border: none; color: #ccc; font-size: 22px; font-weight: bold; cursor: pointer; padding: 0; opacity: 0.7; transition: opacity 0.15s; line-height: 1;`; + closeBtn.onclick = () => { if (notification.parentNode) notification.parentNode.removeChild(notification); }; + + topRightContainer.appendChild(tag); + topRightContainer.appendChild(closeBtn); + + header.appendChild(iconContainer); + header.appendChild(headerTextContent); + header.appendChild(topRightContainer); + + // --- Scrollable Body --- + const body = document.createElement('div'); + body.style.cssText = ` + padding: 0px 20px 16px 20px; /* Adjusted left padding */ + overflow-y: auto; + flex: 1; + `; + + const msgSpan = document.createElement('div'); + msgSpan.style.cssText = `font-size: 14px; color: #ccc; line-height: 1.5; white-space: pre-wrap; word-break: break-word;`; + msgSpan.textContent = message; + body.appendChild(msgSpan); + + // --- Progress Bar (non-scrollable) --- + const progressBar = document.createElement('div'); + progressBar.style.cssText = ` + height: 4px; + width: 100%; + background: ${config.bg}; + box-shadow: 0 0 12px ${config.bg}; + transform-origin: left; + animation: lf-progress ${duration / 1000}s linear; + flex-shrink: 0; + `; + + notification.appendChild(leftBar); // Add bar to main container + notification.appendChild(header); + notification.appendChild(body); + notification.appendChild(progressBar); document.body.appendChild(notification); - - setTimeout(() => { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }, duration); - - log.debug(`Notification shown: ${message}`); + + // --- Keyframes and Timer Logic --- + const styleSheet = document.createElement("style"); + styleSheet.type = "text/css"; + styleSheet.innerText = ` + @keyframes lf-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } } + @keyframes lf-progress-rewind { to { transform: scaleX(1); } } + @keyframes lf-fadein { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } + .notification-scrollbar::-webkit-scrollbar { width: 8px; } + .notification-scrollbar::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; } + .notification-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.25); border-radius: 4px; } + .notification-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); } + `; + body.classList.add('notification-scrollbar'); + document.head.appendChild(styleSheet); + + let dismissTimeout: number | null = null; + const startDismissTimer = () => { + dismissTimeout = window.setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, duration); + progressBar.style.animation = `lf-progress ${duration / 1000}s linear`; + }; + const pauseAndRewindTimer = () => { + if (dismissTimeout !== null) clearTimeout(dismissTimeout); + dismissTimeout = null; + const computedStyle = window.getComputedStyle(progressBar); + progressBar.style.transform = computedStyle.transform; + progressBar.style.animation = 'lf-progress-rewind 0.5s ease-out forwards'; + }; + notification.addEventListener('mouseenter', pauseAndRewindTimer); + notification.addEventListener('mouseleave', startDismissTimer); + startDismissTimer(); + + log.debug(`Notification shown: [Layer Forge] ${message}`); } /** @@ -44,7 +224,7 @@ export function showNotification(message: string, backgroundColor: string = "#4a * @param duration - Duration in milliseconds (default: 3000) */ export function showSuccessNotification(message: string, duration: number = 3000): void { - showNotification(message, "#4a7c59", duration); + showNotification(message, undefined, duration, "success"); } /** @@ -53,7 +233,7 @@ export function showSuccessNotification(message: string, duration: number = 3000 * @param duration - Duration in milliseconds (default: 5000) */ export function showErrorNotification(message: string, duration: number = 5000): void { - showNotification(message, "#c54747", duration); + showNotification(message, undefined, duration, "error"); } /** @@ -62,5 +242,54 @@ export function showErrorNotification(message: string, duration: number = 5000): * @param duration - Duration in milliseconds (default: 3000) */ export function showInfoNotification(message: string, duration: number = 3000): void { - showNotification(message, "#4a6cd4", duration); + showNotification(message, undefined, duration, "info"); +} + +/** + * Shows a warning notification + * @param message - The message to show + * @param duration - Duration in milliseconds (default: 3000) + */ +export function showWarningNotification(message: string, duration: number = 3000): void { + showNotification(message, undefined, duration, "warning"); +} + +/** + * Shows an alert notification + * @param message - The message to show + * @param duration - Duration in milliseconds (default: 3000) + */ +export function showAlertNotification(message: string, duration: number = 3000): void { + showNotification(message, undefined, duration, "alert"); +} + +/** + * Shows a sequence of all notification types for debugging purposes. + * @param message - An optional message to display in all notification types. + */ +export function showAllNotificationTypes(message?: string): void { + const types: ("success" | "error" | "info" | "warning" | "alert")[] = ["success", "error", "info", "warning", "alert"]; + + types.forEach((type, index) => { + const notificationMessage = message || `This is a '${type}' notification.`; + setTimeout(() => { + switch (type) { + case "success": + showSuccessNotification(notificationMessage); + break; + case "error": + showErrorNotification(notificationMessage); + break; + case "info": + showInfoNotification(notificationMessage); + break; + case "warning": + showWarningNotification(notificationMessage); + break; + case "alert": + showAlertNotification(notificationMessage); + break; + } + }, index * 400); // Stagger the notifications + }); }