Found the bug
This commit is contained in:
@@ -125,9 +125,13 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
|
|
||||||
const onSelection = (questionID: string, value: string) => {
|
const onSelection = (questionID: string, value: string) => {
|
||||||
setAnswers((prev) => [...prev.filter((x) => x.id !== questionID), { id: questionID, solution: value }]);
|
setAnswers((prev) => [...prev.filter((x) => x.id !== questionID), { id: questionID, solution: value }]);
|
||||||
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [answers])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4 mt-4 h-full w-full mb-20">
|
<div className="flex flex-col gap-4 mt-4 h-full w-full mb-20">
|
||||||
@@ -214,15 +218,18 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
onClick={() => onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps })}
|
onClick={() => onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps })}
|
||||||
className="max-w-[200px] w-full"
|
className="max-w-[200px] w-full"
|
||||||
disabled={
|
disabled={
|
||||||
exam && typeof partIndex !== "undefined" && exam.module === "level" &&
|
exam && exam.module === "level" &&
|
||||||
typeof exam.parts[0].intro === "string" && questionIndex === 0}
|
typeof exam.parts[0].intro === "string" &&
|
||||||
|
partIndex === 0 &&
|
||||||
|
questionIndex === 0
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="purple"
|
color="purple"
|
||||||
onClick={() => {onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps })}}
|
onClick={() => { onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }) }}
|
||||||
className="max-w-[200px] self-end w-full">
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function Question({
|
|||||||
const renderPrompt = (prompt: string) => {
|
const renderPrompt = (prompt: string) => {
|
||||||
return reactStringReplace(prompt, /(<u>.*?<\/u>)/g, (match) => {
|
return reactStringReplace(prompt, /(<u>.*?<\/u>)/g, (match) => {
|
||||||
const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
|
const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
|
||||||
return word.length > 0 ? <u>{word}</u> : null;
|
return word.length > 0 ? <u key={v4()}>{word}</u> : null;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ function Question({
|
|||||||
"flex flex-col items-center border border-mti-gray-platinum p-4 px-8 rounded-xl gap-4 cursor-pointer bg-white relative select-none",
|
"flex flex-col items-center border border-mti-gray-platinum p-4 px-8 rounded-xl gap-4 cursor-pointer bg-white relative select-none",
|
||||||
userSolution === option.id.toString() && "border-mti-purple-light",
|
userSolution === option.id.toString() && "border-mti-purple-light",
|
||||||
)}>
|
)}>
|
||||||
<span className={clsx("text-sm", userSolution !== option.id.toString() && "opacity-50")}>{option.id.toString()}</span>
|
<span key={v4()} className={clsx("text-sm", userSolution !== option.id.toString() && "opacity-50")}>{option.id.toString()}</span>
|
||||||
<img src={option.src!} alt={`Option ${option.id.toString()}`} />
|
<img src={option.src!} alt={`Option ${option.id.toString()}`} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -79,6 +79,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
exam,
|
exam,
|
||||||
shuffles,
|
shuffles,
|
||||||
hasExamEnded,
|
hasExamEnded,
|
||||||
|
partIndex,
|
||||||
userSolutions: storeUserSolutions,
|
userSolutions: storeUserSolutions,
|
||||||
setQuestionIndex,
|
setQuestionIndex,
|
||||||
setUserSolutions,
|
setUserSolutions,
|
||||||
@@ -107,7 +108,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
setAnswers((prev) => [...prev.filter((x) => x.question !== question.id), { option, question: question.id }]);
|
setAnswers((prev) => [...prev.filter((x) => x.question !== question.id), { option, question: question.id }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [answers])
|
}, [answers])
|
||||||
@@ -136,7 +137,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
const shuffleMap = shuffleMaps.find((map) => map.questionID == x.question);
|
const shuffleMap = shuffleMaps.find((map) => map.questionID == x.question);
|
||||||
if (shuffleMap) {
|
if (shuffleMap) {
|
||||||
isSolutionCorrect = getShuffledSolution(x.option, shuffleMap) == matchingQuestion?.solution;
|
isSolutionCorrect = getShuffledSolution(x.option, shuffleMap) == matchingQuestion?.solution;
|
||||||
}else {
|
} else {
|
||||||
isSolutionCorrect = matchingQuestion?.solution === x.option;
|
isSolutionCorrect = matchingQuestion?.solution === x.option;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +160,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
if (questionIndex === 0) {
|
if (questionIndex === 0) {
|
||||||
onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||||
} else {
|
} else {
|
||||||
|
if (exam?.module === "level" && typeof exam.parts[0].intro !== "undefined" && questionIndex === 0) return;
|
||||||
setQuestionIndex(questionIndex - 1);
|
setQuestionIndex(questionIndex - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +183,11 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="purple" variant="outline" onClick={back} className="max-w-[200px] w-full"
|
<Button color="purple" variant="outline" onClick={back} className="max-w-[200px] w-full"
|
||||||
disabled={
|
disabled={
|
||||||
exam && exam.module === "level" && typeof exam.parts[0].intro === "string" && questionIndex === 0}
|
exam && exam.module === "level" &&
|
||||||
|
typeof exam.parts[0].intro === "string" &&
|
||||||
|
partIndex === 0 &&
|
||||||
|
questionIndex === 0
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function Button({
|
|||||||
type={type}
|
type={type}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer",
|
"rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer select-none",
|
||||||
padding,
|
padding,
|
||||||
colorClassNames[color][variant],
|
colorClassNames[color][variant],
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import Button from "./Low/Button";
|
import Button from "./Low/Button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -10,6 +10,19 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function QuestionsModal({ isOpen, onClose, type = "module", unanswered = false }: Props) {
|
export default function QuestionsModal({ isOpen, onClose, type = "module", unanswered = false }: Props) {
|
||||||
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
|
|
||||||
|
const blockMultipleClicksClose = (x: boolean) => {
|
||||||
|
if (!isClosing) {
|
||||||
|
setIsClosing(true);
|
||||||
|
onClose(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsClosing(false);
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition show={isOpen} as={Fragment}>
|
<Transition show={isOpen} as={Fragment}>
|
||||||
<Dialog onClose={() => onClose(false)} className="relative z-50">
|
<Dialog onClose={() => onClose(false)} className="relative z-50">
|
||||||
@@ -44,10 +57,10 @@ export default function QuestionsModal({ isOpen, onClose, type = "module", unans
|
|||||||
Are you sure you want to continue without completing those questions?
|
Are you sure you want to continue without completing those questions?
|
||||||
</span>
|
</span>
|
||||||
<div className="w-full flex justify-between mt-8">
|
<div className="w-full flex justify-between mt-8">
|
||||||
<Button color="purple" onClick={() => onClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => blockMultipleClicksClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
Go Back
|
Go Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="purple" onClick={() => onClose(true)} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => blockMultipleClicksClose(true)} className="max-w-[200px] self-end w-full">
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,18 +69,16 @@ export default function QuestionsModal({ isOpen, onClose, type = "module", unans
|
|||||||
{type === "blankQuestions" && (
|
{type === "blankQuestions" && (
|
||||||
<>
|
<>
|
||||||
<Dialog.Title className="font-bold text-xl">Questions Unanswered</Dialog.Title>
|
<Dialog.Title className="font-bold text-xl">Questions Unanswered</Dialog.Title>
|
||||||
<span>
|
<div className="flex flex-col text-lg gap-2">
|
||||||
You have left some questions unanswered in the current part. <br />
|
<p>You have left some questions unanswered in the current part.</p>
|
||||||
<br />
|
<p>If you wish to continue, you can still access this part later using the navigation bar at the top or the "Back" button.</p>
|
||||||
If you wish to continue, you can still access this part later using the navigation bar at the top. <br />
|
<p>Do you want to proceed to the next part, or would you like to go back and complete the unanswered questions in the current part?</p>
|
||||||
<br />
|
</div>
|
||||||
Do you want to proceed to the next part, or would you like to go back and complete the unanswered questions in the current part?
|
<div className="w-full flex justify-between mt-6">
|
||||||
</span>
|
<Button color="purple" onClick={() => blockMultipleClicksClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
<div className="w-full flex justify-between mt-8">
|
|
||||||
<Button color="purple" onClick={() => onClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
|
||||||
Go Back
|
Go Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="purple" onClick={() => onClose(true)} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => blockMultipleClicksClose(true)} className="max-w-[200px] self-end w-full">
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,10 +103,10 @@ export default function QuestionsModal({ isOpen, onClose, type = "module", unans
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<div className="w-full flex justify-between mt-8">
|
<div className="w-full flex justify-between mt-8">
|
||||||
<Button color="purple" onClick={() => onClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => blockMultipleClicksClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
Go Back
|
Go Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="purple" onClick={() => onClose(true)} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => blockMultipleClicksClose(true)} className="max-w-[200px] self-end w-full">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ interface Props {
|
|||||||
setContextWordLine: React.Dispatch<React.SetStateAction<number | undefined>>
|
setContextWordLine: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextComponent: React.FC<Props> = ({part, contextWord, setContextWordLine}) => {
|
const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine }) => {
|
||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const calculateLineNumbers = () => {
|
const calculateLineNumbers = () => {
|
||||||
@@ -130,8 +130,6 @@ const TextComponent: React.FC<Props> = ({part, contextWord, setContextWordLine})
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 w-full">
|
|
||||||
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
|
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div ref={textRef} className="h-fit ml-2 flex flex-col gap-4">
|
<div ref={textRef} className="h-fit ml-2 flex flex-col gap-4">
|
||||||
{part.context!.split('\n\n').map((line, index) => {
|
{part.context!.split('\n\n').map((line, index) => {
|
||||||
@@ -139,7 +137,6 @@ const TextComponent: React.FC<Props> = ({part, contextWord, setContextWordLine})
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,32 @@ const typeCheckWordsMC = (words: any[]): words is FillBlanksMCOption[] => {
|
|||||||
|
|
||||||
export default function Level({ exam, showSolutions = false, onFinish, editing = false }: Props) {
|
export default function Level({ exam, showSolutions = false, onFinish, editing = false }: Props) {
|
||||||
const levelBgColor = "bg-ielts-level-light";
|
const levelBgColor = "bg-ielts-level-light";
|
||||||
|
|
||||||
|
const {
|
||||||
|
userSolutions,
|
||||||
|
hasExamEnded,
|
||||||
|
partIndex,
|
||||||
|
exerciseIndex,
|
||||||
|
questionIndex,
|
||||||
|
shuffles,
|
||||||
|
currentSolution,
|
||||||
|
setBgColor,
|
||||||
|
setUserSolutions,
|
||||||
|
setHasExamEnded,
|
||||||
|
setPartIndex,
|
||||||
|
setExerciseIndex,
|
||||||
|
setQuestionIndex,
|
||||||
|
setShuffles,
|
||||||
|
setCurrentSolution
|
||||||
|
} = useExamStore((state) => state);
|
||||||
|
|
||||||
|
|
||||||
const [multipleChoicesDone, setMultipleChoicesDone] = useState<{ id: string; amount: number }[]>([]);
|
const [multipleChoicesDone, setMultipleChoicesDone] = useState<{ id: string; amount: number }[]>([]);
|
||||||
const [showQuestionsModal, setShowQuestionsModal] = useState(false);
|
const [showQuestionsModal, setShowQuestionsModal] = useState(false);
|
||||||
const [continueAnyways, setContinueAnyways] = useState(false);
|
const [continueAnyways, setContinueAnyways] = useState(false);
|
||||||
|
const [textRender, setTextRender] = useState(false);
|
||||||
|
|
||||||
const [seenParts, setSeenParts] = useState<number[]>(showSolutions ? exam.parts.map((_, index) => index) : [0]);
|
const [seenParts, setSeenParts] = useState<number[]>(showSolutions ? exam.parts.map((_, index) => index) : [0]);
|
||||||
const [lastSolution, setLastSolution] = useState<UserSolution | undefined>(undefined);
|
|
||||||
|
|
||||||
const [questionModalKwargs, setQuestionModalKwargs] = useState<{
|
const [questionModalKwargs, setQuestionModalKwargs] = useState<{
|
||||||
type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
|
type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
|
||||||
@@ -46,33 +66,33 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
|
onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
|
||||||
});
|
});
|
||||||
|
|
||||||
const { setBgColor } = useExamStore((state) => state);
|
|
||||||
const { userSolutions, setUserSolutions } = useExamStore((state) => state);
|
|
||||||
const { hasExamEnded, setHasExamEnded } = useExamStore((state) => state);
|
const [currentExercise, setCurrentExercise] = useState<Exercise>(exam.parts[0].exercises[0]);
|
||||||
const { partIndex, setPartIndex } = useExamStore((state) => state);
|
|
||||||
const { exerciseIndex, setExerciseIndex } = useExamStore((state) => state);
|
|
||||||
const [storeQuestionIndex, setStoreQuestionIndex] = useExamStore((state) => [state.questionIndex, state.setQuestionIndex]);
|
|
||||||
const [shuffles, setShuffles] = useExamStore((state) => [state.shuffles, state.setShuffles])
|
|
||||||
const [currentExercise, setCurrentExercise] = useState<Exercise>();
|
|
||||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
||||||
|
|
||||||
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 [contextWord, setContextWord] = useState<string | undefined>(undefined);
|
const [contextWord, setContextWord] = useState<string | undefined>(undefined);
|
||||||
const [contextWordLine, setContextWordLine] = useState<number | undefined>(undefined);
|
const [contextWordLine, setContextWordLine] = useState<number | undefined>(undefined);
|
||||||
const [currentSolution, setCurrentSolution] = useExamStore((state) => [state.currentSolution, state.setCurrentSolution])
|
|
||||||
|
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
if (typeof currentSolution !== "undefined") {
|
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!] : [] }]);
|
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!] : [] }]);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentSolution, exam.id, exam.shuffle, shuffles, currentExercise])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof currentSolution !== "undefined") {
|
||||||
setCurrentSolution(undefined);
|
setCurrentSolution(undefined);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentSolution])
|
}, [currentSolution]);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if (showSolutions) {
|
if (showSolutions) {
|
||||||
const solutionShuffles = userSolutions.map(solution => ({
|
const solutionShuffles = userSolutions.map(solution => ({
|
||||||
exerciseID: solution.exercise,
|
exerciseID: solution.exercise,
|
||||||
@@ -81,22 +101,14 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
setShuffles(solutionShuffles);
|
setShuffles(solutionShuffles);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
},[]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const getExercise = () => {
|
||||||
if (hasExamEnded && exerciseIndex === -1) {
|
|
||||||
setExerciseIndex(exerciseIndex + 1);
|
|
||||||
}
|
|
||||||
}, [hasExamEnded, exerciseIndex, setExerciseIndex]);
|
|
||||||
|
|
||||||
const getExercise = () => {;
|
|
||||||
let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
|
let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
|
||||||
if (!exercise) return undefined;
|
|
||||||
exercise = {
|
exercise = {
|
||||||
...exercise,
|
...exercise,
|
||||||
userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
|
userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles);
|
exercise = shuffleExamExercise(exam.shuffle, exercise, showSolutions, userSolutions, shuffles, setShuffles);
|
||||||
|
|
||||||
return exercise;
|
return exercise;
|
||||||
@@ -105,13 +117,19 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentExercise(getExercise());
|
setCurrentExercise(getExercise());
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [partIndex, exerciseIndex, exam.parts[partIndex].context]);
|
}, [partIndex, exerciseIndex, questionIndex]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
|
const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
|
||||||
if (exerciseIndex !== -1 && currentExercise && currentExercise.type === "multipleChoice" && currentExercise.questions[storeQuestionIndex].prompt) {
|
if (
|
||||||
const match = currentExercise.questions[storeQuestionIndex].prompt.match(regex);
|
exerciseIndex !== -1 && currentExercise &&
|
||||||
|
currentExercise.type === "multipleChoice" &&
|
||||||
|
currentExercise.questions[questionIndex] &&
|
||||||
|
currentExercise.questions[questionIndex].prompt &&
|
||||||
|
exam.parts[partIndex].context
|
||||||
|
) {
|
||||||
|
const match = currentExercise.questions[questionIndex].prompt.match(regex);
|
||||||
if (match) {
|
if (match) {
|
||||||
const word = match[1];
|
const word = match[1];
|
||||||
const originalLineNumber = match[2];
|
const originalLineNumber = match[2];
|
||||||
@@ -120,18 +138,18 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
setContextWord(word);
|
setContextWord(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedPrompt = currentExercise.questions[storeQuestionIndex].prompt.replace(
|
const updatedPrompt = currentExercise.questions[questionIndex].prompt.replace(
|
||||||
`in line ${originalLineNumber}`,
|
`in line ${originalLineNumber}`,
|
||||||
`in line ${contextWordLine || originalLineNumber}`
|
`in line ${contextWordLine || originalLineNumber}`
|
||||||
);
|
);
|
||||||
|
|
||||||
currentExercise.questions[storeQuestionIndex].prompt = updatedPrompt;
|
currentExercise.questions[questionIndex].prompt = updatedPrompt;
|
||||||
} else {
|
} else {
|
||||||
setContextWord(undefined);
|
setContextWord(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentExercise, storeQuestionIndex]);
|
}, [currentExercise, questionIndex]);
|
||||||
|
|
||||||
const nextExercise = (solution?: UserSolution) => {
|
const nextExercise = (solution?: UserSolution) => {
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
@@ -142,28 +160,30 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) {
|
if (partIndex + 1 === exam.parts.length && !hasExamEnded && !showQuestionsModal && !showSolutions && !continueAnyways) {
|
||||||
setLastSolution(solution);
|
|
||||||
modalKwargs();
|
modalKwargs();
|
||||||
setShowQuestionsModal(true);
|
setShowQuestionsModal(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
|
if (partIndex + 1 < exam.parts.length && !hasExamEnded) {
|
||||||
if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions) {
|
if (!answeredEveryQuestion(partIndex) && !continueAnyways && !showSolutions && !seenParts.includes(partIndex + 1)) {
|
||||||
modalKwargs();
|
modalKwargs();
|
||||||
setShowQuestionsModal(true);
|
setShowQuestionsModal(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showSolutions && exam.parts[0].intro) {
|
if (!showSolutions && exam.parts[0].intro && !seenParts.includes(partIndex + 1)) {
|
||||||
setShowPartDivider(true);
|
setShowPartDivider(true);
|
||||||
setBgColor(levelBgColor);
|
setBgColor(levelBgColor);
|
||||||
}
|
}
|
||||||
setSeenParts((prev) => [...prev, partIndex + 1])
|
setSeenParts((prev) => [...prev, partIndex + 1])
|
||||||
|
if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context) {
|
||||||
|
setTextRender(true);
|
||||||
|
}
|
||||||
setPartIndex(partIndex + 1);
|
setPartIndex(partIndex + 1);
|
||||||
setExerciseIndex(!!exam.parts[partIndex + 1].context ? -1 : 0);
|
setExerciseIndex(0);
|
||||||
setStoreQuestionIndex(0);
|
setQuestionIndex(0);
|
||||||
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : storeQuestionIndex }]);
|
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : questionIndex }]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,8 +197,24 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
|
|
||||||
const previousExercise = (solution?: UserSolution) => {
|
const previousExercise = (solution?: UserSolution) => {
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
if (solution) {
|
|
||||||
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]);
|
if (exam.parts[partIndex].context && questionIndex === 0 && !textRender) {
|
||||||
|
setTextRender(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (questionIndex == 0) {
|
||||||
|
setPartIndex(partIndex - 1);
|
||||||
|
const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
|
||||||
|
const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex];
|
||||||
|
setExerciseIndex(lastExerciseIndex);
|
||||||
|
|
||||||
|
if (lastExercise.type === "multipleChoice") {
|
||||||
|
setQuestionIndex(lastExercise.questions.length - 1)
|
||||||
|
} else {
|
||||||
|
setQuestionIndex(0)
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setExerciseIndex(exerciseIndex - 1);
|
setExerciseIndex(exerciseIndex - 1);
|
||||||
@@ -187,7 +223,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
|
const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
|
||||||
const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex];
|
const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex];
|
||||||
if (previousExercise.type === "multipleChoice") {
|
if (previousExercise.type === "multipleChoice") {
|
||||||
setStoreQuestionIndex(previousExercise.questions.length - 1)
|
setQuestionIndex(previousExercise.questions.length - 1)
|
||||||
}
|
}
|
||||||
const multipleChoiceQuestionsDone = [];
|
const multipleChoiceQuestionsDone = [];
|
||||||
for (let i = 0; i < exam.parts.length; i++) {
|
for (let i = 0; i < exam.parts.length; i++) {
|
||||||
@@ -207,13 +243,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (exerciseIndex === -1) {
|
|
||||||
nextExercise()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [exerciseIndex])
|
|
||||||
|
|
||||||
const calculateExerciseIndex = () => {
|
const calculateExerciseIndex = () => {
|
||||||
if (exam.parts[0].intro) {
|
if (exam.parts[0].intro) {
|
||||||
return exam.parts.reduce((acc, curr, index) => {
|
return exam.parts.reduce((acc, curr, index) => {
|
||||||
@@ -221,11 +250,11 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
return acc + countExercises(curr.exercises)
|
return acc + countExercises(curr.exercises)
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, 0) + (storeQuestionIndex + 1);
|
}, 0) + (questionIndex + 1);
|
||||||
} else {
|
} else {
|
||||||
if (partIndex === 0) {
|
if (partIndex === 0) {
|
||||||
return (
|
return (
|
||||||
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) + storeQuestionIndex //+ multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
|
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) + questionIndex //+ multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const exercisesPerPart = exam.parts.map((x) => x.exercises.length);
|
const exercisesPerPart = exam.parts.map((x) => x.exercises.length);
|
||||||
@@ -233,28 +262,56 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
return (
|
return (
|
||||||
exercisesDone +
|
exercisesDone +
|
||||||
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
|
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
|
||||||
storeQuestionIndex
|
questionIndex
|
||||||
+ multipleChoicesDone.reduce((acc, curr) => { return acc + curr.amount }, 0)
|
+ multipleChoicesDone.reduce((acc, curr) => { return acc + curr.amount }, 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderText = () => (
|
const renderText = () => (
|
||||||
|
<>
|
||||||
<div className={clsx("flex flex-col gap-6 w-full bg-mti-gray-seasalt rounded-xl mt-4 relative py-8 px-16")}>
|
<div className={clsx("flex flex-col gap-6 w-full bg-mti-gray-seasalt rounded-xl mt-4 relative py-8 px-16")}>
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col w-full gap-2">
|
<div className="flex flex-col w-full gap-2">
|
||||||
|
{textRender ? (
|
||||||
|
<>
|
||||||
<h4 className="text-xl font-semibold">
|
<h4 className="text-xl font-semibold">
|
||||||
Please read the following excerpt attentively, you will then be asked questions about the text you've read.
|
Please read the following excerpt attentively, you will then be asked questions about the text you've read.
|
||||||
</h4>
|
</h4>
|
||||||
<span className="text-base">You will be allowed to read the text while doing the exercises</span>
|
<span className="text-base">You will be allowed to read the text while doing the exercises</span>
|
||||||
</div>
|
</>
|
||||||
|
) : (
|
||||||
|
<h4 className="text-xl font-semibold">
|
||||||
|
Answer the questions on the right based on what you've read.
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
|
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
|
||||||
|
{exam.parts[partIndex].context &&
|
||||||
<TextComponent
|
<TextComponent
|
||||||
part={exam.parts[partIndex]}
|
part={exam.parts[partIndex]}
|
||||||
contextWord={contextWord}
|
contextWord={contextWord}
|
||||||
setContextWordLine={setContextWordLine}
|
setContextWordLine={setContextWordLine}
|
||||||
/>
|
/>}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
|
{textRender && (
|
||||||
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
|
<Button
|
||||||
|
color="purple"
|
||||||
|
variant="outline"
|
||||||
|
className="max-w-[200px] w-full"
|
||||||
|
onClick={() => { setTextRender(false); previousExercise(); }}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button color="purple" onClick={() => setTextRender(false)} className="max-w-[200px] self-end w-full">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const partLabel = () => {
|
const partLabel = () => {
|
||||||
@@ -286,8 +343,8 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (continueAnyways) {
|
if (continueAnyways) {
|
||||||
nextExercise(lastSolution);
|
|
||||||
setContinueAnyways(false);
|
setContinueAnyways(false);
|
||||||
|
nextExercise();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [continueAnyways]);
|
}, [continueAnyways]);
|
||||||
@@ -314,7 +371,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
showSolutions: showSolutions,
|
showSolutions: showSolutions,
|
||||||
"setExerciseIndex": setExerciseIndex,
|
"setExerciseIndex": setExerciseIndex,
|
||||||
"setPartIndex": setPartIndex,
|
"setPartIndex": setPartIndex,
|
||||||
"runOnClick": setStoreQuestionIndex
|
"runOnClick": setQuestionIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -323,7 +380,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
<div className={clsx("flex flex-col h-full w-full gap-8 items-center", showPartDivider && "justify-center")}>
|
<div className={clsx("flex flex-col h-full w-full gap-8 items-center", showPartDivider && "justify-center")}>
|
||||||
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
|
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
|
||||||
{
|
{
|
||||||
!(partIndex === 0 && storeQuestionIndex === 0 && showPartDivider) &&
|
!(partIndex === 0 && questionIndex === 0 && showPartDivider) &&
|
||||||
<Timer minTimer={exam.minTimer} disableTimer={showSolutions} standalone={true} />
|
<Timer minTimer={exam.minTimer} disableTimer={showSolutions} standalone={true} />
|
||||||
}
|
}
|
||||||
{exam.parts[0].intro && showPartDivider ? <PartDivider part={exam.parts[partIndex]} partIndex={partIndex} onNext={() => { setShowPartDivider(false); setBgColor("bg-white") }} /> : (
|
{exam.parts[0].intro && showPartDivider ? <PartDivider part={exam.parts[partIndex]} partIndex={partIndex} onNext={() => { setShowPartDivider(false); setBgColor("bg-white") }} /> : (
|
||||||
@@ -338,7 +395,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
setExerciseIndex(0);
|
setExerciseIndex(0);
|
||||||
setStoreQuestionIndex(0);
|
setQuestionIndex(0);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={({ selected }) =>
|
className={({ selected }) =>
|
||||||
@@ -370,24 +427,20 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"mb-20 w-full",
|
"mb-20 w-full",
|
||||||
partIndex > -1 && exerciseIndex > -1 && !!exam.parts[partIndex].context && "grid grid-cols-2 gap-4",
|
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
|
||||||
)}>
|
)}>
|
||||||
{partIndex > -1 && !!exam.parts[partIndex].context && renderText()}
|
|
||||||
|
|
||||||
{exerciseIndex > -1 &&
|
{textRender ?
|
||||||
partIndex > -1 &&
|
renderText() :
|
||||||
exerciseIndex < exam.parts[partIndex].exercises.length &&
|
<>
|
||||||
!showSolutions &&
|
{exam.parts[partIndex].context && renderText()}
|
||||||
!editing &&
|
{(showSolutions || editing) ?
|
||||||
currentExercise &&
|
renderSolution(currentExercise, nextExercise, previousExercise)
|
||||||
renderExercise(currentExercise, exam.id, nextExercise, previousExercise)}
|
:
|
||||||
|
renderExercise(currentExercise, exam.id, nextExercise, previousExercise)
|
||||||
{exerciseIndex > -1 &&
|
}
|
||||||
partIndex > -1 &&
|
</>
|
||||||
exerciseIndex < exam.parts[partIndex].exercises.length &&
|
}
|
||||||
(showSolutions || editing) &&
|
|
||||||
currentExercise &&
|
|
||||||
renderSolution(currentExercise, nextExercise, previousExercise)}
|
|
||||||
</div>
|
</div>
|
||||||
{/*exerciseIndex === -1 && partIndex > 0 && (
|
{/*exerciseIndex === -1 && partIndex > 0 && (
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
@@ -401,7 +454,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
className="max-w-[200px] w-full"
|
className="max-w-[200px] w-full"
|
||||||
disabled={
|
disabled={
|
||||||
exam && typeof partIndex !== "undefined" && exam.module === "level" &&
|
exam && typeof partIndex !== "undefined" && exam.module === "level" &&
|
||||||
typeof exam.parts[0].intro === "string" && storeQuestionIndex === 0}
|
typeof exam.parts[0].intro === "string" && questionIndex === 0}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user