From 01ffd863674e7737564eb1a39c643b4e1a6f1e53 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Tue, 16 Jul 2024 22:16:16 +1000
Subject: [PATCH] feat(ui): de-jank staging area and progress images

---
 .../addCommitStagingAreaImageListener.ts      |  4 +
 .../konva/CanvasControlAdapter.ts             |  7 +-
 .../controlLayers/konva/CanvasImage.ts        | 79 ++++++-------------
 .../controlLayers/konva/CanvasManager.ts      |  3 -
 .../konva/CanvasProgressPreview.ts            |  3 +-
 .../controlLayers/konva/CanvasStagingArea.ts  | 17 ++--
 6 files changed, 38 insertions(+), 75 deletions(-)

diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts
index f1501b9533..85fbac41ec 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener.ts
@@ -1,6 +1,7 @@
 import { logger } from 'app/logging/logger';
 import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
 import {
+  $lastProgressEvent,
   layerAddedFromStagingArea,
   sessionStagingAreaImageAccepted,
   sessionStagingAreaReset,
@@ -25,6 +26,9 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
         );
         const { canceled } = await req.unwrap();
         req.reset();
+
+        $lastProgressEvent.set(null);
+
         if (canceled > 0) {
           log.debug(`Canceled ${canceled} canvas batches`);
           toast({
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts
index a69f4f3283..8b4208a6a6 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasControlAdapter.ts
@@ -76,11 +76,8 @@ export class CanvasControlAdapter {
         didDraw = true;
       }
     } else if (!this.image) {
-      this.image = await new CanvasImage(imageObject, {
-        onLoad: () => {
-          this.updateGroup(true);
-        },
-      });
+      this.image = await new CanvasImage(imageObject);
+      this.updateGroup(true);
       this.objectsGroup.add(this.image.konvaImageGroup);
       await this.image.updateImageSource(imageObject.image.name);
     } else if (!this.image.isLoading && !this.image.isError) {
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts
index 9bc9f0778a..cb38a7136e 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasImage.ts
@@ -3,8 +3,7 @@ import { loadImage } from 'features/controlLayers/konva/util';
 import type { ImageObject } from 'features/controlLayers/store/types';
 import { t } from 'i18next';
 import Konva from 'konva';
-import { getImageDTO as defaultGetImageDTO } from 'services/api/endpoints/images';
-import type { ImageDTO } from 'services/api/types';
+import { getImageDTO } from 'services/api/endpoints/images';
 import { assert } from 'tsafe';
 
 export class CanvasImage {
@@ -17,23 +16,10 @@ export class CanvasImage {
   konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
   isLoading: boolean;
   isError: boolean;
-  getImageDTO: (imageName: string) => Promise<ImageDTO | null>;
-  onLoading: () => void;
-  onLoad: (imageName: string, imageEl: HTMLImageElement) => void;
-  onError: () => void;
   lastImageObject: ImageObject;
 
-  constructor(
-    imageObject: ImageObject,
-    options?: {
-      getImageDTO?: (imageName: string) => Promise<ImageDTO | null>;
-      onLoading?: () => void;
-      onLoad?: (konvaImage: Konva.Image) => void;
-      onError?: () => void;
-    }
-  ) {
-    const { getImageDTO, onLoading, onLoad, onError } = options ?? {};
-    const { id, width, height, x, y, filters } = imageObject;
+  constructor(imageObject: ImageObject) {
+    const { id, width, height, x, y } = imageObject;
     this.konvaImageGroup = new Konva.Group({ id, listening: false, x, y });
     this.konvaPlaceholderGroup = new Konva.Group({ listening: false });
     this.konvaPlaceholderRect = new Konva.Rect({
@@ -64,19 +50,23 @@ export class CanvasImage {
     this.konvaImage = null;
     this.isLoading = false;
     this.isError = false;
-    this.getImageDTO = getImageDTO ?? defaultGetImageDTO;
-    this.onLoading = function () {
+    this.lastImageObject = imageObject;
+  }
+
+  async updateImageSource(imageName: string) {
+    try {
       this.isLoading = true;
+      this.konvaImageGroup.visible(true);
+
       if (!this.konvaImage) {
         this.konvaPlaceholderGroup.visible(true);
         this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
       }
-      this.konvaImageGroup.visible(true);
-      if (onLoading) {
-        onLoading();
-      }
-    };
-    this.onLoad = function (imageName: string, imageEl: HTMLImageElement) {
+
+      const imageDTO = await getImageDTO(imageName);
+      assert(imageDTO !== null, 'imageDTO is null');
+      const imageEl = await loadImage(imageDTO.image_url);
+
       if (this.konvaImage) {
         this.konvaImage.setAttrs({
           image: imageEl,
@@ -86,52 +76,31 @@ export class CanvasImage {
           id: this.id,
           listening: false,
           image: imageEl,
-          width,
-          height,
+          width: this.lastImageObject.width,
+          height: this.lastImageObject.height,
         });
         this.konvaImageGroup.add(this.konvaImage);
       }
-      if (filters.length > 0) {
+
+      if (this.lastImageObject.filters.length > 0) {
         this.konvaImage.cache();
-        this.konvaImage.filters(filters.map((f) => FILTER_MAP[f]));
+        this.konvaImage.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
       } else {
         this.konvaImage.clearCache();
         this.konvaImage.filters([]);
       }
+
       this.imageName = imageName;
       this.isLoading = false;
       this.isError = false;
       this.konvaPlaceholderGroup.visible(false);
-      this.konvaImageGroup.visible(true);
-
-      if (onLoad) {
-        onLoad(this.konvaImage);
-      }
-    };
-    this.onError = function () {
+    } catch {
+      this.konvaImage?.visible(false);
       this.imageName = null;
       this.isLoading = false;
       this.isError = true;
-      this.konvaPlaceholderGroup.visible(true);
       this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
-      this.konvaImageGroup.visible(true);
-
-      if (onError) {
-        onError();
-      }
-    };
-    this.lastImageObject = imageObject;
-  }
-
-  async updateImageSource(imageName: string) {
-    try {
-      this.onLoading();
-      const imageDTO = await this.getImageDTO(imageName);
-      assert(imageDTO !== null, 'imageDTO is null');
-      const imageEl = await loadImage(imageDTO.image_url);
-      this.onLoad(imageName, imageEl);
-    } catch {
-      this.onError();
+      this.konvaPlaceholderGroup.visible(true);
     }
   }
 
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
index 8c89e07c67..8d2d5b198c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts
@@ -352,9 +352,6 @@ export class CanvasManager {
       if (lastProgressEvent !== prevLastProgressEvent) {
         log.debug('Rendering progress image');
         await this.preview.progressPreview.render(lastProgressEvent);
-        if (this.stateApi.getSession().isActive) {
-          this.preview.stagingArea.render();
-        }
       }
     });
 
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressPreview.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressPreview.ts
index a37622da68..f2e814cefa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressPreview.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasProgressPreview.ts
@@ -17,8 +17,9 @@ export class CanvasProgressPreview {
 
   async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
     const bboxRect = this.manager.stateApi.getBbox().rect;
+    const session = this.manager.stateApi.getSession();
 
-    if (lastProgressEvent) {
+    if (lastProgressEvent && session.isStaging) {
       const { invocation, step, progress_image } = lastProgressEvent;
       const { dataURL } = progress_image;
       const { x, y, width, height } = bboxRect;
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts
index 4bee9409a5..eada1fadcb 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingArea.ts
@@ -25,16 +25,8 @@ export class CanvasStagingArea {
 
     if (this.selectedImage) {
       const { imageDTO, offsetX, offsetY } = this.selectedImage;
-      if (this.image) {
-        if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
-          this.image.konvaImageGroup.visible(false);
-          this.image.konvaImage?.width(imageDTO.width);
-          this.image.konvaImage?.height(imageDTO.height);
-          this.image.konvaImageGroup.x(bboxRect.x + offsetX);
-          this.image.konvaImageGroup.y(bboxRect.y + offsetY);
-          await this.image.updateImageSource(imageDTO.image_name);
-        }
-      } else {
+
+      if (!this.image) {
         const { image_name, width, height } = imageDTO;
         this.image = new CanvasImage({
           id: 'staging-area-image',
@@ -51,13 +43,16 @@ export class CanvasStagingArea {
           },
         });
         this.group.add(this.image.konvaImageGroup);
+      }
+
+      if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
         this.image.konvaImage?.width(imageDTO.width);
         this.image.konvaImage?.height(imageDTO.height);
         this.image.konvaImageGroup.x(bboxRect.x + offsetX);
         this.image.konvaImageGroup.y(bboxRect.y + offsetY);
         await this.image.updateImageSource(imageDTO.image_name);
+        this.manager.stateApi.resetLastProgressEvent();
       }
-      this.manager.stateApi.resetLastProgressEvent();
       this.image.konvaImageGroup.visible(shouldShowStagedImage);
     } else {
       this.image?.konvaImageGroup.visible(false);