From abddead4023ef7b3d0b1c0fc32e5ad2fa4672966 Mon Sep 17 00:00:00 2001 From: Carlos Mesquita Date: Mon, 19 Aug 2024 23:43:08 +0100 Subject: [PATCH 1/2] Fixed question numbers for fillBlanks exercises, reverted multipleChoice underline prompt, added part label to module title, and changed some styles --- src/components/Exercises/FillBlanks/index.tsx | 156 ++++++++++-------- src/components/Exercises/MultipleChoice.tsx | 17 +- src/components/Medium/ModuleTitle.tsx | 53 +++--- src/components/Solutions/MultipleChoice.tsx | 2 +- src/exams/Level.tsx | 45 ++--- src/utils/moduleUtils.ts | 1 + 6 files changed, 149 insertions(+), 125 deletions(-) diff --git a/src/components/Exercises/FillBlanks/index.tsx b/src/components/Exercises/FillBlanks/index.tsx index 44f8d693..fe95bb51 100644 --- a/src/components/Exercises/FillBlanks/index.tsx +++ b/src/components/Exercises/FillBlanks/index.tsx @@ -44,44 +44,43 @@ const FillBlanks: React.FC = ({ const calculateScore = () => { const total = text.match(/({{\d+}})/g)?.length || 0; const correct = userSolutions.filter((x) => { - const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution; - if (!solution) return false; - - const option = words.find((w) => { - if (typeof w === "string") { - return w.toLowerCase() === x.solution.toLowerCase(); - } else if ('letter' in w) { - return w.word.toLowerCase() === x.solution.toLowerCase(); - } else { - return w.id === x.id; - } - }); - if (!option) return false; - - if (typeof option === "string") { - return solution.toLowerCase() === option.toLowerCase(); - } else if ('letter' in option) { - return solution.toLowerCase() === option.word.toLowerCase(); - } else if ('options' in option) { - /* - if (shuffleMaps.length !== 0) { - const shuffleMap = shuffleMaps.find((map) => map.id == x.id) - if (!shuffleMap) { - return false; - } - const original = shuffleMap[x.solution as keyof typeof shuffleMap]; - return solution.toLowerCase() === (option.options[original as keyof typeof option.options] || '').toLowerCase(); - }*/ + const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution; + if (!solution) return false; - return solution.toLowerCase() === (option.options[x.solution as keyof typeof option.options] || '').toLowerCase(); - } - return false; + const option = words.find((w) => { + if (typeof w === "string") { + return w.toLowerCase() === x.solution.toLowerCase(); + } else if ('letter' in w) { + return w.word.toLowerCase() === x.solution.toLowerCase(); + } else { + return w.id === x.id; + } + }); + if (!option) return false; + + if (typeof option === "string") { + return solution.toLowerCase() === option.toLowerCase(); + } else if ('letter' in option) { + return solution.toLowerCase() === option.word.toLowerCase(); + } else if ('options' in option) { + /* + if (shuffleMaps.length !== 0) { + const shuffleMap = shuffleMaps.find((map) => map.id == x.id) + if (!shuffleMap) { + return false; + } + 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[x.solution as keyof typeof option.options] || '').toLowerCase(); + } + return false; }).length; - + const missing = total - userSolutions.filter((x) => solutions.find((y) => x.id.toString() === y.id.toString())).length; - + return { total, correct, missing }; - }; + }; const renderLines = (line: string) => { return ( @@ -96,24 +95,27 @@ const FillBlanks: React.FC = ({ ) return ( variant === "mc" ? ( - + <> + {/*{`(${id})`}*/} + + ) : ( = ({ } /*const getShuffles = () => { - let shuffle = {}; - if (shuffleMaps.length !== 0) { - shuffle = { - shuffleMaps: shuffleMaps.filter((map) => - answers.some(answer => answer.id === map.id) - ) - } - } - return shuffle; - }*/ + let shuffle = {}; + if (shuffleMaps.length !== 0) { + shuffle = { + shuffleMaps: shuffleMaps.filter((map) => + answers.some(answer => answer.id === map.id) + ) + } + } + return shuffle; + }*/ return ( <>
- + {false && {prompt.split("\\n").map((line, index) => ( {line}
))} -
+
} {text.split("\\n").map((line, index) => (

@@ -162,23 +164,35 @@ const FillBlanks: React.FC = ({ ))} {variant === "mc" && typeCheckWordsMC(words) ? ( - <> + <> {currentMCSelection && ( -

- Options -
+
+ {`${currentMCSelection.id} - Select the appropriate word.`} +
{currentMCSelection.selection?.options && Object.entries(currentMCSelection.selection.options).sort((a, b) => a[0].localeCompare(b[0])).map(([key, value]) => { - return
+ + /*; + ;*/ })}
diff --git a/src/components/Exercises/MultipleChoice.tsx b/src/components/Exercises/MultipleChoice.tsx index 70d8a3ba..e818a60b 100644 --- a/src/components/Exercises/MultipleChoice.tsx +++ b/src/components/Exercises/MultipleChoice.tsx @@ -20,24 +20,21 @@ function Question({ showSolution?: boolean, }) { - /* const renderPrompt = (prompt: string) => { - return reactStringReplace(prompt, /(()[\w\s']+(<\/u>))/g, (match) => { + return reactStringReplace(prompt, /(.*?<\/u>)/g, (match) => { const word = match.replaceAll("", "").replaceAll("", ""); return word.length > 0 ? {word} : null; }); }; - */ return ( - // {renderPrompt(prompt).filter((x) => x?.toString() !== "")} -
+
{isNaN(Number(id)) ? ( - + {renderPrompt(prompt).filter((x) => x?.toString() !== "")} ) : ( - + <> - {id} - + {id} - {renderPrompt(prompt).filter((x) => x?.toString() !== "")} )} @@ -61,7 +58,7 @@ function Question({ key={option.id.toString()} onClick={() => (onSelectOption ? onSelectOption(option.id.toString()) : null)} 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", )}> {option.id.toString()}. @@ -156,7 +153,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti return ( <>
- {prompt} + {/*{"Select the appropriate option."}*/} {questionIndex < questions.length && ( state.setHasExamEnded); - const {timeSpent} = useExamStore((state) => state); + const { timeSpent } = useExamStore((state) => state); useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]); @@ -45,7 +48,7 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot if (timer < 300 && !warningMode) setWarningMode(true); }, [timer, warningMode]); - const moduleIcon: {[key in Module]: ReactNode} = { + const moduleIcon: { [key in Module]: ReactNode } = { reading: , listening: , writing: , @@ -67,9 +70,9 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot "absolute top-4 right-6 bg-mti-gray-seasalt px-4 py-3 flex items-center gap-2 rounded-full text-mti-gray-davy", warningMode && !disableTimer && "bg-mti-red-light text-mti-gray-seasalt", )} - initial={{scale: warningMode && !disableTimer ? 0.8 : 1}} - animate={{scale: warningMode && !disableTimer ? 1.1 : 1}} - transition={{repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut"}}> + initial={{ scale: warningMode && !disableTimer ? 0.8 : 1 }} + animate={{ scale: warningMode && !disableTimer ? 1.1 : 1 }} + transition={{ repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut" }}> {timer > 0 && ( @@ -86,18 +89,24 @@ export default function ModuleTitle({minTimer, module, label, exerciseIndex, tot {timer <= 0 && <>00:00} -
-
{moduleIcon[module]}
-
-
- - {moduleLabels[module]} exam {label && `- ${label}`} - - - Exercise {exerciseIndex}/{totalExercises} - +
+ {partLabel &&
{partLabel.split('\n\n').map((line, index) => { + if(index == 0) return

{line}

+ else return

{line}

+ })}
} +
+
{moduleIcon[module]}
+
+
+ + {moduleLabels[module]} exam {label && `- ${label}`} + + + Question {exerciseIndex}/{totalExercises} + +
+
-
diff --git a/src/components/Solutions/MultipleChoice.tsx b/src/components/Solutions/MultipleChoice.tsx index d3ecfabc..095e3a5e 100644 --- a/src/components/Solutions/MultipleChoice.tsx +++ b/src/components/Solutions/MultipleChoice.tsx @@ -49,7 +49,7 @@ function Question({ const newSolution = solution; //questionShuffleMap ? getShuffledSolution(solution, questionShuffleMap) : solution; const renderPrompt = (prompt: string) => { - return reactStringReplace(prompt, /(()[\w\s']+(<\/u>))/g, (match) => { + return reactStringReplace(prompt, /(.*?<\/u>)/g, (match) => { const word = match.replaceAll("", "").replaceAll("", ""); return word.length > 0 ? {word} : null; }); diff --git a/src/exams/Level.tsx b/src/exams/Level.tsx index ea360929..6b2adf40 100644 --- a/src/exams/Level.tsx +++ b/src/exams/Level.tsx @@ -25,9 +25,9 @@ interface Props { } function TextComponent({ - part, highlightPhrases, contextWord, setContextWordLine + part, contextWord, setContextWordLine }: { - part: LevelPart, highlightPhrases: string[], contextWord: string | undefined, setContextWordLine: React.Dispatch> + part: LevelPart, contextWord: string | undefined, setContextWordLine: React.Dispatch> }) { const textRef = useRef(null); const [lineNumbers, setLineNumbers] = useState([]); @@ -49,13 +49,14 @@ function TextComponent({ offscreenElement.style.width = `${containerWidth}px`; offscreenElement.style.font = computedStyle.font; offscreenElement.style.lineHeight = computedStyle.lineHeight; - offscreenElement.style.wordWrap = 'break-word'; 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; + span.style.display = 'inline-block'; + span.style.height = `calc(1em + 16px)`; offscreenElement.appendChild(span); }); @@ -139,15 +140,10 @@ function TextComponent({
-
- {lineNumbers.map(num => ( -
- {num} -
- ))} -
-
- +
+ {part.context!.split('\n\n').map((line, index) => { + return

{index + 1}{line}

+ })}
@@ -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 [highlightPhrases, setContextHighlight] = useState([]); const [contextWord, setContextWord] = useState(undefined); const [contextWordLine, setContextWordLine] = useState(undefined); @@ -304,7 +299,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing = if (match) { const word = match[1]; const originalLineNumber = match[2]; - setContextHighlight([word]); if (word !== contextWord) { setContextWord(word); @@ -317,7 +311,6 @@ export default function Level({ exam, showSolutions = false, onFinish, editing = currentExercise.questions[storeQuestionIndex].prompt = updatedPrompt; } else { - setContextHighlight([]); 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 }]); } - if (storeQuestionIndex > 0) { - setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]); + if (storeQuestionIndex > 0 || currentExercise?.type == "fillBlanks") { + setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : storeQuestionIndex }]); } 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 }]); } - if (storeQuestionIndex > 0) { - setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: storeQuestionIndex }]); + if (storeQuestionIndex > 0 || currentExercise?.type == "fillBlanks") { + setMultipleChoicesDone((prev) => [...prev.filter((x) => x.id !== currentExercise!.id), { id: currentExercise!.id, amount: currentExercise?.type == "fillBlanks" ? currentExercise.words.length - 1 : storeQuestionIndex }]); } setStoreQuestionIndex(0); @@ -397,7 +390,10 @@ export default function Level({ exam, showSolutions = false, onFinish, editing = exercisesDone + (exerciseIndex === -1 ? 0 : exerciseIndex + 1) + 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 =
@@ -420,11 +415,19 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
); + 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 ( <>
{ const lengthMap = exercises.map((e) => { if (e.type === "multipleChoice") return e.questions.length; if (e.type === "interactiveSpeaking") return e.prompts.length; + if (e.type === "fillBlanks") return e.words.length; return 1; }); From 3299acee36b723ceace727848b705a2a7fc64f03 Mon Sep 17 00:00:00 2001 From: Carlos Mesquita Date: Mon, 19 Aug 2024 23:46:51 +0100 Subject: [PATCH 2/2] Forgot to add a key in Level --- src/exams/Level.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/exams/Level.tsx b/src/exams/Level.tsx index 6b2adf40..a5bdf478 100644 --- a/src/exams/Level.tsx +++ b/src/exams/Level.tsx @@ -16,6 +16,7 @@ import clsx from "clsx"; import { Dispatch, Fragment, SetStateAction, use, useEffect, useMemo, useRef, useState } from "react"; import { BsChevronDown, BsChevronUp } from "react-icons/bs"; import { toast } from "react-toastify"; +import { v4 } from "uuid"; interface Props { exam: LevelExam; @@ -142,7 +143,7 @@ function TextComponent({
{part.context!.split('\n\n').map((line, index) => { - return

{index + 1}{line}

+ return

{index + 1}{line}

})}
@@ -390,10 +391,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing = exercisesDone + (exerciseIndex === -1 ? 0 : exerciseIndex + 1) + storeQuestionIndex + - multipleChoicesDone.reduce((acc, curr) => { - console.log(curr.amount); - return acc + curr.amount - }, 0) + multipleChoicesDone.reduce((acc, curr) => { return acc + curr.amount}, 0) ); };