feat: Default questions
This commit is contained in:
parent
eed127a236
commit
fe641fabae
290
src/constants/questions.dummy.ts
Normal file
290
src/constants/questions.dummy.ts
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import type { AnyTypedQuizQuestion } from "../model/questionTypes/shared";
|
||||||
|
|
||||||
|
export const QUESTIONS_DUMMY: AnyTypedQuizQuestion[] = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
title: "",
|
||||||
|
type: "variant",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1111,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "1",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
largeCheck: false,
|
||||||
|
multi: false,
|
||||||
|
own: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
required: false,
|
||||||
|
innerName: "",
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
title: "Вы идёте в школу",
|
||||||
|
type: "page",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1112,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "1",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
text: "",
|
||||||
|
picture: "",
|
||||||
|
originalPicture: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
title: "Вы немного опоздали, как поступите?",
|
||||||
|
type: "variant",
|
||||||
|
expanded: true,
|
||||||
|
required: true,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1113,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "2",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
largeCheck: false,
|
||||||
|
multi: false,
|
||||||
|
own: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
required: false,
|
||||||
|
innerName: "",
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
id: "answer1",
|
||||||
|
answer: "Извинюсь за опоздание и займу своё место",
|
||||||
|
extendedText: "",
|
||||||
|
hints: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "answer2",
|
||||||
|
answer:
|
||||||
|
"Вынесу дверь с ноги и распинав всех направлюсь к месту у розетки",
|
||||||
|
extendedText: "",
|
||||||
|
hints: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "answer3",
|
||||||
|
answer: "Влечу в кабинет, пробегу его и выпрыгну в окно",
|
||||||
|
extendedText: "",
|
||||||
|
hints: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
title: "Вы открываете дверь кабинета, приготовившись исполнить задуманное",
|
||||||
|
type: "page",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1114,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [
|
||||||
|
//момент выбора куда мы пойдём
|
||||||
|
{
|
||||||
|
next: "7",
|
||||||
|
or: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
question: "2",
|
||||||
|
answers: ["answer1"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
next: "6",
|
||||||
|
or: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
question: "2",
|
||||||
|
answers: ["answer2"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
next: "5",
|
||||||
|
or: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
question: "2",
|
||||||
|
answers: ["answer3"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: "2",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
text: "",
|
||||||
|
picture: "",
|
||||||
|
originalPicture: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "5",
|
||||||
|
title: "Вы умерли",
|
||||||
|
type: "page",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1115,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
text: "",
|
||||||
|
picture: "",
|
||||||
|
originalPicture: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6",
|
||||||
|
title: "Вас вызвали к директору",
|
||||||
|
type: "page",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1116,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
text: "",
|
||||||
|
picture: "",
|
||||||
|
originalPicture: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "7",
|
||||||
|
title: "Вы получили отлично",
|
||||||
|
type: "page",
|
||||||
|
expanded: true,
|
||||||
|
required: false,
|
||||||
|
deleted: false,
|
||||||
|
deleteTimeoutId: 0,
|
||||||
|
backendId: 1117,
|
||||||
|
description: "",
|
||||||
|
openedModalSettings: false,
|
||||||
|
page: 1,
|
||||||
|
quizId: 1,
|
||||||
|
content: {
|
||||||
|
hint: {
|
||||||
|
text: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
main: [],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
back: "",
|
||||||
|
originalBack: "",
|
||||||
|
autofill: false,
|
||||||
|
innerNameCheck: false,
|
||||||
|
innerName: "",
|
||||||
|
text: "",
|
||||||
|
picture: "",
|
||||||
|
originalPicture: "",
|
||||||
|
video: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
@ -48,7 +48,7 @@ export type QuestionVariant = {
|
|||||||
/** Дополнительное поле для текста, emoji, ссылки на картинку */
|
/** Дополнительное поле для текста, emoji, ссылки на картинку */
|
||||||
extendedText: string;
|
extendedText: string;
|
||||||
/** Оригинал изображения (до кропа) */
|
/** Оригинал изображения (до кропа) */
|
||||||
originalImageUrl: string;
|
originalImageUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface QuizQuestionBase {
|
export interface QuizQuestionBase {
|
||||||
|
@ -77,7 +77,7 @@ export default function OptionsAndPicture({ question }: Props) {
|
|||||||
setSelectedVariantId(variant.id);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) return openCropModal(
|
if (variant.extendedText) return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
openImageUploadModal();
|
openImageUploadModal();
|
||||||
@ -102,7 +102,7 @@ export default function OptionsAndPicture({ question }: Props) {
|
|||||||
setSelectedVariantId(variant.id);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) return openCropModal(
|
if (variant.extendedText) return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
openImageUploadModal();
|
openImageUploadModal();
|
||||||
|
@ -68,7 +68,7 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
if (variant.extendedText) {
|
if (variant.extendedText) {
|
||||||
return openCropModal(
|
return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl || ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
if (variant.extendedText) {
|
if (variant.extendedText) {
|
||||||
return openCropModal(
|
return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl || ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,100 +1,121 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { collapseAllQuestions, createUntypedQuestion } from "@root/questions/actions";
|
import {
|
||||||
import { decrementCurrentStep, incrementCurrentStep } from "@root/quizes/actions";
|
collapseAllQuestions,
|
||||||
|
createUntypedQuestion,
|
||||||
|
} from "@root/questions/actions";
|
||||||
|
import {
|
||||||
|
decrementCurrentStep,
|
||||||
|
incrementCurrentStep,
|
||||||
|
} from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import AddPlus from "../../assets/icons/questionsPage/addPlus";
|
import AddPlus from "../../assets/icons/questionsPage/addPlus";
|
||||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||||
import { DraggableList } from "./DraggableList";
|
import { DraggableList } from "./DraggableList";
|
||||||
|
import { setDefaultState } from "@root/questions/actions";
|
||||||
|
|
||||||
export default function QuestionsPage() {
|
export default function QuestionsPage() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
if (!quiz) return null;
|
useEffect(() => {
|
||||||
|
const setDefault = ({ code }: KeyboardEvent) => {
|
||||||
|
if (code === "Backslash") {
|
||||||
|
setDefaultState(Number(quiz?.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
document.addEventListener("keydown", setDefault);
|
||||||
<>
|
|
||||||
<Box
|
return () => {
|
||||||
sx={{
|
document.removeEventListener("keydown", setDefault);
|
||||||
maxWidth: "796px",
|
};
|
||||||
width: "100%",
|
}, []);
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
if (!quiz) return null;
|
||||||
margin: "60px 0 40px 0",
|
|
||||||
}}
|
return (
|
||||||
>
|
<>
|
||||||
<Typography variant={"h5"}>Заголовок квиза</Typography>
|
<Box
|
||||||
<Button
|
sx={{
|
||||||
sx={{
|
maxWidth: "796px",
|
||||||
fontSize: "16px",
|
width: "100%",
|
||||||
lineHeight: "19px",
|
display: "flex",
|
||||||
padding: 0,
|
justifyContent: "space-between",
|
||||||
textDecoration: "underline",
|
margin: "60px 0 40px 0",
|
||||||
color: theme.palette.brightPurple.main,
|
}}
|
||||||
textDecorationColor: theme.palette.brightPurple.main,
|
>
|
||||||
}}
|
<Typography variant={"h5"}>Заголовок квиза</Typography>
|
||||||
onClick={collapseAllQuestions}
|
<Button
|
||||||
>
|
sx={{
|
||||||
Свернуть всё
|
fontSize: "16px",
|
||||||
</Button>
|
lineHeight: "19px",
|
||||||
</Box>
|
padding: 0,
|
||||||
<DraggableList />
|
textDecoration: "underline",
|
||||||
<Box
|
color: theme.palette.brightPurple.main,
|
||||||
sx={{
|
textDecorationColor: theme.palette.brightPurple.main,
|
||||||
display: "flex",
|
}}
|
||||||
justifyContent: "space-between",
|
onClick={collapseAllQuestions}
|
||||||
maxWidth: "796px",
|
>
|
||||||
}}
|
Свернуть всё
|
||||||
>
|
</Button>
|
||||||
<IconButton
|
</Box>
|
||||||
onClick={() => {
|
<DraggableList />
|
||||||
createUntypedQuestion(quiz.backendId);
|
<Box
|
||||||
}}
|
sx={{
|
||||||
sx={{
|
display: "flex",
|
||||||
position: "fixed",
|
justifyContent: "space-between",
|
||||||
left: isMobile ? "20px" : "250px",
|
maxWidth: "796px",
|
||||||
bottom: "20px",
|
}}
|
||||||
}}
|
>
|
||||||
data-cy="create-question"
|
<IconButton
|
||||||
>
|
onClick={() => {
|
||||||
<AddPlus />
|
createUntypedQuestion(quiz.backendId);
|
||||||
</IconButton>
|
}}
|
||||||
<Box sx={{ display: "flex", gap: "8px", marginLeft: "auto" }}>
|
sx={{
|
||||||
<Button
|
position: "fixed",
|
||||||
variant="outlined"
|
left: isMobile ? "20px" : "250px",
|
||||||
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
|
bottom: "20px",
|
||||||
data-cy="back-button"
|
}}
|
||||||
onClick={decrementCurrentStep}
|
data-cy="create-question"
|
||||||
>
|
>
|
||||||
<ArrowLeft />
|
<AddPlus />
|
||||||
</Button>
|
</IconButton>
|
||||||
<Button
|
<Box sx={{ display: "flex", gap: "8px", marginLeft: "auto" }}>
|
||||||
variant="contained"
|
<Button
|
||||||
sx={{
|
variant="outlined"
|
||||||
height: "44px",
|
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
|
||||||
padding: "10px 20px",
|
data-cy="back-button"
|
||||||
borderRadius: "8px",
|
onClick={decrementCurrentStep}
|
||||||
background: theme.palette.brightPurple.main,
|
>
|
||||||
fontSize: "18px",
|
<ArrowLeft />
|
||||||
}}
|
</Button>
|
||||||
onClick={incrementCurrentStep}
|
<Button
|
||||||
>
|
variant="contained"
|
||||||
Следующий шаг
|
sx={{
|
||||||
</Button>
|
height: "44px",
|
||||||
</Box>
|
padding: "10px 20px",
|
||||||
</Box>
|
borderRadius: "8px",
|
||||||
{createPortal(<QuizPreview />, document.body)}
|
background: theme.palette.brightPurple.main,
|
||||||
</>
|
fontSize: "18px",
|
||||||
);
|
}}
|
||||||
|
onClick={incrementCurrentStep}
|
||||||
|
>
|
||||||
|
Следующий шаг
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{createPortal(<QuizPreview />, document.body)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
@ -14,7 +13,6 @@ type DateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Date = ({ stepNumber, question }: DateProps) => {
|
export const Date = ({ stepNumber, question }: DateProps) => {
|
||||||
const [startDate, setStartDate] = useState<Date | null>(new window.Date());
|
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
|
const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
|
||||||
|
|
||||||
@ -30,8 +28,8 @@ export const Date = ({ stepNumber, question }: DateProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={startDate}
|
selected={answer ? new window.Date(answer) : new window.Date()}
|
||||||
onChange={(date) => setStartDate(date)}
|
onChange={(date) => updateAnswer(stepNumber, String(date))}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -79,7 +79,7 @@ export const questionStore = create<QuestionStore>()(
|
|||||||
if (variant.extendedText.startsWith("blob:")) {
|
if (variant.extendedText.startsWith("blob:")) {
|
||||||
variant.extendedText = "";
|
variant.extendedText = "";
|
||||||
}
|
}
|
||||||
if (variant.originalImageUrl.startsWith("blob:")) {
|
if (variant.originalImageUrl?.startsWith("blob:")) {
|
||||||
variant.originalImageUrl = "";
|
variant.originalImageUrl = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -89,7 +89,7 @@ export const questionStore = create<QuestionStore>()(
|
|||||||
if (variant.extendedText.startsWith("blob:")) {
|
if (variant.extendedText.startsWith("blob:")) {
|
||||||
variant.extendedText = "";
|
variant.extendedText = "";
|
||||||
}
|
}
|
||||||
if (variant.originalImageUrl.startsWith("blob:")) {
|
if (variant.originalImageUrl?.startsWith("blob:")) {
|
||||||
variant.originalImageUrl = "";
|
variant.originalImageUrl = "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -181,7 +181,9 @@ export const setVariantOriginalImageUrl = (
|
|||||||
|
|
||||||
if (variant.originalImageUrl === url) return;
|
if (variant.originalImageUrl === url) return;
|
||||||
|
|
||||||
URL.revokeObjectURL(variant.originalImageUrl);
|
if (variant.originalImageUrl) {
|
||||||
|
URL.revokeObjectURL(variant.originalImageUrl);
|
||||||
|
}
|
||||||
variant.originalImageUrl = url;
|
variant.originalImageUrl = url;
|
||||||
}, {
|
}, {
|
||||||
type: "setVariantOriginalImageUrl",
|
type: "setVariantOriginalImageUrl",
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
import { questionApi } from "@api/question";
|
import { questionApi } from "@api/question";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
import { questionToEditQuestionRequest } from "@model/question/edit";
|
import { questionToEditQuestionRequest } from "@model/question/edit";
|
||||||
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
import {
|
||||||
import { AnyTypedQuizQuestion, QuestionVariant, UntypedQuizQuestion, createQuestionVariant } from "@model/questionTypes/shared";
|
QuestionType,
|
||||||
|
RawQuestion,
|
||||||
|
rawQuestionToQuestion,
|
||||||
|
} from "@model/question/question";
|
||||||
|
import {
|
||||||
|
AnyTypedQuizQuestion,
|
||||||
|
QuestionVariant,
|
||||||
|
UntypedQuizQuestion,
|
||||||
|
createQuestionVariant,
|
||||||
|
} from "@model/questionTypes/shared";
|
||||||
import { defaultQuestionByType } from "../../constants/default";
|
import { defaultQuestionByType } from "../../constants/default";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@ -10,20 +19,39 @@ import { enqueueSnackbar } from "notistack";
|
|||||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||||
import { RequestQueue } from "../../utils/requestQueue";
|
import { RequestQueue } from "../../utils/requestQueue";
|
||||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||||
|
import { QUESTIONS_DUMMY } from "../../constants/questions.dummy";
|
||||||
|
|
||||||
|
export const setDefaultState = (quizId: number) =>
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
QUESTIONS_DUMMY.forEach((question) => {
|
||||||
|
state.questions.push(question);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "setDefaultState",
|
||||||
|
quizId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) =>
|
||||||
const untypedQuestions = state.questions.filter(q => q.type === null);
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
const untypedQuestions = state.questions.filter((q) => q.type === null);
|
||||||
|
|
||||||
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
||||||
state.questions.push(...untypedQuestions);
|
state.questions.push(...untypedQuestions);
|
||||||
}, {
|
},
|
||||||
type: "setQuestions",
|
{
|
||||||
questions,
|
type: "setQuestions",
|
||||||
});
|
questions,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const createUntypedQuestion = (quizId: number) => setProducedState(state => {
|
export const createUntypedQuestion = (quizId: number) =>
|
||||||
state.questions.push({
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
state.questions.push({
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
quizId,
|
quizId,
|
||||||
type: null,
|
type: null,
|
||||||
@ -31,396 +59,465 @@ export const createUntypedQuestion = (quizId: number) => setProducedState(state
|
|||||||
description: "",
|
description: "",
|
||||||
deleted: false,
|
deleted: false,
|
||||||
expanded: true,
|
expanded: true,
|
||||||
});
|
});
|
||||||
}, {
|
},
|
||||||
type: "createUntypedQuestion",
|
{
|
||||||
quizId,
|
type: "createUntypedQuestion",
|
||||||
});
|
quizId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const removeQuestion = (questionId: string) => setProducedState(state => {
|
const removeQuestion = (questionId: string) =>
|
||||||
const index = state.questions.findIndex(q => q.id === questionId);
|
setProducedState(
|
||||||
if (index === -1) return;
|
(state) => {
|
||||||
|
const index = state.questions.findIndex((q) => q.id === questionId);
|
||||||
|
if (index === -1) return;
|
||||||
|
|
||||||
state.questions.splice(index, 1);
|
state.questions.splice(index, 1);
|
||||||
}, {
|
},
|
||||||
type: "removeQuestion",
|
{
|
||||||
questionId,
|
type: "removeQuestion",
|
||||||
});
|
questionId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const updateUntypedQuestion = (
|
export const updateUntypedQuestion = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
updateFn: (question: UntypedQuizQuestion) => void,
|
updateFn: (question: UntypedQuizQuestion) => void
|
||||||
) => {
|
) => {
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
(state) => {
|
||||||
if (!question) return;
|
const question = state.questions.find((q) => q.id === questionId);
|
||||||
if (question.type !== null) throw new Error("Cannot update typed question, use 'updateQuestion' instead");
|
if (!question) return;
|
||||||
|
if (question.type !== null)
|
||||||
|
throw new Error(
|
||||||
|
"Cannot update typed question, use 'updateQuestion' instead"
|
||||||
|
);
|
||||||
|
|
||||||
updateFn(question);
|
updateFn(question);
|
||||||
}, {
|
},
|
||||||
type: "updateUntypedQuestion",
|
{
|
||||||
questionId,
|
type: "updateUntypedQuestion",
|
||||||
updateFn: updateFn.toString(),
|
questionId,
|
||||||
});
|
updateFn: updateFn.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanQuestions = () => setProducedState(state => {
|
export const cleanQuestions = () =>
|
||||||
state.questions = [];
|
setProducedState(
|
||||||
}, {
|
(state) => {
|
||||||
type: "cleanQuestions",
|
state.questions = [];
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
type: "cleanQuestions",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const setQuestionBackendId = (questionId: string, backendId: number) => setProducedState(state => {
|
const setQuestionBackendId = (questionId: string, backendId: number) =>
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
setProducedState(
|
||||||
if (!question) return;
|
(state) => {
|
||||||
if (question.type === null) throw new Error("Cannot set backend id for untyped question");
|
const question = state.questions.find((q) => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
if (question.type === null)
|
||||||
|
throw new Error("Cannot set backend id for untyped question");
|
||||||
|
|
||||||
question.backendId = backendId;
|
question.backendId = backendId;
|
||||||
}, {
|
},
|
||||||
type: "setQuestionBackendId",
|
{
|
||||||
questionId: questionId,
|
type: "setQuestionBackendId",
|
||||||
backendId,
|
questionId: questionId,
|
||||||
});
|
backendId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const reorderQuestions = (
|
export const reorderQuestions = (
|
||||||
sourceIndex: number,
|
sourceIndex: number,
|
||||||
destinationIndex: number,
|
destinationIndex: number
|
||||||
) => {
|
) => {
|
||||||
if (sourceIndex === destinationIndex) return;
|
if (sourceIndex === destinationIndex) return;
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
const [removed] = state.questions.splice(sourceIndex, 1);
|
(state) => {
|
||||||
state.questions.splice(destinationIndex, 0, removed);
|
const [removed] = state.questions.splice(sourceIndex, 1);
|
||||||
}, {
|
state.questions.splice(destinationIndex, 0, removed);
|
||||||
type: "reorderQuestions",
|
},
|
||||||
sourceIndex,
|
{
|
||||||
destinationIndex,
|
type: "reorderQuestions",
|
||||||
});
|
sourceIndex,
|
||||||
|
destinationIndex,
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleExpandQuestion = (questionId: string) => setProducedState(state => {
|
export const toggleExpandQuestion = (questionId: string) =>
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
setProducedState(
|
||||||
if (!question) return;
|
(state) => {
|
||||||
|
const question = state.questions.find((q) => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
|
||||||
question.expanded = !question.expanded;
|
question.expanded = !question.expanded;
|
||||||
}, {
|
},
|
||||||
type: "toggleExpandQuestion",
|
{
|
||||||
questionId,
|
type: "toggleExpandQuestion",
|
||||||
});
|
questionId,
|
||||||
|
}
|
||||||
export const collapseAllQuestions = () => setProducedState(state => {
|
);
|
||||||
state.questions.forEach(question => question.expanded = false);
|
|
||||||
}, "collapseAllQuestions");
|
|
||||||
|
|
||||||
|
export const collapseAllQuestions = () =>
|
||||||
|
setProducedState((state) => {
|
||||||
|
state.questions.forEach((question) => (question.expanded = false));
|
||||||
|
}, "collapseAllQuestions");
|
||||||
|
|
||||||
const REQUEST_DEBOUNCE = 200;
|
const REQUEST_DEBOUNCE = 200;
|
||||||
const requestQueue = new RequestQueue();
|
const requestQueue = new RequestQueue();
|
||||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
export const updateQuestion = (
|
export const updateQuestion = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
updateFn: (question: AnyTypedQuizQuestion) => void,
|
updateFn: (question: AnyTypedQuizQuestion) => void
|
||||||
) => {
|
) => {
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
(state) => {
|
||||||
if (!question) return;
|
const question = state.questions.find((q) => q.id === questionId);
|
||||||
if (question.type === null) throw new Error("Cannot update untyped question, use 'updateUntypedQuestion' instead");
|
if (!question) return;
|
||||||
|
if (question.type === null)
|
||||||
|
throw new Error(
|
||||||
|
"Cannot update untyped question, use 'updateUntypedQuestion' instead"
|
||||||
|
);
|
||||||
|
|
||||||
updateFn(question);
|
updateFn(question);
|
||||||
}, {
|
},
|
||||||
type: "updateQuestion",
|
{
|
||||||
questionId,
|
type: "updateQuestion",
|
||||||
updateFn: updateFn.toString(),
|
questionId,
|
||||||
});
|
updateFn: updateFn.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
requestTimeoutId = setTimeout(() => {
|
requestTimeoutId = setTimeout(() => {
|
||||||
requestQueue.enqueue(async () => {
|
requestQueue
|
||||||
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
.enqueue(async () => {
|
||||||
if (!q) return;
|
const q = useQuestionsStore
|
||||||
if (q.type === null) throw new Error("Cannot send update request for untyped question");
|
.getState()
|
||||||
|
.questions.find((q) => q.id === questionId);
|
||||||
|
if (!q) return;
|
||||||
|
if (q.type === null)
|
||||||
|
throw new Error("Cannot send update request for untyped question");
|
||||||
|
|
||||||
const response = await questionApi.edit(questionToEditQuestionRequest(q));
|
const response = await questionApi.edit(
|
||||||
|
questionToEditQuestionRequest(q)
|
||||||
|
);
|
||||||
|
|
||||||
setQuestionBackendId(questionId, response.updated);
|
setQuestionBackendId(questionId, response.updated);
|
||||||
}).catch(error => {
|
})
|
||||||
if (isAxiosCanceledError(error)) return;
|
.catch((error) => {
|
||||||
|
if (isAxiosCanceledError(error)) return;
|
||||||
|
|
||||||
devlog("Error editing question", { error, questionId });
|
devlog("Error editing question", { error, questionId });
|
||||||
enqueueSnackbar("Не удалось сохранить вопрос");
|
enqueueSnackbar("Не удалось сохранить вопрос");
|
||||||
});
|
});
|
||||||
}, REQUEST_DEBOUNCE);
|
}, REQUEST_DEBOUNCE);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addQuestionVariant = (questionId: string) => {
|
export const addQuestionVariant = (questionId: string) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case "variant":
|
case "variant":
|
||||||
case "emoji":
|
case "emoji":
|
||||||
case "select":
|
case "select":
|
||||||
case "images":
|
case "images":
|
||||||
case "varimg":
|
case "varimg":
|
||||||
question.content.variants.push(createQuestionVariant());
|
question.content.variants.push(createQuestionVariant());
|
||||||
break;
|
break;
|
||||||
default: throw new Error(`Cannot add variant to question of type "${question.type}"`);
|
default:
|
||||||
}
|
throw new Error(
|
||||||
});
|
`Cannot add variant to question of type "${question.type}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteQuestionVariant = (questionId: string, variantId: string) => {
|
export const deleteQuestionVariant = (
|
||||||
updateQuestion(questionId, question => {
|
questionId: string,
|
||||||
if (!("variants" in question.content)) return;
|
variantId: string
|
||||||
|
) => {
|
||||||
|
updateQuestion(questionId, (question) => {
|
||||||
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
const variantIndex = question.content.variants.findIndex(
|
||||||
if (variantIndex === -1) return;
|
(variant) => variant.id === variantId
|
||||||
|
);
|
||||||
|
if (variantIndex === -1) return;
|
||||||
|
|
||||||
question.content.variants.splice(variantIndex, 1);
|
question.content.variants.splice(variantIndex, 1);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionVariantField = (
|
export const setQuestionVariantField = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
field: keyof QuestionVariant,
|
field: keyof QuestionVariant,
|
||||||
value: QuestionVariant[keyof QuestionVariant],
|
value: QuestionVariant[keyof QuestionVariant]
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
const variantIndex = question.content.variants.findIndex(
|
||||||
if (variantIndex === -1) return;
|
(variant) => variant.id === variantId
|
||||||
|
);
|
||||||
|
if (variantIndex === -1) return;
|
||||||
|
|
||||||
const variant = question.content.variants[variantIndex];
|
const variant = question.content.variants[variantIndex];
|
||||||
variant[field] = value;
|
|
||||||
});
|
if (value) {
|
||||||
|
variant[field] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reorderQuestionVariants = (
|
export const reorderQuestionVariants = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
sourceIndex: number,
|
sourceIndex: number,
|
||||||
destinationIndex: number,
|
destinationIndex: number
|
||||||
) => {
|
) => {
|
||||||
if (sourceIndex === destinationIndex) return;
|
if (sourceIndex === destinationIndex) return;
|
||||||
|
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
||||||
question.content.variants.splice(destinationIndex, 0, removed);
|
question.content.variants.splice(destinationIndex, 0, removed);
|
||||||
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionBackgroundImage = (
|
export const setQuestionBackgroundImage = (questionId: string, url: string) => {
|
||||||
questionId: string,
|
updateQuestion(questionId, (question) => {
|
||||||
url: string,
|
if (question.content.back === url) return;
|
||||||
) => {
|
|
||||||
updateQuestion(questionId, question => {
|
|
||||||
if (question.content.back === url) return;
|
|
||||||
|
|
||||||
if (
|
if (question.content.back !== question.content.originalBack)
|
||||||
question.content.back !== question.content.originalBack
|
URL.revokeObjectURL(question.content.back);
|
||||||
) URL.revokeObjectURL(question.content.back);
|
question.content.back = url;
|
||||||
question.content.back = url;
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionOriginalBackgroundImage = (
|
export const setQuestionOriginalBackgroundImage = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
url: string,
|
url: string
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (question.content.originalBack === url) return;
|
if (question.content.originalBack === url) return;
|
||||||
|
|
||||||
URL.revokeObjectURL(question.content.originalBack);
|
URL.revokeObjectURL(question.content.originalBack);
|
||||||
question.content.originalBack = url;
|
question.content.originalBack = url;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setVariantImageUrl = (
|
export const setVariantImageUrl = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
url: string,
|
url: string
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variant = question.content.variants.find(variant => variant.id === variantId);
|
const variant = question.content.variants.find(
|
||||||
if (!variant) return;
|
(variant) => variant.id === variantId
|
||||||
|
);
|
||||||
|
if (!variant) return;
|
||||||
|
|
||||||
if (variant.extendedText === url) return;
|
if (variant.extendedText === url) return;
|
||||||
|
|
||||||
if (variant.extendedText !== variant.originalImageUrl) URL.revokeObjectURL(variant.extendedText);
|
if (variant.extendedText !== variant.originalImageUrl)
|
||||||
variant.extendedText = url;
|
URL.revokeObjectURL(variant.extendedText);
|
||||||
});
|
variant.extendedText = url;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setVariantOriginalImageUrl = (
|
export const setVariantOriginalImageUrl = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
url: string,
|
url: string
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variant = question.content.variants.find(
|
const variant = question.content.variants.find(
|
||||||
variant => variant.id === variantId
|
(variant) => variant.id === variantId
|
||||||
) as QuestionVariant | undefined;
|
) as QuestionVariant | undefined;
|
||||||
if (!variant) return;
|
if (!variant) return;
|
||||||
|
|
||||||
if (variant.originalImageUrl === url) return;
|
if (variant.originalImageUrl === url) return;
|
||||||
|
|
||||||
URL.revokeObjectURL(variant.originalImageUrl);
|
if (variant.originalImageUrl) {
|
||||||
variant.originalImageUrl = url;
|
URL.revokeObjectURL(variant.originalImageUrl);
|
||||||
});
|
}
|
||||||
|
variant.originalImageUrl = url;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPageQuestionPicture = (
|
export const setPageQuestionPicture = (questionId: string, url: string) => {
|
||||||
questionId: string,
|
updateQuestion(questionId, (question) => {
|
||||||
url: string,
|
if (question.type !== "page") return;
|
||||||
) => {
|
|
||||||
updateQuestion(questionId, question => {
|
|
||||||
if (question.type !== "page") return;
|
|
||||||
|
|
||||||
if (question.content.picture === url) return;
|
if (question.content.picture === url) return;
|
||||||
|
|
||||||
if (
|
if (question.content.picture !== question.content.originalPicture)
|
||||||
question.content.picture !== question.content.originalPicture
|
URL.revokeObjectURL(question.content.picture);
|
||||||
) URL.revokeObjectURL(question.content.picture);
|
question.content.picture = url;
|
||||||
question.content.picture = url;
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setPageQuestionOriginalPicture = (
|
export const setPageQuestionOriginalPicture = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
url: string,
|
url: string
|
||||||
) => {
|
) => {
|
||||||
updateQuestion(questionId, question => {
|
updateQuestion(questionId, (question) => {
|
||||||
if (question.type !== "page") return;
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
if (question.content.originalPicture === url) return;
|
if (question.content.originalPicture === url) return;
|
||||||
|
|
||||||
URL.revokeObjectURL(question.content.originalPicture);
|
URL.revokeObjectURL(question.content.originalPicture);
|
||||||
question.content.originalPicture = url;
|
question.content.originalPicture = url;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionInnerName = (
|
export const setQuestionInnerName = (questionId: string, name: string) => {
|
||||||
questionId: string,
|
updateQuestion(questionId, (question) => {
|
||||||
name: string,
|
question.content.innerName = name;
|
||||||
) => {
|
});
|
||||||
updateQuestion(questionId, question => {
|
|
||||||
question.content.innerName = name;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const changeQuestionType = (
|
export const changeQuestionType = (questionId: string, type: QuestionType) => {
|
||||||
questionId: string,
|
updateQuestion(questionId, (question) => {
|
||||||
type: QuestionType,
|
question.type = type;
|
||||||
) => {
|
question.content = defaultQuestionByType[type].content;
|
||||||
updateQuestion(questionId, question => {
|
});
|
||||||
question.type = type;
|
|
||||||
question.content = defaultQuestionByType[type].content;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTypedQuestion = async (
|
export const createTypedQuestion = async (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
type: QuestionType,
|
type: QuestionType
|
||||||
) => requestQueue.enqueue(async () => {
|
) =>
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore
|
||||||
|
.getState()
|
||||||
|
.questions.find((q) => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
if (question.type !== null)
|
||||||
|
throw new Error("Cannot upgrade already typed question");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createdQuestion = await questionApi.create({
|
const createdQuestion = await questionApi.create({
|
||||||
quiz_id: question.quizId,
|
quiz_id: question.quizId,
|
||||||
type,
|
type,
|
||||||
title: question.title,
|
title: question.title,
|
||||||
description: question.description,
|
description: question.description,
|
||||||
page: 0,
|
page: 0,
|
||||||
required: true,
|
required: true,
|
||||||
content: JSON.stringify(defaultQuestionByType[type].content),
|
content: JSON.stringify(defaultQuestionByType[type].content),
|
||||||
});
|
});
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
const questionIndex = state.questions.findIndex(q => q.id === questionId);
|
(state) => {
|
||||||
if (questionIndex !== -1) state.questions.splice(
|
const questionIndex = state.questions.findIndex(
|
||||||
questionIndex,
|
(q) => q.id === questionId
|
||||||
1,
|
);
|
||||||
rawQuestionToQuestion(createdQuestion)
|
if (questionIndex !== -1)
|
||||||
|
state.questions.splice(
|
||||||
|
questionIndex,
|
||||||
|
1,
|
||||||
|
rawQuestionToQuestion(createdQuestion)
|
||||||
);
|
);
|
||||||
}, {
|
},
|
||||||
type: "createTypedQuestion",
|
{
|
||||||
question,
|
type: "createTypedQuestion",
|
||||||
});
|
question,
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error creating question", error);
|
devlog("Error creating question", error);
|
||||||
enqueueSnackbar("Не удалось создать вопрос");
|
enqueueSnackbar("Не удалось создать вопрос");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
|
export const deleteQuestion = async (questionId: string) =>
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore
|
||||||
|
.getState()
|
||||||
|
.questions.find((q) => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
if (question.type === null) {
|
if (question.type === null) {
|
||||||
removeQuestion(questionId);
|
removeQuestion(questionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await questionApi.delete(question.backendId);
|
await questionApi.delete(question.backendId);
|
||||||
|
|
||||||
removeQuestion(questionId);
|
removeQuestion(questionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error deleting question", error);
|
devlog("Error deleting question", error);
|
||||||
enqueueSnackbar("Не удалось удалить вопрос");
|
enqueueSnackbar("Не удалось удалить вопрос");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const copyQuestion = async (questionId: string, quizId: number) => requestQueue.enqueue(async () => {
|
export const copyQuestion = async (questionId: string, quizId: number) =>
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore
|
||||||
|
.getState()
|
||||||
|
.questions.find((q) => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
if (question.type === null) {
|
if (question.type === null) {
|
||||||
const copiedQuestion = structuredClone(question);
|
const copiedQuestion = structuredClone(question);
|
||||||
copiedQuestion.id = nanoid();
|
copiedQuestion.id = nanoid();
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
state.questions.push(copiedQuestion);
|
(state) => {
|
||||||
}, {
|
state.questions.push(copiedQuestion);
|
||||||
type: "copyQuestion",
|
},
|
||||||
questionId,
|
{
|
||||||
quizId,
|
type: "copyQuestion",
|
||||||
});
|
questionId,
|
||||||
|
quizId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { updated: newQuestionId } = await questionApi.copy(question.backendId, quizId);
|
const { updated: newQuestionId } = await questionApi.copy(
|
||||||
|
question.backendId,
|
||||||
|
quizId
|
||||||
|
);
|
||||||
|
|
||||||
const copiedQuestion = structuredClone(question);
|
const copiedQuestion = structuredClone(question);
|
||||||
copiedQuestion.backendId = newQuestionId;
|
copiedQuestion.backendId = newQuestionId;
|
||||||
copiedQuestion.id = nanoid();
|
copiedQuestion.id = nanoid();
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(
|
||||||
state.questions.push(copiedQuestion);
|
(state) => {
|
||||||
}, {
|
state.questions.push(copiedQuestion);
|
||||||
type: "copyQuestion",
|
},
|
||||||
questionId,
|
{
|
||||||
quizId,
|
type: "copyQuestion",
|
||||||
});
|
questionId,
|
||||||
|
quizId,
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error copying question", error);
|
devlog("Error copying question", error);
|
||||||
enqueueSnackbar("Не удалось скопировать вопрос");
|
enqueueSnackbar("Не удалось скопировать вопрос");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function setProducedState<A extends string | { type: unknown; }>(
|
function setProducedState<A extends string | { type: unknown }>(
|
||||||
recipe: (state: QuestionsStore) => void,
|
recipe: (state: QuestionsStore) => void,
|
||||||
action?: A,
|
action?: A
|
||||||
) {
|
) {
|
||||||
useQuestionsStore.setState(state => produce(state, recipe), false, action);
|
useQuestionsStore.setState((state) => produce(state, recipe), false, action);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user