frontPanel/src/ui_kit/Modal/CroppingModal.tsx
2023-08-17 12:06:32 +03:00

346 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { FC, useEffect, useRef, useState } from "react";
import { saveAs } from "file-saver";
import ReactCrop, { Crop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
import {
Box,
Button,
Modal,
Slider,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import quiz from "../../assets/quiz-template-6.png";
import { ResetIcon } from "@icons/ResetIcon";
interface Iprops {
opened: boolean;
onClose: () => void;
picture?: string | ArrayBuffer;
}
export const CroppingModal: FC<Iprops> = ({ opened, onClose, picture }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(786));
const style = {
position: "absolute" as "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: isMobile ? "343px" : "620px",
bgcolor: "background.paper",
boxShadow: 24,
padding: "20px",
borderRadius: "8px",
};
const styleSlider = {
width: isMobile ? "350px" : "250px",
color: "#7E2AEA",
height: "12px",
"& .MuiSlider-track": {
border: "none",
},
"& .MuiSlider-rail": {
backgroundColor: "#F2F3F7",
border: `1px solid "#9A9AAF"`,
},
"& .MuiSlider-thumb": {
height: 26,
width: 26,
border: `6px solid #7E2AEA`,
backgroundColor: "white",
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
},
},
};
const [src, setSrc] = useState<string | ArrayBuffer | null>(quiz);
const [crop, setCrop] = useState<Crop>({
unit: "px",
y: 0,
x: 0,
width: 100,
height: 100,
});
const [completedCrop, setCompletedCrop] = useState<Crop | null>(null);
const [imageSize, setImageSize] = useState(580);
const [darken, setDarken] = useState(0);
const fileInputRef = useRef<HTMLInputElement>(null);
console.log(src);
useEffect(() => {
if (picture) {
setSrc(picture);
}
}, [picture]);
const onCropComplete = (crop: Crop) => {
setCompletedCrop(crop);
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target && event.target.result) {
setSrc(event.target.result);
}
};
reader.readAsDataURL(file);
}
};
const handleDownloadClick = async () => {
if (completedCrop && src) {
const croppedImageUrl = await getCroppedAndDarkenedImg(
src,
completedCrop,
"cropped.jpeg",
darken
);
saveAs(croppedImageUrl, "cropped-image.jpeg");
}
};
const getCroppedAndDarkenedImg = (
image: string | ArrayBuffer,
crop: Crop,
fileName: string,
darken: number
): Promise<string> => {
const img = new Image();
img.src = image as string;
let scaleX = 360 / 580;
let scaleY = 219 / 320;
if (img.naturalWidth) {
scaleX = img.naturalWidth / 580;
}
if (img.naturalHeight) {
scaleY = img.naturalHeight / 320;
}
console.log(scaleX);
console.log(scaleY);
const canvas = document.createElement("canvas");
canvas.width = crop.width!;
canvas.height = crop.height!;
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("Canvas context is null");
}
ctx.drawImage(
img,
crop.x! * scaleX,
crop.y! * scaleY,
crop.width! * scaleX,
crop.height! * scaleY,
0,
0,
crop.width!,
crop.height!
);
const imageData = ctx.getImageData(0, 0, crop.width!, crop.height!);
const newImageData = imageData.data.map((value, index) => {
if ((index + 1) % 4 === 0) {
return value;
}
return value * (1 - darken / 100);
});
imageData.data.set(newImageData);
ctx.putImageData(imageData, 0, 0);
return new Promise<string>((resolve, reject) => {
canvas.toBlob((blob) => {
if (!blob) {
reject(new Error("Canvas is empty"));
return;
}
const file = new File([blob], fileName, { type: "image/jpeg" });
const imageUrl = window.URL.createObjectURL(file);
resolve(imageUrl);
}, "image/jpeg");
});
};
return (
<Modal
open={opened}
onClose={onClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Box
sx={{
height: "320px",
padding: "10px",
backgroundSize: "cover",
backgroundRepeat: "no-repeat",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<ReactCrop
crop={crop}
onChange={(newCrop) => setCrop(newCrop)}
onComplete={onCropComplete}
>
{src && (
<img
src={src as string}
style={{
filter: `brightness(${100 - darken}%)`,
maxWidth: "580px",
}}
width={580 * (imageSize / 200)}
height={320}
alt="Crop"
/>
)}
</ReactCrop>
</Box>
<Box
sx={{
color: "#7E2AEA",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: "16xp",
fontWeight: "600",
marginBottom: "50px",
}}
>
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
{Math.round(crop.width)}
</Typography>
x
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
{Math.round(crop.width)}
</Typography>
px
</Box>
<Box
sx={{
display: isMobile ? "block" : "flex",
alignItems: "end",
justifyContent: "space-between",
}}
>
<ResetIcon
onClick={() => {
setCrop((prevCrop: Crop) => ({
...prevCrop,
unit: "px",
x: 210,
y: 10,
width: 210,
height: 300,
}));
setDarken(0);
setImageSize(580);
}}
style={{ marginBottom: "10px", cursor: "pointer" }}
/>
<Box>
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
Размер
</Typography>
<Slider
sx={styleSlider}
value={imageSize}
min={50}
max={200}
step={1}
onChange={(_, newValue) => setImageSize(newValue as number)}
/>
</Box>
<Box>
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
Затемнение
</Typography>
<Slider
sx={styleSlider}
value={darken}
min={0}
max={100}
step={1}
onChange={(_, newValue) => setDarken(newValue as number)}
/>
</Box>
</Box>
<Box
sx={{
marginTop: "40px",
width: "100%",
display: "flex",
justifyContent: "end",
}}
>
<input
ref={fileInputRef}
type="file"
accept="image/*"
id="fileInput"
style={{ display: "none" }}
onChange={handleFileChange}
/>
<Button
onClick={() => fileInputRef.current?.click()}
disableRipple
sx={{
width: "215px",
height: "48px",
color: "#7E2AEA",
borderRadius: "8px",
border: "1px solid #7E2AEA",
marginRight: "10px",
}}
>
Загрузить оригинал
</Button>
<Button
onClick={handleDownloadClick}
disableRipple
sx={{
width: "149px",
height: "48px",
color: "white",
background: "#7E2AEA",
borderRadius: "8px",
}}
>
Обрезать
</Button>
</Box>
</Box>
</Modal>
);
};