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
+ });
}