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.
This commit is contained in:
diodiogod
2026-01-15 09:38:59 -03:00
parent be37966b45
commit 4e5ef18d93
6 changed files with 49 additions and 10 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -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');
});

View File

@@ -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");
}

View File

@@ -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();

View File

@@ -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');
});