Updated the Finish screen along with other tweaks
This commit is contained in:
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"arcanis.vscode-zipfs",
|
|
||||||
"dbaeumer.vscode-eslint"
|
"dbaeumer.vscode-eslint"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"axios": "^1.3.5",
|
"axios": "^1.3.5",
|
||||||
"chart.js": "^4.2.1",
|
"chart.js": "^4.2.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"daisyui": "^2.50.0",
|
"daisyui": "^3.1.5",
|
||||||
"eslint": "8.33.0",
|
"eslint": "8.33.0",
|
||||||
"eslint-config-next": "13.1.6",
|
"eslint-config-next": "13.1.6",
|
||||||
"firebase": "9.19.1",
|
"firebase": "9.19.1",
|
||||||
|
|||||||
@@ -88,8 +88,9 @@ export default function FillBlanks({
|
|||||||
const calculateScore = () => {
|
const calculateScore = () => {
|
||||||
const total = text.match(/({{\d+}})/g)?.length || 0;
|
const total = text.match(/({{\d+}})/g)?.length || 0;
|
||||||
const correct = answers.filter((x) => solutions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
|
const correct = answers.filter((x) => solutions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
|
||||||
|
const missing = total - answers.filter((x) => solutions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
return {total, correct};
|
return {total, correct, missing};
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderLines = (line: string) => {
|
const renderLines = (line: string) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import clsx from "clsx";
|
|||||||
import {Fragment, useState} from "react";
|
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";
|
||||||
|
|
||||||
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>();
|
||||||
@@ -14,8 +15,9 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
|||||||
const calculateScore = () => {
|
const calculateScore = () => {
|
||||||
const total = sentences.length;
|
const total = sentences.length;
|
||||||
const correct = answers.filter((x) => sentences.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
const correct = answers.filter((x) => sentences.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
||||||
|
const missing = total - answers.filter((x) => sentences.find((y) => y.id === x.question)).length;
|
||||||
|
|
||||||
return {total, correct};
|
return {total, correct, missing};
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectOption = (option: string) => {
|
const selectOption = (option: string) => {
|
||||||
@@ -92,23 +94,21 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<button
|
<Button
|
||||||
className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}
|
color="green"
|
||||||
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}>
|
variant="outline"
|
||||||
<div className="absolute left-4">
|
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
className="max-w-[200px] w-full">
|
||||||
</div>
|
|
||||||
Back
|
Back
|
||||||
</button>
|
</Button>
|
||||||
<button
|
|
||||||
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)}
|
<Button
|
||||||
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}>
|
color="green"
|
||||||
|
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
|
||||||
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
<div className="absolute right-4">
|
</Button>
|
||||||
<Icon path={mdiArrowRight} color="white" size={1} />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ export default function MultipleChoice({id, prompt, type, questions, userSolutio
|
|||||||
const calculateScore = () => {
|
const calculateScore = () => {
|
||||||
const total = questions.length;
|
const total = questions.length;
|
||||||
const correct = answers.filter((x) => questions.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
const correct = answers.filter((x) => questions.find((y) => y.id === x.question)?.solution === x.option || false).length;
|
||||||
|
const missing = total - answers.filter((x) => questions.find((y) => y.id === x.question)).length;
|
||||||
|
|
||||||
return {total, correct};
|
return {total, correct, missing};
|
||||||
};
|
};
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
|
|||||||
@@ -180,14 +180,14 @@ export default function Speaking({id, title, text, type, prompts, onNext, onBack
|
|||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onBack({exercise: id, solutions: [], score: {correct: 1, total: 1}, type})}
|
onClick={() => onBack({exercise: id, solutions: [], score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
className="max-w-[200px] self-end w-full">
|
className="max-w-[200px] self-end w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
disabled={!mediaBlob}
|
disabled={!mediaBlob}
|
||||||
onClick={() => onNext({exercise: id, solutions: [], score: {correct: 1, total: 1}, type})}
|
onClick={() => onNext({exercise: id, solutions: [], score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
className="max-w-[200px] self-end w-full">
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ export default function WriteBlanks({id, prompt, type, maxWords, solutions, user
|
|||||||
?.solution.map((y) => y.toLowerCase())
|
?.solution.map((y) => y.toLowerCase())
|
||||||
.includes(x.solution.toLowerCase()) || false,
|
.includes(x.solution.toLowerCase()) || false,
|
||||||
).length;
|
).length;
|
||||||
|
const missing = total - answers.filter((x) => solutions.find((y) => x.id === y.id)).length;
|
||||||
|
|
||||||
return {total, correct};
|
return {total, correct, missing};
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderLines = (line: string) => {
|
const renderLines = (line: string) => {
|
||||||
|
|||||||
@@ -58,14 +58,14 @@ export default function Writing({id, prompt, info, type, wordCounter, attachment
|
|||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => onBack({exercise: id, solutions: [inputText], score: {correct: 1, total: 1}, type})}
|
onClick={() => onBack({exercise: id, solutions: [inputText], score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
className="max-w-[200px] self-end w-full">
|
className="max-w-[200px] self-end w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="green"
|
color="green"
|
||||||
disabled={!isSubmitEnabled}
|
disabled={!isSubmitEnabled}
|
||||||
onClick={() => onNext({exercise: id, solutions: [inputText], score: {correct: 1, total: 1}, type})}
|
onClick={() => onNext({exercise: id, solutions: [inputText], score: {correct: 1, total: 1, missing: 0}, type})}
|
||||||
className="max-w-[200px] self-end w-full">
|
className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -9,18 +9,21 @@ interface Props {
|
|||||||
module: Module;
|
module: Module;
|
||||||
exerciseIndex: number;
|
exerciseIndex: number;
|
||||||
totalExercises: number;
|
totalExercises: number;
|
||||||
|
disableTimer?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModuleTitle({minTimer, module, exerciseIndex, totalExercises}: Props) {
|
export default function ModuleTitle({minTimer, module, exerciseIndex, totalExercises, disableTimer = false}: Props) {
|
||||||
const [timer, setTimer] = useState(minTimer * 60);
|
const [timer, setTimer] = useState(minTimer * 60);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000);
|
if (!disableTimer) {
|
||||||
|
const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
};
|
};
|
||||||
}, [minTimer]);
|
}
|
||||||
|
}, [disableTimer, minTimer]);
|
||||||
|
|
||||||
const moduleIcon: {[key in Module]: ReactNode} = {
|
const moduleIcon: {[key in Module]: ReactNode} = {
|
||||||
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
||||||
@@ -57,7 +60,7 @@ export default function ModuleTitle({minTimer, module, exerciseIndex, totalExerc
|
|||||||
Question {exerciseIndex}/{totalExercises}
|
Question {exerciseIndex}/{totalExercises}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar color={module} label="" percentage={((exerciseIndex - 1) * 100) / totalExercises} className="h-2 w-full" />
|
<ProgressBar color={module} label="" percentage={(exerciseIndex * 100) / totalExercises} className="h-2 w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
|||||||
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
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";
|
||||||
|
|
||||||
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 (
|
||||||
@@ -63,19 +64,14 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
|
<Button color="green" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
||||||
<div className="absolute left-4">
|
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
|
||||||
</div>
|
|
||||||
Back
|
Back
|
||||||
</button>
|
</Button>
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onNext}>
|
|
||||||
|
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
<div className="absolute right-4">
|
</Button>
|
||||||
<Icon path={mdiArrowRight} color="white" size={1} />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,92 +1,199 @@
|
|||||||
import ProfileLevel from "@/components/ProfileLevel";
|
import Button from "@/components/Low/Button";
|
||||||
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
import ModuleTitle from "@/components/Medium/ModuleTitle";
|
||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import {User} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
import {ICONS} from "@/resources/modules";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
import {calculateBandScore} from "@/utils/score";
|
||||||
import Icon from "@mdi/react";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import router from "next/router";
|
import {useRouter} from "next/router";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {BsArrowCounterclockwise, BsBook, BsEyeFill, BsHeadphones, BsMegaphone, BsPen, BsShareFill} from "react-icons/bs";
|
||||||
|
|
||||||
|
interface Score {
|
||||||
|
module: Module;
|
||||||
|
correct: number;
|
||||||
|
total: number;
|
||||||
|
missing: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
modules: Module[];
|
modules: Module[];
|
||||||
scores: {
|
scores: Score[];
|
||||||
module?: Module;
|
|
||||||
correct: number;
|
|
||||||
total: number;
|
|
||||||
}[];
|
|
||||||
onViewResults: () => void;
|
onViewResults: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Finish({user, scores, modules, onViewResults}: Props) {
|
export default function Finish({user, scores, modules, onViewResults}: Props) {
|
||||||
const renderModuleScore = (module: Module) => {
|
const [selectedModule, setSelectedModule] = useState(modules[0]);
|
||||||
const moduleScores = scores.filter((x) => x.module === module);
|
const [selectedScore, setSelectedScore] = useState<Score>(scores.find((x) => x.module === modules[0])!);
|
||||||
if (moduleScores.length === 0) {
|
|
||||||
return <>0</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return moduleScores.map((x, index) => (
|
const exams = useExamStore((state) => state.exams);
|
||||||
<span key={index}>
|
|
||||||
{x.correct} / {x.total}
|
|
||||||
</span>
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderModuleTotal = (module: Module) => {
|
useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]);
|
||||||
const moduleScores = scores.filter((x) => x.module === module);
|
|
||||||
if (moduleScores.length === 0) {
|
|
||||||
return <>0</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = moduleScores.reduce((accumulator, current) => accumulator + current.total, 0);
|
const moduleColors: {[key in Module]: {progress: string; inner: string}} = {
|
||||||
const correct = moduleScores.reduce((accumulator, current) => accumulator + current.correct, 0);
|
reading: {
|
||||||
|
progress: "text-ielts-reading",
|
||||||
return (
|
inner: "bg-ielts-reading-light",
|
||||||
<span>
|
},
|
||||||
{correct} / {total} | {Math.floor((correct / total) * 100)}%
|
listening: {
|
||||||
</span>
|
progress: "text-ielts-listening",
|
||||||
);
|
inner: "bg-ielts-listening-light",
|
||||||
|
},
|
||||||
|
writing: {
|
||||||
|
progress: "text-ielts-writing",
|
||||||
|
inner: "bg-ielts-writing-light",
|
||||||
|
},
|
||||||
|
speaking: {
|
||||||
|
progress: "text-ielts-speaking",
|
||||||
|
inner: "bg-ielts-speaking-light",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full relative">
|
<>
|
||||||
<section className="h-full w-full flex flex-col items-center justify-center gap-4">
|
<div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8">
|
||||||
<ProfileLevel user={user} className="h-1/2" />
|
<ModuleTitle
|
||||||
<div className="h-2/3 w-2/3 flex flex-col items-center gap-4">
|
module={selectedModule}
|
||||||
<div className="rounded-xl p-2 md:p-4 items-center flex lg:justify-center gap-2 md:gap-4 max-w-[100%] overflow-auto">
|
totalExercises={exams.find((x) => x.module === selectedModule)!.exercises.length}
|
||||||
{modules.map((module) => (
|
exerciseIndex={exams.find((x) => x.module === selectedModule)!.exercises.length}
|
||||||
|
minTimer={exams.find((x) => x.module === selectedModule)!.minTimer}
|
||||||
|
disableTimer
|
||||||
|
/>
|
||||||
|
<div className="flex gap-4 self-start">
|
||||||
|
{modules.includes("reading") && (
|
||||||
|
<div
|
||||||
|
onClick={() => setSelectedModule("reading")}
|
||||||
|
className={clsx(
|
||||||
|
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-reading hover:text-white",
|
||||||
|
selectedModule === "reading" ? "bg-ielts-reading text-white" : "bg-mti-gray-smoke text-ielts-reading",
|
||||||
|
)}>
|
||||||
|
<BsBook className="w-6 h-6" />
|
||||||
|
<span className="font-semibold">Reading</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{modules.includes("listening") && (
|
||||||
|
<div
|
||||||
|
onClick={() => setSelectedModule("listening")}
|
||||||
|
className={clsx(
|
||||||
|
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-listening hover:text-white",
|
||||||
|
selectedModule === "listening" ? "bg-ielts-listening text-white" : "bg-mti-gray-smoke text-ielts-listening",
|
||||||
|
)}>
|
||||||
|
<BsHeadphones className="w-6 h-6" />
|
||||||
|
<span className="font-semibold">Listening</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{modules.includes("writing") && (
|
||||||
|
<div
|
||||||
|
onClick={() => setSelectedModule("writing")}
|
||||||
|
className={clsx(
|
||||||
|
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-writing hover:text-white",
|
||||||
|
selectedModule === "writing" ? "bg-ielts-writing text-white" : "bg-mti-gray-smoke text-ielts-writing",
|
||||||
|
)}>
|
||||||
|
<BsPen className="w-6 h-6" />
|
||||||
|
<span className="font-semibold">Writing</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{modules.includes("speaking") && (
|
||||||
|
<div
|
||||||
|
onClick={() => setSelectedModule("speaking")}
|
||||||
|
className={clsx(
|
||||||
|
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-speaking hover:text-white",
|
||||||
|
selectedModule === "speaking" ? "bg-ielts-speaking text-white" : "bg-mti-gray-smoke text-ielts-speaking",
|
||||||
|
)}>
|
||||||
|
<BsMegaphone className="w-6 h-6" />
|
||||||
|
<span className="font-semibold">Speaking</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex gap-9 mt-32 items-center justify-between">
|
||||||
|
<span className="max-w-3xl">
|
||||||
|
Congratulations on your exam performance! You achieved an impressive{" "}
|
||||||
|
<span className="font-bold">
|
||||||
|
level {calculateBandScore(selectedScore.correct, selectedScore.total, selectedModule, user.focus)}
|
||||||
|
</span>
|
||||||
|
, demonstrating excellent mastery of the assessed knowledge.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
If you disagree with the result, you can request a review by a qualified teacher. We are committed to the accuracy and
|
||||||
|
transparency of the results. Please contact us for further information.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Congratulations again on your outstanding achievement! We are here to support you on your academic journey.
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-9 px-16">
|
||||||
|
<div
|
||||||
|
className={clsx("radial-progress overflow-hidden", moduleColors[selectedModule].progress)}
|
||||||
|
style={{"--value": (selectedScore.correct / selectedScore.total) * 100, "--thickness": "12px", "--size": "13rem"} as any}>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col gap-12 min-w-[176px] items-center justify-center border-2 border-ielts-${module} bg-ielts-${module}-transparent py-4 px-2 rounded-xl text-white font-semibold`}
|
className={clsx(
|
||||||
key={module}>
|
"w-48 h-48 rounded-full flex flex-col items-center justify-center",
|
||||||
<div className="flex flex-col items-center">
|
moduleColors[selectedModule].inner,
|
||||||
<Icon path={ICONS[module]} color="white" size={2} />
|
)}>
|
||||||
<span>{module.toUpperCase()}</span>
|
<span className="text-xl">Level</span>
|
||||||
</div>
|
<span className="text-3xl font-bold">
|
||||||
<div className="flex flex-col">{renderModuleScore(module)}</div>
|
{calculateBandScore(selectedScore.correct, selectedScore.total, selectedModule, user.focus)}
|
||||||
<div>TOTAL: {renderModuleTotal(module)}</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
<div className="flex flex-col gap-5">
|
||||||
<div className="w-full flex flex-col gap-4 lg:gap-0 md:flex-row justify-center items-center lg:justify-between">
|
<div className="flex gap-2">
|
||||||
<Link href="/">
|
<div className="w-3 h-3 bg-mti-blue-light rounded-full mt-1" />
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
|
<div className="flex flex-col">
|
||||||
<div className="absolute left-4">
|
<span className="text-mti-blue-light">
|
||||||
<Icon path={mdiArrowLeft} color="white" size={1} />
|
{(((selectedScore.total - selectedScore.missing) / selectedScore.total) * 100).toFixed(0)}%
|
||||||
|
</span>
|
||||||
|
<span className="text-lg">Completion</span>
|
||||||
</div>
|
</div>
|
||||||
Go Home
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onViewResults}>
|
|
||||||
View Solutions
|
|
||||||
<div className="absolute right-4">
|
|
||||||
<Icon path={mdiArrowRight} color="white" size={1} />
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
<div className="flex gap-2">
|
||||||
|
<div className="w-3 h-3 bg-mti-green-light rounded-full mt-1" />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-mti-green-light">{selectedScore.total.toString().padStart(2, "0")}</span>
|
||||||
|
<span className="text-lg">Correct</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="w-3 h-3 bg-mti-orange-light rounded-full mt-1" />
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-mti-orange-light">
|
||||||
|
{(selectedScore.total - selectedScore.correct).toString().padStart(2, "0")}
|
||||||
|
</span>
|
||||||
|
<span className="text-lg">Wrong</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
|
<div className="flex gap-8">
|
||||||
|
<div className="w-fit flex flex-col items-center gap-1 cursor-pointer">
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="w-11 h-11 rounded-full bg-mti-green-light hover:bg-mti-green flex items-center justify-center transition duration-300 ease-in-out">
|
||||||
|
<BsArrowCounterclockwise className="text-white w-7 h-7" />
|
||||||
|
</button>
|
||||||
|
<span>Play Again</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-fit flex flex-col items-center gap-1 cursor-pointer">
|
||||||
|
<button
|
||||||
|
onClick={onViewResults}
|
||||||
|
className="w-11 h-11 rounded-full bg-mti-green-light hover:bg-mti-green flex items-center justify-center transition duration-300 ease-in-out">
|
||||||
|
<BsEyeFill className="text-white w-7 h-7" />
|
||||||
|
</button>
|
||||||
|
<span>Review Answers</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link href="/" className="max-w-[200px] w-full self-end">
|
||||||
|
<Button color="green" className="max-w-[200px] self-end w-full">
|
||||||
|
Dashboard
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,13 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col h-full w-full gap-8 justify-between">
|
<div className="flex flex-col h-full w-full gap-8 justify-between">
|
||||||
<ModuleTitle exerciseIndex={exerciseIndex + 1} minTimer={exam.minTimer} module="listening" totalExercises={exam.exercises.length} />
|
<ModuleTitle
|
||||||
|
exerciseIndex={exerciseIndex + 1}
|
||||||
|
minTimer={exam.minTimer}
|
||||||
|
module="listening"
|
||||||
|
totalExercises={exam.exercises.length}
|
||||||
|
disableTimer={showSolutions}
|
||||||
|
/>
|
||||||
{exerciseIndex === -1 && renderAudioPlayer()}
|
{exerciseIndex === -1 && renderAudioPlayer()}
|
||||||
{exerciseIndex > -1 &&
|
{exerciseIndex > -1 &&
|
||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
|
|||||||
@@ -142,7 +142,13 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col h-full w-full gap-8">
|
<div className="flex flex-col h-full w-full gap-8">
|
||||||
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
|
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
|
||||||
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="reading" totalExercises={exam.exercises.length} />
|
<ModuleTitle
|
||||||
|
minTimer={exam.minTimer}
|
||||||
|
exerciseIndex={exerciseIndex + 1}
|
||||||
|
module="reading"
|
||||||
|
totalExercises={exam.exercises.length}
|
||||||
|
disableTimer={showSolutions}
|
||||||
|
/>
|
||||||
{exerciseIndex === -1 && renderText()}
|
{exerciseIndex === -1 && renderText()}
|
||||||
{exerciseIndex > -1 &&
|
{exerciseIndex > -1 &&
|
||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
|
|||||||
@@ -59,7 +59,13 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props)
|
|||||||
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">
|
||||||
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="speaking" totalExercises={exam.exercises.length} />
|
<ModuleTitle
|
||||||
|
minTimer={exam.minTimer}
|
||||||
|
exerciseIndex={exerciseIndex + 1}
|
||||||
|
module="speaking"
|
||||||
|
totalExercises={exam.exercises.length}
|
||||||
|
disableTimer={showSolutions}
|
||||||
|
/>
|
||||||
{exerciseIndex > -1 &&
|
{exerciseIndex > -1 &&
|
||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
!showSolutions &&
|
!showSolutions &&
|
||||||
|
|||||||
@@ -59,7 +59,13 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
|
|||||||
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">
|
||||||
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="writing" totalExercises={exam.exercises.length} />
|
<ModuleTitle
|
||||||
|
minTimer={exam.minTimer}
|
||||||
|
exerciseIndex={exerciseIndex + 1}
|
||||||
|
module="writing"
|
||||||
|
totalExercises={exam.exercises.length}
|
||||||
|
disableTimer={showSolutions}
|
||||||
|
/>
|
||||||
{exerciseIndex > -1 &&
|
{exerciseIndex > -1 &&
|
||||||
exerciseIndex < exam.exercises.length &&
|
exerciseIndex < exam.exercises.length &&
|
||||||
!showSolutions &&
|
!showSolutions &&
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface UserSolution {
|
|||||||
score: {
|
score: {
|
||||||
correct: number;
|
correct: number;
|
||||||
total: number;
|
total: number;
|
||||||
|
missing: number;
|
||||||
};
|
};
|
||||||
exercise: string;
|
exercise: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,43 @@ export default function Page() {
|
|||||||
setModuleIndex((prev) => prev + 1);
|
setModuleIndex((prev) => prev + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const aggregateScoresByModule = (answers: UserSolution[]): {module: Module; total: number; missing: number; correct: number}[] => {
|
||||||
|
const scores: {[key in Module]: {total: number; missing: number; correct: number}} = {
|
||||||
|
reading: {
|
||||||
|
total: 0,
|
||||||
|
correct: 0,
|
||||||
|
missing: 0,
|
||||||
|
},
|
||||||
|
listening: {
|
||||||
|
total: 0,
|
||||||
|
correct: 0,
|
||||||
|
missing: 0,
|
||||||
|
},
|
||||||
|
writing: {
|
||||||
|
total: 0,
|
||||||
|
correct: 0,
|
||||||
|
missing: 0,
|
||||||
|
},
|
||||||
|
speaking: {
|
||||||
|
total: 0,
|
||||||
|
correct: 0,
|
||||||
|
missing: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
answers.forEach((x) => {
|
||||||
|
scores[x.module!] = {
|
||||||
|
total: scores[x.module!].total + x.score.total,
|
||||||
|
correct: scores[x.module!].correct + x.score.correct,
|
||||||
|
missing: scores[x.module!].missing + x.score.missing,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(scores)
|
||||||
|
.filter((x) => scores[x as Module].total > 0)
|
||||||
|
.map((x) => ({module: x as Module, ...scores[x as Module]}));
|
||||||
|
};
|
||||||
|
|
||||||
const renderScreen = () => {
|
const renderScreen = () => {
|
||||||
if (selectedModules.length === 0) {
|
if (selectedModules.length === 0) {
|
||||||
return <Selection user={user!} onStart={setSelectedModules} />;
|
return <Selection user={user!} onStart={setSelectedModules} />;
|
||||||
@@ -163,7 +200,7 @@ export default function Page() {
|
|||||||
setModuleIndex(0);
|
setModuleIndex(0);
|
||||||
setExam(exams[0]);
|
setExam(exams[0]);
|
||||||
}}
|
}}
|
||||||
scores={userSolutions.map((x) => ({...x.score, module: x.module}))}
|
scores={aggregateScoresByModule(userSolutions)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const academicMarking: {[key: number]: number} = {
|
|||||||
10: 2.5,
|
10: 2.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
const moduleMarkings: {[key in "reading" | "listening"]: {[key in Type]: {[key: number]: number}}} = {
|
const moduleMarkings: {[key in Module]: {[key in Type]: {[key: number]: number}}} = {
|
||||||
reading: {
|
reading: {
|
||||||
academic: academicMarking,
|
academic: academicMarking,
|
||||||
general: readingGeneralMarking,
|
general: readingGeneralMarking,
|
||||||
@@ -45,9 +45,17 @@ const moduleMarkings: {[key in "reading" | "listening"]: {[key in Type]: {[key:
|
|||||||
academic: academicMarking,
|
academic: academicMarking,
|
||||||
general: academicMarking,
|
general: academicMarking,
|
||||||
},
|
},
|
||||||
|
writing: {
|
||||||
|
academic: academicMarking,
|
||||||
|
general: readingGeneralMarking,
|
||||||
|
},
|
||||||
|
speaking: {
|
||||||
|
academic: academicMarking,
|
||||||
|
general: academicMarking,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calculateBandScore = (correct: number, total: number, module: "reading" | "listening", type: Type) => {
|
export const calculateBandScore = (correct: number, total: number, module: Module, type: Type) => {
|
||||||
const marking = moduleMarkings[module][type];
|
const marking = moduleMarkings[module][type];
|
||||||
const percentage = (correct * 100) / total;
|
const percentage = (correct * 100) / total;
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ module.exports = {
|
|||||||
black: "#353338",
|
black: "#353338",
|
||||||
},
|
},
|
||||||
ielts: {
|
ielts: {
|
||||||
reading: {DEFAULT: "#1EB3FF", transparent: "rgba(255, 99, 132, 0.5)"},
|
reading: {DEFAULT: "#1EB3FF", light: "#F0F9FF", transparent: "rgba(255, 99, 132, 0.5)"},
|
||||||
listening: {DEFAULT: "#FF790A", transparent: "rgba(54, 162, 235, 0.5)"},
|
listening: {DEFAULT: "#FF790A", light: "#FFF1E5", transparent: "rgba(54, 162, 235, 0.5)"},
|
||||||
writing: {DEFAULT: "#3D9F11", transparent: "rgba(255, 206, 86, 0.5)"},
|
writing: {DEFAULT: "#3D9F11", light: "#E8FCDF", transparent: "rgba(255, 206, 86, 0.5)"},
|
||||||
speaking: {DEFAULT: "#EF5DA8", transparent: "rgba(75, 192, 192, 0.5)"},
|
speaking: {DEFAULT: "#EF5DA8", light: "#FEF6FA", transparent: "rgba(75, 192, 192, 0.5)"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
51
yarn.lock
51
yarn.lock
@@ -1338,26 +1338,15 @@ color-convert@^2.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
color-name "~1.1.4"
|
color-name "~1.1.4"
|
||||||
|
|
||||||
color-name@^1.0.0, color-name@~1.1.4:
|
color-name@~1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
color-string@^1.9.0:
|
colord@^2.9:
|
||||||
version "1.9.1"
|
version "2.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
|
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
|
||||||
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
|
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
||||||
dependencies:
|
|
||||||
color-name "^1.0.0"
|
|
||||||
simple-swizzle "^0.2.2"
|
|
||||||
|
|
||||||
color@^4.2:
|
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
|
|
||||||
integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
|
|
||||||
dependencies:
|
|
||||||
color-convert "^2.0.1"
|
|
||||||
color-string "^1.9.0"
|
|
||||||
|
|
||||||
combined-stream@^1.0.8:
|
combined-stream@^1.0.8:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
@@ -1390,7 +1379,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
css-selector-tokenizer@^0.8.0:
|
css-selector-tokenizer@^0.8:
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd"
|
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd"
|
||||||
integrity sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==
|
integrity sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==
|
||||||
@@ -1408,14 +1397,14 @@ csstype@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
|
||||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||||
|
|
||||||
daisyui@^2.50.0:
|
daisyui@^3.1.5:
|
||||||
version "2.52.0"
|
version "3.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-2.52.0.tgz#1e22abd655bf4a5cb466e1a1f264bb597e9ec254"
|
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.1.5.tgz#49cb961d03504beebd238d58f0169dbe81103c95"
|
||||||
integrity sha512-LQTA5/IVXAJHBMFoeaEMfd7/akAFPPcdQPR3O9fzzcFiczneJFM73CFPnScmW2sOgn/D83cvkP854ep2T9OfTg==
|
integrity sha512-3nk5L//IU31ISzsonKEFG8ris8hS/MRAc+PrOTOyRbssjv1fYmNKSdIrOhG6op2ED3RyJL4p6I81JUwd7RWqTw==
|
||||||
dependencies:
|
dependencies:
|
||||||
color "^4.2"
|
colord "^2.9"
|
||||||
css-selector-tokenizer "^0.8.0"
|
css-selector-tokenizer "^0.8"
|
||||||
postcss-js "^4.0.0"
|
postcss-js "^4"
|
||||||
tailwindcss "^3"
|
tailwindcss "^3"
|
||||||
|
|
||||||
damerau-levenshtein@^1.0.8:
|
damerau-levenshtein@^1.0.8:
|
||||||
@@ -2323,11 +2312,6 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
|
|||||||
get-intrinsic "^1.2.0"
|
get-intrinsic "^1.2.0"
|
||||||
is-typed-array "^1.1.10"
|
is-typed-array "^1.1.10"
|
||||||
|
|
||||||
is-arrayish@^0.3.1:
|
|
||||||
version "0.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
|
|
||||||
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
|
|
||||||
|
|
||||||
is-bigint@^1.0.1:
|
is-bigint@^1.0.1:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
|
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
|
||||||
@@ -2962,7 +2946,7 @@ postcss-import@^15.1.0:
|
|||||||
read-cache "^1.0.0"
|
read-cache "^1.0.0"
|
||||||
resolve "^1.1.7"
|
resolve "^1.1.7"
|
||||||
|
|
||||||
postcss-js@^4.0.0, postcss-js@^4.0.1:
|
postcss-js@^4, postcss-js@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
|
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
|
||||||
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
|
||||||
@@ -3360,13 +3344,6 @@ signal-exit@^3.0.3, signal-exit@^3.0.7:
|
|||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||||
|
|
||||||
simple-swizzle@^0.2.2:
|
|
||||||
version "0.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
|
||||||
integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
|
|
||||||
dependencies:
|
|
||||||
is-arrayish "^0.3.1"
|
|
||||||
|
|
||||||
slash@^3.0.0:
|
slash@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
|
|||||||
Reference in New Issue
Block a user