import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; import type { AnyTypedQuizQuestion } from "@frontend/squzanswerer"; import { QuestionType } from "@model/question/question"; import { produce, setAutoFreeze } from "immer"; 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"; setAutoFreeze(false); interface QuestionStore { listQuestions: Record; } let isFirstPartialize = true; /** @deprecated */ export const questionStore = create()( 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); } }); }); } 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, }; }, } ) ); /** @deprecated */ export const updateQuestionsList = (quizId: number, index: number, data: Partial) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; questionListClone[quizId][index] = { ...questionListClone[quizId][index], ...data, }; questionStore.setState({ listQuestions: questionListClone }); }; /** @deprecated */ export const updateQuestion = ( 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, } ); /** @deprecated */ export const removeQuestionsByQuizId = (quizId: number) => setProducedState((state) => { delete state.listQuestions[quizId]; }, "removeQuestionsByQuizId"); /** @deprecated */ export const setVariantImageUrl = (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, } ); /** @deprecated */ export const setVariantOriginalImageUrl = (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.originalImageUrl === url) return; if (variant.originalImageUrl) { URL.revokeObjectURL(variant.originalImageUrl); } variant.originalImageUrl = url; }, { type: "setVariantOriginalImageUrl", quizId, questionIndex, variantIndex, url, } ); /** @deprecated */ export const setPageQuestionPicture = (quizId: number, questionIndex: number, url: string) => setProducedState((state) => { const question = state.listQuestions[quizId][questionIndex]; if (question.type !== "page") return; if (question.content.picture === url) return; if (question.content.picture !== question.content.originalPicture) URL.revokeObjectURL(question.content.picture); question.content.picture = url; }); /** @deprecated */ export const setPageQuestionOriginalPicture = (quizId: number, questionIndex: number, url: string) => setProducedState((state) => { 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; }); /** @deprecated */ export const setQuestionBackgroundImage = (quizId: number, questionIndex: number, url: string) => setProducedState((state) => { const question = state.listQuestions[quizId][questionIndex]; if (question.content.back === url) return; if (question.content.back !== question.content.originalBack) URL.revokeObjectURL(question.content.back); question.content.back = url; }); /** @deprecated */ export const setQuestionOriginalBackgroundImage = (quizId: number, questionIndex: number, url: string) => setProducedState((state) => { const question = state.listQuestions[quizId][questionIndex]; if (question.content.originalBack === url) return; URL.revokeObjectURL(question.content.originalBack); question.content.originalBack = url; }); /** @deprecated */ export const updateQuestionsListDragAndDrop = (quizId: number, updatedQuestions: AnyTypedQuizQuestion[]) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; questionStore.setState({ listQuestions: { ...questionListClone, [quizId]: updatedQuestions }, }); }; /** @deprecated */ export const reorderVariants = (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, } ); /** @deprecated */ export const createQuestion = (quizId: number, questionType: QuestionType = "variant", placeIndex = -1) => { 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 }); } }; /** @deprecated */ export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => { const listQuestions = { ...questionStore.getState()["listQuestions"] }; const copiedQuiz = { ...listQuestions[quizId][copiedQuestionIndex] }; listQuestions[quizId].splice(copiedQuestionIndex, 0, { ...copiedQuiz, backendId: getRandom(), }); questionStore.setState({ listQuestions }); }; /** @deprecated */ export const removeQuestionForce = (quizId: number, removedId: number) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; const removedItemIndex = questionListClone[quizId].findIndex(({ backendId: id }) => id === removedId); questionListClone[quizId].splice(removedItemIndex, 1); questionStore.setState({ listQuestions: questionListClone }); }; /** @deprecated */ export const removeQuestion = (quizId: number, index: number) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; questionListClone[quizId][index].deleted = true; questionStore.setState({ listQuestions: questionListClone }); }; /** @deprecated */ export const findQuestionById = (quizId: number) => { let found = null; questionStore.getState()["listQuestions"][quizId].some((quiz: AnyTypedQuizQuestion, index: number) => { if (quiz.backendId === quizId) { found = { quiz, index }; return true; } return false; }); return found; }; function getRandom() { const min = Math.ceil(1000000); const max = Math.floor(10000000); return Math.floor(Math.random() * (max - min)) + min; } function setProducedState(recipe: (state: QuestionStore) => void, action?: A) { questionStore.setState((state) => produce(state, recipe), false, action); }