Fixed question numbers for fillBlanks exercises, reverted multipleChoice underline prompt, added part label to module title, and changed some styles
This commit is contained in:
@@ -72,7 +72,6 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
const original = shuffleMap[x.solution as keyof typeof shuffleMap];
|
const original = shuffleMap[x.solution as keyof typeof shuffleMap];
|
||||||
return solution.toLowerCase() === (option.options[original as keyof typeof option.options] || '').toLowerCase();
|
return solution.toLowerCase() === (option.options[original as keyof typeof option.options] || '').toLowerCase();
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
return solution.toLowerCase() === (option.options[x.solution as keyof typeof option.options] || '').toLowerCase();
|
return solution.toLowerCase() === (option.options[x.solution as keyof typeof option.options] || '').toLowerCase();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -96,6 +95,8 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
variant === "mc" ? (
|
variant === "mc" ? (
|
||||||
|
<>
|
||||||
|
{/*<span className="mr-2">{`(${id})`}</span>*/}
|
||||||
<button
|
<button
|
||||||
className={styles}
|
className={styles}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -114,6 +115,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
>
|
>
|
||||||
{userSolution?.solution === undefined ? <span className="text-transparent select-none">placeholder</span> : <span> {userSolution.solution} </span>}
|
{userSolution?.solution === undefined ? <span className="text-transparent select-none">placeholder</span> : <span> {userSolution.solution} </span>}
|
||||||
</button>
|
</button>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
className={styles}
|
className={styles}
|
||||||
@@ -145,14 +147,14 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
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">
|
||||||
<span className="text-sm w-full leading-6">
|
{false && <span className="text-sm w-full leading-6">
|
||||||
{prompt.split("\\n").map((line, index) => (
|
{prompt.split("\\n").map((line, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{line}
|
{line}
|
||||||
<br />
|
<br />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>}
|
||||||
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
|
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
|
||||||
{text.split("\\n").map((line, index) => (
|
{text.split("\\n").map((line, index) => (
|
||||||
<p key={index} className={clsx(variant === "mc" && "whitespace-pre-wrap")}>
|
<p key={index} className={clsx(variant === "mc" && "whitespace-pre-wrap")}>
|
||||||
@@ -164,11 +166,23 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
{variant === "mc" && typeCheckWordsMC(words) ? (
|
{variant === "mc" && typeCheckWordsMC(words) ? (
|
||||||
<>
|
<>
|
||||||
{currentMCSelection && (
|
{currentMCSelection && (
|
||||||
<div className="bg-mti-gray-smoke rounded-xl px-5 py-6 flex flex-col gap-4">
|
<div className="bg-mti-gray-smoke rounded-xl flex flex-col gap-4 px-16 py-8">
|
||||||
<span className="font-medium text-mti-purple-dark">Options</span>
|
<span className="font-medium text-lg text-mti-purple-dark mb-4 px-2">{`${currentMCSelection.id} - Select the appropriate word.`}</span>
|
||||||
<div className="flex gap-4 flex-wrap">
|
<div className="flex gap-4 flex-wrap justify-between">
|
||||||
{currentMCSelection.selection?.options && Object.entries(currentMCSelection.selection.options).sort((a, b) => a[0].localeCompare(b[0])).map(([key, value]) => {
|
{currentMCSelection.selection?.options && Object.entries(currentMCSelection.selection.options).sort((a, b) => a[0].localeCompare(b[0])).map(([key, value]) => {
|
||||||
return <button
|
return <div
|
||||||
|
key={v4()}
|
||||||
|
onClick={() => onSelection(currentMCSelection.id, value)}
|
||||||
|
className={clsx(
|
||||||
|
"flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base",
|
||||||
|
!!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) &&
|
||||||
|
"border-mti-purple-light",
|
||||||
|
)}>
|
||||||
|
<span className="font-semibold">{key}.</span>
|
||||||
|
<span>{value}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
/*<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"border border-mti-purple-light rounded-full px-3 py-0.5 transition ease-in-out duration-300",
|
"border border-mti-purple-light rounded-full px-3 py-0.5 transition ease-in-out duration-300",
|
||||||
!!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) &&
|
!!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) &&
|
||||||
@@ -178,7 +192,7 @@ const FillBlanks: React.FC<FillBlanksExercise & CommonProps> = ({
|
|||||||
onClick={() => onSelection(currentMCSelection.id, value)}
|
onClick={() => onSelection(currentMCSelection.id, value)}
|
||||||
>
|
>
|
||||||
{value}
|
{value}
|
||||||
</button>;
|
</button>;*/
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,24 +20,21 @@ function Question({
|
|||||||
showSolution?: boolean,
|
showSolution?: boolean,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
/*
|
|
||||||
const renderPrompt = (prompt: string) => {
|
const renderPrompt = (prompt: string) => {
|
||||||
return reactStringReplace(prompt, /((<u>)[\w\s']+(<\/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>{word}</u> : null;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
*/
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// {renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")}
|
<div className="flex flex-col gap-8">
|
||||||
<div className="flex flex-col gap-10">
|
|
||||||
{isNaN(Number(id)) ? (
|
{isNaN(Number(id)) ? (
|
||||||
<span dangerouslySetInnerHTML={{ __html: prompt }} />
|
<span className={clsx(true ? "text-lg" : "text-base")}>{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")}</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="">
|
<span className={clsx(true ? "text-lg" : "text-base")}>
|
||||||
<>
|
<>
|
||||||
{id} - <span dangerouslySetInnerHTML={{ __html: prompt }} />
|
{id} - <span>{renderPrompt(prompt).filter((x) => x?.toString() !== "<u>")}</span>
|
||||||
</>
|
</>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -61,7 +58,7 @@ function Question({
|
|||||||
key={option.id.toString()}
|
key={option.id.toString()}
|
||||||
onClick={() => (onSelectOption ? onSelectOption(option.id.toString()) : null)}
|
onClick={() => (onSelectOption ? onSelectOption(option.id.toString()) : null)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-sm",
|
"flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base",
|
||||||
userSolution === option.id.toString() && "border-mti-purple-light",
|
userSolution === option.id.toString() && "border-mti-purple-light",
|
||||||
)}>
|
)}>
|
||||||
<span className="font-semibold">{option.id.toString()}.</span>
|
<span className="font-semibold">{option.id.toString()}.</span>
|
||||||
@@ -156,7 +153,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-2 mt-4 h-fit w-full mb-20 bg-mti-gray-smoke rounded-xl px-16 py-8">
|
<div className="flex flex-col gap-2 mt-4 h-fit w-full mb-20 bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||||
<span className="text-xl font-semibold">{prompt}</span>
|
{/*<span className="text-xl font-semibold mb-2">{"Select the appropriate option."}</span>*/}
|
||||||
{questionIndex < questions.length && (
|
{questionIndex < questions.length && (
|
||||||
<Question
|
<Question
|
||||||
{...questions[questionIndex]}
|
{...questions[questionIndex]}
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ interface Props {
|
|||||||
exerciseIndex: number;
|
exerciseIndex: number;
|
||||||
totalExercises: number;
|
totalExercises: number;
|
||||||
disableTimer?: boolean;
|
disableTimer?: boolean;
|
||||||
|
partLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModuleTitle({minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false}: Props) {
|
export default function ModuleTitle({
|
||||||
|
minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false, partLabel
|
||||||
|
}: Props) {
|
||||||
const [timer, setTimer] = useState(minTimer * 60);
|
const [timer, setTimer] = useState(minTimer * 60);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [warningMode, setWarningMode] = useState(false);
|
const [warningMode, setWarningMode] = useState(false);
|
||||||
@@ -86,7 +89,12 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
|||||||
{timer <= 0 && <>00:00</>}
|
{timer <= 0 && <>00:00</>}
|
||||||
</span>
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<div className="flex gap-6 w-full h-fit items-center mt-5">
|
<div className="w-full">
|
||||||
|
{partLabel && <div className="text-3xl space-y-4">{partLabel.split('\n\n').map((line, index) => {
|
||||||
|
if(index == 0) return <p className="font-bold">{line}</p>
|
||||||
|
else return <p className="text-2xl font-semibold">{line}</p>
|
||||||
|
})}</div>}
|
||||||
|
<div className={clsx("flex gap-6 w-full h-fit items-center", partLabel ? "mt-10" : "mt-5" )}>
|
||||||
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-lg">{moduleIcon[module]}</div>
|
<div className="w-12 h-12 bg-mti-gray-smoke flex items-center justify-center rounded-lg">{moduleIcon[module]}</div>
|
||||||
<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">
|
||||||
@@ -94,12 +102,13 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot
|
|||||||
{moduleLabels[module]} exam {label && `- ${label}`}
|
{moduleLabels[module]} exam {label && `- ${label}`}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-semibold self-end">
|
<span className="text-sm font-semibold self-end">
|
||||||
Exercise {exerciseIndex}/{totalExercises}
|
Question {exerciseIndex}/{totalExercises}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar color={module} label="" percentage={(exerciseIndex * 100) / totalExercises} className="h-2 w-full" />
|
<ProgressBar color={module} label="" percentage={(exerciseIndex * 100) / totalExercises} className="h-2 w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function Question({
|
|||||||
const newSolution = solution; //questionShuffleMap ? getShuffledSolution(solution, questionShuffleMap) : solution;
|
const newSolution = solution; //questionShuffleMap ? getShuffledSolution(solution, questionShuffleMap) : solution;
|
||||||
|
|
||||||
const renderPrompt = (prompt: string) => {
|
const renderPrompt = (prompt: string) => {
|
||||||
return reactStringReplace(prompt, /((<u>)[\w\s']+(<\/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>{word}</u> : null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TextComponent({
|
function TextComponent({
|
||||||
part, highlightPhrases, contextWord, setContextWordLine
|
part, contextWord, setContextWordLine
|
||||||
}: {
|
}: {
|
||||||
part: LevelPart, highlightPhrases: string[], contextWord: string | undefined, setContextWordLine: React.Dispatch<React.SetStateAction<number | undefined>>
|
part: LevelPart, contextWord: string | undefined, setContextWordLine: React.Dispatch<React.SetStateAction<number | undefined>>
|
||||||
}) {
|
}) {
|
||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
const [lineNumbers, setLineNumbers] = useState<number[]>([]);
|
const [lineNumbers, setLineNumbers] = useState<number[]>([]);
|
||||||
@@ -49,13 +49,14 @@ function TextComponent({
|
|||||||
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.wordWrap = 'break-word';
|
|
||||||
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
|
offscreenElement.style.textAlign = computedStyle.textAlign as CanvasTextAlign;
|
||||||
|
|
||||||
const textContent = textRef.current.textContent || '';
|
const textContent = textRef.current.textContent || '';
|
||||||
textContent.split(/(\s+)/).forEach((word: string) => {
|
textContent.split(/(\s+)/).forEach((word: string) => {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = word;
|
span.textContent = word;
|
||||||
|
span.style.display = 'inline-block';
|
||||||
|
span.style.height = `calc(1em + 16px)`;
|
||||||
offscreenElement.appendChild(span);
|
offscreenElement.appendChild(span);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -139,15 +140,10 @@ function TextComponent({
|
|||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
|
<div className="border border-mti-gray-dim w-full rounded-full opacity-10" />
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
<div className="flex-shrink-0 w-8 pr-2">
|
<div ref={textRef} className="h-fit ml-2 flex flex-col gap-4">
|
||||||
{lineNumbers.map(num => (
|
{part.context!.split('\n\n').map((line, index) => {
|
||||||
<div key={num} className="text-gray-400 flex justify-end" style={{ lineHeight: `${lineHeight}px` }}>
|
return <p><span className="mr-6">{index + 1}</span>{line}</p>
|
||||||
{num}
|
})}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div ref={textRef} className="h-fit whitespace-pre-wrap ml-2">
|
|
||||||
<HighlightContent html={part.context!} highlightPhrases={highlightPhrases} firstOccurence={true} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +172,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
|
|
||||||
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 [highlightPhrases, setContextHighlight] = useState<string[]>([]);
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -304,7 +299,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
if (match) {
|
if (match) {
|
||||||
const word = match[1];
|
const word = match[1];
|
||||||
const originalLineNumber = match[2];
|
const originalLineNumber = match[2];
|
||||||
setContextHighlight([word]);
|
|
||||||
|
|
||||||
if (word !== contextWord) {
|
if (word !== contextWord) {
|
||||||
setContextWord(word);
|
setContextWord(word);
|
||||||
@@ -317,7 +311,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
|
|
||||||
currentExercise.questions[storeQuestionIndex].prompt = updatedPrompt;
|
currentExercise.questions[storeQuestionIndex].prompt = updatedPrompt;
|
||||||
} else {
|
} else {
|
||||||
setContextHighlight([]);
|
|
||||||
setContextWord(undefined);
|
setContextWord(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,8 +322,8 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
|
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storeQuestionIndex > 0) {
|
if (storeQuestionIndex > 0 || currentExercise?.type == "fillBlanks") {
|
||||||
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]);
|
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : storeQuestionIndex }]);
|
||||||
}
|
}
|
||||||
setStoreQuestionIndex(0);
|
setStoreQuestionIndex(0);
|
||||||
|
|
||||||
@@ -377,8 +370,8 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
|
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "level", exam: exam.id }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storeQuestionIndex > 0) {
|
if (storeQuestionIndex > 0 || currentExercise?.type == "fillBlanks") {
|
||||||
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]);
|
setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : storeQuestionIndex }]);
|
||||||
}
|
}
|
||||||
setStoreQuestionIndex(0);
|
setStoreQuestionIndex(0);
|
||||||
|
|
||||||
@@ -397,7 +390,10 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
exercisesDone +
|
exercisesDone +
|
||||||
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
|
(exerciseIndex === -1 ? 0 : exerciseIndex + 1) +
|
||||||
storeQuestionIndex +
|
storeQuestionIndex +
|
||||||
multipleChoicesDone.reduce((acc, curr) => acc + curr.amount, 0)
|
multipleChoicesDone.reduce((acc, curr) => {
|
||||||
|
console.log(curr.amount);
|
||||||
|
return acc + curr.amount
|
||||||
|
}, 0)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -412,7 +408,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
</div>
|
</div>
|
||||||
<TextComponent
|
<TextComponent
|
||||||
part={exam.parts[partIndex]}
|
part={exam.parts[partIndex]}
|
||||||
highlightPhrases={highlightPhrases}
|
|
||||||
contextWord={contextWord}
|
contextWord={contextWord}
|
||||||
setContextWordLine={setContextWordLine}
|
setContextWordLine={setContextWordLine}
|
||||||
/>
|
/>
|
||||||
@@ -420,11 +415,19 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const partLabel = () => {
|
||||||
|
if (currentExercise?.type === "fillBlanks" && typeCheckWordsMC(currentExercise.words))
|
||||||
|
return `Part ${partIndex + 1} (Questions ${currentExercise.words[0].id} - ${currentExercise.words[currentExercise.words.length - 1].id})\n\n${currentExercise.prompt}`
|
||||||
|
if (currentExercise?.type === "multipleChoice")
|
||||||
|
return `Part ${partIndex + 1} (Questions ${currentExercise.questions[0].id} - ${currentExercise.questions[currentExercise.questions.length - 1].id})\n\n${currentExercise.prompt}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col h-full w-full gap-8 items-center">
|
<div className="flex flex-col h-full w-full gap-8 items-center">
|
||||||
<BlankQuestionsModal isOpen={showBlankModal} onClose={confirmFinishModule} />
|
<BlankQuestionsModal isOpen={showBlankModal} onClose={confirmFinishModule} />
|
||||||
<ModuleTitle
|
<ModuleTitle
|
||||||
|
partLabel={partLabel()}
|
||||||
minTimer={exam.minTimer}
|
minTimer={exam.minTimer}
|
||||||
exerciseIndex={calculateExerciseIndex()}
|
exerciseIndex={calculateExerciseIndex()}
|
||||||
module="level"
|
module="level"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const countExercises = (exercises: Exercise[]) => {
|
|||||||
const lengthMap = exercises.map((e) => {
|
const lengthMap = exercises.map((e) => {
|
||||||
if (e.type === "multipleChoice") return e.questions.length;
|
if (e.type === "multipleChoice") return e.questions.length;
|
||||||
if (e.type === "interactiveSpeaking") return e.prompts.length;
|
if (e.type === "interactiveSpeaking") return e.prompts.length;
|
||||||
|
if (e.type === "fillBlanks") return e.words.length;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user