diff --git a/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx
new file mode 100644
index 0000000000..64d5e1beef
--- /dev/null
+++ b/invokeai/frontend/web/src/common/components/ImageMetadataOverlay.tsx
@@ -0,0 +1,54 @@
+import { Badge, Flex } from '@chakra-ui/react';
+import { Image } from 'app/types/invokeai';
+import { isNumber, isString } from 'lodash-es';
+import { useMemo } from 'react';
+
+type ImageMetadataOverlayProps = {
+ image: Image;
+};
+
+const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
+ const dimensions = useMemo(() => {
+ if (!isNumber(image.metadata?.width) || isNumber(!image.metadata?.height)) {
+ return;
+ }
+
+ return `${image.metadata?.width} × ${image.metadata?.height}`;
+ }, [image.metadata]);
+
+ const model = useMemo(() => {
+ if (!isString(image.metadata?.invokeai?.node?.model)) {
+ return;
+ }
+
+ return image.metadata?.invokeai?.node?.model;
+ }, [image.metadata]);
+
+ return (
+
+ {dimensions && (
+
+ {dimensions}
+
+ )}
+ {model && (
+
+ {model}
+
+ )}
+
+ );
+};
+
+export default ImageMetadataOverlay;
diff --git a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx
deleted file mode 100644
index 9d864f5c9c..0000000000
--- a/invokeai/frontend/web/src/common/components/ImageToImageOverlay.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Badge, Box, Flex } from '@chakra-ui/react';
-import { Image } from 'app/types/invokeai';
-
-type ImageToImageOverlayProps = {
- image: Image;
-};
-
-const ImageToImageOverlay = ({ image }: ImageToImageOverlayProps) => {
- return (
-
-
-
- {image.metadata?.width} × {image.metadata?.height}
-
-
-
- );
-};
-
-export default ImageToImageOverlay;
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
index a0fbd7c5d1..b1dbed5a81 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx
@@ -1,4 +1,4 @@
-import { Box, Flex, Image, Skeleton, useBoolean } from '@chakra-ui/react';
+import { Box, Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
@@ -11,7 +11,8 @@ import NextPrevImageButtons from './NextPrevImageButtons';
import CurrentImageHidden from './CurrentImageHidden';
import { DragEvent, memo, useCallback } from 'react';
import { systemSelector } from 'features/system/store/systemSelectors';
-import CurrentImageFallback from './CurrentImageFallback';
+import ImageFallbackSpinner from './ImageFallbackSpinner';
+import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
export const imagesSelector = createSelector(
[uiSelector, gallerySelector, systemSelector],
@@ -50,8 +51,6 @@ const CurrentImagePreview = () => {
} = useAppSelector(imagesSelector);
const { getUrl } = useGetUrl();
- const [isLoaded, { on, off }] = useBoolean();
-
const handleDragStart = useCallback(
(e: DragEvent) => {
if (!image) {
@@ -67,11 +66,11 @@ const CurrentImagePreview = () => {
return (
{progressImage && shouldShowProgressInViewer ? (
@@ -91,28 +90,23 @@ const CurrentImagePreview = () => {
/>
) : (
image && (
-
- ) : (
-
- )
- }
- sx={{
- objectFit: 'contain',
- maxWidth: '100%',
- maxHeight: '100%',
- height: 'auto',
- position: 'absolute',
- borderRadius: 'base',
- }}
- />
+ <>
+ }
+ onDragStart={handleDragStart}
+ sx={{
+ objectFit: 'contain',
+ maxWidth: '100%',
+ maxHeight: '100%',
+ height: 'auto',
+ position: 'absolute',
+ borderRadius: 'base',
+ }}
+ />
+
+ >
)
)}
{shouldShowImageDetails && image && 'metadata' in image && (
diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx
index b812849c44..a2103eb8e2 100644
--- a/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/GalleryProgressImage.tsx
@@ -1,4 +1,4 @@
-import { Box, Flex, Image } from '@chakra-ui/react';
+import { Flex, Image, Spinner } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
@@ -42,6 +42,7 @@ const GalleryProgressImage = () => {
alignItems: 'center',
justifyContent: 'center',
aspectRatio: '1/1',
+ position: 'relative',
}}
>
{
imageRendering: shouldAntialiasProgressImage ? 'auto' : 'pixelated',
}}
/>
+
);
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
index 2e5f166025..35ddefe181 100644
--- a/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/HoverableImage.tsx
@@ -278,6 +278,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
h: 'full',
transition: 'transform 0.2s ease-out',
aspectRatio: '1/1',
+ cursor: 'pointer',
}}
>
{
+const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
const { size = 'xl', ...rest } = props;
return (
@@ -21,4 +21,4 @@ const CurrentImageFallback = (props: CurrentImageFallbackProps) => {
);
};
-export default CurrentImageFallback;
+export default ImageFallbackSpinner;
diff --git a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
index 2326295451..81705086b3 100644
--- a/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/gallerySlice.ts
@@ -1,6 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { Image } from 'app/types/invokeai';
+import { imageReceived, thumbnailReceived } from 'services/thunks/image';
type GalleryImageObjectFitType = 'contain' | 'cover';
@@ -63,6 +64,29 @@ export const gallerySlice = createSlice({
state.shouldUseSingleGalleryColumn = action.payload;
},
},
+ extraReducers(builder) {
+ builder.addCase(imageReceived.fulfilled, (state, action) => {
+ // When we get an updated URL for an image, we need to update the selectedImage in gallery,
+ // which is currently its own object (instead of a reference to an image in results/uploads)
+ const { imagePath } = action.payload;
+ const { imageName } = action.meta.arg;
+
+ if (state.selectedImage?.name === imageName) {
+ state.selectedImage.url = imagePath;
+ }
+ });
+
+ builder.addCase(thumbnailReceived.fulfilled, (state, action) => {
+ // When we get an updated URL for an image, we need to update the selectedImage in gallery,
+ // which is currently its own object (instead of a reference to an image in results/uploads)
+ const { thumbnailPath } = action.payload;
+ const { thumbnailName } = action.meta.arg;
+
+ if (state.selectedImage?.name === thumbnailName) {
+ state.selectedImage.thumbnail = thumbnailPath;
+ }
+ });
+ },
});
export const {
diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
index fbb833a14a..3de1e1cebb 100644
--- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImagePreview.tsx
@@ -1,17 +1,18 @@
-import { Flex, Image, Spinner } from '@chakra-ui/react';
+import { Flex, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
import { useGetUrl } from 'common/util/getUrl';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice';
-import { DragEvent, useCallback, useState } from 'react';
+import { DragEvent, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { ImageType } from 'services/api';
-import ImageToImageOverlay from 'common/components/ImageToImageOverlay';
+import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
import { generationSelector } from 'features/parameters/store/generationSelectors';
import { initialImageSelected } from 'features/parameters/store/actions';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
+import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner';
const selector = createSelector(
[generationSelector],
@@ -30,8 +31,6 @@ const InitialImagePreview = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const [isLoaded, setIsLoaded] = useState(false);
-
const onError = () => {
dispatch(
addToast({
@@ -42,13 +41,10 @@ const InitialImagePreview = () => {
})
);
dispatch(clearInitialImage());
- setIsLoaded(false);
};
const handleDrop = useCallback(
(e: DragEvent) => {
- setIsLoaded(false);
-
const name = e.dataTransfer.getData('invokeai/imageName');
const type = e.dataTransfer.getData('invokeai/imageType') as ImageType;
@@ -62,48 +58,32 @@ const InitialImagePreview = () => {
sx={{
width: 'full',
height: 'full',
+ position: 'relative',
alignItems: 'center',
justifyContent: 'center',
- position: 'relative',
}}
onDrop={handleDrop}
>
-
- {initialImage?.url && (
- <>
- {
- setIsLoaded(true);
- }}
- fallback={
-
-
-
- }
- />
- {isLoaded && }
- >
- )}
- {!initialImage?.url && }
-
+ {initialImage?.url && (
+ <>
+ }
+ onError={onError}
+ sx={{
+ objectFit: 'contain',
+ maxWidth: '100%',
+ maxHeight: '100%',
+ height: 'auto',
+ position: 'absolute',
+ borderRadius: 'base',
+ }}
+ />
+
+ >
+ )}
+ {!initialImage?.url && }
);
};
diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
index 1aeb2a1939..e9cbd21a15 100644
--- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts
+++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts
@@ -418,6 +418,7 @@ export const systemSlice = createSlice({
state.currentStep = 0;
state.totalSteps = 0;
state.statusTranslationKey = 'common.statusConnected';
+ state.progressImage = null;
state.toastQueue.push(
makeToast({ title: t('toast.canceled'), status: 'warning' })