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.
This commit is contained in:
Kurt Hutten
2020-10-26 20:55:16 +11:00
parent c3c472d4d7
commit f3be6de7bd
4 changed files with 50 additions and 37 deletions

View File

@@ -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 (
<div>
<div className="m-8">
<div className="w-full relative" {...getRootProps()}>
{cloudinaryId && <button className="absolute z-10 w-full inset-0 bg-indigo-900 opacity-50 flex justify-center items-center">
<Svg name="pencil" strokeWidth={2} className="text-gray-300 h-48 w-48" />
</button>}
<input {...getInputProps()} />
{/* <Button className variant="outlined">Upload</Button> */}
<button className="absolute inset-0"></button>
<div className="mt-3 text-indigo-500 border-dashed border border-indigo-500 py-8 text-center rounded-lg w-full">
{cloudinaryId && <div className="relative">
<CloudinaryImage
className="object-cover w-full rounded shadow"
cloudName="irevdev"
publicId={cloudinaryId}
width="600"
crop="scale"
/>
</div>}
{!cloudinaryId && <button className="absolute inset-0"></button>}
{!cloudinaryId && <div className="mt-3 text-indigo-500 border-dashed border border-indigo-500 py-8 text-center rounded-lg w-full">
Drop files here ...
or <span className="group flex w-full items-center justify-center">
or <span className="group flex w-full items-center justify-center py-4">
<span className="bg-indigo-500 shadow rounded text-gray-200 cursor-pointer p-2 hover:shadow-lg transform hover:-translate-y-1 transition-all duration-150">upload</span>
</span>
</div>
</div>}
</div>
<Dialog
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
>
<div className="p-4">
<ReactCrop src={file} crop={crop} onChange={newCrop => setCrop(newCrop)} />
<ReactCrop src={file} crop={crop} onImageLoaded={(image) => setImageObj(image)} onChange={newCrop => setCrop(newCrop)} />
<Button onClick={handleImageUpload} variant="outlined">Upload</Button>
</div>
</Dialog>
@@ -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

View File

@@ -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 (
<div className="max-w-7xl mx-auto mt-10">
<Form onSubmit={onSubmit} error={props.error}>
<ImageUploader onImageUpload={(yo) => {console.log('yo', yo)}} />
<FormError
error={props.error}
wrapperClassName="rw-form-error-wrapper"
@@ -60,21 +60,8 @@ const PartForm = (props) => {
/>
<FieldError name="title" className="rw-field-error" />
<Label
name="mainImage"
className="p-0"
errorClassName="rw-label rw-label-error"
>
Main image
</Label>
<TextField
name="mainImage"
defaultValue={props.part?.mainImage}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: false }}
/>
<FieldError name="mainImage" className="rw-field-error" />
<ImageUploader onImageUpload={({cloudinaryPublicId}) => setImageUrl(cloudinaryPublicId)} />
<Label
name="description"

View File

@@ -1,5 +1,6 @@
import { useMutation, useFlash } from '@redwoodjs/web'
import { Link, routes } from '@redwoodjs/router'
import { Image as CloudinaryImage } from 'cloudinary-react'
import avatar from 'src/assets/harold.jpg'
@@ -64,11 +65,19 @@ const PartsList = ({ parts }) => {
<div className="rounded-t-2xl bg-gray-900">
<div className="flex items-center p-2 text-indigo-200">
<div className="h-full absolute inset-0 text-6xl flex items-center justify-center text-indigo-700" ><span>?</span></div>
<div className="mr-4"><img src={avatar} className="rounded-full h-10 w-10" /></div>
<div className="mr-4">
<img src={avatar} className="rounded-full h-10 w-10" />
</div>
<h3>{part.title}</h3>
</div>
<div className="relative z-10">
<img className="h-full" src={part.mainImage}/>
<CloudinaryImage
className="object-cover w-full rounded shadow"
cloudName="irevdev"
publicId={part.mainImage}
width="300"
crop="scale"
/>
</div>
</div>
</Link>

View File

@@ -1,12 +1,15 @@
const Svg = ({name, className: className2}) => {
const Svg = ({name, className: className2, strokeWidth = 2}) => {
const svgs = {
"plus-circle": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>,
"plus":<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>,
"pencil": <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={strokeWidth} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
}
return <div className={"h-10 w-10 " + className2}>