diff --git a/package.json b/package.json
index e76fa912..d1ce3ae6 100755
--- a/package.json
+++ b/package.json
@@ -18,12 +18,15 @@
"@types/react-dnd": "^3.0.2",
"@types/react-dom": "^18.0.0",
"file-saver": "^2.0.5",
+ "html-to-image": "^1.11.11",
"jszip": "^3.10.1",
"notistack": "^3.0.1",
"react": "^18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
+ "react-easy-crop": "^5.0.0",
+ "react-image-crop": "^10.1.5",
"react-image-file-resizer": "^0.4.8",
"react-router-dom": "^6.6.2",
"react-scripts": "5.0.1",
diff --git a/src/assets/icons/CropIcon.tsx b/src/assets/icons/CropIcon.tsx
new file mode 100644
index 00000000..75233e2f
--- /dev/null
+++ b/src/assets/icons/CropIcon.tsx
@@ -0,0 +1,10 @@
+import { FC } from "react";
+
+export const CropIcon: FC = () => (
+
+);
diff --git a/src/assets/icons/ResetIcon.tsx b/src/assets/icons/ResetIcon.tsx
new file mode 100644
index 00000000..a0e3d0e6
--- /dev/null
+++ b/src/assets/icons/ResetIcon.tsx
@@ -0,0 +1,33 @@
+import { CSSProperties, FC } from "react";
+
+interface Iporps {
+ style?: CSSProperties;
+ onClick?: () => void;
+}
+
+export const ResetIcon: FC = ({ style, onClick }) => (
+
+);
diff --git a/src/assets/icons/questionsPage/addImage.tsx b/src/assets/icons/questionsPage/addImage.tsx
index adec08f1..630cb87e 100644
--- a/src/assets/icons/questionsPage/addImage.tsx
+++ b/src/assets/icons/questionsPage/addImage.tsx
@@ -1,30 +1,54 @@
import { Box } from "@mui/material";
+import { FC } from "react";
+interface Iprops {
+ onClick?: () => void;
+}
-// interface Props {
-// color: string;
-// }
+const AddImage: FC = ({ onClick }) => {
+ return (
+
+
+
+ );
+};
-export default function AddImage() {
-
- return (
-
-
-
- );
-}
\ No newline at end of file
+export default AddImage;
diff --git a/src/index.tsx b/src/index.tsx
index b8ac515c..40838698 100755
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -18,6 +18,7 @@ import { Result } from "./pages/Result/Result";
import { Setting } from "./pages/Result/Setting";
import MyQuizzes from "./pages/createQuize/MyQuizzes";
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
+import ImageCrop from "@ui_kit/Modal/ImageCrop";
const routeslink: { path: string; page: JSX.Element; header: boolean; sidebar: boolean }[] = [
{ path: "/", page: , header: false, sidebar: false },
@@ -38,6 +39,7 @@ ReactDOM.render(
} />
))}
} />
+ } />
diff --git a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx
index f7143075..c9bbb6a0 100644
--- a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx
+++ b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx
@@ -1,4 +1,4 @@
-import {Box, Link, Typography, useTheme} from "@mui/material";
+import { Box, Link, Typography, useTheme } from "@mui/material";
import AddImage from "../../../assets/icons/questionsPage/addImage";
import EnterIcon from "../../../assets/icons/questionsPage/enterIcon";
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
@@ -6,57 +6,58 @@ import SwitchOptionsAndPict from "./switchOptionsAndPict";
import React from "react";
interface Props {
- totalIndex: number
+ totalIndex: number;
}
-export default function OptionsAndPicture({totalIndex}: Props) {
- const theme = useTheme();
- const [switchState, setSwitchState] = React.useState('setting');
- const SSHC = (data: string) => {
- setSwitchState(data)
- }
-return (
+export default function OptionsAndPicture({ totalIndex }: Props) {
+ const theme = useTheme();
+ const [switchState, setSwitchState] = React.useState("setting");
+ const SSHC = (data: string) => {
+ setSwitchState(data);
+ };
+ return (
<>
-
-
-
-
- Добавьте ответ
-
-
-
-
- {
- // console.info("I'm a button.");
- // }}
- >
- Добавьте ответ
-
- или нажмите Enter
-
-
+
+
+
+
+ Добавьте ответ
+
-
-
+
+ {
+ // console.info("I'm a button.");
+ // }}
+ >
+ Добавьте ответ
+
+
+ или нажмите Enter
+
+
+
+
+
+
>
-)
-}
\ No newline at end of file
+ );
+}
diff --git a/src/pages/Questions/PageOptions/PageOptions.tsx b/src/pages/Questions/PageOptions/PageOptions.tsx
index 1edb9417..492ca0f2 100644
--- a/src/pages/Questions/PageOptions/PageOptions.tsx
+++ b/src/pages/Questions/PageOptions/PageOptions.tsx
@@ -8,7 +8,7 @@ import SwitchPageOptions from "./switchPageOptions";
type Props = {
disableInput?: boolean;
- totalIndex: number
+ totalIndex: number;
};
export default function PageOptions({ disableInput, totalIndex }: Props) {
@@ -59,7 +59,7 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
-
+
>
);
diff --git a/src/pages/Result/DescriptionForm/ImageAndVideoButtons.tsx b/src/pages/Result/DescriptionForm/ImageAndVideoButtons.tsx
index 2c2f9a03..4512c9fa 100644
--- a/src/pages/Result/DescriptionForm/ImageAndVideoButtons.tsx
+++ b/src/pages/Result/DescriptionForm/ImageAndVideoButtons.tsx
@@ -2,12 +2,18 @@ import { Box, Typography, useTheme } from "@mui/material";
import AddImage from "@icons/questionsPage/addImage";
import AddVideofile from "@icons/questionsPage/addVideofile";
+import { useState } from "react";
+import { CroppingModal } from "@ui_kit/Modal/CroppingModal";
export default function ImageAndVideoButtons() {
const theme = useTheme();
+
+ const [opened, setOpened] = useState(false);
+
return (
-
+ setOpened(true)} />
+ setOpened(false)} />
void;
- sx?: SxProps;
- header: string;
+ children?: React.ReactNode;
+ isExpanded?: boolean;
+ onClick?: () => void;
+ sx?: SxProps;
+ header: string;
}
-export default function AccordMy ({ children, isExpanded = false, onClick, sx, header }: Props) {
- const theme = useTheme();
- const upMd = useMediaQuery(theme.breakpoints.up("md"));
- const upSm = useMediaQuery(theme.breakpoints.up("sm"));
- return (
-
+
+
+
+ {header}
+
+
+
-
-
- {header}
-
-
-
-
-
-
-
-
- {isExpanded && (
-
- {children}
-
-
- )
- }
+
+
- )
-}
\ No newline at end of file
+ {isExpanded && (
+
+ {children}
+
+ )}
+
+
+ );
+}
diff --git a/src/ui_kit/Modal/CroppingModal.tsx b/src/ui_kit/Modal/CroppingModal.tsx
new file mode 100644
index 00000000..ee82a999
--- /dev/null
+++ b/src/ui_kit/Modal/CroppingModal.tsx
@@ -0,0 +1,276 @@
+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";
+import { Box, Button, Modal, Slider, Typography } from "@mui/material";
+
+import quiz from "../../assets/quiz-template-6.png";
+import { ResetIcon } from "@icons/ResetIcon";
+
+interface Iprops {
+ opened: boolean;
+ onClose: () => void;
+}
+
+const style = {
+ position: "absolute" as "absolute",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+ width: "620px",
+ bgcolor: "background.paper",
+ boxShadow: 24,
+ padding: "20px",
+ borderRadius: "8px",
+};
+
+const styleSlider = {
+ width: "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`,
+ },
+ },
+};
+
+export const CroppingModal: FC = ({ opened, onClose }) => {
+ const [src, setSrc] = useState(quiz);
+ const [crop, setCrop] = useState({ unit: "px", y: 0, x: 0, width: 100, height: 100 });
+ const [completedCrop, setCompletedCrop] = useState(null);
+ const [imageSize, setImageSize] = useState(580);
+ const [darken, setDarken] = useState(0);
+ const fileInputRef = useRef(null);
+
+ const onCropComplete = (crop: Crop) => {
+ setCompletedCrop(crop);
+ };
+
+ const handleFileChange = (e: React.ChangeEvent) => {
+ 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 => {
+ const img = new Image();
+ img.src = image as string;
+
+ const scaleX = imageSize < 580 ? img.naturalWidth / (580 * (imageSize / 200)) : img.naturalWidth / 580;
+ const scaleY = img.naturalHeight / 320;
+
+ 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((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 (
+
+
+
+ setCrop(newCrop)} onComplete={onCropComplete}>
+ {src && (
+
+ )}
+
+
+
+
+ {Math.round(crop.width)}x
+ {Math.round(crop.width)}
+ px
+
+
+
+ {
+ setCrop((prevCrop: Crop) => ({
+ ...prevCrop,
+ unit: "px",
+ x: 210,
+ y: 10,
+ width: 210,
+ height: 300,
+ }));
+
+ setDarken(0);
+ setImageSize(580);
+ }}
+ style={{ marginBottom: "10px", cursor: "pointer" }}
+ />
+
+ Размер
+ setImageSize(newValue as number)}
+ />
+
+
+ Затемнение
+ setDarken(newValue as number)}
+ />
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/ui_kit/Modal/ImageCrop.tsx b/src/ui_kit/Modal/ImageCrop.tsx
new file mode 100644
index 00000000..5c0c75e0
--- /dev/null
+++ b/src/ui_kit/Modal/ImageCrop.tsx
@@ -0,0 +1,15 @@
+import { Box, Button } from "@mui/material";
+import { FC, useState } from "react";
+import { CroppingModal } from "./CroppingModal";
+
+const ImageCrop: FC = () => {
+ const [opened, setOpened] = useState(false);
+ return (
+
+ setOpened(false)} />
+
+
+ );
+};
+
+export default ImageCrop;
diff --git a/yarn.lock b/yarn.lock
index c0705d05..cd8d5038 100755
--- a/yarn.lock
+++ b/yarn.lock
@@ -5126,6 +5126,11 @@ html-minifier-terser@^6.0.2:
relateurl "^0.2.7"
terser "^5.10.0"
+html-to-image@^1.11.11:
+ version "1.11.11"
+ resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.11.11.tgz#c0f8a34dc9e4b97b93ff7ea286eb8562642ebbea"
+ integrity sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==
+
html-webpack-plugin@^5.5.0:
version "5.5.0"
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz"
@@ -6648,6 +6653,11 @@ normalize-url@^6.0.1:
resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+normalize-wheel@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45"
+ integrity sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==
+
notistack@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz"
@@ -7769,11 +7779,26 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
+react-easy-crop@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/react-easy-crop/-/react-easy-crop-5.0.0.tgz#8945dccf4d9f578e7d8d06ed71229e93f46d4a43"
+ integrity sha512-ppYg3E0jxpDW+HdgLa65lCykZSsGMuusBuKD3HeTMs/Aod4xiWyAH5jZn5iHlllLUV2c0PPT6FznvdNeLhO2wA==
+ dependencies:
+ normalize-wheel "^1.0.1"
+ tslib "2.0.1"
+
react-error-overlay@^6.0.11:
version "6.0.11"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
+react-image-crop@^10.1.5:
+ version "10.1.5"
+ resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-10.1.5.tgz#60f9d81405b01b6925629cae235e8406616fc0e5"
+ integrity sha512-BL8Rd/UHCE4O5GcYQiWDKVh5JOJb0Ic/Gde2W171v5nY7RyQzFLM1cxIzlYfESLO/lNgBVhDHuEV9RHnqALMkA==
+ dependencies:
+ clsx "^1.2.1"
+
react-image-file-resizer@^0.4.8:
version "0.4.8"
resolved "https://registry.npmjs.org/react-image-file-resizer/-/react-image-file-resizer-0.4.8.tgz"
@@ -8918,6 +8943,11 @@ tsconfig-paths@^3.14.1:
minimist "^1.2.6"
strip-bom "^3.0.0"
+tslib@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
+ integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
+
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"