From 638d0d78f5e45571025b6951466f3bc294b92302 Mon Sep 17 00:00:00 2001 From: Ehsan Date: Fri, 25 Oct 2024 22:16:00 +0330 Subject: [PATCH] design modal crop & handle crop image --- app/component/AlertMsg.js | 35 ------------- app/component/uploadFile/Crop.js | 51 +++++++++++++++++++ app/component/uploadFile/ModalUpload.js | 29 +++++++++++ app/component/uploadFile/cropImg.js | 47 +++++++++++++++++ .../{UploadFile.js => uploadFile/index.js} | 36 +++++++++---- components/home/search/index.js | 1 - .../listMenu/generalInformation/Head.js | 7 +-- components/panel/userAccount/account/Head.js | 7 +-- mui/index.js | 1 + package-lock.json | 33 ++++++++++++ package.json | 2 + 11 files changed, 193 insertions(+), 56 deletions(-) delete mode 100644 app/component/AlertMsg.js create mode 100644 app/component/uploadFile/Crop.js create mode 100644 app/component/uploadFile/ModalUpload.js create mode 100644 app/component/uploadFile/cropImg.js rename app/component/{UploadFile.js => uploadFile/index.js} (63%) diff --git a/app/component/AlertMsg.js b/app/component/AlertMsg.js deleted file mode 100644 index 731fe08..0000000 --- a/app/component/AlertMsg.js +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { Alert, Snackbar } from "@mui/material"; -import { useState } from "react"; - -function AlertMsg() { - const [open, setOpen] = useState(true); - - const handleClick = () => { - setOpen(true); - }; - - const handleClose = (event, reason) => { - if (reason === "clickaway") { - return; - } - - setOpen(false); - }; - - return ( - - - This is a success Alert inside a Snackbar! - - - ); -} - -export default AlertMsg; diff --git a/app/component/uploadFile/Crop.js b/app/component/uploadFile/Crop.js new file mode 100644 index 0000000..ccda35f --- /dev/null +++ b/app/component/uploadFile/Crop.js @@ -0,0 +1,51 @@ +import { Button } from "@mui/material"; +import { useState } from "react"; +import Cropper from "react-easy-crop"; +import { cropImage } from "./cropImg"; + +function Crop({ data, uploadImg, handleClose }) { + const [crop, setCrop] = useState({ x: 0, y: 0 }); + const [zoom, setZoom] = useState(1); + const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); + + const onComplete = (imagePromisse) => + imagePromisse.then((image) => uploadImg(image)); + + return ( +
+
+
+ + setCroppedAreaPixels(croppedAreaPixels) + } + onZoomChange={setZoom} + /> +
+
+
+ + +
+
+ ); +} + +export default Crop; diff --git a/app/component/uploadFile/ModalUpload.js b/app/component/uploadFile/ModalUpload.js new file mode 100644 index 0000000..e2a6590 --- /dev/null +++ b/app/component/uploadFile/ModalUpload.js @@ -0,0 +1,29 @@ +import CloseModalD from "@/components/icons/CloseModalD"; +import { styleDefault } from "@/mui"; +import { Box, Modal } from "@mui/material"; +import Crop from "./Crop"; + +function ModalUpload({ open, handleClose, data, uploadImg }) { + return ( + + +
+

+ برش عکس +

+
+ +
+
+ +
+
+ ); +} + +export default ModalUpload; diff --git a/app/component/uploadFile/cropImg.js b/app/component/uploadFile/cropImg.js new file mode 100644 index 0000000..7686276 --- /dev/null +++ b/app/component/uploadFile/cropImg.js @@ -0,0 +1,47 @@ +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"); + image.src = url; + }); + +async function getCroppedImg(imageSrc, pixelCrop) { + 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)); + + canvas.width = safeArea; + canvas.height = safeArea; + + ctx.drawImage( + image, + safeArea / 2 - image.width * 0.5, + safeArea / 2 - image.height * 0.5 + ); + const data = ctx.getImageData(0, 0, safeArea, safeArea); + + canvas.width = pixelCrop.width; + canvas.height = pixelCrop.height; + + 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) + ); + + return canvas.toDataURL("image/jpeg"); +} + +export const cropImage = async (image, croppedAreaPixels, onError) => { + try { + const croppedImage = await getCroppedImg(image, croppedAreaPixels); + return croppedImage; + } catch (err) { + onError(err); + } +}; diff --git a/app/component/UploadFile.js b/app/component/uploadFile/index.js similarity index 63% rename from app/component/UploadFile.js rename to app/component/uploadFile/index.js index 7111c30..a0f3cbd 100644 --- a/app/component/UploadFile.js +++ b/app/component/uploadFile/index.js @@ -1,42 +1,58 @@ "use client"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import { Button } from "@mui/material"; import ProfileP from "@/components/icons/ProfileP"; import ArrowLeftPU from "@/components/icons/ArrowLeftPU"; import { uploadImage } from "@/helper"; +import ModalUpload from "./ModalUpload"; import Image from "next/image"; function UploadFile({ image, setImage }) { const fileRef = useRef(); + const [open, setOpen] = useState(false); + const [data, setData] = useState(); + + const handleClose = () => setOpen(false); + const handleOpen = () => setOpen(true); + const openInput = () => fileRef.current.click(); const handleChange = async (event) => { const result = await uploadImage(event); - setImage(result); + handleOpen(); + setData(result); + }; + + const uploadImg = (prop) => { + setImage(prop); + handleClose(); }; return ( <> + - {image && image.blob ? ( + {image ? ( img-uploaded ) : ( -
+
)} diff --git a/components/home/search/index.js b/components/home/search/index.js index 578a25e..4f9933d 100644 --- a/components/home/search/index.js +++ b/components/home/search/index.js @@ -54,7 +54,6 @@ function SearchBar() {
- {/* */} ); } diff --git a/components/panel/addDoctor/listMenu/generalInformation/Head.js b/components/panel/addDoctor/listMenu/generalInformation/Head.js index 32301c6..ece237c 100644 --- a/components/panel/addDoctor/listMenu/generalInformation/Head.js +++ b/components/panel/addDoctor/listMenu/generalInformation/Head.js @@ -1,11 +1,8 @@ -import UploadFile from "@/app/component/UploadFile"; +import UploadFile from "@/app/component/uploadFile"; import { useState } from "react"; function Head() { - const [image, setImage] = useState({ - file: "", - blob: "", - }); + const [image, setImage] = useState(""); return (
diff --git a/components/panel/userAccount/account/Head.js b/components/panel/userAccount/account/Head.js index 4f7dc12..a299448 100644 --- a/components/panel/userAccount/account/Head.js +++ b/components/panel/userAccount/account/Head.js @@ -1,13 +1,10 @@ "use client"; -import UploadFile from "@/app/component/UploadFile"; +import UploadFile from "@/app/component/uploadFile"; import { useState } from "react"; function Head() { - const [image, setImage] = useState({ - file: "", - blob: "", - }); + const [image, setImage] = useState(""); return (
diff --git a/mui/index.js b/mui/index.js index 8720be6..ed63d90 100644 --- a/mui/index.js +++ b/mui/index.js @@ -4,6 +4,7 @@ export const styleDefault = { left: "50%", transform: "translate(-50%, -50%)", width: "fit-content", + outline: "none", bgcolor: "#FFF", borderRadius: 2, p: 2, diff --git a/package-lock.json b/package-lock.json index b741aac..e7e1607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,9 @@ "npm": "^10.8.2", "react": "^18.0.0", "react-dom": "^18", + "react-easy-crop": "^5.1.0", "react-google-map-picker": "^1.2.3", + "react-images-uploading": "^3.1.7", "styled-components": "^6.1.11", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1", @@ -14694,6 +14696,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==" + }, "node_modules/npm": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", @@ -19346,6 +19353,19 @@ "react": "^18.3.1" } }, + "node_modules/react-easy-crop": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.1.0.tgz", + "integrity": "sha512-UsYeF/N7zoqtfOSD+2xSt1nRaoBYCI2YLkzmq+hi+aVepS4/bAMhbrLwJtDAP60jsVzWRiQCX7JG+ZtfWcHsiw==", + "dependencies": { + "normalize-wheel": "^1.0.1", + "tslib": "^2.0.1" + }, + "peerDependencies": { + "react": ">=16.4.0", + "react-dom": ">=16.4.0" + } + }, "node_modules/react-error-boundary": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", @@ -19377,6 +19397,19 @@ "react": "^17.0.0" } }, + "node_modules/react-images-uploading": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/react-images-uploading/-/react-images-uploading-3.1.7.tgz", + "integrity": "sha512-woET50eCezm645iIeP4gCoN7HjdR3T64UXC5l53yd+2vHFp+pwABH8Z/aAO5IXDeC1aP6doQ+K738L701zswAw==", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", diff --git a/package.json b/package.json index c936ffb..f09b639 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "npm": "^10.8.2", "react": "^18.0.0", "react-dom": "^18", + "react-easy-crop": "^5.1.0", "react-google-map-picker": "^1.2.3", + "react-images-uploading": "^3.1.7", "styled-components": "^6.1.11", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1",