diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 79a4bfa49e..70568d371d 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -115,6 +115,7 @@ "githubLabel": "Github", "goTo": "Go to", "hotkeysLabel": "Hotkeys", + "loadingImage": "Loading Image", "imageFailedToLoad": "Unable to Load Image", "img2img": "Image To Image", "inpaint": "inpaint", diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts index e5e54c9593..9c3ab76cad 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts @@ -2,6 +2,7 @@ import { rgbaColorToString } from 'features/canvas/util/colorToString'; import { getObjectGroupId } from 'features/controlLayers/konva/naming'; import type { BrushLine, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types'; import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types'; +import { t } from 'i18next'; import Konva from 'konva'; import { getImageDTO } from 'services/api/endpoints/images'; import { v4 as uuidv4 } from 'uuid'; @@ -81,16 +82,58 @@ export const createRectShape = (rectShape: RectShape, layerObjectGroup: Konva.Gr return konvaRect; }; -export const createImageObject = async ( +const createImagePlaceholderGroup = ( + imageObject: ImageObject +): { konvaPlaceholderGroup: Konva.Group; onError: () => void; onLoading: () => void; onLoaded: () => void } => { + const { width, height } = imageObject.image; + const konvaPlaceholderGroup = new Konva.Group({ name: 'image-placeholder', listening: false }); + const konvaPlaceholderRect = new Konva.Rect({ + fill: 'hsl(220 12% 45% / 1)', // 'base.500' + width, + height, + }); + const konvaPlaceholderText = new Konva.Text({ + name: 'image-placeholder-text', + fill: 'hsl(220 12% 10% / 1)', // 'base.900' + width, + height, + align: 'center', + verticalAlign: 'middle', + fontFamily: '"Inter Variable", sans-serif', + fontSize: width / 16, + fontStyle: '600', + text: 'Loading Image', + listening: false, + }); + konvaPlaceholderGroup.add(konvaPlaceholderRect); + konvaPlaceholderGroup.add(konvaPlaceholderText); + + const onError = () => { + konvaPlaceholderText.text(t('common.imageFailedToLoad', 'Image Failed to Load')); + }; + const onLoading = () => { + konvaPlaceholderText.text(t('common.loadingImage', 'Loading Image')); + }; + const onLoaded = () => { + konvaPlaceholderGroup.destroy(); + }; + return { konvaPlaceholderGroup, onError, onLoading, onLoaded }; +}; + +export const createImageObjectGroup = async ( imageObject: ImageObject, layerObjectGroup: Konva.Group, name: string -): Promise => { - const imageDTO = await getImageDTO(imageObject.image.name); - if (!imageDTO) { - return null; - } - return new Promise((resolve) => { +): Promise => { + const konvaImageGroup = new Konva.Group({ id: imageObject.id, name, listening: false }); + const placeholder = createImagePlaceholderGroup(imageObject); + konvaImageGroup.add(placeholder.konvaPlaceholderGroup); + layerObjectGroup.add(konvaImageGroup); + getImageDTO(imageObject.image.name).then((imageDTO) => { + if (!imageDTO) { + placeholder.onError(); + return; + } const imageEl = new Image(); imageEl.onload = () => { const konvaImage = new Konva.Image({ @@ -99,15 +142,16 @@ export const createImageObject = async ( listening: false, image: imageEl, }); - layerObjectGroup.add(konvaImage); - resolve(konvaImage); + placeholder.onLoaded(); + konvaImageGroup.add(konvaImage); }; imageEl.onerror = () => { - resolve(null); + placeholder.onError(); }; imageEl.id = imageObject.id; imageEl.src = imageDTO.image_url; }); + return konvaImageGroup; }; /** * Creates a konva group for a layer's objects. diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts index ca74fee718..84fad00cb9 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts @@ -9,14 +9,13 @@ import { import { createBrushLine, createEraserLine, - createImageObject, + createImageObjectGroup, createObjectGroup, createRectShape, } from 'features/controlLayers/konva/renderers/objects'; import { getScaledFlooredCursorPosition, mapId, selectRasterObjects } from 'features/controlLayers/konva/util'; import type { RasterLayer, Tool } from 'features/controlLayers/store/types'; import Konva from 'konva'; -import { assert } from 'tsafe'; /** * Logic for creating and rendering raster layers. @@ -109,10 +108,7 @@ export const renderRasterLayer = async ( } } - for (let i = 0; i < layerState.objects.length; i++) { - const obj = layerState.objects[i]; - assert(obj); - const zIndex = layerState.objects.length - i; + for (const obj of layerState.objects) { if (obj.type === 'brush_line') { const konvaBrushLine = konvaObjectGroup.findOne(`#${obj.id}`) ?? @@ -121,7 +117,6 @@ export const renderRasterLayer = async ( if (konvaBrushLine.points().length !== obj.points.length) { konvaBrushLine.points(obj.points); } - konvaBrushLine.zIndex(zIndex); } else if (obj.type === 'eraser_line') { const konvaEraserLine = konvaObjectGroup.findOne(`#${obj.id}`) ?? @@ -130,17 +125,14 @@ export const renderRasterLayer = async ( if (konvaEraserLine.points().length !== obj.points.length) { konvaEraserLine.points(obj.points); } - konvaEraserLine.zIndex(zIndex); } else if (obj.type === 'rect_shape') { - const konvaRect = - konvaObjectGroup.findOne(`#${obj.id}`) ?? + if (!konvaObjectGroup.findOne(`#${obj.id}`)) { createRectShape(obj, konvaObjectGroup, RASTER_LAYER_RECT_SHAPE_NAME); - konvaRect.zIndex(zIndex); + } } else if (obj.type === 'image') { - const konvaImage = - konvaObjectGroup.findOne(`#${obj.id}`) ?? - (await createImageObject(obj, konvaObjectGroup, RASTER_LAYER_IMAGE_NAME)); - konvaImage?.zIndex(zIndex); + if (!konvaObjectGroup.findOne(`#${obj.id}`)) { + createImageObjectGroup(obj, konvaObjectGroup, RASTER_LAYER_IMAGE_NAME); + } } }