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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user