Compare commits

...

3 Commits

Author SHA1 Message Date
Mary Hipp
1c8a5c0788 wip 2024-10-15 14:46:50 -04:00
Mary Hipp
548750534b feat(ui): allow multiple images to be dropped for upload - client loops through, api doesnt change 2024-10-15 12:32:43 -04:00
Mary Hipp
918470e454 fix(ui): remove logic that double added uploads to boards 2024-10-15 12:32:13 -04:00
9 changed files with 120 additions and 43 deletions

View File

@@ -1171,7 +1171,7 @@
"setNodeField": "Set as node field",
"somethingWentWrong": "Something Went Wrong",
"uploadFailed": "Upload failed",
"uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image",
"uploadFailedInvalidUploadDesc": "Must be PNG or JPEG images",
"workflowLoaded": "Workflow Loaded",
"problemRetrievingWorkflow": "Problem Retrieving Workflow",
"workflowDeleted": "Workflow Deleted",

View File

@@ -47,26 +47,32 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
status: 'success',
} as const;
const BATCH_UPLOADED_TOAST = {
id: 'BATCH_UPLOADED',
title: t('toast.imageUploaded'),
status: 'info',
withCount: true,
duration: null
} as const;
// default action - just upload and alert user
if (postUploadAction?.type === 'TOAST') {
if (!autoAddBoardId || autoAddBoardId === 'none') {
const title = postUploadAction.title || DEFAULT_UPLOADED_TOAST.title;
toast({ ...DEFAULT_UPLOADED_TOAST, title });
if (postUploadAction.batchCount) {
toast({ ...BATCH_UPLOADED_TOAST, title });
} else {
toast({ ...DEFAULT_UPLOADED_TOAST, title });
}
dispatch(boardIdSelected({ boardId: 'none' }));
dispatch(galleryViewChanged('assets'));
} else {
// Add this image to the board
dispatch(
imagesApi.endpoints.addImageToBoard.initiate({
board_id: autoAddBoardId,
imageDTO,
})
);
// Attempt to get the board's name for the toast
const queryArgs = selectListBoardsQueryArgs(state);
const { data } = boardsApi.endpoints.listAllBoards.select(queryArgs)(state);
const title = postUploadAction.title || DEFAULT_UPLOADED_TOAST.title;
// Fall back to just the board id if we can't find the board for some reason
const board = data?.find((b) => b.board_id === autoAddBoardId);
const description = board
@@ -75,6 +81,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
toast({
...DEFAULT_UPLOADED_TOAST,
title,
description,
});
dispatch(boardIdSelected({ boardId: autoAddBoardId }));

View File

@@ -1,5 +1,5 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { toast } from 'features/toast/toast';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
@@ -9,28 +9,31 @@ import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { useUploadImageMutation } from 'services/api/endpoints/images';
import type { PostUploadAction } from 'services/api/types';
import { batchIndexIncremented, uploadingBatchChanged } from '../../features/gallery/store/gallerySlice';
const accept: Accept = {
'image/png': ['.png'],
'image/jpeg': ['.jpg', '.jpeg', '.png'],
};
const selectPostUploadAction = createMemoizedSelector(selectActiveTab, (activeTabName) => {
let postUploadAction: PostUploadAction = { type: 'TOAST' };
if (activeTabName === 'upscaling') {
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
}
return postUploadAction;
});
export const useFullscreenDropzone = () => {
const { t } = useTranslation();
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
const postUploadAction = useAppSelector(selectPostUploadAction);
const [uploadImage] = useUploadImageMutation();
const activeTabName = useAppSelector(selectActiveTab);
const dispatch = useAppDispatch()
const getPostUploadAction = useCallback(
(singleImage: boolean): PostUploadAction | undefined => {
if (singleImage && activeTabName === 'upscaling') {
return { type: 'SET_UPSCALE_INITIAL_IMAGE' };
} else if (singleImage) {
return { type: 'TOAST' };
}
},
[activeTabName]
);
const fileRejectionCallback = useCallback(
(rejection: FileRejection) => {
@@ -47,7 +50,7 @@ export const useFullscreenDropzone = () => {
);
const fileAcceptedCallback = useCallback(
(file: File) => {
(file: File, postUploadAction: PostUploadAction | undefined) => {
uploadImage({
file,
image_category: 'user',
@@ -56,7 +59,7 @@ export const useFullscreenDropzone = () => {
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
});
},
[autoAddBoardId, postUploadAction, uploadImage]
[autoAddBoardId, uploadImage]
);
const onDrop = useCallback(
@@ -75,11 +78,41 @@ export const useFullscreenDropzone = () => {
fileRejectionCallback(rejection);
});
acceptedFiles.forEach((file: File) => {
fileAcceptedCallback(file);
const postUploadAction = getPostUploadAction(acceptedFiles.length === 1);
if (acceptedFiles.length === 1) {
acceptedFiles.forEach((file: File) => {
fileAcceptedCallback(file, postUploadAction);
});
} else {
const batchTotal = acceptedFiles.length
let index = 0
// dispatch(uploadingBatchChanged({uploadingBatch: true, batchTotal: acceptedFiles.length}))
toast({
id: 'BATCH_UPLOADING',
title: "Batch uploading",
status: 'info',
updateDescription: true,
description: `Uploading ${index} or ${batchTotal}`,
duration: null
});
acceptedFiles.forEach((file: File) => {
fileAcceptedCallback(file, undefined);
toast({
id: 'BATCH_UPLOADING',
title: "Batch uploading",
status: 'info',
updateDescription: true,
description: `Uploading ${index} of ${batchTotal}`,
duration: null
});
});
}
},
[t, fileAcceptedCallback, fileRejectionCallback]
[t, fileAcceptedCallback, fileRejectionCallback, getPostUploadAction]
);
const onDragOver = useCallback(() => {
@@ -91,7 +124,6 @@ export const useFullscreenDropzone = () => {
noClick: true,
onDrop,
onDragOver,
multiple: false,
noKeyboard: true,
});
@@ -120,3 +152,5 @@ export const useFullscreenDropzone = () => {
return { dropzone, isHandlingUpload, setIsHandlingUpload };
};

View File

@@ -8,6 +8,7 @@ import type { PostUploadAction } from 'services/api/types';
type UseImageUploadButtonArgs = {
postUploadAction?: PostUploadAction;
isDisabled?: boolean;
allowMultiple?: boolean;
};
/**
@@ -29,24 +30,20 @@ type UseImageUploadButtonArgs = {
* <Button {...getUploadButtonProps()} /> // will open the file dialog on click
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
*/
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
export const useImageUploadButton = ({ postUploadAction, isDisabled, allowMultiple }: UseImageUploadButtonArgs) => {
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const [uploadImage] = useUploadImageMutation();
const onDropAccepted = useCallback(
(files: File[]) => {
const file = files[0];
if (!file) {
return;
for (const file of files) {
uploadImage({
file,
image_category: 'user',
is_intermediate: false,
postUploadAction: postUploadAction ?? { type: 'TOAST' },
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
});
}
uploadImage({
file,
image_category: 'user',
is_intermediate: false,
postUploadAction: postUploadAction ?? { type: 'TOAST' },
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
});
},
[autoAddBoardId, postUploadAction, uploadImage]
);
@@ -60,7 +57,7 @@ export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageU
onDropAccepted,
disabled: isDisabled,
noDrag: true,
multiple: false,
multiple: allowMultiple || false,
});
return { getUploadButtonProps, getUploadInputProps, openUploader };

View File

@@ -24,6 +24,7 @@ import { PiMagnifyingGlassBold } from 'react-icons/pi';
import { useBoardName } from 'services/api/hooks/useBoardName';
import GallerySettingsPopover from './GallerySettingsPopover/GallerySettingsPopover';
import { GalleryUploadButton } from './GalleryUploadButton';
import GalleryImageGrid from './ImageGrid/GalleryImageGrid';
import { GalleryPagination } from './ImageGrid/GalleryPagination';
import { GallerySearch } from './ImageGrid/GallerySearch';
@@ -87,6 +88,7 @@ export const Gallery = () => {
</Tab>
</Tooltip>
<Flex h="full" justifyContent="flex-end">
<GalleryUploadButton />
<GallerySettingsPopover />
<IconButton
size="sm"

View File

@@ -0,0 +1,22 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { t } from 'i18next';
import { PiUploadBold } from 'react-icons/pi';
export const GalleryUploadButton = () => {
const uploadApi = useImageUploadButton({ postUploadAction: { type: 'TOAST' }, allowMultiple: true });
return (
<>
<IconButton
size="sm"
alignSelf="stretch"
variant="link"
aria-label={t('accessibility.uploadImage')}
tooltip={t('accessibility.uploadImage')}
icon={<PiUploadBold />}
{...uploadApi.getUploadButtonProps()}
/>
<input {...uploadApi.getUploadInputProps()} />
</>
);
};

View File

@@ -27,6 +27,7 @@ const initialGalleryState: GalleryState = {
shouldShowArchivedBoards: false,
boardsListOrderBy: 'created_at',
boardsListOrderDir: 'DESC',
uploadingBatch: false,
};
export const gallerySlice = createSlice({
@@ -169,6 +170,14 @@ export const gallerySlice = createSlice({
boardsListOrderDirChanged: (state, action: PayloadAction<OrderDir>) => {
state.boardsListOrderDir = action.payload;
},
uploadingBatchChanged: (state, action: PayloadAction<{uploadingBatch: boolean; batchTotal?: number}>) => {
const {uploadingBatch, batchTotal} = action.payload
state.uploadingBatch = uploadingBatch
state.batchTotal = batchTotal
},
batchIndexIncremented: (state ) => {
state.batchIndex = (state.batchIndex || 0) + 1
},
},
});
@@ -196,6 +205,8 @@ export const {
searchTermChanged,
boardsListOrderByChanged,
boardsListOrderDirChanged,
uploadingBatchChanged,
batchIndexIncremented
} = gallerySlice.actions;
export const selectGallerySlice = (state: RootState) => state.gallery;

View File

@@ -30,4 +30,7 @@ export type GalleryState = {
shouldShowArchivedBoards: boolean;
boardsListOrderBy: BoardRecordOrderBy;
boardsListOrderDir: OrderDir;
uploadingBatch: boolean;
batchTotal?: number;
batchIndex?: number;
};

View File

@@ -222,6 +222,7 @@ type UpscaleInitialImageAction = {
type ToastAction = {
type: 'TOAST';
title?: string;
batchCount?: number;
};
type AddToBatchAction = {