From ed920affbcde82157d3efc7f302c51f55444ed51 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Thu, 19 Oct 2023 17:11:07 +0300 Subject: [PATCH] refactor crop modal --- src/ui_kit/Modal/CropModal.tsx | 489 +++++++++++++++------------------ 1 file changed, 224 insertions(+), 265 deletions(-) diff --git a/src/ui_kit/Modal/CropModal.tsx b/src/ui_kit/Modal/CropModal.tsx index 90935c72..2efc417c 100644 --- a/src/ui_kit/Modal/CropModal.tsx +++ b/src/ui_kit/Modal/CropModal.tsx @@ -1,116 +1,99 @@ -import React, { useState, useRef, useEffect, FC } from "react"; -import ReactCrop, { Crop, PixelCrop } from "react-image-crop"; import { Box, Button, + IconButton, Modal, Slider, + SxProps, + Theme, Typography, useMediaQuery, useTheme, } from "@mui/material"; +import React, { FC, useEffect, useRef, useState } from "react"; +import ReactCrop, { Crop, PixelCrop } from "react-image-crop"; import { canvasPreview } from "./utils/canvasPreview"; -import { useDebounceEffect } from "./utils/useDebounceEffect"; import { ResetIcon } from "@icons/ResetIcon"; -import "react-image-crop/dist/ReactCrop.css"; import { CropIcon } from "@icons/CropIcon"; +import "react-image-crop/dist/ReactCrop.css"; + + +const styleSlider: SxProps = { + 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`, + }, + }, +}; interface Iprops { opened: boolean; - onClose: React.Dispatch>; + onClose: () => void; picture?: string; onCropPress?: (imageUrl: string) => void; } export const CropModal: FC = ({ opened, onClose, picture, onCropPress }) => { - const [imgSrc, setImgSrc] = useState(""); - - const imgRef = useRef(null); - const fileInputRef = useRef(null); - const previewCanvasRef = useRef(null); - const hiddenAnchorRef = useRef(null); - const blobUrlRef = useRef(""); + const theme = useTheme(); const [crop, setCrop] = useState(); const [completedCrop, setCompletedCrop] = useState(); - const [rotate, setRotate] = useState(0); const [darken, setDarken] = useState(0); - - const theme = useTheme(); + const [rotate, setRotate] = useState(0); + const [imgSrc, setImgSrc] = useState(""); + const [width, setWidth] = useState(0); + const blobUrlRef = useRef(""); + const fileInputRef = useRef(null); + const imgRef = useRef(null); const isMobile = useMediaQuery(theme.breakpoints.down(786)); useEffect(() => { - if (picture) { - setImgSrc(picture); - } + if (picture) setImgSrc(picture); }, [picture]); - const styleModal = { - position: "absolute", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - width: isMobile ? "343px" : "620px", - bgcolor: "background.paper", - boxShadow: 24, - padding: "20px", - borderRadius: "8px", - }; + const handleFileChange = (event: React.ChangeEvent) => { + if (!event.target.files?.length) return; - 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 rotateImage = () => { - const newRotation = (rotate + 90) % 360; - setRotate(newRotation); - }; - - const onSelectFile = (event: React.ChangeEvent) => { - if (event.target.files && event.target.files.length > 0) { - setCrop(undefined); - const reader = new FileReader(); - reader.addEventListener("load", () => - setImgSrc(reader.result?.toString() || "") - ); - reader.readAsDataURL(event.target.files[0]); + setCrop(undefined); + try { + const url = URL.createObjectURL(event.target.files[0]); + setImgSrc(url); + } catch (error) { + console.error("Failed to create object url for image", error); } }; - const onDownloadCropClick = () => { - if (!previewCanvasRef.current) { - throw new Error("Crop canvas does not exist"); - } + const handleCropClick = () => { + if (!completedCrop) throw new Error("No completed crop"); + if (!imgRef.current) throw new Error("No image"); const canvasCopy = document.createElement("canvas"); const ctx = canvasCopy.getContext("2d"); - canvasCopy.width = previewCanvasRef.current.width; - canvasCopy.height = previewCanvasRef.current.height; - ctx!.filter = `brightness(${100 - darken}%)`; - ctx!.drawImage(previewCanvasRef.current, 0, 0); + if (!ctx) throw new Error("No 2d context"); + + canvasCopy.width = completedCrop.width; + canvasCopy.height = completedCrop.height; + ctx.filter = `brightness(${100 - darken}%)`; + + canvasPreview(imgRef.current, canvasCopy, completedCrop, rotate); canvasCopy.toBlob((blob) => { if (!blob) { @@ -120,36 +103,13 @@ export const CropModal: FC = ({ opened, onClose, picture, onCropPress }) URL.revokeObjectURL(blobUrlRef.current); } blobUrlRef.current = URL.createObjectURL(blob); - hiddenAnchorRef.current!.href = blobUrlRef.current; - hiddenAnchorRef.current!.click(); setImgSrc(blobUrlRef.current); onCropPress?.(blobUrlRef.current); + setCrop(undefined); }); }; - useDebounceEffect( - async () => { - if ( - completedCrop?.width && - completedCrop?.height && - imgRef.current && - previewCanvasRef.current - ) { - canvasPreview( - imgRef.current, - previewCanvasRef.current, - completedCrop, - rotate - ); - } - }, - 100, - [completedCrop, rotate] - ); - - const [width, setWidth] = useState(0); - const getImageSize = () => { if (imgRef.current) { const imageWidth = imgRef.current.naturalWidth; @@ -170,185 +130,184 @@ export const CropModal: FC = ({ opened, onClose, picture, onCropPress }) } }; + function handleSaveClick() { + onClose(); + } + return ( - <> - - - - {imgSrc && ( - setCrop(percentCrop)} - onComplete={(c) => setCompletedCrop(c)} - maxWidth={500} - minWidth={50} - maxHeight={320} - minHeight={50} - > - Crop me - - )} - - - - {crop?.width ? Math.round(crop.width) + "px" : ""} - - - {crop?.height ? Math.round(crop.height) + "px" : ""} - - - - - - - - Размер - - { - setWidth(newValue as number); + + + + {imgSrc && ( + setCrop(percentCrop)} + onComplete={(c) => setCompletedCrop(c)} + maxWidth={500} + minWidth={50} + maxHeight={320} + minHeight={50} + > + Crop me - - - - Затемнение - - setDarken(newValue as number)} - /> - + + )} + + + + {crop?.width ? Math.round(crop.width) + "px" : ""} + + + {crop?.height ? Math.round(crop.height) + "px" : ""} + + + + + setRotate(r => (r + 90) % 360)}> + + + + + Размер + + { + setWidth(newValue as number); + }} + /> - + + Затемнение + + setDarken(newValue as number)} + /> + + + + + - - - - - {completedCrop && ( -
- +
- )} - - + > + + Обрезать + + + + ); };