new crop-modal
This commit is contained in:
parent
19e7dab62c
commit
c979280d45
@ -5,7 +5,7 @@ import {
|
|||||||
uploadQuestionImage,
|
uploadQuestionImage,
|
||||||
} from "@root/questions/actions";
|
} from "@root/questions/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal/CropModal";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
uploadQuestionImage,
|
uploadQuestionImage,
|
||||||
} from "@root/questions/actions";
|
} from "@root/questions/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal/CropModal";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal/CropModal";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { UploadImageModal } from "../../pages/Questions/UploadImage/UploadImageModal";
|
import { UploadImageModal } from "../../pages/Questions/UploadImage/UploadImageModal";
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal/CropModal";
|
||||||
|
|
||||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||||
import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal";
|
import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal";
|
||||||
|
|||||||
@ -1,14 +1,8 @@
|
|||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
import { ResetIcon } from "@icons/ResetIcon";
|
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
|
||||||
Modal,
|
Modal,
|
||||||
Slider,
|
|
||||||
SxProps,
|
|
||||||
Theme,
|
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
@ -22,35 +16,17 @@ import ReactCrop, {
|
|||||||
makeAspectCrop,
|
makeAspectCrop,
|
||||||
} from "react-image-crop";
|
} from "react-image-crop";
|
||||||
import "react-image-crop/dist/ReactCrop.css";
|
import "react-image-crop/dist/ReactCrop.css";
|
||||||
import { isImageBlobAGifFile } from "../../utils/isImageBlobAGifFile";
|
import { isImageBlobAGifFile } from "@utils/isImageBlobAGifFile";
|
||||||
import {
|
import {
|
||||||
getModifiedImageBlob,
|
getModifiedImageBlob,
|
||||||
getRotatedImageBlob,
|
getRotatedImageBlob,
|
||||||
} from "./utils/imageManipulation";
|
} from "../utils/imageManipulation";
|
||||||
|
import DevaceMobileIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceMobileIcon";
|
||||||
const styleSlider: SxProps<Theme> = {
|
import BackArrowIcon from "@icons/BackArrowIcon";
|
||||||
color: "#7E2AEA",
|
import DevaceDesktopIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceDesktopIcon";
|
||||||
height: "12px",
|
import DevaceTabletIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceTabletIcon";
|
||||||
"& .MuiSlider-track": {
|
import DevaceSmallIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceSmallIcon";
|
||||||
border: "none",
|
import SwitchCaseCrop from "@ui_kit/Modal/CropModal/SwitchCaseCrop";
|
||||||
},
|
|
||||||
"& .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 Props {
|
interface Props {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -65,6 +41,7 @@ interface Props {
|
|||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const stepsScreen: string[] = ["desktop", "tablet", "mobile", "small"]
|
||||||
|
|
||||||
export const CropModal: FC<Props> = ({
|
export const CropModal: FC<Props> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -80,11 +57,44 @@ export const CropModal: FC<Props> = ({
|
|||||||
const [percentCrop, setPercentCrop] = useState<PercentCrop | undefined>(
|
const [percentCrop, setPercentCrop] = useState<PercentCrop | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [darken, setDarken] = useState(0);
|
const [darken, setDarken] = useState(0);
|
||||||
const [imageWidth, setImageWidth] = useState<number | null>(null);
|
const [imageWidth, setImageWidth] = useState<number | null>(null);
|
||||||
const [imageHeight, setImageHeight] = useState<number | null>(null);
|
const [imageHeight, setImageHeight] = useState<number | null>(null);
|
||||||
|
const [modalStep, setModalStep] = useState<string>(stepsScreen[0]);
|
||||||
|
const [lastStep, setLastStep] = useState<boolean>(null)
|
||||||
const cropImageElementRef = useRef<HTMLImageElement>(null);
|
const cropImageElementRef = useRef<HTMLImageElement>(null);
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||||
|
let stepIndex = stepsScreen.indexOf(modalStep)
|
||||||
|
const modalTitle = {
|
||||||
|
"desktop": {name: "Десктоп", icon: <DevaceDesktopIcon/>},
|
||||||
|
"tablet": {name:"Планшет", icon: <DevaceTabletIcon/>},
|
||||||
|
"mobile": {name:"Телефон", icon: <DevaceMobileIcon/>},
|
||||||
|
"small": {name:"Самые узкие экраны", icon: <DevaceSmallIcon/>}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextStep = () => {
|
||||||
|
if (stepIndex === stepsScreen.length - 1) {
|
||||||
|
onClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (stepIndex === stepsScreen.length - 2) {
|
||||||
|
setLastStep(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextStepIndex = stepIndex + 1
|
||||||
|
setModalStep(stepsScreen[nextStepIndex]);
|
||||||
|
};
|
||||||
|
const handlePrevStep = () => {
|
||||||
|
|
||||||
|
if (stepIndex === 0) return
|
||||||
|
if (stepIndex === stepsScreen.length - 1) {
|
||||||
|
setLastStep(false)
|
||||||
|
}
|
||||||
|
let nextStepIndex = stepIndex - 1
|
||||||
|
|
||||||
|
setModalStep(stepsScreen[nextStepIndex]);
|
||||||
|
};
|
||||||
|
|
||||||
const imageUrl = useMemo(
|
const imageUrl = useMemo(
|
||||||
() => imageBlob && URL.createObjectURL(imageBlob),
|
() => imageBlob && URL.createObjectURL(imageBlob),
|
||||||
@ -196,7 +206,6 @@ export const CropModal: FC<Props> = ({
|
|||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
padding: "20px",
|
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
width: isMobile ? "343px" : "620px",
|
width: isMobile ? "343px" : "620px",
|
||||||
height: isMobile ? "80vh" : undefined,
|
height: isMobile ? "80vh" : undefined,
|
||||||
@ -207,132 +216,37 @@ export const CropModal: FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "320px",
|
height: isMobile ? "91px" : "70px",
|
||||||
backgroundSize: "cover",
|
backgroundColor: "#F2F3F7",
|
||||||
backgroundRepeat: "no-repeat",
|
padding: "20px",
|
||||||
display: "flex",
|
borderRadius: "8px 8px 0px 0px",
|
||||||
alignItems: "center",
|
}}
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{imageUrl && (
|
<Typography sx={{ color: "#9A9AAF", fontSize: "18px" }}>
|
||||||
<ReactCrop
|
Настройте вариант отображения картинки на разных девайсах
|
||||||
crop={percentCrop}
|
|
||||||
onChange={(_, percentCrop) => setPercentCrop(percentCrop)}
|
|
||||||
minWidth={5}
|
|
||||||
minHeight={5}
|
|
||||||
locked
|
|
||||||
aspect={
|
|
||||||
cropAspectRatio
|
|
||||||
? cropAspectRatio.width / cropAspectRatio.height
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
onLoad={(e) => {
|
|
||||||
setImageWidth(e.currentTarget.naturalWidth);
|
|
||||||
setImageHeight(e.currentTarget.naturalHeight);
|
|
||||||
|
|
||||||
if (cropImageElementRef.current) {
|
|
||||||
setPercentCrop(
|
|
||||||
getInitialCrop(
|
|
||||||
cropImageElementRef.current.width,
|
|
||||||
cropImageElementRef.current.height,
|
|
||||||
cropAspectRatio
|
|
||||||
? cropAspectRatio.width / cropAspectRatio.height
|
|
||||||
: 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
ref={cropImageElementRef}
|
|
||||||
alt="Crop me"
|
|
||||||
src={imageUrl}
|
|
||||||
style={{
|
|
||||||
filter: `brightness(${100 - darken}%)`,
|
|
||||||
maxWidth: "100%",
|
|
||||||
maxHeight: "320px",
|
|
||||||
display: "block",
|
|
||||||
objectFit: "contain",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ReactCrop>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
mt: "40px",
|
|
||||||
display: isMobile ? "block" : "flex",
|
|
||||||
alignItems: "end",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton onClick={handleRotateClick}>
|
|
||||||
<ResetIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Box>
|
|
||||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
|
||||||
Размер
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Slider
|
|
||||||
sx={[
|
|
||||||
styleSlider,
|
|
||||||
{
|
|
||||||
width: isMobile ? undefined : "200px",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={percentCrop?.width ?? 1}
|
|
||||||
min={1}
|
|
||||||
max={100}
|
|
||||||
step={0.1}
|
|
||||||
onChange={(_, newValue) => {
|
|
||||||
if (typeof newValue === "number") handleSizeChange(newValue);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
|
||||||
Затемнение
|
|
||||||
</Typography>
|
|
||||||
<Slider
|
|
||||||
sx={[
|
|
||||||
styleSlider,
|
|
||||||
{
|
|
||||||
width: isMobile ? undefined : "200px",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={darken}
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
onChange={(_, newValue) => setDarken(newValue as number)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{onDeleteClick && (
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
onDeleteClick?.();
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
height: "48px",
|
|
||||||
width: "48px",
|
|
||||||
p: 0,
|
|
||||||
color: theme.palette.orange.main,
|
|
||||||
borderRadius: "50%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<SwitchCaseCrop
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
handleSizeChange={handleSizeChange}
|
||||||
|
handleRotateClick={handleRotateClick}
|
||||||
|
getInitialCrop={getInitialCrop}
|
||||||
|
cropAspectRatio={cropAspectRatio}
|
||||||
|
onDeleteClick={onDeleteClick}
|
||||||
|
onClose={onClose}
|
||||||
|
modalProps={modalTitle}
|
||||||
|
modalStep={modalStep}
|
||||||
|
stepIndex={stepIndex}
|
||||||
|
cropImageElementRef={cropImageElementRef}
|
||||||
|
/>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "40px",
|
marginTop: "40px",
|
||||||
|
padding: "0 20px 20px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "5px",
|
||||||
flexWrap: isMobile ? "wrap" : undefined,
|
flexWrap: isMobile ? "wrap" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -345,25 +259,59 @@ export const CropModal: FC<Props> = ({
|
|||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
border: "1px solid #7E2AEA",
|
border: "1px solid #7E2AEA",
|
||||||
px: "20px",
|
px: "20px",
|
||||||
|
width: isMobile ? "100%" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Сохранить оригинал
|
Сохранить оригинал
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Box
|
||||||
onClick={handleSaveModifiedImage}
|
sx={{
|
||||||
disableRipple
|
display: "flex",
|
||||||
variant="contained"
|
gap: "5px",
|
||||||
sx={{
|
ml: "auto",
|
||||||
height: "48px",
|
}}
|
||||||
borderRadius: "8px",
|
>
|
||||||
border: "1px solid #7E2AEA",
|
<Button
|
||||||
marginRight: "10px",
|
onClick={()=> handlePrevStep()}
|
||||||
px: "20px",
|
variant="contained"
|
||||||
ml: "auto",
|
sx={{
|
||||||
}}
|
fontSize: "16px",
|
||||||
>
|
padding: "10px 15px",
|
||||||
Сохранить редактированное
|
color: "#FFFFFF",
|
||||||
</Button>
|
border: "1px solid #9A9AAF",
|
||||||
|
background: "#FFFFFF",
|
||||||
|
"&:hover": {
|
||||||
|
color: "#FFFFFF",
|
||||||
|
border: `1px solid ${theme.palette.primary.dark}`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<BackArrowIcon color={"#7E2AEA"}/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleNextStep()
|
||||||
|
// handleSaveModifiedImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disableRipple
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "1px solid #7E2AEA",
|
||||||
|
p: "10px 15px",
|
||||||
|
width: isMobile ? "100%" : undefined,
|
||||||
|
// ml: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{lastStep ?
|
||||||
|
"Сохранить редактированное" : "Далее"
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import {Box} from "@mui/material";
|
||||||
|
|
||||||
|
export default function DevaceDesktopIcon() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "36px",
|
||||||
|
width: "36px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: "6px",
|
||||||
|
backgroundColor: "#EEE4FC"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="2" y="17" width="20" height="3" rx="1" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||||
|
<rect x="3" y="5" width="18" height="12" rx="1" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||||
|
<path d="M14 5.5L10 5.5" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import {Box} from "@mui/material";
|
||||||
|
|
||||||
|
export default function DevaceMobileIcon() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "36px",
|
||||||
|
width: "36px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: "6px",
|
||||||
|
backgroundColor: "#EEE4FC"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="6" y="2" width="12" height="20" rx="3" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||||
|
<path d="M14 2.5L10 2.5" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
23
src/ui_kit/Modal/CropModal/IconCropModal/DevaceSmallIcon.tsx
Normal file
23
src/ui_kit/Modal/CropModal/IconCropModal/DevaceSmallIcon.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import {Box} from "@mui/material";
|
||||||
|
|
||||||
|
export default function DevaceSmallIcon() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "36px",
|
||||||
|
width: "36px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: "6px",
|
||||||
|
backgroundColor: "#EEE4FC"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="7.5" y="2" width="9" height="20" rx="3" stroke="#7E2AEA" strokeWidth="1.5"/>
|
||||||
|
<path d="M13.5 2.5L10.5 2.5" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import {Box} from "@mui/material";
|
||||||
|
|
||||||
|
export default function DevaceTabletIcon() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "36px",
|
||||||
|
width: "36px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: "6px",
|
||||||
|
backgroundColor: "#EEE4FC"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19.5 19.5V4.5C19.5 3.67157 18.8284 3 18 3L6 3C5.17157 3 4.5 3.67157 4.5 4.5L4.5 19.5C4.5 20.3284 5.17157 21 6 21H18C18.8284 21 19.5 20.3284 19.5 19.5Z" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M14 4L10 4" stroke="#7E2AEA" strokeWidth="2.5" strokeLinecap="round"/>
|
||||||
|
<path d="M4.5 18H19.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
116
src/ui_kit/Modal/CropModal/SwitchCaseCrop.tsx
Normal file
116
src/ui_kit/Modal/CropModal/SwitchCaseCrop.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import CropGeneral from "@ui_kit/Modal/CropModal/cropGeneral";
|
||||||
|
import DevaceDesktopIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceDesktopIcon";
|
||||||
|
import DevaceTabletIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceTabletIcon";
|
||||||
|
import DevaceMobileIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceMobileIcon";
|
||||||
|
import DevaceSmallIcon from "@ui_kit/Modal/CropModal/IconCropModal/DevaceSmallIcon";
|
||||||
|
import {PercentCrop} from "react-image-crop";
|
||||||
|
import {MutableRefObject} from "react";
|
||||||
|
|
||||||
|
const modalProps = {
|
||||||
|
"desktop": {name: "Десктоп", icon: <DevaceDesktopIcon/>},
|
||||||
|
"tablet": {name:"Планшет", icon: <DevaceTabletIcon/>},
|
||||||
|
"mobile": {name:"Телефон", icon: <DevaceMobileIcon/>},
|
||||||
|
"small": {name:"Самые узкие экраны", icon: <DevaceSmallIcon/>}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props{
|
||||||
|
imageUrl: null | string;
|
||||||
|
handleSizeChange: (a: number)=> void;
|
||||||
|
handleRotateClick: Promise<void>;
|
||||||
|
getInitialCrop: (imageWidth: number, imageHeight: number, aspectRatio: number)=> PercentCrop;
|
||||||
|
modalProps: {string:{}};
|
||||||
|
modalStep: string;
|
||||||
|
stepIndex: number;
|
||||||
|
onClose: () => void;
|
||||||
|
cropImageElementRef: MutableRefObject<HTMLImageElement>;
|
||||||
|
onDeleteClick?: () => void;
|
||||||
|
cropAspectRatio?: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SwitchCaseCrop ({imageUrl,
|
||||||
|
cropAspectRatio,
|
||||||
|
handleSizeChange,
|
||||||
|
handleRotateClick,
|
||||||
|
getInitialCrop,
|
||||||
|
onDeleteClick,
|
||||||
|
onClose,
|
||||||
|
modalStep,
|
||||||
|
stepIndex,
|
||||||
|
cropImageElementRef}: Props) {
|
||||||
|
switch (modalStep) {
|
||||||
|
case "desktop": {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<CropGeneral
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
handleSizeChange={handleSizeChange}
|
||||||
|
handleRotateClick={handleRotateClick}
|
||||||
|
getInitialCrop={getInitialCrop}
|
||||||
|
cropAspectRatio={cropAspectRatio}
|
||||||
|
onDeleteClick={onDeleteClick}
|
||||||
|
onClose={onClose}
|
||||||
|
modalProps={modalProps.desktop}
|
||||||
|
modalStep={stepIndex}
|
||||||
|
cropImageElementRef={cropImageElementRef}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case "tablet": {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<CropGeneral
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
handleSizeChange={handleSizeChange}
|
||||||
|
handleRotateClick={handleRotateClick}
|
||||||
|
getInitialCrop={getInitialCrop}
|
||||||
|
cropAspectRatio={cropAspectRatio}
|
||||||
|
onDeleteClick={onDeleteClick}
|
||||||
|
onClose={onClose}
|
||||||
|
modalProps={modalProps.tablet}
|
||||||
|
modalStep={stepIndex}
|
||||||
|
cropImageElementRef={cropImageElementRef}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case "mobile": {
|
||||||
|
return(
|
||||||
|
<CropGeneral
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
handleSizeChange={handleSizeChange}
|
||||||
|
handleRotateClick={handleRotateClick}
|
||||||
|
getInitialCrop={getInitialCrop}
|
||||||
|
cropAspectRatio={cropAspectRatio}
|
||||||
|
onDeleteClick={onDeleteClick}
|
||||||
|
onClose={onClose}
|
||||||
|
modalProps={modalProps.mobile}
|
||||||
|
modalStep={stepIndex}
|
||||||
|
cropImageElementRef={cropImageElementRef}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case "small": {
|
||||||
|
return(
|
||||||
|
<CropGeneral
|
||||||
|
imageUrl={imageUrl}
|
||||||
|
handleSizeChange={handleSizeChange}
|
||||||
|
handleRotateClick={handleRotateClick}
|
||||||
|
getInitialCrop={getInitialCrop}
|
||||||
|
cropAspectRatio={cropAspectRatio}
|
||||||
|
onDeleteClick={onDeleteClick}
|
||||||
|
onClose={onClose}
|
||||||
|
modalProps={modalProps.small}
|
||||||
|
modalStep={stepIndex}
|
||||||
|
cropImageElementRef={cropImageElementRef}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
217
src/ui_kit/Modal/CropModal/cropGeneral.tsx
Normal file
217
src/ui_kit/Modal/CropModal/cropGeneral.tsx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import {Box, IconButton, Slider, SxProps, Theme, Typography, useMediaQuery, useTheme} from "@mui/material";
|
||||||
|
import ReactCrop, {PercentCrop} from "react-image-crop";
|
||||||
|
import {ResetIcon} from "@icons/ResetIcon";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import {MutableRefObject, useRef, useState} from "react";
|
||||||
|
|
||||||
|
interface Props{
|
||||||
|
imageUrl: null | string;
|
||||||
|
handleSizeChange: (a: number)=> void;
|
||||||
|
handleRotateClick: Promise<void>;
|
||||||
|
getInitialCrop: (imageWidth: number, imageHeight: number, aspectRatio: number)=> PercentCrop;
|
||||||
|
modalProps: any;
|
||||||
|
modalStep: number;
|
||||||
|
onClose: () => void;
|
||||||
|
cropImageElementRef: MutableRefObject<HTMLImageElement>;
|
||||||
|
onDeleteClick?: () => void;
|
||||||
|
cropAspectRatio?: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleSlider: SxProps<Theme> = {
|
||||||
|
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`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CropGeneral({imageUrl,
|
||||||
|
cropAspectRatio,
|
||||||
|
handleSizeChange,
|
||||||
|
handleRotateClick,
|
||||||
|
getInitialCrop,
|
||||||
|
modalProps,
|
||||||
|
onDeleteClick,
|
||||||
|
onClose,
|
||||||
|
modalStep,
|
||||||
|
cropImageElementRef}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||||
|
|
||||||
|
const [percentCrop, setPercentCrop] = useState<PercentCrop | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
const [imageWidth, setImageWidth] = useState<number | null>(null);
|
||||||
|
const [imageHeight, setImageHeight] = useState<number | null>(null);
|
||||||
|
const [darken, setDarken] = useState(0);
|
||||||
|
const step = modalStep + 1
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "20px 20px 0",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "12px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>{modalProps.name}</Typography>
|
||||||
|
{modalProps.icon}
|
||||||
|
</Box>
|
||||||
|
{onDeleteClick && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
onDeleteClick?.();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
height: "48px",
|
||||||
|
width: "48px",
|
||||||
|
p: 0,
|
||||||
|
color: theme.palette.orange.main,
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Typography sx={{p: "0 20px 20px", fontSize: "14px", color: "#9A9AAF"}}>
|
||||||
|
{step + " шаг"}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "320px",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: "0 20px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{imageUrl && (
|
||||||
|
<ReactCrop
|
||||||
|
crop={percentCrop}
|
||||||
|
onChange={(_, percentCrop) => setPercentCrop(percentCrop)}
|
||||||
|
minWidth={5}
|
||||||
|
minHeight={5}
|
||||||
|
locked
|
||||||
|
aspect={
|
||||||
|
cropAspectRatio
|
||||||
|
? cropAspectRatio.width / cropAspectRatio.height
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
onLoad={(e) => {
|
||||||
|
setImageWidth(e.currentTarget.naturalWidth);
|
||||||
|
setImageHeight(e.currentTarget.naturalHeight);
|
||||||
|
|
||||||
|
if (cropImageElementRef.current) {
|
||||||
|
setPercentCrop(
|
||||||
|
getInitialCrop(
|
||||||
|
cropImageElementRef.current.width,
|
||||||
|
cropImageElementRef.current.height,
|
||||||
|
cropAspectRatio
|
||||||
|
? cropAspectRatio.width / cropAspectRatio.height
|
||||||
|
: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={cropImageElementRef}
|
||||||
|
alt="Crop me"
|
||||||
|
src={imageUrl}
|
||||||
|
style={{
|
||||||
|
filter: `brightness(${100 - darken}%)`,
|
||||||
|
maxWidth: "100%",
|
||||||
|
maxHeight: "320px",
|
||||||
|
display: "block",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ReactCrop>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
mt: "40px",
|
||||||
|
display: isMobile ? "block" : "flex",
|
||||||
|
alignItems: "end",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "0 20px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton onClick={handleRotateClick}>
|
||||||
|
<ResetIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Box>
|
||||||
|
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||||
|
Размер
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
sx={[
|
||||||
|
styleSlider,
|
||||||
|
{
|
||||||
|
width: isMobile ? undefined : "200px",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={percentCrop?.width ?? 1}
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={0.1}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
if (typeof newValue === "number") handleSizeChange(newValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||||
|
Затемнение
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
sx={[
|
||||||
|
styleSlider,
|
||||||
|
{
|
||||||
|
width: isMobile ? undefined : "200px",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={darken}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
onChange={(_, newValue) => setDarken(newValue as number)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user