From bc7eaea911fcb2d111c13b395d2955f045d4de2d Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 15 Jun 2023 15:39:40 +0100 Subject: [PATCH] Implemented the speaking exercise; Cleaned up a bit of the code; --- src/components/Exercises/Speaking.tsx | 190 ++++++++++++++++++++++---- src/exams/Selection.tsx | 28 ++-- src/exams/Speaking.tsx | 36 +++-- src/pages/index.tsx | 16 ++- src/utils/score.ts | 6 + 5 files changed, 208 insertions(+), 68 deletions(-) diff --git a/src/components/Exercises/Speaking.tsx b/src/components/Exercises/Speaking.tsx index 29b77a38..82d93d65 100644 --- a/src/components/Exercises/Speaking.tsx +++ b/src/components/Exercises/Speaking.tsx @@ -6,23 +6,50 @@ import clsx from "clsx"; import {CommonProps} from "."; import {Fragment, useEffect, useState} from "react"; import {toast} from "react-toastify"; +import {BsCheckCircleFill, BsMicFill, BsPauseCircle, BsPlayCircle, BsTrashFill} from "react-icons/bs"; +import dynamic from "next/dynamic"; +import Button from "../Low/Button"; + +const Waveform = dynamic(() => import("../Waveform"), {ssr: false}); +const ReactMediaRecorder = dynamic(() => import("react-media-recorder").then((mod) => mod.ReactMediaRecorder), { + ssr: false, +}); export default function Speaking({id, title, text, type, prompts, onNext, onBack}: SpeakingExercise & CommonProps) { + const [recordingDuration, setRecordingDuration] = useState(0); + const [isRecording, setIsRecording] = useState(false); + const [mediaBlob, setMediaBlob] = useState(); + + useEffect(() => { + let recordingInterval: NodeJS.Timer | undefined = undefined; + if (isRecording) { + recordingInterval = setInterval(() => setRecordingDuration((prev) => prev + 1), 1000); + } else if (recordingInterval) { + clearInterval(recordingInterval); + } + + return () => { + if (recordingInterval) clearInterval(recordingInterval); + }; + }, [isRecording]); + return ( -
-
- {title} - - {text.split("\\n").map((line, index) => ( - - {line} -
-
- ))} -
-
- You should talk about the following things: -
+
+
+
+ {title} + + {text.split("\\n").map((line, index) => ( + + {line} +
+
+ ))} +
+
+
+ You should talk about the following things: +
{prompts.map((x, index) => (
  • {x} @@ -32,21 +59,132 @@ export default function Speaking({id, title, text, type, prompts, onNext, onBack
  • -
    - - + +
    ); diff --git a/src/exams/Selection.tsx b/src/exams/Selection.tsx index a97e0dca..79d793f7 100644 --- a/src/exams/Selection.tsx +++ b/src/exams/Selection.tsx @@ -1,19 +1,14 @@ /* eslint-disable @next/next/no-img-element */ -import Icon from "@mdi/react"; -import {mdiAccountVoice, mdiArrowLeft, mdiArrowRight, mdiBookOpen, mdiHeadphones, mdiPen} from "@mdi/js"; -import {useEffect, useState} from "react"; +import {useState} from "react"; import {Module} from "@/interfaces"; import clsx from "clsx"; -import {useRouter} from "next/router"; -import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles"; -import ProfileLevel from "@/components/ProfileLevel"; import {User} from "@/interfaces/user"; -import useExamStore from "@/stores/examStore"; import ProgressBar from "@/components/Low/ProgressBar"; -import {BsBook, BsCheckCircle, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} from "react-icons/bs"; -import {averageScore, formatModuleTotalStats, totalExams, totalExamsByModule} from "@/utils/stats"; +import {BsBook, BsCheckCircle, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; +import {totalExamsByModule} from "@/utils/stats"; import useStats from "@/hooks/useStats"; import Button from "@/components/Low/Button"; +import {calculateAverageLevel} from "@/utils/score"; interface Props { user: User; @@ -24,10 +19,6 @@ export default function Selection({user, onStart}: Props) { const [selectedModules, setSelectedModules] = useState([]); const {stats} = useStats(user?.id); - const calculateAverageLevel = () => { - return Object.keys(user!.levels).reduce((accumulator, current) => user!.levels[current as Module] + accumulator, 0) / 4; - }; - const toggleModule = (module: Module) => { const modules = selectedModules.filter((x) => x !== module); setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module])); @@ -45,13 +36,18 @@ export default function Selection({user, onStart}: Props) {
    {user.type}
    - +
    diff --git a/src/exams/Speaking.tsx b/src/exams/Speaking.tsx index ef0c4060..25a70aac 100644 --- a/src/exams/Speaking.tsx +++ b/src/exams/Speaking.tsx @@ -1,4 +1,5 @@ import {renderExercise} from "@/components/Exercises"; +import ModuleTitle from "@/components/Medium/ModuleTitle"; import {renderSolution} from "@/components/Solutions"; import {infoButtonStyle} from "@/constants/buttonStyles"; import {UserSolution, SpeakingExam} from "@/interfaces/exam"; @@ -38,27 +39,24 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props) }; const previousExercise = () => { - setExerciseIndex((prev) => prev - 1); + if (exerciseIndex > 0) { + setExerciseIndex((prev) => prev - 1); + } }; return ( -
    - {exerciseIndex > -1 && - exerciseIndex < exam.exercises.length && - !showSolutions && - renderExercise(exam.exercises[exerciseIndex], nextExercise, previousExercise)} - {exerciseIndex > -1 && - exerciseIndex < exam.exercises.length && - showSolutions && - renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)} - {exerciseIndex === -1 && ( - - )} -
    + <> +
    + + {exerciseIndex > -1 && + exerciseIndex < exam.exercises.length && + !showSolutions && + renderExercise(exam.exercises[exerciseIndex], nextExercise, previousExercise)} + {exerciseIndex > -1 && + exerciseIndex < exam.exercises.length && + showSolutions && + renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)} +
    + ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0a8644ac..f97e5dba 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,6 +15,7 @@ import {capitalize} from "lodash"; import {Module} from "@/interfaces"; import ProgressBar from "@/components/Low/ProgressBar"; import Layout from "@/components/High/Layout"; +import {calculateAverageLevel} from "@/utils/score"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -44,10 +45,6 @@ export default function Home() { if (user) setShowDiagnostics(user.isFirstLogin); }, [user]); - const calculateAverageLevel = () => { - return Object.keys(user!.levels).reduce((accumulator, current) => user!.levels[current as Module] + accumulator, 0) / 4; - }; - if (user && showDiagnostics) { return ( <> @@ -90,13 +87,18 @@ export default function Home() {
    {capitalize(user.type)}
    - +
    diff --git a/src/utils/score.ts b/src/utils/score.ts index 66c1e07d..c7fd92e3 100644 --- a/src/utils/score.ts +++ b/src/utils/score.ts @@ -1,3 +1,5 @@ +import {Module} from "@/interfaces"; + type Type = "academic" | "general"; const readingGeneralMarking: {[key: number]: number} = { @@ -59,3 +61,7 @@ export const calculateBandScore = (correct: number, total: number, module: "read return 0; }; + +export const calculateAverageLevel = (levels: {[key in Module]: number}) => { + return Object.keys(levels).reduce((accumulator, current) => levels[current as Module] + accumulator, 0) / 4; +};