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
+
+
}
+
+
+
+ );
+}
+
+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)} />
+