mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-24 22:12:17 -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:
@@ -34,7 +34,6 @@ export class CanvasInteractions {
|
|||||||
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
this.canvas.canvas.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||||
this.canvas.canvas.addEventListener('keyup', this.handleKeyUp.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));
|
document.addEventListener('paste', this.handlePasteEvent.bind(this));
|
||||||
|
|
||||||
this.canvas.canvas.addEventListener('mouseenter', (e) => {
|
this.canvas.canvas.addEventListener('mouseenter', (e) => {
|
||||||
@@ -46,13 +45,11 @@ export class CanvasInteractions {
|
|||||||
this.handleMouseLeave(e);
|
this.handleMouseLeave(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add drag & drop support
|
|
||||||
this.canvas.canvas.addEventListener('dragover', this.handleDragOver.bind(this));
|
this.canvas.canvas.addEventListener('dragover', this.handleDragOver.bind(this));
|
||||||
this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this));
|
this.canvas.canvas.addEventListener('dragenter', this.handleDragEnter.bind(this));
|
||||||
this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this));
|
this.canvas.canvas.addEventListener('dragleave', this.handleDragLeave.bind(this));
|
||||||
this.canvas.canvas.addEventListener('drop', this.handleDrop.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));
|
this.canvas.canvas.addEventListener('contextmenu', this.handleContextMenu.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +95,6 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
this.interaction.lastClickTime = currentTime;
|
this.interaction.lastClickTime = currentTime;
|
||||||
|
|
||||||
// Check for right click to show blend mode menu on selected layers
|
|
||||||
if (e.button === 2) {
|
if (e.button === 2) {
|
||||||
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
const clickedLayerResult = this.canvas.canvasLayers.getLayerAtPosition(worldCoords.x, worldCoords.y);
|
||||||
if (clickedLayerResult && this.canvas.selectedLayers.includes(clickedLayerResult.layer)) {
|
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) {
|
if (e.shiftKey) {
|
||||||
this.startCanvasResize(worldCoords);
|
this.startCanvasResize(worldCoords);
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
@@ -230,7 +225,7 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu(e) {
|
handleContextMenu(e) {
|
||||||
// Prevent default context menu on canvas
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,8 +370,8 @@ export class CanvasInteractions {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key.toLowerCase() === 'v') {
|
if (e.key.toLowerCase() === 'v') {
|
||||||
// Don't prevent default - let the natural paste event fire
|
|
||||||
// which is handled by handlePasteEvent
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,7 +737,6 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag & Drop handlers
|
|
||||||
handleDragOver(e) {
|
handleDragOver(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
||||||
@@ -759,7 +753,7 @@ export class CanvasInteractions {
|
|||||||
handleDragLeave(e) {
|
handleDragLeave(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation(); // Prevent ComfyUI from handling this event
|
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)) {
|
if (!this.canvas.canvas.contains(e.relatedTarget)) {
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.canvas.canvas.style.backgroundColor = '';
|
||||||
this.canvas.canvas.style.border = '';
|
this.canvas.canvas.style.border = '';
|
||||||
@@ -772,7 +766,6 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
log.info("Canvas drag & drop event intercepted - preventing ComfyUI workflow loading");
|
||||||
|
|
||||||
// Reset visual feedback
|
|
||||||
this.canvas.canvas.style.backgroundColor = '';
|
this.canvas.canvas.style.backgroundColor = '';
|
||||||
this.canvas.canvas.style.border = '';
|
this.canvas.canvas.style.border = '';
|
||||||
|
|
||||||
@@ -800,11 +793,10 @@ export class CanvasInteractions {
|
|||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = async () => {
|
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 fitOnAddWidget = this.canvas.node.widgets.find(w => w.name === "fit_on_add");
|
||||||
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
||||||
|
|
||||||
// Use the same method as "Add Image" button for consistency
|
|
||||||
await this.canvas.addLayer(img, {}, addMode);
|
await this.canvas.addLayer(img, {}, addMode);
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
@@ -818,9 +810,8 @@ export class CanvasInteractions {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paste event handler that respects clipboard preference
|
|
||||||
async handlePasteEvent(e) {
|
async handlePasteEvent(e) {
|
||||||
// Check if we should handle this paste event
|
|
||||||
const shouldHandle = this.canvas.isMouseOver ||
|
const shouldHandle = this.canvas.isMouseOver ||
|
||||||
this.canvas.canvas.contains(document.activeElement) ||
|
this.canvas.canvas.contains(document.activeElement) ||
|
||||||
document.activeElement === this.canvas.canvas ||
|
document.activeElement === this.canvas.canvas ||
|
||||||
@@ -833,11 +824,10 @@ export class CanvasInteractions {
|
|||||||
|
|
||||||
log.info("Paste event detected, checking clipboard preference");
|
log.info("Paste event detected, checking clipboard preference");
|
||||||
|
|
||||||
// Check clipboard preference first
|
|
||||||
const preference = this.canvas.canvasLayers.clipboardPreference;
|
const preference = this.canvas.canvasLayers.clipboardPreference;
|
||||||
|
|
||||||
if (preference === 'clipspace') {
|
if (preference === 'clipspace') {
|
||||||
// For clipspace preference, always delegate to ClipboardManager
|
|
||||||
log.info("Clipboard preference is clipspace, delegating to ClipboardManager");
|
log.info("Clipboard preference is clipspace, delegating to ClipboardManager");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -845,7 +835,6 @@ export class CanvasInteractions {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For system preference, check direct image data first, then delegate
|
|
||||||
const clipboardData = e.clipboardData;
|
const clipboardData = e.clipboardData;
|
||||||
if (clipboardData && clipboardData.items) {
|
if (clipboardData && clipboardData.items) {
|
||||||
for (const item of clipboardData.items) {
|
for (const item of clipboardData.items) {
|
||||||
@@ -871,7 +860,6 @@ export class CanvasInteractions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no direct image data, delegate to ClipboardManager with system preference
|
|
||||||
await this.canvas.canvasLayers.clipboardManager.handlePaste('mouse', preference);
|
await this.canvas.canvasLayers.clipboardManager.handlePaste('mouse', preference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,38 +35,33 @@ export class CanvasLayers {
|
|||||||
async copySelectedLayers() {
|
async copySelectedLayers() {
|
||||||
if (this.canvas.selectedLayers.length === 0) return;
|
if (this.canvas.selectedLayers.length === 0) return;
|
||||||
|
|
||||||
// Always copy to internal clipboard first
|
|
||||||
this.internalClipboard = this.canvas.selectedLayers.map(layer => ({...layer}));
|
this.internalClipboard = this.canvas.selectedLayers.map(layer => ({...layer}));
|
||||||
log.info(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`);
|
log.info(`Copied ${this.internalClipboard.length} layer(s) to internal clipboard.`);
|
||||||
|
|
||||||
// Get flattened image
|
|
||||||
const blob = await this.getFlattenedSelectionAsBlob();
|
const blob = await this.getFlattenedSelectionAsBlob();
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
log.warn("Failed to create flattened selection blob");
|
log.warn("Failed to create flattened selection blob");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy to external clipboard based on preference
|
|
||||||
if (this.clipboardPreference === 'clipspace') {
|
if (this.clipboardPreference === 'clipspace') {
|
||||||
try {
|
try {
|
||||||
// Copy to ComfyUI Clipspace
|
|
||||||
const dataURL = await new Promise((resolve) => {
|
const dataURL = await new Promise((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => resolve(reader.result);
|
reader.onload = () => resolve(reader.result);
|
||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create temporary image for clipspace
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// Add to ComfyUI Clipspace
|
|
||||||
if (this.canvas.node.imgs) {
|
if (this.canvas.node.imgs) {
|
||||||
this.canvas.node.imgs = [img];
|
this.canvas.node.imgs = [img];
|
||||||
} else {
|
} else {
|
||||||
this.canvas.node.imgs = [img];
|
this.canvas.node.imgs = [img];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ComfyUI's clipspace functionality
|
|
||||||
if (ComfyApp.copyToClipspace) {
|
if (ComfyApp.copyToClipspace) {
|
||||||
ComfyApp.copyToClipspace(this.canvas.node);
|
ComfyApp.copyToClipspace(this.canvas.node);
|
||||||
log.info("Flattened selection copied to ComfyUI Clipspace.");
|
log.info("Flattened selection copied to ComfyUI Clipspace.");
|
||||||
@@ -78,7 +73,7 @@ export class CanvasLayers {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to copy image to ComfyUI Clipspace:", error);
|
log.error("Failed to copy image to ComfyUI Clipspace:", error);
|
||||||
// Fallback to system clipboard
|
|
||||||
try {
|
try {
|
||||||
const item = new ClipboardItem({'image/png': blob});
|
const item = new ClipboardItem({'image/png': blob});
|
||||||
await navigator.clipboard.write([item]);
|
await navigator.clipboard.write([item]);
|
||||||
@@ -88,7 +83,7 @@ export class CanvasLayers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Copy to system clipboard (default behavior)
|
|
||||||
try {
|
try {
|
||||||
const item = new ClipboardItem({'image/png': blob});
|
const item = new ClipboardItem({'image/png': blob});
|
||||||
await navigator.clipboard.write([item]);
|
await navigator.clipboard.write([item]);
|
||||||
@@ -140,7 +135,6 @@ export class CanvasLayers {
|
|||||||
try {
|
try {
|
||||||
log.info(`Paste operation started with preference: ${this.clipboardPreference}`);
|
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);
|
await this.clipboardManager.handlePaste(addMode, this.clipboardPreference);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -478,7 +472,6 @@ export class CanvasLayers {
|
|||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Create draggable title bar
|
|
||||||
const titleBar = document.createElement('div');
|
const titleBar = document.createElement('div');
|
||||||
titleBar.style.cssText = `
|
titleBar.style.cssText = `
|
||||||
background: #3a3a3a;
|
background: #3a3a3a;
|
||||||
@@ -493,7 +486,6 @@ export class CanvasLayers {
|
|||||||
`;
|
`;
|
||||||
titleBar.textContent = 'Blend Mode';
|
titleBar.textContent = 'Blend Mode';
|
||||||
|
|
||||||
// Create content area
|
|
||||||
const content = document.createElement('div');
|
const content = document.createElement('div');
|
||||||
content.style.cssText = `
|
content.style.cssText = `
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -502,7 +494,6 @@ export class CanvasLayers {
|
|||||||
menu.appendChild(titleBar);
|
menu.appendChild(titleBar);
|
||||||
menu.appendChild(content);
|
menu.appendChild(content);
|
||||||
|
|
||||||
// Add drag functionality
|
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let dragOffset = { x: 0, y: 0 };
|
let dragOffset = { x: 0, y: 0 };
|
||||||
|
|
||||||
@@ -511,7 +502,6 @@ export class CanvasLayers {
|
|||||||
const newX = e.clientX - dragOffset.x;
|
const newX = e.clientX - dragOffset.x;
|
||||||
const newY = e.clientY - dragOffset.y;
|
const newY = e.clientY - dragOffset.y;
|
||||||
|
|
||||||
// Keep menu within viewport bounds
|
|
||||||
const maxX = window.innerWidth - menu.offsetWidth;
|
const maxX = window.innerWidth - menu.offsetWidth;
|
||||||
const maxY = window.innerHeight - menu.offsetHeight;
|
const maxY = window.innerHeight - menu.offsetHeight;
|
||||||
|
|
||||||
@@ -530,13 +520,12 @@ export class CanvasLayers {
|
|||||||
|
|
||||||
titleBar.addEventListener('mousedown', (e) => {
|
titleBar.addEventListener('mousedown', (e) => {
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
// Calculate offset from mouse position to menu's top-left corner
|
|
||||||
dragOffset.x = e.clientX - parseInt(menu.style.left);
|
dragOffset.x = e.clientX - parseInt(menu.style.left);
|
||||||
dragOffset.y = e.clientY - parseInt(menu.style.top);
|
dragOffset.y = e.clientY - parseInt(menu.style.top);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Add global event listeners for dragging
|
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
document.addEventListener('mouseup', handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
textContent: "Paste Image",
|
textContent: "Paste Image",
|
||||||
title: "Paste image from clipboard",
|
title: "Paste image from clipboard",
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
// Use the direct handlePaste method from CanvasLayers
|
|
||||||
const fitOnAddWidget = node.widgets.find(w => w.name === "fit_on_add");
|
const fitOnAddWidget = node.widgets.find(w => w.name === "fit_on_add");
|
||||||
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
const addMode = fitOnAddWidget && fitOnAddWidget.value ? 'fit' : 'center';
|
||||||
canvas.canvasLayers.handlePaste(addMode);
|
canvas.canvasLayers.handlePaste(addMode);
|
||||||
@@ -1091,8 +1091,8 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
height: "100%"
|
height: "100%"
|
||||||
}
|
}
|
||||||
}, [controlPanel, canvasContainer]);
|
}, [controlPanel, canvasContainer]);
|
||||||
// Drag & drop is now handled by CanvasInteractions.js
|
|
||||||
// Removed duplicate handlers to prevent double loading
|
|
||||||
|
|
||||||
const mainWidget = node.addDOMWidget("mainContainer", "widget", mainContainer);
|
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 {
|
try {
|
||||||
log.info(`ClipboardManager handling paste with preference: ${preference}`);
|
log.info(`ClipboardManager handling paste with preference: ${preference}`);
|
||||||
|
|
||||||
// PRIORITY 1: Check internal clipboard first (copied layers)
|
|
||||||
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
||||||
log.info("Found layers in internal clipboard, pasting layers");
|
log.info("Found layers in internal clipboard, pasting layers");
|
||||||
this.canvas.canvasLayers.pasteLayers();
|
this.canvas.canvasLayers.pasteLayers();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIORITY 2: Check external clipboard based on preference
|
|
||||||
if (preference === 'clipspace') {
|
if (preference === 'clipspace') {
|
||||||
log.info("Attempting paste from ComfyUI Clipspace");
|
log.info("Attempting paste from ComfyUI Clipspace");
|
||||||
const success = await this.tryClipspacePaste(addMode);
|
const success = await this.tryClipspacePaste(addMode);
|
||||||
@@ -37,7 +35,6 @@ export class ClipboardManager {
|
|||||||
log.info("No image found in ComfyUI Clipspace");
|
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");
|
log.info("Attempting paste from system clipboard");
|
||||||
return await this.trySystemClipboardPaste(addMode);
|
return await this.trySystemClipboardPaste(addMode);
|
||||||
|
|
||||||
@@ -84,7 +81,6 @@ export class ClipboardManager {
|
|||||||
async trySystemClipboardPaste(addMode) {
|
async trySystemClipboardPaste(addMode) {
|
||||||
log.info("ClipboardManager: Checking system clipboard for images and paths");
|
log.info("ClipboardManager: Checking system clipboard for images and paths");
|
||||||
|
|
||||||
// First try modern clipboard API for both images and text
|
|
||||||
if (navigator.clipboard?.read) {
|
if (navigator.clipboard?.read) {
|
||||||
try {
|
try {
|
||||||
const clipboardItems = await navigator.clipboard.read();
|
const clipboardItems = await navigator.clipboard.read();
|
||||||
@@ -92,7 +88,6 @@ export class ClipboardManager {
|
|||||||
for (const item of clipboardItems) {
|
for (const item of clipboardItems) {
|
||||||
log.debug("Clipboard item types:", item.types);
|
log.debug("Clipboard item types:", item.types);
|
||||||
|
|
||||||
// Check for image data first
|
|
||||||
const imageType = item.types.find(type => type.startsWith('image/'));
|
const imageType = item.types.find(type => type.startsWith('image/'));
|
||||||
if (imageType) {
|
if (imageType) {
|
||||||
try {
|
try {
|
||||||
@@ -114,7 +109,6 @@ export class ClipboardManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for text types (file paths, URLs)
|
|
||||||
const textTypes = ['text/plain', 'text/uri-list'];
|
const textTypes = ['text/plain', 'text/uri-list'];
|
||||||
for (const textType of textTypes) {
|
for (const textType of textTypes) {
|
||||||
if (item.types.includes(textType)) {
|
if (item.types.includes(textType)) {
|
||||||
@@ -140,7 +134,6 @@ export class ClipboardManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to text-only API
|
|
||||||
if (navigator.clipboard?.readText) {
|
if (navigator.clipboard?.readText) {
|
||||||
try {
|
try {
|
||||||
const text = await navigator.clipboard.readText();
|
const text = await navigator.clipboard.readText();
|
||||||
@@ -173,17 +166,14 @@ export class ClipboardManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim whitespace
|
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
|
|
||||||
// Check if it's empty after trimming
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return false;
|
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://')) {
|
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 {
|
try {
|
||||||
new URL(text);
|
new URL(text);
|
||||||
log.debug("Detected valid 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 = [
|
const imageExtensions = [
|
||||||
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp',
|
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp',
|
||||||
'.svg', '.tiff', '.tif', '.ico', '.avif'
|
'.svg', '.tiff', '.tif', '.ico', '.avif'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if the text ends with a valid image extension (case insensitive)
|
|
||||||
const hasImageExtension = imageExtensions.some(ext =>
|
const hasImageExtension = imageExtensions.some(ext =>
|
||||||
text.toLowerCase().endsWith(ext)
|
text.toLowerCase().endsWith(ext)
|
||||||
);
|
);
|
||||||
@@ -210,8 +198,7 @@ export class ClipboardManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic path validation for local files - should look like a file path
|
|
||||||
// Accept both Windows and Unix style paths
|
|
||||||
const pathPatterns = [
|
const pathPatterns = [
|
||||||
/^[a-zA-Z]:[\\\/]/, // Windows absolute path (C:\... or C:/...)
|
/^[a-zA-Z]:[\\\/]/, // Windows absolute path (C:\... or C:/...)
|
||||||
/^[\\\/]/, // Unix absolute path (/...)
|
/^[\\\/]/, // Unix absolute path (/...)
|
||||||
@@ -238,7 +225,7 @@ export class ClipboardManager {
|
|||||||
* @returns {Promise<boolean>} - True if successful, false otherwise
|
* @returns {Promise<boolean>} - True if successful, false otherwise
|
||||||
*/
|
*/
|
||||||
async loadImageFromPath(filePath, addMode) {
|
async loadImageFromPath(filePath, addMode) {
|
||||||
// Method 1: Direct loading for URLs
|
|
||||||
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
|
if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
|
||||||
try {
|
try {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@@ -261,7 +248,6 @@ export class ClipboardManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 2: Load local files via backend endpoint
|
|
||||||
try {
|
try {
|
||||||
log.info("Attempting to load local file via backend");
|
log.info("Attempting to load local file via backend");
|
||||||
const success = await this.loadFileViaBackend(filePath, addMode);
|
const success = await this.loadFileViaBackend(filePath, addMode);
|
||||||
@@ -272,7 +258,6 @@ export class ClipboardManager {
|
|||||||
log.warn("Backend loading failed:", error);
|
log.warn("Backend loading failed:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 3: Fallback to file picker
|
|
||||||
try {
|
try {
|
||||||
log.info("Falling back to file picker");
|
log.info("Falling back to file picker");
|
||||||
const success = await this.promptUserForFile(filePath, addMode);
|
const success = await this.promptUserForFile(filePath, addMode);
|
||||||
@@ -283,7 +268,6 @@ export class ClipboardManager {
|
|||||||
log.warn("File picker failed:", error);
|
log.warn("File picker failed:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 4: Show user a helpful message
|
|
||||||
this.showFilePathMessage(filePath);
|
this.showFilePathMessage(filePath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -298,7 +282,6 @@ export class ClipboardManager {
|
|||||||
try {
|
try {
|
||||||
log.info("Loading file via ComfyUI backend:", filePath);
|
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", {
|
const response = await api.fetchApi("/ycnode/load_image_from_path", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -324,7 +307,6 @@ export class ClipboardManager {
|
|||||||
|
|
||||||
log.info("Successfully loaded image via ComfyUI backend:", filePath);
|
log.info("Successfully loaded image via ComfyUI backend:", filePath);
|
||||||
|
|
||||||
// Create image from the returned base64 data
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const success = await new Promise((resolve) => {
|
const success = await new Promise((resolve) => {
|
||||||
img.onload = async () => {
|
img.onload = async () => {
|
||||||
@@ -356,13 +338,12 @@ export class ClipboardManager {
|
|||||||
*/
|
*/
|
||||||
async promptUserForFile(originalPath, addMode) {
|
async promptUserForFile(originalPath, addMode) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Create a temporary file input
|
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
fileInput.type = 'file';
|
||||||
fileInput.accept = 'image/*';
|
fileInput.accept = 'image/*';
|
||||||
fileInput.style.display = 'none';
|
fileInput.style.display = 'none';
|
||||||
|
|
||||||
// Extract filename from path for user reference
|
|
||||||
const fileName = originalPath.split(/[\\\/]/).pop();
|
const fileName = originalPath.split(/[\\\/]/).pop();
|
||||||
|
|
||||||
fileInput.onchange = async (event) => {
|
fileInput.onchange = async (event) => {
|
||||||
@@ -397,7 +378,6 @@ export class ClipboardManager {
|
|||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
|
||||||
document.body.removeChild(fileInput);
|
document.body.removeChild(fileInput);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -407,10 +387,8 @@ export class ClipboardManager {
|
|||||||
resolve(false);
|
resolve(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show a brief notification to the user
|
|
||||||
this.showNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
|
this.showNotification(`Detected image path: ${fileName}. Please select the file to load it.`, 3000);
|
||||||
|
|
||||||
// Add to DOM and trigger click
|
|
||||||
document.body.appendChild(fileInput);
|
document.body.appendChild(fileInput);
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
});
|
});
|
||||||
@@ -434,7 +412,6 @@ export class ClipboardManager {
|
|||||||
showEmptyClipboardMessage(addMode) {
|
showEmptyClipboardMessage(addMode) {
|
||||||
const message = `Copied a file? Browser can't access file paths for security. Click here to select the file manually.`;
|
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');
|
const notification = document.createElement('div');
|
||||||
notification.style.cssText = `
|
notification.style.cssText = `
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -464,7 +441,6 @@ export class ClipboardManager {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add hover effect
|
|
||||||
notification.onmouseenter = () => {
|
notification.onmouseenter = () => {
|
||||||
notification.style.backgroundColor = '#3d6bb0';
|
notification.style.backgroundColor = '#3d6bb0';
|
||||||
notification.style.borderColor = '#5a8bd8';
|
notification.style.borderColor = '#5a8bd8';
|
||||||
@@ -476,7 +452,6 @@ export class ClipboardManager {
|
|||||||
notification.style.transform = 'translateY(0)';
|
notification.style.transform = 'translateY(0)';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add click handler to open file picker
|
|
||||||
notification.onclick = async () => {
|
notification.onclick = async () => {
|
||||||
document.body.removeChild(notification);
|
document.body.removeChild(notification);
|
||||||
try {
|
try {
|
||||||
@@ -489,10 +464,8 @@ export class ClipboardManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add to DOM
|
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
// Auto-remove after longer duration
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (notification.parentNode) {
|
if (notification.parentNode) {
|
||||||
notification.parentNode.removeChild(notification);
|
notification.parentNode.removeChild(notification);
|
||||||
@@ -508,7 +481,7 @@ export class ClipboardManager {
|
|||||||
* @param {number} duration - Duration in milliseconds
|
* @param {number} duration - Duration in milliseconds
|
||||||
*/
|
*/
|
||||||
showNotification(message, duration = 3000) {
|
showNotification(message, duration = 3000) {
|
||||||
// Create notification element
|
|
||||||
const notification = document.createElement('div');
|
const notification = document.createElement('div');
|
||||||
notification.style.cssText = `
|
notification.style.cssText = `
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -526,10 +499,8 @@ export class ClipboardManager {
|
|||||||
`;
|
`;
|
||||||
notification.textContent = message;
|
notification.textContent = message;
|
||||||
|
|
||||||
// Add to DOM
|
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
// Remove after duration
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (notification.parentNode) {
|
if (notification.parentNode) {
|
||||||
notification.parentNode.removeChild(notification);
|
notification.parentNode.removeChild(notification);
|
||||||
|
|||||||
Reference in New Issue
Block a user