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 { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { import {
$lastProgressEvent,
layerAddedFromStagingArea, layerAddedFromStagingArea,
sessionStagingAreaImageAccepted, sessionStagingAreaImageAccepted,
sessionStagingAreaReset, sessionStagingAreaReset,
@ -25,6 +26,9 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
); );
const { canceled } = await req.unwrap(); const { canceled } = await req.unwrap();
req.reset(); req.reset();
$lastProgressEvent.set(null);
if (canceled > 0) { if (canceled > 0) {
log.debug(`Canceled ${canceled} canvas batches`); log.debug(`Canceled ${canceled} canvas batches`);
toast({ toast({

View File

@ -76,11 +76,8 @@ export class CanvasControlAdapter {
didDraw = true; didDraw = true;
} }
} else if (!this.image) { } else if (!this.image) {
this.image = await new CanvasImage(imageObject, { this.image = await new CanvasImage(imageObject);
onLoad: () => { this.updateGroup(true);
this.updateGroup(true);
},
});
this.objectsGroup.add(this.image.konvaImageGroup); this.objectsGroup.add(this.image.konvaImageGroup);
await this.image.updateImageSource(imageObject.image.name); await this.image.updateImageSource(imageObject.image.name);
} else if (!this.image.isLoading && !this.image.isError) { } 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 type { ImageObject } from 'features/controlLayers/store/types';
import { t } from 'i18next'; import { t } from 'i18next';
import Konva from 'konva'; import Konva from 'konva';
import { getImageDTO as defaultGetImageDTO } from 'services/api/endpoints/images'; import { getImageDTO } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export class CanvasImage { 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 konvaImage: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
isLoading: boolean; isLoading: boolean;
isError: boolean; isError: boolean;
getImageDTO: (imageName: string) => Promise<ImageDTO | null>;
onLoading: () => void;
onLoad: (imageName: string, imageEl: HTMLImageElement) => void;
onError: () => void;
lastImageObject: ImageObject; lastImageObject: ImageObject;
constructor( constructor(imageObject: ImageObject) {
imageObject: ImageObject, const { id, width, height, x, y } = 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;
this.konvaImageGroup = new Konva.Group({ id, listening: false, x, y }); this.konvaImageGroup = new Konva.Group({ id, listening: false, x, y });
this.konvaPlaceholderGroup = new Konva.Group({ listening: false }); this.konvaPlaceholderGroup = new Konva.Group({ listening: false });
this.konvaPlaceholderRect = new Konva.Rect({ this.konvaPlaceholderRect = new Konva.Rect({
@ -64,19 +50,23 @@ export class CanvasImage {
this.konvaImage = null; this.konvaImage = null;
this.isLoading = false; this.isLoading = false;
this.isError = false; this.isError = false;
this.getImageDTO = getImageDTO ?? defaultGetImageDTO; this.lastImageObject = imageObject;
this.onLoading = function () { }
async updateImageSource(imageName: string) {
try {
this.isLoading = true; this.isLoading = true;
this.konvaImageGroup.visible(true);
if (!this.konvaImage) { if (!this.konvaImage) {
this.konvaPlaceholderGroup.visible(true); this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image')); this.konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image'));
} }
this.konvaImageGroup.visible(true);
if (onLoading) { const imageDTO = await getImageDTO(imageName);
onLoading(); assert(imageDTO !== null, 'imageDTO is null');
} const imageEl = await loadImage(imageDTO.image_url);
};
this.onLoad = function (imageName: string, imageEl: HTMLImageElement) {
if (this.konvaImage) { if (this.konvaImage) {
this.konvaImage.setAttrs({ this.konvaImage.setAttrs({
image: imageEl, image: imageEl,
@ -86,52 +76,31 @@ export class CanvasImage {
id: this.id, id: this.id,
listening: false, listening: false,
image: imageEl, image: imageEl,
width, width: this.lastImageObject.width,
height, height: this.lastImageObject.height,
}); });
this.konvaImageGroup.add(this.konvaImage); this.konvaImageGroup.add(this.konvaImage);
} }
if (filters.length > 0) {
if (this.lastImageObject.filters.length > 0) {
this.konvaImage.cache(); this.konvaImage.cache();
this.konvaImage.filters(filters.map((f) => FILTER_MAP[f])); this.konvaImage.filters(this.lastImageObject.filters.map((f) => FILTER_MAP[f]));
} else { } else {
this.konvaImage.clearCache(); this.konvaImage.clearCache();
this.konvaImage.filters([]); this.konvaImage.filters([]);
} }
this.imageName = imageName; this.imageName = imageName;
this.isLoading = false; this.isLoading = false;
this.isError = false; this.isError = false;
this.konvaPlaceholderGroup.visible(false); this.konvaPlaceholderGroup.visible(false);
this.konvaImageGroup.visible(true); } catch {
this.konvaImage?.visible(false);
if (onLoad) {
onLoad(this.konvaImage);
}
};
this.onError = function () {
this.imageName = null; this.imageName = null;
this.isLoading = false; this.isLoading = false;
this.isError = true; this.isError = true;
this.konvaPlaceholderGroup.visible(true);
this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load')); this.konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load'));
this.konvaImageGroup.visible(true); this.konvaPlaceholderGroup.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();
} }
} }

View File

@ -352,9 +352,6 @@ export class CanvasManager {
if (lastProgressEvent !== prevLastProgressEvent) { if (lastProgressEvent !== prevLastProgressEvent) {
log.debug('Rendering progress image'); log.debug('Rendering progress image');
await this.preview.progressPreview.render(lastProgressEvent); 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) { async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
const bboxRect = this.manager.stateApi.getBbox().rect; 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 { invocation, step, progress_image } = lastProgressEvent;
const { dataURL } = progress_image; const { dataURL } = progress_image;
const { x, y, width, height } = bboxRect; const { x, y, width, height } = bboxRect;

View File

@ -25,16 +25,8 @@ export class CanvasStagingArea {
if (this.selectedImage) { if (this.selectedImage) {
const { imageDTO, offsetX, offsetY } = this.selectedImage; const { imageDTO, offsetX, offsetY } = this.selectedImage;
if (this.image) {
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) { if (!this.image) {
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 {
const { image_name, width, height } = imageDTO; const { image_name, width, height } = imageDTO;
this.image = new CanvasImage({ this.image = new CanvasImage({
id: 'staging-area-image', id: 'staging-area-image',
@ -51,13 +43,16 @@ export class CanvasStagingArea {
}, },
}); });
this.group.add(this.image.konvaImageGroup); 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?.width(imageDTO.width);
this.image.konvaImage?.height(imageDTO.height); this.image.konvaImage?.height(imageDTO.height);
this.image.konvaImageGroup.x(bboxRect.x + offsetX); this.image.konvaImageGroup.x(bboxRect.x + offsetX);
this.image.konvaImageGroup.y(bboxRect.y + offsetY); this.image.konvaImageGroup.y(bboxRect.y + offsetY);
await this.image.updateImageSource(imageDTO.image_name); await this.image.updateImageSource(imageDTO.image_name);
this.manager.stateApi.resetLastProgressEvent();
} }
this.manager.stateApi.resetLastProgressEvent();
this.image.konvaImageGroup.visible(shouldShowStagedImage); this.image.konvaImageGroup.visible(shouldShowStagedImage);
} else { } else {
this.image?.konvaImageGroup.visible(false); this.image?.konvaImageGroup.visible(false);