mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
feat: add notification system with deduplication
Implemented a comprehensive notification system with smart deduplication for LayerForge's "Paste Image" operations. The system prevents duplicate error/warning notifications while providing clear feedback for all clipboard operations including success, failure, and edge cases.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { createModuleLogger } from "./LoggerUtils.js";
|
||||
import { showNotification, showInfoNotification } from "./NotificationUtils.js";
|
||||
import { showNotification, showInfoNotification, showErrorNotification, showWarningNotification } from "./NotificationUtils.js";
|
||||
import { withErrorHandling, createValidationError, createNetworkError, createFileError } from "../ErrorHandler.js";
|
||||
import { safeClipspacePaste } from "./ClipspaceUtils.js";
|
||||
// @ts-ignore
|
||||
@@ -18,6 +18,7 @@ export class ClipboardManager {
|
||||
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
||||
log.info("Found layers in internal clipboard, pasting layers");
|
||||
this.canvas.canvasLayers.pasteLayers();
|
||||
showInfoNotification("Layers pasted from internal clipboard");
|
||||
return true;
|
||||
}
|
||||
if (preference === 'clipspace') {
|
||||
@@ -27,9 +28,20 @@ export class ClipboardManager {
|
||||
return true;
|
||||
}
|
||||
log.info("No image found in ComfyUI Clipspace");
|
||||
// Don't show error here, will try system clipboard next
|
||||
}
|
||||
log.info("Attempting paste from system clipboard");
|
||||
return await this.trySystemClipboardPaste(addMode);
|
||||
const systemSuccess = await this.trySystemClipboardPaste(addMode);
|
||||
if (!systemSuccess) {
|
||||
// No valid image found in any clipboard
|
||||
if (preference === 'clipspace') {
|
||||
showWarningNotification("No valid image found in Clipspace or system clipboard");
|
||||
}
|
||||
else {
|
||||
showWarningNotification("No valid image found in clipboard");
|
||||
}
|
||||
}
|
||||
return systemSuccess;
|
||||
}, 'ClipboardManager.handlePaste');
|
||||
/**
|
||||
* Attempts to paste from ComfyUI Clipspace
|
||||
@@ -51,6 +63,7 @@ export class ClipboardManager {
|
||||
const img = new Image();
|
||||
img.onload = async () => {
|
||||
await this.canvas.canvasLayers.addLayerWithImage(img, {}, addMode);
|
||||
showInfoNotification("Image pasted from Clipspace");
|
||||
};
|
||||
img.src = clipspaceImage.src;
|
||||
return true;
|
||||
@@ -96,6 +109,7 @@ export class ClipboardManager {
|
||||
img.onload = async () => {
|
||||
log.info("Successfully loaded image from backend response");
|
||||
await this.canvas.canvasLayers.addLayerWithImage(img, {}, addMode);
|
||||
showInfoNotification("Image loaded from file path");
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
@@ -131,6 +145,7 @@ export class ClipboardManager {
|
||||
img.onload = async () => {
|
||||
log.info("Successfully loaded image from system clipboard");
|
||||
await this.canvas.canvasLayers.addLayerWithImage(img, {}, addMode);
|
||||
showInfoNotification("Image pasted from system clipboard");
|
||||
};
|
||||
if (event.target?.result) {
|
||||
img.src = event.target.result;
|
||||
@@ -252,10 +267,12 @@ export class ClipboardManager {
|
||||
img.onload = async () => {
|
||||
log.info("Successfully loaded image from URL");
|
||||
await this.canvas.canvasLayers.addLayerWithImage(img, {}, addMode);
|
||||
showInfoNotification("Image loaded from URL");
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
log.warn("Failed to load image from URL:", filePath);
|
||||
showErrorNotification(`Failed to load image from URL\nThe link might be incorrect or may not point to an image file.: ${filePath}`, 5000, true);
|
||||
resolve(false);
|
||||
};
|
||||
img.src = filePath;
|
||||
@@ -313,6 +330,7 @@ export class ClipboardManager {
|
||||
img.onload = async () => {
|
||||
log.info("Successfully loaded image from file picker");
|
||||
await this.canvas.canvasLayers.addLayerWithImage(img, {}, addMode);
|
||||
showInfoNotification("Image loaded from selected file");
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createModuleLogger } from "./LoggerUtils.js";
|
||||
const log = createModuleLogger('NotificationUtils');
|
||||
// Store active notifications for deduplication
|
||||
const activeNotifications = new Map();
|
||||
/**
|
||||
* Utility functions for showing notifications to the user
|
||||
*/
|
||||
@@ -8,10 +10,50 @@ const log = createModuleLogger('NotificationUtils');
|
||||
* @param message - The message to show
|
||||
* @param backgroundColor - Background color (default: #4a6cd4)
|
||||
* @param duration - Duration in milliseconds (default: 3000)
|
||||
* @param type - Type of notification
|
||||
* @param deduplicate - If true, will not show duplicate messages and will refresh existing ones (default: false)
|
||||
*/
|
||||
export function showNotification(message, backgroundColor = "#4a6cd4", duration = 3000, type = "info") {
|
||||
export function showNotification(message, backgroundColor = "#4a6cd4", duration = 3000, type = "info", deduplicate = false) {
|
||||
// Remove any existing prefix to avoid double prefixing
|
||||
message = message.replace(/^\[Layer Forge\]\s*/, "");
|
||||
// If deduplication is enabled, check if this message already exists
|
||||
if (deduplicate) {
|
||||
const existingNotification = activeNotifications.get(message);
|
||||
if (existingNotification) {
|
||||
log.debug(`Notification already exists, refreshing timer: ${message}`);
|
||||
// Clear existing timeout
|
||||
if (existingNotification.timeout !== null) {
|
||||
clearTimeout(existingNotification.timeout);
|
||||
}
|
||||
// Find the progress bar and restart its animation
|
||||
const progressBar = existingNotification.element.querySelector('div[style*="animation"]');
|
||||
if (progressBar) {
|
||||
// Reset animation
|
||||
progressBar.style.animation = 'none';
|
||||
// Force reflow
|
||||
void progressBar.offsetHeight;
|
||||
// Restart animation
|
||||
progressBar.style.animation = `lf-progress ${duration / 1000}s linear`;
|
||||
}
|
||||
// Set new timeout
|
||||
const newTimeout = window.setTimeout(() => {
|
||||
const notification = existingNotification.element;
|
||||
notification.style.animation = 'lf-fadeout 0.3s ease-out forwards';
|
||||
notification.addEventListener('animationend', () => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
activeNotifications.delete(message);
|
||||
const container = document.getElementById('lf-notification-container');
|
||||
if (container && container.children.length === 0) {
|
||||
container.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, duration);
|
||||
existingNotification.timeout = newTimeout;
|
||||
return; // Don't create a new notification
|
||||
}
|
||||
}
|
||||
// Type-specific config
|
||||
const config = {
|
||||
success: { icon: "✔️", title: "Success", bg: "#1fd18b" },
|
||||
@@ -148,6 +190,10 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
body.classList.add('notification-scrollbar');
|
||||
let dismissTimeout = null;
|
||||
const closeNotification = () => {
|
||||
// Remove from active notifications map if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.delete(message);
|
||||
}
|
||||
notification.style.animation = 'lf-fadeout 0.3s ease-out forwards';
|
||||
notification.addEventListener('animationend', () => {
|
||||
if (notification.parentNode) {
|
||||
@@ -171,40 +217,77 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
progressBar.style.transform = computedStyle.transform;
|
||||
progressBar.style.animation = 'lf-progress-rewind 0.5s ease-out forwards';
|
||||
};
|
||||
notification.addEventListener('mouseenter', pauseAndRewindTimer);
|
||||
notification.addEventListener('mouseleave', startDismissTimer);
|
||||
notification.addEventListener('mouseenter', () => {
|
||||
pauseAndRewindTimer();
|
||||
// Update stored timeout if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
notification.addEventListener('mouseleave', () => {
|
||||
startDismissTimer();
|
||||
// Update stored timeout if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = dismissTimeout;
|
||||
}
|
||||
}
|
||||
});
|
||||
startDismissTimer();
|
||||
// Store notification if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.set(message, { element: notification, timeout: dismissTimeout });
|
||||
}
|
||||
log.debug(`Notification shown: [Layer Forge] ${message}`);
|
||||
}
|
||||
/**
|
||||
* Shows a success notification
|
||||
* @param message - The message to show
|
||||
* @param duration - Duration in milliseconds (default: 3000)
|
||||
* @param deduplicate - If true, will not show duplicate messages (default: false)
|
||||
*/
|
||||
export function showSuccessNotification(message, duration = 3000) {
|
||||
showNotification(message, undefined, duration, "success");
|
||||
export function showSuccessNotification(message, duration = 3000, deduplicate = false) {
|
||||
showNotification(message, undefined, duration, "success", deduplicate);
|
||||
}
|
||||
/**
|
||||
* Shows an error notification
|
||||
* @param message - The message to show
|
||||
* @param duration - Duration in milliseconds (default: 5000)
|
||||
* @param deduplicate - If true, will not show duplicate messages (default: false)
|
||||
*/
|
||||
export function showErrorNotification(message, duration = 5000) {
|
||||
showNotification(message, undefined, duration, "error");
|
||||
export function showErrorNotification(message, duration = 5000, deduplicate = false) {
|
||||
showNotification(message, undefined, duration, "error", deduplicate);
|
||||
}
|
||||
/**
|
||||
* Shows an info notification
|
||||
* @param message - The message to show
|
||||
* @param duration - Duration in milliseconds (default: 3000)
|
||||
* @param deduplicate - If true, will not show duplicate messages (default: false)
|
||||
*/
|
||||
export function showInfoNotification(message, duration = 3000) {
|
||||
showNotification(message, undefined, duration, "info");
|
||||
export function showInfoNotification(message, duration = 3000, deduplicate = false) {
|
||||
showNotification(message, undefined, duration, "info", deduplicate);
|
||||
}
|
||||
/**
|
||||
* Shows a warning notification
|
||||
* @param message - The message to show
|
||||
* @param duration - Duration in milliseconds (default: 3000)
|
||||
* @param deduplicate - If true, will not show duplicate messages (default: false)
|
||||
*/
|
||||
export function showWarningNotification(message, duration = 3000) {
|
||||
showNotification(message, undefined, duration, "warning");
|
||||
export function showWarningNotification(message, duration = 3000, deduplicate = false) {
|
||||
showNotification(message, undefined, duration, "warning", deduplicate);
|
||||
}
|
||||
/**
|
||||
* Shows an alert notification
|
||||
* @param message - The message to show
|
||||
* @param duration - Duration in milliseconds (default: 3000)
|
||||
* @param deduplicate - If true, will not show duplicate messages (default: false)
|
||||
*/
|
||||
export function showAlertNotification(message, duration = 3000) {
|
||||
showNotification(message, undefined, duration, "alert");
|
||||
export function showAlertNotification(message, duration = 3000, deduplicate = false) {
|
||||
showNotification(message, undefined, duration, "alert", deduplicate);
|
||||
}
|
||||
/**
|
||||
* Shows a sequence of all notification types for debugging purposes.
|
||||
@@ -214,7 +297,7 @@ export function showAllNotificationTypes(message) {
|
||||
types.forEach((type, index) => {
|
||||
const notificationMessage = message || `This is a '${type}' notification.`;
|
||||
setTimeout(() => {
|
||||
showNotification(notificationMessage, undefined, 3000, type);
|
||||
showNotification(notificationMessage, undefined, 3000, type, false);
|
||||
}, index * 400); // Stagger the notifications
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user