Merge branch 'dev' into 'main'

при удалении вопросов из списка и или дерева гарантируем, что default будет...

See merge request frontend/squiz!67
This commit is contained in:
Nastya 2023-12-07 23:14:11 +00:00
commit d718b3d08e
44 changed files with 481 additions and 19503 deletions

19001
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -40,6 +40,7 @@
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-easy-crop": "^5.0.0", "react-easy-crop": "^5.0.0",
"react-error-boundary": "^4.0.11",
"react-image-crop": "^10.1.5", "react-image-crop": "^10.1.5",
"react-image-file-resizer": "^0.4.8", "react-image-file-resizer": "^0.4.8",
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",

@ -11,8 +11,8 @@ import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz"; import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
import Landing from "./pages/Landing/Landing"; import Landing from "./pages/Landing/Landing";
import QuestionsPage from "./pages/Questions/QuestionsPage"; import QuestionsPage from "./pages/Questions/QuestionsPage";
import { Result } from "./pages/Result/Result"; import { Result } from "./pages/ResultPage/Result";
import { Setting } from "./pages/Result/Setting"; import { Setting } from "./pages/ResultPage/Setting";
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull"; import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
import Main from "./pages/main"; import Main from "./pages/main";
import StartPage from "./pages/startPage/StartPage"; import StartPage from "./pages/startPage/StartPage";

@ -11,6 +11,7 @@ import { QUIZ_QUESTION_SELECT } from "./select";
import { QUIZ_QUESTION_TEXT } from "./text"; import { QUIZ_QUESTION_TEXT } from "./text";
import { QUIZ_QUESTION_VARIANT } from "./variant"; import { QUIZ_QUESTION_VARIANT } from "./variant";
import { QUIZ_QUESTION_VARIMG } from "./varimg"; import { QUIZ_QUESTION_VARIMG } from "./varimg";
import { QUIZ_QUESTION_RESULT } from "./result";
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = { export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
@ -25,4 +26,5 @@ export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuesti
"text": QUIZ_QUESTION_TEXT, "text": QUIZ_QUESTION_TEXT,
"variant": QUIZ_QUESTION_VARIANT, "variant": QUIZ_QUESTION_VARIANT,
"varimg": QUIZ_QUESTION_VARIMG, "varimg": QUIZ_QUESTION_VARIMG,
"result": QUIZ_QUESTION_RESULT,
} as const; } as const;

26
src/constants/result.ts Normal file

@ -0,0 +1,26 @@
import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionResult } from "../model/questionTypes/result";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId"> = {
...QUIZ_QUESTION_BASE,
type: "result",
content: {
...QUIZ_QUESTION_BASE.content,
multi: false,
own: false,
innerNameCheck: false,
innerName: "",
required: false,
variants: [
{
id: nanoid(),
answer: "",
extendedText: "",
hints: "",
originalImageUrl: "",
},
],
},
};

@ -14,7 +14,8 @@ export type QuestionType =
| "number" | "number"
| "file" | "file"
| "page" | "page"
| "rating"; | "rating"
| "result";
/** Type that comes from server */ /** Type that comes from server */
export interface RawQuestion { export interface RawQuestion {

@ -0,0 +1,29 @@
import type {
QuizQuestionBase,
QuestionVariant,
QuestionHint,
PreviewRule,
} from "./shared";
export interface QuizQuestionResult extends QuizQuestionBase {
type: "result";
content: {
id: string;
/** Чекбокс "Можно несколько" */
multi: boolean;
/** Чекбокс "Вариант "свой ответ"" */
own: boolean;
/** Чекбокс "Внутреннее название вопроса" */
innerNameCheck: boolean;
/** Поле "Внутреннее название вопроса" */
innerName: string;
/** Чекбокс "Необязательный вопрос" */
required: boolean;
variants: QuestionVariant[];
hint: QuestionHint;
rule: PreviewRule;
back: string;
originalBack: string;
autofill: boolean;
};
}

@ -29,8 +29,6 @@ export type QuizResultsType = true | null;
export interface QuizConfig { export interface QuizConfig {
type: QuizType; type: QuizType;
logo: string | null;
originalLogo: string | null;
noStartPage: boolean; noStartPage: boolean;
startpageType: QuizStartpageType; startpageType: QuizStartpageType;
results: QuizResultsType; results: QuizResultsType;
@ -39,6 +37,10 @@ export interface QuizConfig {
description: string; description: string;
button: string; button: string;
position: QuizStartpageAlignType; position: QuizStartpageAlignType;
favIcon: string | null;
originalFavIcon: string | null;
logo: string | null;
originalLogo: string | null;
background: { background: {
type: null | "image" | "video"; type: null | "image" | "video";
desktop: string | null; desktop: string | null;
@ -61,8 +63,6 @@ export interface QuizConfig {
export const defaultQuizConfig: QuizConfig = { export const defaultQuizConfig: QuizConfig = {
type: null, type: null,
logo: null,
originalLogo: null,
noStartPage: false, noStartPage: false,
startpageType: null, startpageType: null,
results: null, results: null,
@ -71,6 +71,10 @@ export const defaultQuizConfig: QuizConfig = {
description: "", description: "",
button: "", button: "",
position: "left", position: "left",
favIcon: null,
originalFavIcon: null,
logo: null,
originalLogo: null,
background: { background: {
type: null, type: null,
desktop: null, desktop: null,

@ -6,7 +6,8 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions" import { updateRootContentId } from "@root/quizes/actions"
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared" import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId } from "@root/questions/actions"; import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
import { withErrorBoundary } from "react-error-boundary";
import { storeToNodes } from "./helper"; import { storeToNodes } from "./helper";
@ -21,6 +22,7 @@ import type {
} from "cytoscape"; } from "cytoscape";
import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList"; import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { Typography } from "@mui/material";
type PopperItem = { type PopperItem = {
id: () => string; id: () => string;
@ -111,13 +113,13 @@ interface Props {
} }
export const CsComponent = ({ function CsComponent ({
modalQuestionParentContentId, modalQuestionParentContentId,
modalQuestionTargetContentId, modalQuestionTargetContentId,
setOpenedModalQuestions, setOpenedModalQuestions,
setModalQuestionParentContentId, setModalQuestionParentContentId,
setModalQuestionTargetContentId setModalQuestionTargetContentId
}: Props) => { }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore() const { dragQuestionContentId, questions, desireToOpenABranchingModal } = useQuestionsStore()
@ -131,17 +133,19 @@ export const CsComponent = ({
const gearsContainer = useRef<HTMLDivElement | null>(null); const gearsContainer = useRef<HTMLDivElement | null>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const cy = cyRef?.current const cy = cyRef?.current
if (desireToOpenABranchingModal) { if (desireToOpenABranchingModal) {
setTimeout(() => { setTimeout(() => {
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true) cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
}, 250) }, 250)
} else { } else {
cy?.elements().data("eroticeyeblink", false) cy?.elements().data("eroticeyeblink", false)
} }
}, [desireToOpenABranchingModal]) }, [desireToOpenABranchingModal])
useLayoutEffect(() => { useLayoutEffect(() => {
updateOpenedModalSettingsId() updateOpenedModalSettingsId()
// updateRootContentId(quiz.id, "")
// clearRuleForAll()
}, []) }, [])
useEffect(() => { useEffect(() => {
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
@ -149,7 +153,6 @@ const cy = cyRef?.current
} }
setModalQuestionParentContentId("") setModalQuestionParentContentId("")
setModalQuestionTargetContentId("") setModalQuestionTargetContentId("")
}, [modalQuestionTargetContentId]) }, [modalQuestionTargetContentId])
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
@ -229,6 +232,7 @@ const cy = cyRef?.current
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.default = "" question.content.rule.default = ""
}) })
clearRuleForAll()
} else { } else {
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) { if (targetNodeContentId && parentQuestionContentId) {
@ -266,11 +270,21 @@ const cy = cyRef?.current
question.content.rule.default = "" question.content.rule.default = ""
}) })
updateQuestion(parentQuestionContentId, question => { //чистим rule родителя
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку const parentQuestion = getQuestionByContentId(parentQuestionContentId)
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = "" const newRule = {}
}) newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = questions.filter((q) => {
return q.content.rule.parentId === parentQuestionContentId && q.content.id !== targetQuestionContentId
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
console.log(newRule)
updateQuestion(parentQuestionContentId, (PQ) => {
PQ.content.rule = newRule
})
} }
@ -686,7 +700,7 @@ const cy = cyRef?.current
}} }}
// autolock // autolock
/> />
{/* <button onClick={() => { <button onClick={() => {
console.log("NODES____________________________") console.log("NODES____________________________")
cyRef.current?.elements().forEach((ele: any) => { cyRef.current?.elements().forEach((ele: any) => {
console.log(ele.data()) console.log(ele.data())
@ -695,7 +709,23 @@ const cy = cyRef?.current
<button onClick={() => { <button onClick={() => {
console.log("ELEMENTS____________________________") console.log("ELEMENTS____________________________")
console.log(questions) console.log(questions)
}}>elements</button> */} }}>elements</button>
</> </>
); );
}; };
function Clear () {
const quiz = useCurrentQuiz();
updateRootContentId(quiz.id, "")
clearRuleForAll()
return <></>
}
export default withErrorBoundary(CsComponent, {
fallback: <Clear/>,
onError: (error, info) => {
enqueueSnackbar("Дерево порвалось")
console.log(info)
console.log(error)
},
});

@ -1,6 +1,6 @@
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { FirstNodeField } from "./FirstNodeField"; import { FirstNodeField } from "./FirstNodeField";
import { CsComponent } from "./CsComponent"; import CsComponent from "./CsComponent";
import { useQuestionsStore } from "@root/questions/store" import { useQuestionsStore } from "@root/questions/store"
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useState } from "react"; import { useState } from "react";

@ -68,7 +68,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
return ( return (
<> <>
<Paper <Paper
id={question.content.id} id={question.id}
data-cy="quiz-question-card" data-cy="quiz-question-card"
sx={{ sx={{
maxWidth: "796px", maxWidth: "796px",

@ -22,10 +22,10 @@ import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/question
export default function QuestionsPage() { export default function QuestionsPage() {
const theme = useTheme(); const theme = useTheme();
const { openedModalSettingsId } = useQuestionsStore(); const { openedModalSettingsId, openBranchingPanel } = useQuestionsStore();
const isMobile = useMediaQuery(theme.breakpoints.down(660)); const isMobile = useMediaQuery(theme.breakpoints.down(660));
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const {openBranchingPanel} = useQuestionsStore.getState() console.log(quiz)
useLayoutEffect(() => { useLayoutEffect(() => {
updateOpenBranchingPanel(false) updateOpenBranchingPanel(false)
updateEditSomeQuestion() updateEditSomeQuestion()

@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import image from "../../assets/Rectangle 110.png"; import image from "../../assets/Rectangle 110.png";
import Info from "../../assets/icons/Info"; import Info from "../../assets/icons/Info";
import CreationFullCard from "./CreationFullCard"; import CreationFullCard from "./FirstEntry";
export const Result = () => { export const Result = () => {

@ -0,0 +1,19 @@
import { useQuestionsStore } from "@root/questions/store";
import FirstEntry from "./FirstEntry"
export default function ResultPage() {
const { questions } = useQuestionsStore();
//ищём хотя бы один result
const haveResult = questions.some((question) => {
question.type === "result"
})
return (
<>
</>
);
}

@ -55,8 +55,8 @@ export const Question = ({
sx={{ sx={{
minHeight: "calc(100vh - 75px)", minHeight: "calc(100vh - 75px)",
width: "100%", width: "100%",
maxWidth: "1000px", maxWidth: "1440px",
padding: "20px 10px 0", padding: "40px 25px 20px",
margin: "0 auto", margin: "0 auto",
}} }}
> >

@ -1,16 +1,15 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
RadioGroup, RadioGroup,
FormControlLabel, FormControlLabel,
Radio, Radio,
useTheme, useTheme,
useMediaQuery, useMediaQuery, FormControl,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -92,7 +91,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
)} )}
</Box> </Box>
</Box> </Box>
<FormControlLabel <FormControl
key={id} key={id}
sx={{ sx={{
display: "block", display: "block",

@ -14,7 +14,8 @@ export const Page = ({ currentQuestion }: PageProps) => {
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5" sx={{ paddingBottom: "25px" }}>{currentQuestion.title}</Typography>
<Typography>{currentQuestion.content.text}</Typography>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -24,17 +25,20 @@ export const Page = ({ currentQuestion }: PageProps) => {
}} }}
> >
{currentQuestion.content.picture && ( {currentQuestion.content.picture && (
<img <Box sx={{borderRadius: "12px",
src={currentQuestion.content.picture} border: "1px solid #9A9AAF", overflow: "hidden" }}>
alt="" <img
style={{ src={currentQuestion.content.picture}
display: "block", alt=""
width: "100%", style={{
height: "100%", display: "block",
maxHeight: "80vh", width: "100%",
objectFit: "contain", height: "100%",
}} objectFit: "contain",
/> }}
/>
</Box>
)} )}
{currentQuestion.content.video && ( {currentQuestion.content.video && (
<video <video

@ -53,20 +53,22 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
marginTop: "20px", marginTop: "20px",
}} }}
> >
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "row", flexWrap: "wrap", width: "100%", gap: "20px", }}>
{currentQuestion.content.variants.map(({ id, answer }, index) => ( {currentQuestion.content.variants.map(({ id, answer }, index) => (
<FormControlLabel <FormControlLabel
key={id} key={id}
sx={{ sx={{
marginBottom: "15px", margin: "0",
borderRadius: "5px", borderRadius: "12px",
padding: "15px", padding: "15px",
color: theme.palette.grey2.main,
border: `1px solid ${theme.palette.grey2.main}`, border: `1px solid ${theme.palette.grey2.main}`,
display: "flex", display: "flex",
gap: "10px", maxWidth: "685px",
justifyContent: "space-between",
width: "100%"
}} }}
value={index} value={index}
labelPlacement="start"
control={ control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} /> <Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
} }

@ -35,8 +35,8 @@ import UploadIcon from "../../assets/icons/UploadIcon";
import ModalSizeImage from "./ModalSizeImage"; import ModalSizeImage from "./ModalSizeImage";
import SelectableIconButton from "./SelectableIconButton"; import SelectableIconButton from "./SelectableIconButton";
import { DropZone } from "./dropZone"; import { DropZone } from "./dropZone";
import DropFav from "./dropfavicon";
import Extra from "./extra"; import Extra from "./extra";
import { resizeFavIcon } from "@ui_kit/reactImageFileResizer";
const designTypes = [ const designTypes = [
@ -85,6 +85,33 @@ export default function StartPageSettings() {
if (!quiz) return null; // TODO throw and catch with error boundary if (!quiz) return null; // TODO throw and catch with error boundary
const favIconDropZoneElement = (
<DropZone
sx={{ height: "48px", width: "48px" }}
deleteIconSx={{ right: -40, top: -10 }}
imageUrl={quiz.config.startpage.favIcon}
originalImageUrl={quiz.config.startpage.originalFavIcon}
onImageUploadClick={async file => {
const resizedImage = await resizeFavIcon(file);
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
quiz.config.startpage.favIcon = url;
quiz.config.startpage.originalFavIcon = url;
});
}}
onImageSaveClick={async file => {
const resizedImage = await resizeFavIcon(file);
uploadQuizImage(quiz.id, resizedImage, (quiz, url) => {
quiz.config.startpage.favIcon = url;
});
}}
onDeleteClick={() => {
updateQuiz(quiz.id, quiz => {
quiz.config.startpage.favIcon = null;
});
}}
/>
);
return ( return (
<> <>
<Typography <Typography
@ -304,7 +331,6 @@ export default function StartPageSettings() {
</Typography> </Typography>
<DropZone <DropZone
text={"5 MB максимум"} text={"5 MB максимум"}
heightImg={"110px"}
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.startpage.background.desktop} imageUrl={quiz.config.startpage.background.desktop}
originalImageUrl={quiz.config.startpage.background.originalDesktop} originalImageUrl={quiz.config.startpage.background.originalDesktop}
@ -373,7 +399,6 @@ export default function StartPageSettings() {
</Typography> </Typography>
<DropZone <DropZone
text={"5 MB максимум"} text={"5 MB максимум"}
heightImg={"110px"}
imageUrl={quiz.config.startpage.background.mobile} imageUrl={quiz.config.startpage.background.mobile}
originalImageUrl={quiz.config.startpage.background.originalMobile} originalImageUrl={quiz.config.startpage.background.originalMobile}
onImageUploadClick={file => { onImageUploadClick={file => {
@ -476,7 +501,6 @@ export default function StartPageSettings() {
</Typography> </Typography>
<DropZone <DropZone
text={"5 MB максимум"} text={"5 MB максимум"}
heightImg={"110px"}
imageUrl={quiz.config.startpage.background.mobile} imageUrl={quiz.config.startpage.background.mobile}
originalImageUrl={quiz.config.startpage.background.originalMobile} originalImageUrl={quiz.config.startpage.background.originalMobile}
onImageUploadClick={file => { onImageUploadClick={file => {
@ -557,24 +581,23 @@ export default function StartPageSettings() {
</Typography> </Typography>
<DropZone <DropZone
text={"5 MB максимум"} text={"5 MB максимум"}
heightImg={"110px"}
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.logo} imageUrl={quiz.config.startpage.logo}
originalImageUrl={quiz.config.originalLogo} originalImageUrl={quiz.config.startpage.originalLogo}
onImageUploadClick={file => { onImageUploadClick={file => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.logo = url; quiz.config.startpage.logo = url;
quiz.config.originalLogo = url; quiz.config.startpage.originalLogo = url;
}); });
}} }}
onImageSaveClick={file => { onImageSaveClick={file => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.logo = url; quiz.config.startpage.logo = url;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, quiz => {
quiz.config.logo = null; quiz.config.startpage.logo = null;
}); });
}} }}
/> />
@ -597,12 +620,7 @@ export default function StartPageSettings() {
gap: "10px", gap: "10px",
}} }}
> >
<DropFav {favIconDropZoneElement}
sx={{ height: "48px", width: "48px" }}
heightImg={"48px"}
widthImg={"48px"}
/>
<Typography <Typography
sx={{ sx={{
color: theme.palette.orange.main, color: theme.palette.orange.main,
@ -645,24 +663,23 @@ export default function StartPageSettings() {
</Typography> </Typography>
<DropZone <DropZone
text={"5 MB максимум"} text={"5 MB максимум"}
heightImg={"110px"}
sx={{ maxWidth: "300px" }} sx={{ maxWidth: "300px" }}
imageUrl={quiz.config.logo} imageUrl={quiz.config.startpage.logo}
originalImageUrl={quiz.config.originalLogo} originalImageUrl={quiz.config.startpage.originalLogo}
onImageUploadClick={file => { onImageUploadClick={file => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.logo = url; quiz.config.startpage.logo = url;
quiz.config.originalLogo = url; quiz.config.startpage.originalLogo = url;
}); });
}} }}
onImageSaveClick={file => { onImageSaveClick={file => {
uploadQuizImage(quiz.id, file, (quiz, url) => { uploadQuizImage(quiz.id, file, (quiz, url) => {
quiz.config.logo = url; quiz.config.startpage.logo = url;
}); });
}} }}
onDeleteClick={() => { onDeleteClick={() => {
updateQuiz(quiz.id, quiz => { updateQuiz(quiz.id, quiz => {
quiz.config.logo = null; quiz.config.startpage.logo = null;
}); });
}} }}
/> />
@ -685,12 +702,7 @@ export default function StartPageSettings() {
gap: "10px", gap: "10px",
}} }}
> >
<DropFav {favIconDropZoneElement}
sx={{ height: "48px", width: "48px" }}
heightImg={"48px"}
widthImg={"48px"}
/>
<Typography <Typography
sx={{ sx={{
color: theme.palette.orange.main, color: theme.palette.orange.main,

@ -22,8 +22,7 @@ const allowedFileTypes = ["image/png", "image/jpeg", "image/gif"];
interface Props { interface Props {
text?: string; text?: string;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
heightImg: string; deleteIconSx?: SxProps<Theme>;
widthImg?: string;
imageUrl: string | null; imageUrl: string | null;
originalImageUrl: string | null; originalImageUrl: string | null;
onImageUploadClick: (image: Blob) => void; onImageUploadClick: (image: Blob) => void;
@ -32,7 +31,7 @@ interface Props {
} }
//Научи функцию принимать данные для валидации //Научи функцию принимать данные для валидации
export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImageUrl, onImageUploadClick, onImageSaveClick, onDeleteClick }: Props) => { export const DropZone = ({ text, sx, deleteIconSx, imageUrl, originalImageUrl, onImageUploadClick, onImageSaveClick, onDeleteClick }: Props) => {
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const [isDropReady, setIsDropReady] = useState<boolean>(false); const [isDropReady, setIsDropReady] = useState<boolean>(false);
@ -103,14 +102,15 @@ export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImag
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
borderRadius: "8px", borderRadius: "8px",
overflow: "hidden",
}} }}
> >
{imageUrl ? {imageUrl ?
<img <img
height={heightImg}
width={widthImg}
src={imageUrl} src={imageUrl}
style={{ style={{
width: "100%",
height: "100%",
objectFit: "scale-down", objectFit: "scale-down",
}} }}
/> />
@ -144,6 +144,7 @@ export const DropZone = ({ text, sx, heightImg, widthImg, imageUrl, originalImag
borderRadius: "8px", borderRadius: "8px",
borderBottomRightRadius: 0, borderBottomRightRadius: 0,
borderTopLeftRadius: 0, borderTopLeftRadius: 0,
...deleteIconSx,
}} }}
> >
<DeleteIcon /> <DeleteIcon />

@ -1,274 +0,0 @@
import { Box, ButtonBase, SxProps, Theme, Typography, useTheme } from "@mui/material";
import Resizer from "@ui_kit/reactImageFileResizer";
import saveAs from "file-saver";
import JSZip from "jszip";
import { enqueueSnackbar } from "notistack";
import { useEffect, useState } from "react";
import UploadIcon from "../../assets/icons/UploadIcon";
interface Props {
text?: string;
sx?: SxProps<Theme>;
heightImg: string;
widthImg?: string;
}
const imageFavicon = [
{
maxWidth: 16,
maxHeight: 16,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 32,
maxHeight: 32,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 48,
maxHeight: 48,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 76,
maxHeight: 76,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 96,
maxHeight: 96,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 120,
maxHeight: 120,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 128,
maxHeight: 128,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 144,
maxHeight: 144,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 152,
maxHeight: 152,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 167,
maxHeight: 167,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 180,
maxHeight: 180,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 192,
maxHeight: 192,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 196,
maxHeight: 196,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 228,
maxHeight: 288,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 256,
maxHeight: 256,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 300,
maxHeight: 300,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 384,
maxHeight: 384,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
{
maxWidth: 512,
maxHeight: 512,
compressFormat: "PNG",
quality: 100,
rotation: 0,
},
];
export default ({ text, sx, heightImg, widthImg }: Props) => {
const theme = useTheme();
const [favList, setFavList] = useState<string[]>([]);
useEffect(() => {
if (favList.length === 18) {
const zip = new JSZip(); //создание зип архива
favList.forEach((uri, i) => {
const idx = uri.indexOf("base64,") + "base64,".length; //обработка строки картинки
const content = uri.substring(idx); //обработка строки картинки
zip.file(`fav${i}.jpg`, content, { base64: true }); //сохранение картинки в архив с именем "fav.jpg"
});
zip.generateAsync({ type: "blob" }).then(function (content) {
// скачивание архива
saveAs(content, "fav.zip"); // скачивание архива
}); // скачивание архива
}
}, [favList]);
const callback = (uri: string) => {
setFavList((old) => [...old, uri]);
};
const imgHC = (imgInp: HTMLInputElement) => {
const file = imgInp.files?.[0];
if (file) {
setFavList([]);
if (file.size < 5242880) {
setData(URL.createObjectURL(file));
imageFavicon.forEach((obj) => {
try {
Resizer.imageFileResizer(
file,
obj.maxWidth,
obj.compressFormat,
obj.quality,
obj.rotation,
callback,
"string"
);
} catch (err) {
console.log(err);
}
});
} else {
enqueueSnackbar("Размер картинки слишком велик");
}
}
};
const [data, setData] = useState("");
const [ready, setReady] = useState(false);
const dragenterHC = () => {
setReady(true);
};
const dragexitHC = () => {
setReady(false);
};
const dropHC = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
setReady(false);
const file = event.dataTransfer.files[0];
if (file.size < 5242880) {
setData(URL.createObjectURL(file));
} else {
enqueueSnackbar("Размер картинки слишком велик");
}
};
const dragOverHC = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
};
return (
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
<input onChange={(event) => imgHC(event.target)} hidden accept="image/*" multiple type="file" />
<Box
onDragEnter={dragenterHC}
onDragExit={dragexitHC}
onDrop={dropHC}
onDragOver={dragOverHC}
sx={{
width: "100%",
height: "120px",
position: "relative",
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: theme.palette.background.default,
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
borderRadius: "8px",
opacity: data ? "0.5" : 1,
...sx,
}}
>
<UploadIcon />
<Typography
sx={{
position: "absolute",
right: "10px",
bottom: "10px",
color: theme.palette.orange.main,
fontSize: "16px",
lineHeight: "19px",
textDecoration: "underline",
}}
>
{text}
</Typography>
{data ? (
<img
height={heightImg}
width={widthImg}
src={data}
style={{
position: "absolute",
zIndex: "-1",
objectFit: "revert-layer",
}}
/>
) : null}
</Box>
</ButtonBase>
);
};

@ -1,3 +1,4 @@
import { questionApi } from "@api/question"; import { questionApi } from "@api/question";
import { quizApi } from "@api/quiz"; import { quizApi } from "@api/quiz";
import { devlog } from "@frontend/kitui"; import { devlog } from "@frontend/kitui";
@ -13,6 +14,7 @@ import { RequestQueue } from "../../utils/requestQueue";
import { updateRootContentId } from "@root/quizes/actions" import { updateRootContentId } from "@root/quizes/actions"
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks"
import { QuestionsStore, useQuestionsStore } from "./store"; import { QuestionsStore, useQuestionsStore } from "./store";
import { withErrorBoundary } from "react-error-boundary";
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => { export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
@ -321,8 +323,8 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
await questionApi.delete(question.backendId); await questionApi.delete(question.backendId);
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quizId, "") updateRootContentId(quizId, "")
clearRoleForAll() clearRuleForAll()
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очищаем его потомков } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[] const clearQuestions = [] as string[]
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
@ -343,8 +345,22 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
}) })
}) })
} //чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = questions.filter((q) => {
return q.content.rule.parentId === question.content.rule.parentId && q.content.id !== question.content.id
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
console.log(newRule)
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
}
removeQuestion(questionId); removeQuestion(questionId);
} catch (error) { } catch (error) {
devlog("Error deleting question", error); devlog("Error deleting question", error);
@ -425,7 +441,7 @@ export const updateDragQuestionContentId = (contentId?: string) => {
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null }); useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
} }
export const clearRoleForAll = () => { export const clearRuleForAll = () => {
const { questions } = useQuestionsStore.getState() const { questions } = useQuestionsStore.getState()
questions.forEach(question => { questions.forEach(question => {
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) { if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
@ -455,3 +471,61 @@ export const clearDesireToOpenABranchingModal = () => {
export const updateEditSomeQuestion = (contentId?: string) => { export const updateEditSomeQuestion = (contentId?: string) => {
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId }) useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
} }
export const createFrontResult = (quizId: number) => setProducedState(state => {
state.questions.push({
id: nanoid(),
quizId,
type: "result",
title: "",
description: "",
deleted: false,
expanded: true,
});
}, {
type: "createFrontResult",
quizId,
});
export const createBackResult = async (
questionId: string,
type: QuestionType,
) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return;
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
try {
const createdQuestion = await questionApi.create({
quiz_id: question.quizId,
type,
title: question.title,
description: question.description,
page: 0,
required: true,
content: JSON.stringify(defaultQuestionByType[type].content),
});
setProducedState(state => {
const questionIndex = state.questions.findIndex(q => q.id === questionId);
if (questionIndex !== -1) state.questions.splice(
questionIndex,
1,
rawQuestionToQuestion(createdQuestion)
);
}, {
type: "createTypedQuestion",
question,
});
} catch (error) {
devlog("Error creating question", error);
enqueueSnackbar("Не удалось создать вопрос");
}
});
export const updateResult = () => {
}

@ -112,17 +112,12 @@ export default function HeaderFull() {
width: "36px", width: "36px",
}} }}
/> />
<IconButton <LogoutButton
sx={{ onClick={handleLogoutClick}
ml: "20px", sx={{
bgcolor: "#F2F3F7", ml: "20px",
borderRadius: "6px", }}
height: "36px", />
width: "36px",
}}
>
<LogoutButton onClick={handleLogoutClick} />
</IconButton>
</> </>
)} )}
</Box> </Box>

@ -65,7 +65,7 @@ export default function QuizPreviewLayout() {
> >
<Box <Box
sx={{ sx={{
p: "16px", p: "40px 20px 20px",
whiteSpace: "break-spaces", whiteSpace: "break-spaces",
overflowY: "auto", overflowY: "auto",
flexGrow: 1, flexGrow: 1,
@ -219,9 +219,9 @@ export default function QuizPreviewLayout() {
} }
function QuestionPreviewComponent({ question }: { function QuestionPreviewComponent({ question }: {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion | undefined;
}) { }) {
if (question.type === null) return null; if (!question || question.type === null) return null;
switch (question.type) { switch (question.type) {
case "variant": return <Variant question={question} />; case "variant": return <Variant question={question} />;

@ -16,19 +16,22 @@ export default function Page({ question }: Props) {
gap: 1, gap: 1,
}} }}
> >
<Typography variant="h6" data-cy="question-title">{question.title}</Typography> <Typography variant="h6" data-cy="question-title" sx={{ paddingBottom: "25px" }}>{question.title}</Typography>
<Typography data-cy="question-text">{question.content.text}</Typography> <Typography data-cy="question-text" sx={{ paddingBottom: "20px" }}>{question.content.text}</Typography>
{question.content.picture && ( {question.content.picture && (
<img <Box sx={{borderRadius: "12px",
src={question.content.picture} border: "1px solid #9A9AAF", width: "100%", overflow: "hidden"}}>
alt="" <img
style={{ src={question.content.picture}
width: "100%", alt=""
display: "block", style={{
objectFit: "scale-down", display: "block",
flexGrow: 1, width: "100%",
}} height: "100%",
/> objectFit: "contain",
}}
/>
</Box>
)} )}
</Box> </Box>
); );

@ -6,6 +6,7 @@ import {
FormLabel, FormLabel,
Radio, Radio,
RadioGroup, RadioGroup,
useRadioGroup,
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
@ -13,6 +14,9 @@ import {
import InfoIcon from "@icons/InfoIcon"; import InfoIcon from "@icons/InfoIcon";
import type { QuizQuestionVariant } from "model/questionTypes/variant"; import type { QuizQuestionVariant } from "model/questionTypes/variant";
import CustomRadio from "@ui_kit/CustomRadio";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
interface Props { interface Props {
question: QuizQuestionVariant; question: QuizQuestionVariant;
@ -27,13 +31,17 @@ export default function Variant({ question }: Props) {
return ( return (
<FormControl fullWidth> <FormControl fullWidth>
<FormLabel id="quiz-question-radio-group" data-cy="question-title"> <FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{color: "#000000", marginBottom: "20px", fontSize: "24px", fontWeight: 500}}>
{question.title} {question.title}
</FormLabel> </FormLabel>
<RadioGroup <RadioGroup
aria-labelledby="quiz-question-radio-group" aria-labelledby="quiz-question-radio-group"
value={value} value={value}
onChange={handleChange} onChange={handleChange}
sx={{
flexDirection: "row",
gap: "20px"
}}
> >
{question.content.variants {question.content.variants
.filter(({ answer }) => answer) .filter(({ answer }) => answer)
@ -42,11 +50,21 @@ export default function Variant({ question }: Props) {
key={index} key={index}
value={variant.answer} value={variant.answer}
data-cy="variant-answer" data-cy="variant-answer"
labelPlacement="start"
sx={{borderRadius: "12px",
border: value === value ? "1px solid #7E2AEA" : "1px solid #9A9AAF",
padding: "20px",
justifyContent: "space-between",
maxWidth: "685px",
width: "100%",
margin: 0
}}
control={ control={
<Radio <Radio
inputProps={{ inputProps={{
"data-cy": "variant-radio", "data-cy": "variant-radio",
}} }}
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
/> />
} }
label={ label={

@ -1,13 +1,13 @@
import { useState, ChangeEvent } from "react"; import { useState, ChangeEvent, useEffect } from "react";
import { import {
Box, Box,
FormControl, FormControl,
FormControlLabel, FormControlLabel,
FormLabel, FormLabel,
Radio, Radio,
RadioGroup, RadioGroup,
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import InfoIcon from "@icons/InfoIcon"; import InfoIcon from "@icons/InfoIcon";
@ -16,103 +16,113 @@ import type { QuestionVariant } from "model/questionTypes/shared";
import type { QuizQuestionVarImg } from "model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
interface Props { interface Props {
question: QuizQuestionVarImg; question: QuizQuestionVarImg;
} }
export default function Varimg({ question }: Props) { export default function Varimg({ question }: Props) {
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1); const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setSelectedVariantIndex( setSelectedVariantIndex(
question.content.variants.findIndex( question.content.variants.findIndex(
(variant) => variant.answer === event.target.value (variant) => variant.answer === event.target.value
) )
); );
}; };
const currentVariant: QuestionVariant | undefined = const currentVariant: QuestionVariant | undefined =
question.content.variants[selectedVariantIndex]; question.content.variants[selectedVariantIndex];
return ( return (
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
gap: 2, gap: 2,
}}
>
<FormControl>
<FormLabel
id="quiz-question-radio-group"
data-cy="question-title"
>
{question.title}
</FormLabel>
<RadioGroup
aria-labelledby="quiz-question-radio-group"
value={currentVariant?.answer ?? ""}
onChange={handleChange}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>{variant.answer}</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
))}
</RadioGroup>
</FormControl>
<Box
sx={{
border: "1px solid #E3E3E3",
maxWidth: "400px",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "8px",
}}
>
{currentVariant?.extendedText ? (
<img
src={currentVariant.extendedText}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}} }}
/> >
) : ( <FormControl>
<Typography p={2}> <FormLabel
{selectedVariantIndex === -1 id="quiz-question-radio-group"
? "Выберите вариант" data-cy="question-title"
: "Картинка отсутствует"} >
</Typography> {question.title}
)} </FormLabel>
</Box> <RadioGroup
</Box> aria-labelledby="quiz-question-radio-group"
); value={currentVariant?.answer ?? ""}
onChange={handleChange}
>
{question.content.variants
.filter(({ answer }) => answer)
.map((variant, index) => (
<FormControlLabel
key={index}
value={variant.answer}
data-cy="variant-answer"
control={
<Radio
inputProps={{
"data-cy": "variant-radio",
}}
/>
}
label={
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Typography>{variant.answer}</Typography>
{variant.hints && (
<Tooltip title={variant.hints} placement="right">
<Box>
<InfoIcon />
</Box>
</Tooltip>
)}
</Box>
}
/>
))}
</RadioGroup>
</FormControl>
<Box
sx={{
border: "1px solid #E3E3E3",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
{currentVariant ?
currentVariant.extendedText ? (
<img
src={currentVariant.extendedText}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
) : (
<Typography p={2}>Картинка отсутствует</Typography>
) : question.content.back ? (
<img
src={question.content.back}
data-cy="variant-image"
alt="question variant"
style={{
width: "100%",
display: "block",
objectFit: "scale-down",
flexGrow: 1,
}}
/>
) : (
<Typography p={2}>Выберите вариант</Typography>
)
}
</Box>
</Box>
);
} }

@ -53,9 +53,9 @@ export default function QuizPreviewLayout() {
}} }}
> >
{quiz.config.startpage.background.type === "image" && {quiz.config.startpage.background.type === "image" &&
quiz.config.startpage.background.desktop && ( quiz.config.startpage.logo && (
<img <img
src={quiz.config.startpage.background.desktop} src={quiz.config.startpage.logo}
style={{ style={{
height: "30px", height: "30px",
maxWidth: "50px", maxWidth: "50px",

@ -175,3 +175,19 @@ class Resizer {
); );
}, },
}; };
export function resizeFavIcon(blob: Blob) {
return new Promise<Blob>(resolve => {
Resizer.createResizedImage(
new File([blob], "image"),
48,
"PNG",
100,
0,
async (file: Blob) => {
resolve(file);
},
"blob"
);
});
}

@ -4,8 +4,8 @@ import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage"; import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
import QuestionsPage from "../pages/Questions/QuestionsPage"; import QuestionsPage from "../pages/Questions/QuestionsPage";
import { QuestionsMap } from "../pages/QuestionsMap"; import { QuestionsMap } from "../pages/QuestionsMap";
import { Result } from "../pages/Result/Result"; import { Result } from "../pages/ResultPage/Result";
import { Setting } from "../pages/Result/Setting"; import { Setting } from "../pages/ResultPage/Setting";
import StartPageSettings from "../pages/startPage/StartPageSettings"; import StartPageSettings from "../pages/startPage/StartPageSettings";
import StepOne from "../pages/startPage/stepOne"; import StepOne from "../pages/startPage/stepOne";
import Steptwo from "../pages/startPage/steptwo"; import Steptwo from "../pages/startPage/steptwo";

@ -8628,6 +8628,13 @@ react-easy-crop@^5.0.0:
normalize-wheel "^1.0.1" normalize-wheel "^1.0.1"
tslib "2.0.1" tslib "2.0.1"
react-error-boundary@^4.0.11:
version "4.0.11"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c"
integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==
dependencies:
"@babel/runtime" "^7.12.5"
react-error-overlay@^6.0.11: react-error-overlay@^6.0.11:
version "6.0.11" version "6.0.11"
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz" resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"