From dd5fc5470f48da77a49284f4a185b2744dadd3c1 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Sat, 10 Jan 2026 11:12:31 -0300 Subject: [PATCH 01/14] Fix keyboard shortcuts capturing events when node is unfocused Prevent LayerForge from intercepting Ctrl+C, Ctrl+V, and other keyboard shortcuts when the canvas is not focused. This was causing unwanted popups and interfering with other nodes in ComfyUI. Changes: - Remove document.body focus check from handlePasteEvent - Add focus validation to handleKeyDown before processing shortcuts - Modifier keys (Ctrl, Shift, Alt, Meta) are still tracked globally - All other shortcuts only trigger when canvas is focused Fixes issue where paste events were captured globally regardless of focus. --- js/CanvasInteractions.js | 15 ++++++++++++--- src/CanvasInteractions.ts | 21 ++++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index 7de76c9..c3432d1 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -519,14 +519,24 @@ export class CanvasInteractions { return targetHeight / oldHeight; } handleKeyDown(e) { + // Always track modifier keys regardless of focus if (e.key === 'Control') this.interaction.isCtrlPressed = true; if (e.key === 'Meta') this.interaction.isMetaPressed = true; if (e.key === 'Shift') this.interaction.isShiftPressed = true; - if (e.key === 'Alt') { + if (e.key === 'Alt') this.interaction.isAltPressed = true; + // Check if canvas is focused before handling any shortcuts + const shouldHandle = this.canvas.isMouseOver || + this.canvas.canvas.contains(document.activeElement) || + document.activeElement === this.canvas.canvas; + if (!shouldHandle) { + return; + } + // Canvas-specific key handlers (only when focused) + if (e.key === 'Alt') { e.preventDefault(); } if (e.key.toLowerCase() === 's') { @@ -1157,8 +1167,7 @@ export class CanvasInteractions { async handlePasteEvent(e) { const shouldHandle = this.canvas.isMouseOver || this.canvas.canvas.contains(document.activeElement) || - document.activeElement === this.canvas.canvas || - document.activeElement === document.body; + document.activeElement === this.canvas.canvas; if (!shouldHandle) { log.debug("Paste event ignored - not focused on canvas"); return; diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index 0381d46..e636214 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -646,11 +646,23 @@ export class CanvasInteractions { } handleKeyDown(e: KeyboardEvent): void { + // Always track modifier keys regardless of focus if (e.key === 'Control') this.interaction.isCtrlPressed = true; if (e.key === 'Meta') this.interaction.isMetaPressed = true; if (e.key === 'Shift') this.interaction.isShiftPressed = true; + if (e.key === 'Alt') this.interaction.isAltPressed = true; + + // Check if canvas is focused before handling any shortcuts + const shouldHandle = this.canvas.isMouseOver || + this.canvas.canvas.contains(document.activeElement) || + document.activeElement === this.canvas.canvas; + + if (!shouldHandle) { + return; + } + + // Canvas-specific key handlers (only when focused) if (e.key === 'Alt') { - this.interaction.isAltPressed = true; e.preventDefault(); } if (e.key.toLowerCase() === 's') { @@ -664,7 +676,7 @@ export class CanvasInteractions { this.canvas.shapeTool.activate(); return; } - + // Globalne skróty (Undo/Redo/Copy/Paste) const mods = this.getModifierState(e); if (mods.ctrl || mods.meta) { @@ -1345,9 +1357,8 @@ export class CanvasInteractions { const shouldHandle = this.canvas.isMouseOver || this.canvas.canvas.contains(document.activeElement) || - document.activeElement === this.canvas.canvas || - document.activeElement === document.body; - + document.activeElement === this.canvas.canvas; + if (!shouldHandle) { log.debug("Paste event ignored - not focused on canvas"); return; From be37966b45673014b2d6a845913b924d7ec39c97 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Wed, 14 Jan 2026 16:11:22 -0300 Subject: [PATCH 02/14] Add DOM connection check to prevent capturing events in subgraphs Ensure the canvas element is actually connected to the DOM before handling paste events. This prevents LayerForge from capturing paste events when navigating in subgraphs where the canvas is not visible. Adds check for canvas.isConnected and document.body.contains() to verify the canvas is part of the active DOM tree. --- js/CanvasInteractions.js | 4 ++++ src/CanvasInteractions.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index c3432d1..24056a6 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -1165,6 +1165,10 @@ export class CanvasInteractions { } } async handlePasteEvent(e) { + // Check if canvas is connected to DOM and visible + if (!this.canvas.canvas.isConnected || !document.body.contains(this.canvas.canvas)) { + return; + } const shouldHandle = this.canvas.isMouseOver || this.canvas.canvas.contains(document.activeElement) || document.activeElement === this.canvas.canvas; diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index e636214..3476166 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -1354,6 +1354,10 @@ export class CanvasInteractions { } async handlePasteEvent(e: ClipboardEvent): Promise { + // Check if canvas is connected to DOM and visible + if (!this.canvas.canvas.isConnected || !document.body.contains(this.canvas.canvas)) { + return; + } const shouldHandle = this.canvas.isMouseOver || this.canvas.canvas.contains(document.activeElement) || From 4e5ef18d93cbfa4d080fc682c44bde7921174b94 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Thu, 15 Jan 2026 09:38:59 -0300 Subject: [PATCH 03/14] Fix canvas initialization and sizing bugs - Add image loading validation before sending canvas data to server Prevents 'Failed to get confirmation' error when images haven't finished loading after workflow reload. Waits 100ms and checks if all layer images are complete before rendering output. - Improve layer loading error handling in CanvasState Better logging when layers fail to load from IndexedDB. Allows empty canvas as valid state instead of failing. - Add ResizeObserver for canvas container Fixes bug where canvas only shows in top half of node. Watches container size changes and triggers re-render to ensure canvas dimensions are correctly calculated after DOM layout. --- js/CanvasIO.js | 12 ++++++++++++ js/CanvasState.js | 8 ++++---- js/CanvasView.js | 6 ++++++ src/CanvasIO.ts | 18 ++++++++++++++++-- src/CanvasState.ts | 8 ++++---- src/CanvasView.ts | 7 +++++++ 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/js/CanvasIO.js b/js/CanvasIO.js index 582843a..833e480 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -197,6 +197,18 @@ export class CanvasIO { } async _renderOutputData() { log.info("=== RENDERING OUTPUT DATA FOR COMFYUI ==="); + // Check if layers have valid images loaded + const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + if (layersWithoutImages.length > 0) { + log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); + // Wait a bit for images to load + await new Promise(resolve => setTimeout(resolve, 100)); + // Check again + const stillIncomplete = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + if (stillIncomplete.length > 0) { + throw new Error(`Canvas not ready: ${stillIncomplete.length} layer(s) still have incomplete image data. Try clicking on a layer to force initialization, or wait a moment and try again.`); + } + } // Użyj zunifikowanych funkcji z CanvasLayers const imageBlob = await this.canvas.canvasLayers.getFlattenedCanvasAsBlob(); const maskBlob = await this.canvas.canvasLayers.getFlattenedMaskAsBlob(); diff --git a/js/CanvasState.js b/js/CanvasState.js index 74e6be4..3fb28b9 100644 --- a/js/CanvasState.js +++ b/js/CanvasState.js @@ -88,10 +88,10 @@ export class CanvasState { log.debug(`Output Area resized to ${this.canvas.width}x${this.canvas.height} and viewport set.`); const loadedLayers = await this._loadLayers(savedState.layers); this.canvas.layers = loadedLayers.filter((l) => l !== null); - log.info(`Loaded ${this.canvas.layers.length} layers.`); - if (this.canvas.layers.length === 0) { - log.warn("No valid layers loaded, state may be corrupted."); - return false; + log.info(`Loaded ${this.canvas.layers.length} layers from ${savedState.layers.length} saved layers.`); + if (this.canvas.layers.length === 0 && savedState.layers.length > 0) { + log.warn(`Failed to load any layers. Saved state had ${savedState.layers.length} layers but all failed to load. This may indicate corrupted IndexedDB data.`); + // Don't return false - allow empty canvas to be valid } this.canvas.updateSelectionAfterHistory(); this.canvas.render(); diff --git a/js/CanvasView.js b/js/CanvasView.js index 4b12db2..0a3659d 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -884,6 +884,12 @@ async function createCanvasWidget(node, widget, app) { if (controlsElement) { resizeObserver.observe(controlsElement); } + // Watch the canvas container itself to detect size changes and fix canvas dimensions + const canvasContainerResizeObserver = new ResizeObserver(() => { + // Force re-read of canvas dimensions on next render + canvas.render(); + }); + canvasContainerResizeObserver.observe(canvasContainer); canvas.canvas.addEventListener('focus', () => { canvasContainer.classList.add('has-focus'); }); diff --git a/src/CanvasIO.ts b/src/CanvasIO.ts index 4072a4d..c7708a8 100644 --- a/src/CanvasIO.ts +++ b/src/CanvasIO.ts @@ -217,11 +217,25 @@ export class CanvasIO { async _renderOutputData(): Promise<{ image: string, mask: string }> { log.info("=== RENDERING OUTPUT DATA FOR COMFYUI ==="); - + + // Check if layers have valid images loaded + const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + if (layersWithoutImages.length > 0) { + log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); + // Wait a bit for images to load + await new Promise(resolve => setTimeout(resolve, 100)); + + // Check again + const stillIncomplete = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + if (stillIncomplete.length > 0) { + throw new Error(`Canvas not ready: ${stillIncomplete.length} layer(s) still have incomplete image data. Try clicking on a layer to force initialization, or wait a moment and try again.`); + } + } + // Użyj zunifikowanych funkcji z CanvasLayers const imageBlob = await this.canvas.canvasLayers.getFlattenedCanvasAsBlob(); const maskBlob = await this.canvas.canvasLayers.getFlattenedMaskAsBlob(); - + if (!imageBlob || !maskBlob) { throw new Error("Failed to generate canvas or mask blobs"); } diff --git a/src/CanvasState.ts b/src/CanvasState.ts index 4023a0d..5687662 100644 --- a/src/CanvasState.ts +++ b/src/CanvasState.ts @@ -118,11 +118,11 @@ export class CanvasState { log.debug(`Output Area resized to ${this.canvas.width}x${this.canvas.height} and viewport set.`); const loadedLayers = await this._loadLayers(savedState.layers); this.canvas.layers = loadedLayers.filter((l): l is Layer => l !== null); - log.info(`Loaded ${this.canvas.layers.length} layers.`); + log.info(`Loaded ${this.canvas.layers.length} layers from ${savedState.layers.length} saved layers.`); - if (this.canvas.layers.length === 0) { - log.warn("No valid layers loaded, state may be corrupted."); - return false; + if (this.canvas.layers.length === 0 && savedState.layers.length > 0) { + log.warn(`Failed to load any layers. Saved state had ${savedState.layers.length} layers but all failed to load. This may indicate corrupted IndexedDB data.`); + // Don't return false - allow empty canvas to be valid } this.canvas.updateSelectionAfterHistory(); diff --git a/src/CanvasView.ts b/src/CanvasView.ts index a1940c7..d487910 100644 --- a/src/CanvasView.ts +++ b/src/CanvasView.ts @@ -1000,6 +1000,13 @@ $el("label.clipboard-switch.mask-switch", { resizeObserver.observe(controlsElement); } + // Watch the canvas container itself to detect size changes and fix canvas dimensions + const canvasContainerResizeObserver = new ResizeObserver(() => { + // Force re-read of canvas dimensions on next render + canvas.render(); + }); + canvasContainerResizeObserver.observe(canvasContainer); + canvas.canvas.addEventListener('focus', () => { canvasContainer.classList.add('has-focus'); }); From 068ed9ee59dcc42587c3606b56ecab6d02794bd7 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Thu, 15 Jan 2026 09:40:33 -0300 Subject: [PATCH 04/14] Skip sending canvas data for bypassed nodes Fix critical issue where LayerForge was trying to send canvas data even when the node was bypassed (mode === 4). This caused unnecessary errors and blocked workflow execution. Now properly checks node.mode before attempting to send data via WebSocket, skipping bypassed nodes entirely. --- js/CanvasView.js | 17 ++++++++++++----- src/CanvasView.ts | 19 +++++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/js/CanvasView.js b/js/CanvasView.js index 0a3659d..a785f91 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -1044,13 +1044,20 @@ app.registerExtension({ log.info(`Found ${canvasNodeInstances.size} CanvasNode(s). Sending data via WebSocket...`); const sendPromises = []; for (const [nodeId, canvasWidget] of canvasNodeInstances.entries()) { - if (app.graph.getNodeById(nodeId) && canvasWidget.canvas && canvasWidget.canvas.canvasIO) { - log.debug(`Sending data for canvas node ${nodeId}`); - sendPromises.push(canvasWidget.canvas.canvasIO.sendDataViaWebSocket(nodeId)); - } - else { + const node = app.graph.getNodeById(nodeId); + if (!node) { log.warn(`Node ${nodeId} not found in graph, removing from instances map.`); canvasNodeInstances.delete(nodeId); + continue; + } + // Skip bypassed nodes + if (node.mode === 4) { + log.debug(`Node ${nodeId} is bypassed, skipping data send.`); + continue; + } + if (canvasWidget.canvas && canvasWidget.canvas.canvasIO) { + log.debug(`Sending data for canvas node ${nodeId}`); + sendPromises.push(canvasWidget.canvas.canvasIO.sendDataViaWebSocket(nodeId)); } } try { diff --git a/src/CanvasView.ts b/src/CanvasView.ts index d487910..13e16c6 100644 --- a/src/CanvasView.ts +++ b/src/CanvasView.ts @@ -1202,12 +1202,23 @@ app.registerExtension({ const sendPromises: Promise[] = []; for (const [nodeId, canvasWidget] of canvasNodeInstances.entries()) { - if (app.graph.getNodeById(nodeId) && canvasWidget.canvas && canvasWidget.canvas.canvasIO) { - log.debug(`Sending data for canvas node ${nodeId}`); - sendPromises.push(canvasWidget.canvas.canvasIO.sendDataViaWebSocket(nodeId)); - } else { + const node = app.graph.getNodeById(nodeId); + + if (!node) { log.warn(`Node ${nodeId} not found in graph, removing from instances map.`); canvasNodeInstances.delete(nodeId); + continue; + } + + // Skip bypassed nodes + if (node.mode === 4) { + log.debug(`Node ${nodeId} is bypassed, skipping data send.`); + continue; + } + + if (canvasWidget.canvas && canvasWidget.canvas.canvasIO) { + log.debug(`Sending data for canvas node ${nodeId}`); + sendPromises.push(canvasWidget.canvas.canvasIO.sendDataViaWebSocket(nodeId)); } } From 986e0a23a235a65580c7a56681bd3107a986552d Mon Sep 17 00:00:00 2001 From: diodiogod Date: Sat, 17 Jan 2026 15:03:00 -0300 Subject: [PATCH 05/14] Fix canvas sizing bug by separating display and output dimensions The canvas was getting corrupted to a small strip because of confusion between two different dimension types: - Output area dimensions (logical working area, e.g. 512x512) - Display canvas dimensions (actual pixels shown on screen) Root cause: Setting canvas.width/height attributes to match output area while also using CSS width:100%/height:100% created conflicts. When zooming or reloading, wrong dimensions would be read and saved. Fix: Remove canvas element width/height attribute assignments. Let the render loop control display size based on clientWidth/clientHeight. Keep output area dimensions separate. This prevents the canvas from being saved with corrupted tiny dimensions and fixes the issue where canvas would only show in a small strip after zooming or reloading workflows. --- js/Canvas.js | 4 ++-- js/CanvasLayers.js | 4 ++-- src/Canvas.ts | 4 ++-- src/CanvasLayers.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/js/Canvas.js b/js/Canvas.js index 16c0bdb..66f8692 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -443,8 +443,8 @@ export class Canvas { * Inicjalizuje podstawowe właściwości canvas */ initCanvas() { - this.canvas.width = this.width; - this.canvas.height = this.height; + // Don't set canvas.width/height here - let the render loop handle it based on clientWidth/clientHeight + // this.width and this.height are for the OUTPUT AREA, not the display canvas this.canvas.style.border = '1px solid black'; this.canvas.style.maxWidth = '100%'; this.canvas.style.backgroundColor = '#606060'; diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index 0e08158..a1e180f 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -1100,8 +1100,8 @@ export class CanvasLayers { this.canvas.width = width; this.canvas.height = height; this.canvas.maskTool.resize(width, height); - this.canvas.canvas.width = width; - this.canvas.canvas.height = height; + // Don't set canvas.width/height - the render loop will handle display size + // this.canvas.width/height are for OUTPUT AREA dimensions, not display canvas this.canvas.render(); if (saveHistory) { this.canvas.canvasState.saveStateToDB(); diff --git a/src/Canvas.ts b/src/Canvas.ts index f08b72d..7e29b97 100644 --- a/src/Canvas.ts +++ b/src/Canvas.ts @@ -578,8 +578,8 @@ export class Canvas { * Inicjalizuje podstawowe właściwości canvas */ initCanvas() { - this.canvas.width = this.width; - this.canvas.height = this.height; + // Don't set canvas.width/height here - let the render loop handle it based on clientWidth/clientHeight + // this.width and this.height are for the OUTPUT AREA, not the display canvas this.canvas.style.border = '1px solid black'; this.canvas.style.maxWidth = '100%'; this.canvas.style.backgroundColor = '#606060'; diff --git a/src/CanvasLayers.ts b/src/CanvasLayers.ts index d2d9343..b3f4dda 100644 --- a/src/CanvasLayers.ts +++ b/src/CanvasLayers.ts @@ -1263,8 +1263,8 @@ export class CanvasLayers { this.canvas.height = height; this.canvas.maskTool.resize(width, height); - this.canvas.canvas.width = width; - this.canvas.canvas.height = height; + // Don't set canvas.width/height - the render loop will handle display size + // this.canvas.width/height are for OUTPUT AREA dimensions, not display canvas this.canvas.render(); From 66cbcb641b606086088ac753bf626648dd97b045 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Sat, 17 Jan 2026 20:30:36 -0300 Subject: [PATCH 06/14] Add retry logic for image loading validation Increase robustness of image loading check before sending canvas data. Now retries up to 5 times with 200ms delays (1 second total) instead of a single 100ms wait. This fixes the 'Failed to get confirmation from server' error that appeared when executing workflows immediately after ComfyUI restart, before images finished loading from IndexedDB. Prevents workflow execution failures due to timing issues during canvas initialization. --- js/CanvasIO.js | 27 +++++++++++++++++---------- src/CanvasIO.ts | 29 +++++++++++++++++++---------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/js/CanvasIO.js b/js/CanvasIO.js index 833e480..75f58f7 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -197,16 +197,23 @@ export class CanvasIO { } async _renderOutputData() { log.info("=== RENDERING OUTPUT DATA FOR COMFYUI ==="); - // Check if layers have valid images loaded - const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); - if (layersWithoutImages.length > 0) { - log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); - // Wait a bit for images to load - await new Promise(resolve => setTimeout(resolve, 100)); - // Check again - const stillIncomplete = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); - if (stillIncomplete.length > 0) { - throw new Error(`Canvas not ready: ${stillIncomplete.length} layer(s) still have incomplete image data. Try clicking on a layer to force initialization, or wait a moment and try again.`); + // Check if layers have valid images loaded, with retry logic + const maxRetries = 5; + const retryDelay = 200; + for (let attempt = 0; attempt < maxRetries; attempt++) { + const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + if (layersWithoutImages.length === 0) { + break; // All images loaded + } + if (attempt === 0) { + log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); + } + if (attempt < maxRetries - 1) { + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + else { + // Last attempt failed + throw new Error(`Canvas not ready after ${maxRetries} attempts: ${layersWithoutImages.length} layer(s) still have incomplete image data. Try waiting a moment and running again.`); } } // Użyj zunifikowanych funkcji z CanvasLayers diff --git a/src/CanvasIO.ts b/src/CanvasIO.ts index c7708a8..3212b0e 100644 --- a/src/CanvasIO.ts +++ b/src/CanvasIO.ts @@ -218,17 +218,26 @@ export class CanvasIO { async _renderOutputData(): Promise<{ image: string, mask: string }> { log.info("=== RENDERING OUTPUT DATA FOR COMFYUI ==="); - // Check if layers have valid images loaded - const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); - if (layersWithoutImages.length > 0) { - log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); - // Wait a bit for images to load - await new Promise(resolve => setTimeout(resolve, 100)); + // Check if layers have valid images loaded, with retry logic + const maxRetries = 5; + const retryDelay = 200; - // Check again - const stillIncomplete = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); - if (stillIncomplete.length > 0) { - throw new Error(`Canvas not ready: ${stillIncomplete.length} layer(s) still have incomplete image data. Try clicking on a layer to force initialization, or wait a moment and try again.`); + for (let attempt = 0; attempt < maxRetries; attempt++) { + const layersWithoutImages = this.canvas.layers.filter(layer => !layer.image || !layer.image.complete); + + if (layersWithoutImages.length === 0) { + break; // All images loaded + } + + if (attempt === 0) { + log.warn(`${layersWithoutImages.length} layer(s) have incomplete image data. Waiting for images to load...`); + } + + if (attempt < maxRetries - 1) { + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } else { + // Last attempt failed + throw new Error(`Canvas not ready after ${maxRetries} attempts: ${layersWithoutImages.length} layer(s) still have incomplete image data. Try waiting a moment and running again.`); } } From 27ad139cd53af18b874c6623a1e24baea0af0adf Mon Sep 17 00:00:00 2001 From: diodiogod Date: Mon, 19 Jan 2026 17:57:14 -0300 Subject: [PATCH 07/14] Add layer copy/paste and node duplication with layers Implements two new features: - Layer copy/paste within canvas using Ctrl+C/V - Node duplication that preserves all layers Layer Copy/Paste: - Added Ctrl+V keyboard shortcut handler for pasting layers - Intercept keydown events during capture phase to handle before ComfyUI - Focus canvas when layer is clicked to ensure shortcuts work - Prevent layers panel from stealing focus on mousedown Node Duplication: - Store source node ID during serialize for copy operations - Track pending copy sources across node ID changes (-1 to real ID) - Copy canvas state from source to destination in onAdded hook - Use Map to persist copy metadata through node lifecycle --- js/CanvasInteractions.js | 10 ++++++ js/CanvasLayersPanel.js | 4 +++ js/CanvasView.js | 59 ++++++++++++++++++++++++++++++++ src/CanvasInteractions.ts | 12 +++++++ src/CanvasLayersPanel.ts | 7 +++- src/CanvasView.ts | 72 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 162 insertions(+), 2 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index 24056a6..e1bb145 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -104,6 +104,8 @@ export class CanvasInteractions { // Add a blur event listener to the window to reset key states window.addEventListener('blur', this.onBlur); document.addEventListener('paste', this.onPaste); + // Intercept Ctrl+V during capture phase to handle layer paste before ComfyUI + document.addEventListener('keydown', this.onKeyDown, { capture: true }); this.canvas.canvas.addEventListener('mouseenter', this.onMouseEnter); this.canvas.canvas.addEventListener('mouseleave', this.onMouseLeave); this.canvas.canvas.addEventListener('dragover', this.onDragOver); @@ -119,6 +121,8 @@ export class CanvasInteractions { this.canvas.canvas.removeEventListener('wheel', this.onWheel); this.canvas.canvas.removeEventListener('keydown', this.onKeyDown); this.canvas.canvas.removeEventListener('keyup', this.onKeyUp); + // Remove document-level capture listener + document.removeEventListener('keydown', this.onKeyDown, { capture: true }); window.removeEventListener('blur', this.onBlur); document.removeEventListener('paste', this.onPaste); this.canvas.canvas.removeEventListener('mouseenter', this.onMouseEnter); @@ -570,6 +574,12 @@ export class CanvasInteractions { this.canvas.canvasLayers.copySelectedLayers(); } break; + case 'v': + // Paste layers from internal clipboard + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + } + break; default: handled = false; break; diff --git a/js/CanvasLayersPanel.js b/js/CanvasLayersPanel.js index 61c2b3e..015c039 100644 --- a/js/CanvasLayersPanel.js +++ b/js/CanvasLayersPanel.js @@ -285,6 +285,8 @@ export class CanvasLayersPanel { if (nameElement && nameElement.classList.contains('editing')) { return; } + // Prevent the layers panel from stealing focus + e.preventDefault(); this.handleLayerClick(e, layer, index); }); // --- PRAWY PRZYCISK: ODJAZNACZ LAYER --- @@ -329,6 +331,8 @@ export class CanvasLayersPanel { // Aktualizuj tylko wygląd (klasy CSS), bez niszczenia DOM this.updateSelectionAppearance(); this.updateButtonStates(); + // Focus the canvas so keyboard shortcuts (like Ctrl+C/V) work for layer operations + this.canvas.canvas.focus(); log.debug(`Layer clicked: ${layer.name}, selection count: ${this.canvas.canvasSelection.selectedLayers.length}`); } startEditingLayerName(nameElement, layer) { diff --git a/js/CanvasView.js b/js/CanvasView.js index a785f91..9146b60 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -1076,6 +1076,8 @@ app.registerExtension({ }, async beforeRegisterNodeDef(nodeType, nodeData, app) { if (nodeType.comfyClass === "LayerForgeNode") { + // Map to track pending copy sources across node ID changes + const pendingCopySources = new Map(); const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { log.debug("CanvasNode onNodeCreated: Base widget setup."); @@ -1106,6 +1108,38 @@ app.registerExtension({ log.info(`Registered CanvasNode instance for ID: ${this.id}`); // Store the canvas widget on the node this.canvasWidget = canvasWidget; + // Check if this node has a pending copy source (from onConfigure) + // Check both the current ID and -1 (temporary ID during paste) + let sourceNodeId = pendingCopySources.get(this.id); + if (!sourceNodeId) { + sourceNodeId = pendingCopySources.get(-1); + if (sourceNodeId) { + // Transfer from -1 to the real ID and clear -1 + pendingCopySources.delete(-1); + } + } + if (sourceNodeId && sourceNodeId !== this.id) { + log.info(`Node ${this.id} will copy canvas state from node ${sourceNodeId}`); + // Clear the flag + pendingCopySources.delete(this.id); + // Copy the canvas state now that the widget is initialized + setTimeout(async () => { + try { + const { getCanvasState, setCanvasState } = await import('./db.js'); + const sourceState = await getCanvasState(String(sourceNodeId)); + if (!sourceState) { + log.debug(`No canvas state found for source node ${sourceNodeId}`); + return; + } + await setCanvasState(String(this.id), sourceState); + await canvasWidget.canvas.loadInitialState(); + log.info(`Canvas state copied successfully from node ${sourceNodeId} to node ${this.id}`); + } + catch (error) { + log.error(`Error copying canvas state:`, error); + } + }, 100); + } // Check if there are already connected inputs setTimeout(() => { if (this.inputs && this.inputs.length > 0) { @@ -1271,6 +1305,31 @@ app.registerExtension({ } return onRemoved?.apply(this, arguments); }; + // Handle copy/paste - save canvas state when copying + const originalSerialize = nodeType.prototype.serialize; + nodeType.prototype.serialize = function () { + const data = originalSerialize ? originalSerialize.apply(this) : {}; + // Store a reference to the source node ID so we can copy layer data + data.sourceNodeId = this.id; + log.debug(`Serializing node ${this.id} for copy`); + return data; + }; + // Handle copy/paste - load canvas state from source node when pasting + const originalConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = async function (data) { + if (originalConfigure) { + originalConfigure.apply(this, [data]); + } + // Store the source node ID in the map (persists across node ID changes) + // This will be picked up later in onAdded when the canvas widget is ready + if (data.sourceNodeId && data.sourceNodeId !== this.id) { + const existingSource = pendingCopySources.get(this.id); + if (!existingSource) { + pendingCopySources.set(this.id, data.sourceNodeId); + log.debug(`Stored pending copy source: ${data.sourceNodeId} for node ${this.id}`); + } + } + }; const originalGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function (_, options) { // FIRST: Call original to let other extensions add their options diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index 3476166..dc1c4b0 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -176,6 +176,9 @@ export class CanvasInteractions { document.addEventListener('paste', this.onPaste as unknown as EventListener); + // Intercept Ctrl+V during capture phase to handle layer paste before ComfyUI + document.addEventListener('keydown', this.onKeyDown as EventListener, { capture: true }); + this.canvas.canvas.addEventListener('mouseenter', this.onMouseEnter as EventListener); this.canvas.canvas.addEventListener('mouseleave', this.onMouseLeave as EventListener); @@ -195,6 +198,9 @@ export class CanvasInteractions { this.canvas.canvas.removeEventListener('keydown', this.onKeyDown as EventListener); this.canvas.canvas.removeEventListener('keyup', this.onKeyUp as EventListener); + // Remove document-level capture listener + document.removeEventListener('keydown', this.onKeyDown as EventListener, { capture: true }); + window.removeEventListener('blur', this.onBlur); document.removeEventListener('paste', this.onPaste as unknown as EventListener); @@ -697,6 +703,12 @@ export class CanvasInteractions { this.canvas.canvasLayers.copySelectedLayers(); } break; + case 'v': + // Paste layers from internal clipboard + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + } + break; default: handled = false; break; diff --git a/src/CanvasLayersPanel.ts b/src/CanvasLayersPanel.ts index 2ee7280..41b4e70 100644 --- a/src/CanvasLayersPanel.ts +++ b/src/CanvasLayersPanel.ts @@ -336,6 +336,8 @@ export class CanvasLayersPanel { if (nameElement && nameElement.classList.contains('editing')) { return; } + // Prevent the layers panel from stealing focus + e.preventDefault(); this.handleLayerClick(e, layer, index); }); @@ -383,11 +385,14 @@ export class CanvasLayersPanel { // Aktualizuj wewnętrzny stan zaznaczenia w obiekcie canvas // Ta funkcja NIE powinna już wywoływać onSelectionChanged w panelu. this.canvas.updateSelectionLogic(layer, isCtrlPressed, isShiftPressed, index); - + // Aktualizuj tylko wygląd (klasy CSS), bez niszczenia DOM this.updateSelectionAppearance(); this.updateButtonStates(); + // Focus the canvas so keyboard shortcuts (like Ctrl+C/V) work for layer operations + this.canvas.canvas.focus(); + log.debug(`Layer clicked: ${layer.name}, selection count: ${this.canvas.canvasSelection.selectedLayers.length}`); } diff --git a/src/CanvasView.ts b/src/CanvasView.ts index 13e16c6..64bb863 100644 --- a/src/CanvasView.ts +++ b/src/CanvasView.ts @@ -1239,6 +1239,9 @@ app.registerExtension({ async beforeRegisterNodeDef(nodeType: any, nodeData: any, app: ComfyApp) { if (nodeType.comfyClass === "LayerForgeNode") { + // Map to track pending copy sources across node ID changes + const pendingCopySources = new Map(); + const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function (this: ComfyNode) { log.debug("CanvasNode onNodeCreated: Base widget setup."); @@ -1271,10 +1274,47 @@ app.registerExtension({ const canvasWidget = await createCanvasWidget(this, null, app); canvasNodeInstances.set(this.id, canvasWidget); log.info(`Registered CanvasNode instance for ID: ${this.id}`); - + // Store the canvas widget on the node (this as any).canvasWidget = canvasWidget; + // Check if this node has a pending copy source (from onConfigure) + // Check both the current ID and -1 (temporary ID during paste) + let sourceNodeId = pendingCopySources.get(this.id); + if (!sourceNodeId) { + sourceNodeId = pendingCopySources.get(-1); + if (sourceNodeId) { + // Transfer from -1 to the real ID and clear -1 + pendingCopySources.delete(-1); + } + } + + if (sourceNodeId && sourceNodeId !== this.id) { + log.info(`Node ${this.id} will copy canvas state from node ${sourceNodeId}`); + + // Clear the flag + pendingCopySources.delete(this.id); + + // Copy the canvas state now that the widget is initialized + setTimeout(async () => { + try { + const { getCanvasState, setCanvasState } = await import('./db.js'); + const sourceState = await getCanvasState(String(sourceNodeId)); + + if (!sourceState) { + log.debug(`No canvas state found for source node ${sourceNodeId}`); + return; + } + + await setCanvasState(String(this.id), sourceState); + await canvasWidget.canvas.loadInitialState(); + log.info(`Canvas state copied successfully from node ${sourceNodeId} to node ${this.id}`); + } catch (error) { + log.error(`Error copying canvas state:`, error); + } + }, 100); + } + // Check if there are already connected inputs setTimeout(() => { if (this.inputs && this.inputs.length > 0) { @@ -1458,6 +1498,36 @@ app.registerExtension({ return onRemoved?.apply(this, arguments as any); }; + // Handle copy/paste - save canvas state when copying + const originalSerialize = nodeType.prototype.serialize; + nodeType.prototype.serialize = function (this: ComfyNode) { + const data = originalSerialize ? originalSerialize.apply(this) : {}; + + // Store a reference to the source node ID so we can copy layer data + data.sourceNodeId = this.id; + log.debug(`Serializing node ${this.id} for copy`); + + return data; + }; + + // Handle copy/paste - load canvas state from source node when pasting + const originalConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = async function (this: ComfyNode, data: any) { + if (originalConfigure) { + originalConfigure.apply(this, [data]); + } + + // Store the source node ID in the map (persists across node ID changes) + // This will be picked up later in onAdded when the canvas widget is ready + if (data.sourceNodeId && data.sourceNodeId !== this.id) { + const existingSource = pendingCopySources.get(this.id); + if (!existingSource) { + pendingCopySources.set(this.id, data.sourceNodeId); + log.debug(`Stored pending copy source: ${data.sourceNodeId} for node ${this.id}`); + } + } + }; + const originalGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; nodeType.prototype.getExtraMenuOptions = function (this: ComfyNode, _: any, options: any[]) { // FIRST: Call original to let other extensions add their options From 9b04729561578435ca143863f6d2b627e669fa09 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Mon, 19 Jan 2026 18:24:42 -0300 Subject: [PATCH 08/14] Enable cross-workflow node duplication with layers Store canvas state in IndexedDB clipboard on copy, allowing nodes to be duplicated with their layers preserved across different workflows. When copying a node, the canvas state is stored in a special '__clipboard__' entry that persists across workflow switches. On paste, if the source node doesn't exist (indicating cross-workflow paste), the system falls back to loading from the clipboard entry. --- js/CanvasView.js | 27 ++++++++++++++++++++++++--- src/CanvasView.ts | 28 +++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/js/CanvasView.js b/js/CanvasView.js index 9146b60..b9ce8a4 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -1126,14 +1126,19 @@ app.registerExtension({ setTimeout(async () => { try { const { getCanvasState, setCanvasState } = await import('./db.js'); - const sourceState = await getCanvasState(String(sourceNodeId)); + let sourceState = await getCanvasState(String(sourceNodeId)); + // If source node doesn't exist (cross-workflow paste), try clipboard if (!sourceState) { - log.debug(`No canvas state found for source node ${sourceNodeId}`); + log.debug(`No canvas state found for source node ${sourceNodeId}, checking clipboard`); + sourceState = await getCanvasState('__clipboard__'); + } + if (!sourceState) { + log.debug(`No canvas state found in clipboard either`); return; } await setCanvasState(String(this.id), sourceState); await canvasWidget.canvas.loadInitialState(); - log.info(`Canvas state copied successfully from node ${sourceNodeId} to node ${this.id}`); + log.info(`Canvas state copied successfully to node ${this.id}`); } catch (error) { log.error(`Error copying canvas state:`, error); @@ -1312,6 +1317,22 @@ app.registerExtension({ // Store a reference to the source node ID so we can copy layer data data.sourceNodeId = this.id; log.debug(`Serializing node ${this.id} for copy`); + // Store canvas state in a clipboard entry for cross-workflow paste + // 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 + await setCanvasState('__clipboard__', sourceState); + log.debug(`Stored canvas state in clipboard for node ${this.id}`); + } + } + catch (error) { + log.error('Error storing canvas state to clipboard:', error); + } + })(); return data; }; // Handle copy/paste - load canvas state from source node when pasting diff --git a/src/CanvasView.ts b/src/CanvasView.ts index 64bb863..ec1890a 100644 --- a/src/CanvasView.ts +++ b/src/CanvasView.ts @@ -1299,16 +1299,22 @@ app.registerExtension({ setTimeout(async () => { try { const { getCanvasState, setCanvasState } = await import('./db.js'); - const sourceState = await getCanvasState(String(sourceNodeId)); + let sourceState = await getCanvasState(String(sourceNodeId)); + + // If source node doesn't exist (cross-workflow paste), try clipboard + if (!sourceState) { + log.debug(`No canvas state found for source node ${sourceNodeId}, checking clipboard`); + sourceState = await getCanvasState('__clipboard__'); + } if (!sourceState) { - log.debug(`No canvas state found for source node ${sourceNodeId}`); + log.debug(`No canvas state found in clipboard either`); return; } await setCanvasState(String(this.id), sourceState); await canvasWidget.canvas.loadInitialState(); - log.info(`Canvas state copied successfully from node ${sourceNodeId} to node ${this.id}`); + log.info(`Canvas state copied successfully to node ${this.id}`); } catch (error) { log.error(`Error copying canvas state:`, error); } @@ -1507,6 +1513,22 @@ app.registerExtension({ data.sourceNodeId = this.id; log.debug(`Serializing node ${this.id} for copy`); + // Store canvas state in a clipboard entry for cross-workflow paste + // 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 + await setCanvasState('__clipboard__', sourceState); + log.debug(`Stored canvas state in clipboard for node ${this.id}`); + } + } catch (error) { + log.error('Error storing canvas state to clipboard:', error); + } + })(); + return data; }; From ce4d3329874de82d4ec86102d5e12dab021e9edd Mon Sep 17 00:00:00 2001 From: diodiogod Date: Mon, 2 Feb 2026 18:22:54 -0300 Subject: [PATCH 09/14] Fix Ctrl+V to paste from system clipboard when internal clipboard is empty --- js/CanvasInteractions.js | 6 +- src/CanvasInteractions.ts | 146 +++++++++++++++++++------------------- 2 files changed, 74 insertions(+), 78 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index e1bb145..21faa55 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -575,10 +575,8 @@ export class CanvasInteractions { } break; case 'v': - // Paste layers from internal clipboard - if (this.canvas.canvasLayers.internalClipboard.length > 0) { - this.canvas.canvasLayers.pasteLayers(); - } + // Paste from internal clipboard or system clipboard + this.canvas.canvasLayers.handlePaste('mouse'); break; default: handled = false; diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index dc1c4b0..52a1a4a 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -132,16 +132,16 @@ export class CanvasInteractions { const mouseBufferY = (worldCoords.y - this.canvas.viewport.y) * this.canvas.viewport.zoom; const newZoom = Math.max(0.1, Math.min(10, this.canvas.viewport.zoom * zoomFactor)); - + this.canvas.viewport.zoom = newZoom; this.canvas.viewport.x = worldCoords.x - (mouseBufferX / this.canvas.viewport.zoom); this.canvas.viewport.y = worldCoords.y - (mouseBufferY / this.canvas.viewport.zoom); - + // Update stroke overlay if mask tool is drawing during zoom if (this.canvas.maskTool.isDrawing) { this.canvas.maskTool.handleViewportChange(); } - + this.canvas.onViewportChange?.(); } @@ -234,7 +234,7 @@ export class CanvasInteractions { const rotatedY = dx * Math.sin(rad) + dy * Math.cos(rad); // Sprawdź czy punkt jest wewnątrz prostokąta layera - if (Math.abs(rotatedX) <= layer.width / 2 && + if (Math.abs(rotatedX) <= layer.width / 2 && Math.abs(rotatedY) <= layer.height / 2) { return true; } @@ -331,11 +331,11 @@ export class CanvasInteractions { this.startCanvasResize(coords.world); return; } - + // 2. Inne przyciski myszy if (e.button === 2) { // Prawy przycisk myszy this.preventEventDefaults(e); - + // Sprawdź czy kliknięto w obszarze któregokolwiek z zaznaczonych layerów (niezależnie od przykrycia) if (this.isPointInSelectedLayers(coords.world.x, coords.world.y)) { // Nowa logika przekazuje tylko współrzędne świata, menu pozycjonuje się samo @@ -360,7 +360,7 @@ export class CanvasInteractions { if (grabIconLayer) { // Start dragging the selected layer(s) without changing selection this.interaction.mode = 'potential-drag'; - this.interaction.dragStart = {...coords.world}; + this.interaction.dragStart = { ...coords.world }; return; } @@ -369,7 +369,7 @@ export class CanvasInteractions { this.prepareForDrag(clickedLayerResult.layer, coords.world); return; } - + // 4. Domyślna akcja na tle (lewy przycisk bez modyfikatorów) this.startPanning(e, true); // clearSelection = true } @@ -377,7 +377,7 @@ export class CanvasInteractions { handleMouseMove(e: MouseEvent): void { const coords = this.getMouseCoordinates(e); this.canvas.lastMousePosition = coords.world; // Zawsze aktualizuj ostatnią pozycję myszy - + // Sprawdź, czy rozpocząć przeciąganie if (this.interaction.mode === 'potential-drag') { const dx = coords.world.x - this.interaction.dragStart.x; @@ -390,7 +390,7 @@ export class CanvasInteractions { }); } } - + switch (this.interaction.mode) { case 'drawingMask': this.canvas.maskTool.handleMouseMove(coords.world, coords.view); @@ -425,12 +425,12 @@ export class CanvasInteractions { // Check if hovering over grab icon const wasHovering = this.interaction.hoveringGrabIcon; this.interaction.hoveringGrabIcon = this.getGrabIconAtPosition(coords.world.x, coords.world.y) !== null; - + // Re-render if hover state changed to show/hide grab icon if (wasHovering !== this.interaction.hoveringGrabIcon) { this.canvas.render(); } - + this.updateCursor(coords.world); // Update brush cursor on overlay if mask tool is active if (this.canvas.maskTool.isActive) { @@ -447,7 +447,7 @@ export class CanvasInteractions { handleMouseUp(e: MouseEvent): void { const coords = this.getMouseCoordinates(e); - + if (this.interaction.mode === 'drawingMask') { this.canvas.maskTool.handleMouseUp(coords.view); // Render only once after drawing is complete @@ -476,7 +476,7 @@ export class CanvasInteractions { if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer?.cropMode) { this.canvas.canvasLayers.handleCropBoundsTransformEnd(this.interaction.transformingLayer); } - + // Handle end of scale transformation (normal transform mode) before resetting interaction state if (this.interaction.mode === 'resizing' && this.interaction.transformingLayer && !this.interaction.transformingLayer.cropMode) { this.canvas.canvasLayers.handleScaleTransformEnd(this.interaction.transformingLayer); @@ -500,7 +500,7 @@ export class CanvasInteractions { log.info(`Mouse position: world(${coords.world.x.toFixed(1)}, ${coords.world.y.toFixed(1)}) view(${coords.view.x.toFixed(1)}, ${coords.view.y.toFixed(1)})`); log.info(`Output Area Bounds: x=${bounds.x}, y=${bounds.y}, w=${bounds.width}, h=${bounds.height}`); log.info(`Viewport: x=${this.canvas.viewport.x.toFixed(1)}, y=${this.canvas.viewport.y.toFixed(1)}, zoom=${this.canvas.viewport.zoom.toFixed(2)}`); - + this.canvas.canvasSelection.selectedLayers.forEach((layer: Layer, index: number) => { const relativeToOutput = { x: layer.x - bounds.x, @@ -547,7 +547,7 @@ export class CanvasInteractions { handleWheel(e: WheelEvent): void { this.preventEventDefaults(e); const coords = this.getMouseCoordinates(e); - + if (this.canvas.maskTool.isActive || this.canvas.canvasSelection.selectedLayers.length === 0) { // Zoom operation for mask tool or when no layers selected const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1; @@ -555,7 +555,7 @@ export class CanvasInteractions { } else { // Check if mouse is over any selected layer const isOverSelectedLayer = this.isPointInSelectedLayers(coords.world.x, coords.world.y); - + if (isOverSelectedLayer) { // Layer transformation when layers are selected and mouse is over selected layer this.handleLayerWheelTransformation(e); @@ -565,7 +565,7 @@ export class CanvasInteractions { this.performZoomOperation(coords.world, zoomFactor); } } - + this.canvas.render(); if (!this.canvas.maskTool.isActive) { this.canvas.requestSaveState(); @@ -621,7 +621,7 @@ export class CanvasInteractions { layer.height *= scaleFactor; layer.x += (oldWidth - layer.width) / 2; layer.y += (oldHeight - layer.height) / 2; - + // Handle wheel scaling end for layers with blend area this.canvas.canvasLayers.handleWheelScalingEnd(layer); } @@ -637,11 +637,11 @@ export class CanvasInteractions { } else { targetHeight = (Math.ceil(oldHeight / gridSize) - 1) * gridSize; } - + if (targetHeight < gridSize / 2) { targetHeight = gridSize / 2; } - + if (Math.abs(oldHeight - targetHeight) < 1) { if (direction > 0) targetHeight += gridSize; else targetHeight -= gridSize; @@ -704,10 +704,8 @@ export class CanvasInteractions { } break; case 'v': - // Paste layers from internal clipboard - if (this.canvas.canvasLayers.internalClipboard.length > 0) { - this.canvas.canvasLayers.pasteLayers(); - } + // Paste from internal clipboard or system clipboard + this.canvas.canvasLayers.handlePaste('mouse'); break; default: handled = false; @@ -724,7 +722,7 @@ export class CanvasInteractions { if (this.canvas.canvasSelection.selectedLayers.length > 0) { const step = mods.shift ? 10 : 1; let needsRender = false; - + // Używamy e.code dla spójności i niezależności od układu klawiatury const movementKeys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'BracketLeft', 'BracketRight']; if (movementKeys.includes(e.code)) { @@ -748,7 +746,7 @@ export class CanvasInteractions { this.canvas.canvasSelection.removeSelectedLayers(); return; } - + if (needsRender) { this.canvas.render(); } @@ -794,7 +792,7 @@ export class CanvasInteractions { this.canvas.saveState(); this.canvas.canvasState.saveStateToDB(); } - + // Reset interaction mode if it's something that can get "stuck" if (this.interaction.mode !== 'none' && this.interaction.mode !== 'drawingMask') { this.resetInteractionState(); @@ -844,7 +842,7 @@ export class CanvasInteractions { originalHeight: layer.originalHeight, cropBounds: layer.cropBounds ? { ...layer.cropBounds } : undefined }; - this.interaction.dragStart = {...worldCoords}; + this.interaction.dragStart = { ...worldCoords }; if (handle === 'rot') { this.interaction.mode = 'rotating'; @@ -878,9 +876,9 @@ export class CanvasInteractions { this.canvas.canvasSelection.updateSelection([layer]); } } - + this.interaction.mode = 'potential-drag'; - this.interaction.dragStart = {...worldCoords}; + this.interaction.dragStart = { ...worldCoords }; } startPanning(e: MouseEvent, clearSelection: boolean = true): void { @@ -896,8 +894,8 @@ export class CanvasInteractions { this.interaction.mode = 'resizingCanvas'; const startX = snapToGrid(worldCoords.x); const startY = snapToGrid(worldCoords.y); - this.interaction.canvasResizeStart = {x: startX, y: startY}; - this.interaction.canvasResizeRect = {x: startX, y: startY, width: 0, height: 0}; + this.interaction.canvasResizeStart = { x: startX, y: startY }; + this.interaction.canvasResizeRect = { x: startX, y: startY, width: 0, height: 0 }; this.canvas.render(); } @@ -911,7 +909,7 @@ export class CanvasInteractions { updateCanvasMove(worldCoords: Point): void { const dx = worldCoords.x - this.interaction.dragStart.x; const dy = worldCoords.y - this.interaction.dragStart.y; - + // Po prostu przesuwamy outputAreaBounds const bounds = this.canvas.outputAreaBounds; this.interaction.canvasMoveRect = { @@ -935,11 +933,11 @@ export class CanvasInteractions { width: moveRect.width, height: moveRect.height }; - + // Update mask canvas to ensure it covers the new output area position this.canvas.maskTool.updateMaskCanvasForOutputArea(); } - + this.canvas.render(); this.canvas.saveState(); } @@ -949,13 +947,13 @@ export class CanvasInteractions { const dy = e.clientY - this.interaction.panStart.y; this.canvas.viewport.x -= dx / this.canvas.viewport.zoom; this.canvas.viewport.y -= dy / this.canvas.viewport.zoom; - this.interaction.panStart = {x: e.clientX, y: e.clientY}; - + this.interaction.panStart = { x: e.clientX, y: e.clientY }; + // Update stroke overlay if mask tool is drawing during pan if (this.canvas.maskTool.isDrawing) { this.canvas.maskTool.handleViewportChange(); } - + this.canvas.render(); this.canvas.onViewportChange?.(); } @@ -1018,7 +1016,7 @@ export class CanvasInteractions { const o = this.interaction.transformOrigin; if (!o) return; - + const handle = this.interaction.resizeHandle; const anchor = this.interaction.resizeAnchor; const rad = o.rotation * Math.PI / 180; @@ -1036,7 +1034,7 @@ export class CanvasInteractions { // Determine sign based on handle const signX = handle?.includes('e') ? 1 : (handle?.includes('w') ? -1 : 0); const signY = handle?.includes('s') ? 1 : (handle?.includes('n') ? -1 : 0); - + localVecX *= signX; localVecY *= signY; @@ -1046,13 +1044,13 @@ export class CanvasInteractions { if (layer.cropMode && o.cropBounds && o.originalWidth && o.originalHeight) { // CROP MODE: Calculate delta based on mouse movement and apply to cropBounds. - + // Calculate mouse movement since drag start, in the layer's local coordinate system. const dragStartX_local = this.interaction.dragStart.x - (o.centerX ?? 0); const dragStartY_local = this.interaction.dragStart.y - (o.centerY ?? 0); const mouseX_local = mouseX - (o.centerX ?? 0); const mouseY_local = mouseY - (o.centerY ?? 0); - + // Rotate mouse delta into the layer's unrotated frame const deltaX_world = mouseX_local - dragStartX_local; const deltaY_world = mouseY_local - dragStartY_local; @@ -1065,20 +1063,20 @@ export class CanvasInteractions { if (layer.flipV) { mouseDeltaY_local *= -1; } - + // Convert the on-screen mouse delta to an image-space delta. const screenToImageScaleX = o.originalWidth / o.width; const screenToImageScaleY = o.originalHeight / o.height; - + const delta_image_x = mouseDeltaX_local * screenToImageScaleX; const delta_image_y = mouseDeltaY_local * screenToImageScaleY; - + let newCropBounds = { ...o.cropBounds }; // Start with the bounds from the beginning of the drag // Apply the image-space delta to the appropriate edges of the crop bounds const isFlippedH = layer.flipH; const isFlippedV = layer.flipV; - + if (handle?.includes('w')) { if (isFlippedH) newCropBounds.width += delta_image_x; else { @@ -1105,10 +1103,10 @@ export class CanvasInteractions { newCropBounds.height -= delta_image_y; } else newCropBounds.height += delta_image_y; } - - // Clamp crop bounds to stay within the original image and maintain minimum size + + // Clamp crop bounds to stay within the original image and maintain minimum size if (newCropBounds.width < 1) { - if (handle?.includes('w')) newCropBounds.x = o.cropBounds.x + o.cropBounds.width -1; + if (handle?.includes('w')) newCropBounds.x = o.cropBounds.x + o.cropBounds.width - 1; newCropBounds.width = 1; } if (newCropBounds.height < 1) { @@ -1136,7 +1134,7 @@ export class CanvasInteractions { // TRANSFORM MODE: Resize the layer's main transform frame let newWidth = localVecX; let newHeight = localVecY; - + if (isShiftPressed) { const originalAspectRatio = o.width / o.height; if (Math.abs(newWidth) > Math.abs(newHeight) * originalAspectRatio) { @@ -1148,10 +1146,10 @@ export class CanvasInteractions { if (newWidth < 10) newWidth = 10; if (newHeight < 10) newHeight = 10; - + layer.width = newWidth; layer.height = newHeight; - + // Update position to keep anchor point fixed const deltaW = layer.width - o.width; const deltaH = layer.height - o.height; @@ -1217,7 +1215,7 @@ export class CanvasInteractions { this.canvas.updateOutputAreaSize(newWidth, newHeight); } - + this.canvas.render(); this.canvas.saveState(); } @@ -1315,7 +1313,7 @@ export class CanvasInteractions { // Store the original canvas size for extension calculations this.canvas.originalCanvasSize = { width: newWidth, height: newHeight }; - + // Store the original position where custom shape was drawn for extension calculations this.canvas.originalOutputAreaPosition = { x: newX, y: newY }; @@ -1324,10 +1322,10 @@ export class CanvasInteractions { const ext = this.canvas.outputAreaExtensions; const extendedWidth = newWidth + ext.left + ext.right; const extendedHeight = newHeight + ext.top + ext.bottom; - + // Update canvas size with extensions this.canvas.updateOutputAreaSize(extendedWidth, extendedHeight, false); - + // Set outputAreaBounds accounting for extensions this.canvas.outputAreaBounds = { x: newX - ext.left, // Adjust position by left extension @@ -1335,19 +1333,19 @@ export class CanvasInteractions { width: extendedWidth, height: extendedHeight }; - + log.info(`New custom shape with extensions: original(${newX}, ${newY}) extended(${newX - ext.left}, ${newY - ext.top}) size(${extendedWidth}x${extendedHeight})`); } else { // No extensions - use original size and position this.canvas.updateOutputAreaSize(newWidth, newHeight, false); - + this.canvas.outputAreaBounds = { x: newX, y: newY, width: newWidth, height: newHeight }; - + log.info(`New custom shape without extensions: position(${newX}, ${newY}) size(${newWidth}x${newHeight})`); } @@ -1383,7 +1381,7 @@ export class CanvasInteractions { log.info("Paste event detected, checking clipboard preference"); const preference = this.canvas.canvasLayers.clipboardPreference; - + if (preference === 'clipspace') { log.info("Clipboard preference is clipspace, delegating to ClipboardManager"); @@ -1427,7 +1425,7 @@ export class CanvasInteractions { public activateOutputAreaTransform(): void { // Clear any existing interaction state before starting transform this.resetInteractionState(); - + // Deactivate any active tools that might conflict if (this.canvas.shapeTool.isActive) { this.canvas.shapeTool.deactivate(); @@ -1435,10 +1433,10 @@ export class CanvasInteractions { if (this.canvas.maskTool.isActive) { this.canvas.maskTool.deactivate(); } - + // Clear selection to avoid confusion this.canvas.canvasSelection.updateSelection([]); - + // Set transform mode this.interaction.mode = 'transformingOutputArea'; this.canvas.render(); @@ -1447,7 +1445,7 @@ export class CanvasInteractions { private getOutputAreaHandle(worldCoords: Point): string | null { const bounds = this.canvas.outputAreaBounds; const threshold = 10 / this.canvas.viewport.zoom; - + // Define handle positions const handles = { 'nw': { x: bounds.x, y: bounds.y }, @@ -1474,7 +1472,7 @@ export class CanvasInteractions { private startOutputAreaTransform(handle: string, worldCoords: Point): void { this.interaction.outputAreaTransformHandle = handle; this.interaction.dragStart = { ...worldCoords }; - + const bounds = this.canvas.outputAreaBounds; this.interaction.transformOrigin = { x: bounds.x, @@ -1497,17 +1495,17 @@ export class CanvasInteractions { 'sw': { x: bounds.x + bounds.width, y: bounds.y }, 'w': { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 }, }; - + this.interaction.outputAreaTransformAnchor = anchorMap[handle]; } private resizeOutputAreaFromHandle(worldCoords: Point, isShiftPressed: boolean): void { const o = this.interaction.transformOrigin; if (!o) return; - + const handle = this.interaction.outputAreaTransformHandle; const anchor = this.interaction.outputAreaTransformAnchor; - + let newX = o.x; let newY = o.y; let newWidth = o.width; @@ -1578,7 +1576,7 @@ export class CanvasInteractions { private updateOutputAreaTransformCursor(worldCoords: Point): void { const handle = this.getOutputAreaHandle(worldCoords); - + if (handle) { const cursorMap: { [key: string]: string } = { 'n': 'ns-resize', 's': 'ns-resize', @@ -1594,16 +1592,16 @@ export class CanvasInteractions { private finalizeOutputAreaTransform(): void { const bounds = this.canvas.outputAreaBounds; - + // Update canvas size and mask tool this.canvas.updateOutputAreaSize(bounds.width, bounds.height); - + // Update mask canvas for new output area this.canvas.maskTool.updateMaskCanvasForOutputArea(); - + // Save state this.canvas.saveState(); - + // Reset transform handle but keep transform mode active this.interaction.outputAreaTransformHandle = null; } From ab5d71597aff54f439b180397fe021b26e5de1d8 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Mon, 2 Feb 2026 20:56:56 -0300 Subject: [PATCH 10/14] Fix: Allow paste event to bubble for system clipboard access --- js/CanvasInteractions.js | 11 +++++++++-- src/CanvasInteractions.ts | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/js/CanvasInteractions.js b/js/CanvasInteractions.js index 21faa55..ef6ec67 100644 --- a/js/CanvasInteractions.js +++ b/js/CanvasInteractions.js @@ -575,8 +575,15 @@ export class CanvasInteractions { } break; case 'v': - // Paste from internal clipboard or system clipboard - this.canvas.canvasLayers.handlePaste('mouse'); + // Only handle internal clipboard paste here. + // If internal clipboard is empty, let the paste event bubble + // so handlePasteEvent can access e.clipboardData for system images. + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + } else { + // Don't preventDefault - let paste event fire for system clipboard + handled = false; + } break; default: handled = false; diff --git a/src/CanvasInteractions.ts b/src/CanvasInteractions.ts index 52a1a4a..8f39703 100644 --- a/src/CanvasInteractions.ts +++ b/src/CanvasInteractions.ts @@ -704,8 +704,15 @@ export class CanvasInteractions { } break; case 'v': - // Paste from internal clipboard or system clipboard - this.canvas.canvasLayers.handlePaste('mouse'); + // Only handle internal clipboard paste here. + // If internal clipboard is empty, let the paste event bubble + // so handlePasteEvent can access e.clipboardData for system images. + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + } else { + // Don't preventDefault - let paste event fire for system clipboard + handled = false; + } break; default: handled = false; From d44d944f2d3b8386f9404d00b59b3ccc5569d329 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Mon, 2 Feb 2026 23:15:08 -0300 Subject: [PATCH 11/14] Fix: Restore drag-and-drop in layers panel --- js/CanvasLayersPanel.js | 2 -- src/CanvasLayersPanel.ts | 50 +++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/js/CanvasLayersPanel.js b/js/CanvasLayersPanel.js index 015c039..fa989bd 100644 --- a/js/CanvasLayersPanel.js +++ b/js/CanvasLayersPanel.js @@ -285,8 +285,6 @@ export class CanvasLayersPanel { if (nameElement && nameElement.classList.contains('editing')) { return; } - // Prevent the layers panel from stealing focus - e.preventDefault(); this.handleLayerClick(e, layer, index); }); // --- PRAWY PRZYCISK: ODJAZNACZ LAYER --- diff --git a/src/CanvasLayersPanel.ts b/src/CanvasLayersPanel.ts index 41b4e70..3f91c49 100644 --- a/src/CanvasLayersPanel.ts +++ b/src/CanvasLayersPanel.ts @@ -133,7 +133,7 @@ export class CanvasLayersPanel { `; this.layersContainer = this.container.querySelector('#layers-container'); - + // Setup event listeners dla przycisków this.setupControlButtons(); this.setupMasterVisibilityToggle(); @@ -269,7 +269,7 @@ export class CanvasLayersPanel { layerRow.className = 'layer-row'; layerRow.draggable = true; layerRow.dataset.layerIndex = String(index); - + const isSelected = this.canvas.canvasSelection.selectedLayers.includes(layer); if (isSelected) { layerRow.classList.add('selected'); @@ -318,7 +318,7 @@ export class CanvasLayersPanel { const scale = Math.min(48 / layer.image.width, 48 / layer.image.height); const scaledWidth = layer.image.width * scale; const scaledHeight = layer.image.height * scale; - + // Wycentruj obraz const x = (48 - scaledWidth) / 2; const y = (48 - scaledHeight) / 2; @@ -336,8 +336,6 @@ export class CanvasLayersPanel { if (nameElement && nameElement.classList.contains('editing')) { return; } - // Prevent the layers panel from stealing focus - e.preventDefault(); this.handleLayerClick(e, layer, index); }); @@ -399,15 +397,15 @@ export class CanvasLayersPanel { startEditingLayerName(nameElement: HTMLElement, layer: Layer): void { const currentName = layer.name; nameElement.classList.add('editing'); - + const input = document.createElement('input'); input.type = 'text'; input.value = currentName; input.style.width = '100%'; - + nameElement.innerHTML = ''; nameElement.appendChild(input); - + input.focus(); input.select(); @@ -417,7 +415,7 @@ export class CanvasLayersPanel { layer.name = newName; nameElement.classList.remove('editing'); nameElement.textContent = newName; - + this.canvas.saveState(); log.info(`Layer renamed to: ${newName}`); }; @@ -441,11 +439,11 @@ export class CanvasLayersPanel { if (!existingNames.includes(proposedName)) { return proposedName; } - + // Sprawdź czy nazwa już ma numerację w nawiasach const match = proposedName.match(/^(.+?)\s*\((\d+)\)$/); let baseName, startNumber; - + if (match) { baseName = match[1].trim(); startNumber = parseInt(match[2]) + 1; @@ -453,34 +451,34 @@ export class CanvasLayersPanel { baseName = proposedName; startNumber = 1; } - + // Znajdź pierwszą dostępną numerację let counter = startNumber; let uniqueName; - + do { uniqueName = `${baseName} (${counter})`; counter++; } while (existingNames.includes(uniqueName)); - + return uniqueName; } toggleLayerVisibility(layer: Layer): void { layer.visible = !layer.visible; - + // If layer became invisible and is selected, deselect it if (!layer.visible && this.canvas.canvasSelection.selectedLayers.includes(layer)) { const newSelection = this.canvas.canvasSelection.selectedLayers.filter((l: Layer) => l !== layer); this.canvas.updateSelection(newSelection); } - + this.canvas.render(); this.canvas.requestSaveState(); - + // Update the eye icon in the panel this.renderLayers(); - + log.info(`Layer "${layer.name}" visibility toggled to: ${layer.visible}`); } @@ -540,7 +538,7 @@ export class CanvasLayersPanel { const line = document.createElement('div'); line.className = 'drag-insertion-line'; - + if (isUpperHalf) { line.style.top = '-1px'; } else { @@ -568,7 +566,7 @@ export class CanvasLayersPanel { const rect = e.currentTarget.getBoundingClientRect(); const midpoint = rect.top + rect.height / 2; const isUpperHalf = e.clientY < midpoint; - + // Oblicz docelowy indeks let insertIndex = targetIndex; if (!isUpperHalf) { @@ -577,7 +575,7 @@ export class CanvasLayersPanel { // Użyj nowej, centralnej funkcji do przesuwania warstw this.canvas.canvasLayers.moveLayers(this.draggedElements, { toIndex: insertIndex }); - + log.info(`Dropped ${this.draggedElements.length} layers at position ${insertIndex}`); } @@ -615,17 +613,17 @@ export class CanvasLayersPanel { */ updateButtonStates(): void { if (!this.container) return; - + const deleteBtn = this.container.querySelector('#delete-layer-btn') as HTMLButtonElement; const hasSelectedLayers = this.canvas.canvasSelection.selectedLayers.length > 0; - + if (deleteBtn) { deleteBtn.disabled = !hasSelectedLayers; - deleteBtn.title = hasSelectedLayers + deleteBtn.title = hasSelectedLayers ? `Delete ${this.canvas.canvasSelection.selectedLayers.length} selected layer(s)` : 'No layers selected'; } - + log.debug(`Button states updated - delete button ${hasSelectedLayers ? 'enabled' : 'disabled'}`); } @@ -646,7 +644,7 @@ export class CanvasLayersPanel { this.layersContainer = null; this.draggedElements = []; this.removeDragInsertionLine(); - + log.info('CanvasLayersPanel destroyed'); } } From b8fbcee67a7589b8931cc362d470d250827c2694 Mon Sep 17 00:00:00 2001 From: diodiogod Date: Tue, 3 Feb 2026 02:00:24 -0300 Subject: [PATCH 12/14] Fix focus/modifier issues and improve multi-layer selection UX --- .vscode/settings.json | 1 + __pycache__/__init__.cpython-312.pyc | Bin 0 -> 796 bytes __pycache__/__init__.cpython-313.pyc | Bin 0 -> 843 bytes __pycache__/canvas_node.cpython-312.pyc | Bin 0 -> 61328 bytes __pycache__/canvas_node.cpython-313.pyc | Bin 0 -> 62407 bytes js/CanvasInteractions.js | 13 +++++++++---- js/CanvasLayersPanel.js | 20 ++++++++++++++++++++ python/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 248 bytes python/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 297 bytes python/__pycache__/config.cpython-312.pyc | Bin 0 -> 210 bytes python/__pycache__/config.cpython-313.pyc | Bin 0 -> 259 bytes python/__pycache__/logger.cpython-312.pyc | Bin 0 -> 14272 bytes python/__pycache__/logger.cpython-313.pyc | Bin 0 -> 14611 bytes src/CanvasInteractions.ts | 14 +++++++++++--- src/CanvasLayersPanel.ts | 20 ++++++++++++++++++++ 15 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 __pycache__/__init__.cpython-313.pyc create mode 100644 __pycache__/canvas_node.cpython-312.pyc create mode 100644 __pycache__/canvas_node.cpython-313.pyc create mode 100644 python/__pycache__/__init__.cpython-312.pyc create mode 100644 python/__pycache__/__init__.cpython-313.pyc create mode 100644 python/__pycache__/config.cpython-312.pyc create mode 100644 python/__pycache__/config.cpython-313.pyc create mode 100644 python/__pycache__/logger.cpython-312.pyc create mode 100644 python/__pycache__/logger.cpython-313.pyc diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01373a52c5d7e85240fb20993ebaafee8bf534a7 GIT binary patch literal 796 zcmZ`$&ubGw6n>LmyG@!D4Ykmq1UzVK7QIf$Dq{sPq9CR>k#X;9Z+@7Ov{$2YU-rTY>CvkTahtts2aoA4%OXJus)sNp6AT! zcCaZXX593PCz%1qIlpO37!ysy_#<2i3ggQ2#|pPdO=DDTG`d_h^asLtQppjEC}mQA zLz;{$S)<+Py(~Ob>RoObZMPj)G-rJcXMGCBs;*iXuXcK4dA)7WuEsXZMbX3AHSY>H z@*;#TAb0_DqW~b^f!vo57WxYVG)N9phpA!uF#SGxoH|M!r;pN~9-ZA?KZ`y&hmD_b z?;FJSBVQr*1!CuLTU_@q4)l@Nv*bC2=qM=Y>8(w{?+Cs;lHtbEe)xN^+?UL#==>Wv CQNIBI literal 0 HcmV?d00001 diff --git a/__pycache__/__init__.cpython-313.pyc b/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..635ed18bd3217be391735df2b41b2e07a10200e9 GIT binary patch literal 843 zcmZ`$L2DC16rM@a>^5yuG}Jat3^$P;< z-2;KCS4sb;Ox^TAAj8o`so1kW zF?5MFfI=v|rmU(=)c{t@*DvdT``cjg3QBeatM?&`p$Hn1aRf!#=qy0WQ^?{Cm{Ko1 zrg7ojmVzQtq+OFeaahANoSMnJA?(chMa{2j_w<_OI&4~VI&PaCvj~y(Y^hW*R*;zVi}?a76|-B!$gb;yUVKx3O!9@Yo_n@Y z-by7zn0L7dIb1~A)O{hMmd#D(3Z-T-o3>1$&%_boqL0>{3-mN~w+@+PKS+nZ(4!0b8P8y%bdFoPJqSaTl zVRD>vTJ`Spf}wBbih9<-?e47(><<|y;&!Rei))8wNwu>1qu{R$Faz|?0jbp~$A%|qk+b}t;72%Ugr dFDBv1jTMP+Nj%w8!L^CE@o%xxuELV?-rr^W%)0;p literal 0 HcmV?d00001 diff --git a/__pycache__/canvas_node.cpython-312.pyc b/__pycache__/canvas_node.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3491ccd4859a2e8fb68e4b23a6218a50351dc87a GIT binary patch literal 61328 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 Uh57m>gZAB`oF8<^TWy literal 0 HcmV?d00001 diff --git a/__pycache__/canvas_node.cpython-313.pyc b/__pycache__/canvas_node.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9abddf1a6fa5396660f76862c5924dc8fb68c021 GIT binary patch literal 62407 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 wGm?GorIGog^sQ#@(wdDWTb l !== layer); - this.canvas.canvasSelection.updateSelection(newSelection); - } + // If already selected, do NOT deselect - allows dragging multiple layers with Ctrl held + // User can use right-click in layers panel to deselect individual layers } else { if (!this.canvas.canvasSelection.selectedLayers.includes(layer)) { diff --git a/js/CanvasLayersPanel.js b/js/CanvasLayersPanel.js index fa989bd..0e2f46c 100644 --- a/js/CanvasLayersPanel.js +++ b/js/CanvasLayersPanel.js @@ -123,6 +123,26 @@ export class CanvasLayersPanel { e.preventDefault(); e.stopPropagation(); this.deleteSelectedLayers(); + return; + } + // Handle Ctrl+C/V for layer copy/paste when panel has focus + if (e.ctrlKey || e.metaKey) { + if (e.key.toLowerCase() === 'c') { + if (this.canvas.canvasSelection.selectedLayers.length > 0) { + e.preventDefault(); + e.stopPropagation(); + this.canvas.canvasLayers.copySelectedLayers(); + log.info('Layers copied from panel'); + } + } + else if (e.key.toLowerCase() === 'v') { + e.preventDefault(); + e.stopPropagation(); + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + log.info('Layers pasted from panel'); + } + } } }); log.debug('Panel structure created'); diff --git a/python/__pycache__/__init__.cpython-312.pyc b/python/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae77ad4db04f94f76199a83e508df8727ac0c6d5 GIT binary patch literal 248 zcmX@j%ge<81goQyGUI{tV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##?MT`RVDYMMca&uAe6JEe0^_7JGbrVopwc{7Qz;Ak%(Dd0E92mn0_Tq^4x1 zrIi+E=I0p#L9tPcOJYf4Oh96Cc4B&Jag1|*Zdzrir&&yLX>mz@ZhT&T3Q#mBu`;zN zEx#x|HKw4lBqKjBCO$qhFS8^*Uaz3?7l%!5eoARhs$CH$&?=C-iur-W2WCb_#-|J- P54eRpv>Mrq*nwgI1A{{K literal 0 HcmV?d00001 diff --git a/python/__pycache__/__init__.cpython-313.pyc b/python/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11a7cce4e3174fe8e172a0b1b7679d95f32ebbef GIT binary patch literal 297 zcmey&%ge<81goQyGUI{tV-N=hn4pZ$VnD`JhG2$ZMsEf$#v(=qhF~Ur#v-P4W=)ot zj6g|E##?MT`RVDYMMca&uAe6JEe0^_7JGbrVopwc{7Qz;KnaFh-CkBP#U+VJIjJd` zX=$a!nfZB!Ku~NHgnei?(F00=oeE^X>4fhms$>! zh%e8~OUW-UjxWeB0viXDh)FIjF3Hc0&&y8%8k3V)nOc;VUzDB-G@&FTKQAUeJ~J<~ xBtBlRpz;=nO>TZlX-=wL5hu`XAdeLD1BnmJjEsy=8AKj%3wLNWvKO%f#Q;QDRLKAU literal 0 HcmV?d00001 diff --git a/python/__pycache__/config.cpython-312.pyc b/python/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bda3fd4ff578fd17d6c0a722bb2c84177247fee7 GIT binary patch literal 210 zcmX@j%ge<81goQyGIN0RV-N=h7@>^M96-i&h7^V0TLgW85tQrurcroH*yxS0tEmzp*jix literal 0 HcmV?d00001 diff --git a/python/__pycache__/config.cpython-313.pyc b/python/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4550b307ba333759a88139cd3e8dec2e0d4e1eb GIT binary patch literal 259 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 OPv<_)HPX3+ivI_z#f#_w literal 0 HcmV?d00001 diff --git a/python/__pycache__/logger.cpython-313.pyc b/python/__pycache__/logger.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb1a720fff2aaf2fd6364f88e6935d461d2eda0 GIT binary patch literal 14611 zcmcgTX>b$SnXS8Z*phAek`EeRSO&~tE}JXH2N>I8Mr{FunJB_)8-Xl&yCsaBnO(w8 zGA5NRo2^io9jbs7RX{SEVRoi6lWcZtCKGmY>{h7}Svft<7FCaA2-WWLb0I`d1(!5SDPpZP-DT%}fT zO{R^KgSK@_KJ#Tw>*2>n17DPTmn;qCIo8gap*+XxSxfGfvR2l{=D|G9+~aMe*nHN` zI@kiXkS$`J%`{(3PNNu3qXgEzF0*DOr=CzLX;}&_%eDedVB*V;m(@^PRMC-1JuhUSJr7t=%!tu;{rh|*b z_&_)^!3YyVj2mUb(UItQAQB2N!Ek`-h>i|V_+#m zxBlq8ABRKZ(R*+F^mS(Z-ua(?eP|Nr-Y`Cq`GJFd^v*al5{{mLm4-Mb96!N~!SY9= z!T7!NFivkY9Oa|qP!k;qG2_hXXhevHL%6VLEHHE`z>I}Mr*RN@EYc-192$x9axFi} zO)`_CAua-&xc3Gh4MxX9!f6=N@(2;`acd+~PbAhAiH}0I2#1Y@A|u_QFn1sj35Gd- zD19*G12`sWp%FfpsN)ncGKEYG46Fs_)dAz`c_VA!O{|fpSrcy_q1|SQ?u(A}ac8*j zqY~)&2qnfNCj#W3M>yD{Ab>ltAd5u%{gJ>Z=l4q%zaMcC#=On%|EoCQNv^T`{lg(% zh=oHDE)s=m133>#kLMxj@jN60IS)ziIppb*iv4~8_cr7Y#A1BtL_Ed`em|cFvo63N zC0#h!b{txq2y?;E@NirRMI)Oa37e0124aEZF9e282SzyIc$#!PT}L|ly4pR*$0k}f zwRpI3rNTcRiU5`c|5%hK!zmTVhvGskI_i%^gD^}ODQ_4l4@N-3I}S$(wA(y3!5d*c z1R%163IN}r=4|=ry5_8T=Q?E||IGi}P96mwY5fRKXVOqH*t&6)e74A+7WuPjR5EzG z*sg&|eZ}C$(SdGB4^s*}4ZR*2uq$1Epsl{Yt)6w$l1(N{e;7|qvZs$h;9^J~f@Fyc zoPP+(Q;@7Mj6W8I0||4I34a7byyO@SoFzqmAvDSPM^8w$6M)0`7<7wAVv-{k8sz}t zqho$R!)PG3h|tm!om$ui3;S{|QZxFfEubSn0?;50fb2jz5W@3OyEQyrN4ZUsqXSpS z1%bn1f*8j?+6lwOn*pdrOfVV@oB|G=0QQ2`6HuVArpj?cdg!7g%;WkNHUikAxkL>r zHPnFKN4*TVJX)woH- zq|0WD9?%VQuDZXo6XF?Cmj}&|_Q>WWpr%WdPvcd19x9Zi2a%;N8z4ht3aq9m zA4mru<;7JmNxA!OP0xqQZauIknFCj~Bt<~tc+E=+;U~pS_M;%^U*3WcSIV4y~ z{_fXC4YOqMDA4F+Kl2511s8l3ixluCqhjSPbN(WF}GqMYCj->m-Z(9RMch zH{f`Btd@z{bTS~I6edzjGQk}9OEPvG>O16R-9{eIUNZ9BP?Qf!CJ+<20DcE49pky- z&{@esru1{5xCA^U`+uqyz_TFT5hgjMn+B&B8KXkfM&N|UqH)VcoqzY?N1&v}s z?%w>D~*y@JZP!MO)>9NndE0GCt0ypdrk0b^PYpyXC1> zJH=Hy=gXO!Ww%?C?BL-IG5)^TEgu|0c2L zP@?>W86&KFrfjxgcKfV5VXsTjb%c%G8d-|yJe~o!nMZ==?Ff)a`2qlv$?p$Fhd|t* z(BKhGJkp(%mzFL=TsZ9a-_h_W*#u<7FUc5PgG~`2?aAWg8`Q7$J3iHG4fUVWC5HOP z6aZNlVY@}bT#*n9uR%>tVGec!5$7PpPthpGL70OD0T65mfM%8kXkpC&t*iy0jkN;I zV{HKQ**t)DHXopawF4|*9RLg20)RzqAwVZv#1|9QPy*ENlpGn;ua79+N5^1s@p=IJ z@0|yeDiUB$gTxJ>E&$?_?Z!+e&rr5pM2ShH<`iU5C4g*z+|o4U22NM7K@F-(1(XJy zl#fzmpN|TwU5WHme`PY^tlqiif?aAo-Jny$G7_D+|5%R)BP9vAJ`h^?1j^>UU)S@SISx_8=p8g!RJlLtb|Qf zenQWYEWGB@HHyXp%;LBtA!ajJ+z9v&t6B=q4|LDcq>O^s(CjlBL0k{GO?*G}kn%gb zy4!ty1OARfp6;Igl71*UHX#{@qG*{*CN4sZOkybkVF_TGhNFQXm_=CcALhYEvFm4@A&!OX^Z3Zn4CjELl6H|CV*Gv;sc1 zxzcj{fD|+5rRW-wu6aP$J*;G=^uMCZm!AmASaKryfFrodYcfVpE(?I@C>E$4y2&Y5 zD^`ODf8KdTG=nB(gh5)#`83OPXcf$*>it|T;Y4jp|IC-wqO<}{suty1C)v%nDXN+p zRPBUZy1(Z0_N&AFDqIehjn=DL21zPFTM)b$ zWlx0w=S*d81c!0HhUJxwH;*Cv+l zOqI8bbgheUQL$#Ti|;aUAP>lJIFZmU=Z zmxg57o+k!s?LH0OI%;p8xOM8rsibw|d}GsFmOGY2%R#ZRH`Vx}*!W_ykxd+VS!{fH zsuOQ5vo_II|Ik`{lfN~2V=`&oH1A%ED^G0c6W#qOH!Hf?q}!Kx$uGM7=`MMqt>K|{ z)y>Vf+HSNZt?TFO*C5go8#~4Nu5=r-XsdZ>ZG3azTLt##E0!G2$2$1_@fkxot4YzX)gs%{qCW^op&#yUuj;W; zA60J#_;IbfXASkU)$X3{)X#0so;uynYc&YhSbDbTe%_)%xZQG4ult2g18|W;#RQ`f z!|6vrQY)FVUTm=>fsq3}Ffl0bLwSPSk*s3eU ztD0e%A!}AxA*)64&FWZvs}^@rNB%(Iq6e5H5V&A8N3?^gxeZQ=oV%6B0w;!%I5G6f zI+<*tpTi-Oi7kL(Vp?Hr4XTh;6iY|6?OI%?oEz4tLf$;-?9B*v^;$^8DG{9mcQoiD z2?;TO^z`J~!47ZFKuA*e02Viv)JsLO-&S|(Ym=3XN={UxtU^GLEAr9TEl9`nF!B^wf z5fLemrr`u6F~vn9ndRF?oaZFhGO?)Ey#ZZ>_W*#egtkpvFIZEwOQc=1+aAz0DAXL& zJ1^{<-g{y1?CzA+En3~TgKwR_bNa35o#^|aRPzC``M_hXDX(STQFz(<_tx3YYrR)` zZ=1!+&F>G~ckFxYpd8LA>r`yY`q?u(<*Zns40$cTbrdJcIzH(9xc9@}#0xKpoyQU# zgNealf*Vc*M-n9`A2>quj`EbF_P(R`fusK6`psz>(Elq((?fgFl=Y8~?J&?E1hjBI ztg6|kqkf{Zbhvas$!phk6ze`JHo%WXYzo$~%%+8qxkMdxWNzR{4sK0+HB=mt!#8Ri zC+!@_#BjiOG63z|S{e747jWpbYf7?$2OVyA@(r+Z8RoNU-^8wXV*%WkR>OS__a)!U za6l#Dsp8)PYHwV3EaCmk_fiOmYJz&tz6bFm0F5=}?i;>4ul&%rJbTgfE7Pyc6_tVHt5|hy?Ul8OhV7zjN6NKJ zbnSZo@W+E64kljoi(Owyx&l*nl#dlxPhK0nGMcommAg5LMf;iry=FNdt7d{0&V405 zCif@eDa^$RKH^QO%l3rnVdYB_Je$z?G!O+)6+Q7o;QUKc##5O~&b-|uP95>3hO5x2iA-y#add_m{G(qh{ye44Vbl^dVNOpDyY zJT1nxAVmt}4X}h4q9;NeKS^Fj#AD+hy@PrAjYpQen6!q~XnJ$^GX=g&Q|2v%A}CdZC3~!N zx6G|KVt@cYN~k3X-+cIVMXD265h@X*Pw&m4##ne$s5rwTrc7ucS6iPpb2H5qE>Opz zeFiV?d`UX`4WEfMwCWfz$lS(B%dx;%NFXB1!*7AMi-Ly8TP^UnfPw8=iXj*Tr$5|b zA?%@IGJ}yb)FlH$^#jwRlYNBRt{c|y;L3&D39x#Mhwz=Yd84EY@FR;%b@Q$fF2bE1 z*4r z%Ul|M8<}4I(EJ7X#eWCEKOjJZO)?X=rN6)Z1%3t#(f*foFfH74F~XH)!0B=2lJWF7 ze)+3d`6UE62|O|Y5hCA1@YzRNNGLIXustbor=oZUX*I(25rKgc`U%tsX8;frE@iJ1 z?RD1=r|d1Fy#*-#VL@@KV6|AV`g%jMzzsoqBUMy#x$$D7=-P58mUL}N7H*j`L8;Sq zx$R=x?5W#z$)XKYGzf;`>dXBX`>)r(S^8Gxoyw$h8+jC9woh9wSY}%9(o>fEbn9|c z4{4fk4ce6}8Ta5((PXJ)q?ILU1DIVeYPK68aH;FmY||jk-O)}K!_JuqybL5y7U5MP zc^C(ej(9|oY(AsfxRY%@|07m?AHWim*%0JT#7Cr(Wq2j!dZ5t-w9)~y;2xmANzasA zExT5Ir8+^k-mZhq-)%_#-1^@kiy%#oPry6F*y;Ep z_9WXQ5^li5{{&htazP+tE_Okf!L^rucD3Adr`9=Pufhtfxd&Nu&->0Ky@wnwT%`M9 z8u^YrfH|~0-KDvn5b>Q@-;1CR!8!zo5lkR>6~TD~Uq_Hp2SoO~i)BAV@UI9yLhvyH z46O292)Y4~ClBd|M+dMF0YsK5;bj1y;lra14;{{PJ-^fI4OLHVR>SJ2t~G|@r(TWS z;Cx!617OL3JVWEt>Uu-TQ;(*=(Dt-J3xL)dT2;{YDg4~~m;ylXLFQYesa1)MnmYTv zXD$~C`ocHB6+11PwTf)!{|P#SLyf34L+b8_ptSUEnMI+Mn*KZsejmnD7EF$fSlEjeNs19Lix=5@zIVo$ z&^OLH@9P_{mn8L#|6hyzm#jq+vRb}KQjDMu7RmnyWd6Sv^I_Iv2zf7G3@Jv?h>Q7U z&c&d%C*o)`Wd0O-+eQ(~Q_KGwbo3}^hdc&(mDsY4Lzw}w1^x#L^0beTSxOHl=bSm5 z(AUleALwhBV+`p4{}zx&(Tq{d`vAUjgEBeg3T-EjZUN zqh1RGC<)n{p#C;w5cOHe$!?c!pLYLi)B}PHSeMFZD>#nVK>hO%3T;#+wzn~B4Vg!E znK{6vV~E$nkju~wCVCp#@bDi0KTAC0a2epE&_%&L3wh$A;04SPEv1$H!!=NlP1X|e z)D!0dj6VVn6#fA;X~Uzy_%2wiwN0;ezuy0~{+acnp7}3R{#;P zBPuMTX>uxEBu#=8@}FP`k;xtxdY2r&*uzWE1TaPkC^#X8uF*Wvnl<``5=vwFgo1z8 zPpJG)DEn`yvfoe}KcO7Yj0TN;YWx`mpT~s|%zNGQHP6g`ye7@*Z3Nrf(H)cdvG?L$ z{IR@Qs;E{hs=Xcnzs+N-F;9EW{Me}scu?uzwkU&xK$UgsCFo(%o-;y^;wmzEY581X z3Hac_kug{1nsWlIc~W35)Sk7G}#n!Qgb0{_}TSzJkT g#W~|sqgK-dR)fZ^0`jx&nZ>9n{9U10Q%JD*zje$ l !== layer); - this.canvas.canvasSelection.updateSelection(newSelection); } + // If already selected, do NOT deselect - allows dragging multiple layers with Ctrl held + // User can use right-click in layers panel to deselect individual layers } else { if (!this.canvas.canvasSelection.selectedLayers.includes(layer)) { this.canvas.canvasSelection.updateSelection([layer]); diff --git a/src/CanvasLayersPanel.ts b/src/CanvasLayersPanel.ts index 3f91c49..6626a5e 100644 --- a/src/CanvasLayersPanel.ts +++ b/src/CanvasLayersPanel.ts @@ -144,6 +144,26 @@ export class CanvasLayersPanel { e.preventDefault(); e.stopPropagation(); this.deleteSelectedLayers(); + return; + } + + // Handle Ctrl+C/V for layer copy/paste when panel has focus + if (e.ctrlKey || e.metaKey) { + if (e.key.toLowerCase() === 'c') { + if (this.canvas.canvasSelection.selectedLayers.length > 0) { + e.preventDefault(); + e.stopPropagation(); + this.canvas.canvasLayers.copySelectedLayers(); + log.info('Layers copied from panel'); + } + } else if (e.key.toLowerCase() === 'v') { + e.preventDefault(); + e.stopPropagation(); + if (this.canvas.canvasLayers.internalClipboard.length > 0) { + this.canvas.canvasLayers.pasteLayers(); + log.info('Layers pasted from panel'); + } + } } }); From b1f29eefdb507a5bddb049201bddf9b0db03ccce Mon Sep 17 00:00:00 2001 From: diodiogod Date: Tue, 3 Feb 2026 15:12:51 -0300 Subject: [PATCH 13/14] Cleanup: Remove accidentally committed __pycache__ and .vscode folders --- .vscode/settings.json | 1 - __pycache__/__init__.cpython-312.pyc | Bin 796 -> 0 bytes __pycache__/__init__.cpython-313.pyc | Bin 843 -> 0 bytes __pycache__/canvas_node.cpython-312.pyc | Bin 61328 -> 0 bytes __pycache__/canvas_node.cpython-313.pyc | Bin 62407 -> 0 bytes 5 files changed, 1 deletion(-) delete mode 100644 .vscode/settings.json delete mode 100644 __pycache__/__init__.cpython-312.pyc delete mode 100644 __pycache__/__init__.cpython-313.pyc delete mode 100644 __pycache__/canvas_node.cpython-312.pyc delete mode 100644 __pycache__/canvas_node.cpython-313.pyc diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 01373a52c5d7e85240fb20993ebaafee8bf534a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 796 zcmZ`$&ubGw6n>LmyG@!D4Ykmq1UzVK7QIf$Dq{sPq9CR>k#X;9Z+@7Ov{$2YU-rTY>CvkTahtts2aoA4%OXJus)sNp6AT! zcCaZXX593PCz%1qIlpO37!ysy_#<2i3ggQ2#|pPdO=DDTG`d_h^asLtQppjEC}mQA zLz;{$S)<+Py(~Ob>RoObZMPj)G-rJcXMGCBs;*iXuXcK4dA)7WuEsXZMbX3AHSY>H z@*;#TAb0_DqW~b^f!vo57WxYVG)N9phpA!uF#SGxoH|M!r;pN~9-ZA?KZ`y&hmD_b z?;FJSBVQr*1!CuLTU_@q4)l@Nv*bC2=qM=Y>8(w{?+Cs;lHtbEe)xN^+?UL#==>Wv CQNIBI diff --git a/__pycache__/__init__.cpython-313.pyc b/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 635ed18bd3217be391735df2b41b2e07a10200e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmZ`$L2DC16rM@a>^5yuG}Jat3^$P;< z-2;KCS4sb;Ox^TAAj8o`so1kW zF?5MFfI=v|rmU(=)c{t@*DvdT``cjg3QBeatM?&`p$Hn1aRf!#=qy0WQ^?{Cm{Ko1 zrg7ojmVzQtq+OFeaahANoSMnJA?(chMa{2j_w<_OI&4~VI&PaCvj~y(Y^hW*R*;zVi}?a76|-B!$gb;yUVKx3O!9@Yo_n@Y z-by7zn0L7dIb1~A)O{hMmd#D(3Z-T-o3>1$&%_boqL0>{3-mN~w+@+PKS+nZ(4!0b8P8y%bdFoPJqSaTl zVRD>vTJ`Spf}wBbih9<-?e47(><<|y;&!Rei))8wNwu>1qu{R$Faz|?0jbp~$A%|qk+b}t;72%Ugr dFDBv1jTMP+Nj%w8!L^CE@o%xxuELV?-rr^W%)0;p diff --git a/__pycache__/canvas_node.cpython-312.pyc b/__pycache__/canvas_node.cpython-312.pyc deleted file mode 100644 index 3491ccd4859a2e8fb68e4b23a6218a50351dc87a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61328 zcmeFad30M>njeUj07#Gk36KPLfcqv&)Iv%WwQ!ZFg_5;dqU~Z)d=N#Nn;$?+f1wYeuzwTK3lU>DXJ}m%`qLJ_Fv`QRA4Y&&1;DMpMVq z`qCol%zb7SFTF3Fy)*hU@Yau7#w>jn7AIxYI+od&saBb&B*SRdn61w?X797JP~&L! zSWaIq{H8t!%OQWPU=Xz$E9@(Zlu_JQ%tA`~O4!@k=VWhJp9}9asV$MXrG2F=tgNq$ zy-^=lu6eY4tfH@Ctg^3itg5eSth%pytfsGKthTRqtgf$)rA{BMA6wVAPOZ|Y+!Z6w z%H0=za~bYR_qsfMcuOOFjl}&%G>+`!Ozw=4Z_2--?~%c%U-qpL(=CTb-)qDiipG$A z?#*whCBOXUEOMzf++D_WlUs;X}bmj$~Obo*>Ja^FPgZVXl(q}jhvTD ziN3j_*f@JsT=A{~g$RVd%uIL$!2+cffzqIt-^kxQ~K`X2cwQNQftD&>}NRj;bvihSah7RKQT7xJ?iFPbo2PFJ?B1+m(KeFH->krmme7S zoSERqJjkG^ZOS`waDsD>c2Dpum!s~XG55H4z&kuK9=0Cl-H1Ot?&dls#?K57Ap;A1 z{~7 z@vz=a$-*Z0`9b$2WrIvj2M+D&KhSle>p(bdaA5q!0Z;!pY8BQYL#k&Q-u>Q*{xid) z?y!M=aKn7u($u2{7W_SM!nvaIMplyw&~r%bSN)0lPg90fe)SL3zpp;3x~b{q?TCk( zXW(^H6V?szL!NNT^QhsF$3x)4>HPe}zLsY_-htDjZf^L@nJEwI+K9)q@!8G+@4&N% z2L_)<_j{i0m>4^A;doEevx8F}@5C6Zy64%^feUUP9X;fJHl|4plNUZWwvG;ro#qC1 z^2I2q8h;OLa9&p3($9|x)dv^352_C?oq_WJpH;tkJ9z?rXe4zEpARR}GQNmDP~ps} zfhSUfA9&{v3RU|TJ3pw}ztjzfcOvlX*T70;tzCm!3N9tb3Fqqd1nlHf1LsQO~(6#jxKy&P6)emaWwWV!~oq^Ie zR<5rU%LL2CIXph>9Ud4R_PIGHCX6#?y7(%e#+n%(A8K(%B9BdQQ=@L@_=MLv@Z!Mm zC{526XYw`_qTsdKpjKS~2Rh@iATbWvY?#Oiyq3q33v74Ug2oNHri3!mmT{=9mg(&z=|Z z*3XwO=dJ(R6?nSsNbln+K+ucdfWo&>9UI7T!uid*9v}!^-*x23p(AUQ$-14vN}jr$ zwqTx3+_eksM4Yw9+p9$|dq!xMCp_}bN%Ml=h`<^xPkoX=iXg^Fq~>6VT8 z_e!hijlNEQ%^(^bqOo+gDPSyR{avH(u1TSaj1P>t`}@PG{rwVDf#2NU|H9P3s2qat z?$Lhl1=O6138zPDIOrZ7?e7QV>!8A^zjjMCZGo33hg#(!@c7xQs!(RZ%lkgn>GWkE ztKhIout;%ZE|1b9W6nlf{i+couK{Wl9dj+e9R>S8KI0aDP#f@B8cf@_*c-RnuHeo{q+yHvFBa3j#AVb`xbxO60hbfsd zOi`do58`+PIDjTH^Z-D)pIdn4{N?k(+*&cWcEz0e#@^TVzH#WaL$htO{QL`xu1kjk z=2rRp!PgGXm0vm-FjvW6C}#VdWMGbqU2>gJ_k8Z zsVw#U3sh3$1A~t8{XH{-I&rXqx`V-r1MpxYP>s-FB|Lt$yz-_2KSn5i!sA!YbkkbD zc8Jaw>fRa+e-iQdQ*df@(pad^hmB|41Kug#jkCtYly`EC(iZbc|*0{fXsF;8X&mgs8-0 zX7K~2z|sjs^z4Td4@*~a^WWTjt>tRVcUuFw)dcfGxdrsPmu7im=W9EIX;orcl~CRC zLE83^*%~w#i{|1F&CXS=%G@e}vDmSsF=1o*GAecjgj@6#J|#XQRPBUE>fLFK_=qx6 z(I|570`L*&iv}pZq=3#avK63Aa)rJlN0hADZX00Da8ex z2a+LvoKBwA->&kjIUTj}E7XS7Kc+VnbWXiid3vS1YLut{isiA^$CPJ~bCXs|naZmG zWsomj(^{{Bc5+59(TmvQ*UIVQ#}33v)wK4a_Cn+xL!31AvNgyLAp@VI*L}{Z03j#m z_JVTg=31PLc*k=Tr~HVt&^RZ9an3WKUpk}k4Ac<7Ghdk!i$LHSk#ZSD(kZQ6;8#-G z7H5r-EnI#a<%O+7?s&k+hzemXK8HGwA%>?MOZjEhswT_S63Q#OR(!QMn72jD+afgW74v#7?F%`}gU&6Y zb4$S4ESQV#S)6m7H+rx42FrJg<-3KpV`BO74=l%5tPY`Ild!o<*wiiL?OC?&4Q*|m z(Ot>7oDsCvi`M%2r$y_QQ1kYe9j>73YlXr}W@@8#a0eS>J@7utT5GJjsk*%~x&6V2Pc zSk)r%a}Uw$UTH7xw5e{p+BV?zuFcfBL;G%FZs%6*yIb|}$8B*6SX@9t0*g_aaFh}f zJ>Wl%Cc>$G0FUxgkl#mDSVqd0#+H%VtDJN=O|-ly4w+;bsV}P8GQvA%8EJcm{}X_^ zuTYxOn4Dgk(8_#s_H<%i>)8wqYeBXHg@1SqSO6N!=I+hNj`bopxquI6hHnOb#}F zn}2+CG4n?!7WN7`JC@BmS8Taotm+Z?xrYeuuTEX4Sr9)x(>7&{DB1 zev`fIE5Ky7PjT8ezslYw!Et4;`%qTw1PbS}+}!=qyd)gS zSD7%W)`<5J)zo$7nBaBpEq%-b1q6kf}?nh|tt79E=xPKb`ykM-K!>WoXB zZ}h&_8#Gso=4zp4yJ&8GXjG-=2ss-*G&f2UQH>1QOx*Ufvc-WvuUvEpIqgAnhiL9# zkfc-nxyOORUuny3bEw{NnA$bkcU-w`JGAfY(8E8dL!kT+lFLx5zlA53vYHGHkWrL}Eku@nzMs{KqWOvwW?w@19Flvetd~{Ta`KC>EG0zAxfc!O97oL`pd|fBEM3(H2gDj>0HK}I>fbbRxWc`4PVxqdd|k#x$HMJ zTn?AZIq=SVQ;XANes9>&e(2DFuD0H8pS63~>zo*OwsGA0!#E$heb!F*nSrTMue05K zcHqU~3BE>4Bqa7NYy@)&Br7n2O%dOjGd}atvlw?UhF=_ZpYxgb4s&k#S6Fw9pK^!w z-2L?eh^BDC@F`J96ETIh;pXmy**DI@9OXA zM2eJ2#!(NOrpAXse#3zgon+)eiW}zLT-Z3lxY$IJJ3GvA?s0yHRJ3+*)Dz1`OY7-9 zeEeAdu~Ub;j{5QwW@}8?pCP&DA{@X+qsm#c;1+tG5k~xC?X*yQG32UU)Zg7HoPANO zKPNcPhc<4%n=L$hT6p2SxZ{G*=nECPK#R;?;$ZK${kP!&H?RRxuyqB{J@&GI=HlG@$EXQ^g&aSeupPZI z=mw7>a$05`>9Y^`h*aTm?sP^Q7DsGT(6Q5zojn#k^3ptEPcjfGi;H;DVoo#)S?(2e z@)Mg3jycWq)u?g?3eH_LVhM{F9(Q6rN=t{N zF{Bv)6e2C0I_w!9$4)Um=;n_iNjOEyKb(4EV06mO(ucL&@L1U3!5VUd2(I^>9hf9q zxSJoF!YcMq!JEior!I+p&Ck*YhQkufRG_~tNkzaZ6D)2NiyIeQf#S_G zX$iseb-~*0V(s?DHnDc+Ql(hCHzvWtQ;X_AaSIaJiUo7=XZK?f=B6Ke78`%`;=)-W zXVi-Wdt+K~2 ztuMWdHQyfeYmzQf6_=6^!bS3O{8~n|>WY#-k`W}4R#~F*0K|fk$A0TH^3ItC)}jsX#Ay*`%&A{b32IfUT< z8M2Q;2njyiz_+mR^zbM|!v^^7n_6t-#Msgizc`!@S(c&k{=o@wym*hVC=Pm1QE|W{ zou8)?2x{;*$@yQ&p-$j$k@Kf;!s>3?xB(?5Cq{A7p}jZ)M-t&i1e3fMawI%kRBEv8rc?bXGn#L|8H1~&cyr{aBeaJlJTf*nqv5~pPZ=Qrp@c{7 z11>!R@>51gU@_t0GGs#BbPDn*P6)dxiz*Y{b^h#l{2H}jmXE9 zEFZa~k+Mfa<=T!^tR2Q>5dm?e>d_cplq@|XwX!ezmdl+sd)LWdM;c^b^zAouHo0G? z)4h#y$Os8?B|LI{r!)NNBP7a}@c1)0`>kv_H_XFkIl{R5$C(CG)Ni3p%b&{SGOkVf$j*ew_=!+QBy@Mu&^!u_SvC1+Uy^(<{sjn0k_YOb zY@%w+hw;WC)wI>yA(!S)liSmY(B}ZjrZfF1{!E%>|NqR8dr5x(HfDz3I?^TQ7kw-9 zCF%GkA6u^Q&6u4aW|kEBGr3HnabjK+Gw4wA8_XM{JTqwCyl&B{{8r3YC!3K`DD7hT zm&QRK!;Jddy)n?!@Fk(An&0F%JQjMEMWCnQ3DDE# zrn3G=!O*NHz|btNT!wnnwl4`o?S5NQ7;67YFx39FVQ4niM0Ny*RwTf|O4M;JIB0t^ z90WN#m8)WqH+2o%raQVeWgo*es=ue9bR7F9_bu=+{d5la$GP}(tdZjy_{JKslaxO~ z%&UaQk5c7YOy?!m>RWlh0B*G6Qbeg<7cbCK+Z>gkI-o=;QuX%U^FM9X=IWNUC!+#1Y%c|%MLMR5|CszMkU>5I-G*~e{( z*;$j!%v((gY{>OH;>u{2+w{aTwtnd{V*6#=S~HnzS+mDtFjYXs=j2vK-!guUI!7^( z?a`L_(aJMlf%pAC%c-|a)d-D)bD+DN!U+`kmiF};S>ZAvMDzN8kwd)Skb4G1v&{-Nnu6*$Ix+Y>WEp%F&~znElyhK|cMotEphxEfb7}~TK@ZcT zBk@kBFTLLBd470u5=j~w8hizEszIp9jzSN5kQqog_IR9J^C&d=2~zQUtm!jL=$<3BoyiLE3C z7abh+9+LM(CdPW_K~PM=LMQ$Vqr>{_ktR?Erw6=)XPpygoIaDp=&AP9NSvF0K>V=w z$e#AF_Hgf>n_zE3l6-8y`*{u(*TVRA>nF#Dwx32zn>N==)Y43eBg#Vi&kb|lvruM= za!mWrx}mD&^%YP9&~@PY3_%-FY6eS$ni4Pqi&RJDg!NJ;DUpP>NJ6RJECqUG{pXq% zXIw#^eip-Ljf+q1G)V3A@QetpKEPi^aXvX;sU}3HMMeIAx*!GmR$znq3Obl#vE12H z<8cEoNfbWqK7AAe4y9)T31M~p5MC2`c`t;A0aD3ro+DnwPI7nmP5N^ULK;Q5&Fh!nlVl zS8{xnZKt8;0FEP86x1HZos!anJip~0^JPf0T=~8J=+q!B?K4xOqZcA+X|_*Ft4?Y7 z00xnY_LV3+hDek+_H>IA>XyAV8=1gK*a}$!1~`yoKP~sLA+o@kc4yc^!f;ILlw;y0 zq}Gl(0eSHVSdb)Gh#&`{aSs*(<^;BPst&6Mp!#IIThBAPmN7p2&X^> z1APwN9*EmafxoepY0`&HG_wYHe&9k_GduwSjtgG5r{@ruECVk>B1c-VVbkfR&63(r z*oZ2>NQ~BSN<{zXeJb!S6{v^8Kgsb7OisGTxv*XC874UsseM>GIWfueEV^fMbQqGb z$Xv=QteZf>umOrhuqfaS>!pg36#amA(?*DDptAi^gY-T(KjGo0sc;WDL_3uDxuueX z4=qXlJLLQ!IV20lL^UKvFD+&MO>({m2V7+ac^Di?W$-}KpGp}X8|p`&877BE!Hyon zrZ9EpjGLq}3=~8n|7Nx{JpEE_)x%)}O%zIHq*U}HoX$QeX^reBB~Ft^^?CP&utk}5 z5vh@k=m(aKY_dSACo5N)IBAmqGz}k=XIW)2SHQ|5FcPVniIpm)mFmNyr!qgQy~MIA zMUR00{e(x-!u&qs`pOa*Sjkq#XNc$#3=#{gTVpz+z6a$Z$o&Rw8%5j3pshu;wJdcB zww7hvJ|<(ldntFxbhk*zdwSV=@?LI1Ft<+3tqbI?TfL}NS?$E;3R)UOOGD7IMYL>L zwls&Vc1a9*y=Yw@v^I;@=4I=)6>FYQ*t}?1xUggq@(+J#eG2lx)~yd;(yG$4h1{ov zBd3I?`h>no;RT;C`I7LGU&x+bHeZDB7KLwG*t4*0ao^HQLfL+z`-T1?;Vdus zE(&Ka3PayoRjG2?G!Ip(^mffX=tvb`E)Hg{3uLYf6_w1Sf)g*=YVYT|=IU-uz3sp0 z|1h`pUP0xxT~~Jn3hEcs9~3l(YPSSycZs#TmehgT_SyaSDmDZwn#78xg%`w%ZL_H> z4yRDoyqLP+U)mDlg;%DNj9*C!UvEV=`gowIw;)au$BzU#j4 z`)But3d^n?xO!mTu~;#CV7YMjN@abpvPrCLS~wqU-YYim4K(iyRPGN}9uX^#1S*fs z?g_OV7M?mO96lv%?VH^qIEn?yJ(5~*EU;ODs`ng>!Y+Wg= z5UM+t_Pl@aor6oegd<0VW6uPSofeOs7LE=ImE2z!x*-={-!OY%rJ{De1-&t+MlUT4 z+-wNewu-f_f!dwH+D@^yGf>-o*Cy5;2-X}EYmO}+e>!;lIq~>&ftqj3rL5Ghe|!DS z^^28D6@l8la|X1szTxebn=Nm(&KX0_Dw4dLKe_0hGcP+kS86u|Yg@$Hmc^~X?FYo| z2LjuB1GR^OwI{^d6M@>3bB2(!KIq&eIyZ$HI+vba>JjRDL+dub{oKvxLJeEq9=|#M zu|c<`^r1@UDtnl!a@7j;7|r-jpl0{487<3)}a z20cQp_phB(s|Bjc=G9`=ZuJ3mw71HIDrx+ig^K;lg$M50ibPutO{&^k?zcy8j((WC z9dqK^&Z|2E1$FZ;d{D3uv4?F4;HtH#cjdj zF0r_4>3QMk(}CiXGije{k-;a>Bq(4os%hb+g)z+Fyraw3VcBCF z9)xR4(6wE3ZAZVjb}wa$u8x^~E2Y)J(v4#2#)XzZ>CTz`E6$1=sn=6)ZKRpG@O+@I zJ>cvJI`@mt{dcm->TUePUOi(D{r|{Oq#*IRGS9%cf;pZOB8c948bw#*LQTNcidvVHU-Mn{-70;%_GaxuU!Zn(prkEW z(j%7i+${@~^v>*Eah2V0TzAY@2V9$it`^bN^0SRUZ~19Uz}0;>TXgjzkBZu0#TKz* z%fd*YqHU={tk^r#%WB!N)En$PDR!O|I!+12eIMGNxldXq+ZO8$CSP z#Slec-HsjM)Jkd9jk@b~Lc`8?-S3aRGqzlMY{n9@6bY6J7U%e~_30H?<&A>t1@q6n zTk-z7ch)Vt4$oxVC*_Zwi{A?FIwtNqChR;e9LGB3gWP$MJ1-1g5ORFW=9lh~HXI3m zFPm%co9#kw_p*6!$X+)04Z*elLu@7$-wk9+z0ZEuXW+t8ZvgDE&P+3x{b`!Hmi+@&BjT-lB^Nylo5DH;)QICkU#N(0auC0_MfJ&)80^T z)c&b{WBkN3p)l6R{~1NcDQYNvi5QVbro&CEqy3spks_+6)2Vs`eLVzIBhSH9%(N!P z+=WrjHTq6QH+w>g^k{0?M`|Q8MOvmx^3Jc9&t=W)13CymLZeq3)24E26GSGHNG;2K zHKNdv<@$l|rO04=)UTKMHORdZx$BUZ&I6^ks4TNz_k?=eqq)jH$aO`=ZW{7WN746c z{7q}l#rSHpy*Krr>l#4Df;%!f^tm3cn|_=gn}jgvFX4< z=1QjRO77)cIvxE$bHi}mz_9PW#ms=_9&D}IPx9A%RV7ln+eZt&Nw)PbX zKCG+`zy52j%t5HW>g2kOB!gErIW+qAD>WeLI=}k)PV5Obzjj2?Nr;J?;@6*1dQGi* z{Y}WfXh(>tn(&bQrdu&`I1CCIB9IkS2$Bzxbn5|2OgdWc2DT$A5{1|iwZ!>({a?#` zfe2hMoP!owFK@;CkJBh67((rPK!T@)l1MfWfHuGgBn3v)br{t;7E2&u7vIK&U5;Qz z>~ixow1PMYeW#u9NJbrhNGp(f`DN8y=3L{S*m)f641$fq>;WDr5;lAX?P^Y_bICnQIkZl6S8Jq(y!z= zu8dqBnals{occ@MD~|kYhO35a=BwtpzJ;8CW9y}!6-(wD=UzKU)T7zq4=vU6r{3;#lrgeQ-Q+fnXXW7;q13Q$ZZHY zide4h`BPBCTXt;yY&Bg4%9LGY%6KF7wbaWOFQqOUD}Pm77O<3GbUQ2k0(C0hQ8;m0 zyNs$GqfeO>0geg(7c`g)CV? zi&F$7cWn|an+4-$HtFLQB9^z*H3D*u*HTG_49keeH0%RSGq8=0j35NaNqLe9h0AFH zz#$s+EK&g}(ff5!1%sc@0&t?*Bs@_K3V^U8pkMlA;^NTNR1gziDw!A|&X1eEU1VGaYwMO*(;P#W1aivMsU7hGE!u zbeMzfQqmNJp;H#ba5AU&EYfaqLNOG&V4mT6%n=DXe+#4Jt73VB?XyPi$V+12ro}`A z12(=UFxbHlyFEUq#4O^b7z;Lry+#d~nrw0Q)}%32<*=pyocpwgaReobzJA#29`o=7 zi5a^jh83h8h3cn=0ydGu4pasSI-6y4Aq1acJVYv|AIKwgSW(BYg>?z3#qjR239wn% z5$rw6hn>S{wnl)o1fvUJD3VpSAITFc>8nlHv>p@pXSB*eB*!XWP02NNs8`BN*}f@c zFT7_fnUknv+oAeaN=yV{M{mH^`$){yOjYTQ2X<9q-Hah*&kfqEM0?e|L9lON3<6N2 zL0!*3_@T8I?193%k8xxy0U>F2`wu+xy8>0)h00c;^#~|^A6k#Cf&ozyH7J6{5K(FC z&uoiN{dvx!QOIcvn%hNlJF^4O0XqQ9q)1y;`*zhkwQUx>Zf`erRA}$$ayy*bJ5D|P zaUe{A?h61TWa`X|o|B%Wph+i(M&HQ*PxKrWv1@9S0T@oGO46@Yf`W6g5_GRdOVIoV zCD3U*;fY$pU?rr)l%V|$N=O2pImLz%E5R63g6_9ef=NDSvl3EcO3?q7N>DTiSPABs z5>kGH5|WKWy4(gbd#B7J1FWN2dVSgJM@dB%f*?cg^$`W`bDp?4PMGU=@b>_#36|mZ ztJE~l0Vbp;*({9J!D3p}%K1u8MYgi!di@S4RbcX|{#bKG`;YW`1AI`FC{z4U_(xPm=yfKfmJlkNRpG5(nB!anCz4lqaTi~*aNg6DJ2Bv8@QA= zl{1ji#z<>`P`t24{>rQdf|9%jif+Ml%4?>Pti<+_J!+Dv_NPQQ@Dcl?vE-I=rZ=_7 zM>$bNWBMaj)<`m1KD|PEP&vnOsf3JhX|d9n&?3_@gA`hv-IDf=Uuof^z2a50zc@=G zq60mOG>q9-p97`Q7Rf%E10D@>-0UTo&mN8}0#wMqqHnpM;!z)@J! z|C%U?`1&79(uN3|0IF3e=ZQ1xu4!XT$yUEHKKJB3$LYK-IS;=w3m%zg*k%4$N2{h8 z#;xgW3`(%^q7ij{OY%k0w5Hh6%#v3mt-zzIKh>U7*VuXqZ_z*;$PDuw{bL>oQ;=eB z3Lz*x9HSfivZNm*8YEl6z_H`oNC+VdNbNwjA!?xrROUg1NY;uNGFN=WJ?I{O5!jpr zfn?Dq8HdYozBoJ(MIggk2+%RAHOZ7QQ`EZvj#zoVtfN5coNXQZW5nmmCG;*L?m%Kg zQ4}vyJqXs-ML$BaC32&Mb8vJ5Vt-B|?7`Mn)?{|47a{ za%#zm$1THpW;~uJcuCz!Z34zkaHjy$h(A$2m4wRuuBglyC(a2&^z5R)Q~k&hfxd9yKp}H zPh=GHex^N`St({#F6>>-+;OQr1ZrdL<=WY&16dV8bA@28_*GHKrz%(aH`FtOIDuZ- zdU@-co@>)rr-LPpVoBo%w#HRWQT9=FsHF7T`K#x@>zmbuN-G&(c59$?^Q_@-N-9HO z)Yf0GU#t>(PmAjZ1EpNBkP`|ym_@l)QFG(u^^@~Mfr_Tt)KGc#jlI|R&YuXBZ=N;X zD{=;l8UjTP_i8qUsvB3FSTb$<- z%G#Eymh$d42(DAhjy^EHD{4cP>q3?5k*q3zHVwvBN?g}Qua3@d4-_{C9nFHHnK9Cv zh$AkP?*3~>8{*{TT{(IA~P3YK77R%Z`Jg zVkgySVdyW*?hgD#rC9e=p!i78LI25g8&Z6}x?NT5eAta*vW4u8%jQiXTlS@%Uwdv~ z-uzD8o+kC5sk;rjw^QKz7rH&wnxCq>Q&aw>7QX+z8sFZjZ0Ro2+;*zF(^GD{bmXs4 zr?ro0GimwiI5Gm2ppV_TqXdLxws>K!CWVYuM71WNZ%*eQ_Hf3;wmmU76PFsr zkBFulFRTChDMeC0mPFAB>bp%TH7$zaM2R)=ef)%an3CndncviK>9;auoF~Fng|Q6A zRgE*6!FEYYF9WQ;(vGukCX3E?A4&9BB6max5)cOdSE#)&zlT}kiG;IRacN+63TZ4 z^XtWY(2B+U<{1McE9_i6w>T{1?_IX`+{<iLPxy z*ACIOV{!PdJwUQDpXyaPRiHuGi^+;<$+pF+#XOzg7Q7atzJD07yU`{%iTP@~Rhf2y2Pt ztY7n*E*YSO`L4p`A$|?$vQYr2O9X)988NnB^@*X0;5iDKliDdt0o|8v9st0~p)v{3 zELVVL21b=VIBax+#i_9CVMa^=pt029*p2B4wJ{~jfxtPJcFUXqG+`8)0ZqK4#|{QG z(<4B$xP#^efx9>`CSm_Fz%1;E0!%iKfRqC)MX0>cbBIk0u*u>?V;qyg;BL$*Ux5M! z6Q^X0vztLe{wac3N8rGA{892Vv^xp_89XGA#UG>4cvKwLD;0>xAE)prm?KgMfw>5j zvnJNSx8+O0xx{4U1nK@S4+Zpurwng9WFx<8q=u3gAqgs2rd|OAE_D+!+{yW8b#JN+ZCx5jTexGaog+amv z*VDcdIr-40{Ui*r9)RJu0=m+YfiB1!MFGzvcJyPyU_w^16d$qcqks-vPe~WV;zCy} zTH~7K$Ki+ z^gaifGLg)fno^Gx4z`R4`vab8CL$FVvH8!X(=XOC=9@Nr$(9*mogyt-8`a@TzhN??bE3-l(q~n*pJfVPnCN*0gG0o zJd%~fS(4=tdrfTwgHFAe$~FVM6E=g)$YxNW(1IfL3k8OQL}Te7)BjG8XMhoV@xj`| z)bF4Z$JDd1Sq!2b$C%6~pGJUp3BUPw$hkw#d*pC%eEG^Akg&AnEuh9C0e%MQ89V)N zDe=D}=N>t~BIobO`S;}9Cuhwa!5rNY!l|sX6915H6=Ad?_LD?eRQ!LVoc=pGpTh~K zNlHLWp^5){`W)R&NQQ-WlgJaMXzKs=&<#*d&S|@|ohwnn{QFw!L_ENVHW7wv9lZ=bIq{50Q6}OCaL@UH=ROy^Bcf zc5drJ*G%uSy=A4iJXqWy7B|eF53b)WuHPM4-ySIL2o~=Xi}wYJ56tWdH6Oe?boZ37 z<>{G&bCsgKW*+RtjnLDWJtNvG1Y2XMq-MSngd_;cgGy9;z4rU{GyB27ymIjJ!MVBx z_sqd%d+SO`MX+SOSh9X0Ex4gm+|U`=&>bk*6D;W!OL_w(hf%Yp19wl}-6w2T%0Xd` z_hrO5w2y4$Y) z-7ZyEis~17^8X^m)SapQg{8UMtbH#<4gY%vJ-)wZ){{R|3%{=-7C*=Muajh%)-ioF z&-#~`6ww+~kPVn0(LFB85lSw`kW4Itj9bh5gA8}3)vpnp+d)MPnuX}}-Mx6N(4kW_OyQbxsLCkBYq%uRscxde-2i||OT{F21>HBt`| zM9+wU=ynhgN+DeltZosjTY}X)#cEJO?y3XT`vRr=XDs(Bs)H3RVnxg1uDd71?MDL@ z#{_FBBtT};{!Qs-s0!s3K?s;G+g)f}saY>uDgAlnPb>etI#AOQtl1~l?7Q0>s5v^D z5pt9T9krsPR#*o`BBA4`P_U3Ie-=v;#W(u)BA1tdrPyKUx<6xBPKZHD$1{cVQ}KW=ZM zfZHwl4z20-E(5;YNilWWw0A7qIy1EI8rAT>YtkdmyBT`&+qCfe9I*$7*nvz=QT{Oo zB??PH^TqGPN5OMpDV#qY#qt3e)$;lSywf~MnmHNmSk7C949T!w!0Je?9;Gfn9=E6K z48FxKtH8wtN-m%uKlVhx?4twn#p2pYpx;gf`o+?T*WvW?gxq6@7>ey2g0vuFD5sVu zQhv?7{5R1n`~aL|40Oqq1~tBP`^t$j!9W-gS)$AWSB=;_P;?9$B;xBIQa(g-U?2>1 zhbr>N11p}RZ^=+iV$;f$)=G**K!-m|P83}v(8eH51X0XM1QZb{v;9lsi?LH%O8hMX zGAk%ZMjNw0i72lMmTwiyw+6~v1gqqU(vE1N^l7=KHbaRKTf= z!p>fy@(>_IER7Tp1T>2X*|j?4BDzCE4ifbGHFHwDeQ@c(3PEIMj?`&1$>pR=>;J=+k{@XTvd!gaB!$4n3 zOdZ+U+tsBVR_&csHT-wd^oVoEswaQ87Jgr$3_FM`8?mm>Y$}lUNgVv3or7_t{uVrc zNiv7=qcG};kG#SYWiFU}jzUd{ri#j-k>rgh|M5^oy0Qdohb|_@1fU1&TfxbI#U$MJ z7rk#T8NoA|oXCmoap5+ zP(#de4uFk{)XRu78bkKUt!0#*GWl!t4a!aocMOyrMQUcGG8#+v0mH75{n58vhFqtS zx@ef}<1BLMbV|%zvVO%8&*ZWqbRpeHy_|RSjoxaA{*ry1?M-D@7zAgP{>d|2jk(RF ze&!Onmg8D!H)3~xk+vv%KyldzY5Q+YCnD9(wtXj67Lr3C zSb1>mE)0fEO-@2jhuwMkbBuiqg^F+pUqzgyBdRty9g(b}6_21AI*GSSs0RN&Wp)>i zd_`c4yi-h7`2UllM6O^+8U>v|+z(%ca^y<356P#lngSl;CK2KH=+TL>OPDef=`n>< z+~=V}>0$PiqN+^HxRHe6{FrM0U&;9rR5qoliF}vH3GF?~hbBwd$`lN6D`%wtAqoY1 zK)ga>13wz(f!sVl!80_;Lpv73kjRk)pYqpkzHir;LX_6d;ExDda>HTLPpi6C;7Ud1 z*Pgxl?03HbLpn3MP@(hM-m81(o(U8-&X|6c>x|(L1G^zT^GC27pg@^+-j7V{tIIQOs==HnuP4cEo3NKsflEaNrw)t3P>Gj?A)^yy9#5 zSM%q3mh&1G%I*~uU)y?h>)g}_V4Ig#km-ndHwkWapbh1fH(IW@e7_a8M;ry$j8~0w zj)jU@BlNnW);hKZH+71eIs==!1EqU{rM+Tl@7;5O(x+z)_sKrb-n)5s(*zeCJXcCe zgC!fpk`1A%x=?-lQu9)cPShhbC-!r@4 zwJn{xQ!MO0EV!OZ=o}FDvdf6Lms$1&q{w0qx^3^4Ee+hMTyhBc`L|027G@%g}%RUFzwCPzHi;NH(PtxqK5yjRfm{& z?G$=9TTfr|we+RP)KjayTe+>LLi@`SHT=JH>JjIc6?*d5YT*Yvl{T2LQNB?soFYjV zN}`gw$qA3F_sI7WEIL8XNphYeXPBH(a!8$%DL8&Jvc{zqZjKdMe8iAJY+#5o*a?SZ z6;0VeQ?Y0&4w@=NQ$^5JBbqRS+5)Eb2byYA&I7F~wL`s1-iOE3s??lhu@47KPd(5S zno2145yXb~Ve2E2YeiG7PU0k!!LpE+1T%OC5lA9kJaRW78N-W&eMlSFH<1$SAuW4mhWr4hg`4_~z#!LHF@}0r_S}`A1!vgtDmy%moy43k0)Xki=>@7_h z)KhtIC-$||OBBz_2{|E=y)ko}J+MKcO$?BVEUiAL*4Y?^J&E zwjJy`8a8$x@9j9&bEr3L>}WgIwdc^0Q~cYM*S{p^19HAk&N4Yfj+ZEXj5o1~er_k{ z962wMGfmE3irfQ-_Au3Mwo3Wa^pTu?3fO><&t6r93W@}sBV;d~=^^fDz+V3HLD0Xl za$t8cR9H;^i(I(%Gdl&}vsAfxFCUVUJk+J=U7uKO`j$^>4SL5XSs-hxALN+z*lG$@ zDVZN@GWDt0O;R#f$+udmvRAx(AY?0tW2ty~FLOT8Wg=;1zTUCARpqFhxj-mqAh$}; z*&pckYV=3cpB&TaklM_0Ox0&Za)fWy!M>&GU6F6_t>)v~CmM^sBJvHs)l&9Nr{5C! z245t`9tJ+1KP) zkSzHhpCXCvp{TOD3^vz#M>!(sIR!iecxrmXDRd{{s0TVSdgg`S^DT69LN5WKS=!YOI zt~Bna3spVOqvu~%g|hQ5>0VC_?vSVbR`Ggt^qf8`ergqH(b$ zMlTNGa^>Yk=|Lncmg@?FX3MmWOY^$qFH!XiNK99H%jEA-qBD9i`c+X%#@7lZZU!4| zOH?VvuX_cClU@Pron}fGeho@42l+ahla-~(jHZdp5tcnLe{9iE%2CeETPc@}T$|%^ zwI^#?0?{*C?`&2|&KhYHa&BzdvaurcYHTg4<7(kpBS+;BgSOVmZQ$~vqxA~56RF4K za&YgvgfFSXmVHML^>(@k0rVJ!kKc|ge1!)JnxcWOD{U21hF=*dWJEtvV_^(jirzNLCG` zPy|c<7wCp^+#AeKO`@CzpO;0Iuqi1QC5hZqjNo1^)re(N<0MP>0~k{jAD5lnZ)33X zf{QsWU_K}v7~5&Pq)v;Dku)h-cd^N>^p(DtHF7QAI6Fu7?tKf+Ld)2QeQ(-JY3O0 z>)n?vF&GFaP#F7&sCH@o%b(A%sZ)~^Hq)GFh%9LS4}fLyual#286<0IGxUwI9|)-l zTO`gouAAVoy4)Q9zaTuUbAvG;T}-bVoZ<#>I{2+ERyED0EL;;kXP4|hi_Rd4?$J0{6v+S3BE$_@{!}hiKhccPKvoFNjB&>-=hdrPP0{l>`{_#ZVSU-@S~cnNTsRe$IA;xCgmRI03XH`Od4B)hL&E-N zS5>O#)O$5}mGscH0NMLAEBTekIIm3|YCA0KehL{MQTI^Bc|DYIK@Ku5EJDWFMPGo) zLL`4U*z013dK(?+mGC*oHTjQsP8>9BJ@3voJQ4kD|H}$z11{dy=u_r zZGNEDBST256s@M|k>RQVd42IPKVkwmiy6RPcWZFL_12kr+^^lVY;GoN)A<{g&5a>* z<`<}kDeH63am=PmZHDe9)jQVq{LVDhow^eIcsI?|y-EA7eM^@~`wNp1{$FI4bZyZ7 zVuP9dtsA==wC^?O;eT(F7V+O}GVQ6-zPGDvPpS6(Qa$`}^jYPAFp3!%Z>%krLYwTE zAS3qtQ^{$vGOEm}$tnl#3{@wSP>WJ-nZ!yG+OCq1mgSSLi7}g&?8qU9ZV`POh z(60$kOsF*>2tOfpj5SYBOuQ`VOROuG8xt$Zfn*5Bo5qlted!EB}`yQ&Lx)~Jto8>)67N* zQ2YyzBCf_>{+}VaWc=*_d^MVnR62aPj^3`(4y6XaKe;3I-|ZUu<%|Evwfyz}cGu7! zcl?vl|M_T8sAGiJA9r{CHX2s2)Z*;nZUjhkFk65Oq9kAj}e0Lc%FWV5knZQH;kXj)C1j!G}$~bi>vtK1#R# z{8Ln!|1LRf=h#H~(cU8O9D|g%MZrTO*ULyZ*Ypz$TV_)yY$4WYDjPU^sL*xo;MIfku0UZu zjB6%@ZF+NGu&_}qYz!1`TIdQCw#;r(;ikyV3kV+NF?ODvZTeQbtpxA`rBg2rIMcCmmiyC~Rs>A-!9 z{SDu1zM!R2v{Wuzpx|65v2X(=8)wWgGAEczK3lOkVA#UsV8-4y{kUUM{iE)MtwK)g zvU$gfEf;)M1b)s;c)rq^(rHq?Ych2LRA%LNHfY~%(8E7S{VP8NYS3A#2YwL680eEj zzO?;GQgq<7!^%R2fgM#+&O+?TjzBJZ0ALkXQBpt_S={_afKKJ~FQ<+w(Q!Hk7%hmE z41DQ1RlaIG<8h(W`tdA;Cjn|qKq}>TwM0#T%0;$ZI%qNa_@$1PC!w`OK^d7BemO11 z$dn><^olwrUz0y2Su5hpiUF}n#N}iV#td$#l2LK|irq_Qh&Y8>H=>Bn$!(1G@Q4EF z9u0#~RkT-9{l+KP9&sKwnrUn_vki z*BqO)D39_>LzCJkKoe>H5xSOa{zc~n)D`?`K0wrI^LPW0vPQX`BO7I3^ewkI3J;!; zd%8be&fTkIlgub#a)ANBWqTZ4Hv7}g;A#g3&*B6=6Gc5Qs1(Hmg*ps6V$F2MZ&;7) zxO!wTs%OT7)PqAO7JVKEmHW_$-uJfY=hA2uP;XS_G!wJ_ZMlP_FkK_@>1^HS_LdK*E9&eD3b|GIba3H$%l7l5U z&(x?_zQK|IAwrMw|D59FNO)3W6wnOi8(5|5Nb;H35AOxO6qf9vc`l3(^4AD|nx_(v zl5>+Bnl{p%ia#PBJCfg&@vN|6e4>AdhmsdN`x`vNLt_)e93P^ni{$)o(=fQty9Yt^ z^yMZ}4k7{Fgy#h@Ho)^VJ5BZ#vmots>2IW6Pn&O_?+~3E?pFL&-7o8e6DNfer-Xfd zm(%Y_H)Y-2Jbz?`x`g%JLe3ss>~udf=StD#qCbKSpWD;S(EXDG zKRmFoQ^;>$wszdl%fD81wJ2oI59L?Qt4Y4);G#>cZx!-(Kn@0$=pLH%`B^h5u>6ym zFBEQEX!=R(4_g-+BHGh=CD)3t76ntc(Lh-NhLze4%Wxs6xr=1b@K zn^kD)Ov*i5;p_{4up?AlK6e3Vcd)2gENTvb#?pOn%@(AtK<(N6gX1@zz5eVnG^|10 zxp4WycV3zs_#n3`gh8}iw=Adw&W*GBP+9eSi&(bh@_~D0TYu0JENc?WngV587qbIp zyMkpsVp-2!SD@^`%z;qZ`h{AttaawVeQRORS|M61f>yfcQP{A1Y3to*g!WU**1rFz zy=xDS;yCj&`_S%cSFhDdtJg}4B#;(8&;#=j0xS?9fe=Q*vBTNMf)yYEk}@kE+DI5X zxylmf4ym|0$yG^3zL!Og#B#+|##gx*>aYz7HnZOK?RpVsk?_Y|>K+0s=h(hK?)UZV z%&f#B#qs68+k)0iPj^pGPfvG$U;n<}H=R{H-YwdzKi$QbB-PyG5R4WK7kn|>COOv9 znrwID^;5gJVRz8d7PhAQwn1TnWS@IT^Y+4KSBnXXk0#@e_1a(7nsD;V_4<|s!wroV z$2SsKXfm~|)84Qax2)3MSf$7DoMk>O`Pnj`28qQOZslnT783e+6N}0(5i^k~3wqos z`jg9%msrHxx=4A7h!9Up@&}Zs=oJ2s%2Tu;zjt|Nxi!?k@^iFv667Z#V!#&6VQk8P z%hLTwBthDFV8S!NAA2pqgTp)-K3wH_xpcL3^fS& z3TTOjA5gDfy1f8;R4nD-fsO}EW8pH=rF+GODhhU3Bh)XFUlEqo64d-chC0H=W28}2 z_CSY9_HyuU2G1prKXTTpr=!7^DGXJju25ekz~62eXRX z$i#b;0MAEzb=*(_=9+|IUg#T4Xaw){)7^Lq!yi;9gVGO-i{y{<4j0K0uNA`FcCZ>KSGO}7rAS^=Qjw!=ERsJ?zZ+ES zHW#h6l9JUr3HK zF*VGRp9jxO>p%93w~yscb7#AA=FL0mljhF9&myJq?mV|+P~*;5?MxM)hWeB9;Z{vU zF_0v8feL^cw8{0Kr(9e3S$Mb8z2sq~&@;LVFBZwYmf4OL57|!JV4KPQs!9n)<+#+l zhB4ndyP`0WSd;=;X)0UmQS?cNdw`G%$32}}BUT1WfJ(#N*$1LqEanD2KS4N;5@jO< zfSeK;krQk5$;iQCVG##Ap%oE26%Yfn7y_uJtKYeQz$qN>>4EN~&e_-12T%i4D7X}R z6A;Rql&X@T-a&-ykJ8fUECmcHWn?QFPep~-rUIfWDrET^s*0YDKIjDcJzjGR48@zq z@>O~|4g#b@TR9$YTHgRnDZsHSUE1H(<4xNSO7{J|J)Qet*U?ki*MHdSqN9B>!1Tc3 zmHTiGbVzm0zS=b__tn%K>F=#R!jf9{!*S(7KsD%W&Z3KSrnUJ0$p!?{qHBJowU| zQHB1Tf^!t~B8cd_Ja}=ETgU;5(023;NEjy%Diq^iEJLEGVi+iTpolc+M8THK2pdFH zWQ$EYWahgp!n-3$?Bm(zIldqFfre5#h?u3n5;8tn8P=rpB>9{`o}P$NK8>gs7)D;n zCsiVn2tghgQ35wgT;}ZF5er4jV_)%`?CuUU90^n(4JnI^3ln4m(77?Eg#x^d@*^y|_`#+s(eD_`AqVcV-M7g{dmU&{)Vw~g(b+TL=r zV$1u~xL(^3|6rMOV|<_HgMsumzRw{GjxN zxZ5orMtEYZIb62ka-~@I*nN&GZ_|i-o&yxDQPW0-1O)TEe;f!oZs?zi0`sTq8QGr<|o?S;OUu%LU?Nd&NKL6!#t!5B7?E z17fdN^nO!x4F;V!U2kOf^?fes2}bsKNym!kQ}C%9j>|iy3Fnu>_?^1Dp1Qj}Ddn^K zhj{7*znjI5j3onjI=y|$m?qk4Uay_}#!pvGmWtMOL1X=t?E5pEogdD0gtJv%OZ!Gt zrZ5pOy7bIhlP=Z%TR3|sLDZygvDYZ8t>&x^NM-vi{Qu-PSZ_pKpW#&cd6ue6eAVbET&o5Q?mQ|I&qNPH}W~C;olrLDUn>%!{mr)G7R8#;%pe%qcP-voH zr4|qYvY7Ia6jpb-oZ_NQ#tyLh=|L-`2yo;u4e&I~1oVSyMZKb;K#U8&+d%`Up)?22z~G3Je!)0P-;Z)k0-NBl+S9Oh41aof_OTwUqwlOOs7o?)daR8 zNO6+ycd|!EfPL(yId#eTl6A?A5iJ!}3aee7+CRT!?TTY+H;USo$mbBF89BDvQtteC z_dmgTlIDocO3h%iRK95II{&cNU8LM4s=ZY_%!&N1eYLiet$>OEuY6dKl+!Q?udvno z=+5ZcS0UnNK>WG|#Bve=qGB}}YuFkUfgTPT`khQ15w2m;1&%}-O<+;k*^g2^LJuM( z-lj_Vw!r6;_(9SH;0=ecX%4#up_5F0Xk{L)mAAA3cRk!Gz*vOImd;)nIzU)}`3NMI zn9SblsFlpg4rl{lw!y@Ps3YE(Tu@8MIE1brqgs2sSqdk)e2?hO+5VC@U2;t!aV*J* z2?fZ%f~{0zTP;r+K`eqZ#3!-l}kgu_P|~x!vOvPsUU?#pAXhkNAUE_;>Yc+ z+N_#nbP|mbj6j$#4Y{i9_KHytMByyhBQxWWn4C=N@jjzMLu7euj%Xzn^?*f|qHJMH z?1x&6RC@EHwiA+3$h?Z8X6f=H?qmX$WMaXns8oj?@?$|!)Yl8i4%zIx4jw!T7{q}kP(O5Te`WUr=(x9oheaOHHz&TEXbss-aq}LJ~`8QLE{6(6cb$G0FY6Rp>swn zz^Y#9&1-^;ExsN0n({4;eIQsSuMVbQDX>a56up0@>!EK;NX2T1rEyN%gd9WdztB=2Fn3_8jRQ{ebG# zMa3|2ly>BWN2shsvZm|k2mjUqaH?&mWbaUXYVQmJOGK%v+$V{AhUiU%Pf^0ZV_xV}5%=@j8vlP$;QjN%THWM3Y4_^jm198 za1UEumrPhBdS(&Mt*!Mz#>9{PV^g{D6$(eYgW~y~TQ6oNAugz-2ppNvyeC9~zg>V1=Ao{Ml?dXDI^bRa6LQyuADJX3^RdG&aXH z%!;u6ut!d)i++WNT zppsvZU&h#G{vXdII`s<;+0C{5mAw2$7k8zOZ!FPWS+|K!u4X6W!d1BC!m-QLoUgrF zQ?jL8`*t>u21e?41IG5K1GS(4IRrM+IIMf@D;3ETS; zq~<61@EEHAcNCHYHt@Nl;1)1(Fk<23ZMDQl$EyY{5_yxn-ou8I0$vN?}W-o6(ZC1CnT#1ssyvn>4(Y0;c)F3d>>su%sn*N{Tj~alH$c+eZ5t|fg>G`mE z7Y0UF;SOc~7Ycqu!M{;JFJ5Bp`k0PhM-b8Wbe4lor-ESaP(V3<@1z{=)b zwlbh;*ky?%N^>?j8&K~A20gG{60Ml<4ZL?r{mQ1=h+g`GYzK}U0=r(18`xIa9zFtR z28W_+hKNOitHxR*S#VEv_Kn)L3jc*$u;3BsPCTn|c80s9OS`2@f*(eG@^pI9@TyRH zkw3j?e0!*PrN4OPWP6}^E%1`X^?~&DKI2q!`nm42-D5dpR>lz76v(a!Bv<;h(>D8P z(@4{Jg5S2xX9BH}PydnG7DjCINb~s}QHWj zKeyrXl0a^w4H7qd{Xs*qB77 zO8Vr_*@n+|8w(mMxvR^y6jqwH*tAzyXKhJ=gBLxH4=8E_yb5L5ErudW8FsbNVHXM^ z46llc3hARilULdjHd>Kk%;082vg^R69%E9E-IKJsOlC@o#0N4 zNliZ86xE*En^cn>pitU?(2Cb5&2%HTp7nnh(!}fk$`k`LxP|MbpHnYGkA2bfbDkO# zv96o;g6I9@-iFE7(7j~dMVw;NjPcmR(;`gUT^KK2_tFPr<Prsz^R1=Ne1Fh!Ti=kOm#A}jj=D}|YYesibje#Jb>of?0{0Jez6q$(+QY>1K+ zR5H)Sne466$^LJVOPb;Tj*eO>c!dHs)3aHhXS1?!gJOR|Gu&l5xjE*D%_ zE~ME}h*9+e$eXTTLTjw)Pn=?f7lJOpK*`w3h zMN!*~P`@ryXS>#|pn= zMZi%Va;ydIMZmEx= zOL*66A6+rBV%#;kG-zGp+dP%!_+iRRDHF+;+JjjQzUHtcR+xOubebpTJ~1?o8_1CB~1cb0~wH0GS~iY z)7oYocU@=No}#^;w0?V{_7{nI91DKxu{$DK*yKyyA6*C^E%m5&I%3}o`&6OI`w-^X-np!Ophv#ws@G-YPk3|dY_%nYY>qKo)*BnkkFvOFg0P?`w@ znld8kQRT3w5?ZfLI3NJwj$o$C_oztH;YG~NUwULlY z0jb^vTJ$J6AgBP2A*54WD)_DmUUB?r=f0zzCpwRkU7kcm)hQ@sh{8Oa%5o}`260IP z$wU#R2c$1va8bc|R4{YMR$D_#%VWpExhQlVklM74k|aR$i~BO1okVU*3QXZYDZr*P zCmg4M)RYXnA)KI7QtwH^1{*@w(^V%0G|PW(te2i;gW$lBf~1^>1d|yp&+{L11-H25 zTb$(2GZN1RLV?% zB1&(#2d4j4{!H^nDLc+INkQsPoZ1@B?7XA1h)LB!T@4si`JyxW5Lf8u@Vy4P<+BL} zzDUeiI?Lhjtj)$}`Hr7Eb@mhu8M7QtKds!F$lLC56n@%fI|R0!dmM!`ZF(*(<4n>t zpA_OV{d{JKcldcnh+pF8LDRD)z^}ch`5MpLVH%>Tn_-7DeHu=aJU4lvpD!G*3-Fcq zG+TJSj8dHtdViWoNJ35nS zk(7mrtAe`KztUAlbSv1u>Q6Nqef}3SX-M|Dhx+HwQ@5o;iMDCkwtVj0RKvDh?YlWV Uh57m>gZAB`oF8<^TWy diff --git a/__pycache__/canvas_node.cpython-313.pyc b/__pycache__/canvas_node.cpython-313.pyc deleted file mode 100644 index 9abddf1a6fa5396660f76862c5924dc8fb68c021..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62407 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 wGm?GorIGog^sQ#@(wdDWTb Date: Thu, 5 Feb 2026 16:23:10 +0100 Subject: [PATCH 14/14] Cleanup: Remove accidentally committed python\__pycache__folder --- python/__pycache__/__init__.cpython-312.pyc | Bin 248 -> 0 bytes python/__pycache__/__init__.cpython-313.pyc | Bin 297 -> 0 bytes python/__pycache__/config.cpython-312.pyc | Bin 210 -> 0 bytes python/__pycache__/config.cpython-313.pyc | Bin 259 -> 0 bytes python/__pycache__/logger.cpython-312.pyc | Bin 14272 -> 0 bytes python/__pycache__/logger.cpython-313.pyc | Bin 14611 -> 0 bytes 6 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 python/__pycache__/__init__.cpython-312.pyc delete mode 100644 python/__pycache__/__init__.cpython-313.pyc delete mode 100644 python/__pycache__/config.cpython-312.pyc delete mode 100644 python/__pycache__/config.cpython-313.pyc delete mode 100644 python/__pycache__/logger.cpython-312.pyc delete mode 100644 python/__pycache__/logger.cpython-313.pyc diff --git a/python/__pycache__/__init__.cpython-312.pyc b/python/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ae77ad4db04f94f76199a83e508df8727ac0c6d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmX@j%ge<81goQyGUI{tV-N=hn4pZ$VnD`ph7^Vr#vF!R#wbQch7_iB#weyrW=)ot zj6g|E##?MT`RVDYMMca&uAe6JEe0^_7JGbrVopwc{7Qz;Ak%(Dd0E92mn0_Tq^4x1 zrIi+E=I0p#L9tPcOJYf4Oh96Cc4B&Jag1|*Zdzrir&&yLX>mz@ZhT&T3Q#mBu`;zN zEx#x|HKw4lBqKjBCO$qhFS8^*Uaz3?7l%!5eoARhs$CH$&?=C-iur-W2WCb_#-|J- P54eRpv>Mrq*nwgI1A{{K diff --git a/python/__pycache__/__init__.cpython-313.pyc b/python/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 11a7cce4e3174fe8e172a0b1b7679d95f32ebbef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmey&%ge<81goQyGUI{tV-N=hn4pZ$VnD`JhG2$ZMsEf$#v(=qhF~Ur#v-P4W=)ot zj6g|E##?MT`RVDYMMca&uAe6JEe0^_7JGbrVopwc{7Qz;KnaFh-CkBP#U+VJIjJd` zX=$a!nfZB!Ku~NHgnei?(F00=oeE^X>4fhms$>! zh%e8~OUW-UjxWeB0viXDh)FIjF3Hc0&&y8%8k3V)nOc;VUzDB-G@&FTKQAUeJ~J<~ xBtBlRpz;=nO>TZlX-=wL5hu`XAdeLD1BnmJjEsy=8AKj%3wLNWvKO%f#Q;QDRLKAU diff --git a/python/__pycache__/config.cpython-312.pyc b/python/__pycache__/config.cpython-312.pyc deleted file mode 100644 index bda3fd4ff578fd17d6c0a722bb2c84177247fee7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210 zcmX@j%ge<81goQyGIN0RV-N=h7@>^M96-i&h7^V0TLgW85tQrurcroH*yxS0tEmzp*jix diff --git a/python/__pycache__/config.cpython-313.pyc b/python/__pycache__/config.cpython-313.pyc deleted file mode 100644 index d4550b307ba333759a88139cd3e8dec2e0d4e1eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 259 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 OPv<_)HPX3+ivI_z#f#_w diff --git a/python/__pycache__/logger.cpython-313.pyc b/python/__pycache__/logger.cpython-313.pyc deleted file mode 100644 index dcb1a720fff2aaf2fd6364f88e6935d461d2eda0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14611 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