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 + +
+
} +
+ setIsModalOpen(false)} + > +
+ setImageObj(image)} onChange={newCrop => setCrop(newCrop)} /> + +
+
+
+ ); +} + +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%', } } },