frontPanel/src/ui_kit/Modal/CroppingModal.tsx

294 lines
8.0 KiB
TypeScript
Raw Normal View History

2023-08-12 08:31:21 +00:00
import React, { FC, useRef, useState } from "react";
import { saveAs } from "file-saver";
import ReactCrop, { Crop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
2023-08-12 13:31:27 +00:00
import { Box, Button, Modal, Slider, Typography, useMediaQuery, useTheme } from "@mui/material";
2023-08-12 08:31:21 +00:00
import quiz from "../../assets/quiz-template-6.png";
import { ResetIcon } from "@icons/ResetIcon";
interface Iprops {
opened: boolean;
onClose: () => void;
}
2023-08-12 13:31:27 +00:00
export const CroppingModal: FC<Iprops> = ({ opened, onClose }) => {
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",
2023-08-12 08:31:21 +00:00
boxShadow: `0px 0px 0px 3px white,
2023-08-12 13:31:27 +00:00
0px 4px 4px 3px #C3C8DD`,
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0px 0px 0px 3px white,
0px 4px 4px 3px #C3C8DD`,
},
2023-08-12 08:31:21 +00:00
},
2023-08-12 13:31:27 +00:00
};
2023-08-12 08:31:21 +00:00
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);
2023-08-12 13:31:27 +00:00
console.log(src);
2023-08-12 08:31:21 +00:00
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;
2023-08-12 13:31:27 +00:00
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);
2023-08-12 08:31:21 +00:00
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>
2023-08-12 13:31:27 +00:00
<Box sx={{ display: isMobile ? "block" : "flex", alignItems: "end", justifyContent: "space-between" }}>
2023-08-12 08:31:21 +00:00
<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>
);
};