2023-11-02 16:45:28 +00:00
|
|
|
|
import { questionApi } from "@api/question";
|
2023-12-01 18:05:59 +00:00
|
|
|
|
import { quizApi } from "@api/quiz";
|
2023-11-02 16:45:28 +00:00
|
|
|
|
import { devlog } from "@frontend/kitui";
|
2023-11-27 23:07:24 +00:00
|
|
|
|
import { questionToEditQuestionRequest } from "@model/question/edit";
|
2023-11-16 16:41:25 +00:00
|
|
|
|
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
2023-11-29 13:49:52 +00:00
|
|
|
|
import { AnyTypedQuizQuestion, QuestionVariant, UntypedQuizQuestion, createQuestionVariant } from "@model/questionTypes/shared";
|
|
|
|
|
import { defaultQuestionByType } from "../../constants/default";
|
2023-11-02 16:45:28 +00:00
|
|
|
|
import { produce } from "immer";
|
2023-11-28 19:16:00 +00:00
|
|
|
|
import { nanoid } from "nanoid";
|
2023-11-02 16:45:28 +00:00
|
|
|
|
import { enqueueSnackbar } from "notistack";
|
2023-11-14 16:44:27 +00:00
|
|
|
|
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
2023-11-23 19:07:57 +00:00
|
|
|
|
import { RequestQueue } from "../../utils/requestQueue";
|
2023-12-10 17:41:57 +00:00
|
|
|
|
import { updateRootContentId } from "@root/quizes/actions";
|
|
|
|
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
2023-11-27 23:07:24 +00:00
|
|
|
|
import { QuestionsStore, useQuestionsStore } from "./store";
|
2023-12-14 13:56:26 +00:00
|
|
|
|
import { useUiTools } from "../uiTools/store";
|
2023-12-07 00:03:43 +00:00
|
|
|
|
import { withErrorBoundary } from "react-error-boundary";
|
2023-12-21 08:29:51 +00:00
|
|
|
|
import { QuizQuestionResult } from "@model/questionTypes/result";
|
2023-12-28 12:54:04 +00:00
|
|
|
|
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
|
|
|
|
|
2023-11-14 20:15:52 +00:00
|
|
|
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
2023-12-23 02:16:02 +00:00
|
|
|
|
const untypedResultQuestions = state.questions.filter(q => q.type === null);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
2023-11-16 16:41:25 +00:00
|
|
|
|
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
2023-12-07 22:56:31 +00:00
|
|
|
|
state.questions.push(...untypedResultQuestions);
|
2023-11-14 16:44:27 +00:00
|
|
|
|
}, {
|
2023-11-14 20:15:52 +00:00
|
|
|
|
type: "setQuestions",
|
|
|
|
|
questions,
|
2023-11-14 16:44:27 +00:00
|
|
|
|
});
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
2023-12-12 14:47:47 +00:00
|
|
|
|
export const createUntypedQuestion = (quizId: number, insertAfterQuestionId?: string) => setProducedState(state => {
|
|
|
|
|
const newUntypedQuestion = {
|
2023-11-29 13:49:52 +00:00
|
|
|
|
id: nanoid(),
|
|
|
|
|
quizId,
|
|
|
|
|
type: null,
|
|
|
|
|
title: "",
|
|
|
|
|
description: "",
|
|
|
|
|
deleted: false,
|
|
|
|
|
expanded: true,
|
2023-12-12 14:47:47 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (insertAfterQuestionId) {
|
|
|
|
|
const index = state.questions.findIndex(q => q.id === insertAfterQuestionId);
|
|
|
|
|
if (index === -1) return;
|
|
|
|
|
state.questions.splice(index + 1, 0, newUntypedQuestion);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.questions.push(newUntypedQuestion);
|
2023-11-17 15:42:49 +00:00
|
|
|
|
}, {
|
2023-11-29 13:49:52 +00:00
|
|
|
|
type: "createUntypedQuestion",
|
|
|
|
|
quizId,
|
2023-11-17 15:42:49 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
const removeQuestion = (questionId: string) => setProducedState(state => {
|
2023-11-16 16:41:25 +00:00
|
|
|
|
const index = state.questions.findIndex(q => q.id === questionId);
|
2023-11-27 23:07:24 +00:00
|
|
|
|
if (index === -1) return;
|
|
|
|
|
|
2023-11-16 16:41:25 +00:00
|
|
|
|
state.questions.splice(index, 1);
|
2023-11-15 18:38:02 +00:00
|
|
|
|
}, {
|
|
|
|
|
type: "removeQuestion",
|
|
|
|
|
questionId,
|
|
|
|
|
});
|
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
export const updateUntypedQuestion = (
|
|
|
|
|
questionId: string,
|
|
|
|
|
updateFn: (question: UntypedQuizQuestion) => void,
|
|
|
|
|
) => {
|
|
|
|
|
setProducedState(state => {
|
|
|
|
|
const question = state.questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question) return;
|
|
|
|
|
if (question.type !== null) throw new Error("Cannot update typed question, use 'updateQuestion' instead");
|
|
|
|
|
|
|
|
|
|
updateFn(question);
|
|
|
|
|
}, {
|
|
|
|
|
type: "updateUntypedQuestion",
|
|
|
|
|
questionId,
|
|
|
|
|
updateFn: updateFn.toString(),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const cleanQuestions = () => setProducedState(state => {
|
|
|
|
|
state.questions = [];
|
|
|
|
|
}, {
|
|
|
|
|
type: "cleanQuestions",
|
|
|
|
|
});
|
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
const setQuestionBackendId = (questionId: string, backendId: number) => setProducedState(state => {
|
2023-11-16 16:41:25 +00:00
|
|
|
|
const question = state.questions.find(q => q.id === questionId);
|
2023-11-02 16:45:28 +00:00
|
|
|
|
if (!question) return;
|
2023-11-29 13:49:52 +00:00
|
|
|
|
if (question.type === null) throw new Error("Cannot set backend id for untyped question");
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
question.backendId = backendId;
|
2023-11-02 16:45:28 +00:00
|
|
|
|
}, {
|
2023-11-27 23:07:24 +00:00
|
|
|
|
type: "setQuestionBackendId",
|
|
|
|
|
questionId: questionId,
|
|
|
|
|
backendId,
|
2023-11-02 16:45:28 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-12-08 13:36:00 +00:00
|
|
|
|
const updateQuestionOrders = () => {
|
|
|
|
|
const questions = useQuestionsStore.getState().questions.filter(
|
2023-12-09 11:31:16 +00:00
|
|
|
|
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
2023-12-08 13:36:00 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
questions.forEach((question, index) => {
|
|
|
|
|
updateQuestion(question.id, question => {
|
|
|
|
|
question.page = index;
|
|
|
|
|
}, true);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-16 16:41:25 +00:00
|
|
|
|
export const reorderQuestions = (
|
|
|
|
|
sourceIndex: number,
|
|
|
|
|
destinationIndex: number,
|
|
|
|
|
) => {
|
|
|
|
|
if (sourceIndex === destinationIndex) return;
|
|
|
|
|
|
|
|
|
|
setProducedState(state => {
|
|
|
|
|
const [removed] = state.questions.splice(sourceIndex, 1);
|
|
|
|
|
state.questions.splice(destinationIndex, 0, removed);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
}, {
|
|
|
|
|
type: "reorderQuestions",
|
|
|
|
|
sourceIndex,
|
|
|
|
|
destinationIndex,
|
2023-11-16 16:41:25 +00:00
|
|
|
|
});
|
2023-12-08 13:36:00 +00:00
|
|
|
|
|
|
|
|
|
updateQuestionOrders();
|
2023-11-16 16:41:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
export const toggleExpandQuestion = (questionId: string) => setProducedState(state => {
|
2023-11-16 16:41:25 +00:00
|
|
|
|
const question = state.questions.find(q => q.id === questionId);
|
2023-11-15 18:38:02 +00:00
|
|
|
|
if (!question) return;
|
|
|
|
|
|
|
|
|
|
question.expanded = !question.expanded;
|
2023-11-29 13:49:52 +00:00
|
|
|
|
}, {
|
|
|
|
|
type: "toggleExpandQuestion",
|
|
|
|
|
questionId,
|
2023-11-15 18:38:02 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-11-16 16:41:25 +00:00
|
|
|
|
export const collapseAllQuestions = () => setProducedState(state => {
|
|
|
|
|
state.questions.forEach(question => question.expanded = false);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
}, "collapseAllQuestions");
|
|
|
|
|
|
2023-12-12 19:05:30 +00:00
|
|
|
|
const DELETE_TIMEOUT = 5000;
|
|
|
|
|
|
|
|
|
|
export const deleteQuestionWithTimeout = (questionId: string, deleteFn: (questionId: string) => void) => setProducedState(state => {
|
|
|
|
|
const question = state.questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question) return;
|
|
|
|
|
if (question.type === null || question.type === "result") {
|
2023-12-14 16:50:02 +00:00
|
|
|
|
queueMicrotask(() => deleteFn(questionId));
|
2023-12-12 19:05:30 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
question.deleted = true;
|
|
|
|
|
clearTimeout(question.deleteTimeoutId);
|
|
|
|
|
question.deleteTimeoutId = window.setTimeout(() => {
|
|
|
|
|
deleteFn(questionId);
|
|
|
|
|
}, DELETE_TIMEOUT);
|
|
|
|
|
}, {
|
|
|
|
|
type: "deleteQuestionWithTimeout",
|
|
|
|
|
questionId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const cancelQuestionDeletion = (questionId: string) => setProducedState(state => {
|
|
|
|
|
const question = state.questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question || question.type === null || question.type === "result") return;
|
|
|
|
|
|
|
|
|
|
question.deleted = false;
|
|
|
|
|
clearTimeout(question.deleteTimeoutId);
|
|
|
|
|
}, {
|
|
|
|
|
type: "cancelQuestionDeletion",
|
|
|
|
|
questionId,
|
|
|
|
|
});
|
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
|
|
|
|
const REQUEST_DEBOUNCE = 200;
|
|
|
|
|
const requestQueue = new RequestQueue();
|
|
|
|
|
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
|
|
|
|
|
2023-12-27 07:25:30 +00:00
|
|
|
|
export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
2023-11-29 13:49:52 +00:00
|
|
|
|
questionId: string,
|
2023-12-13 18:19:12 +00:00
|
|
|
|
updateFn: (question: T) => void,
|
2023-12-08 13:36:00 +00:00
|
|
|
|
skipQueue = false,
|
2023-11-29 13:49:52 +00:00
|
|
|
|
) => {
|
|
|
|
|
setProducedState(state => {
|
2023-12-03 13:09:10 +00:00
|
|
|
|
const question = state.questions.find(q => q.id === questionId) || state.questions.find(q => q.type !== null && q.content.id === questionId);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
if (!question) return;
|
|
|
|
|
if (question.type === null) throw new Error("Cannot update untyped question, use 'updateUntypedQuestion' instead");
|
2023-12-02 16:23:40 +00:00
|
|
|
|
|
2023-12-13 18:19:12 +00:00
|
|
|
|
updateFn(question as T);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
}, {
|
|
|
|
|
type: "updateQuestion",
|
|
|
|
|
questionId,
|
|
|
|
|
updateFn: updateFn.toString(),
|
|
|
|
|
});
|
|
|
|
|
|
2023-12-02 09:59:31 +00:00
|
|
|
|
// clearTimeout(requestTimeoutId);
|
2023-12-08 13:36:00 +00:00
|
|
|
|
|
|
|
|
|
const request = async () => {
|
2023-12-03 13:09:10 +00:00
|
|
|
|
const q = useQuestionsStore.getState().questions.find(q => q.id === questionId) || useQuestionsStore.getState().questions.find(q => q.type !== null && q.content.id === questionId);
|
|
|
|
|
if (!q) return;
|
|
|
|
|
if (q.type === null) throw new Error("Cannot send update request for untyped question");
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
2023-12-08 13:36:00 +00:00
|
|
|
|
try {
|
2023-12-28 12:54:04 +00:00
|
|
|
|
const response = await questionApi.edit(questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)));
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
2023-12-09 22:03:53 +00:00
|
|
|
|
//Если мы делаем листочек веточкой - удаляем созданный к нему результ
|
2023-12-10 17:41:57 +00:00
|
|
|
|
const questionResult = useQuestionsStore.getState().questions.find(questionResult => questionResult.type === "result" && questionResult.content.rule.parentId === q.content.id);
|
|
|
|
|
if (questionResult && q.content.rule.default.length !== 0) deleteQuestion(questionResult.quizId);
|
2023-12-13 17:16:34 +00:00
|
|
|
|
|
|
|
|
|
if (q.backendId !== response.updated) {
|
|
|
|
|
console.warn(`Question backend id has changed from ${q.backendId} to ${response.updated}`);
|
|
|
|
|
}
|
2023-12-08 13:36:00 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
if (isAxiosCanceledError(error)) return;
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
2023-12-08 13:36:00 +00:00
|
|
|
|
devlog("Error editing question", { error, questionId });
|
|
|
|
|
enqueueSnackbar("Не удалось сохранить вопрос");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (skipQueue) {
|
|
|
|
|
request();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// requestTimeoutId = setTimeout(() => {
|
|
|
|
|
requestQueue.enqueue(request);
|
2023-12-02 09:59:31 +00:00
|
|
|
|
// }, REQUEST_DEBOUNCE);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
};
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
export const addQuestionVariant = (questionId: string) => {
|
|
|
|
|
updateQuestion(questionId, question => {
|
2023-11-15 18:38:02 +00:00
|
|
|
|
switch (question.type) {
|
|
|
|
|
case "variant":
|
|
|
|
|
case "emoji":
|
|
|
|
|
case "select":
|
|
|
|
|
case "images":
|
|
|
|
|
case "varimg":
|
2023-11-28 19:16:00 +00:00
|
|
|
|
question.content.variants.push(createQuestionVariant());
|
2023-11-15 18:38:02 +00:00
|
|
|
|
break;
|
2023-11-28 19:16:00 +00:00
|
|
|
|
default: throw new Error(`Cannot add variant to question of type "${question.type}"`);
|
2023-11-15 18:38:02 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
export const deleteQuestionVariant = (questionId: string, variantId: string) => {
|
|
|
|
|
updateQuestion(questionId, question => {
|
2023-11-15 18:38:02 +00:00
|
|
|
|
if (!("variants" in question.content)) return;
|
|
|
|
|
|
|
|
|
|
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
|
|
|
|
if (variantIndex === -1) return;
|
|
|
|
|
|
|
|
|
|
question.content.variants.splice(variantIndex, 1);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const setQuestionVariantField = (
|
2023-11-27 23:07:24 +00:00
|
|
|
|
questionId: string,
|
2023-11-15 18:38:02 +00:00
|
|
|
|
variantId: string,
|
|
|
|
|
field: keyof QuestionVariant,
|
|
|
|
|
value: QuestionVariant[keyof QuestionVariant],
|
|
|
|
|
) => {
|
2023-11-27 23:07:24 +00:00
|
|
|
|
updateQuestion(questionId, question => {
|
2023-11-15 18:38:02 +00:00
|
|
|
|
if (!("variants" in question.content)) return;
|
2023-12-01 14:33:55 +00:00
|
|
|
|
|
2023-11-15 18:38:02 +00:00
|
|
|
|
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
|
|
|
|
if (variantIndex === -1) return;
|
2023-12-01 14:33:55 +00:00
|
|
|
|
|
2023-11-15 18:38:02 +00:00
|
|
|
|
const variant = question.content.variants[variantIndex];
|
|
|
|
|
variant[field] = value;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const reorderQuestionVariants = (
|
2023-11-27 23:07:24 +00:00
|
|
|
|
questionId: string,
|
2023-11-15 18:38:02 +00:00
|
|
|
|
sourceIndex: number,
|
|
|
|
|
destinationIndex: number,
|
|
|
|
|
) => {
|
|
|
|
|
if (sourceIndex === destinationIndex) return;
|
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
updateQuestion(questionId, question => {
|
2023-11-15 18:38:02 +00:00
|
|
|
|
if (!("variants" in question.content)) return;
|
2023-12-01 14:33:55 +00:00
|
|
|
|
|
2023-11-15 18:38:02 +00:00
|
|
|
|
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
|
|
|
|
question.content.variants.splice(destinationIndex, 0, removed);
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
export const uploadQuestionImage = async (
|
2023-11-27 23:07:24 +00:00
|
|
|
|
questionId: string,
|
2023-12-01 18:05:59 +00:00
|
|
|
|
quizQid: string | undefined,
|
|
|
|
|
blob: Blob,
|
|
|
|
|
updateFn: (question: AnyTypedQuizQuestion, imageId: string) => void,
|
2023-11-16 16:41:25 +00:00
|
|
|
|
) => {
|
2023-12-01 18:05:59 +00:00
|
|
|
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question || !quizQid) return;
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
try {
|
|
|
|
|
const response = await quizApi.addImages(question.quizId, blob);
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
const values = Object.values(response);
|
|
|
|
|
if (values.length !== 1) {
|
|
|
|
|
console.warn("Error uploading image");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
const imageId = values[0];
|
|
|
|
|
const imageUrl = `https://squiz.pena.digital/squizimages/${quizQid}/${imageId}`;
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
updateQuestion(questionId, question => {
|
|
|
|
|
updateFn(question, imageUrl);
|
|
|
|
|
});
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
return imageUrl;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
devlog("Error uploading question image", error);
|
2023-11-16 16:41:25 +00:00
|
|
|
|
|
2023-12-01 18:05:59 +00:00
|
|
|
|
enqueueSnackbar("Не удалось загрузить изображение");
|
2023-12-01 14:33:55 +00:00
|
|
|
|
}
|
2023-11-16 16:41:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const setQuestionInnerName = (
|
2023-11-27 23:07:24 +00:00
|
|
|
|
questionId: string,
|
2023-11-16 16:41:25 +00:00
|
|
|
|
name: string,
|
|
|
|
|
) => {
|
2023-11-27 23:07:24 +00:00
|
|
|
|
updateQuestion(questionId, question => {
|
2023-11-16 16:41:25 +00:00
|
|
|
|
question.content.innerName = name;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
export const changeQuestionType = (
|
2023-11-27 23:07:24 +00:00
|
|
|
|
questionId: string,
|
2023-11-29 13:49:52 +00:00
|
|
|
|
type: QuestionType,
|
2023-11-02 16:45:28 +00:00
|
|
|
|
) => {
|
2023-11-29 13:49:52 +00:00
|
|
|
|
updateQuestion(questionId, question => {
|
2023-12-08 13:36:00 +00:00
|
|
|
|
const oldId = question.content.id;
|
|
|
|
|
const oldRule = question.content.rule;
|
|
|
|
|
oldRule.main = [];
|
2023-11-29 13:49:52 +00:00
|
|
|
|
question.type = type;
|
|
|
|
|
question.content = defaultQuestionByType[type].content;
|
2023-12-08 13:36:00 +00:00
|
|
|
|
question.content.id = oldId;
|
|
|
|
|
question.content.rule = oldRule;
|
2023-11-27 23:07:24 +00:00
|
|
|
|
});
|
2023-11-02 16:45:28 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
export const createTypedQuestion = async (
|
|
|
|
|
questionId: string,
|
|
|
|
|
type: QuestionType,
|
|
|
|
|
) => requestQueue.enqueue(async () => {
|
2023-12-08 13:36:00 +00:00
|
|
|
|
const questions = useQuestionsStore.getState().questions;
|
|
|
|
|
const question = questions.find(q => q.id === questionId);
|
2023-11-29 13:49:52 +00:00
|
|
|
|
if (!question) return;
|
|
|
|
|
if (question.type !== null) throw new Error("Cannot upgrade already typed question");
|
2023-12-03 13:09:10 +00:00
|
|
|
|
|
2023-12-10 17:41:57 +00:00
|
|
|
|
const untypedOrResultQuestionsLength = questions.filter(q => q.type === "result" || q.type === null).length;
|
|
|
|
|
|
2023-11-02 16:45:28 +00:00
|
|
|
|
try {
|
2023-11-29 13:49:52 +00:00
|
|
|
|
const createdQuestion = await questionApi.create({
|
|
|
|
|
quiz_id: question.quizId,
|
2023-11-16 16:41:25 +00:00
|
|
|
|
type,
|
2023-11-29 13:49:52 +00:00
|
|
|
|
title: question.title,
|
|
|
|
|
description: question.description,
|
2023-12-10 17:41:57 +00:00
|
|
|
|
page: questions.length - untypedOrResultQuestionsLength,
|
2023-12-08 11:53:41 +00:00
|
|
|
|
required: false,
|
2023-11-29 13:49:52 +00:00
|
|
|
|
content: JSON.stringify(defaultQuestionByType[type].content),
|
2023-11-14 16:44:27 +00:00
|
|
|
|
});
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
setProducedState(state => {
|
|
|
|
|
const questionIndex = state.questions.findIndex(q => q.id === questionId);
|
|
|
|
|
if (questionIndex !== -1) state.questions.splice(
|
|
|
|
|
questionIndex,
|
|
|
|
|
1,
|
|
|
|
|
rawQuestionToQuestion(createdQuestion)
|
|
|
|
|
);
|
|
|
|
|
}, {
|
|
|
|
|
type: "createTypedQuestion",
|
|
|
|
|
question,
|
|
|
|
|
});
|
2023-12-12 14:47:47 +00:00
|
|
|
|
|
|
|
|
|
updateQuestionOrders();
|
2023-11-02 16:45:28 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
devlog("Error creating question", error);
|
|
|
|
|
enqueueSnackbar("Не удалось создать вопрос");
|
|
|
|
|
}
|
2023-11-27 23:07:24 +00:00
|
|
|
|
});
|
|
|
|
|
|
2023-12-11 10:08:54 +00:00
|
|
|
|
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
|
2023-12-04 13:33:43 +00:00
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question) return;
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
2023-12-10 17:41:57 +00:00
|
|
|
|
|
|
|
|
|
|
2023-11-29 13:49:52 +00:00
|
|
|
|
if (question.type === null) {
|
|
|
|
|
removeQuestion(questionId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-02 16:45:28 +00:00
|
|
|
|
try {
|
2023-11-27 23:07:24 +00:00
|
|
|
|
await questionApi.delete(question.backendId);
|
2023-12-12 14:47:47 +00:00
|
|
|
|
|
2023-11-02 16:45:28 +00:00
|
|
|
|
removeQuestion(questionId);
|
2023-12-10 17:41:57 +00:00
|
|
|
|
|
|
|
|
|
updateQuestionOrders();
|
2023-11-02 16:45:28 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
devlog("Error deleting question", error);
|
|
|
|
|
enqueueSnackbar("Не удалось удалить вопрос");
|
|
|
|
|
}
|
2023-11-27 23:07:24 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const copyQuestion = async (questionId: string, quizId: number) => requestQueue.enqueue(async () => {
|
|
|
|
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
|
|
|
|
if (!question) return;
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
2023-12-04 13:33:43 +00:00
|
|
|
|
const frontId = nanoid();
|
2023-11-29 13:49:52 +00:00
|
|
|
|
if (question.type === null) {
|
|
|
|
|
const copiedQuestion = structuredClone(question);
|
2023-12-10 17:41:57 +00:00
|
|
|
|
copiedQuestion.id = frontId;
|
2023-11-29 13:49:52 +00:00
|
|
|
|
|
|
|
|
|
setProducedState(state => {
|
|
|
|
|
state.questions.push(copiedQuestion);
|
|
|
|
|
}, {
|
|
|
|
|
type: "copyQuestion",
|
|
|
|
|
questionId,
|
|
|
|
|
quizId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-15 18:38:02 +00:00
|
|
|
|
try {
|
2023-11-27 23:07:24 +00:00
|
|
|
|
const { updated: newQuestionId } = await questionApi.copy(question.backendId, quizId);
|
2023-11-15 18:38:02 +00:00
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
const copiedQuestion = structuredClone(question);
|
|
|
|
|
copiedQuestion.backendId = newQuestionId;
|
2023-12-08 13:36:00 +00:00
|
|
|
|
copiedQuestion.id = frontId;
|
|
|
|
|
copiedQuestion.content.id = frontId;
|
2023-12-11 10:08:54 +00:00
|
|
|
|
copiedQuestion.content.rule = { main: [], parentId: "", default: "", children: [] };
|
2023-11-15 18:38:02 +00:00
|
|
|
|
|
2023-11-27 23:07:24 +00:00
|
|
|
|
setProducedState(state => {
|
2023-11-16 16:41:25 +00:00
|
|
|
|
state.questions.push(copiedQuestion);
|
2023-11-15 18:38:02 +00:00
|
|
|
|
}, {
|
|
|
|
|
type: "copyQuestion",
|
2023-11-29 13:49:52 +00:00
|
|
|
|
questionId,
|
2023-11-15 18:38:02 +00:00
|
|
|
|
quizId,
|
|
|
|
|
});
|
2023-12-08 13:36:00 +00:00
|
|
|
|
|
|
|
|
|
updateQuestionOrders();
|
2023-11-15 18:38:02 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
devlog("Error copying question", error);
|
|
|
|
|
enqueueSnackbar("Не удалось скопировать вопрос");
|
|
|
|
|
}
|
2023-11-27 23:07:24 +00:00
|
|
|
|
});
|
2023-11-02 16:45:28 +00:00
|
|
|
|
|
|
|
|
|
function setProducedState<A extends string | { type: unknown; }>(
|
|
|
|
|
recipe: (state: QuestionsStore) => void,
|
|
|
|
|
action?: A,
|
|
|
|
|
) {
|
|
|
|
|
useQuestionsStore.setState(state => produce(state, recipe), false, action);
|
2023-11-29 15:45:15 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2023-12-14 09:40:53 +00:00
|
|
|
|
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
|
|
|
|
export const getQuestionById = (questionId: string | null) => {
|
|
|
|
|
if (questionId === null) return null;
|
|
|
|
|
return useQuestionsStore.getState().questions.find(q => q.id === questionId) || null;
|
|
|
|
|
};
|
2023-12-01 19:56:13 +00:00
|
|
|
|
export const getQuestionByContentId = (questionContentId: string | null) => {
|
|
|
|
|
if (questionContentId === null) return null;
|
2023-12-02 13:11:57 +00:00
|
|
|
|
return useQuestionsStore.getState().questions.find(q => {
|
2023-12-03 13:09:10 +00:00
|
|
|
|
if (q.type === null) return false;
|
2023-12-05 23:34:40 +00:00
|
|
|
|
|
2023-12-03 13:09:10 +00:00
|
|
|
|
return (q.content.id === questionContentId);
|
|
|
|
|
}) || null;
|
2023-12-01 19:56:13 +00:00
|
|
|
|
};
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
2023-12-14 09:40:53 +00:00
|
|
|
|
|
2023-12-04 13:33:43 +00:00
|
|
|
|
|
2023-12-07 00:03:43 +00:00
|
|
|
|
export const clearRuleForAll = () => {
|
2023-12-10 17:41:57 +00:00
|
|
|
|
const { questions } = useQuestionsStore.getState();
|
2023-12-27 07:25:30 +00:00
|
|
|
|
return Promise.allSettled(
|
|
|
|
|
questions.map(question => {
|
|
|
|
|
if (question.type !== null &&
|
2023-12-21 08:29:51 +00:00
|
|
|
|
(question.content.rule.main.length > 0
|
2023-12-27 07:25:30 +00:00
|
|
|
|
|| question.content.rule.default.length > 0
|
|
|
|
|
|| question.content.rule.parentId.length > 0)
|
|
|
|
|
&& question.type !== "result") {
|
|
|
|
|
updateQuestion(question.content.id, question => {
|
|
|
|
|
question.content.rule.parentId = "";
|
|
|
|
|
question.content.rule.main = [];
|
|
|
|
|
question.content.rule.default = "";
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
)
|
2023-12-10 17:41:57 +00:00
|
|
|
|
};
|
2023-12-04 16:33:50 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
export const createResult = async (
|
|
|
|
|
quizId: number,
|
|
|
|
|
parentContentId?: string
|
|
|
|
|
) => requestQueue.enqueue(async () => {
|
|
|
|
|
if (!quizId || !parentContentId) {
|
|
|
|
|
console.error("Нет данных для создания результата. quizId: ", quizId, ", quizId: ", parentContentId)
|
|
|
|
|
}
|
2023-12-07 21:30:26 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
//Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным
|
2023-12-27 07:25:30 +00:00
|
|
|
|
const question = useQuestionsStore.getState().questions.find(q => q.type !== null && q?.content.rule.parentId === parentContentId)
|
2023-12-07 21:30:26 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
if (question) {//существует, делаем активным
|
2023-12-07 21:30:26 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
updateQuestion(question.id, (q) => {
|
|
|
|
|
q.content.usage = true
|
|
|
|
|
})
|
2023-12-07 21:30:26 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
} else {//не существует, создаём
|
|
|
|
|
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content));
|
|
|
|
|
content.rule.parentId = parentContentId;
|
2023-12-27 07:25:30 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
try {
|
2023-12-27 07:25:30 +00:00
|
|
|
|
const createdQuestion: RawQuestion = await questionApi.create({
|
2023-12-21 08:29:51 +00:00
|
|
|
|
quiz_id: quizId,
|
|
|
|
|
type: "result",
|
|
|
|
|
title: "",
|
|
|
|
|
description: "",
|
|
|
|
|
page: 101,
|
|
|
|
|
required: true,
|
|
|
|
|
content: JSON.stringify(content),
|
|
|
|
|
});
|
2023-12-27 07:25:30 +00:00
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
setProducedState(state => {
|
|
|
|
|
state.questions.push(rawQuestionToQuestion(createdQuestion))
|
|
|
|
|
}, {
|
|
|
|
|
type: "createBackResult",
|
|
|
|
|
createdQuestion,
|
|
|
|
|
});
|
2023-12-28 16:09:54 +00:00
|
|
|
|
return createdQuestion
|
2023-12-21 08:29:51 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
devlog("Error creating question", error);
|
|
|
|
|
enqueueSnackbar("Не удалось создать вопрос");
|
|
|
|
|
}
|
2023-12-27 07:25:30 +00:00
|
|
|
|
}
|
2023-12-07 21:30:26 +00:00
|
|
|
|
});
|
2023-12-16 04:20:07 +00:00
|
|
|
|
|