From a2f7cdd6b26e21f01cc1cfd1d595772ff1a9dc69 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 28 Nov 2019 16:17:39 +0100 Subject: [PATCH] Add multiple image upload --- src/components/ImageTile/ImageTile.tsx | 51 ++++++++++++------ src/components/ImageUpload/ImageUpload.tsx | 32 +++++------ .../ProductImages/ProductImages.tsx | 47 ++++++++++++++-- src/utils/handlers/multiFileUploadHandler.ts | 54 +++++++++++++++++++ 4 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 src/utils/handlers/multiFileUploadHandler.ts diff --git a/src/components/ImageTile/ImageTile.tsx b/src/components/ImageTile/ImageTile.tsx index 9e71294a5..5d9312f22 100644 --- a/src/components/ImageTile/ImageTile.tsx +++ b/src/components/ImageTile/ImageTile.tsx @@ -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,30 +56,39 @@ interface ImageTileProps { alt?: string; url: string; }; + loading?: boolean; onImageDelete?: () => void; onImageEdit?: (event: React.ChangeEvent) => void; } const ImageTile: React.FC = props => { - const { onImageDelete, onImageEdit, image } = props; + const { loading, onImageDelete, onImageEdit, image } = props; const classes = useStyles(props); return (
-
-
- {onImageEdit && ( - - - - )} - {onImageDelete && ( - - - - )} -
+
+ {loading ? ( + + ) : ( +
+ {onImageEdit && ( + + + + )} + {onImageDelete && ( + + + + )} +
+ )}
{image.alt}
diff --git a/src/components/ImageUpload/ImageUpload.tsx b/src/components/ImageUpload/ImageUpload.tsx index 5216a0013..070ecc974 100644 --- a/src/components/ImageUpload/ImageUpload.tsx +++ b/src/components/ImageUpload/ImageUpload.tsx @@ -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 = props => { const { children, - className, disableClick, - isActiveClassName, iconContainerActiveClassName, iconContainerClassName, + isActiveClassName, onImageUpload } = props; const classes = useStyles(props); return ( - onImageUpload(files[0])} - > + {({ isDragActive, getInputProps, getRootProps }) => ( <>
- + ({ imageUpload: { height: "100%", left: 0, + outline: 0, position: "absolute", top: 0, width: "100%" @@ -134,13 +136,14 @@ const SortableImage = SortableElement( 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( - ({ items, onImageDelete, onImageEdit, ...props }) => ( + ({ items, preview, onImageDelete, onImageEdit, ...props }) => (
{items.map((image, index) => ( ( onImageDelete={onImageDelete(image.id)} /> ))} + {preview + .sort((a, b) => (a.sortOrder > b.sortOrder ? 1 : -1)) + .map(image => ( + + ))}
) ); @@ -169,6 +177,32 @@ const ProductImages: React.FC = 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 ( @@ -191,9 +225,11 @@ const ProductImages: React.FC = props => { 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 = props => { disableClick={true} iconContainerClassName={classes.imageUploadIcon} iconContainerActiveClassName={classes.imageUploadIconActive} - onImageUpload={onImageUpload} + onImageUpload={handleImageUpload} > {({ isDragActive }) => ( @@ -224,6 +260,7 @@ const ProductImages: React.FC = props => { helperClass="dragged" axis="xy" items={images} + preview={imagesToUpload} onSortEnd={onImageReorder} className={classNames({ [classes.root]: true, @@ -237,7 +274,7 @@ const ProductImages: React.FC = props => { ) : ( - + )}
diff --git a/src/utils/handlers/multiFileUploadHandler.ts b/src/utils/handlers/multiFileUploadHandler.ts new file mode 100644 index 000000000..b52591243 --- /dev/null +++ b/src/utils/handlers/multiFileUploadHandler.ts @@ -0,0 +1,54 @@ +type CreateMultiFileUploadHandlerCallbacks = Partial<{ + onAfterUpload: (index: number, all: number) => void; + onBeforeUpload: (index: number, all: number) => void; + onCompleted: (files: FileList) => void; + onError: (index: number, all: number) => void; + onStart: (files: FileList) => void; +}>; + +export function createMultiFileUploadHandler( + upload: (file: File) => Promise, + { + onAfterUpload, + onBeforeUpload, + onCompleted, + onError, + onStart + }: CreateMultiFileUploadHandlerCallbacks +) { + async function uploadImage(files: FileList, fileIndex: number) { + if (files.length > fileIndex) { + try { + if (onBeforeUpload) { + onBeforeUpload(fileIndex, files.length); + } + + await upload(files[fileIndex]); + + if (onAfterUpload) { + onAfterUpload(fileIndex, files.length); + } + } catch (exception) { + console.error( + `Could not upload file #${fileIndex + 1}. Reason: ${exception}` + ); + if (onError) { + onError(fileIndex, files.length); + } + } finally { + await uploadImage(files, fileIndex + 1); + } + } + } + return async (files: FileList) => { + if (onStart) { + onStart(files); + } + + await uploadImage(files, 0); + + if (onCompleted) { + onCompleted(files); + } + }; +}