import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; import type { AnyQuizQuestion, QuizQuestionType } from "../model/questionTypes/shared"; 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; openedModalSettings: string; } let isFirstPartialize = true; export const questionStore = create()( persist( devtools( () => ({ listQuestions: {}, openedModalSettings: "", }), { 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(({ id, deleted }) => { if (deleted) { const removedItemIndex = state.listQuestions[quizId].findIndex( (item) => item.id === 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, }; }, } ) ); 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 }); }; export const removeQuestionsByQuizId = (quizId: number) => setProducedState(state => { delete state.listQuestions[quizId]; }, "removeQuestionsByQuizId"); 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 (!("originalImageUrl" in variant)) return; if (variant.extendedText === url) return; if (variant.extendedText !== variant.originalImageUrl) URL.revokeObjectURL(variant.extendedText); variant.extendedText = url; }, { type: "setVariantImageUrl", quizId, questionIndex, variantIndex, url, }); 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 (!("originalImageUrl" in variant)) return; if (variant.originalImageUrl === url) return; URL.revokeObjectURL(variant.originalImageUrl); variant.originalImageUrl = url; }, { type: "setVariantOriginalImageUrl", quizId, questionIndex, variantIndex, url, }); export const updateQuestionsListDragAndDrop = ( quizId: number, updatedQuestions: AnyQuizQuestion[] ) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; questionStore.setState({ listQuestions: { ...questionListClone, [quizId]: updatedQuestions }, }); }; 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, }); export const createQuestion = ( quizId: number, questionType: QuizQuestionType = "nonselected", 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, { ...defaultObject, id } ); questionStore.setState({ listQuestions: newData }); } }; export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => { const listQuestions = { ...questionStore.getState()["listQuestions"] }; const copiedQuiz = listQuestions[quizId][copiedQuestionIndex]; copiedQuiz.id = getRandom(); listQuestions[quizId].splice( copiedQuestionIndex, 0, copiedQuiz ); questionStore.setState({ listQuestions }); }; export const removeQuestionForce = (quizId: number, removedId: number) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; const removedItemIndex = questionListClone[quizId].findIndex( ({ id }) => id === removedId ); questionListClone[quizId].splice(removedItemIndex, 1); questionStore.setState({ listQuestions: questionListClone }); }; export const removeQuestion = (quizId: number, index: number) => { const questionListClone = { ...questionStore.getState()["listQuestions"] }; questionListClone[quizId][index].deleted = true; questionStore.setState({ listQuestions: questionListClone }); }; export const resetSomeField = (data: Record) => { questionStore.setState(data); }; export const findQuestionById = (quizId: number) => { let found = null; questionStore .getState() ["listQuestions"][quizId].some((quiz: AnyQuizQuestion, index: number) => { if (quiz.id === 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); }