project migration to typescript

Project migration to typescript
This commit is contained in:
Dariusz L
2025-07-04 04:22:51 +02:00
parent 3e4cdf10bc
commit 5adc77471f
60 changed files with 12565 additions and 3021 deletions

View File

@@ -1,33 +1,29 @@
import {app, ComfyApp} from "../../scripts/app.js";
import {api} from "../../scripts/api.js";
import {removeImage} from "./db.js";
import {MaskTool} from "./MaskTool.js";
import {CanvasState} from "./CanvasState.js";
import {CanvasInteractions} from "./CanvasInteractions.js";
import {CanvasLayers} from "./CanvasLayers.js";
import {CanvasLayersPanel} from "./CanvasLayersPanel.js";
import {CanvasRenderer} from "./CanvasRenderer.js";
import {CanvasIO} from "./CanvasIO.js";
import {ImageReferenceManager} from "./ImageReferenceManager.js";
import {BatchPreviewManager} from "./BatchPreviewManager.js";
import {createModuleLogger} from "./utils/LoggerUtils.js";
// @ts-ignore
import { api } from "../../scripts/api.js";
import { MaskTool } from "./MaskTool.js";
import { CanvasState } from "./CanvasState.js";
import { CanvasInteractions } from "./CanvasInteractions.js";
import { CanvasLayers } from "./CanvasLayers.js";
import { CanvasLayersPanel } from "./CanvasLayersPanel.js";
import { CanvasRenderer } from "./CanvasRenderer.js";
import { CanvasIO } from "./CanvasIO.js";
import { ImageReferenceManager } from "./ImageReferenceManager.js";
import { BatchPreviewManager } from "./BatchPreviewManager.js";
import { createModuleLogger } from "./utils/LoggerUtils.js";
import { debounce } from "./utils/CommonUtils.js";
import {CanvasMask} from "./CanvasMask.js";
import {CanvasSelection} from "./CanvasSelection.js";
import { CanvasMask } from "./CanvasMask.js";
import { CanvasSelection } from "./CanvasSelection.js";
const useChainCallback = (original, next) => {
if (original === undefined || original === null) {
return next;
}
return function(...args) {
const originalReturn = original.apply(this, args);
const nextReturn = next.apply(this, args);
return nextReturn === undefined ? originalReturn : nextReturn;
};
if (original === undefined || original === null) {
return next;
}
return function (...args) {
const originalReturn = original.apply(this, args);
const nextReturn = next.apply(this, args);
return nextReturn === undefined ? originalReturn : nextReturn;
};
};
const log = createModuleLogger('Canvas');
/**
* Canvas - Fasada dla systemu rysowania
*
@@ -41,65 +37,72 @@ export class Canvas {
this.node = node;
this.widget = widget;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d', {willReadFrequently: true});
const ctx = this.canvas.getContext('2d', { willReadFrequently: true });
if (!ctx)
throw new Error("Could not create canvas context");
this.ctx = ctx;
this.width = 512;
this.height = 512;
this.layers = [];
this.onStateChange = callbacks.onStateChange || null;
this.lastMousePosition = {x: 0, y: 0};
this.onStateChange = callbacks.onStateChange;
this.onHistoryChange = callbacks.onHistoryChange;
this.lastMousePosition = { x: 0, y: 0 };
this.viewport = {
x: -(this.width / 4),
y: -(this.height / 4),
zoom: 0.8,
};
this.offscreenCanvas = document.createElement('canvas');
this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
alpha: false
});
this.dataInitialized = false;
this.pendingDataCheck = null;
this.imageCache = new Map();
this._initializeModules(callbacks);
this._setupCanvas();
this.requestSaveState = () => { };
this.maskTool = new MaskTool(this, { onStateChange: this.onStateChange });
this.canvasMask = new CanvasMask(this);
this.canvasState = new CanvasState(this);
this.canvasSelection = new CanvasSelection(this);
this.canvasInteractions = new CanvasInteractions(this);
this.canvasLayers = new CanvasLayers(this);
this.canvasLayersPanel = new CanvasLayersPanel(this);
this.canvasRenderer = new CanvasRenderer(this);
this.canvasIO = new CanvasIO(this);
this.imageReferenceManager = new ImageReferenceManager(this);
this.batchPreviewManagers = [];
this.pendingBatchContext = null;
this.interaction = this.canvasInteractions.interaction;
this.previewVisible = false;
this.isMouseOver = false;
this._initializeModules();
this._setupCanvas();
log.debug('Canvas widget element:', this.node);
log.info('Canvas initialized', {
nodeId: this.node.id,
dimensions: {width: this.width, height: this.height},
dimensions: { width: this.width, height: this.height },
viewport: this.viewport
});
this.setPreviewVisibility(false);
}
async waitForWidget(name, node, interval = 100, timeout = 20000) {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const check = () => {
const widget = node.widgets.find(w => w.name === name);
const widget = node.widgets.find((w) => w.name === name);
if (widget) {
resolve(widget);
} else if (Date.now() - startTime > timeout) {
}
else if (Date.now() - startTime > timeout) {
reject(new Error(`Widget "${name}" not found within timeout.`));
} else {
}
else {
setTimeout(check, interval);
}
};
check();
});
}
/**
* Kontroluje widoczność podglądu canvas
* @param {boolean} visible - Czy podgląd ma być widoczny
@@ -107,11 +110,9 @@ export class Canvas {
async setPreviewVisibility(visible) {
this.previewVisible = visible;
log.info("Canvas preview visibility set to:", visible);
const imagePreviewWidget = await this.waitForWidget("$$canvas-image-preview", this.node);
if (imagePreviewWidget) {
log.debug("Found $$canvas-image-preview widget, controlling visibility");
if (visible) {
if (imagePreviewWidget.options) {
imagePreviewWidget.options.hidden = false;
@@ -125,7 +126,8 @@ export class Canvas {
imagePreviewWidget.computeSize = function () {
return [0, 250]; // Szerokość 0 (auto), wysokość 250
};
} else {
}
else {
if (imagePreviewWidget.options) {
imagePreviewWidget.options.hidden = true;
}
@@ -135,44 +137,27 @@ export class Canvas {
if ('hidden' in imagePreviewWidget) {
imagePreviewWidget.hidden = true;
}
imagePreviewWidget.computeSize = function () {
return [0, 0]; // Szerokość 0, wysokość 0
};
}
this.render()
} else {
this.render();
}
else {
log.warn("$$canvas-image-preview widget not found in Canvas.js");
}
}
/**
* Inicjalizuje moduły systemu canvas
* @private
*/
_initializeModules(callbacks) {
_initializeModules() {
log.debug('Initializing Canvas modules...');
// Stwórz opóźnioną wersję funkcji zapisu stanu
this.requestSaveState = debounce(this.saveState.bind(this), 500);
this.requestSaveState = debounce(() => this.saveState(), 500);
this._addAutoRefreshToggle();
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
this.canvasMask = new CanvasMask(this);
this.canvasState = new CanvasState(this);
this.canvasSelection = new CanvasSelection(this);
this.canvasInteractions = new CanvasInteractions(this);
this.canvasLayers = new CanvasLayers(this);
this.canvasLayersPanel = new CanvasLayersPanel(this);
this.canvasRenderer = new CanvasRenderer(this);
this.canvasIO = new CanvasIO(this);
this.imageReferenceManager = new ImageReferenceManager(this);
this.batchPreviewManagers = [];
this.pendingBatchContext = null;
log.debug('Canvas modules initialized successfully');
}
/**
* Konfiguruje podstawowe właściwości canvas
* @private
@@ -181,14 +166,11 @@ export class Canvas {
this.initCanvas();
this.canvasInteractions.setupEventListeners();
this.canvasIO.initNodeData();
this.layers = this.layers.map(layer => ({
this.layers = this.layers.map((layer) => ({
...layer,
opacity: 1
}));
}
/**
* Ładuje stan canvas z bazy danych
*/
@@ -201,24 +183,21 @@ export class Canvas {
}
this.saveState();
this.render();
// Dodaj to wywołanie, aby panel renderował się po załadowaniu stanu
if (this.canvasLayersPanel) {
this.canvasLayersPanel.onLayersChanged();
}
}
/**
* Zapisuje obecny stan
* @param {boolean} replaceLast - Czy zastąpić ostatni stan w historii
*/
saveState(replaceLast = false) {
log.debug('Saving canvas state', {replaceLast, layersCount: this.layers.length});
log.debug('Saving canvas state', { replaceLast, layersCount: this.layers.length });
this.canvasState.saveState(replaceLast);
this.incrementOperationCount();
this._notifyStateChange();
}
/**
* Cofnij ostatnią operację
*/
@@ -226,21 +205,16 @@ export class Canvas {
log.info('Performing undo operation');
const historyInfo = this.canvasState.getHistoryInfo();
log.debug('History state before undo:', historyInfo);
this.canvasState.undo();
this.incrementOperationCount();
this._notifyStateChange();
// Powiadom panel warstw o zmianie stanu warstw
if (this.canvasLayersPanel) {
this.canvasLayersPanel.onLayersChanged();
this.canvasLayersPanel.onSelectionChanged();
}
log.debug('Undo completed, layers count:', this.layers.length);
}
/**
* Ponów cofniętą operację
*/
@@ -248,27 +222,22 @@ export class Canvas {
log.info('Performing redo operation');
const historyInfo = this.canvasState.getHistoryInfo();
log.debug('History state before redo:', historyInfo);
this.canvasState.redo();
this.incrementOperationCount();
this._notifyStateChange();
// Powiadom panel warstw o zmianie stanu warstw
if (this.canvasLayersPanel) {
this.canvasLayersPanel.onLayersChanged();
this.canvasLayersPanel.onSelectionChanged();
}
log.debug('Redo completed, layers count:', this.layers.length);
}
/**
* Renderuje canvas
*/
render() {
this.canvasRenderer.render();
}
/**
* Dodaje warstwę z obrazem
* @param {Image} image - Obraz do dodania
@@ -277,49 +246,40 @@ export class Canvas {
*/
async addLayer(image, layerProps = {}, addMode = 'default') {
const result = await this.canvasLayers.addLayerWithImage(image, layerProps, addMode);
// Powiadom panel warstw o dodaniu nowej warstwy
if (this.canvasLayersPanel) {
this.canvasLayersPanel.onLayersChanged();
}
return result;
}
/**
* Usuwa wybrane warstwy
*/
removeLayersByIds(layerIds) {
if (!layerIds || layerIds.length === 0) return;
if (!layerIds || layerIds.length === 0)
return;
const initialCount = this.layers.length;
this.saveState();
this.layers = this.layers.filter(l => !layerIds.includes(l.id));
this.layers = this.layers.filter((l) => !layerIds.includes(l.id));
// If the current selection was part of the removal, clear it
const newSelection = this.canvasSelection.selectedLayers.filter(l => !layerIds.includes(l.id));
const newSelection = this.canvasSelection.selectedLayers.filter((l) => !layerIds.includes(l.id));
this.canvasSelection.updateSelection(newSelection);
this.render();
this.saveState();
if (this.canvasLayersPanel) {
this.canvasLayersPanel.onLayersChanged();
}
log.info(`Removed ${initialCount - this.layers.length} layers by ID.`);
}
removeSelectedLayers() {
return this.canvasSelection.removeSelectedLayers();
}
/**
* Duplikuje zaznaczone warstwy (w pamięci, bez zapisu stanu)
*/
duplicateSelectedLayers() {
return this.canvasSelection.duplicateSelectedLayers();
}
/**
* Aktualizuje zaznaczenie warstw i powiadamia wszystkie komponenty.
* To jest "jedyne źródło prawdy" o zmianie zaznaczenia.
@@ -328,14 +288,12 @@ export class Canvas {
updateSelection(newSelection) {
return this.canvasSelection.updateSelection(newSelection);
}
/**
* Logika aktualizacji zaznaczenia, wywoływana przez panel warstw.
*/
updateSelectionLogic(layer, isCtrlPressed, isShiftPressed, index) {
return this.canvasSelection.updateSelectionLogic(layer, isCtrlPressed, isShiftPressed, index);
}
/**
* Zmienia rozmiar obszaru wyjściowego
* @param {number} width - Nowa szerokość
@@ -345,32 +303,27 @@ export class Canvas {
updateOutputAreaSize(width, height, saveHistory = true) {
return this.canvasLayers.updateOutputAreaSize(width, height, saveHistory);
}
/**
* Eksportuje spłaszczony canvas jako blob
*/
async getFlattenedCanvasAsBlob() {
return this.canvasLayers.getFlattenedCanvasAsBlob();
}
/**
* Eksportuje spłaszczony canvas z maską jako kanałem alpha
*/
async getFlattenedCanvasWithMaskAsBlob() {
return this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
}
/**
* Importuje najnowszy obraz
*/
async importLatestImage() {
return this.canvasIO.importLatestImage();
}
_addAutoRefreshToggle() {
let autoRefreshEnabled = false;
let lastExecutionStartTime = 0;
const handleExecutionStart = () => {
if (autoRefreshEnabled) {
lastExecutionStartTime = Date.now();
@@ -393,62 +346,40 @@ export class Canvas {
this.render(); // Trigger render to show the pending outline immediately
}
};
const handleExecutionSuccess = async () => {
if (autoRefreshEnabled) {
log.info('Auto-refresh triggered, importing latest images.');
if (!this.pendingBatchContext) {
log.warn("execution_start did not fire, cannot process batch. Awaiting next execution.");
return;
}
// Use the captured output area for image import
const newLayers = await this.canvasIO.importLatestImages(
lastExecutionStartTime,
this.pendingBatchContext.outputArea
);
const newLayers = await this.canvasIO.importLatestImages(lastExecutionStartTime, this.pendingBatchContext.outputArea);
if (newLayers && newLayers.length > 1) {
const newManager = new BatchPreviewManager(
this,
this.pendingBatchContext.spawnPosition,
this.pendingBatchContext.outputArea
);
const newManager = new BatchPreviewManager(this, this.pendingBatchContext.spawnPosition, this.pendingBatchContext.outputArea);
this.batchPreviewManagers.push(newManager);
newManager.show(newLayers);
}
// Consume the context
this.pendingBatchContext = null;
// Final render to clear the outline if it was the last one
this.render();
}
};
this.node.addWidget(
'toggle',
'Auto-refresh after generation',
false,
(value) => {
autoRefreshEnabled = value;
log.debug('Auto-refresh toggled:', value);
}, {
serialize: false
}
);
this.node.addWidget('toggle', 'Auto-refresh after generation', false, (value) => {
autoRefreshEnabled = value;
log.debug('Auto-refresh toggled:', value);
}, {
serialize: false
});
api.addEventListener('execution_start', handleExecutionStart);
api.addEventListener('execution_success', handleExecutionSuccess);
this.node.onRemoved = useChainCallback(this.node.onRemoved, () => {
log.info('Node removed, cleaning up auto-refresh listeners.');
api.removeEventListener('execution_start', handleExecutionStart);
api.removeEventListener('execution_success', handleExecutionSuccess);
});
}
/**
* Uruchamia edytor masek
* @param {Image|HTMLCanvasElement|null} predefinedMask - Opcjonalna maska do nałożenia po otwarciu editora
@@ -457,8 +388,6 @@ export class Canvas {
async startMaskEditor(predefinedMask = null, sendCleanImage = true) {
return this.canvasMask.startMaskEditor(predefinedMask, sendCleanImage);
}
/**
* Inicjalizuje podstawowe właściwości canvas
*/
@@ -473,29 +402,24 @@ export class Canvas {
this.canvas.tabIndex = 0;
this.canvas.style.outline = 'none';
}
/**
* Pobiera współrzędne myszy w układzie świata
* @param {MouseEvent} e - Zdarzenie myszy
*/
getMouseWorldCoordinates(e) {
const rect = this.canvas.getBoundingClientRect();
const mouseX_DOM = e.clientX - rect.left;
const mouseY_DOM = e.clientY - rect.top;
if (!this.offscreenCanvas)
throw new Error("Offscreen canvas not initialized");
const scaleX = this.offscreenCanvas.width / rect.width;
const scaleY = this.offscreenCanvas.height / rect.height;
const mouseX_Buffer = mouseX_DOM * scaleX;
const mouseY_Buffer = mouseY_DOM * scaleY;
const worldX = (mouseX_Buffer / this.viewport.zoom) + this.viewport.x;
const worldY = (mouseY_Buffer / this.viewport.zoom) + this.viewport.y;
return {x: worldX, y: worldY};
return { x: worldX, y: worldY };
}
/**
* Pobiera współrzędne myszy w układzie widoku
* @param {MouseEvent} e - Zdarzenie myszy
@@ -504,23 +428,18 @@ export class Canvas {
const rect = this.canvas.getBoundingClientRect();
const mouseX_DOM = e.clientX - rect.left;
const mouseY_DOM = e.clientY - rect.top;
const scaleX = this.canvas.width / rect.width;
const scaleY = this.canvas.height / rect.height;
const mouseX_Canvas = mouseX_DOM * scaleX;
const mouseY_Canvas = mouseY_DOM * scaleY;
return {x: mouseX_Canvas, y: mouseY_Canvas};
return { x: mouseX_Canvas, y: mouseY_Canvas };
}
/**
* Aktualizuje zaznaczenie po operacji historii
*/
updateSelectionAfterHistory() {
return this.canvasSelection.updateSelectionAfterHistory();
}
/**
* Aktualizuje przyciski historii
*/
@@ -533,7 +452,6 @@ export class Canvas {
});
}
}
/**
* Zwiększa licznik operacji (dla garbage collection)
*/
@@ -542,7 +460,6 @@ export class Canvas {
this.imageReferenceManager.incrementOperationCount();
}
}
/**
* Czyści zasoby canvas
*/
@@ -552,7 +469,6 @@ export class Canvas {
}
log.info("Canvas destroyed");
}
/**
* Powiadamia o zmianie stanu
* @private