Skip to content

Commit 7025777

Browse files
fix(ui): fix init image display buttons
- Reset and Upload buttons along top of initial image - Also had to mess around with the control net & DnD image stuff after changing the styles - Abstract image upload logic into hook - does not handle native HTML drag and drop upload - only the button click upload
1 parent 9429ddf commit 7025777

File tree

6 files changed

+179
-140
lines changed

6 files changed

+179
-140
lines changed

invokeai/frontend/web/src/common/components/IAIDndImage.tsx

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@ import IAIIconButton from 'common/components/IAIIconButton';
1212
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
1313
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
1414
import { AnimatePresence } from 'framer-motion';
15-
import { ReactElement, SyntheticEvent, useCallback } from 'react';
15+
import { ReactElement, SyntheticEvent } from 'react';
1616
import { memo, useRef } from 'react';
17-
import { FaImage, FaTimes, FaUndo, FaUpload } from 'react-icons/fa';
17+
import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
1818
import { ImageDTO } from 'services/api/types';
1919
import { v4 as uuidv4 } from 'uuid';
2020
import IAIDropOverlay from './IAIDropOverlay';
21-
import { PostUploadAction, imageUploaded } from 'services/api/thunks/image';
22-
import { useDropzone } from 'react-dropzone';
23-
import { useAppDispatch } from 'app/store/storeHooks';
21+
import { PostUploadAction } from 'services/api/thunks/image';
22+
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
2423

2524
type IAIDndImageProps = {
2625
image: ImageDTO | null | undefined;
@@ -39,6 +38,7 @@ type IAIDndImageProps = {
3938
minSize?: number;
4039
postUploadAction?: PostUploadAction;
4140
imageSx?: ChakraProps['sx'];
41+
fitContainer?: boolean;
4242
};
4343

4444
const IAIDndImage = (props: IAIDndImageProps) => {
@@ -58,8 +58,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
5858
minSize = 24,
5959
postUploadAction,
6060
imageSx,
61+
fitContainer = false,
6162
} = props;
62-
const dispatch = useAppDispatch();
63+
6364
const dndId = useRef(uuidv4());
6465

6566
const {
@@ -87,31 +88,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
8788
disabled: isDragDisabled || !image,
8889
});
8990

90-
const handleOnDropAccepted = useCallback(
91-
(files: Array<File>) => {
92-
const file = files[0];
93-
if (!file) {
94-
return;
95-
}
96-
97-
dispatch(
98-
imageUploaded({
99-
file,
100-
image_category: 'user',
101-
is_intermediate: false,
102-
postUploadAction,
103-
})
104-
);
105-
},
106-
[dispatch, postUploadAction]
107-
);
108-
109-
const { getRootProps, getInputProps } = useDropzone({
110-
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
111-
onDropAccepted: handleOnDropAccepted,
112-
noDrag: true,
113-
multiple: false,
114-
disabled: isUploadDisabled,
91+
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
92+
postUploadAction,
93+
isDisabled: isUploadDisabled,
11594
});
11695

11796
const setNodeRef = useCombinedRefs(setDroppableRef, setDraggableRef);
@@ -149,13 +128,13 @@ const IAIDndImage = (props: IAIDndImageProps) => {
149128
sx={{
150129
w: 'full',
151130
h: 'full',
131+
position: fitContainer ? 'absolute' : 'relative',
152132
alignItems: 'center',
153133
justifyContent: 'center',
154134
}}
155135
>
156136
<Image
157137
src={image.image_url}
158-
fallbackStrategy="beforeLoadOrError"
159138
fallback={fallback}
160139
onError={onError}
161140
objectFit="contain"
@@ -205,9 +184,9 @@ const IAIDndImage = (props: IAIDndImageProps) => {
205184
color: 'base.500',
206185
...uploadButtonStyles,
207186
}}
208-
{...getRootProps()}
187+
{...getUploadButtonProps()}
209188
>
210-
<input {...getInputProps()} />
189+
<input {...getUploadInputProps()} />
211190
<Icon
212191
as={isUploadDisabled ? FaImage : FaUpload}
213192
sx={{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useAppDispatch } from 'app/store/storeHooks';
2+
import { useCallback } from 'react';
3+
import { useDropzone } from 'react-dropzone';
4+
import { PostUploadAction, imageUploaded } from 'services/api/thunks/image';
5+
6+
type UseImageUploadButtonArgs = {
7+
postUploadAction?: PostUploadAction;
8+
isDisabled?: boolean;
9+
};
10+
11+
/**
12+
* Provides image uploader functionality to any component.
13+
*
14+
* @example
15+
* const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
16+
* postUploadAction: {
17+
* type: 'SET_CONTROLNET_IMAGE',
18+
* controlNetId: '12345',
19+
* },
20+
* isDisabled: getIsUploadDisabled(),
21+
* });
22+
*
23+
* // in the render function
24+
* <Button {...getUploadButtonProps()} /> // will open the file dialog on click
25+
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
26+
*/
27+
export const useImageUploadButton = ({
28+
postUploadAction,
29+
isDisabled,
30+
}: UseImageUploadButtonArgs) => {
31+
const dispatch = useAppDispatch();
32+
const onDropAccepted = useCallback(
33+
(files: File[]) => {
34+
const file = files[0];
35+
if (!file) {
36+
return;
37+
}
38+
39+
dispatch(
40+
imageUploaded({
41+
file,
42+
image_category: 'user',
43+
is_intermediate: false,
44+
postUploadAction,
45+
})
46+
);
47+
},
48+
[dispatch, postUploadAction]
49+
);
50+
51+
const {
52+
getRootProps: getUploadButtonProps,
53+
getInputProps: getUploadInputProps,
54+
open: openUploader,
55+
} = useDropzone({
56+
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
57+
onDropAccepted,
58+
disabled: isDisabled,
59+
noDrag: true,
60+
multiple: false,
61+
});
62+
63+
return { getUploadButtonProps, getUploadInputProps, openUploader };
64+
};

invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx

Lines changed: 37 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { Box, ChakraProps, Flex } from '@chakra-ui/react';
1010
import IAIDndImage from 'common/components/IAIDndImage';
1111
import { createSelector } from '@reduxjs/toolkit';
1212
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
13-
import { AnimatePresence, motion } from 'framer-motion';
1413
import { IAIImageLoadingFallback } from 'common/components/IAIImageFallback';
1514
import IAIIconButton from 'common/components/IAIIconButton';
1615
import { FaUndo } from 'react-icons/fa';
@@ -105,64 +104,46 @@ const ControlNetImagePreview = (props: Props) => {
105104
<IAIDndImage
106105
image={controlImage}
107106
onDrop={handleDrop}
108-
isDropDisabled={Boolean(
109-
processedControlImage && processorType !== 'none'
110-
)}
107+
isDropDisabled={shouldShowProcessedImage}
111108
isUploadDisabled={Boolean(controlImage)}
112109
postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }}
113-
imageSx={imageSx}
110+
imageSx={{
111+
pointerEvents: shouldShowProcessedImage ? 'none' : 'auto',
112+
...imageSx,
113+
}}
114114
/>
115-
<AnimatePresence>
116-
{shouldShowProcessedImage && (
117-
<motion.div
118-
style={{ width: '100%' }}
119-
initial={{
120-
opacity: 0,
121-
}}
122-
animate={{
123-
opacity: 1,
124-
transition: { duration: 0.1 },
125-
}}
126-
exit={{
127-
opacity: 0,
128-
transition: { duration: 0.1 },
129-
}}
130-
>
131-
<>
132-
{shouldShowProcessedImageBackdrop && (
133-
<Box
134-
sx={{
135-
position: 'absolute',
136-
top: 0,
137-
insetInlineStart: 0,
138-
w: 'full',
139-
h: 'full',
140-
bg: 'base.900',
141-
opacity: 0.7,
142-
}}
143-
/>
144-
)}
145-
<Box
146-
sx={{
147-
position: 'absolute',
148-
top: 0,
149-
insetInlineStart: 0,
150-
w: 'full',
151-
h: 'full',
152-
}}
153-
>
154-
<IAIDndImage
155-
image={processedControlImage}
156-
onDrop={handleDrop}
157-
payloadImage={controlImage}
158-
isUploadDisabled={true}
159-
imageSx={imageSx}
160-
/>
161-
</Box>
162-
</>
163-
</motion.div>
164-
)}
165-
</AnimatePresence>
115+
{shouldShowProcessedImage && (
116+
<Box
117+
sx={{
118+
position: 'absolute',
119+
top: 0,
120+
insetInlineStart: 0,
121+
w: 'full',
122+
h: 'full',
123+
}}
124+
>
125+
{shouldShowProcessedImageBackdrop && (
126+
<Box
127+
sx={{
128+
position: 'absolute',
129+
top: 0,
130+
insetInlineStart: 0,
131+
w: 'full',
132+
h: 'full',
133+
bg: 'base.900',
134+
opacity: 0.7,
135+
}}
136+
/>
137+
)}
138+
<IAIDndImage
139+
image={processedControlImage}
140+
onDrop={handleDrop}
141+
payloadImage={controlImage}
142+
isUploadDisabled={true}
143+
imageSx={imageSx}
144+
/>
145+
</Box>
146+
)}
166147
{pendingControlImages.includes(controlNetId) && (
167148
<Box
168149
sx={{

invokeai/frontend/web/src/features/gallery/components/CurrentImageDisplay.tsx

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Box, Flex } from '@chakra-ui/react';
1+
import { Flex } from '@chakra-ui/react';
22
import { createSelector } from '@reduxjs/toolkit';
33
import { useAppSelector } from 'app/store/storeHooks';
44
import { systemSelector } from 'features/system/store/systemSelectors';
5-
import { isEqual } from 'lodash-es';
65

76
import { gallerySelector } from '../store/gallerySelectors';
87
import CurrentImageButtons from './CurrentImageButtons';
@@ -22,13 +21,8 @@ export const currentImageDisplaySelector = createSelector(
2221
defaultSelectorOptions
2322
);
2423

25-
/**
26-
* Displays the current image if there is one, plus associated actions.
27-
*/
2824
const CurrentImageDisplay = () => {
29-
const { hasSelectedImage, hasProgressImage } = useAppSelector(
30-
currentImageDisplaySelector
31-
);
25+
const { hasSelectedImage } = useAppSelector(currentImageDisplaySelector);
3226

3327
return (
3428
<Flex
@@ -43,24 +37,8 @@ const CurrentImageDisplay = () => {
4337
justifyContent: 'center',
4438
}}
4539
>
46-
<Flex
47-
flexDirection="column"
48-
sx={{
49-
w: 'full',
50-
h: 'full',
51-
alignItems: 'center',
52-
justifyContent: 'center',
53-
gap: 4,
54-
position: 'absolute',
55-
}}
56-
>
57-
<CurrentImagePreview />
58-
</Flex>
59-
{hasSelectedImage && (
60-
<Box sx={{ position: 'absolute', top: 0 }}>
61-
<CurrentImageButtons />
62-
</Box>
63-
)}
40+
{hasSelectedImage && <CurrentImageButtons />}
41+
<CurrentImagePreview />
6442
</Flex>
6543
);
6644
};

invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ const CurrentImagePreview = () => {
5151
shouldAntialiasProgressImage,
5252
} = useAppSelector(imagesSelector);
5353

54-
// const image = useAppSelector((state: RootState) =>
55-
// selectImagesById(state, selectedImage ?? '')
56-
// );
57-
5854
const {
5955
currentData: image,
6056
isLoading,
@@ -79,7 +75,6 @@ const CurrentImagePreview = () => {
7975
sx={{
8076
width: 'full',
8177
height: 'full',
82-
position: 'relative',
8378
alignItems: 'center',
8479
justifyContent: 'center',
8580
}}
@@ -101,21 +96,13 @@ const CurrentImagePreview = () => {
10196
}}
10297
/>
10398
) : (
104-
<Flex
105-
sx={{
106-
width: 'full',
107-
height: 'full',
108-
alignItems: 'center',
109-
justifyContent: 'center',
110-
}}
111-
>
112-
<IAIDndImage
113-
image={image}
114-
onDrop={handleDrop}
115-
fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />}
116-
isUploadDisabled={true}
117-
/>
118-
</Flex>
99+
<IAIDndImage
100+
image={image}
101+
onDrop={handleDrop}
102+
fallback={<IAIImageLoadingFallback sx={{ bg: 'none' }} />}
103+
isUploadDisabled={true}
104+
fitContainer
105+
/>
119106
)}
120107
{shouldShowImageDetails && image && (
121108
<Box

0 commit comments

Comments
 (0)