Merge pull request #277 from mirumee/add/multiple-image-upload
Allow multiple images to be uploaded
This commit is contained in:
commit
9e30bb7231
9 changed files with 255 additions and 46 deletions
|
@ -15,6 +15,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Use searches as hooks instead of components - #262 by @dominik-zeglen
|
||||
- Add navigator - #267 by @dominik-zeglen
|
||||
- Fix voucher limit - #271 by @dominik-zeglen
|
||||
- Allow multiple images to be uploaded - #277 by @dominik-zeglen
|
||||
|
||||
## 2.0.0
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ const CategoryBackground: React.FC<CategoryBackgroundProps> = props => {
|
|||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={anchor}
|
||||
accept="image/*"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -90,7 +91,7 @@ const CategoryBackground: React.FC<CategoryBackgroundProps> = props => {
|
|||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
<ImageUpload onImageUpload={files => onImageUpload(files[0])} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
|
|
|
@ -92,6 +92,7 @@ export const CollectionImage: React.FC<CollectionImageProps> = props => {
|
|||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
type="file"
|
||||
ref={anchor}
|
||||
accept="image/*"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ export const CollectionImage: React.FC<CollectionImageProps> = props => {
|
|||
</div>
|
||||
</CardContent>
|
||||
) : image === null ? (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
<ImageUpload onImageUpload={files => onImageUpload(files[0])} />
|
||||
) : (
|
||||
<CardContent>
|
||||
<ImageTile image={image} onImageDelete={onImageDelete} />
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
image: {
|
||||
|
@ -37,6 +38,13 @@ const useStyles = makeStyles(theme => ({
|
|||
top: 0,
|
||||
width: 148
|
||||
},
|
||||
imageOverlayShow: {
|
||||
"&$imageOverlay": {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "center"
|
||||
}
|
||||
},
|
||||
imageOverlayToolbar: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end"
|
||||
|
@ -48,18 +56,26 @@ interface ImageTileProps {
|
|||
alt?: string;
|
||||
url: string;
|
||||
};
|
||||
loading?: boolean;
|
||||
onImageDelete?: () => void;
|
||||
onImageEdit?: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const ImageTile: React.FC<ImageTileProps> = props => {
|
||||
const { onImageDelete, onImageEdit, image } = props;
|
||||
const { loading, onImageDelete, onImageEdit, image } = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<div className={classes.imageContainer} data-tc="product-image">
|
||||
<div className={classes.imageOverlay}>
|
||||
<div
|
||||
className={classNames(classes.imageOverlay, {
|
||||
[classes.imageOverlayShow]: loading
|
||||
})}
|
||||
>
|
||||
{loading ? (
|
||||
<CircularProgress size={32} />
|
||||
) : (
|
||||
<div className={classes.imageOverlayToolbar}>
|
||||
{onImageEdit && (
|
||||
<IconButton color="primary" onClick={onImageEdit}>
|
||||
|
@ -72,6 +88,7 @@ const ImageTile: React.FC<ImageTileProps> = props => {
|
|||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<img className={classes.image} src={image.url} alt={image.alt} />
|
||||
</div>
|
||||
|
|
|
@ -14,11 +14,11 @@ interface ImageUploadProps {
|
|||
isActiveClassName?: string;
|
||||
iconContainerClassName?: string;
|
||||
iconContainerActiveClassName?: string;
|
||||
onImageUpload: (file: File) => void;
|
||||
onImageUpload: (file: FileList) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
containerDragActive: {
|
||||
backdrop: {
|
||||
background: fade(theme.palette.primary.main, 0.1),
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
|
@ -57,41 +57,37 @@ const useStyles = makeStyles(theme => ({
|
|||
export const ImageUpload: React.FC<ImageUploadProps> = props => {
|
||||
const {
|
||||
children,
|
||||
|
||||
className,
|
||||
disableClick,
|
||||
isActiveClassName,
|
||||
iconContainerActiveClassName,
|
||||
iconContainerClassName,
|
||||
isActiveClassName,
|
||||
onImageUpload
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
|
||||
return (
|
||||
<Dropzone
|
||||
disableClick={disableClick}
|
||||
onDrop={files => onImageUpload(files[0])}
|
||||
>
|
||||
<Dropzone disableClick={disableClick} onDrop={onImageUpload}>
|
||||
{({ isDragActive, getInputProps, getRootProps }) => (
|
||||
<>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={classNames({
|
||||
[classes.photosIconContainer]: true,
|
||||
[classes.containerDragActive]: isDragActive,
|
||||
[className]: !!className,
|
||||
[isActiveClassName]: !!isActiveClassName && isDragActive
|
||||
className={classNames(className, classes.photosIconContainer, {
|
||||
[classes.backdrop]: isDragActive,
|
||||
[isActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames({
|
||||
[iconContainerClassName]: !!iconContainerClassName,
|
||||
[iconContainerActiveClassName]:
|
||||
!!iconContainerActiveClassName && isDragActive
|
||||
className={classNames(iconContainerClassName, {
|
||||
[iconContainerActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<input {...getInputProps()} className={classes.fileField} />
|
||||
<input
|
||||
{...getInputProps()}
|
||||
className={classes.fileField}
|
||||
accept="image/*"
|
||||
/>
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText}>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -7,6 +7,7 @@ import ImageTile from "@saleor/components/ImageTile";
|
|||
import ImageUpload from "@saleor/components/ImageUpload";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import createMultiFileUploadHandler from "@saleor/utils/handlers/multiFileUploadHandler";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -72,6 +73,7 @@ const useStyles = makeStyles(theme => ({
|
|||
imageUpload: {
|
||||
height: "100%",
|
||||
left: 0,
|
||||
outline: 0,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
width: "100%"
|
||||
|
@ -134,13 +136,14 @@ const SortableImage = SortableElement<SortableImageProps>(
|
|||
|
||||
interface ImageListContainerProps {
|
||||
className: string;
|
||||
items: any;
|
||||
items: ProductDetails_product_images[];
|
||||
preview: ProductDetails_product_images[];
|
||||
onImageDelete: (id: string) => () => void;
|
||||
onImageEdit: (id: string) => () => void;
|
||||
}
|
||||
|
||||
const ImageListContainer = SortableContainer<ImageListContainerProps>(
|
||||
({ items, onImageDelete, onImageEdit, ...props }) => (
|
||||
({ items, preview, onImageDelete, onImageEdit, ...props }) => (
|
||||
<div {...props}>
|
||||
{items.map((image, index) => (
|
||||
<SortableImage
|
||||
|
@ -151,6 +154,11 @@ const ImageListContainer = SortableContainer<ImageListContainerProps>(
|
|||
onImageDelete={onImageDelete(image.id)}
|
||||
/>
|
||||
))}
|
||||
{preview
|
||||
.sort((a, b) => (a.sortOrder > b.sortOrder ? 1 : -1))
|
||||
.map(image => (
|
||||
<ImageTile loading={true} image={image} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
@ -169,6 +177,32 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
const upload = React.useRef(null);
|
||||
const [imagesToUpload, setImagesToUpload] = React.useState<
|
||||
ProductDetails_product_images[]
|
||||
>([]);
|
||||
|
||||
const handleImageUpload = createMultiFileUploadHandler(onImageUpload, {
|
||||
onAfterUpload: () =>
|
||||
setImagesToUpload(prevImagesToUpload => prevImagesToUpload.slice(1)),
|
||||
onStart: files => {
|
||||
Array.from(files).forEach((file, fileIndex) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = event => {
|
||||
setImagesToUpload(prevImagesToUpload => [
|
||||
...prevImagesToUpload,
|
||||
{
|
||||
__typename: "ProductImage",
|
||||
alt: "",
|
||||
id: "",
|
||||
sortOrder: fileIndex,
|
||||
url: event.target.result as string
|
||||
}
|
||||
]);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
|
@ -191,9 +225,11 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
<input
|
||||
className={classes.fileField}
|
||||
id="fileUpload"
|
||||
onChange={event => onImageUpload(event.target.files[0])}
|
||||
onChange={event => handleImageUpload(event.target.files)}
|
||||
multiple
|
||||
type="file"
|
||||
ref={upload}
|
||||
accept="image/*"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -215,7 +251,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
disableClick={true}
|
||||
iconContainerClassName={classes.imageUploadIcon}
|
||||
iconContainerActiveClassName={classes.imageUploadIconActive}
|
||||
onImageUpload={onImageUpload}
|
||||
onImageUpload={handleImageUpload}
|
||||
>
|
||||
{({ isDragActive }) => (
|
||||
<CardContent>
|
||||
|
@ -224,6 +260,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
helperClass="dragged"
|
||||
axis="xy"
|
||||
items={images}
|
||||
preview={imagesToUpload}
|
||||
onSortEnd={onImageReorder}
|
||||
className={classNames({
|
||||
[classes.root]: true,
|
||||
|
@ -237,7 +274,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
</ImageUpload>
|
||||
</>
|
||||
) : (
|
||||
<ImageUpload onImageUpload={onImageUpload} />
|
||||
<ImageUpload onImageUpload={handleImageUpload} />
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
@ -16634,6 +16634,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -17411,6 +17412,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -18363,6 +18365,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -18383,6 +18386,7 @@ Ctrl + K"
|
|||
class=""
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -19292,6 +19296,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -20296,6 +20301,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -21271,6 +21277,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -22873,6 +22880,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="CollectionImage-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -24156,6 +24164,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="CollectionImage-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -25352,6 +25361,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="CollectionImage-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -27476,6 +27486,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="CollectionImage-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -27496,6 +27507,7 @@ Ctrl + K"
|
|||
class=""
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -28217,6 +28229,7 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="CollectionImage-fileField-id"
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
|
@ -28237,6 +28250,7 @@ Ctrl + K"
|
|||
class=""
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -93010,8 +93024,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -93026,13 +93042,14 @@ Ctrl + K"
|
|||
class="makeStyles-imageGridContainer-id"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-photosIconContainer-id makeStyles-imageUpload-id"
|
||||
class="makeStyles-imageUpload-id makeStyles-photosIconContainer-id"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-imageUploadIcon-id"
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -94532,8 +94549,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -94548,13 +94567,14 @@ Ctrl + K"
|
|||
class="makeStyles-imageGridContainer-id"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-photosIconContainer-id makeStyles-imageUpload-id"
|
||||
class="makeStyles-imageUpload-id makeStyles-photosIconContainer-id"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-imageUploadIcon-id"
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -96313,8 +96333,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -96329,13 +96351,14 @@ Ctrl + K"
|
|||
class="makeStyles-imageGridContainer-id"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-photosIconContainer-id makeStyles-imageUpload-id"
|
||||
class="makeStyles-imageUpload-id makeStyles-photosIconContainer-id"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-imageUploadIcon-id"
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -97896,8 +97919,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -99186,8 +99211,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -99209,6 +99236,7 @@ Ctrl + K"
|
|||
class=""
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
@ -100874,8 +100902,10 @@ Ctrl + K"
|
|||
</span>
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
class="makeStyles-fileField-id"
|
||||
id="fileUpload"
|
||||
multiple=""
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
|
@ -100890,13 +100920,14 @@ Ctrl + K"
|
|||
class="makeStyles-imageGridContainer-id"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-photosIconContainer-id makeStyles-imageUpload-id"
|
||||
class="makeStyles-imageUpload-id makeStyles-photosIconContainer-id"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-imageUploadIcon-id"
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
autocomplete="off"
|
||||
class="makeStyles-fileField-id"
|
||||
multiple=""
|
||||
|
|
67
src/utils/handlers/multiFileUploadHandler.test.ts
Normal file
67
src/utils/handlers/multiFileUploadHandler.test.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import createMultiFileUploadHandler from "./multiFileUploadHandler";
|
||||
|
||||
const testFiles = Array(5)
|
||||
.fill(0)
|
||||
.map(() => new File([""], "mockFile"));
|
||||
|
||||
describe("Multiple file upload handler", () => {
|
||||
it("properly handles success", done => {
|
||||
const cbs = {
|
||||
onAfterUpload: jest.fn(),
|
||||
onBeforeUpload: jest.fn(),
|
||||
onCompleted: jest.fn(files =>
|
||||
expect(files.length).toBe(testFiles.length)
|
||||
),
|
||||
onError: jest.fn(),
|
||||
onStart: jest.fn()
|
||||
};
|
||||
const handle = createMultiFileUploadHandler(() => {
|
||||
const promise = new Promise(resolve => {
|
||||
expect(cbs.onBeforeUpload).toBeCalledTimes(
|
||||
cbs.onAfterUpload.mock.calls.length + 1
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
return promise;
|
||||
}, cbs);
|
||||
|
||||
handle((testFiles as unknown) as FileList).then(() => {
|
||||
expect(cbs.onAfterUpload).toBeCalledTimes(testFiles.length);
|
||||
expect(cbs.onBeforeUpload).toBeCalledTimes(testFiles.length);
|
||||
expect(cbs.onCompleted).toBeCalledTimes(1);
|
||||
expect(cbs.onError).toBeCalledTimes(0);
|
||||
expect(cbs.onStart).toBeCalledTimes(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("properly handles error", done => {
|
||||
const cbs = {
|
||||
onAfterUpload: jest.fn(),
|
||||
onBeforeUpload: jest.fn(),
|
||||
onCompleted: jest.fn(files =>
|
||||
expect(files.length).toBe(testFiles.length)
|
||||
),
|
||||
onError: jest.fn(),
|
||||
onStart: jest.fn()
|
||||
};
|
||||
const handle = createMultiFileUploadHandler((_, fileIndex) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
if (fileIndex === 2) {
|
||||
reject();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}, cbs);
|
||||
|
||||
handle((testFiles as unknown) as FileList).then(() => {
|
||||
expect(cbs.onAfterUpload).toBeCalledTimes(testFiles.length - 1);
|
||||
expect(cbs.onBeforeUpload).toBeCalledTimes(testFiles.length);
|
||||
expect(cbs.onCompleted).toBeCalledTimes(1);
|
||||
expect(cbs.onError).toBeCalledTimes(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
58
src/utils/handlers/multiFileUploadHandler.ts
Normal file
58
src/utils/handlers/multiFileUploadHandler.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
export type CreateMultiFileUploadHandlerCallbacks = Partial<{
|
||||
onAfterUpload: (index: number, files: File[]) => void;
|
||||
onBeforeUpload: (index: number, files: File[]) => void;
|
||||
onCompleted: (files: File[]) => void;
|
||||
onError: (index: number, files: File[]) => void;
|
||||
onStart: (files: File[]) => void;
|
||||
}>;
|
||||
|
||||
function createMultiFileUploadHandler<T>(
|
||||
upload: (file: File, fileIndex: number) => Promise<T>,
|
||||
{
|
||||
onAfterUpload,
|
||||
onBeforeUpload,
|
||||
onCompleted,
|
||||
onError,
|
||||
onStart
|
||||
}: CreateMultiFileUploadHandlerCallbacks
|
||||
) {
|
||||
async function uploadImage(files: File[], fileIndex: number): Promise<void> {
|
||||
if (files.length > fileIndex) {
|
||||
try {
|
||||
if (onBeforeUpload) {
|
||||
onBeforeUpload(fileIndex, files);
|
||||
}
|
||||
|
||||
await upload(files[fileIndex], fileIndex);
|
||||
|
||||
if (onAfterUpload) {
|
||||
onAfterUpload(fileIndex, files);
|
||||
}
|
||||
} catch (exception) {
|
||||
console.error(
|
||||
`Could not upload file #${fileIndex + 1}. Reason: ${exception}`
|
||||
);
|
||||
if (onError) {
|
||||
onError(fileIndex, files);
|
||||
}
|
||||
} finally {
|
||||
await uploadImage(files, fileIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return async (files: FileList): Promise<void> => {
|
||||
const fileArray = Array.from(files);
|
||||
|
||||
if (onStart) {
|
||||
onStart(fileArray);
|
||||
}
|
||||
|
||||
await uploadImage(fileArray, 0);
|
||||
|
||||
if (onCompleted) {
|
||||
onCompleted(fileArray);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default createMultiFileUploadHandler;
|
Loading…
Reference in a new issue