diff --git a/js/Canvas.js b/js/Canvas.js index 253b8f5..729c3f3 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -7,6 +7,7 @@ import {CanvasRenderer} from "./CanvasRenderer.js"; import {CanvasIO} from "./CanvasIO.js"; import {ImageReferenceManager} from "./ImageReferenceManager.js"; import {createModuleLogger} from "./utils/LoggerUtils.js"; + const log = createModuleLogger('Canvas'); export class Canvas { @@ -45,7 +46,7 @@ export class Canvas { this.canvasIO = new CanvasIO(this); this.imageReferenceManager = new ImageReferenceManager(this); this.interaction = this.canvasInteractions.interaction; - + this.setupEventListeners(); this.initNodeData(); @@ -137,6 +138,7 @@ export class Canvas { this.onSelectionChange(); } } + async copySelectedLayers() { return this.canvasLayers.copySelectedLayers(); } @@ -265,8 +267,6 @@ export class Canvas { } - - async getFlattenedCanvasAsBlob() { return this.canvasLayers.getFlattenedCanvasAsBlob(); } diff --git a/js/CanvasIO.js b/js/CanvasIO.js index ef9a353..c029b0a 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -26,7 +26,7 @@ export class CanvasIO { log.info(`Starting saveToServer (disk) with fileName: ${fileName} for node: ${nodeId}`); this._saveInProgress = this._performSave(fileName, outputMode); window.canvasSaveStates.set(saveKey, this._saveInProgress); - + try { return await this._saveInProgress; } finally { @@ -54,25 +54,25 @@ export class CanvasIO { } return new Promise((resolve) => { - const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(this.canvas.width, this.canvas.height); - const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height); + const {canvas: tempCanvas, ctx: tempCtx} = createCanvas(this.canvas.width, this.canvas.height); + const {canvas: maskCanvas, ctx: maskCtx} = createCanvas(this.canvas.width, this.canvas.height); tempCtx.fillStyle = '#ffffff'; tempCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); const visibilityCanvas = document.createElement('canvas'); visibilityCanvas.width = this.canvas.width; visibilityCanvas.height = this.canvas.height; - const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true }); + const visibilityCtx = visibilityCanvas.getContext('2d', {alpha: true}); maskCtx.fillStyle = '#ffffff'; maskCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); - + log.debug(`Canvas contexts created, starting layer rendering`); const sortedLayers = this.canvas.layers.sort((a, b) => a.zIndex - b.zIndex); log.debug(`Processing ${sortedLayers.length} layers in order`); sortedLayers.forEach((layer, index) => { log.debug(`Processing layer ${index}: zIndex=${layer.zIndex}, size=${layer.width}x${layer.height}, pos=(${layer.x},${layer.y})`); log.debug(`Layer ${index}: blendMode=${layer.blendMode || 'normal'}, opacity=${layer.opacity !== undefined ? layer.opacity : 1}`); - + tempCtx.save(); tempCtx.globalCompositeOperation = layer.blendMode || 'normal'; tempCtx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1; @@ -80,7 +80,7 @@ export class CanvasIO { tempCtx.rotate(layer.rotation * Math.PI / 180); tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); - + log.debug(`Layer ${index} rendered successfully`); visibilityCtx.save(); visibilityCtx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); @@ -96,7 +96,7 @@ export class CanvasIO { maskData.data[i] = maskData.data[i + 1] = maskData.data[i + 2] = maskValue; maskData.data[i + 3] = 255; } - + maskCtx.putImageData(maskData, 0, 0); const toolMaskCanvas = this.canvas.maskTool.getMask(); if (toolMaskCanvas) { @@ -111,7 +111,7 @@ export class CanvasIO { const maskX = this.canvas.maskTool.x; const maskY = this.canvas.maskTool.y; - + log.debug(`Extracting mask from world position (${maskX}, ${maskY}) for output area (0,0) to (${this.canvas.width}, ${this.canvas.height})`); const sourceX = Math.max(0, -maskX); // Where in the mask canvas to start reading @@ -130,7 +130,7 @@ export class CanvasIO { if (copyWidth > 0 && copyHeight > 0) { log.debug(`Copying mask region: source(${sourceX}, ${sourceY}) to dest(${destX}, ${destY}) size(${copyWidth}, ${copyHeight})`); - + tempMaskCtx.drawImage( toolMaskCanvas, sourceX, sourceY, copyWidth, copyHeight, // Source rectangle @@ -153,13 +153,13 @@ export class CanvasIO { const imageData = tempCanvas.toDataURL('image/png'); const maskData = maskCanvas.toDataURL('image/png'); log.info("Returning image and mask data as base64 for RAM mode."); - resolve({ image: imageData, mask: maskData }); + resolve({image: imageData, mask: maskData}); return; } const fileNameWithoutMask = fileName.replace('.png', '_without_mask.png'); log.info(`Saving image without mask as: ${fileNameWithoutMask}`); - + tempCanvas.toBlob(async (blobWithoutMask) => { log.debug(`Created blob for image without mask, size: ${blobWithoutMask.size} bytes`); const formDataWithoutMask = new FormData(); @@ -193,7 +193,7 @@ export class CanvasIO { if (resp.status === 200) { const maskFileName = fileName.replace('.png', '_mask.png'); log.info(`Saving mask as: ${maskFileName}`); - + maskCanvas.toBlob(async (maskBlob) => { log.debug(`Created blob for mask, size: ${maskBlob.size} bytes`); const maskFormData = new FormData(); @@ -237,18 +237,18 @@ export class CanvasIO { async _renderOutputData() { return new Promise((resolve) => { - const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(this.canvas.width, this.canvas.height); - const { canvas: maskCanvas, ctx: maskCtx } = createCanvas(this.canvas.width, this.canvas.height); + const {canvas: tempCanvas, ctx: tempCtx} = createCanvas(this.canvas.width, this.canvas.height); + const {canvas: maskCanvas, ctx: maskCtx} = createCanvas(this.canvas.width, this.canvas.height); tempCtx.fillStyle = '#ffffff'; tempCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); const visibilityCanvas = document.createElement('canvas'); visibilityCanvas.width = this.canvas.width; visibilityCanvas.height = this.canvas.height; - const visibilityCtx = visibilityCanvas.getContext('2d', { alpha: true }); + const visibilityCtx = visibilityCanvas.getContext('2d', {alpha: true}); maskCtx.fillStyle = '#ffffff'; // Start with a white mask (nothing masked) maskCtx.fillRect(0, 0, this.canvas.width, this.canvas.height); - + const sortedLayers = this.canvas.layers.sort((a, b) => a.zIndex - b.zIndex); sortedLayers.forEach((layer) => { @@ -289,19 +289,19 @@ export class CanvasIO { const maskX = this.canvas.maskTool.x; const maskY = this.canvas.maskTool.y; - + log.debug(`[renderOutputData] Extracting mask from world position (${maskX}, ${maskY})`); const sourceX = Math.max(0, -maskX); const sourceY = Math.max(0, -maskY); const destX = Math.max(0, maskX); const destY = Math.max(0, maskY); - + const copyWidth = Math.min(toolMaskCanvas.width - sourceX, this.canvas.width - destX); const copyHeight = Math.min(toolMaskCanvas.height - sourceY, this.canvas.height - destY); - + if (copyWidth > 0 && copyHeight > 0) { - tempMaskCtx.drawImage( + tempMaskCtx.drawImage( toolMaskCanvas, sourceX, sourceY, copyWidth, copyHeight, destX, destY, copyWidth, copyHeight @@ -312,7 +312,7 @@ export class CanvasIO { for (let i = 0; i < tempMaskData.data.length; i += 4) { const alpha = tempMaskData.data[i + 3]; - tempMaskData.data[i] = tempMaskData.data[i+1] = tempMaskData.data[i+2] = alpha; + tempMaskData.data[i] = tempMaskData.data[i + 1] = tempMaskData.data[i + 2] = alpha; tempMaskData.data[i + 3] = 255; // Solid alpha } tempMaskCtx.putImageData(tempMaskData, 0, 0); @@ -321,18 +321,18 @@ export class CanvasIO { maskCtx.globalCompositeOperation = 'screen'; maskCtx.drawImage(tempMaskCanvas, 0, 0); } - + const imageDataUrl = tempCanvas.toDataURL('image/png'); const maskDataUrl = maskCanvas.toDataURL('image/png'); - - resolve({ image: imageDataUrl, mask: maskDataUrl }); + + resolve({image: imageDataUrl, mask: maskDataUrl}); }); } async sendDataViaWebSocket(nodeId) { log.info(`Preparing to send data for node ${nodeId} via WebSocket.`); - - const { image, mask } = await this._renderOutputData(); + + const {image, mask} = await this._renderOutputData(); try { log.info(`Sending data for node ${nodeId}...`); @@ -342,7 +342,7 @@ export class CanvasIO { image: image, mask: mask, }, true); // `true` requires an acknowledgment - + log.info(`Data for node ${nodeId} has been sent and acknowledged by the server.`); return true; } catch (error) { @@ -357,7 +357,7 @@ export class CanvasIO { try { log.debug("Adding input to canvas:", {inputImage}); - const { canvas: tempCanvas, ctx: tempCtx } = createCanvas(inputImage.width, inputImage.height); + const {canvas: tempCanvas, ctx: tempCtx} = createCanvas(inputImage.width, inputImage.height); const imgData = new ImageData( inputImage.data, diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index d08cc66..6132964 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -1,5 +1,6 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; import {snapToGrid, getSnapAdjustment} from "./utils/CommonUtils.js"; + const log = createModuleLogger('CanvasInteractions'); export class CanvasInteractions { @@ -504,7 +505,7 @@ export class CanvasInteractions { }); this.canvas.maskTool.updatePosition(-finalX, -finalY); - + this.canvas.viewport.x -= finalX; this.canvas.viewport.y -= finalY; } diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index bb2f219..77714c3 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -2,6 +2,7 @@ import {saveImage, removeImage} from "./db.js"; import {createModuleLogger} from "./utils/LoggerUtils.js"; import {generateUUID, generateUniqueFileName} from "./utils/CommonUtils.js"; import {withErrorHandling, createValidationError} from "./ErrorHandler.js"; + const log = createModuleLogger('CanvasLayers'); export class CanvasLayers { @@ -358,6 +359,7 @@ export class CanvasLayers { this.canvasLayers.selectedLayer = layer; this.canvasLayers.render(); } + isRotationHandle(x, y) { if (!this.canvasLayers.selectedLayer) return false; @@ -428,12 +430,18 @@ export class CanvasLayers { const handleRadius = 5; const handles = { 'nw': {x: this.canvasLayers.selectedLayer.x, y: this.canvasLayers.selectedLayer.y}, - 'ne': {x: this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width, y: this.canvasLayers.selectedLayer.y}, + 'ne': { + x: this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width, + y: this.canvasLayers.selectedLayer.y + }, 'se': { x: this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width, y: this.canvasLayers.selectedLayer.y + this.canvasLayers.selectedLayer.height }, - 'sw': {x: this.canvasLayers.selectedLayer.x, y: this.canvasLayers.selectedLayer.y + this.canvasLayers.selectedLayer.height} + 'sw': { + x: this.canvasLayers.selectedLayer.x, + y: this.canvasLayers.selectedLayer.y + this.canvasLayers.selectedLayer.height + } }; for (const [position, point] of Object.entries(handles)) { @@ -443,6 +451,7 @@ export class CanvasLayers { } return null; } + showBlendModeMenu(x, y) { const existingMenu = document.getElementById('blend-mode-menu'); if (existingMenu) { @@ -533,7 +542,7 @@ export class CanvasLayers { return await this.canvasLayers.saveToServer(fileName); } }; - + await saveWithFallback(this.canvasLayers.widget.value); if (this.canvasLayers.node) { app.graph.runStep(); @@ -594,6 +603,7 @@ export class CanvasLayers { modeElement.appendChild(slider); } } + async getFlattenedCanvasAsBlob() { return new Promise((resolve, reject) => { const tempCanvas = document.createElement('canvas'); @@ -633,6 +643,7 @@ export class CanvasLayers { }, 'image/png'); }); } + async getFlattenedSelectionAsBlob() { if (this.canvasLayers.selectedLayers.length === 0) { return null; diff --git a/js/CanvasRenderer.js b/js/CanvasRenderer.js index 72daae5..5465154 100644 --- a/js/CanvasRenderer.js +++ b/js/CanvasRenderer.js @@ -1,4 +1,5 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; + const log = createModuleLogger('CanvasRenderer'); export class CanvasRenderer { @@ -95,7 +96,7 @@ export class CanvasRenderer { } ctx.drawImage(maskImage, this.canvas.maskTool.x, this.canvas.maskTool.y); - + ctx.globalAlpha = 1.0; ctx.restore(); } @@ -105,7 +106,7 @@ export class CanvasRenderer { ctx.restore(); - if (this.canvas.canvas.width !== this.canvas.offscreenCanvas.width || + if (this.canvas.canvas.width !== this.canvas.offscreenCanvas.width || this.canvas.canvas.height !== this.canvas.offscreenCanvas.height) { this.canvas.canvas.width = this.canvas.offscreenCanvas.width; this.canvas.canvas.height = this.canvas.offscreenCanvas.height; @@ -115,7 +116,7 @@ export class CanvasRenderer { renderInteractionElements(ctx) { const interaction = this.canvas.interaction; - + if (interaction.mode === 'resizingCanvas' && interaction.canvasResizeRect) { const rect = interaction.canvasResizeRect; ctx.save(); @@ -147,7 +148,7 @@ export class CanvasRenderer { ctx.restore(); } } - + if (interaction.mode === 'movingCanvas' && interaction.canvasMoveRect) { const rect = interaction.canvasMoveRect; ctx.save(); diff --git a/js/CanvasState.js b/js/CanvasState.js index 1eb1f1b..35c7b1f 100644 --- a/js/CanvasState.js +++ b/js/CanvasState.js @@ -2,6 +2,7 @@ import {getCanvasState, setCanvasState, saveImage, getImage} from "./db.js"; import {createModuleLogger} from "./utils/LoggerUtils.js"; import {generateUUID, cloneLayers, getStateSignature, debounce} from "./utils/CommonUtils.js"; import {withErrorHandling} from "./ErrorHandler.js"; + const log = createModuleLogger('CanvasState'); export class CanvasState { @@ -31,7 +32,7 @@ export class CanvasState { } this._loadInProgress = this._performLoad(); - + try { const result = await this._loadInProgress; return result; @@ -78,7 +79,7 @@ export class CanvasState { * @returns {Promise} Załadowane warstwy */ async _loadLayers(layersData) { - const imagePromises = layersData.map((layerData, index) => + const imagePromises = layersData.map((layerData, index) => this._loadSingleLayer(layerData, index) ); return Promise.all(imagePromises); @@ -111,7 +112,7 @@ export class CanvasState { */ _loadLayerFromImageId(layerData, index, resolve) { log.debug(`Layer ${index}: Loading image with id: ${layerData.imageId}`); - + if (this.canvas.imageCache.has(layerData.imageId)) { log.debug(`Layer ${index}: Image found in cache.`); const imageSrc = this.canvas.imageCache.get(layerData.imageId); @@ -144,7 +145,7 @@ export class CanvasState { _convertLegacyLayer(layerData, index, resolve) { log.info(`Layer ${index}: Found imageSrc, converting to new format with imageId.`); const imageId = generateUUID(); - + saveImage(imageId, layerData.imageSrc) .then(() => { log.info(`Layer ${index}: Image saved to IndexedDB with id: ${imageId}`); @@ -283,7 +284,7 @@ export class CanvasState { saveMaskState(replaceLast = false) { if (!this.canvas.maskTool) return; - + if (replaceLast && this.maskUndoStack.length > 0) { this.maskUndoStack.pop(); } @@ -321,7 +322,7 @@ export class CanvasState { undoLayersState() { if (this.layersUndoStack.length <= 1) return; - + const currentState = this.layersUndoStack.pop(); this.layersRedoStack.push(currentState); const prevState = this.layersUndoStack[this.layersUndoStack.length - 1]; @@ -333,7 +334,7 @@ export class CanvasState { redoLayersState() { if (this.layersRedoStack.length === 0) return; - + const nextState = this.layersRedoStack.pop(); this.layersUndoStack.push(nextState); this.canvas.layers = cloneLayers(nextState); @@ -344,33 +345,33 @@ export class CanvasState { undoMaskState() { if (!this.canvas.maskTool || this.maskUndoStack.length <= 1) return; - + const currentState = this.maskUndoStack.pop(); this.maskRedoStack.push(currentState); - + if (this.maskUndoStack.length > 0) { const prevState = this.maskUndoStack[this.maskUndoStack.length - 1]; const maskCanvas = this.canvas.maskTool.getMask(); const maskCtx = maskCanvas.getContext('2d'); maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); maskCtx.drawImage(prevState, 0, 0); - + this.canvas.render(); } - + this.canvas.updateHistoryButtons(); } redoMaskState() { if (!this.canvas.maskTool || this.maskRedoStack.length === 0) return; - + const nextState = this.maskRedoStack.pop(); this.maskUndoStack.push(nextState); const maskCanvas = this.canvas.maskTool.getMask(); const maskCtx = maskCanvas.getContext('2d'); maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); maskCtx.drawImage(nextState, 0, 0); - + this.canvas.render(); this.canvas.updateHistoryButtons(); } diff --git a/js/CanvasView.js b/js/CanvasView.js index 563cad3..8b3ea8c 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -672,12 +672,12 @@ async function createCanvasWidget(node, widget, app) { try { const stats = canvas.getGarbageCollectionStats(); log.info("GC Stats before cleanup:", stats); - + await canvas.runGarbageCollection(); - + const newStats = canvas.getGarbageCollectionStats(); log.info("GC Stats after cleanup:", newStats); - + alert(`Garbage collection completed!\nTracked images: ${newStats.trackedImages}\nTotal references: ${newStats.totalReferences}\nOperations: ${newStats.operationCount}/${newStats.operationThreshold}`); } catch (e) { log.error("Failed to run garbage collection:", e); @@ -789,9 +789,6 @@ async function createCanvasWidget(node, widget, app) { }; - - - const mainContainer = $el("div.painterMainContainer", { style: { position: "relative", @@ -920,7 +917,6 @@ async function createCanvasWidget(node, widget, app) { if (!window.canvasExecutionStates) { window.canvasExecutionStates = new Map(); } - node.canvasWidget = canvas; @@ -944,12 +940,12 @@ app.registerExtension({ init() { const originalQueuePrompt = app.queuePrompt; - app.queuePrompt = async function(number, prompt) { + app.queuePrompt = async function (number, prompt) { log.info("Preparing to queue prompt..."); - + if (canvasNodeInstances.size > 0) { log.info(`Found ${canvasNodeInstances.size} CanvasNode(s). Sending data via WebSocket...`); - + const sendPromises = []; for (const [nodeId, canvasWidget] of canvasNodeInstances.entries()) { @@ -976,7 +972,7 @@ app.registerExtension({ return; // Stop execution } } - + log.info("All pre-prompt tasks complete. Proceeding with original queuePrompt."); return originalQueuePrompt.apply(this, arguments); @@ -994,7 +990,7 @@ app.registerExtension({ return r; }; - nodeType.prototype.onAdded = async function() { + nodeType.prototype.onAdded = async function () { log.info(`CanvasNode onAdded, ID: ${this.id}`); log.debug(`Available widgets in onAdded:`, this.widgets.map(w => w.name)); @@ -1003,6 +999,10 @@ app.registerExtension({ return; } + // Iterate through every widget attached to this node + this.widgets.forEach(w => { + log.debug(`Widget name: ${w.name}, type: ${w.type}, value: ${w.value}`); + }); const nodeIdWidget = this.widgets.find(w => w.name === "node_id"); if (nodeIdWidget) { @@ -1028,7 +1028,7 @@ app.registerExtension({ if (window.canvasExecutionStates) { window.canvasExecutionStates.delete(this.id); } - + const tooltip = document.getElementById(`painter-help-tooltip-${this.id}`); if (tooltip) { tooltip.remove(); diff --git a/js/ErrorHandler.js b/js/ErrorHandler.js index cdbf154..e722f24 100644 --- a/js/ErrorHandler.js +++ b/js/ErrorHandler.js @@ -60,7 +60,7 @@ export class ErrorHandler { this.logError(normalizedError, context); this.recordError(normalizedError); this.incrementErrorCount(normalizedError.type); - + return normalizedError; } @@ -75,29 +75,29 @@ export class ErrorHandler { if (error instanceof AppError) { return error; } - + if (error instanceof Error) { const type = this.categorizeError(error, context); return new AppError( error.message, type, - { context, ...additionalInfo }, + {context, ...additionalInfo}, error ); } - + if (typeof error === 'string') { return new AppError( error, ErrorTypes.SYSTEM, - { context, ...additionalInfo } + {context, ...additionalInfo} ); } - + return new AppError( 'Unknown error occurred', ErrorTypes.SYSTEM, - { context, originalError: error, ...additionalInfo } + {context, originalError: error, ...additionalInfo} ); } @@ -109,30 +109,30 @@ export class ErrorHandler { */ categorizeError(error, context) { const message = error.message.toLowerCase(); - if (message.includes('fetch') || message.includes('network') || + if (message.includes('fetch') || message.includes('network') || message.includes('connection') || message.includes('timeout')) { return ErrorTypes.NETWORK; } - if (message.includes('file') || message.includes('read') || + if (message.includes('file') || message.includes('read') || message.includes('write') || message.includes('path')) { return ErrorTypes.FILE_IO; } - if (message.includes('invalid') || message.includes('required') || + if (message.includes('invalid') || message.includes('required') || message.includes('validation') || message.includes('format')) { return ErrorTypes.VALIDATION; } - if (message.includes('image') || message.includes('canvas') || + if (message.includes('image') || message.includes('canvas') || message.includes('blob') || message.includes('tensor')) { return ErrorTypes.IMAGE_PROCESSING; } - if (message.includes('state') || message.includes('cache') || + if (message.includes('state') || message.includes('cache') || message.includes('storage')) { return ErrorTypes.STATE_MANAGEMENT; } if (context.toLowerCase().includes('canvas')) { return ErrorTypes.CANVAS; } - + return ErrorTypes.SYSTEM; } @@ -224,6 +224,7 @@ export class ErrorHandler { log.info('Error history cleared'); } } + const errorHandler = new ErrorHandler(); /** @@ -233,7 +234,7 @@ const errorHandler = new ErrorHandler(); * @returns {Function} Opakowana funkcja */ export function withErrorHandling(fn, context) { - return async function(...args) { + return async function (...args) { try { return await fn.apply(this, args); } catch (error) { @@ -251,10 +252,10 @@ export function withErrorHandling(fn, context) { * @param {string} context - Kontekst wykonania */ export function handleErrors(context) { - return function(target, propertyKey, descriptor) { + return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; - - descriptor.value = async function(...args) { + + descriptor.value = async function (...args) { try { return await originalMethod.apply(this, args); } catch (error) { @@ -266,7 +267,7 @@ export function handleErrors(context) { throw handledError; } }; - + return descriptor; }; } @@ -327,24 +328,25 @@ export async function safeExecute(operation, fallbackValue = null, context = 'Sa */ export async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000, context = 'RetryOperation') { let lastError; - + for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; - + if (attempt === maxRetries) { break; } - + const delay = baseDelay * Math.pow(2, attempt); - log.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, { error: error.message, context }); + log.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms`, {error: error.message, context}); await new Promise(resolve => setTimeout(resolve, delay)); } } - - throw errorHandler.handle(lastError, context, { attempts: maxRetries + 1 }); + + throw errorHandler.handle(lastError, context, {attempts: maxRetries + 1}); } -export { errorHandler }; + +export {errorHandler}; export default errorHandler; diff --git a/js/ImageCache.js b/js/ImageCache.js index 124ab5b..b5bd830 100644 --- a/js/ImageCache.js +++ b/js/ImageCache.js @@ -1,4 +1,5 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; + const log = createModuleLogger('ImageCache'); export class ImageCache { diff --git a/js/ImageReferenceManager.js b/js/ImageReferenceManager.js index 4fa8d7c..bc6a4ac 100644 --- a/js/ImageReferenceManager.js +++ b/js/ImageReferenceManager.js @@ -26,11 +26,11 @@ export class ImageReferenceManager { if (this.gcTimer) { clearInterval(this.gcTimer); } - + this.gcTimer = setInterval(() => { this.performGarbageCollection(); }, this.gcInterval); - + log.info("Garbage collection started with interval:", this.gcInterval / 1000, "seconds"); } @@ -51,11 +51,11 @@ export class ImageReferenceManager { */ addReference(imageId) { if (!imageId) return; - + const currentCount = this.imageReferences.get(imageId) || 0; this.imageReferences.set(imageId, currentCount + 1); this.imageLastUsed.set(imageId, Date.now()); - + log.debug(`Added reference to image ${imageId}, count: ${currentCount + 1}`); } @@ -65,7 +65,7 @@ export class ImageReferenceManager { */ removeReference(imageId) { if (!imageId) return; - + const currentCount = this.imageReferences.get(imageId) || 0; if (currentCount <= 1) { this.imageReferences.delete(imageId); @@ -86,7 +86,7 @@ export class ImageReferenceManager { usedImageIds.forEach(imageId => { this.addReference(imageId); }); - + log.info(`Updated references for ${usedImageIds.size} unique images`); } @@ -120,7 +120,7 @@ export class ImageReferenceManager { }); }); } - + log.debug(`Collected ${usedImageIds.size} used image IDs`); return usedImageIds; } @@ -136,7 +136,7 @@ export class ImageReferenceManager { const allImageIds = await getAllImageIds(); const unusedImages = []; const now = Date.now(); - + for (const imageId of allImageIds) { if (!usedImageIds.has(imageId)) { @@ -146,11 +146,11 @@ export class ImageReferenceManager { if (age > this.maxAge) { unusedImages.push(imageId); } else { - log.debug(`Image ${imageId} is unused but too young (age: ${Math.round(age/1000)}s)`); + log.debug(`Image ${imageId} is unused but too young (age: ${Math.round(age / 1000)}s)`); } } } - + log.debug(`Found ${unusedImages.length} unused images ready for cleanup`); return unusedImages; } catch (error) { @@ -168,11 +168,11 @@ export class ImageReferenceManager { log.debug("No unused images to cleanup"); return; } - + log.info(`Starting cleanup of ${unusedImages.length} unused images`); let cleanedCount = 0; let errorCount = 0; - + for (const imageId of unusedImages) { try { @@ -184,16 +184,16 @@ export class ImageReferenceManager { this.imageReferences.delete(imageId); this.imageLastUsed.delete(imageId); - + cleanedCount++; log.debug(`Cleaned up image: ${imageId}`); - + } catch (error) { errorCount++; log.error(`Error cleaning up image ${imageId}:`, error); } } - + log.info(`Garbage collection completed: ${cleanedCount} images cleaned, ${errorCount} errors`); } @@ -205,10 +205,10 @@ export class ImageReferenceManager { log.debug("Garbage collection already running, skipping"); return; } - + this.isGcRunning = true; log.info("Starting garbage collection..."); - + try { this.updateReferences(); @@ -218,7 +218,7 @@ export class ImageReferenceManager { const unusedImages = await this.findUnusedImages(usedImageIds); await this.cleanupUnusedImages(unusedImages); - + } catch (error) { log.error("Error during garbage collection:", error); } finally { @@ -232,7 +232,7 @@ export class ImageReferenceManager { incrementOperationCount() { this.operationCount++; log.debug(`Operation count: ${this.operationCount}/${this.operationThreshold}`); - + if (this.operationCount >= this.operationThreshold) { log.info(`Operation threshold reached (${this.operationThreshold}), triggering garbage collection`); this.operationCount = 0; // Reset counter diff --git a/js/MaskTool.js b/js/MaskTool.js index 716a75d..6cbc04c 100644 --- a/js/MaskTool.js +++ b/js/MaskTool.js @@ -1,4 +1,5 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; + const log = createModuleLogger('Mask_tool'); export class MaskTool { @@ -34,7 +35,7 @@ export class MaskTool { this.x = -extraSpace / 2; this.y = -extraSpace / 2; - + this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height); log.info(`Initialized mask canvas with extended size: ${this.maskCanvas.width}x${this.maskCanvas.height}, origin at (${this.x}, ${this.y})`); } @@ -46,7 +47,7 @@ export class MaskTool { this.canvasInstance.canvasState.saveMaskState(); } this.canvasInstance.updateHistoryButtons(); - + log.info("Mask tool activated"); } @@ -54,7 +55,7 @@ export class MaskTool { this.isActive = false; this.canvasInstance.interaction.mode = 'none'; this.canvasInstance.updateHistoryButtons(); - + log.info("Mask tool deactivated"); } @@ -104,17 +105,17 @@ export class MaskTool { const canvasWidth = this.maskCanvas.width; const canvasHeight = this.maskCanvas.height; - - if (canvasX >= 0 && canvasX < canvasWidth && + + if (canvasX >= 0 && canvasX < canvasWidth && canvasY >= 0 && canvasY < canvasHeight && - canvasLastX >= 0 && canvasLastX < canvasWidth && + canvasLastX >= 0 && canvasLastX < canvasWidth && canvasLastY >= 0 && canvasLastY < canvasHeight) { - + this.maskCtx.beginPath(); this.maskCtx.moveTo(canvasLastX, canvasLastY); this.maskCtx.lineTo(canvasX, canvasY); const gradientRadius = this.brushSize / 2; - + if (this.brushSoftness === 0) { this.maskCtx.strokeStyle = `rgba(255, 255, 255, ${this.brushStrength})`; } else { @@ -187,21 +188,21 @@ export class MaskTool { const newWidth = isIncreasingWidth ? width + extraSpace : Math.max(oldWidth, width + extraSpace); const newHeight = isIncreasingHeight ? height + extraSpace : Math.max(oldHeight, height + extraSpace); - + this.maskCanvas.width = newWidth; this.maskCanvas.height = newHeight; this.maskCtx = this.maskCanvas.getContext('2d'); - + if (oldMask.width > 0 && oldMask.height > 0) { const offsetX = this.x - oldX; const offsetY = this.y - oldY; this.maskCtx.drawImage(oldMask, offsetX, offsetY); - + log.debug(`Preserved mask content with offset (${offsetX}, ${offsetY})`); } - + log.info(`Mask canvas resized to ${this.maskCanvas.width}x${this.maskCanvas.height}, position (${this.x}, ${this.y})`); log.info(`Canvas size change: width ${isIncreasingWidth ? 'increased' : 'decreased'}, height ${isIncreasingHeight ? 'increased' : 'decreased'}`); } diff --git a/js/db.js b/js/db.js index 6c979b9..6fa546d 100644 --- a/js/db.js +++ b/js/db.js @@ -1,4 +1,5 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; + const log = createModuleLogger('db'); const DB_NAME = 'CanvasNodeDB'; @@ -89,7 +90,7 @@ export async function getCanvasState(id) { const db = await openDB(); const transaction = db.transaction([STATE_STORE_NAME], 'readonly'); const store = transaction.objectStore(STATE_STORE_NAME); - + const result = await createDBRequest(store, 'get', id, "Error getting canvas state"); log.debug(`Get success for id: ${id}`, result ? 'found' : 'not found'); return result ? result.state : null; @@ -100,7 +101,7 @@ export async function setCanvasState(id, state) { const db = await openDB(); const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME); - + await createDBRequest(store, 'put', {id, state}, "Error setting canvas state"); log.debug(`Set success for id: ${id}`); } @@ -110,7 +111,7 @@ export async function removeCanvasState(id) { const db = await openDB(); const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME); - + await createDBRequest(store, 'delete', id, "Error removing canvas state"); log.debug(`Remove success for id: ${id}`); } @@ -120,7 +121,7 @@ export async function saveImage(imageId, imageSrc) { const db = await openDB(); const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(IMAGE_STORE_NAME); - + await createDBRequest(store, 'put', {imageId, imageSrc}, "Error saving image"); log.debug(`Image saved successfully for id: ${imageId}`); } @@ -130,7 +131,7 @@ export async function getImage(imageId) { const db = await openDB(); const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); const store = transaction.objectStore(IMAGE_STORE_NAME); - + const result = await createDBRequest(store, 'get', imageId, "Error getting image"); log.debug(`Get image success for id: ${imageId}`, result ? 'found' : 'not found'); return result ? result.imageSrc : null; @@ -141,7 +142,7 @@ export async function removeImage(imageId) { const db = await openDB(); const transaction = db.transaction([IMAGE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(IMAGE_STORE_NAME); - + await createDBRequest(store, 'delete', imageId, "Error removing image"); log.debug(`Remove image success for id: ${imageId}`); } @@ -151,15 +152,15 @@ export async function getAllImageIds() { const db = await openDB(); const transaction = db.transaction([IMAGE_STORE_NAME], 'readonly'); const store = transaction.objectStore(IMAGE_STORE_NAME); - + return new Promise((resolve, reject) => { const request = store.getAllKeys(); - + request.onerror = (event) => { log.error("Error getting all image IDs:", event.target.error); reject("Error getting all image IDs"); }; - + request.onsuccess = (event) => { const imageIds = event.target.result; log.debug(`Found ${imageIds.length} image IDs in database`); @@ -173,7 +174,7 @@ export async function clearAllCanvasStates() { const db = await openDB(); const transaction = db.transaction([STATE_STORE_NAME], 'readwrite'); const store = transaction.objectStore(STATE_STORE_NAME); - + await createDBRequest(store, 'clear', null, "Error clearing canvas states"); log.info("All canvas states cleared successfully."); } diff --git a/js/logger.js b/js/logger.js index a14b200..08cbf2e 100644 --- a/js/logger.js +++ b/js/logger.js @@ -1,6 +1,6 @@ /** * Logger - Centralny system logowania dla ComfyUI-LayerForge - * + * * Funkcje: * - Różne poziomy logowania (DEBUG, INFO, WARN, ERROR) * - Możliwość włączania/wyłączania logów globalnie lub per moduł @@ -39,7 +39,7 @@ const LEVEL_NAMES = { class Logger { constructor() { - this.config = { ...DEFAULT_CONFIG }; + this.config = {...DEFAULT_CONFIG}; this.logs = []; this.enabled = true; this.loadConfig(); @@ -50,7 +50,7 @@ class Logger { * @param {Object} config - Obiekt konfiguracyjny */ configure(config) { - this.config = { ...this.config, ...config }; + this.config = {...this.config, ...config}; this.saveConfig(); return this; } @@ -147,7 +147,7 @@ class Logger { * @param {Object} logData - Dane logu */ printToConsole(logData) { - const { timestamp, module, level, levelName, args } = logData; + const {timestamp, module, level, levelName, args} = logData; const prefix = `[${timestamp}] [${module}] [${levelName}]`; if (this.config.useColors && typeof console.log === 'function') { const color = COLORS[level] || '#000000'; @@ -178,7 +178,7 @@ class Logger { return arg; }) })); - + localStorage.setItem(this.config.storageKey, JSON.stringify(simplifiedLogs)); } catch (e) { console.error('Failed to save logs to localStorage:', e); @@ -223,7 +223,7 @@ class Logger { try { const storedConfig = localStorage.getItem('layerforge_logger_config'); if (storedConfig) { - this.config = { ...this.config, ...JSON.parse(storedConfig) }; + this.config = {...this.config, ...JSON.parse(storedConfig)}; } } catch (e) { console.error('Failed to load logger config from localStorage:', e); @@ -251,23 +251,23 @@ class Logger { console.warn('No logs to export'); return; } - + let content; let mimeType; let extension; - + if (format === 'json') { content = JSON.stringify(this.logs, null, 2); mimeType = 'application/json'; extension = 'json'; } else { - content = this.logs.map(log => + content = this.logs.map(log => `[${log.timestamp}] [${log.module}] [${log.levelName}] ${log.args.join(' ')}` ).join('\n'); mimeType = 'text/plain'; extension = 'txt'; } - const blob = new Blob([content], { type: mimeType }); + const blob = new Blob([content], {type: mimeType}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -277,7 +277,7 @@ class Logger { document.body.removeChild(a); URL.revokeObjectURL(url); } - + /** * Log na poziomie DEBUG * @param {string} module - Nazwa modułu @@ -314,6 +314,7 @@ class Logger { this.log(module, LogLevel.ERROR, ...args); } } + export const logger = new Logger(); export const debug = (module, ...args) => logger.debug(module, ...args); export const info = (module, ...args) => logger.info(module, ...args);