From a9ac43c06074b756b8e3c7e9df968ff2a2450bbb Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Sun, 25 Oct 2020 19:56:58 +1100 Subject: [PATCH 1/3] Update Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94e0d0a..7a934b9 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ yarn rw dev Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/.redwood/functions/*`. -You may need to register a account depending on what issue you are trying to tackle, This can be done by clicking the login button on the top right. +You may need to register a account depending on what issue you are trying to tackle, This can be done by clicking the login button on the top right. This will open up netlify's idenitiy modal asking for the websites url, since it will notice you developing locally. Enter `https://cadhub.xyz/` than use you email, verify your email and you should be set. (some routes are protected, but permissions is a big area that needs a lot of work in the near future, so it's in a very incomplete state atm) ### Note: -- 2.39.5 From c3c472d4d7a4681113a193fca0939311f2386bb2 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Mon, 26 Oct 2020 17:55:17 +1100 Subject: [PATCH 2/3] Add crop and upload to cloudinary --- web/package.json | 3 + web/src/cascade | 2 +- web/src/components/PartForm/ImageUploader.js | 105 +++++++++++++++++++ web/src/components/PartForm/PartForm.js | 5 +- yarn.lock | 52 ++++++++- 5 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 web/src/components/PartForm/ImageUploader.js 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..476293f --- /dev/null +++ b/web/src/components/PartForm/ImageUploader.js @@ -0,0 +1,105 @@ +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 'react-image-crop/dist/ReactCrop.css' + +const CLOUDINARY_UPLOAD_PRESET = process.env.GATSBY_PROD_PRESET || "dev_preset"; +const CLOUDINARY_UPLOAD_URL = "https://api.cloudinary.com/v1_1/irevdev/upload"; + +export default function ImageUploader({ onImageUpload }) { + const [isModalOpen, setIsModalOpen] = useState(false) + const [file, setFile] = useState() + const [crop, setCrop] = useState({ + aspect: 16 / 9, + unit: '%', + width: 100, + }); + async function handleImageUpload() { + var image = new Image(); + image.src = file + const croppedFile = await getCroppedImg(image, 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}) + } + } 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 ( +
+
+ + {/* */} + +
+ Drop files here ... + or + upload + +
+
+ setIsModalOpen(false)} + > +
+ 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..b5aa2cd 100644 --- a/web/src/components/PartForm/PartForm.js +++ b/web/src/components/PartForm/PartForm.js @@ -4,12 +4,13 @@ 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"; @@ -34,6 +35,8 @@ const PartForm = (props) => { return (
+ {console.log('yo', yo)}} /> + Date: Mon, 26 Oct 2020 20:55:16 +1100 Subject: [PATCH 3/3] Integrate image uploader with new part and make image editable that is to say you can easily pick another image if you didn't like the first. --- web/src/components/PartForm/ImageUploader.js | 44 +++++++++++++------- web/src/components/PartForm/PartForm.js | 21 ++-------- web/src/components/Parts/Parts.js | 13 +++++- web/src/components/Svg/Svg.js | 9 ++-- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/web/src/components/PartForm/ImageUploader.js b/web/src/components/PartForm/ImageUploader.js index 476293f..4809ec0 100644 --- a/web/src/components/PartForm/ImageUploader.js +++ b/web/src/components/PartForm/ImageUploader.js @@ -4,23 +4,25 @@ 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 = process.env.GATSBY_PROD_PRESET || "dev_preset"; -const CLOUDINARY_UPLOAD_URL = "https://api.cloudinary.com/v1_1/irevdev/upload"; +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 }) { +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() { - var image = new Image(); - image.src = file - const croppedFile = await getCroppedImg(image, crop, 'avatar') + const croppedFile = await getCroppedImg(imageObj, crop, 'avatar') console.log(croppedFile) const imageData = new FormData(); imageData.append('upload_preset', CLOUDINARY_UPLOAD_PRESET); @@ -30,6 +32,8 @@ export default function ImageUploader({ onImageUpload }) { 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) @@ -47,24 +51,35 @@ export default function ImageUploader({ onImageUpload }) { const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }); return ( -
+
+ {cloudinaryId && } - {/* */} - -
+ {cloudinaryId &&
+ +
} + {!cloudinaryId && } + {!cloudinaryId &&
Drop files here ... - or + or upload -
+
}
setIsModalOpen(false)} >
- setCrop(newCrop)} /> + setImageObj(image)} onChange={newCrop => setCrop(newCrop)} />
@@ -79,7 +94,6 @@ function getCroppedImg(image, crop, fileName) { canvas.width = crop.width; canvas.height = crop.height; const ctx = canvas.getContext('2d'); - ctx.drawImage( image, crop.x * scaleX, @@ -89,7 +103,7 @@ function getCroppedImg(image, crop, fileName) { 0, 0, crop.width, - crop.height, + crop.height ); // As Base64 string diff --git a/web/src/components/PartForm/PartForm.js b/web/src/components/PartForm/PartForm.js index b5aa2cd..daecc16 100644 --- a/web/src/components/PartForm/PartForm.js +++ b/web/src/components/PartForm/PartForm.js @@ -17,11 +17,13 @@ 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) { @@ -35,8 +37,6 @@ const PartForm = (props) => { return (
- {console.log('yo', yo)}} /> - { /> - - - + setImageUrl(cloudinaryPublicId)} /> +