diff --git a/.vscode/settings.json b/.vscode/settings.json
index a95220c..65a4f69 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -15,5 +15,8 @@
"./web/src/pages",
"./web/src/index.js",
"./web/src/Routes.js",
+ ],
+ "cSpell.words": [
+ "Uploader"
]
}
diff --git a/web/src/components/ImageUploader/ImageUploader.js b/web/src/components/ImageUploader/ImageUploader.js
new file mode 100644
index 0000000..39e78a4
--- /dev/null
+++ b/web/src/components/ImageUploader/ImageUploader.js
@@ -0,0 +1,121 @@
+import React, { useCallback, useState } from "react";
+import { useDropzone } from "react-dropzone";
+import Button from "@material-ui/core/Button";
+import axios from 'axios'
+import ReactCrop from 'react-image-crop'
+import { Dialog } from '@material-ui/core'
+import { Image as CloudinaryImage } from 'cloudinary-react'
+import 'react-image-crop/dist/ReactCrop.css'
+import Svg from 'src/components/Svg/Svg.js'
+
+const CLOUDINARY_UPLOAD_PRESET = "CadHub_project_images";
+const CLOUDINARY_UPLOAD_URL = "https://api.cloudinary.com/v1_1/irevdev/upload";
+
+export default function ImageUploader({ onImageUpload, imageUrl, aspectRatio, className, isEditable }) {
+ const [isModalOpen, setIsModalOpen] = useState(false)
+ const [file, setFile] = useState()
+ const [cloudinaryId, setCloudinaryId] = useState(imageUrl)
+ const [imageObj, setImageObj] = useState()
+ const [crop, setCrop] = useState({
+ aspect: aspectRatio,
+ unit: '%',
+ width: 100,
+ });
+ async function handleImageUpload() {
+ const croppedFile = await getCroppedImg(imageObj, crop, 'avatar')
+ console.log(croppedFile)
+ const imageData = new FormData();
+ imageData.append('upload_preset', CLOUDINARY_UPLOAD_PRESET);
+ imageData.append('file', croppedFile);
+ let upload = axios.post(CLOUDINARY_UPLOAD_URL, imageData)
+ try {
+ const { data } = await upload
+ if (data && data.public_id !== "") {
+ onImageUpload({cloudinaryPublicId: data.public_id})
+ setCloudinaryId(data.public_id)
+ setIsModalOpen(false)
+ }
+ } catch (e) {
+ console.error('ERROR', e)
+ }
+ }
+ // Drag and Drop
+ const onDrop = useCallback(acceptedFiles => {
+ setIsModalOpen(true)
+ const fileReader = new FileReader()
+ fileReader.onload = () => {
+ setFile(fileReader.result)
+ }
+ fileReader.readAsDataURL(acceptedFiles[0])
+ }, []);
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
+ return (
+
+
+ {cloudinaryId && isEditable &&
}
+ {isEditable &&
}
+ {(cloudinaryId || !isEditable) &&
+
+
}
+ {!cloudinaryId &&
}
+ {!cloudinaryId && isEditable &&
+
+ Drop files here ...
+ or
+ upload
+
+
+
}
+
+
+
+ );
+}
+
+function getCroppedImg(image, crop, fileName) {
+ const canvas = document.createElement('canvas');
+ const scaleX = image.naturalWidth / image.width;
+ const scaleY = image.naturalHeight / image.height;
+ canvas.width = crop.width;
+ canvas.height = crop.height;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(
+ image,
+ crop.x * scaleX,
+ crop.y * scaleY,
+ crop.width * scaleX,
+ crop.height * scaleY,
+ 0,
+ 0,
+ crop.width,
+ crop.height
+ );
+
+ // As Base64 string
+ // const base64Image = canvas.toDataURL('image/jpeg');
+
+ // As a blob
+ return new Promise((resolve, reject) => {
+ canvas.toBlob(blob => {
+ blob.name = fileName;
+ resolve(blob);
+ }, 'image/jpeg', 1);
+ });
+}
diff --git a/web/src/components/ImageUploader/ImageUploader.stories.js b/web/src/components/ImageUploader/ImageUploader.stories.js
new file mode 100644
index 0000000..f5b9f65
--- /dev/null
+++ b/web/src/components/ImageUploader/ImageUploader.stories.js
@@ -0,0 +1,42 @@
+import ImageUploader from './ImageUploader'
+
+export const generated = () => {
+ return (
+ <>
+ AspectRatio:1, no initial image, editable
+ <
+ ImageUploader
+ onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)}
+ aspectRatio={1}
+ isEditable={true}
+ className={"bg-red-400 rounded-half rounded-br-xl"}
+ />
+ AspectRatio 16:9, no initial image, editable
+ <
+ ImageUploader
+ onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)}
+ aspectRatio={16/9}
+ isEditable={true}
+ className={"bg-red-400 rounded-xl"}
+ imageUrl="CadHub/inakek2urbreynblzhgt"
+ />
+ AspectRatio:1, no initial image, NOT editable
+ <
+ ImageUploader
+ onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)}
+ aspectRatio={1}
+ className={"rounded-half rounded-br-xl"}
+ />
+ AspectRatio ,16:9 no initial image, NOT editable
+ <
+ ImageUploader
+ onImageUpload={({cloudinaryPublicId}) => console.log(cloudinaryPublicId)}
+ aspectRatio={16/9}
+ className={"rounded-xl"}
+ imageUrl="CadHub/inakek2urbreynblzhgt"
+ />
+ >
+ )
+}
+
+export default { title: 'Components/ImageUploader' }
diff --git a/web/src/components/ImageUploader/ImageUploader.test.js b/web/src/components/ImageUploader/ImageUploader.test.js
new file mode 100644
index 0000000..1a05ab3
--- /dev/null
+++ b/web/src/components/ImageUploader/ImageUploader.test.js
@@ -0,0 +1,11 @@
+import { render } from '@redwoodjs/testing'
+
+import ImageUploader from './ImageUploader'
+
+describe('ImageUploader', () => {
+ it('renders successfully', () => {
+ expect(() => {
+ render()
+ }).not.toThrow()
+ })
+})
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index c15b6f2..c49984b 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -22,6 +22,9 @@ module.exports = {
},
skew: {
'-20': "-20deg"
+ },
+ borderRadius: {
+ half: '50%',
}
}
},