diff --git a/web/package.json b/web/package.json index db3c665..a9942bc 100644 --- a/web/package.json +++ b/web/package.json @@ -18,6 +18,7 @@ "@redwoodjs/forms": "^0.19.2", "@redwoodjs/router": "^0.19.2", "@redwoodjs/web": "^0.19.2", + "cloudinary-react": "^1.6.7", "controlkit": "^0.1.9", "golden-layout": "^1.5.9", "jquery": "^3.5.1", @@ -28,6 +29,8 @@ "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-dropzone": "^11.2.1", + "react-image-crop": "^8.6.6", "rich-markdown-editor": "^11.0.2", "styled-components": "^5.2.0", "three": "^0.118.3" diff --git a/web/src/cascade b/web/src/cascade index 62f9612..e634591 160000 --- a/web/src/cascade +++ b/web/src/cascade @@ -1 +1 @@ -Subproject commit 62f961293d72558e59cdcbe0707ef15a06d30c12 +Subproject commit e634591e27dd41fec1638b278be3c298c6ab4b5a diff --git a/web/src/components/PartForm/ImageUploader.js b/web/src/components/PartForm/ImageUploader.js new file mode 100644 index 0000000..4809ec0 --- /dev/null +++ b/web/src/components/PartForm/ImageUploader.js @@ -0,0 +1,119 @@ +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/?custom_coordinates=10,10,20,20"; + +export default function ImageUploader({ onImageUpload, imageUrl }) { + const [isModalOpen, setIsModalOpen] = useState(false) + const [file, setFile] = useState() + const [cloudinaryId, setCloudinaryId] = useState(imageUrl) + const [imageObj, setImageObj] = useState() + const [crop, setCrop] = useState({ + aspect: 16 / 9, + 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 && } + + {cloudinaryId &&
+ +
} + {!cloudinaryId && } + {!cloudinaryId &&
+ 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/PartForm/PartForm.js b/web/src/components/PartForm/PartForm.js index c300d8c..daecc16 100644 --- a/web/src/components/PartForm/PartForm.js +++ b/web/src/components/PartForm/PartForm.js @@ -4,23 +4,26 @@ import { FieldError, Label, TextField, - TextAreaField, Submit, } from '@redwoodjs/forms' import { useState } from 'react'; import { navigate, routes } from '@redwoodjs/router' import { useFlash } from '@redwoodjs/web' +import ImageUploader from './ImageUploader.js' + import Editor from "rich-markdown-editor"; const PartForm = (props) => { const { addMessage } = useFlash() const [description, setDescription] = useState(props?.part?.description) + const [imageUrl, setImageUrl] = useState(props?.part?.mainImage) const onSubmit = async (data, e) => { await props.onSave({ ...data, description, + mainImage: imageUrl }, props?.part?.id) const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde if(shouldOpenIde) { @@ -57,21 +60,8 @@ const PartForm = (props) => { /> - - - + setImageUrl(cloudinaryPublicId)} /> +