Previous Level exams were being broken by the part divider changes, fixed it.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Button from "@/components/Low/Button";
|
||||
import { Module } from "@/interfaces";
|
||||
import { LevelPart, UserSolution } from "@/interfaces/exam";
|
||||
import clsx from "clsx";
|
||||
import { ReactNode } from "react";
|
||||
import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen } from "react-icons/bs";
|
||||
|
||||
@@ -21,10 +22,10 @@ const PartDivider: React.FC<Props> = ({ partIndex, part, onNext }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-3/6 h-fit border bg-white rounded-3xl p-12 gap-8">
|
||||
<div className={clsx("flex flex-col h-fit border bg-white rounded-3xl p-12 gap-8", part.intro ? "w-3/6" : "items-center my-auto")}>
|
||||
{/** only level for now */}
|
||||
<div className="flex flex-row gap-4 items-center"><div className="w-12 h-12 bg-ielts-level flex items-center justify-center rounded-lg">{moduleIcon["level"]}</div><p className="text-3xl">{`Part ${partIndex + 1}`}</p></div>
|
||||
{part.intro!.split('\\n\\n').map((x, index) => <p key={`line-${index}`} className="text-2xl text-clip">{x}</p>)}
|
||||
<div className="flex flex-row gap-4 items-center"><div className="w-12 h-12 bg-ielts-level flex items-center justify-center rounded-lg">{moduleIcon["level"]}</div><p className="text-3xl">{part.intro ? `Part ${partIndex + 1}` : "Placement Test"}</p></div>
|
||||
{part.intro && part.intro.split('\\n\\n').map((x, index) => <p key={`line-${index}`} className="text-2xl text-clip">{x}</p>)}
|
||||
<div className="flex items-center justify-center mt-4">
|
||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full text-2xl">
|
||||
{partIndex === 0 ? `Start now`: `Start Part ${partIndex + 1}`}
|
||||
|
||||
@@ -31,15 +31,23 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
|
||||
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
|
||||
|
||||
const textContent = textRef.current.textContent || '';
|
||||
textContent.split(/(\s+)/).forEach((word: string) => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = word;
|
||||
offscreenElement.appendChild(span);
|
||||
const lines = textContent.split(/\n/).map(line =>
|
||||
line.split(/(\s+)/).map(word => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = word;
|
||||
return span;
|
||||
})
|
||||
);
|
||||
|
||||
// Append all spans to offscreenElement
|
||||
lines.forEach(line => {
|
||||
line.forEach(span => offscreenElement.appendChild(span));
|
||||
offscreenElement.appendChild(document.createElement('br'));
|
||||
});
|
||||
|
||||
document.body.appendChild(offscreenElement);
|
||||
|
||||
const lines: string[][] = [[]];
|
||||
const processedLines: string[][] = [[]];
|
||||
let currentLine = 1;
|
||||
let currentLineTop: number | undefined;
|
||||
let contextWordLine: number | null = null;
|
||||
@@ -58,16 +66,16 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
|
||||
if (currentLineTop !== undefined && top > currentLineTop) {
|
||||
currentLine++;
|
||||
currentLineTop = top;
|
||||
lines.push([]);
|
||||
processedLines.push([]);
|
||||
}
|
||||
|
||||
lines[lines.length - 1].push(span.textContent?.trim() || '');
|
||||
processedLines[processedLines.length - 1].push(span.textContent?.trim() || '');
|
||||
|
||||
if (contextWord && contextWordLine === null && span.textContent?.includes(contextWord)) {
|
||||
contextWordLine = currentLine;
|
||||
}
|
||||
});
|
||||
setLineNumbers(lines.map((_, index) => index + 1));
|
||||
setLineNumbers(processedLines.map((_, index) => index + 1));
|
||||
if (contextWordLine) {
|
||||
setContextWordLine(contextWordLine);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Button from "@/components/Low/Button";
|
||||
import ModuleTitle from "@/components/Medium/ModuleTitle";
|
||||
import { renderSolution } from "@/components/Solutions";
|
||||
import { Module } from "@/interfaces";
|
||||
import { Exercise, FillBlanksMCOption, LevelExam, MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap, UserSolution } from "@/interfaces/exam";
|
||||
import { Exercise, FillBlanksMCOption, LevelExam, MultipleChoiceExercise, UserSolution } from "@/interfaces/exam";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import { countExercises } from "@/utils/moduleUtils";
|
||||
import clsx from "clsx";
|
||||
@@ -68,8 +68,15 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
|
||||
});
|
||||
|
||||
const [currentExercise, setCurrentExercise] = useState<Exercise>(exam.parts[0].exercises[0]);
|
||||
const [currentExercise, setCurrentExercise] = useState<Exercise | undefined>(undefined);
|
||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
||||
const [startNow, setStartNow] = useState<boolean>(true && !showSolutions);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) {
|
||||
setCurrentExercise(exam.parts[0].exercises[0]);
|
||||
}
|
||||
}, [currentExercise, partIndex, exerciseIndex]);
|
||||
|
||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||
|
||||
@@ -108,10 +115,9 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
let exercise = exam.parts[partIndex]?.exercises[exerciseIndex];
|
||||
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);
|
||||
|
||||
return exercise;
|
||||
};
|
||||
|
||||
@@ -162,7 +168,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
return;
|
||||
}
|
||||
|
||||
if(partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways) {
|
||||
if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways) {
|
||||
modalKwargs();
|
||||
setShowQuestionsModal(true);
|
||||
}
|
||||
@@ -219,11 +225,22 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
if (i == (partIndex - 1)) break;
|
||||
for (let j = 0; j < exam.parts[i].exercises.length; j++) {
|
||||
const exercise = exam.parts[i].exercises[j];
|
||||
if (exercise.type === "multipleChoice") {
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.questions.length - 1 })
|
||||
}
|
||||
if (exercise.type === "fillBlanks") {
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.words.length - 1 })
|
||||
switch(exercise.type) {
|
||||
case 'multipleChoice':
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.questions.length - 1 })
|
||||
break;
|
||||
case 'fillBlanks':
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.words.length - 1 })
|
||||
break;
|
||||
case 'writeBlanks':
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.solutions.length - 1 })
|
||||
break;
|
||||
case 'matchSentences':
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.sentences.length - 1})
|
||||
break;
|
||||
case 'trueFalse':
|
||||
multipleChoiceQuestionsDone.push({ id: exercise.id, amount: exercise.questions.length - 1})
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,28 +250,12 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
};
|
||||
|
||||
const calculateExerciseIndex = () => {
|
||||
if (exam.parts[0].intro) {
|
||||
return exam.parts.reduce((acc, curr, index) => {
|
||||
if (index < partIndex) {
|
||||
return acc + countExercises(curr.exercises)
|
||||
}
|
||||
return acc;
|
||||
}, 0) + (questionIndex + 1);
|
||||
} else {
|
||||
if (partIndex === 0) {
|
||||
return (
|
||||
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) + questionIndex //+ multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
|
||||
);
|
||||
return exam.parts.reduce((acc, curr, index) => {
|
||||
if (index < partIndex) {
|
||||
return acc + countExercises(curr.exercises)
|
||||
}
|
||||
const exercisesPerPart = exam.parts.map((x) => x.exercises.length);
|
||||
const exercisesDone = exercisesPerPart.filter((_, index) => index < partIndex).reduce((acc, curr) => curr + acc, 0);
|
||||
return (
|
||||
exercisesDone +
|
||||
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
|
||||
questionIndex
|
||||
+ multipleChoicesDone.reduce((acc, curr) => { return acc + curr.amount }, 0)
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, 0) + (questionIndex + 1);
|
||||
};
|
||||
|
||||
const renderText = () => (
|
||||
@@ -320,11 +321,17 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
const answeredEveryQuestion = (partIndex: number) => {
|
||||
return exam.parts[partIndex].exercises.every((exercise) => {
|
||||
const userSolution = userSolutions.find(x => x.exercise === exercise.id);
|
||||
if (exercise.type === "multipleChoice") {
|
||||
return userSolution?.solutions.length === exercise.questions.length;
|
||||
}
|
||||
if (exercise.type === "fillBlanks") {
|
||||
return userSolution?.solutions.length === exercise.words.length;
|
||||
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;
|
||||
});
|
||||
@@ -405,8 +412,8 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
<>
|
||||
{exam.parts[partIndex].context && renderText()}
|
||||
{(showSolutions || editing) ?
|
||||
renderSolution(currentExercise, nextExercise, previousExercise) :
|
||||
renderExercise(currentExercise, exam.id, next, previousExercise)
|
||||
currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) :
|
||||
currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise)
|
||||
}
|
||||
</>
|
||||
}
|
||||
@@ -419,10 +426,10 @@ 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")}>
|
||||
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
|
||||
{
|
||||
!(partIndex === 0 && questionIndex === 0 && showPartDivider) &&
|
||||
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
|
||||
<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") }} /> : (
|
||||
{(showPartDivider || startNow) ? <PartDivider part={exam.parts[partIndex]} partIndex={partIndex} onNext={() => { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }} /> : (
|
||||
<>
|
||||
{exam.parts[0].intro && (
|
||||
<div className="w-full">
|
||||
@@ -470,33 +477,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
||||
)}>
|
||||
{memoizedRender}
|
||||
</div>
|
||||
{/*exerciseIndex === -1 && partIndex > 0 && (
|
||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||
<Button
|
||||
color="purple"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setExerciseIndex(exam.parts[partIndex - 1].exercises.length - 1);
|
||||
setPartIndex(partIndex - 1);
|
||||
}}
|
||||
className="max-w-[200px] w-full"
|
||||
disabled={
|
||||
exam && typeof partIndex !== "undefined" && exam.module === "level" &&
|
||||
typeof exam.parts[0].intro === "string" && questionIndex === 0}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
)*/}
|
||||
{exerciseIndex === -1 && partIndex === 0 && (
|
||||
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
|
||||
Start now
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user