2023-08-10 13:45:44 +00:00
|
|
|
import { create } from "zustand";
|
2023-10-21 13:16:57 +00:00
|
|
|
import { devtools, persist } from "zustand/middleware";
|
2023-10-02 19:43:07 +00:00
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
import type { AnyTypedQuizQuestion } from "../model/questionTypes/shared";
|
2023-10-02 19:43:07 +00:00
|
|
|
|
2023-11-15 18:38:02 +00:00
|
|
|
import { QuestionType } from "@model/question/question";
|
2023-10-21 13:16:57 +00:00
|
|
|
import { produce, setAutoFreeze } from "immer";
|
2023-10-03 14:03:57 +00:00
|
|
|
import { QUIZ_QUESTION_BASE } from "../constants/base";
|
|
|
|
import { QUIZ_QUESTION_DATE } from "../constants/date";
|
|
|
|
import { QUIZ_QUESTION_EMOJI } from "../constants/emoji";
|
|
|
|
import { QUIZ_QUESTION_FILE } from "../constants/file";
|
|
|
|
import { QUIZ_QUESTION_IMAGES } from "../constants/images";
|
|
|
|
import { QUIZ_QUESTION_NUMBER } from "../constants/number";
|
|
|
|
import { QUIZ_QUESTION_PAGE } from "../constants/page";
|
|
|
|
import { QUIZ_QUESTION_RATING } from "../constants/rating";
|
|
|
|
import { QUIZ_QUESTION_SELECT } from "../constants/select";
|
|
|
|
import { QUIZ_QUESTION_TEXT } from "../constants/text";
|
|
|
|
import { QUIZ_QUESTION_VARIANT } from "../constants/variant";
|
|
|
|
import { QUIZ_QUESTION_VARIMG } from "../constants/varimg";
|
2023-10-13 12:11:00 +00:00
|
|
|
|
|
|
|
setAutoFreeze(false);
|
2023-07-20 22:02:20 +00:00
|
|
|
|
2023-06-28 23:32:43 +00:00
|
|
|
interface QuestionStore {
|
2023-12-31 02:53:25 +00:00
|
|
|
listQuestions: Record<string, AnyTypedQuizQuestion[]>;
|
2023-06-28 23:32:43 +00:00
|
|
|
}
|
|
|
|
|
2023-09-27 14:14:48 +00:00
|
|
|
let isFirstPartialize = true;
|
2023-09-20 12:42:14 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-06-28 23:32:43 +00:00
|
|
|
export const questionStore = create<QuestionStore>()(
|
2023-12-31 02:53:25 +00:00
|
|
|
persist(
|
|
|
|
devtools(
|
|
|
|
() => ({
|
|
|
|
listQuestions: {},
|
|
|
|
}),
|
|
|
|
{
|
|
|
|
name: "Question",
|
|
|
|
enabled: process.env.NODE_ENV === "development",
|
|
|
|
trace: process.env.NODE_ENV === "development",
|
|
|
|
actionsBlacklist: "ignored",
|
|
|
|
},
|
|
|
|
),
|
|
|
|
{
|
|
|
|
name: "question",
|
|
|
|
partialize: (state: QuestionStore) => {
|
|
|
|
if (isFirstPartialize) {
|
|
|
|
isFirstPartialize = false;
|
|
|
|
|
|
|
|
Object.keys(state.listQuestions).forEach((quizId) => {
|
|
|
|
[...state.listQuestions[quizId]].forEach(
|
|
|
|
({ backendId: id, deleted }) => {
|
|
|
|
if (deleted) {
|
|
|
|
const removedItemIndex = state.listQuestions[
|
|
|
|
quizId
|
|
|
|
].findIndex((item) => item.backendId === id);
|
|
|
|
|
|
|
|
state.listQuestions[quizId].splice(removedItemIndex, 1);
|
2023-10-16 10:20:07 +00:00
|
|
|
}
|
2023-12-31 02:53:25 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-10-21 13:16:57 +00:00
|
|
|
}
|
2023-12-31 02:53:25 +00:00
|
|
|
|
|
|
|
return state;
|
|
|
|
},
|
|
|
|
merge: (persistedState, currentState) => {
|
|
|
|
const state = persistedState as QuestionStore;
|
|
|
|
|
|
|
|
// replace blob urls with ""
|
|
|
|
Object.values(state.listQuestions).forEach((questions) => {
|
|
|
|
questions.forEach((question) => {
|
|
|
|
if (question.type === "page") {
|
|
|
|
if (question.content.picture.startsWith("blob:")) {
|
|
|
|
question.content.picture = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (question.type === "images") {
|
|
|
|
question.content.variants.forEach((variant) => {
|
|
|
|
if (variant.extendedText.startsWith("blob:")) {
|
|
|
|
variant.extendedText = "";
|
|
|
|
}
|
|
|
|
if (variant.originalImageUrl?.startsWith("blob:")) {
|
|
|
|
variant.originalImageUrl = "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (question.type === "varimg") {
|
|
|
|
question.content.variants.forEach((variant) => {
|
|
|
|
if (variant.extendedText.startsWith("blob:")) {
|
|
|
|
variant.extendedText = "";
|
|
|
|
}
|
|
|
|
if (variant.originalImageUrl?.startsWith("blob:")) {
|
|
|
|
variant.originalImageUrl = "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
...currentState,
|
|
|
|
...state,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
),
|
2023-08-10 13:45:44 +00:00
|
|
|
);
|
2023-11-20 17:22:13 +00:00
|
|
|
|
|
|
|
/** @deprecated */
|
2023-11-29 13:49:52 +00:00
|
|
|
export const updateQuestionsList = <T = AnyTypedQuizQuestion>(
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
index: number,
|
|
|
|
data: Partial<T>,
|
2023-09-06 11:51:08 +00:00
|
|
|
) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
|
|
|
questionListClone[quizId][index] = {
|
|
|
|
...questionListClone[quizId][index],
|
|
|
|
...data,
|
|
|
|
};
|
|
|
|
questionStore.setState({ listQuestions: questionListClone });
|
2023-07-27 17:30:55 +00:00
|
|
|
};
|
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-11-29 13:49:52 +00:00
|
|
|
export const updateQuestion = <T extends AnyTypedQuizQuestion>(
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
recipe: (question: T) => void,
|
|
|
|
) =>
|
|
|
|
setProducedState(
|
|
|
|
(state) => {
|
|
|
|
const question = state.listQuestions[quizId][questionIndex] as T;
|
|
|
|
|
|
|
|
recipe(question);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "updateQuestion",
|
|
|
|
quizId,
|
|
|
|
questionIndex,
|
|
|
|
recipe,
|
|
|
|
},
|
|
|
|
);
|
2023-10-24 13:02:09 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-12-31 02:53:25 +00:00
|
|
|
export const removeQuestionsByQuizId = (quizId: number) =>
|
|
|
|
setProducedState((state) => {
|
2023-10-23 15:33:56 +00:00
|
|
|
delete state.listQuestions[quizId];
|
2023-12-31 02:53:25 +00:00
|
|
|
}, "removeQuestionsByQuizId");
|
2023-10-23 15:33:56 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-23 15:48:27 +00:00
|
|
|
export const setVariantImageUrl = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
variantIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState(
|
|
|
|
(state) => {
|
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
if (!("variants" in question.content)) return;
|
|
|
|
|
|
|
|
const variant = question.content.variants[variantIndex];
|
|
|
|
|
|
|
|
if (variant.extendedText === url) return;
|
|
|
|
|
|
|
|
if (variant.extendedText !== variant.originalImageUrl)
|
|
|
|
URL.revokeObjectURL(variant.extendedText);
|
|
|
|
variant.extendedText = url;
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "setVariantImageUrl",
|
|
|
|
quizId,
|
|
|
|
questionIndex,
|
|
|
|
variantIndex,
|
|
|
|
url,
|
|
|
|
},
|
|
|
|
);
|
2023-10-23 15:48:27 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-23 15:48:27 +00:00
|
|
|
export const setVariantOriginalImageUrl = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
variantIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState(
|
|
|
|
(state) => {
|
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
if (!("variants" in question.content)) return;
|
2023-10-23 15:48:27 +00:00
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
const variant = question.content.variants[variantIndex];
|
2023-10-23 15:48:27 +00:00
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
if (variant.originalImageUrl === url) return;
|
2023-10-23 15:48:27 +00:00
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
if (variant.originalImageUrl) {
|
2023-12-01 14:33:55 +00:00
|
|
|
URL.revokeObjectURL(variant.originalImageUrl);
|
2023-12-31 02:53:25 +00:00
|
|
|
}
|
|
|
|
variant.originalImageUrl = url;
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "setVariantOriginalImageUrl",
|
|
|
|
quizId,
|
|
|
|
questionIndex,
|
|
|
|
variantIndex,
|
|
|
|
url,
|
|
|
|
},
|
|
|
|
);
|
2023-10-23 15:48:27 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-24 13:02:09 +00:00
|
|
|
export const setPageQuestionPicture = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState((state) => {
|
2023-10-24 13:02:09 +00:00
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
if (question.type !== "page") return;
|
|
|
|
|
|
|
|
if (question.content.picture === url) return;
|
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
if (question.content.picture !== question.content.originalPicture)
|
|
|
|
URL.revokeObjectURL(question.content.picture);
|
2023-10-24 13:02:09 +00:00
|
|
|
question.content.picture = url;
|
2023-12-31 02:53:25 +00:00
|
|
|
});
|
2023-10-24 13:02:09 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-24 13:02:09 +00:00
|
|
|
export const setPageQuestionOriginalPicture = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState((state) => {
|
2023-10-24 13:02:09 +00:00
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
if (question.type !== "page") return;
|
|
|
|
|
|
|
|
if (question.content.originalPicture === url) return;
|
|
|
|
|
|
|
|
URL.revokeObjectURL(question.content.originalPicture);
|
|
|
|
question.content.originalPicture = url;
|
2023-12-31 02:53:25 +00:00
|
|
|
});
|
2023-10-24 13:02:09 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-24 15:18:31 +00:00
|
|
|
export const setQuestionBackgroundImage = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState((state) => {
|
2023-10-24 15:18:31 +00:00
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
|
|
|
|
if (question.content.back === url) return;
|
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
if (question.content.back !== question.content.originalBack)
|
|
|
|
URL.revokeObjectURL(question.content.back);
|
2023-10-24 15:18:31 +00:00
|
|
|
question.content.back = url;
|
2023-12-31 02:53:25 +00:00
|
|
|
});
|
2023-10-24 15:18:31 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-24 15:18:31 +00:00
|
|
|
export const setQuestionOriginalBackgroundImage = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
url: string,
|
|
|
|
) =>
|
|
|
|
setProducedState((state) => {
|
2023-10-24 15:18:31 +00:00
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
|
|
|
|
if (question.content.originalBack === url) return;
|
|
|
|
|
|
|
|
URL.revokeObjectURL(question.content.originalBack);
|
|
|
|
question.content.originalBack = url;
|
2023-12-31 02:53:25 +00:00
|
|
|
});
|
2023-10-24 15:18:31 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-08-11 06:15:04 +00:00
|
|
|
export const updateQuestionsListDragAndDrop = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
updatedQuestions: AnyTypedQuizQuestion[],
|
2023-08-11 06:15:04 +00:00
|
|
|
) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
|
|
|
questionStore.setState({
|
|
|
|
listQuestions: { ...questionListClone, [quizId]: updatedQuestions },
|
|
|
|
});
|
2023-07-27 17:30:55 +00:00
|
|
|
};
|
2023-07-20 22:02:20 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-23 15:48:27 +00:00
|
|
|
export const reorderVariants = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionIndex: number,
|
|
|
|
sourceIndex: number,
|
|
|
|
destinationIndex: number,
|
|
|
|
) =>
|
|
|
|
setProducedState(
|
|
|
|
(state) => {
|
|
|
|
if (sourceIndex === destinationIndex) return;
|
|
|
|
|
|
|
|
const question = state.listQuestions[quizId][questionIndex];
|
|
|
|
if (!("variants" in question.content)) return;
|
|
|
|
|
|
|
|
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
|
|
|
question.content.variants.splice(destinationIndex, 0, removed);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: sourceIndex === destinationIndex ? "reorderVariants" : "ignored",
|
|
|
|
quizId,
|
|
|
|
questionIndex,
|
|
|
|
sourceIndex,
|
|
|
|
destinationIndex,
|
|
|
|
},
|
|
|
|
);
|
2023-08-18 11:16:56 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-10-02 19:43:07 +00:00
|
|
|
export const createQuestion = (
|
2023-12-31 02:53:25 +00:00
|
|
|
quizId: number,
|
|
|
|
questionType: QuestionType = "variant",
|
|
|
|
placeIndex = -1,
|
2023-10-02 19:43:07 +00:00
|
|
|
) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const id = getRandom();
|
|
|
|
const newData = { ...questionStore.getState()["listQuestions"] };
|
|
|
|
|
|
|
|
if (!newData[quizId]) {
|
|
|
|
newData[quizId] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultObject = [
|
|
|
|
QUIZ_QUESTION_BASE,
|
|
|
|
QUIZ_QUESTION_DATE,
|
|
|
|
QUIZ_QUESTION_EMOJI,
|
|
|
|
QUIZ_QUESTION_FILE,
|
|
|
|
QUIZ_QUESTION_IMAGES,
|
|
|
|
QUIZ_QUESTION_NUMBER,
|
|
|
|
QUIZ_QUESTION_PAGE,
|
|
|
|
QUIZ_QUESTION_RATING,
|
|
|
|
QUIZ_QUESTION_SELECT,
|
|
|
|
QUIZ_QUESTION_TEXT,
|
|
|
|
QUIZ_QUESTION_VARIANT,
|
|
|
|
QUIZ_QUESTION_VARIMG,
|
|
|
|
].find((defaultObjectItem) => defaultObjectItem.type === questionType);
|
|
|
|
|
|
|
|
if (defaultObject) {
|
|
|
|
newData[quizId].splice(
|
|
|
|
placeIndex < 0 ? newData[quizId].length : placeIndex,
|
|
|
|
0,
|
|
|
|
{ ...JSON.parse(JSON.stringify(defaultObject)), backendId: id },
|
|
|
|
);
|
|
|
|
|
|
|
|
questionStore.setState({ listQuestions: newData });
|
|
|
|
}
|
2023-07-27 17:30:55 +00:00
|
|
|
};
|
2023-07-20 22:02:20 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-09-06 13:20:12 +00:00
|
|
|
export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const listQuestions = { ...questionStore.getState()["listQuestions"] };
|
2023-08-11 07:25:28 +00:00
|
|
|
|
2023-10-16 10:20:07 +00:00
|
|
|
const copiedQuiz = { ...listQuestions[quizId][copiedQuestionIndex] };
|
|
|
|
listQuestions[quizId].splice(copiedQuestionIndex, 0, {
|
|
|
|
...copiedQuiz,
|
2023-11-27 23:07:24 +00:00
|
|
|
backendId: getRandom(),
|
2023-10-16 10:20:07 +00:00
|
|
|
});
|
2023-08-11 07:25:28 +00:00
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
questionStore.setState({ listQuestions });
|
2023-08-11 07:25:28 +00:00
|
|
|
};
|
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-09-27 14:14:48 +00:00
|
|
|
export const removeQuestionForce = (quizId: number, removedId: number) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
|
|
|
const removedItemIndex = questionListClone[quizId].findIndex(
|
|
|
|
({ backendId: id }) => id === removedId,
|
|
|
|
);
|
|
|
|
questionListClone[quizId].splice(removedItemIndex, 1);
|
|
|
|
questionStore.setState({ listQuestions: questionListClone });
|
2023-09-27 14:14:48 +00:00
|
|
|
};
|
2023-09-06 11:51:08 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-09-27 14:14:48 +00:00
|
|
|
export const removeQuestion = (quizId: number, index: number) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
|
|
|
questionListClone[quizId][index].deleted = true;
|
|
|
|
questionStore.setState({ listQuestions: questionListClone });
|
2023-07-27 17:30:55 +00:00
|
|
|
};
|
2023-07-20 22:02:20 +00:00
|
|
|
|
2023-11-20 17:22:13 +00:00
|
|
|
/** @deprecated */
|
2023-09-06 13:20:12 +00:00
|
|
|
export const findQuestionById = (quizId: number) => {
|
2023-12-31 02:53:25 +00:00
|
|
|
let found = null;
|
|
|
|
questionStore
|
|
|
|
.getState()
|
|
|
|
["listQuestions"][quizId].some(
|
|
|
|
(quiz: AnyTypedQuizQuestion, index: number) => {
|
2023-11-27 23:07:24 +00:00
|
|
|
if (quiz.backendId === quizId) {
|
2023-12-31 02:53:25 +00:00
|
|
|
found = { quiz, index };
|
|
|
|
return true;
|
2023-10-21 13:16:57 +00:00
|
|
|
}
|
|
|
|
return false;
|
2023-12-31 02:53:25 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
return found;
|
2023-07-27 17:30:55 +00:00
|
|
|
};
|
2023-07-11 10:43:04 +00:00
|
|
|
|
2023-10-15 00:40:40 +00:00
|
|
|
function getRandom() {
|
2023-12-31 02:53:25 +00:00
|
|
|
const min = Math.ceil(1000000);
|
|
|
|
const max = Math.floor(10000000);
|
|
|
|
return Math.floor(Math.random() * (max - min)) + min;
|
2023-10-21 13:16:57 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 02:53:25 +00:00
|
|
|
function setProducedState<A extends string | { type: unknown }>(
|
|
|
|
recipe: (state: QuestionStore) => void,
|
|
|
|
action?: A,
|
2023-10-21 13:16:57 +00:00
|
|
|
) {
|
2023-12-31 02:53:25 +00:00
|
|
|
questionStore.setState((state) => produce(state, recipe), false, action);
|
2023-10-16 10:20:07 +00:00
|
|
|
}
|