import { quizApi } from "@api/quiz"; import { devlog, getMessageFromFetchError } from "@frontend/kitui"; import { quizToEditQuizRequest } from "@model/quiz/edit"; import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz"; import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings"; import { produce } from "immer"; import { enqueueSnackbar } from "notistack"; import { NavigateFunction } from "react-router-dom"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { RequestQueue } from "../../utils/requestQueue"; import { QuizStore, useQuizStore } from "./store"; import { createUntypedQuestion } from "@root/questions/actions"; import { useCurrentQuiz } from "./hooks" export const setEditQuizId = (quizId: number | null) => setProducedState(state => { state.editQuizId = quizId; }, { type: "setEditQuizId", quizId, }); export const resetEditConfig = () => setProducedState(state => { state.editQuizId = null; state.currentStep = 0; }, "resetEditConfig"); export const setQuizes = (quizes: RawQuiz[] | null) => setProducedState(state => { state.quizes = quizes?.map(rawQuizToQuiz) ?? []; }, { type: "setQuizes", quizes, }); const addQuiz = (quiz: Quiz) => setProducedState(state => { state.quizes.push(quiz); }, { type: "addQuiz", quiz, }); const removeQuiz = (quizId: string) => setProducedState(state => { const index = state.quizes.findIndex(q => q.id === quizId); if (index === -1) return; state.quizes.splice(index, 1); }, { type: "removeQuiz", quizId, }); const setQuizBackendId = (quizId: string, backendId: number) => setProducedState(state => { const quiz = state.quizes.find(q => q.id === quizId); if (!quiz) return; quiz.backendId = backendId; }, { type: "setQuizBackendId", quizId, backendId, }); export const incrementCurrentStep = () => setProducedState(state => { state.currentStep = Math.min(maxQuizSetupSteps - 1, state.currentStep + 1); }, { type: "incrementCurrentStep", }); export const decrementCurrentStep = () => setProducedState(state => { state.currentStep = Math.max(0, state.currentStep - 1); }, { type: "decrementCurrentStep", }); export const setCurrentStep = (step: number) => setProducedState(state => { state.currentStep = Math.max(0, Math.min(maxQuizSetupSteps - 1, step)); }, { type: "setCurrentStep", step, }); export const setQuizType = ( quizId: string, quizType: QuizConfig["type"], ) => { updateQuiz( quizId, quiz => { quiz.config.type = quizType; }, ); }; export const setQuizStartpageType = ( quizId: string, startpageType: QuizConfig["startpageType"], ) => { updateQuiz( quizId, quiz => { quiz.config.startpageType = startpageType; }, ); }; const REQUEST_DEBOUNCE = 200; const requestQueue = new RequestQueue(); let requestTimeoutId: ReturnType; export const updateQuiz = ( quizId: string | null | undefined, updateFn: (quiz: Quiz) => void, ) => { if (!quizId) return; setProducedState(state => { const quiz = state.quizes.find(q => q.id === quizId); if (!quiz) return; updateFn(quiz); }, { type: "updateQuiz", quizId, updateFn: updateFn.toString(), }); clearTimeout(requestTimeoutId); requestTimeoutId = setTimeout(async () => { requestQueue.enqueue(async () => { const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId); if (!quiz) return; const response = await quizApi.edit(quizToEditQuizRequest(quiz)); setQuizBackendId(quizId, response.updated); setEditQuizId(response.updated); }).catch(error => { if (isAxiosCanceledError(error)) return; devlog("Error editing quiz", error, quizId); enqueueSnackbar("Не удалось сохранить настройки квиза"); }); }, REQUEST_DEBOUNCE); }; export const createQuiz = async (navigate: NavigateFunction) => requestQueue.enqueue(async () => { try { const rawQuiz = await quizApi.create(); const quiz = rawQuizToQuiz(rawQuiz); addQuiz(quiz); setEditQuizId(quiz.backendId); navigate("/edit"); await createUntypedQuestion(rawQuiz.id); } catch (error) { devlog("Error creating quiz", error); const message = getMessageFromFetchError(error) ?? ""; enqueueSnackbar(`Не удалось создать квиз. ${message}`); } }); export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async () => { const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId); if (!quiz) return; try { await quizApi.delete(quiz.backendId); removeQuiz(quizId); } catch (error) { devlog("Error deleting quiz", error); const message = getMessageFromFetchError(error) ?? ""; enqueueSnackbar(`Не удалось удалить квиз. ${message}`); } }); export const updateRootContentId = (quizId: string, id:string) => updateQuiz( quizId, quiz => { quiz.config.haveRoot = id }, ); // TODO copy quiz export const uploadQuizImage = async ( quizId: string, blob: Blob, updateFn: (quiz: Quiz, imageId: string) => void, ) => { const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId); if (!quiz) return; try { const response = await quizApi.addImages(quiz.backendId, blob); const values = Object.values(response); if (values.length !== 1) { console.warn("Error uploading image"); return; } const imageId = values[0]; updateQuiz(quizId, quiz => { updateFn(quiz, `https://squiz.pena.digital/squizimages/${quiz.qid}/${imageId}`); }); } catch (error) { devlog("Error uploading quiz image", error); enqueueSnackbar("Не удалось загрузить изображение"); } }; function setProducedState( recipe: (state: QuizStore) => void, action?: A, ) { useQuizStore.setState(state => produce(state, recipe), false, action); }