diff --git a/src/exams/Level/index.tsx b/src/exams/Level/index.tsx index ef35f4e3..7ac86d13 100644 --- a/src/exams/Level/index.tsx +++ b/src/exams/Level/index.tsx @@ -18,502 +18,542 @@ import { Tab } from "@headlessui/react"; import Modal from "@/components/Modal"; import { typeCheckWordsMC } from "@/utils/type.check"; import SectionNavbar from "../Navigation/SectionNavbar"; +import AudioPlayer from "@/components/Low/AudioPlayer"; interface Props { - exam: LevelExam; - showSolutions?: boolean; - onFinish: (userSolutions: UserSolution[]) => void; - preview?: boolean; - partDividers?: boolean; + exam: LevelExam; + showSolutions?: boolean; + onFinish: (userSolutions: UserSolution[]) => void; + preview?: boolean; + partDividers?: boolean; } export default function Level({ exam, showSolutions = false, onFinish, preview = false }: Props) { - const levelBgColor = "bg-ielts-level-light"; + const levelBgColor = "bg-ielts-level-light"; - const examState = useExamStore((state) => state); - const persistentExamState = usePersistentExamStore((state) => state); + const examState = useExamStore((state) => state); + const persistentExamState = usePersistentExamStore((state) => state); - const { - userSolutions, - hasExamEnded, - partIndex, - exerciseIndex, - questionIndex, - shuffles, - currentSolution, - setBgColor, - setUserSolutions, - setHasExamEnded, - setPartIndex, - setExerciseIndex, - setQuestionIndex, - setShuffles, - setCurrentSolution - } = !preview ? examState : persistentExamState; + const { + userSolutions, + hasExamEnded, + partIndex, + exerciseIndex, + questionIndex, + shuffles, + currentSolution, + setBgColor, + setUserSolutions, + setHasExamEnded, + setPartIndex, + setExerciseIndex, + setQuestionIndex, + setShuffles, + setCurrentSolution + } = !preview ? examState : persistentExamState; - // In case client want to switch back - const textRenderDisabled = true; + // In case client want to switch back + const textRenderDisabled = true; - const [showSubmissionModal, setShowSubmissionModal] = useState(false); - const [showQuestionsModal, setShowQuestionsModal] = useState(false); - const [continueAnyways, setContinueAnyways] = useState(false); - const [textRender, setTextRender] = useState(false); - const [changedPrompt, setChangedPrompt] = useState(false); - const [nextExerciseCalled, setNextExerciseCalled] = useState(false); - const [currentSolutionSet, setCurrentSolutionSet] = useState(false); + const [timesListened, setTimesListened] = useState(0); + const [showSubmissionModal, setShowSubmissionModal] = useState(false); + const [showQuestionsModal, setShowQuestionsModal] = useState(false); + const [continueAnyways, setContinueAnyways] = useState(false); + const [textRender, setTextRender] = useState(false); + const [changedPrompt, setChangedPrompt] = useState(false); + const [nextExerciseCalled, setNextExerciseCalled] = useState(false); + const [currentSolutionSet, setCurrentSolutionSet] = useState(false); - const [seenParts, setSeenParts] = useState>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0])); + const [seenParts, setSeenParts] = useState>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0])); - const [questionModalKwargs, setQuestionModalKwargs] = useState<{ - type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined; - }>({ - type: "blankQuestions", - onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } } - }); + const [questionModalKwargs, setQuestionModalKwargs] = useState<{ + type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined; + }>({ + type: "blankQuestions", + onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } } + }); - const [currentExercise, setCurrentExercise] = useState(undefined); - const [showPartDivider, setShowPartDivider] = useState(typeof exam.parts[0].intro === "string" && !showSolutions); - const [startNow, setStartNow] = useState(!showSolutions); + const [currentExercise, setCurrentExercise] = useState(undefined); + const [showPartDivider, setShowPartDivider] = useState(typeof exam.parts[0].intro === "string" && !showSolutions); + const [startNow, setStartNow] = useState(!showSolutions); - useEffect(() => { - if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) { - setCurrentExercise(exam.parts[0].exercises[0]); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentExercise, partIndex, exerciseIndex]); + useEffect(() => { + if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) { + setCurrentExercise(exam.parts[0].exercises[0]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentExercise, partIndex, exerciseIndex]); - const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0)); + const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0)); - const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined); - const [contextWordLines, setContextWordLines] = useState(undefined); - const [totalLines, setTotalLines] = useState(0); + const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined); + const [contextWordLines, setContextWordLines] = useState(undefined); + const [totalLines, setTotalLines] = useState(0); - const [showSolutionsSave, setShowSolutionsSave] = useState(showSolutions ? userSolutions.filter((x) => x.module === "level") : undefined) + const [showSolutionsSave, setShowSolutionsSave] = useState(showSolutions ? userSolutions.filter((x) => x.module === "level") : undefined) - useEffect(() => { - if (typeof currentSolution !== "undefined") { - setUserSolutions([...userSolutions.filter((x) => x.exercise !== currentSolution.exercise), { ...currentSolution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]); - setCurrentSolutionSet(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentSolution, exam.id, exam.shuffle, shuffles, currentExercise]) + useEffect(() => { + if (typeof currentSolution !== "undefined") { + setUserSolutions([...userSolutions.filter((x) => x.exercise !== currentSolution.exercise), { ...currentSolution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]); + setCurrentSolutionSet(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentSolution, exam.id, exam.shuffle, shuffles, currentExercise]) - useEffect(() => { - if (typeof currentSolution !== "undefined") { - setCurrentSolution(undefined); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentSolution]); + useEffect(() => { + if (typeof currentSolution !== "undefined") { + setCurrentSolution(undefined); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentSolution]); - useEffect(() => { - if (showSolutions) { - const solutionShuffles = userSolutions.map(solution => ({ - exerciseID: solution.exercise, - shuffles: solution.shuffleMaps || [] - })); - setShuffles(solutionShuffles); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + if (showSolutions) { + const solutionShuffles = userSolutions.map(solution => ({ + exerciseID: solution.exercise, + shuffles: solution.shuffleMaps || [] + })); + setShuffles(solutionShuffles); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - const getExercise = () => { - let exercise = exam.parts[partIndex]?.exercises[exerciseIndex]; - exercise = { - ...exercise, - userSolutions: userSolutions.find((x) => x.exercise == exercise.id)?.solutions || [], - }; - exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles); - return exercise; - }; + const getExercise = () => { + let exercise = exam.parts[partIndex]?.exercises[exerciseIndex]; + exercise = { + ...exercise, + userSolutions: userSolutions.find((x) => x.exercise == exercise.id)?.solutions || [], + }; + exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles); + return exercise; + }; - useEffect(() => { - setCurrentExercise(getExercise()); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [partIndex, exerciseIndex, questionIndex]); + useEffect(() => { + setCurrentExercise(getExercise()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [partIndex, exerciseIndex, questionIndex]); - const next = () => { - setNextExerciseCalled(true); - } + const next = () => { + setNextExerciseCalled(true); + } - const nextExercise = () => { - scrollToTop(); + const nextExercise = () => { + scrollToTop(); - if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) { - setExerciseIndex(exerciseIndex + 1); - setCurrentSolutionSet(false); - return; - } + if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) { + setExerciseIndex(exerciseIndex + 1); + setCurrentSolutionSet(false); + return; + } - if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) { - modalKwargs(); - setShowQuestionsModal(true); - return; - } + if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) { + modalKwargs(); + setShowQuestionsModal(true); + return; + } - if (partIndex + 1 < exam.parts.length && !hasExamEnded) { - if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) { - modalKwargs(); - setShowQuestionsModal(true); - return; - } + if (partIndex + 1 < exam.parts.length && !hasExamEnded) { + if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) { + modalKwargs(); + setShowQuestionsModal(true); + return; + } - if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) { - setShowPartDivider(true); - setBgColor(levelBgColor); - } + if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) { + setShowPartDivider(true); + setBgColor(levelBgColor); + } - setSeenParts(prev => new Set(prev).add(partIndex + 1)); + setSeenParts(prev => new Set(prev).add(partIndex + 1)); - if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) { - setTextRender(true); - } - setPartIndex(partIndex + 1); - setExerciseIndex(0); - setQuestionIndex(0); - setCurrentSolutionSet(false); - return; - } + if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) { + setTextRender(true); + } - if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) { - modalKwargs(); - setShowQuestionsModal(true); - } + setTimesListened(0); + setPartIndex(partIndex + 1); + setExerciseIndex(0); + setQuestionIndex(0); + setCurrentSolutionSet(false); + return; + } - setHasExamEnded(false); - setCurrentSolutionSet(false); - if (typeof showSolutionsSave !== "undefined") { - onFinish(showSolutionsSave); - } else { - onFinish(userSolutions); - } - } + if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) { + modalKwargs(); + setShowQuestionsModal(true); + } - useEffect(() => { - if (nextExerciseCalled && currentSolutionSet) { - nextExercise(); - setNextExerciseCalled(false); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [nextExerciseCalled, currentSolutionSet]) + setHasExamEnded(false); + setCurrentSolutionSet(false); + if (typeof showSolutionsSave !== "undefined") { + onFinish(showSolutionsSave); + } else { + onFinish(userSolutions); + } + } - const previousExercise = (solution?: UserSolution) => { - scrollToTop(); + useEffect(() => { + if (nextExerciseCalled && currentSolutionSet) { + nextExercise(); + setNextExerciseCalled(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [nextExerciseCalled, currentSolutionSet]) - if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) { - setTextRender(true); - return; - } + const previousExercise = (solution?: UserSolution) => { + scrollToTop(); - if (questionIndex == 0) { - setPartIndex(partIndex - 1); - if (!seenParts.has(partIndex - 1)) { - setBgColor(levelBgColor); - setShowPartDivider(true); - setQuestionIndex(0); - setSeenParts(prev => new Set(prev).add(partIndex - 1)); - return; - } + if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) { + setTextRender(true); + return; + } - const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1; - const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex]; - setExerciseIndex(lastExerciseIndex); + if (questionIndex == 0) { + setPartIndex(partIndex - 1); + if (!seenParts.has(partIndex - 1)) { + setBgColor(levelBgColor); + setShowPartDivider(true); + setQuestionIndex(0); + setSeenParts(prev => new Set(prev).add(partIndex - 1)); + return; + } - if (lastExercise.type === "multipleChoice") { - setQuestionIndex(lastExercise.questions.length - 1) - } else { - setQuestionIndex(0) - } - return; - } + const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1; + const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex]; + setExerciseIndex(lastExerciseIndex); - setExerciseIndex(exerciseIndex - 1); - if (exerciseIndex - 1 === -1) { - setPartIndex(partIndex - 1); - const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1; - const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex]; - if (previousExercise.type === "multipleChoice") { - setQuestionIndex(previousExercise.questions.length - 1) - } - } + if (lastExercise.type === "multipleChoice") { + setQuestionIndex(lastExercise.questions.length - 1) + } else { + setQuestionIndex(0) + } + return; + } - }; + setExerciseIndex(exerciseIndex - 1); + if (exerciseIndex - 1 === -1) { + setPartIndex(partIndex - 1); + const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1; + const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex]; + if (previousExercise.type === "multipleChoice") { + setQuestionIndex(previousExercise.questions.length - 1) + } + } - const calculateExerciseIndex = () => { - return exam.parts.reduce((acc, curr, index) => { - if (index < partIndex) { - return acc + countExercises(curr.exercises) - } - return acc; - }, 0) + (questionIndex + 1); - }; + }; - const renderText = () => ( - <> -
- <> -
- {textRender && !textRenderDisabled ? ( - <> -

- Please read the following excerpt attentively, you will then be asked questions about the text you've read. -

- You will be allowed to read the text while doing the exercises - - ) : ( -

- Answer the questions on the right based on what you've read. -

- )} -
- {exam.parts[partIndex].context && - } -
- -
- {textRender && !textRenderDisabled && ( -
- + const calculateExerciseIndex = () => { + return exam.parts.reduce((acc, curr, index) => { + if (index < partIndex) { + return acc + countExercises(curr.exercises) + } + return acc; + }, 0) + (questionIndex + 1); + }; - -
- )} - - ); + const renderAudioPlayer = () => ( +
+ {exam?.parts[partIndex]?.audio?.source ? ( + <> +
+

Please listen to the following audio attentively.

+ + {(() => { + const audioRepeatTimes = exam?.parts[partIndex]?.audio?.repeatableTimes; + return audioRepeatTimes && audioRepeatTimes > 0 + ? `You will only be allowed to listen to the audio ${audioRepeatTimes - timesListened} time(s).` + : "You may listen to the audio as many times as you would like."; + })()} + +
+
+ setTimesListened((prev) => prev + 1)} + disabled={exam?.parts[partIndex]?.audio?.repeatableTimes != null && + timesListened === exam.parts[partIndex]?.audio?.repeatableTimes} + disablePause + /> +
+ + ) : ( + This section will be displayed the audio once it has been generated. + )} - const partLabel = () => { - const partCategory = exam.parts[partIndex].category ? ` (${exam.parts[partIndex].category})` : ''; - if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words)) - return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})${partCategory}\n\n${currentExercise.prompt}` +
+ ); - if (currentExercise?.type === "multipleChoice") { - return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})${partCategory}\n\n${currentExercise.prompt}` - } + const renderText = () => ( + <> +
+ <> +
+ {textRender && !textRenderDisabled ? ( + <> +

+ Please read the following excerpt attentively, you will then be asked questions about the text you've read. +

+ You will be allowed to read the text while doing the exercises + + ) : ( +

+ Answer the questions on the right based on what you've read. +

+ )} +
+ {(exam.parts[partIndex].context || exam.parts[partIndex].text) && + } +
+ +
+ {textRender && !textRenderDisabled && ( +
+ - if (typeof exam.parts[partIndex].context === "string") { - const nextExercise = exam.parts[partIndex].exercises[0] as MultipleChoiceExercise; - return `Part ${partIndex + 1} (Questions ${nextExercise.questions[0].id} - ${nextExercise.questions[nextExercise.questions.length - 1].id})${partCategory}\n\n${nextExercise.prompt}` - } - } + +
+ )} + + ); - const answeredEveryQuestion = (partIndex: number) => { - return exam.parts[partIndex].exercises.every((exercise) => { - const userSolution = userSolutions.find(x => x.exercise === exercise.id); - switch (exercise.type) { - case 'multipleChoice': - return userSolution?.solutions.length === exercise.questions.length; - case 'fillBlanks': - return userSolution?.solutions.length === exercise.words.length; - case 'writeBlanks': - return userSolution?.solutions.length === exercise.solutions.length; - case 'matchSentences': - return userSolution?.solutions.length === exercise.sentences.length; - case 'trueFalse': - return userSolution?.solutions.length === exercise.questions.length; - } - return false; - }); - } + const partLabel = () => { + const partCategory = exam.parts[partIndex].category ? ` (${exam.parts[partIndex].category})` : ''; + if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words)) + return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})${partCategory}\n\n${currentExercise.prompt}` - useEffect(() => { - const regex = /.*?['"](.*?)['"] in line (\d+)\?$/; + if (currentExercise?.type === "multipleChoice") { + return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})${partCategory}\n\n${currentExercise.prompt}` + } - const findMatch = (index: number) => { - if (currentExercise && currentExercise.type === "multipleChoice" && currentExercise!.questions[index]) { - const match = currentExercise!.questions[index].prompt.match(regex); - if (match) { - return { match: match[1], originalLine: match[2] } - } - } - return; - } + if (typeof exam.parts[partIndex].context === "string") { + const nextExercise = exam.parts[partIndex].exercises[0] as MultipleChoiceExercise; + return `Part ${partIndex + 1} (Questions ${nextExercise.questions[0].id} - ${nextExercise.questions[nextExercise.questions.length - 1].id})${partCategory}\n\n${nextExercise.prompt}` + } + } - // if the client for some whatever random reason decides - // to add more questions update this - const numberOfQuestions = 2; + const answeredEveryQuestion = (partIndex: number) => { + return exam.parts[partIndex].exercises.every((exercise) => { + const userSolution = userSolutions.find(x => x.exercise === exercise.id); + switch (exercise.type) { + case 'multipleChoice': + return userSolution?.solutions.length === exercise.questions.length; + case 'fillBlanks': + return userSolution?.solutions.length === exercise.words.length; + case 'writeBlanks': + return userSolution?.solutions.length === exercise.solutions.length; + case 'matchSentences': + return userSolution?.solutions.length === exercise.sentences.length; + case 'trueFalse': + return userSolution?.solutions.length === exercise.questions.length; + } + return false; + }); + } - if (exam.parts[partIndex].context) { - const hits = Array.from({ length: numberOfQuestions }).reduce<{ match: string, originalLine: string }[]>((acc, _, i) => { - const result = findMatch(questionIndex + i); - if (!!result) { - acc.push(result); - } - return acc; - }, []); + useEffect(() => { + const regex = /.*?['"](.*?)['"] in line (\d+)\?$/; - if (hits.length > 0) { - setContextWords(hits) - } - } + const findMatch = (index: number) => { + if (currentExercise && currentExercise.type === "multipleChoice" && currentExercise!.questions[index]) { + const match = currentExercise!.questions[index].prompt.match(regex); + if (match) { + return { match: match[1], originalLine: match[2] } + } + } + return; + } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentExercise, questionIndex, totalLines]); + // if the client for some whatever random reason decides + // to add more questions update this + const numberOfQuestions = 2; - useEffect(() => { - if ( - exerciseIndex !== -1 && currentExercise && - currentExercise.type === "multipleChoice" && - exam.parts[partIndex].context && contextWordLines - ) { - if (contextWordLines.length > 0) { - contextWordLines.forEach((n, i) => { - if (contextWords && contextWords[i] && n !== -1) { - const updatedPrompt = currentExercise!.questions[questionIndex + i].prompt.replace( - `in line ${contextWords[i].originalLine}`, - `in line ${n}` - ); - currentExercise!.questions[questionIndex + i].prompt = updatedPrompt; - } - }) - setChangedPrompt(true); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [contextWordLines]); + if (exam.parts[partIndex].context) { + const hits = Array.from({ length: numberOfQuestions }).reduce<{ match: string, originalLine: string }[]>((acc, _, i) => { + const result = findMatch(questionIndex + i); + if (!!result) { + acc.push(result); + } + return acc; + }, []); + + if (hits.length > 0) { + setContextWords(hits) + } + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentExercise, questionIndex, totalLines]); + + useEffect(() => { + if ( + exerciseIndex !== -1 && currentExercise && + currentExercise.type === "multipleChoice" && + exam.parts[partIndex].context && contextWordLines + ) { + if (contextWordLines.length > 0) { + contextWordLines.forEach((n, i) => { + if (contextWords && contextWords[i] && n !== -1) { + const updatedPrompt = currentExercise!.questions[questionIndex + i].prompt.replace( + `in line ${contextWords[i].originalLine}`, + `in line ${n}` + ); + currentExercise!.questions[questionIndex + i].prompt = updatedPrompt; + } + }) + setChangedPrompt(true); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contextWordLines]); - useEffect(() => { - if (continueAnyways) { - setContinueAnyways(false); - nextExercise(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [continueAnyways]); + useEffect(() => { + if (continueAnyways) { + setContinueAnyways(false); + nextExercise(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [continueAnyways]); - const modalKwargs = () => { - const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = { - type: "blankQuestions", - unanswered: false, - onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } } - }; + const modalKwargs = () => { + const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = { + type: "blankQuestions", + unanswered: false, + onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } } + }; - if (partIndex === exam.parts.length - 1) { - kwargs.type = "submit" - kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex)); - kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }; - } - setQuestionModalKwargs(kwargs); - } + if (partIndex === exam.parts.length - 1) { + kwargs.type = "submit" + kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestion(partIndex)); + kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }; + } + setQuestionModalKwargs(kwargs); + } - const mcNavKwargs = { - userSolutions: userSolutions, - exam: exam, - partIndex: partIndex, - showSolutions: showSolutions, - setExerciseIndex: setExerciseIndex, - setPartIndex: setPartIndex, - runOnClick: setQuestionIndex - } + const mcNavKwargs = { + userSolutions: userSolutions, + exam: exam, + partIndex: partIndex, + showSolutions: showSolutions, + setExerciseIndex: setExerciseIndex, + setPartIndex: setPartIndex, + runOnClick: setQuestionIndex + } - const memoizedRender = useMemo(() => { - setChangedPrompt(false); - return ( - <> - {textRender && !textRenderDisabled ? - renderText() : - <> - {exam.parts[partIndex]?.context && renderText()} - {(showSolutions) ? - currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) : - currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise) - } - - } - ) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [textRender, currentExercise, changedPrompt]); + const memoizedRender = useMemo(() => { + setChangedPrompt(false); + return ( + <> + {textRender && !textRenderDisabled ? + renderText() : + <> + {exam.parts[partIndex]?.context && renderText()} + {exam.parts[partIndex]?.audio && renderAudioPlayer()} + {(showSolutions) ? + currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) : + currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise) + } + + } + + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [textRender, currentExercise, changedPrompt]); - return ( - <> -
- { }} - title={"Confirm Submission"} - > - <> -

Are you sure you want to proceed with the submission?

-
- - -
- -
- - { - !(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) && - - } - {(showPartDivider || startNow) ? - { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }} - /> : ( - <> - {exam.parts[0].intro && ( - { - setExerciseIndex(0); - setQuestionIndex(0); - if (!seenParts.has(index)) { - setShowPartDivider(true); - setBgColor(levelBgColor); - setSeenParts(prev => new Set(prev).add(index)); - } - } - } /> - )} - x.exercises))} - disableTimer={showSolutions} - showTimer={false} - {...mcNavKwargs} - /> -
- {memoizedRender} -
- - )} -
- - ); + return ( + <> +
+ { }} + title={"Confirm Submission"} + > + <> +

Are you sure you want to proceed with the submission?

+
+ + +
+ +
+ + { + !(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) && + + } + {(showPartDivider || startNow) ? + { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }} + /> : ( + <> + {exam.parts[0].intro && ( + { + setExerciseIndex(0); + setQuestionIndex(0); + if (!seenParts.has(index)) { + setShowPartDivider(true); + setBgColor(levelBgColor); + setSeenParts(prev => new Set(prev).add(index)); + } + } + } /> + )} + x.exercises))} + disableTimer={showSolutions} + showTimer={false} + {...mcNavKwargs} + /> +
+ {memoizedRender} +
+ + )} +
+ + ); }