ENCOA-107, ENCOA-115 Fixed completion percentage, brought back the line numbers, 'Level Exam' was replaced by 'Placement Test', Next/Back instructions with quotes, 'Submit' on last level question
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const websiteUrl = process.env.NODE_ENV === 'production' ? "https://encoach.com" : "http://localhost:3000";
|
const websiteUrl = process.env.NODE_ENV === 'production' ? "https://encoach.com" : "http://localhost:3000";
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: false,
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution;
|
const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution;
|
||||||
if (!solution) return false;
|
if (!solution) return false;
|
||||||
const option = correctWords!.find((w: any) => {
|
const option = correctWords!.find((w: any) => {
|
||||||
console.log(w);
|
|
||||||
if (typeof w === "string") {
|
if (typeof w === "string") {
|
||||||
return w.toLowerCase() === x.solution.toLowerCase();
|
return w.toLowerCase() === x.solution.toLowerCase();
|
||||||
} else if ('letter' in w) {
|
} else if ('letter' in w) {
|
||||||
@@ -85,7 +84,8 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
const id = match.replaceAll(/[\{\}]/g, "");
|
const id = match.replaceAll(/[\{\}]/g, "");
|
||||||
const userSolution = answers.find((x) => x.id === id);
|
const userSolution = answers.find((x) => x.id === id);
|
||||||
const styles = clsx(
|
const styles = clsx(
|
||||||
"rounded-full hover:text-white focus:ring-0 focus:outline-none focus:!text-white focus:bg-mti-purple transition duration-300 ease-in-out my-1 px-5 py-2 text-center",
|
"rounded-full hover:text-white transition duration-300 ease-in-out my-1 px-5 py-2 text-center",
|
||||||
|
currentMCSelection?.id == id && "!bg-mti-purple !text-white !outline-none !ring-0",
|
||||||
!userSolution && "text-center text-mti-purple-light bg-mti-purple-ultralight",
|
!userSolution && "text-center text-mti-purple-light bg-mti-purple-ultralight",
|
||||||
userSolution && "text-center text-mti-purple-dark bg-mti-purple-ultralight",
|
userSolution && "text-center text-mti-purple-dark bg-mti-purple-ultralight",
|
||||||
)
|
)
|
||||||
@@ -122,7 +122,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [variant, words, setCurrentMCSelection, answers]);
|
}, [variant, words, setCurrentMCSelection, answers, currentMCSelection]);
|
||||||
|
|
||||||
const memoizedLines = useMemo(() => {
|
const memoizedLines = useMemo(() => {
|
||||||
return text.split("\\n").map((line, index) => (
|
return text.split("\\n").map((line, index) => (
|
||||||
@@ -131,7 +131,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
));
|
));
|
||||||
}, [text, variant, renderLines]);
|
}, [text, variant, renderLines, currentMCSelection]);
|
||||||
|
|
||||||
|
|
||||||
const onSelection = (questionID: string, value: string) => {
|
const onSelection = (questionID: string, value: string) => {
|
||||||
@@ -139,10 +139,9 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//if (variant === "mc") {
|
if (variant === "mc") {
|
||||||
console.log(answers);
|
|
||||||
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])
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
questionIndex,
|
questionIndex,
|
||||||
|
exerciseIndex,
|
||||||
exam,
|
exam,
|
||||||
shuffles,
|
shuffles,
|
||||||
hasExamEnded,
|
hasExamEnded,
|
||||||
@@ -143,7 +144,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
}
|
}
|
||||||
return isSolutionCorrect || false;
|
return isSolutionCorrect || false;
|
||||||
}).length;
|
}).length;
|
||||||
const missing = total - correct;
|
const missing = total - answers!.filter((x) => questions.find((y) => x.question.toString() === y.id.toString())).length;
|
||||||
return { total, correct, missing };
|
return { total, correct, missing };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -193,7 +194,12 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="purple" onClick={next} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={next} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
{
|
||||||
|
exam && exam.module === "level" &&
|
||||||
|
partIndex === exam.parts.length - 1 &&
|
||||||
|
exerciseIndex === exam.parts[partIndex].exercises.length - 1 &&
|
||||||
|
questionIndex === questions.length - 1
|
||||||
|
? "Submit" : "Next"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import Button from "../Low/Button";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
minTimer: number;
|
minTimer: number;
|
||||||
@@ -85,7 +86,7 @@ export default function ModuleTitle({
|
|||||||
return "!bg-mti-gray-davy !border--mti-gray-davy !text-mti-gray-davy !text-white hover:!bg-gray-700";
|
return "!bg-mti-gray-davy !border--mti-gray-davy !text-mti-gray-davy !text-white hover:!bg-gray-700";
|
||||||
}
|
}
|
||||||
|
|
||||||
return userQuestionSolution === newSolution ?
|
return userQuestionSolution === newSolution ?
|
||||||
"!bg-mti-purple-light !text-mti-purple-light !text-white hover:!bg-mti-purple-dark" :
|
"!bg-mti-purple-light !text-mti-purple-light !text-white hover:!bg-mti-purple-dark" :
|
||||||
"!bg-mti-rose-light !border-mti-rose-light !text-mti-rose-light !text-white hover:!bg-mti-rose-dark";
|
"!bg-mti-rose-light !border-mti-rose-light !text-mti-rose-light !text-white hover:!bg-mti-rose-dark";
|
||||||
}
|
}
|
||||||
@@ -105,10 +106,10 @@ export default function ModuleTitle({
|
|||||||
key={index}
|
key={index}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"w-12 h-12 flex items-center justify-center rounded-lg text-sm font-bold transition-all duration-200 ease-in-out",
|
"w-12 h-12 flex items-center justify-center rounded-lg text-sm font-bold transition-all duration-200 ease-in-out",
|
||||||
(showSolutions ?
|
(showSolutions ?
|
||||||
getQuestionColor(questionNumber.toString(), solution, userQuestionSolution) :
|
getQuestionColor(questionNumber.toString(), solution, userQuestionSolution) :
|
||||||
(isAnswered ?
|
(isAnswered ?
|
||||||
"bg-mti-purple-light border-mti-purple-light text-white hover:bg-mti-purple-dark hover:border-mti-purple-dark":
|
"bg-mti-purple-light border-mti-purple-light text-white hover:bg-mti-purple-dark hover:border-mti-purple-dark" :
|
||||||
"bg-white border-gray-400 hover:bg-gray-100 hover:text-gray-700"
|
"bg-white border-gray-400 hover:bg-gray-100 hover:text-gray-700"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -126,25 +127,26 @@ export default function ModuleTitle({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showTimer && <Timer minTimer={minTimer} disableTimer={disableTimer} />}
|
{showTimer && <Timer minTimer={minTimer} disableTimer={disableTimer} />}
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{partLabel && (
|
{partLabel && (
|
||||||
<div className="text-3xl space-y-4">
|
<div className="text-3xl space-y-4">
|
||||||
{partLabel.split("\n\n").map((line, index) => {
|
{partLabel.split("\n\n").map((partInstructions, index) => {
|
||||||
if (index == 0)
|
if (index === 0)
|
||||||
return (
|
return (
|
||||||
<p key={index} className="font-bold">
|
<p key={index} className="font-bold">
|
||||||
{line}
|
{partInstructions}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
<p key={index} className="text-2xl font-semibold">
|
<div key={index} className="text-2xl font-semibold flex flex-col gap-2">
|
||||||
{line}
|
{partInstructions.split("\\n").map((line, lineIndex) => (
|
||||||
</p>
|
<span key={lineIndex}>{line}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +156,10 @@ export default function ModuleTitle({
|
|||||||
<div className="flex flex-col gap-3 w-full">
|
<div className="flex flex-col gap-3 w-full">
|
||||||
<div className="w-full flex justify-between">
|
<div className="w-full flex justify-between">
|
||||||
<span className="text-base font-semibold">
|
<span className="text-base font-semibold">
|
||||||
{moduleLabels[module]} exam {label && `- ${label}`}
|
{module === "level"
|
||||||
|
? "Placement Test"
|
||||||
|
: `${moduleLabels[module]} exam${label ? ` - ${label}` : ''}`
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-semibold self-end">
|
<span className="text-sm font-semibold self-end">
|
||||||
Question {exerciseIndex}/{totalExercises}
|
Question {exerciseIndex}/{totalExercises}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap } from "@/interfaces/exam";
|
import { MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap } from "@/interfaces/exam";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { CommonProps } from ".";
|
import { CommonProps } from ".";
|
||||||
import Button from "../Low/Button";
|
import Button from "../Low/Button";
|
||||||
@@ -101,7 +100,7 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
).length;
|
).length;
|
||||||
const missing = total - userSolutions.filter((x) => questions.find((y) => y.id.toString() === x.question.toString())).length;
|
const missing = total - userSolutions.filter((x) => questions.find((y) => x.question.toString() === y.id.toString())).length;
|
||||||
return { total, correct, missing };
|
return { total, correct, missing };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LevelPart } from "@/interfaces/exam";
|
import { LevelPart } from "@/interfaces/exam";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
part: LevelPart,
|
part: LevelPart,
|
||||||
@@ -9,78 +9,65 @@ interface Props {
|
|||||||
|
|
||||||
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 [lineNumbers, setLineNumbers] = useState<number[]>([]);
|
||||||
|
const [lineHeight, setLineHeight] = useState<number>(0);
|
||||||
|
|
||||||
const calculateLineNumbers = () => {
|
const calculateLineNumbers = () => {
|
||||||
if (textRef.current) {
|
if (textRef.current) {
|
||||||
const computedStyle = window.getComputedStyle(textRef.current);
|
const computedStyle = window.getComputedStyle(textRef.current);
|
||||||
|
const lineHeightValue = parseFloat(computedStyle.lineHeight);
|
||||||
const containerWidth = textRef.current.clientWidth;
|
const containerWidth = textRef.current.clientWidth;
|
||||||
|
setLineHeight(lineHeightValue);
|
||||||
|
|
||||||
const offscreenElement = document.createElement('div');
|
const offscreenElement = document.createElement('div');
|
||||||
offscreenElement.style.position = 'absolute';
|
offscreenElement.style.position = 'absolute';
|
||||||
offscreenElement.style.top = '-9999px';
|
offscreenElement.style.top = '-9999px';
|
||||||
offscreenElement.style.left = '-9999px';
|
offscreenElement.style.left = '-9999px';
|
||||||
|
offscreenElement.style.whiteSpace = 'pre-wrap';
|
||||||
offscreenElement.style.width = `${containerWidth}px`;
|
offscreenElement.style.width = `${containerWidth}px`;
|
||||||
offscreenElement.style.font = computedStyle.font;
|
offscreenElement.style.font = computedStyle.font;
|
||||||
offscreenElement.style.lineHeight = computedStyle.lineHeight;
|
offscreenElement.style.lineHeight = computedStyle.lineHeight;
|
||||||
offscreenElement.style.whiteSpace = 'pre-wrap';
|
|
||||||
offscreenElement.style.wordWrap = 'break-word';
|
offscreenElement.style.wordWrap = 'break-word';
|
||||||
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
|
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
|
||||||
|
|
||||||
const paragraphs = part.context!.split('\n\n');
|
const textContent = textRef.current.textContent || '';
|
||||||
let currentLine = 1;
|
textContent.split(/(\s+)/).forEach((word: string) => {
|
||||||
let contextWordLine: number | null = null;
|
const span = document.createElement('span');
|
||||||
const paragraphLineStarts: number[] = [];
|
span.textContent = word;
|
||||||
|
offscreenElement.appendChild(span);
|
||||||
paragraphs.forEach((paragraph, pIndex) => {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.style.margin = '0';
|
|
||||||
p.style.padding = '0';
|
|
||||||
|
|
||||||
paragraph.split(/(\s+)/).forEach((word: string) => {
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.textContent = word;
|
|
||||||
p.appendChild(span);
|
|
||||||
});
|
|
||||||
|
|
||||||
offscreenElement.appendChild(p);
|
|
||||||
|
|
||||||
if (pIndex < paragraphs.length - 1) {
|
|
||||||
const gap = document.createElement('div');
|
|
||||||
gap.style.height = '16px'; // gap-4
|
|
||||||
offscreenElement.appendChild(gap);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild(offscreenElement);
|
document.body.appendChild(offscreenElement);
|
||||||
|
|
||||||
|
const lines: string[][] = [[]];
|
||||||
|
let currentLine = 1;
|
||||||
let currentLineTop: number | undefined;
|
let currentLineTop: number | undefined;
|
||||||
const elements = offscreenElement.querySelectorAll('p, div');
|
let contextWordLine: number | null = null;
|
||||||
|
|
||||||
elements.forEach((element) => {
|
const firstChild = offscreenElement.firstChild as HTMLElement;
|
||||||
if (element.tagName === 'P') {
|
if (firstChild) {
|
||||||
const spans = element.querySelectorAll<HTMLSpanElement>('span');
|
currentLineTop = firstChild.getBoundingClientRect().top;
|
||||||
paragraphLineStarts.push(currentLine);
|
}
|
||||||
|
|
||||||
spans.forEach(span => {
|
const spans = offscreenElement.querySelectorAll<HTMLSpanElement>('span');
|
||||||
const rect = span.getBoundingClientRect();
|
|
||||||
const top = rect.top;
|
|
||||||
|
|
||||||
if (currentLineTop === undefined || top > currentLineTop) {
|
spans.forEach(span => {
|
||||||
if (currentLineTop !== undefined) {
|
const rect = span.getBoundingClientRect();
|
||||||
currentLine++;
|
const top = rect.top;
|
||||||
}
|
|
||||||
currentLineTop = top;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextWord && contextWordLine === null && span.textContent?.includes(contextWord)) {
|
if (currentLineTop !== undefined && top > currentLineTop) {
|
||||||
contextWordLine = currentLine;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (element.tagName === 'DIV') { // Gap
|
|
||||||
currentLine++;
|
currentLine++;
|
||||||
currentLineTop = undefined;
|
currentLineTop = top;
|
||||||
|
lines.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines[lines.length - 1].push(span.textContent?.trim() || '');
|
||||||
|
|
||||||
|
if (contextWord && contextWordLine === null && span.textContent?.includes(contextWord)) {
|
||||||
|
contextWordLine = currentLine;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setLineNumbers(lines.map((_, index) => index + 1));
|
||||||
if (contextWordLine) {
|
if (contextWordLine) {
|
||||||
setContextWordLine(contextWordLine);
|
setContextWordLine(contextWordLine);
|
||||||
}
|
}
|
||||||
@@ -131,10 +118,15 @@ const TextComponent: React.FC<Props> = ({ part, contextWord, setContextWordLine
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div ref={textRef} className="h-fit ml-2 flex flex-col gap-4">
|
<div className="flex-shrink-0 w-8 pr-2">
|
||||||
{part.context!.split('\n\n').map((line, index) => {
|
{lineNumbers.map(num => (
|
||||||
return <p key={`line-${index}`}><span className="mr-6">{index + 1}</span>{line}</p>
|
<div key={num} className="text-gray-400 flex justify-end" style={{ lineHeight: `${lineHeight}px` }}>
|
||||||
})}
|
{num}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div ref={textRef} className="h-fit whitespace-pre-wrap ml-2">
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: part.context! }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Exercise, FillBlanksMCOption, LevelExam, MultipleChoiceExercise, Multip
|
|||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import { countExercises } from "@/utils/moduleUtils";
|
import { countExercises } from "@/utils/moduleUtils";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { use, useEffect, useState } from "react";
|
import { use, useEffect, useMemo, useState } from "react";
|
||||||
import TextComponent from "./TextComponent";
|
import TextComponent from "./TextComponent";
|
||||||
import PartDivider from "./PartDivider";
|
import PartDivider from "./PartDivider";
|
||||||
import Timer from "@/components/Medium/Timer";
|
import Timer from "@/components/Medium/Timer";
|
||||||
@@ -56,6 +56,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
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 [textRender, setTextRender] = useState(false);
|
||||||
|
const [changedPrompt, setChangedPrompt] = 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]);
|
||||||
|
|
||||||
@@ -66,8 +67,6 @@ 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 [currentExercise, setCurrentExercise] = useState<Exercise>(exam.parts[0].exercises[0]);
|
const [currentExercise, setCurrentExercise] = useState<Exercise>(exam.parts[0].exercises[0]);
|
||||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
||||||
|
|
||||||
@@ -120,37 +119,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
}, [partIndex, exerciseIndex, questionIndex]);
|
}, [partIndex, exerciseIndex, questionIndex]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
|
|
||||||
if (
|
|
||||||
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) {
|
|
||||||
const word = match[1];
|
|
||||||
const originalLineNumber = match[2];
|
|
||||||
|
|
||||||
if (word !== contextWord) {
|
|
||||||
setContextWord(word);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedPrompt = currentExercise.questions[questionIndex].prompt.replace(
|
|
||||||
`in line ${originalLineNumber}`,
|
|
||||||
`in line ${contextWordLine || originalLineNumber}`
|
|
||||||
);
|
|
||||||
|
|
||||||
currentExercise.questions[questionIndex].prompt = updatedPrompt;
|
|
||||||
} else {
|
|
||||||
setContextWord(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentExercise, questionIndex]);
|
|
||||||
|
|
||||||
const nextExercise = (solution?: UserSolution) => {
|
const nextExercise = (solution?: UserSolution) => {
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
|
|
||||||
@@ -341,6 +309,38 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const regex = /.*?['"](.*?)['"] in line (\d+)\?$/;
|
||||||
|
if (
|
||||||
|
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) {
|
||||||
|
const word = match[1];
|
||||||
|
const originalLineNumber = match[2];
|
||||||
|
|
||||||
|
if (word !== contextWord) {
|
||||||
|
setContextWord(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedPrompt = currentExercise.questions[questionIndex].prompt.replace(
|
||||||
|
`in line ${originalLineNumber}`,
|
||||||
|
`in line ${contextWordLine || originalLineNumber}`
|
||||||
|
);
|
||||||
|
|
||||||
|
currentExercise.questions[questionIndex].prompt = updatedPrompt;
|
||||||
|
setChangedPrompt(true);
|
||||||
|
} else {
|
||||||
|
setContextWord(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentExercise, questionIndex, contextWordLine]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (continueAnyways) {
|
if (continueAnyways) {
|
||||||
setContinueAnyways(false);
|
setContinueAnyways(false);
|
||||||
@@ -375,6 +375,24 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const memoizedRender = useMemo(() => {
|
||||||
|
setChangedPrompt(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{textRender ?
|
||||||
|
renderText() :
|
||||||
|
<>
|
||||||
|
{exam.parts[partIndex].context && renderText()}
|
||||||
|
{(showSolutions || editing) ?
|
||||||
|
renderSolution(currentExercise, nextExercise, previousExercise)
|
||||||
|
:
|
||||||
|
renderExercise(currentExercise, exam.id, nextExercise, previousExercise)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>)
|
||||||
|
}, [textRender, currentExercise, changedPrompt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<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")}>
|
||||||
@@ -429,18 +447,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
"mb-20 w-full",
|
"mb-20 w-full",
|
||||||
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
|
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
|
||||||
)}>
|
)}>
|
||||||
|
{memoizedRender}
|
||||||
{textRender ?
|
|
||||||
renderText() :
|
|
||||||
<>
|
|
||||||
{exam.parts[partIndex].context && renderText()}
|
|
||||||
{(showSolutions || editing) ?
|
|
||||||
renderSolution(currentExercise, nextExercise, previousExercise)
|
|
||||||
:
|
|
||||||
renderExercise(currentExercise, exam.id, 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">
|
||||||
|
|||||||
Reference in New Issue
Block a user