frontPanel/src/stores/quizes/actions.ts

224 lines
6.2 KiB
TypeScript
Raw Normal View History

2023-11-13 18:04:51 +00:00
import { quizApi } from "@api/quiz";
import { devlog, getMessageFromFetchError } from "@frontend/kitui";
2023-11-27 23:07:24 +00:00
import { quizToEditQuizRequest } from "@model/quiz/edit";
2023-11-13 18:04:51 +00:00
import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz";
2023-11-27 23:07:24 +00:00
import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings";
2023-11-13 18:04:51 +00:00
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";
2023-11-29 13:49:52 +00:00
import { createUntypedQuestion } from "@root/questions/actions";
2023-12-04 13:33:43 +00:00
import { useCurrentQuiz } from "./hooks"
2023-11-13 18:04:51 +00:00
2023-11-14 13:37:20 +00:00
export const setEditQuizId = (quizId: number | null) => setProducedState(state => {
state.editQuizId = quizId;
2023-11-17 15:42:49 +00:00
}, {
type: "setEditQuizId",
quizId,
2023-11-14 13:37:20 +00:00
});
2023-11-14 14:22:10 +00:00
export const resetEditConfig = () => setProducedState(state => {
state.editQuizId = null;
2023-11-27 23:07:24 +00:00
state.currentStep = 0;
2023-11-29 13:49:52 +00:00
}, "resetEditConfig");
2023-11-14 14:22:10 +00:00
2023-11-13 18:04:51 +00:00
export const setQuizes = (quizes: RawQuiz[] | null) => setProducedState(state => {
2023-11-27 23:07:24 +00:00
state.quizes = quizes?.map(rawQuizToQuiz) ?? [];
2023-11-13 18:04:51 +00:00
}, {
type: "setQuizes",
quizes,
});
2023-11-27 23:07:24 +00:00
const addQuiz = (quiz: Quiz) => setProducedState(state => {
state.quizes.push(quiz);
2023-11-13 18:04:51 +00:00
}, {
2023-11-27 23:07:24 +00:00
type: "addQuiz",
2023-11-13 18:04:51 +00:00
quiz,
});
2023-11-27 23:07:24 +00:00
const removeQuiz = (quizId: string) => setProducedState(state => {
const index = state.quizes.findIndex(q => q.id === quizId);
if (index === -1) return;
state.quizes.splice(index, 1);
2023-11-13 18:04:51 +00:00
}, {
type: "removeQuiz",
quizId,
});
2023-11-27 23:07:24 +00:00
const setQuizBackendId = (quizId: string, backendId: number) => setProducedState(state => {
const quiz = state.quizes.find(q => q.id === quizId);
2023-11-13 18:04:51 +00:00
if (!quiz) return;
2023-11-27 23:07:24 +00:00
quiz.backendId = backendId;
2023-11-13 18:04:51 +00:00
}, {
2023-11-27 23:07:24 +00:00
type: "setQuizBackendId",
2023-11-13 18:04:51 +00:00
quizId,
2023-11-27 23:07:24 +00:00
backendId,
2023-11-13 18:04:51 +00:00
});
export const incrementCurrentStep = () => setProducedState(state => {
2023-11-27 23:07:24 +00:00
state.currentStep = Math.min(maxQuizSetupSteps - 1, state.currentStep + 1);
2023-11-14 13:10:41 +00:00
}, {
type: "incrementCurrentStep",
2023-11-13 18:04:51 +00:00
});
export const decrementCurrentStep = () => setProducedState(state => {
2023-11-27 23:07:24 +00:00
state.currentStep = Math.max(0, state.currentStep - 1);
2023-11-14 13:10:41 +00:00
}, {
type: "decrementCurrentStep",
2023-11-13 18:04:51 +00:00
});
export const setCurrentStep = (step: number) => setProducedState(state => {
2023-11-27 23:07:24 +00:00
state.currentStep = Math.max(0, Math.min(maxQuizSetupSteps - 1, step));
2023-11-29 13:49:52 +00:00
}, {
type: "setCurrentStep",
step,
2023-11-13 18:04:51 +00:00
});
export const setQuizType = (
2023-11-27 23:07:24 +00:00
quizId: string,
2023-11-13 18:04:51 +00:00
quizType: QuizConfig["type"],
) => {
2023-11-27 23:07:24 +00:00
updateQuiz(
2023-11-13 18:04:51 +00:00
quizId,
quiz => {
quiz.config.type = quizType;
},
);
};
export const setQuizStartpageType = (
2023-11-27 23:07:24 +00:00
quizId: string,
2023-11-13 18:04:51 +00:00
startpageType: QuizConfig["startpageType"],
) => {
2023-11-27 23:07:24 +00:00
updateQuiz(
2023-11-13 18:04:51 +00:00
quizId,
quiz => {
quiz.config.startpageType = startpageType;
},
);
};
2023-11-27 23:07:24 +00:00
const REQUEST_DEBOUNCE = 200;
const requestQueue = new RequestQueue();
let requestTimeoutId: ReturnType<typeof setTimeout>;
2023-11-13 18:04:51 +00:00
2023-11-27 23:07:24 +00:00
export const updateQuiz = async (
quizId: string | null | undefined,
2023-11-13 18:04:51 +00:00
updateFn: (quiz: Quiz) => void,
) => {
2023-11-14 13:10:41 +00:00
if (!quizId) return;
2023-11-27 23:07:24 +00:00
setProducedState(state => {
const quiz = state.quizes.find(q => q.id === quizId);
if (!quiz) return;
2023-11-13 18:04:51 +00:00
2023-11-27 23:07:24 +00:00
updateFn(quiz);
}, {
type: "updateQuiz",
quizId,
updateFn: updateFn.toString(),
});
2023-11-13 18:04:51 +00:00
clearTimeout(requestTimeoutId);
requestTimeoutId = setTimeout(async () => {
2023-11-27 23:07:24 +00:00
requestQueue.enqueue(async () => {
const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId);
if (!quiz) return;
2023-11-13 18:04:51 +00:00
2023-11-27 23:07:24 +00:00
const response = await quizApi.edit(quizToEditQuizRequest(quiz));
2023-11-13 18:04:51 +00:00
2023-11-27 23:07:24 +00:00
setQuizBackendId(quizId, response.updated);
setEditQuizId(response.updated);
}).catch(error => {
if (isAxiosCanceledError(error)) return;
2023-11-13 18:04:51 +00:00
2023-11-27 23:07:24 +00:00
devlog("Error editing quiz", error, quizId);
enqueueSnackbar("Не удалось сохранить настройки квиза");
});
}, REQUEST_DEBOUNCE);
2023-11-13 18:04:51 +00:00
};
2023-11-27 23:07:24 +00:00
export const createQuiz = async (navigate: NavigateFunction) => requestQueue.enqueue(async () => {
2023-11-14 16:43:21 +00:00
try {
2023-11-27 23:07:24 +00:00
const rawQuiz = await quizApi.create();
const quiz = rawQuizToQuiz(rawQuiz);
2023-11-14 16:43:21 +00:00
2023-11-27 23:07:24 +00:00
addQuiz(quiz);
setEditQuizId(quiz.backendId);
2023-11-14 16:43:21 +00:00
navigate("/edit");
2023-11-29 13:49:52 +00:00
await createUntypedQuestion(rawQuiz.id);
2023-11-14 16:43:21 +00:00
} catch (error) {
devlog("Error creating quiz", error);
const message = getMessageFromFetchError(error) ?? "";
enqueueSnackbar(`Не удалось создать квиз. ${message}`);
}
2023-11-27 23:07:24 +00:00
});
export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async () => {
const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId);
if (!quiz) return;
2023-11-14 16:43:21 +00:00
try {
2023-11-27 23:07:24 +00:00
await quizApi.delete(quiz.backendId);
2023-11-14 16:43:21 +00:00
removeQuiz(quizId);
} catch (error) {
devlog("Error deleting quiz", error);
const message = getMessageFromFetchError(error) ?? "";
enqueueSnackbar(`Не удалось удалить квиз. ${message}`);
}
2023-11-27 23:07:24 +00:00
});
export const updateRootContentId = (quizId: string, id:string) => updateQuiz(
2023-11-29 15:45:15 +00:00
quizId,
quiz => {
2023-12-04 13:33:43 +00:00
quiz.config.haveRoot = id
2023-11-29 15:45:15 +00:00
},
);
2023-11-27 23:07:24 +00:00
2023-12-04 13:33:43 +00:00
2023-11-27 23:07:24 +00:00
// TODO copy quiz
2023-11-14 16:43:21 +00:00
2023-12-01 18:05:59 +00:00
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("Не удалось загрузить изображение");
}
};
2023-11-13 18:04:51 +00:00
function setProducedState<A extends string | { type: unknown; }>(
recipe: (state: QuizStore) => void,
action?: A,
) {
useQuizStore.setState(state => produce(state, recipe), false, action);
}