feat(ui): de-jank staging area and progress images

This commit is contained in:
psychedelicious 2024-07-16 22:16:16 +10:00
parent ef4d6c26f6
commit af815cf7eb
6 changed files with 38 additions and 75 deletions

View File

@ -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({

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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