Redesigned the MatchSentences exercise

This commit is contained in:
Tiago Ribeiro
2023-06-22 22:28:29 +01:00
parent fe4a97ec85
commit 79ed521703
9 changed files with 146 additions and 86 deletions

View File

@@ -40,6 +40,7 @@
"react-player": "^2.12.0", "react-player": "^2.12.0",
"react-string-replace": "^1.1.0", "react-string-replace": "^1.1.0",
"react-toastify": "^9.1.2", "react-toastify": "^9.1.2",
"react-xarrows": "^2.0.2",
"swr": "^2.1.3", "swr": "^2.1.3",
"typescript": "4.9.5", "typescript": "4.9.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",

View File

@@ -7,6 +7,7 @@ import {Fragment, useState} from "react";
import LineTo from "react-lineto"; import LineTo from "react-lineto";
import {CommonProps} from "."; import {CommonProps} from ".";
import Button from "../Low/Button"; import Button from "../Low/Button";
import Xarrow from "react-xarrows";
export default function MatchSentences({id, options, type, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) { export default function MatchSentences({id, options, type, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
const [selectedQuestion, setSelectedQuestion] = useState<string>(); const [selectedQuestion, setSelectedQuestion] = useState<string>();
@@ -32,8 +33,8 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
return ( return (
<> <>
<div className="flex flex-col items-center gap-8"> <div className="flex flex-col gap-4 mt-4 h-full mb-20">
<span className="text-base md:text-lg font-medium text-center px-2 md:px-4 lg:px-48"> <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}
@@ -41,55 +42,44 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
</Fragment> </Fragment>
))} ))}
</span> </span>
<div className="grid grid-cols-2 gap-16 place-items-center"> <div className="flex gap-8 w-full items-center justify-between bg-mti-gray-smoke rounded-xl px-24 py-6">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-4">
{sentences.map(({sentence, id, color}) => ( {sentences.map(({sentence, id, color}) => (
<div <div key={`question_${id}`} className="flex items-center justify-end gap-2 cursor-pointer">
key={`question_${id}`} <span>{sentence} </span>
className="flex items-center justify-end gap-2 cursor-pointer" <button
onClick={() => setSelectedQuestion((prev) => (prev === id ? undefined : id))}> id={id}
<span> onClick={() => setSelectedQuestion((prev) => (prev === id ? undefined : id))}
<span className="font-semibold">{id}.</span> {sentence}{" "} className={clsx(
</span> "bg-mti-green-ultralight text-mti-green hover:text-white hover:bg-mti-green w-8 h-8 rounded-full z-10",
<div "transition duration-300 ease-in-out",
style={{borderColor: color, backgroundColor: selectedQuestion === id ? color : "transparent"}} selectedQuestion === id && "!text-white bg-mti-green",
className={clsx("border-2 border-blue-500 w-4 h-4 rounded-full", id)} id,
/> )}>
{id}
</button>
</div> </div>
))} ))}
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-4">
{options.map(({sentence, id}) => ( {options.map(({sentence, id}) => (
<div <div key={`answer_${id}`} className={clsx("flex items-center justify-start gap-2 cursor-pointer")}>
key={`answer_${id}`} <button
className={clsx("flex items-center justify-start gap-2 cursor-pointer")} id={id}
onClick={() => selectOption(id)}> onClick={() => selectOption(id)}
<div className={clsx(
style={ "bg-mti-green-ultralight text-mti-green hover:text-white hover:bg-mti-green w-8 h-8 rounded-full z-10",
answers.find((x) => x.option === id) "transition duration-300 ease-in-out",
? { id,
border: `2px solid ${getSentenceColor(answers.find((x) => x.option === id)!.question)}`, )}>
} {id}
: {} </button>
} <span>{sentence}</span>
className={clsx("border-2 border-green-500 bg-transparent w-4 h-4 rounded-full", id)}
/>
<span>
<span className="font-semibold">{id}.</span> {sentence}{" "}
</span>
</div> </div>
))} ))}
</div> </div>
{answers.map((solution, index) => ( {answers.map((solution, index) => (
<div key={`solution_${index}`} className="absolute"> <Xarrow key={index} start={solution.question} end={solution.option} lineColor="#307912" showHead={false} />
<LineTo
className="rounded-full"
from={solution.question}
to={solution.option}
borderColor={sentences.find((x) => x.id === solution.question)!.color}
borderWidth={5}
/>
</div>
))} ))}
</div> </div>
</div> </div>

View File

@@ -7,12 +7,13 @@ import ProgressBar from "../Low/ProgressBar";
interface Props { interface Props {
minTimer: number; minTimer: number;
module: Module; module: Module;
label?: string;
exerciseIndex: number; exerciseIndex: number;
totalExercises: number; totalExercises: number;
disableTimer?: boolean; disableTimer?: boolean;
} }
export default function ModuleTitle({minTimer, module, exerciseIndex, totalExercises, disableTimer = false}: Props) { export default function ModuleTitle({minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false}: Props) {
const [timer, setTimer] = useState(minTimer * 60); const [timer, setTimer] = useState(minTimer * 60);
useEffect(() => { useEffect(() => {
@@ -55,7 +56,9 @@ export default function ModuleTitle({minTimer, module, exerciseIndex, totalExerc
<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">
<span className="text-base font-semibold">{moduleLabels[module]} exam</span> <span className="text-base font-semibold">
{moduleLabels[module]} exam {label && `- ${label}`}
</span>
<span className="text-xs font-normal self-end text-mti-gray-davy"> <span className="text-xs font-normal self-end text-mti-gray-davy">
Question {exerciseIndex}/{totalExercises} Question {exerciseIndex}/{totalExercises}
</span> </span>

View File

@@ -82,6 +82,17 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
</p> </p>
))} ))}
</span> </span>
<div className="flex gap-4 items-center">
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-green" />= Correct
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-blue" />= Unanswered
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-orange" />= Wrong
</div>
</div>
</div> </div>
<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">

View File

@@ -7,12 +7,13 @@ import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import {Fragment} from "react"; import {Fragment} from "react";
import Button from "../Low/Button"; import Button from "../Low/Button";
import Xarrow from "react-xarrows";
export default function MatchSentencesSolutions({options, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) { export default function MatchSentencesSolutions({options, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
return ( return (
<> <>
<div className="flex flex-col items-center gap-8"> <div className="flex flex-col gap-4 mt-4 h-full mb-20">
<span className="text-base md:text-lg font-medium text-center px-2 md:px-4 lg:px-48"> <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}
@@ -20,48 +21,67 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
</Fragment> </Fragment>
))} ))}
</span> </span>
<div className="flex gap-8 w-full items-center justify-between bg-mti-gray-smoke rounded-xl px-24 py-6">
<div className="grid grid-cols-2 gap-16 place-items-center"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-1"> {sentences.map(({sentence, id, solution}) => (
{sentences.map(({sentence, id, color, solution}) => ( <div key={`question_${id}`} className="flex items-center justify-end gap-2 cursor-pointer">
<div <span>{sentence} </span>
key={`question_${id}`} <button
className={clsx( id={id}
"flex items-center justify-end gap-2 cursor-pointer", className={clsx(
userSolutions.find((x) => x.question === id)?.option === solution ? "text-green-500" : "text-red-500", "w-8 h-8 rounded-full z-10 text-white",
)}> "transition duration-300 ease-in-out",
<span> !userSolutions.find((x) => x.question === id) && "!bg-mti-blue",
<span className="font-semibold">{id}.</span> {sentence}{" "} userSolutions.find((x) => x.question === id)?.option === solution && "bg-mti-green",
</span> userSolutions.find((x) => x.question === id)?.option !== solution && "bg-mti-orange",
<div style={{borderColor: color}} className={clsx("border-2 border-blue-500 w-4 h-4 rounded-full", id)} /> )}>
{id}
</button>
</div> </div>
))} ))}
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-4">
{options.map(({sentence, id}) => ( {options.map(({sentence, id}) => (
<div key={`answer_${id}`} className={clsx("flex items-center justify-start gap-2 cursor-pointer")}> <div key={`answer_${id}`} className={clsx("flex items-center justify-start gap-2 cursor-pointer")}>
<div <button
style={ id={id}
sentences.find((x) => x.solution === id) className={clsx(
? { "bg-mti-green-ultralight text-mti-green hover:text-white hover:bg-mti-green w-8 h-8 rounded-full z-10",
border: `2px solid ${sentences.find((x) => x.solution === id)!.color}`, "transition duration-300 ease-in-out",
} )}>
: {} {id}
} </button>
className={clsx("border-2 border-green-500 bg-transparent w-4 h-4 rounded-full", id)} <span>{sentence}</span>
/>
<span>
<span className="font-semibold">{id}.</span> {sentence}{" "}
</span>
</div> </div>
))} ))}
</div> </div>
{sentences.map((sentence, index) => ( {sentences.map((sentence, index) => (
<div key={`solution_${index}`} className="absolute"> <Xarrow
<LineTo className="rounded-full" from={sentence.id} to={sentence.solution} borderColor={sentence.color} borderWidth={5} /> key={index}
</div> start={sentence.id}
end={sentence.solution}
lineColor={
!userSolutions.find((x) => x.question === sentence.id)
? "#0696ff"
: userSolutions.find((x) => x.question === sentence.id)?.option === sentence.solution
? "#307912"
: "#FF6000"
}
showHead={false}
/>
))} ))}
</div> </div>
<div className="flex gap-4 items-center">
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-green" />= Correct
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-blue" />= Unanswered
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-orange" />= Wrong
</div>
</div>
</div> </div>
<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">

View File

@@ -75,14 +75,27 @@ export default function MultipleChoice({prompt, questions, userSolutions, onNext
return ( return (
<> <>
<div className="flex flex-col gap-2 mt-4 h-full mb-20 bg-mti-gray-smoke rounded-xl px-16 py-8"> <div className="flex flex-col gap-4 w-full h-full mb-20">
<span className="text-xl font-semibold">{prompt}</span> <div className="flex flex-col gap-2 mt-4 h-full bg-mti-gray-smoke rounded-xl px-16 py-8">
{questionIndex < questions.length && ( <span className="text-xl font-semibold">{prompt}</span>
<Question {questionIndex < questions.length && (
{...questions[questionIndex]} <Question
userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option} {...questions[questionIndex]}
/> userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option}
)} />
)}
</div>
<div className="flex gap-4 items-center">
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-green" />= Correct
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-blue" />= Unanswered
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-orange" />= Wrong
</div>
</div>
</div> </div>
<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">

View File

@@ -110,6 +110,17 @@ export default function WriteBlanksSolutions({
</p> </p>
))} ))}
</span> </span>
<div className="flex gap-4 items-center">
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-green" />= Correct
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-blue" />= Unanswered
</div>
<div className="flex gap-2 items-center">
<div className="w-4 h-4 rounded-full bg-mti-orange" />= Wrong
</div>
</div>
</div> </div>
<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">

View File

@@ -4,6 +4,7 @@ import Icon from "@mdi/react";
import {mdiArrowRight, mdiNotebook} from "@mdi/js"; import {mdiArrowRight, mdiNotebook} from "@mdi/js";
import clsx from "clsx"; import clsx from "clsx";
import {infoButtonStyle} from "@/constants/buttonStyles"; import {infoButtonStyle} from "@/constants/buttonStyles";
import {convertCamelCaseToReadable} from "@/utils/string";
import {Dialog, Transition} from "@headlessui/react"; import {Dialog, Transition} from "@headlessui/react";
import {renderExercise} from "@/components/Exercises"; import {renderExercise} from "@/components/Exercises";
import {renderSolution} from "@/components/Solutions"; import {renderSolution} from "@/components/Solutions";
@@ -148,6 +149,7 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
module="reading" module="reading"
totalExercises={exam.exercises.length} totalExercises={exam.exercises.length}
disableTimer={showSolutions} disableTimer={showSolutions}
label={exerciseIndex === -1 ? undefined : convertCamelCaseToReadable(exam.exercises[exerciseIndex].type)}
/> />
{exerciseIndex === -1 && renderText()} {exerciseIndex === -1 && renderText()}
{exerciseIndex > -1 && {exerciseIndex > -1 &&

View File

@@ -873,7 +873,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
"@types/prop-types@*": "@types/prop-types@*", "@types/prop-types@^15.7.3":
version "15.7.5" version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
@@ -3178,6 +3178,15 @@ react-transition-group@^4.4.1:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react-xarrows@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-xarrows/-/react-xarrows-2.0.2.tgz#7555687612339eaefd4ed55fc5c63f2302726d9c"
integrity sha512-tDlAqaxHNmy0vegW/6NdhoWyXJq1LANX/WUAlHyzoHe9BwFVnJPPDghmDjYeVr7XWFmBrVTUrHsrW7GKYI6HtQ==
dependencies:
"@types/prop-types" "^15.7.3"
lodash "^4.17.21"
prop-types "^15.7.2"
react@17.0.2: react@17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"