diff --git a/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx b/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx index b8037c69924..64d1861f9d3 100644 --- a/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx +++ b/invokeai/frontend/web/src/common/hooks/useImageUploadButton.tsx @@ -1,8 +1,12 @@ +import { logger } from 'app/logging/logger'; import { useAppSelector } from 'app/store/storeHooks'; import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors'; import { selectMaxImageUploadCount } from 'features/system/store/configSlice'; +import { toast } from 'features/toast/toast'; import { useCallback } from 'react'; +import type { FileRejection } from 'react-dropzone'; import { useDropzone } from 'react-dropzone'; +import { useTranslation } from 'react-i18next'; import { useUploadImageMutation } from 'services/api/endpoints/images'; import type { PostUploadAction } from 'services/api/types'; @@ -12,6 +16,8 @@ type UseImageUploadButtonArgs = { allowMultiple?: boolean; }; +const log = logger('gallery'); + /** * Provides image uploader functionality to any component. * @@ -39,22 +45,50 @@ export const useImageUploadButton = ({ const autoAddBoardId = useAppSelector(selectAutoAddBoardId); const [uploadImage] = useUploadImageMutation(); const maxImageUploadCount = useAppSelector(selectMaxImageUploadCount); + const { t } = useTranslation(); const onDropAccepted = useCallback( (files: File[]) => { - for (const file of files) { + for (const [i, file] of files.entries()) { uploadImage({ file, image_category: 'user', is_intermediate: false, postUploadAction: postUploadAction ?? { type: 'TOAST' }, board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId, + isFirstUploadOfBatch: i === 0, }); } }, [autoAddBoardId, postUploadAction, uploadImage] ); + const onDropRejected = useCallback( + (fileRejections: FileRejection[]) => { + if (fileRejections.length > 0) { + const errors = fileRejections.map((rejection) => ({ + errors: rejection.errors.map(({ message }) => message), + file: rejection.file.path, + })); + log.error({ errors }, 'Invalid upload'); + const description = + maxImageUploadCount === undefined + ? t('toast.uploadFailedInvalidUploadDesc') + : t('toast.uploadFailedInvalidUploadDesc_withCount', { count: maxImageUploadCount }); + + toast({ + id: 'UPLOAD_FAILED', + title: t('toast.uploadFailed'), + description, + status: 'error', + }); + + return; + } + }, + [maxImageUploadCount, t] + ); + const { getRootProps: getUploadButtonProps, getInputProps: getUploadInputProps, @@ -62,6 +96,7 @@ export const useImageUploadButton = ({ } = useDropzone({ accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] }, onDropAccepted, + onDropRejected, disabled: isDisabled, noDrag: true, multiple: allowMultiple && (maxImageUploadCount === undefined || maxImageUploadCount > 1),