mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Remove comments and cleanup event handling code
Removed redundant and explanatory comments from CanvasInteractions.js, CanvasLayers.js, and ClipboardManager.js to improve code readability. Deleted the REFACTORING_GUIDE.md documentation file. Minor code cleanups were made to event handler logic and UI widget setup, with no changes to core functionality.
This commit is contained in:
@@ -33,8 +33,7 @@ export class CanvasInteractions {
|
||||
this.canvas.canvas.addEventListener('wheel', this.handleWheel.bind(this), {passive: false});
|
||||
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||
|
||||
// Add paste event listener like ComfyUI does
|
||||
|
||||
document.addEventListener('paste', this.handlePasteEvent.bind(this));
|
||||
|
||||
this.canvas.canvas.addEventListener('mouseenter', (e) => {
|
||||
@@ -46,13 +45,11 @@ export class CanvasInteractions {
|
||||
this.handleMouseLeave(e);
|
||||
});
|
||||
|
||||
// Add drag & drop support
|
||||
this.canvas.canvas.addEventListener('dragover', this.handleDragOver.bind(this));
|
||||
this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this));
|
||||
this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this));
|
||||
this.canvas.canvas.addEventListener('drop', this.handleDrop.bind(this));
|
||||
|
||||
// Prevent default context menu on canvas
|
||||
|
||||
this.canvas.canvas.addEventListener('contextmenu', this.handleContextMenu.bind(this));
|
||||
}
|
||||
|
||||
@@ -98,7 +95,6 @@ export class CanvasInteractions {
|
||||
}
|
||||
this.interaction.lastClickTime = currentTime;
|
||||
|
||||
// Check for right click to show blend mode menu on selected layers
|
||||
if (e.button === 2) {
|
||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
||||
if (clickedLayerResult && this.canvas.selectedLayers.includes(clickedLayerResult.layer)) {
|
||||
@@ -108,7 +104,6 @@ export class CanvasInteractions {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Shift key first to start output area drawing, ignoring layers
|
||||
if (e.shiftKey) {
|
||||
this.startCanvasResize(worldCoords);
|
||||
this.canvas.render();
|
||||
@@ -230,7 +225,7 @@ export class CanvasInteractions {
|
||||
}
|
||||
|
||||
handleContextMenu(e) {
|
||||
// Prevent default context menu on canvas
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
@@ -375,8 +370,8 @@ export class CanvasInteractions {
|
||||
return;
|
||||
}
|
||||
if (e.key.toLowerCase() === 'v') {
|
||||
// Don't prevent default - let the natural paste event fire
|
||||
// which is handled by handlePasteEvent
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -742,7 +737,6 @@ export class CanvasInteractions {
|
||||
}
|
||||
}
|
||||
|
||||
// Drag & Drop handlers
|
||||
handleDragOver(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
||||
@@ -759,7 +753,7 @@ export class CanvasInteractions {
|
||||
handleDragLeave(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
||||
// Only reset if we're actually leaving the canvas (not just moving between child elements)
|
||||
|
||||
if (!this.canvas.canvas.contains(e.relatedTarget)) {
|
||||
this.canvas.canvas.style.backgroundColor = '';
|
||||
this.canvas.canvas.style.border = '';
|
||||
@@ -771,8 +765,7 @@ export class CanvasInteractions {
|
||||
e.stopPropagation(); // CRITICAL: Prevent ComfyUI from handling this event and loading workflow
|
||||
|
||||
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
||||
|
||||
// Reset visual feedback
|
||||
|
||||
this.canvas.canvas.style.backgroundColor = '';
|
||||
this.canvas.canvas.style.border = '';
|
||||
|
||||
@@ -800,11 +793,10 @@ export class CanvasInteractions {
|
||||
reader.onload = async (e) => {
|
||||
const img = new Image();
|
||||
img.onload = async () => {
|
||||
// Check fit_on_add widget to determine add mode
|
||||
|
||||
const fitOnAddWidget = this.canvas.node.widgets.find(w => w.name === "fit_on_add");
|
||||
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
||||
|
||||
// Use the same method as "Add Image" button for consistency
|
||||
|
||||
await this.canvas.addLayer(img, {}, addMode);
|
||||
};
|
||||
img.onerror = () => {
|
||||
@@ -818,9 +810,8 @@ export class CanvasInteractions {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
// Paste event handler that respects clipboard preference
|
||||
async handlePasteEvent(e) {
|
||||
// Check if we should handle this paste event
|
||||
|
||||
const shouldHandle = this.canvas.isMouseOver ||
|
||||
this.canvas.canvas.contains(document.activeElement) ||
|
||||
document.activeElement === this.canvas.canvas ||
|
||||
@@ -832,20 +823,18 @@ export class CanvasInteractions {
|
||||
}
|
||||
|
||||
log.info("Paste event detected, checking clipboard preference");
|
||||
|
||||
// Check clipboard preference first
|
||||
|
||||
const preference = this.canvas.canvasLayers.clipboardPreference;
|
||||
|
||||
if (preference === 'clipspace') {
|
||||
// For clipspace preference, always delegate to ClipboardManager
|
||||
|
||||
log.info("Clipboard preference is clipspace, delegating to ClipboardManager");
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await this.canvas.canvasLayers.clipboardManager.handlePaste('mouse', preference);
|
||||
return;
|
||||
}
|
||||
|
||||
// For system preference, check direct image data first, then delegate
|
||||
|
||||
const clipboardData = e.clipboardData;
|
||||
if (clipboardData && clipboardData.items) {
|
||||
for (const item of clipboardData.items) {
|
||||
@@ -870,8 +859,7 @@ export class CanvasInteractions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no direct image data, delegate to ClipboardManager with system preference
|
||||
|
||||
await this.canvas.canvasLayers.clipboardManager.handlePaste('mouse', preference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,39 +34,34 @@ export class CanvasLayers {
|
||||
|
||||
async copySelectedLayers() {
|
||||
if (this.canvas.selectedLayers.length === 0) return;
|
||||
|
||||
// Always copy to internal clipboard first
|
||||
|
||||
this.internalClipboard = this.canvas.selectedLayers.map(layer => ({...layer}));
|
||||
log.info(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`);
|
||||
|
||||
// Get flattened image
|
||||
|
||||
const blob = await this.getFlattenedSelectionAsBlob();
|
||||
if (!blob) {
|
||||
log.warn("Failed to create flattened selection blob");
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy to external clipboard based on preference
|
||||
if (this.clipboardPreference === 'clipspace') {
|
||||
try {
|
||||
// Copy to ComfyUI Clipspace
|
||||
|
||||
const dataURL = await new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
// Create temporary image for clipspace
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
// Add to ComfyUI Clipspace
|
||||
|
||||
if (this.canvas.node.imgs) {
|
||||
this.canvas.node.imgs = [img];
|
||||
} else {
|
||||
this.canvas.node.imgs = [img];
|
||||
}
|
||||
|
||||
// Use ComfyUI's clipspace functionality
|
||||
|
||||
if (ComfyApp.copyToClipspace) {
|
||||
ComfyApp.copyToClipspace(this.canvas.node);
|
||||
log.info("Flattened selection copied to ComfyUI Clipspace.");
|
||||
@@ -78,7 +73,7 @@ export class CanvasLayers {
|
||||
|
||||
} catch (error) {
|
||||
log.error("Failed to copy image to ComfyUI Clipspace:", error);
|
||||
// Fallback to system clipboard
|
||||
|
||||
try {
|
||||
const item = new ClipboardItem({'image/png': blob});
|
||||
await navigator.clipboard.write([item]);
|
||||
@@ -88,7 +83,7 @@ export class CanvasLayers {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Copy to system clipboard (default behavior)
|
||||
|
||||
try {
|
||||
const item = new ClipboardItem({'image/png': blob});
|
||||
await navigator.clipboard.write([item]);
|
||||
@@ -140,7 +135,6 @@ export class CanvasLayers {
|
||||
try {
|
||||
log.info(`Paste operation started with preference: ${this.clipboardPreference}`);
|
||||
|
||||
// Delegate all clipboard handling to ClipboardManager (it will check internal clipboard first)
|
||||
await this.clipboardManager.handlePaste(addMode, this.clipboardPreference);
|
||||
|
||||
} catch (err) {
|
||||
@@ -478,7 +472,6 @@ export class CanvasLayers {
|
||||
min-width: 200px;
|
||||
`;
|
||||
|
||||
// Create draggable title bar
|
||||
const titleBar = document.createElement('div');
|
||||
titleBar.style.cssText = `
|
||||
background: #3a3a3a;
|
||||
@@ -493,7 +486,6 @@ export class CanvasLayers {
|
||||
`;
|
||||
titleBar.textContent = 'Blend Mode';
|
||||
|
||||
// Create content area
|
||||
const content = document.createElement('div');
|
||||
content.style.cssText = `
|
||||
padding: 5px;
|
||||
@@ -502,7 +494,6 @@ export class CanvasLayers {
|
||||
menu.appendChild(titleBar);
|
||||
menu.appendChild(content);
|
||||
|
||||
// Add drag functionality
|
||||
let isDragging = false;
|
||||
let dragOffset = { x: 0, y: 0 };
|
||||
|
||||
@@ -510,8 +501,7 @@ export class CanvasLayers {
|
||||
if (isDragging) {
|
||||
const newX = e.clientX - dragOffset.x;
|
||||
const newY = e.clientY - dragOffset.y;
|
||||
|
||||
// Keep menu within viewport bounds
|
||||
|
||||
const maxX = window.innerWidth - menu.offsetWidth;
|
||||
const maxY = window.innerHeight - menu.offsetHeight;
|
||||
|
||||
@@ -530,13 +520,12 @@ export class CanvasLayers {
|
||||
|
||||
titleBar.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
// Calculate offset from mouse position to menu's top-left corner
|
||||
|
||||
dragOffset.x = e.clientX - parseInt(menu.style.left);
|
||||
dragOffset.y = e.clientY - parseInt(menu.style.top);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Add global event listeners for dragging
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
});
|
||||
|
||||
@@ -582,7 +582,7 @@ async function createCanvasWidget(node, widget, app) {
|
||||
textContent: "Paste Image",
|
||||
title: "Paste image from clipboard",
|
||||
onclick: () => {
|
||||
// Use the direct handlePaste method from CanvasLayers
|
||||
|
||||
const fitOnAddWidget = node.widgets.find(w => w.name === "fit_on_add");
|
||||
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
||||
canvas.canvasLayers.handlePaste(addMode);
|
||||
@@ -1091,8 +1091,8 @@ async function createCanvasWidget(node, widget, app) {
|
||||
height: "100%"
|
||||
}
|
||||
}, [controlPanel, canvasContainer]);
|
||||
// Drag & drop is now handled by CanvasInteractions.js
|
||||
// Removed duplicate handlers to prevent double loading
|
||||
|
||||
|
||||
|
||||
const mainWidget = node.addDOMWidget("mainContainer", "widget", mainContainer);
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
# Przewodnik refaktoryzacji Canvas
|
||||
|
||||
## Podsumowanie wykonanych prac
|
||||
|
||||
Przeprowadzono kompleksową refaktoryzację klasy `Canvas` oraz powiązanych plików w celu poprawy architektury i zastosowania wzorca fasady.
|
||||
|
||||
## Zmiany w architekturze
|
||||
|
||||
### 1. Wzorzec Fasady w `Canvas.js`
|
||||
Klasa `Canvas` została przekształcona w prawdziwą fasadę:
|
||||
|
||||
#### Struktura przed refaktoryzacją:
|
||||
- ✗ Dziesiątki metod delegujących (`copySelectedLayers() { return this.canvasLayers.copySelectedLayers(); }`)
|
||||
- ✗ Canvas jako pośrednik dla wszystkich operacji
|
||||
- ✗ Trudność w utrzymaniu kodu
|
||||
|
||||
#### Struktura po refaktoryzacji:
|
||||
- ✅ **Główne operacje fasady**: `loadInitialState()`, `saveState()`, `render()`, `addLayer()`
|
||||
- ✅ **Publiczne moduły**: `canvas.canvasLayers`, `canvas.canvasInteractions`, `canvas.canvasIO`, `canvas.canvasState`
|
||||
- ✅ **Metody delegujące**: Zachowane dla kompatybilności, wyraźnie oznaczone jako tymczasowe
|
||||
|
||||
### 2. Nowa struktura `Canvas.js`
|
||||
```
|
||||
Canvas/
|
||||
├── Konstruktor i inicjalizacja
|
||||
│ ├── _initializeModules()
|
||||
│ └── _setupCanvas()
|
||||
├── Główne operacje fasady
|
||||
│ ├── loadInitialState()
|
||||
│ ├── saveState()
|
||||
│ ├── render()
|
||||
│ └── addLayer()
|
||||
├── Operacje na masce
|
||||
│ └── startMaskEditor()
|
||||
├── Metody pomocnicze
|
||||
│ ├── getMouseWorldCoordinates()
|
||||
│ ├── updateHistoryButtons()
|
||||
│ └── incrementOperationCount()
|
||||
└── Metody delegujące (tymczasowe)
|
||||
└── [zachowane dla kompatybilności]
|
||||
```
|
||||
|
||||
### 3. Aktualizacja `CanvasView.js`
|
||||
Główny interfejs użytkownika został zaktualizowany aby używać nowego podejścia:
|
||||
|
||||
#### Przykłady zmian:
|
||||
```javascript
|
||||
// PRZED
|
||||
onclick: () => canvas.mirrorHorizontal()
|
||||
|
||||
// PO
|
||||
onclick: () => canvas.canvasLayers.mirrorHorizontal()
|
||||
|
||||
// PRZED
|
||||
const imageData = await canvas.getLayerImageData(selectedLayer);
|
||||
|
||||
// PO
|
||||
const imageData = await canvas.canvasLayers.getLayerImageData(selectedLayer);
|
||||
```
|
||||
|
||||
## Mapowanie modułów
|
||||
|
||||
| Moduł | Odpowiedzialność | Przykładowe metody |
|
||||
|-------|------------------|-------------------|
|
||||
| `canvasLayers` | Operacje na warstwach | `copySelectedLayers()`, `moveLayerUp()`, `mirrorHorizontal()` |
|
||||
| `canvasInteractions` | Obsługa interakcji | `handleMouseMove()`, `handleKeyDown()` |
|
||||
| `canvasIO` | Operacje wejścia/wyjścia | `importLatestImage()`, `sendDataViaWebSocket()` |
|
||||
| `canvasState` | Zarządzanie stanem | `saveStateToDB()`, `undo()`, `redo()` |
|
||||
| `canvasRenderer` | Renderowanie | `render()` (wywoływane przez fasadę) |
|
||||
| `maskTool` | Narzędzie masek | `activate()`, `setBrushSize()` |
|
||||
| `imageReferenceManager` | Zarządzanie pamięcią | `manualGarbageCollection()` |
|
||||
|
||||
## Instrukcje migracji
|
||||
|
||||
### Stare podejście (przestarzałe)
|
||||
```javascript
|
||||
// Bezpośrednie wywołanie metod delegujących
|
||||
canvas.copySelectedLayers();
|
||||
canvas.handleMouseMove(e);
|
||||
canvas.getLayerImageData(layer);
|
||||
```
|
||||
|
||||
### Nowe podejście (zalecane)
|
||||
```javascript
|
||||
// Dostęp bezpośrednio do modułów
|
||||
canvas.canvasLayers.copySelectedLayers();
|
||||
canvas.canvasInteractions.handleMouseMove(e);
|
||||
canvas.canvasLayers.getLayerImageData(layer);
|
||||
|
||||
// Lub użycie głównych operacji fasady
|
||||
canvas.render();
|
||||
canvas.saveState();
|
||||
canvas.addLayer(image);
|
||||
```
|
||||
|
||||
### Zasady wyboru podejścia
|
||||
1. **Główne operacje** → Używaj fasady (`canvas.render()`, `canvas.saveState()`)
|
||||
2. **Operacje specjalistyczne** → Używaj modułów (`canvas.canvasLayers.mirrorHorizontal()`)
|
||||
3. **Częste operacje** → Metody delegujące zostały zachowane dla kompatybilności
|
||||
|
||||
## Korzyści refaktoryzacji
|
||||
|
||||
### Przed refaktoryzacją:
|
||||
- 🔴 80+ metod delegujących w klasie Canvas
|
||||
- 🔴 Każda nowa funkcja wymagała aktualizacji fasady
|
||||
- 🔴 Trudne debugowanie i śledzenie przepływu danych
|
||||
- 🔴 Naruszenie zasady Single Responsibility
|
||||
|
||||
### Po refaktoryzacji:
|
||||
- ✅ **Czysta fasada** z kluczowymi operacjami wysokiego poziomu
|
||||
- ✅ **Modułowa architektura** z jasnym podziałem odpowiedzialności
|
||||
- ✅ **Łatwiejsze utrzymanie** - zmiany w module nie wpływają na fasadę
|
||||
- ✅ **Większa elastyczność** - wybór między uproszczonym a szczegółowym interfejsem
|
||||
- ✅ **Kompatybilność wsteczna** - istniejący kod nadal działa
|
||||
|
||||
## Status refaktoryzacji
|
||||
|
||||
### ✅ Zakończone zadania
|
||||
|
||||
1. **Refaktoryzacja klasy Canvas** - przekształcenie w prawdziwą fasadę ✅
|
||||
2. **Aktualizacja CanvasView.js** - migracja do nowego podejścia ✅
|
||||
3. **Implementacja wzorca fasady** - główne operacje wysokiego poziomu ✅
|
||||
4. **Zachowanie kompatybilności** - metody delegujące dla istniejącego kodu ✅
|
||||
|
||||
### 📋 Zmiany w CanvasView.js
|
||||
|
||||
Wszystkie wywołania zostały zaktualizowane zgodnie z nowym podejściem:
|
||||
|
||||
```javascript
|
||||
// Operacje I/O
|
||||
canvas.canvasIO.importLatestImage()
|
||||
canvas.canvasLayers.handlePaste(addMode)
|
||||
|
||||
// Operacje na warstwach
|
||||
canvas.canvasLayers.moveLayerUp()
|
||||
canvas.canvasLayers.moveLayerDown()
|
||||
canvas.canvasLayers.mirrorHorizontal()
|
||||
canvas.canvasLayers.mirrorVertical()
|
||||
canvas.canvasLayers.getLayerImageData(selectedLayer)
|
||||
|
||||
// Garbage Collection
|
||||
canvas.imageReferenceManager.getStats()
|
||||
canvas.imageReferenceManager.manualGarbageCollection()
|
||||
```
|
||||
|
||||
### 🎯 Kolejne kroki
|
||||
|
||||
1. **Monitorowanie działania** - sprawdzenie czy wszystkie funkcje działają poprawnie ✅
|
||||
2. **Usunięcie metod delegujących do CanvasState** - zakończone ✅
|
||||
3. **Rozszerzenie dokumentacji** - dla poszczególnych modułów ✅
|
||||
4. **Dodanie testów jednostkowych** - dla modułów
|
||||
|
||||
### 🔧 Ostatnie poprawki (2025-06-29)
|
||||
|
||||
1. **Dodano brakujące metody w CanvasLayers.js** ✅
|
||||
- `resizeLayer(scale)` - zmienia rozmiar wybranych warstw
|
||||
- `rotateLayer(angle)` - obraca wybrane warstwy
|
||||
- Poprawiono delegację z Canvas.js do CanvasLayers.js
|
||||
|
||||
2. **Weryfikacja spójności** ✅
|
||||
- Wszystkie delegacje w Canvas.js wskazują na istniejące metody w modułach
|
||||
- CanvasView.js używa nowego podejścia modułowego
|
||||
- Dokumentacja została zaktualizowana
|
||||
|
||||
3. **Finalne poprawki architektury** ✅
|
||||
- Poprawiono konstruktor CanvasLayers.js - zmieniono mylącą nazwę parametru z `canvasLayers` na `canvas`
|
||||
- Zaktualizowano wszystkie odwołania `this.canvasLayers.` na `this.canvas.` w CanvasLayers.js
|
||||
- Poprawiono wywołania w CanvasView.js - `canvas.rotateLayer()` → `canvas.canvasLayers.rotateLayer()`
|
||||
- Wszystkie moduły używają teraz spójnej konwencji nazewnictwa
|
||||
|
||||
4. **Usunięcie metod delegujących do CanvasState** ✅
|
||||
- Usunięto metodę delegującą `saveStateToDB()` z Canvas.js
|
||||
- Zaktualizowano wszystkie wywołania w CanvasView.js: `canvas.undo()` → `canvas.canvasState.undo()`
|
||||
- Zaktualizowano wszystkie wywołania w CanvasInteractions.js dla operacji undo/redo i copy/paste
|
||||
- Zaktualizowano wywołania w CanvasLayers.js i CanvasIO.js
|
||||
- Wszystkie operacje na stanie używają teraz bezpośrednio modułu `canvasState`
|
||||
|
||||
5. **Usunięcie metod delegujących do CanvasLayers** ✅
|
||||
- Usunięto 14 metod delegujących do CanvasLayers z Canvas.js
|
||||
- Zaktualizowano wszystkie wywołania w CanvasRenderer.js, CanvasIO.js i CanvasInteractions.js
|
||||
- Wszystkie operacje na warstwach używają teraz bezpośrednio modułu `canvasLayers`
|
||||
- Canvas.js zawiera teraz tylko główne operacje fasady i niezbędne metody pomocnicze
|
||||
|
||||
6. **Usunięcie metod delegujących do CanvasInteractions** ✅
|
||||
- Usunięto ostatnią metodę delegującą `handleMouseMove()` z Canvas.js
|
||||
- Metoda nie była używana w żadnym pliku, więc usunięcie było bezpieczne
|
||||
- Wszystkie operacje interakcji używają teraz bezpośrednio modułu `canvasInteractions`
|
||||
- Canvas.js jest teraz prawdziwą fasadą bez niepotrzebnych metod delegujących
|
||||
|
||||
## Uwagi dla deweloperów
|
||||
|
||||
- ✅ **Refaktoryzacja w pełni zakończona** - wszystkie pliki zostały zaktualizowane
|
||||
- ✅ **Nowy kod** używa modułów bezpośrednio zgodnie z wzorcem fasady
|
||||
- ✅ **Wszystkie metody delegujące** do głównych modułów zostały usunięte
|
||||
- ✅ **Czysta fasada** - Canvas.js zawiera tylko główne operacje wysokiego poziomu
|
||||
- ✅ **Spójna architektura** - wszystkie moduły używają poprawnych referencji
|
||||
- ✅ **Minimalne delegacje** - pozostały tylko metody do ImageReferenceManager
|
||||
- 📚 **Dokumentacja** została zaktualizowana w tym przewodniku
|
||||
- 🔄 **Kompatybilność** z istniejącym kodem jest zachowana
|
||||
|
||||
**Refaktoryzacja została w pełni zakończona!** Canvas.js jest teraz prawdziwą fasadą bez niepotrzebnych metod delegujących. System jest gotowy do dalszego rozwoju z czystą architekturą opartą na wzorcu fasady.
|
||||
|
||||
### 📋 Mapowanie kompletnych funkcjonalności
|
||||
|
||||
| Funkcjonalność | Moduł | Metoda | Status |
|
||||
|----------------|-------|--------|--------|
|
||||
| Dodawanie warstw | `canvasLayers` | `addLayerWithImage()` | ✅ |
|
||||
| Kopiowanie/wklejanie | `canvasLayers` | `copySelectedLayers()`, `handlePaste()` | ✅ |
|
||||
| Przesuwanie warstw | `canvasLayers` | `moveLayerUp()`, `moveLayerDown()` | ✅ |
|
||||
| Transformacje | `canvasLayers` | `resizeLayer()`, `rotateLayer()` | ✅ |
|
||||
| Odbicia lustrzane | `canvasLayers` | `mirrorHorizontal()`, `mirrorVertical()` | ✅ |
|
||||
| Obsługa interakcji | `canvasInteractions` | `handleMouseMove()`, `handleKeyDown()` | ✅ |
|
||||
| Zarządzanie stanem | `canvasState` | `saveState()`, `undo()`, `redo()` | ✅ |
|
||||
| Operacje I/O | `canvasIO` | `importLatestImage()`, `sendDataViaWebSocket()` | ✅ |
|
||||
| Renderowanie | `canvasRenderer` | `render()` | ✅ |
|
||||
| Zarządzanie pamięcią | `imageReferenceManager` | `manualGarbageCollection()` | ✅ |
|
||||
@@ -20,14 +20,12 @@ export class ClipboardManager {
|
||||
try {
|
||||
log.info(`ClipboardManager handling paste with preference: ${preference}`);
|
||||
|
||||
// PRIORITY 1: Check internal clipboard first (copied layers)
|
||||
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
||||
log.info("Found layers in internal clipboard, pasting layers");
|
||||
this.canvas.canvasLayers.pasteLayers();
|
||||
return true;
|
||||
}
|
||||
|
||||
// PRIORITY 2: Check external clipboard based on preference
|
||||
if (preference === 'clipspace') {
|
||||
log.info("Attempting paste from ComfyUI Clipspace");
|
||||
const success = await this.tryClipspacePaste(addMode);
|
||||
@@ -37,7 +35,6 @@ export class ClipboardManager {
|
||||
log.info("No image found in ComfyUI Clipspace");
|
||||
}
|
||||
|
||||
// PRIORITY 3: Always try system clipboard (either as primary or fallback)
|
||||
log.info("Attempting paste from system clipboard");
|
||||
return await this.trySystemClipboardPaste(addMode);
|
||||
|
||||
@@ -83,16 +80,14 @@ export class ClipboardManager {
|
||||
*/
|
||||
async trySystemClipboardPaste(addMode) {
|
||||
log.info("ClipboardManager: Checking system clipboard for images and paths");
|
||||
|
||||
// First try modern clipboard API for both images and text
|
||||
|
||||
if (navigator.clipboard?.read) {
|
||||
try {
|
||||
const clipboardItems = await navigator.clipboard.read();
|
||||
|
||||
for (const item of clipboardItems) {
|
||||
log.debug("Clipboard item types:", item.types);
|
||||
|
||||
// Check for image data first
|
||||
|
||||
const imageType = item.types.find(type => type.startsWith('image/'));
|
||||
if (imageType) {
|
||||
try {
|
||||
@@ -113,8 +108,7 @@ export class ClipboardManager {
|
||||
log.debug("Error reading image data:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for text types (file paths, URLs)
|
||||
|
||||
const textTypes = ['text/plain', 'text/uri-list'];
|
||||
for (const textType of textTypes) {
|
||||
if (item.types.includes(textType)) {
|
||||
@@ -140,7 +134,6 @@ export class ClipboardManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to text-only API
|
||||
if (navigator.clipboard?.readText) {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
@@ -173,17 +166,14 @@ export class ClipboardManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
text = text.trim();
|
||||
|
||||
// Check if it's empty after trimming
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a URL first (URLs have priority and don't need file extensions)
|
||||
if (text.startsWith('http://') || text.startsWith('https://') || text.startsWith('file://')) {
|
||||
// For URLs, we're more permissive - any valid URL could potentially be an image
|
||||
|
||||
try {
|
||||
new URL(text);
|
||||
log.debug("Detected valid URL:", text);
|
||||
@@ -194,13 +184,11 @@ export class ClipboardManager {
|
||||
}
|
||||
}
|
||||
|
||||
// For local file paths, check for image extensions
|
||||
const imageExtensions = [
|
||||
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp',
|
||||
'.svg', '.tiff', '.tif', '.ico', '.avif'
|
||||
];
|
||||
|
||||
// Check if the text ends with a valid image extension (case insensitive)
|
||||
const hasImageExtension = imageExtensions.some(ext =>
|
||||
text.toLowerCase().endsWith(ext)
|
||||
);
|
||||
@@ -210,8 +198,7 @@ export class ClipboardManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Basic path validation for local files - should look like a file path
|
||||
// Accept both Windows and Unix style paths
|
||||
|
||||
const pathPatterns = [
|
||||
/^[a-zA-Z]:[\\\/]/, // Windows absolute path (C:\... or C:/...)
|
||||
/^[\\\/]/, // Unix absolute path (/...)
|
||||
@@ -238,7 +225,7 @@ export class ClipboardManager {
|
||||
* @returns {Promise<boolean>} - True if successful, false otherwise
|
||||
*/
|
||||
async loadImageFromPath(filePath, addMode) {
|
||||
// Method 1: Direct loading for URLs
|
||||
|
||||
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
|
||||
try {
|
||||
const img = new Image();
|
||||
@@ -261,7 +248,6 @@ export class ClipboardManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Load local files via backend endpoint
|
||||
try {
|
||||
log.info("Attempting to load local file via backend");
|
||||
const success = await this.loadFileViaBackend(filePath, addMode);
|
||||
@@ -272,7 +258,6 @@ export class ClipboardManager {
|
||||
log.warn("Backend loading failed:", error);
|
||||
}
|
||||
|
||||
// Method 3: Fallback to file picker
|
||||
try {
|
||||
log.info("Falling back to file picker");
|
||||
const success = await this.promptUserForFile(filePath, addMode);
|
||||
@@ -283,7 +268,6 @@ export class ClipboardManager {
|
||||
log.warn("File picker failed:", error);
|
||||
}
|
||||
|
||||
// Method 4: Show user a helpful message
|
||||
this.showFilePathMessage(filePath);
|
||||
return false;
|
||||
}
|
||||
@@ -297,8 +281,7 @@ export class ClipboardManager {
|
||||
async loadFileViaBackend(filePath, addMode) {
|
||||
try {
|
||||
log.info("Loading file via ComfyUI backend:", filePath);
|
||||
|
||||
// Use the backend endpoint to load image from path
|
||||
|
||||
const response = await api.fetchApi("/ycnode/load_image_from_path", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -323,8 +306,7 @@ export class ClipboardManager {
|
||||
}
|
||||
|
||||
log.info("Successfully loaded image via ComfyUI backend:", filePath);
|
||||
|
||||
// Create image from the returned base64 data
|
||||
|
||||
const img = new Image();
|
||||
const success = await new Promise((resolve) => {
|
||||
img.onload = async () => {
|
||||
@@ -356,13 +338,12 @@ export class ClipboardManager {
|
||||
*/
|
||||
async promptUserForFile(originalPath, addMode) {
|
||||
return new Promise((resolve) => {
|
||||
// Create a temporary file input
|
||||
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = 'image/*';
|
||||
fileInput.style.display = 'none';
|
||||
|
||||
// Extract filename from path for user reference
|
||||
const fileName = originalPath.split(/[\\\/]/).pop();
|
||||
|
||||
fileInput.onchange = async (event) => {
|
||||
@@ -396,8 +377,7 @@ export class ClipboardManager {
|
||||
log.warn("Selected file is not an image");
|
||||
resolve(false);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
|
||||
document.body.removeChild(fileInput);
|
||||
};
|
||||
|
||||
@@ -407,10 +387,8 @@ export class ClipboardManager {
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
// Show a brief notification to the user
|
||||
this.showNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
|
||||
|
||||
// Add to DOM and trigger click
|
||||
document.body.appendChild(fileInput);
|
||||
fileInput.click();
|
||||
});
|
||||
@@ -433,8 +411,7 @@ export class ClipboardManager {
|
||||
*/
|
||||
showEmptyClipboardMessage(addMode) {
|
||||
const message = `Copied a file? Browser can't access file paths for security. Click here to select the file manually.`;
|
||||
|
||||
// Create clickable notification
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
@@ -464,7 +441,6 @@ export class ClipboardManager {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add hover effect
|
||||
notification.onmouseenter = () => {
|
||||
notification.style.backgroundColor = '#3d6bb0';
|
||||
notification.style.borderColor = '#5a8bd8';
|
||||
@@ -476,7 +452,6 @@ export class ClipboardManager {
|
||||
notification.style.transform = 'translateY(0)';
|
||||
};
|
||||
|
||||
// Add click handler to open file picker
|
||||
notification.onclick = async () => {
|
||||
document.body.removeChild(notification);
|
||||
try {
|
||||
@@ -489,10 +464,8 @@ export class ClipboardManager {
|
||||
}
|
||||
};
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto-remove after longer duration
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
@@ -508,7 +481,7 @@ export class ClipboardManager {
|
||||
* @param {number} duration - Duration in milliseconds
|
||||
*/
|
||||
showNotification(message, duration = 3000) {
|
||||
// Create notification element
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
@@ -526,10 +499,8 @@ export class ClipboardManager {
|
||||
`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Add to DOM
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after duration
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
|
||||
Reference in New Issue
Block a user