design modal crop & handle crop image

This commit is contained in:
Ehsan
2024-10-25 22:16:00 +03:30
parent e874284bb6
commit 638d0d78f5
11 changed files with 193 additions and 56 deletions
-35
View File
@@ -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 (
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert
onClose={handleClose}
severity="success"
variant="filled"
sx={{ width: "100%" }}
>
This is a success Alert inside a Snackbar!
</Alert>
</Snackbar>
);
}
export default AlertMsg;
+51
View File
@@ -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 (
<div className="mt-[16px]">
<div className="px-[24px]">
<div className="relative w-[424px] h-[380px] rounded-[8px] overflow-hidden">
<Cropper
crop={crop}
zoom={zoom}
aspect={4 / 3}
image={data.blob}
onCropChange={setCrop}
onCropComplete={(_, croppedAreaPixels) =>
setCroppedAreaPixels(croppedAreaPixels)
}
onZoomChange={setZoom}
/>
</div>
</div>
<div className="flex items-center justify-end gap-[16px] mt-[16px]">
<Button
variant="outlined"
onClick={handleClose}
className="!text-[16px] !max-w-[calc(50%_-_8px)] dark:!bg-[#C7CEF4] dark:!text-[#5559CE] !font-medium !border-[#5559CE] !py-[5px] md:!py-[7px] lg:!py-[9px] !shadow-none"
>
انصراف
</Button>
<Button
variant="contained"
onClick={() => onComplete(cropImage(data.blob, croppedAreaPixels))}
className="!text-[16px] !max-w-[calc(50%_-_8px)] !font-medium !shadow-none !py-[5px] md:!py-[7px] lg:!py-[9px]"
>
ذخیره
</Button>
</div>
</div>
);
}
export default Crop;
+29
View File
@@ -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 (
<Modal open={open} onClose={handleClose} className="!hidden md:!flex">
<Box
sx={styleDefault}
className="!pt-[12px] md:!pt-[14px] lg:!py-[16px]
dark:!bg-[#222433] dark:!shadow-none !px-[12px] sm:!px-[16px] md:!px-[20px] lg:!px-[24px]
!pb-[12px] sm:!pb-[16px] lg:!pb-[20px] dark:!border-transparent"
>
<div className="flex items-center justify-between">
<p className="text-[#3B3B3B] dark:text-[#DBDBDB] text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-medium">
برش عکس
</p>
<div className="cursor-pointer" onClick={handleClose}>
<CloseModalD />
</div>
</div>
<Crop data={data} uploadImg={uploadImg} handleClose={handleClose} />
</Box>
</Modal>
);
}
export default ModalUpload;
+47
View File
@@ -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);
}
};
@@ -1,42 +1,58 @@
"use client"; "use client";
import { useRef } from "react"; import { useRef, useState } from "react";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import ProfileP from "@/components/icons/ProfileP"; import ProfileP from "@/components/icons/ProfileP";
import ArrowLeftPU from "@/components/icons/ArrowLeftPU"; import ArrowLeftPU from "@/components/icons/ArrowLeftPU";
import { uploadImage } from "@/helper"; import { uploadImage } from "@/helper";
import ModalUpload from "./ModalUpload";
import Image from "next/image"; import Image from "next/image";
function UploadFile({ image, setImage }) { function UploadFile({ image, setImage }) {
const fileRef = useRef(); 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 openInput = () => fileRef.current.click();
const handleChange = async (event) => { const handleChange = async (event) => {
const result = await uploadImage(event); const result = await uploadImage(event);
setImage(result); handleOpen();
setData(result);
};
const uploadImg = (prop) => {
setImage(prop);
handleClose();
}; };
return ( return (
<> <>
<ModalUpload
open={open}
data={data}
uploadImg={uploadImg}
handleClose={handleClose}
/>
<input <input
className="contents"
ref={fileRef}
type="file" type="file"
ref={fileRef}
className="contents"
onChange={handleChange} onChange={handleChange}
/> />
{image && image.blob ? ( {image ? (
<Image <Image
width={24} width={24}
height={24} height={24}
src={image.blob} src={image}
alt="img-uploaded" alt="img-uploaded"
className="w-[74px] h-[74px] rounded-full overflow-hidden object-cover" className="w-[74px] h-[74px] rounded-full overflow-hidden object-cover"
/> />
) : ( ) : (
<div <div className="p-[24px] grid place-items-center border border-solid border-[#EFEFEF] bg-[#EFEFEF] rounded-full">
className="p-[24px] grid cursor-pointer place-items-center border border-solid border-[#EFEFEF] bg-[#EFEFEF] rounded-full"
onClick={openInput}
>
<ProfileP /> <ProfileP />
</div> </div>
)} )}
-1
View File
@@ -54,7 +54,6 @@ function SearchBar() {
<SearchD /> <SearchD />
</div> </div>
</Button> </Button>
{/* <AlertMsg /> */}
</ButtonGroup> </ButtonGroup>
); );
} }
@@ -1,11 +1,8 @@
import UploadFile from "@/app/component/UploadFile"; import UploadFile from "@/app/component/uploadFile";
import { useState } from "react"; import { useState } from "react";
function Head() { function Head() {
const [image, setImage] = useState({ const [image, setImage] = useState("");
file: "",
blob: "",
});
return ( return (
<div className="flex items-center justify-center md:justify-start gap-[10px]"> <div className="flex items-center justify-center md:justify-start gap-[10px]">
+2 -5
View File
@@ -1,13 +1,10 @@
"use client"; "use client";
import UploadFile from "@/app/component/UploadFile"; import UploadFile from "@/app/component/uploadFile";
import { useState } from "react"; import { useState } from "react";
function Head() { function Head() {
const [image, setImage] = useState({ const [image, setImage] = useState("");
file: "",
blob: "",
});
return ( return (
<div className="flex items-center justify-center md:justify-start gap-[10px]"> <div className="flex items-center justify-center md:justify-start gap-[10px]">
+1
View File
@@ -4,6 +4,7 @@ export const styleDefault = {
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: "fit-content", width: "fit-content",
outline: "none",
bgcolor: "#FFF", bgcolor: "#FFF",
borderRadius: 2, borderRadius: 2,
p: 2, p: 2,
+33
View File
@@ -28,7 +28,9 @@
"npm": "^10.8.2", "npm": "^10.8.2",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18", "react-dom": "^18",
"react-easy-crop": "^5.1.0",
"react-google-map-picker": "^1.2.3", "react-google-map-picker": "^1.2.3",
"react-images-uploading": "^3.1.7",
"styled-components": "^6.1.11", "styled-components": "^6.1.11",
"stylis": "^4.3.4", "stylis": "^4.3.4",
"stylis-plugin-rtl": "^2.1.1", "stylis-plugin-rtl": "^2.1.1",
@@ -14694,6 +14696,11 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/npm": {
"version": "10.8.2", "version": "10.8.2",
"resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz",
@@ -19346,6 +19353,19 @@
"react": "^18.3.1" "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": { "node_modules/react-error-boundary": {
"version": "3.1.4", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
@@ -19377,6 +19397,19 @@
"react": "^17.0.0" "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": { "node_modules/react-is": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+2
View File
@@ -29,7 +29,9 @@
"npm": "^10.8.2", "npm": "^10.8.2",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18", "react-dom": "^18",
"react-easy-crop": "^5.1.0",
"react-google-map-picker": "^1.2.3", "react-google-map-picker": "^1.2.3",
"react-images-uploading": "^3.1.7",
"styled-components": "^6.1.11", "styled-components": "^6.1.11",
"stylis": "^4.3.4", "stylis": "^4.3.4",
"stylis-plugin-rtl": "^2.1.1", "stylis-plugin-rtl": "^2.1.1",