Refactor notification progress to use requestAnimationFrame and fix db imports
- Replace CSS animation-based notification progress bar with requestAnimationFrame-driven animation - Centralize getCanvasState/setCanvasState imports at top-level in CanvasView (remove dynamic import()) - Clean up animation frame lifecycle tracking to prevent orphaned rAF callbacks - Set local debug log level in python/config.py for development diagnostics
This commit is contained in:
+1
-3
@@ -6,7 +6,7 @@ import { ChangeTracker } from "../../scripts/changeTracker.js";
|
||||
import { $el } from "../../scripts/ui.js";
|
||||
import { addStylesheet, getUrl, loadTemplate } from "./utils/ResourceManager.js";
|
||||
import { Canvas } from "./Canvas.js";
|
||||
import { clearAllCanvasStates } from "./db.js";
|
||||
import { clearAllCanvasStates, getCanvasState, setCanvasState } from "./db.js";
|
||||
import { ImageCache } from "./ImageCache.js";
|
||||
import { createCanvas } from "./utils/CommonUtils.js";
|
||||
import { createModuleLogger } from "./utils/LoggerUtils.js";
|
||||
@@ -1248,7 +1248,6 @@ app.registerExtension({
|
||||
// Copy the canvas state now that the widget is initialized
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const { getCanvasState, setCanvasState } = await import('./db.js');
|
||||
let sourceState = await getCanvasState(String(sourceNodeId));
|
||||
// If source node doesn't exist (cross-workflow paste), try clipboard
|
||||
if (!sourceState) {
|
||||
@@ -1444,7 +1443,6 @@ app.registerExtension({
|
||||
// This happens async but that's fine since paste happens later
|
||||
(async () => {
|
||||
try {
|
||||
const { getCanvasState, setCanvasState } = await import('./db.js');
|
||||
const sourceState = await getCanvasState(String(this.id));
|
||||
if (sourceState) {
|
||||
// Store in a special "clipboard" entry
|
||||
|
||||
@@ -25,15 +25,25 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
if (existingNotification.timeout !== null) {
|
||||
clearTimeout(existingNotification.timeout);
|
||||
}
|
||||
if (existingNotification.animationFrame !== null) {
|
||||
cancelAnimationFrame(existingNotification.animationFrame);
|
||||
existingNotification.animationFrame = null;
|
||||
}
|
||||
// Find the progress bar and restart its animation
|
||||
const progressBar = existingNotification.element.querySelector('div[style*="animation"]');
|
||||
const progressBar = existingNotification.element.querySelector('div[style*="lf-progress"], div[style*="scaleX"]');
|
||||
if (progressBar) {
|
||||
// Reset animation
|
||||
progressBar.style.animation = 'none';
|
||||
// Force reflow
|
||||
void progressBar.offsetHeight;
|
||||
// Restart animation
|
||||
progressBar.style.animation = `lf-progress ${duration / 1000}s linear`;
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
const startedAt = performance.now();
|
||||
const updateProgress = (now) => {
|
||||
const progress = Math.max(0, 1 - ((now - startedAt) / duration));
|
||||
progressBar.style.transform = `scaleX(${progress})`;
|
||||
if (progress > 0) {
|
||||
existingNotification.animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
};
|
||||
existingNotification.animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
// Set new timeout
|
||||
const newTimeout = window.setTimeout(() => {
|
||||
@@ -42,6 +52,10 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
notification.addEventListener('animationend', () => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored?.animationFrame !== null && stored?.animationFrame !== undefined) {
|
||||
cancelAnimationFrame(stored.animationFrame);
|
||||
}
|
||||
activeNotifications.delete(message);
|
||||
const container = document.getElementById('lf-notification-container');
|
||||
if (container && container.children.length === 0) {
|
||||
@@ -146,7 +160,7 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
body.appendChild(msgSpan);
|
||||
// --- Progress Bar ---
|
||||
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;`;
|
||||
progressBar.style.cssText = `height: 4px; width: 100%; background: ${config.bg}; box-shadow: 0 0 12px ${config.bg}; transform-origin: left; transform: scaleX(1); transition: none; flex-shrink: 0;`;
|
||||
// --- Assemble Notification ---
|
||||
notification.appendChild(leftBar);
|
||||
notification.appendChild(header);
|
||||
@@ -189,7 +203,16 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
}
|
||||
body.classList.add('notification-scrollbar');
|
||||
let dismissTimeout = null;
|
||||
let progressAnimationFrame = null;
|
||||
const closeNotification = () => {
|
||||
if (dismissTimeout !== null) {
|
||||
clearTimeout(dismissTimeout);
|
||||
dismissTimeout = null;
|
||||
}
|
||||
if (progressAnimationFrame !== null) {
|
||||
cancelAnimationFrame(progressAnimationFrame);
|
||||
progressAnimationFrame = null;
|
||||
}
|
||||
// Remove from active notifications map if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.delete(message);
|
||||
@@ -206,16 +229,37 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
};
|
||||
closeBtn.onclick = closeNotification;
|
||||
const startDismissTimer = () => {
|
||||
if (dismissTimeout !== null)
|
||||
clearTimeout(dismissTimeout);
|
||||
if (progressAnimationFrame !== null)
|
||||
cancelAnimationFrame(progressAnimationFrame);
|
||||
dismissTimeout = window.setTimeout(closeNotification, duration);
|
||||
progressBar.style.animation = `lf-progress ${duration / 1000}s linear`;
|
||||
progressBar.style.animation = 'none';
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
const startedAt = performance.now();
|
||||
const updateProgress = (now) => {
|
||||
const progress = Math.max(0, 1 - ((now - startedAt) / duration));
|
||||
progressBar.style.transform = `scaleX(${progress})`;
|
||||
if (progress > 0) {
|
||||
progressAnimationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
};
|
||||
progressAnimationFrame = requestAnimationFrame(updateProgress);
|
||||
};
|
||||
const pauseAndRewindTimer = () => {
|
||||
if (dismissTimeout !== null)
|
||||
clearTimeout(dismissTimeout);
|
||||
dismissTimeout = null;
|
||||
if (progressAnimationFrame !== null)
|
||||
cancelAnimationFrame(progressAnimationFrame);
|
||||
progressAnimationFrame = null;
|
||||
const computedStyle = window.getComputedStyle(progressBar);
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = computedStyle.transform;
|
||||
progressBar.style.animation = 'lf-progress-rewind 0.5s ease-out forwards';
|
||||
void progressBar.offsetHeight;
|
||||
progressBar.style.transition = 'transform 0.5s ease-out';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
};
|
||||
notification.addEventListener('mouseenter', () => {
|
||||
pauseAndRewindTimer();
|
||||
@@ -224,6 +268,7 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = null;
|
||||
stored.animationFrame = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -234,13 +279,14 @@ export function showNotification(message, backgroundColor = "#4a6cd4", duration
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = dismissTimeout;
|
||||
stored.animationFrame = progressAnimationFrame;
|
||||
}
|
||||
}
|
||||
});
|
||||
startDismissTimer();
|
||||
// Store notification if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.set(message, { element: notification, timeout: dismissTimeout });
|
||||
activeNotifications.set(message, { element: notification, timeout: dismissTimeout, animationFrame: progressAnimationFrame });
|
||||
}
|
||||
log.debug(`Notification shown: [Layer Forge] ${message}`);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Log level for development.
|
||||
# Possible values: 'DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'
|
||||
LOG_LEVEL = 'NONE'
|
||||
LOG_LEVEL = 'DEBUG'
|
||||
|
||||
+1
-3
@@ -12,7 +12,7 @@ import {$el} from "../../scripts/ui.js";
|
||||
import { addStylesheet, getUrl, loadTemplate } from "./utils/ResourceManager.js";
|
||||
|
||||
import {Canvas} from "./Canvas.js";
|
||||
import {clearAllCanvasStates} from "./db.js";
|
||||
import {clearAllCanvasStates, getCanvasState, setCanvasState} from "./db.js";
|
||||
import {ImageCache} from "./ImageCache.js";
|
||||
import {generateUniqueFileName, createCanvas} from "./utils/CommonUtils.js";
|
||||
import {createModuleLogger} from "./utils/LoggerUtils.js";
|
||||
@@ -1446,7 +1446,6 @@ app.registerExtension({
|
||||
// Copy the canvas state now that the widget is initialized
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const { getCanvasState, setCanvasState } = await import('./db.js');
|
||||
let sourceState = await getCanvasState(String(sourceNodeId));
|
||||
|
||||
// If source node doesn't exist (cross-workflow paste), try clipboard
|
||||
@@ -1665,7 +1664,6 @@ app.registerExtension({
|
||||
// This happens async but that's fine since paste happens later
|
||||
(async () => {
|
||||
try {
|
||||
const { getCanvasState, setCanvasState } = await import('./db.js');
|
||||
const sourceState = await getCanvasState(String(this.id));
|
||||
if (sourceState) {
|
||||
// Store in a special "clipboard" entry
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createModuleLogger } from "./LoggerUtils.js";
|
||||
const log = createModuleLogger('NotificationUtils');
|
||||
|
||||
// Store active notifications for deduplication
|
||||
const activeNotifications = new Map<string, { element: HTMLDivElement, timeout: number | null }>();
|
||||
const activeNotifications = new Map<string, { element: HTMLDivElement, timeout: number | null, animationFrame: number | null }>();
|
||||
|
||||
/**
|
||||
* Utility functions for showing notifications to the user
|
||||
@@ -37,16 +37,26 @@ export function showNotification(
|
||||
if (existingNotification.timeout !== null) {
|
||||
clearTimeout(existingNotification.timeout);
|
||||
}
|
||||
if (existingNotification.animationFrame !== null) {
|
||||
cancelAnimationFrame(existingNotification.animationFrame);
|
||||
existingNotification.animationFrame = null;
|
||||
}
|
||||
|
||||
// Find the progress bar and restart its animation
|
||||
const progressBar = existingNotification.element.querySelector('div[style*="animation"]') as HTMLDivElement;
|
||||
const progressBar = existingNotification.element.querySelector('div[style*="lf-progress"], div[style*="scaleX"]') as HTMLDivElement;
|
||||
if (progressBar) {
|
||||
// Reset animation
|
||||
progressBar.style.animation = 'none';
|
||||
// Force reflow
|
||||
void progressBar.offsetHeight;
|
||||
// Restart animation
|
||||
progressBar.style.animation = `lf-progress ${duration / 1000}s linear`;
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
const startedAt = performance.now();
|
||||
const updateProgress = (now: number) => {
|
||||
const progress = Math.max(0, 1 - ((now - startedAt) / duration));
|
||||
progressBar.style.transform = `scaleX(${progress})`;
|
||||
if (progress > 0) {
|
||||
existingNotification.animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
};
|
||||
existingNotification.animationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
|
||||
// Set new timeout
|
||||
@@ -56,6 +66,10 @@ export function showNotification(
|
||||
notification.addEventListener('animationend', () => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored?.animationFrame !== null && stored?.animationFrame !== undefined) {
|
||||
cancelAnimationFrame(stored.animationFrame);
|
||||
}
|
||||
activeNotifications.delete(message);
|
||||
const container = document.getElementById('lf-notification-container');
|
||||
if (container && container.children.length === 0) {
|
||||
@@ -172,7 +186,7 @@ export function showNotification(
|
||||
|
||||
// --- Progress Bar ---
|
||||
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;`;
|
||||
progressBar.style.cssText = `height: 4px; width: 100%; background: ${config.bg}; box-shadow: 0 0 12px ${config.bg}; transform-origin: left; transform: scaleX(1); transition: none; flex-shrink: 0;`;
|
||||
|
||||
// --- Assemble Notification ---
|
||||
notification.appendChild(leftBar);
|
||||
@@ -220,7 +234,16 @@ export function showNotification(
|
||||
body.classList.add('notification-scrollbar');
|
||||
|
||||
let dismissTimeout: number | null = null;
|
||||
let progressAnimationFrame: number | null = null;
|
||||
const closeNotification = () => {
|
||||
if (dismissTimeout !== null) {
|
||||
clearTimeout(dismissTimeout);
|
||||
dismissTimeout = null;
|
||||
}
|
||||
if (progressAnimationFrame !== null) {
|
||||
cancelAnimationFrame(progressAnimationFrame);
|
||||
progressAnimationFrame = null;
|
||||
}
|
||||
// Remove from active notifications map if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.delete(message);
|
||||
@@ -240,16 +263,36 @@ export function showNotification(
|
||||
closeBtn.onclick = closeNotification;
|
||||
|
||||
const startDismissTimer = () => {
|
||||
if (dismissTimeout !== null) clearTimeout(dismissTimeout);
|
||||
if (progressAnimationFrame !== null) cancelAnimationFrame(progressAnimationFrame);
|
||||
|
||||
dismissTimeout = window.setTimeout(closeNotification, duration);
|
||||
progressBar.style.animation = `lf-progress ${duration / 1000}s linear`;
|
||||
progressBar.style.animation = 'none';
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
|
||||
const startedAt = performance.now();
|
||||
const updateProgress = (now: number) => {
|
||||
const progress = Math.max(0, 1 - ((now - startedAt) / duration));
|
||||
progressBar.style.transform = `scaleX(${progress})`;
|
||||
if (progress > 0) {
|
||||
progressAnimationFrame = requestAnimationFrame(updateProgress);
|
||||
}
|
||||
};
|
||||
progressAnimationFrame = requestAnimationFrame(updateProgress);
|
||||
};
|
||||
|
||||
const pauseAndRewindTimer = () => {
|
||||
if (dismissTimeout !== null) clearTimeout(dismissTimeout);
|
||||
dismissTimeout = null;
|
||||
if (progressAnimationFrame !== null) cancelAnimationFrame(progressAnimationFrame);
|
||||
progressAnimationFrame = null;
|
||||
const computedStyle = window.getComputedStyle(progressBar);
|
||||
progressBar.style.transition = 'none';
|
||||
progressBar.style.transform = computedStyle.transform;
|
||||
progressBar.style.animation = 'lf-progress-rewind 0.5s ease-out forwards';
|
||||
void progressBar.offsetHeight;
|
||||
progressBar.style.transition = 'transform 0.5s ease-out';
|
||||
progressBar.style.transform = 'scaleX(1)';
|
||||
};
|
||||
|
||||
notification.addEventListener('mouseenter', () => {
|
||||
@@ -259,6 +302,7 @@ export function showNotification(
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = null;
|
||||
stored.animationFrame = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -270,6 +314,7 @@ export function showNotification(
|
||||
const stored = activeNotifications.get(message);
|
||||
if (stored) {
|
||||
stored.timeout = dismissTimeout;
|
||||
stored.animationFrame = progressAnimationFrame;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -278,7 +323,7 @@ export function showNotification(
|
||||
|
||||
// Store notification if deduplicate is enabled
|
||||
if (deduplicate) {
|
||||
activeNotifications.set(message, { element: notification, timeout: dismissTimeout });
|
||||
activeNotifications.set(message, { element: notification, timeout: dismissTimeout, animationFrame: progressAnimationFrame });
|
||||
}
|
||||
|
||||
log.debug(`Notification shown: [Layer Forge] ${message}`);
|
||||
|
||||
Reference in New Issue
Block a user