Commit 165f37b9 authored by Trần Ngọc Nam Anh's avatar Trần Ngọc Nam Anh

Implement Image Cropper

parent eef22d3e
...@@ -10361,6 +10361,11 @@ ...@@ -10361,6 +10361,11 @@
"sort-keys": "^1.0.0" "sort-keys": "^1.0.0"
} }
}, },
"normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU="
},
"nouislider": { "nouislider": {
"version": "14.1.1", "version": "14.1.1",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz", "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz",
...@@ -12721,6 +12726,22 @@ ...@@ -12721,6 +12726,22 @@
"scheduler": "^0.18.0" "scheduler": "^0.18.0"
} }
}, },
"react-easy-crop": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-3.5.2.tgz",
"integrity": "sha512-cwSGO/wk42XDpEyrdAcnQ6OJetVDZZO2ry1i19+kSGZQ750aN06RU9y9z95B5QI6sW3SnaWQRKv5r5GSqVV//g==",
"requires": {
"normalize-wheel": "^1.0.1",
"tslib": "2.0.1"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}
}
},
"react-error-overlay": { "react-error-overlay": {
"version": "6.0.9", "version": "6.0.9",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
......
...@@ -25,6 +25,7 @@ import { Routes } from "reoutes"; ...@@ -25,6 +25,7 @@ import { Routes } from "reoutes";
import VerifyCode from "services/VerifyCode"; import VerifyCode from "services/VerifyCode";
import ForgotPassword from "services/ForgotPassword"; import ForgotPassword from "services/ForgotPassword";
import ResetPassword from "services/ResetPassword"; import ResetPassword from "services/ResetPassword";
import Demo from "components/Generals/ImageCropper";
// import { Routes } from "./reoutes"; // import { Routes } from "./reoutes";
const RouteWithLoader = ({ component: Component, ...rest }) => { const RouteWithLoader = ({ component: Component, ...rest }) => {
...@@ -40,6 +41,7 @@ export default () => ( ...@@ -40,6 +41,7 @@ export default () => (
<Route exact path="/verify-code" component={VerifyCode} /> <Route exact path="/verify-code" component={VerifyCode} />
<Route exact path="/forgot-password" component={ForgotPassword} /> <Route exact path="/forgot-password" component={ForgotPassword} />
<Route exact path="/reset-password" component={ResetPassword} /> <Route exact path="/reset-password" component={ResetPassword} />
<Route exact path="/test" component={Demo} />
<RouteWithLoader exact path="/" component={SearchProfile} /> <RouteWithLoader exact path="/" component={SearchProfile} />
<RouteWithLoader exact path="/profile" component={SearchProfile} /> <RouteWithLoader exact path="/profile" component={SearchProfile} />
<RouteWithLoader exact path="/profile/add" component={AddProfile} /> <RouteWithLoader exact path="/profile/add" component={AddProfile} />
......
const createImage = url =>
new Promise((resolve, reject) => {
const image = new Image()
image.addEventListener('load', () => resolve(image))
image.addEventListener('error', error => reject(error))
image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox
image.src = url
})
function getRadianAngle(degreeValue) {
return (degreeValue * Math.PI) / 180
}
/**
* This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
* @param {File} image - Image File url
* @param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop
* @param {number} rotation - optional rotation parameter
*/
export default async function getCroppedImg(imageSrc, pixelCrop, rotation = 0) {
const image = await createImage(imageSrc)
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const maxSize = Math.max(image.width, image.height)
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2))
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea
canvas.height = safeArea
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2)
ctx.rotate(getRadianAngle(rotation))
ctx.translate(-safeArea / 2, -safeArea / 2)
// draw rotated image and store data.
ctx.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5
)
const data = ctx.getImageData(0, 0, safeArea, safeArea)
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width
canvas.height = pixelCrop.height
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
)
// As Base64 string
// return canvas.toDataURL('image/jpeg');
// As a blob
return new Promise(resolve => {
canvas.toBlob(file => {
resolve(URL.createObjectURL(file))
}, 'image/jpeg')
})
}
\ No newline at end of file
import Cropper from 'react-easy-crop';
import React, { useState, useCallback } from 'react';
import { Input, Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import getCroppedImg from './CropImage'
import './ImageCropper.scss'
const Demo = () => {
const [crop, setCrop] = useState({ x: 0, y: 0 })
const [zoom, setZoom] = useState(1)
const [rotation, setRotation] = useState(0);
const [image, setImage] = useState(null);
const [croppedAreaPixels, setCroppedAreaPixels] = useState(null)
const [croppedImage, setCroppedImage] = useState(null)
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
const handleFileChoose = (event) => {
setImage(URL.createObjectURL(event.target.files[0]));
toggle();
}
const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
setCroppedAreaPixels(croppedAreaPixels);
}, [])
const handleCrop = () => {
showCroppedImage()
}
const showCroppedImage = useCallback(async () => {
try {
const croppedImage = await getCroppedImg(
image,
croppedAreaPixels,
rotation
)
console.log('donee', { croppedImage })
setCroppedImage(croppedImage)
} catch (e) {
console.error(e)
}
}, [croppedAreaPixels, rotation])
return (
<>
<Input type="file" name="file" id="file-logo" className="w-auto" onChange={handleFileChoose} />
<Modal isOpen={modal} toggle={toggle} className="modal-lg" contentClassName="modal-image-cropper">
<ModalHeader toggle={toggle}>Chn phn nh mong mun</ModalHeader>
<ModalBody>
<Cropper
image={image}
crop={crop}
zoom={zoom}
aspect={1 / 1}
onCropChange={setCrop}
onCropComplete={onCropComplete}
onZoomChange={setZoom}
/>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={handleCrop}>Xác nhn</Button>{' '}
<Button color="secondary" onClick={toggle}>Quay li</Button>
</ModalFooter>
</Modal>
</>
)
}
export default Demo;
\ No newline at end of file
.modal-image-cropper{
height: 80vh;
}
\ No newline at end of file
...@@ -5,5 +5,6 @@ export const Routes = { ...@@ -5,5 +5,6 @@ export const Routes = {
Signup: { path: "./signup" }, Signup: { path: "./signup" },
ForgotPassword: { path: "./forgot-password" }, ForgotPassword: { path: "./forgot-password" },
ResetPassword: {path: "./reset-password"}, ResetPassword: {path: "./reset-password"},
Test: {path: "./Test"},
Home: { path: "" } Home: { path: "" }
} }
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment