diff --git a/components/play-button.tsx b/components/play-button.tsx index 40c7b08..d8621fe 100644 --- a/components/play-button.tsx +++ b/components/play-button.tsx @@ -1,9 +1,7 @@ import { Button } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; -import { useEffect } from "react"; let audioContext: AudioContext; -// let audioQueue: string[] = []; let currentlyPlaying = false; const playAudioBuffer = (buffer: AudioBuffer): Promise => { @@ -22,11 +20,11 @@ const playAudioBuffer = (buffer: AudioBuffer): Promise => { export const playAudio = (urlOrDataURI: string): Promise => { return new Promise((resolve, reject) => { if (!urlOrDataURI) { - resolve(); + return resolve(); } if (currentlyPlaying) { - return; + return resolve(); } currentlyPlaying = true; @@ -61,20 +59,13 @@ export const playAudio = (urlOrDataURI: string): Promise => { /** A React component */ export function PlayButton({ dataURI }: { dataURI: string }) { - const playSound = () => { + const playSound = async () => { if (dataURI) { - playAudio(dataURI); + await playAudio(dataURI); } }; useHotkeys([["c", playSound]]); - // Use the useEffect hook to listen for changes to dataURI - useEffect(() => { - if (dataURI) { - playAudio(dataURI); - } - }, [dataURI]); // Dependency array includes dataURI - if (!dataURI) { return ( <> diff --git a/components/record-button.tsx b/components/record-button.tsx index f20b1be..622f966 100644 --- a/components/record-button.tsx +++ b/components/record-button.tsx @@ -105,6 +105,7 @@ type Props = { onStart?: () => void; onRecord: (data: string) => void; lessonType: string; + disabled?: boolean; }; export function RecordButton(props: Props) { const { isRecording, stop, start } = useVoiceRecorder(async (data) => { @@ -114,9 +115,11 @@ export function RecordButton(props: Props) { props.onRecord(b64); }); const doStart = () => { - props.onStart?.(); - start(); - } + if (!props.disabled) { + props.onStart?.(); + start(); + } + }; useHotkeys([ [ "v", @@ -138,11 +141,11 @@ export function RecordButton(props: Props) { break; } return isRecording ? ( - ) : ( - ); diff --git a/pages/_study_reducer.ts b/pages/_study_reducer.ts index dd8562f..b93528e 100644 --- a/pages/_study_reducer.ts +++ b/pages/_study_reducer.ts @@ -12,10 +12,8 @@ export type Quiz = { type State = { numQuizzesAwaitingServerResponse: number; - currentQuizIndex: number; errors: string[]; - quizIDsForLesson: string[]; - failedQuizzes: Set; + quizIDsForLesson: number[]; phrasesById: Record; }; @@ -24,11 +22,10 @@ type LessonType = keyof Quiz["audio"]; type QuizResult = "error" | "failure" | "success"; type Action = - | { type: "WILL_GRADE" } - | { type: "ADD_ERROR"; message: string } - | { type: "FAIL_QUIZ"; id: string } - | { type: "FLAG_QUIZ"; id: string } - | { type: "DID_GRADE"; id: string; result: QuizResult }; + | { type: "WILL_GRADE"; id: number } + | { type: "USER_GAVE_UP"; id: number } + | { type: "FLAG_QUIZ"; id: number } + | { type: "DID_GRADE"; id: number; result: QuizResult }; export type CurrentQuiz = { id: number; @@ -39,29 +36,26 @@ export type CurrentQuiz = { repetitions: number; }; -export function gotoNextQuiz(state: State): State { - return { - ...state, - currentQuizIndex: state.currentQuizIndex + 1 - }; +export function gotoNextQuiz(state: State, lastQuizID: number): State { + const filter = (id: number) => id !== lastQuizID; + const quizIDsForLesson = state.quizIDsForLesson.filter(filter); + return { ...state, quizIDsForLesson }; } export const newQuizState = (state: Partial = {}): State => { const phrasesById = state.phrasesById || {}; - const remainingQuizIDs = Object.keys(phrasesById); + const remainingQuizIDs = Object.keys(phrasesById).map((x) => parseInt(x)); return { - currentQuizIndex: 0, numQuizzesAwaitingServerResponse: 0, phrasesById, quizIDsForLesson: remainingQuizIDs, errors: [], - failedQuizzes: new Set(), ...state, }; }; export function currentQuiz(state: State): CurrentQuiz | undefined { - const quizID = state.quizIDsForLesson[state.currentQuizIndex]; + const quizID = state.quizIDsForLesson[0]; const quiz = state.phrasesById[quizID]; if (!quiz) { return undefined; @@ -74,7 +68,9 @@ export function currentQuiz(state: State): CurrentQuiz | undefined { if (quiz.repetitions < 2) { lessonType = "dictation"; } else { - lessonType = Math.random() > 0.5 ? "listening" : "speaking"; + const nonce = quiz.id + quiz.repetitions; + const x = nonce % 2; + lessonType = x === 0 ? "listening" : "speaking"; } return ( quiz && { @@ -88,67 +84,36 @@ export function currentQuiz(state: State): CurrentQuiz | undefined { ); } -export function quizReducer(state: State, action: Action): State { +function reduce(state: State, action: Action): State { switch (action.type) { - case "ADD_ERROR": - return { ...state, errors: [...state.errors, action.message] }; - - case "FAIL_QUIZ": - // Add to failed quiz set. - const failedQuizzes = new Set(state.failedQuizzes); - failedQuizzes.add(action.id); - // Remove from list of remaining quizzes. - const remainingQuizIDs = state.quizIDsForLesson.filter( - (id) => id !== action.id, - ); - return gotoNextQuiz({ - ...state, - failedQuizzes, - quizIDsForLesson: remainingQuizIDs, - }); - + case "USER_GAVE_UP": case "FLAG_QUIZ": - const filter = (id: string) => id !== action.id; - return gotoNextQuiz({ - ...state, - quizIDsForLesson: state.quizIDsForLesson.filter(filter), - }); - + return gotoNextQuiz(state, action.id); case "WILL_GRADE": return { ...state, numQuizzesAwaitingServerResponse: state.numQuizzesAwaitingServerResponse + 1, }; - case "DID_GRADE": let numQuizzesAwaitingServerResponse = state.numQuizzesAwaitingServerResponse - 1; - const nextState = { - ...state, - numQuizzesAwaitingServerResponse, - }; - switch (action.result) { - case "failure": - return gotoNextQuiz({ - ...nextState, - failedQuizzes: new Set(state.failedQuizzes).add(action.id), - }); - case "error": - // In the case of a server error, - // we push the quiz onto the end of the list - // and try again later. - return gotoNextQuiz({ - ...nextState, - quizIDsForLesson: [...state.quizIDsForLesson, action.id], - }); - case "success": - return gotoNextQuiz({ ...nextState }); - default: - throw new Error("Invalid quiz result " + action.result); - } + return gotoNextQuiz( + { + ...state, + numQuizzesAwaitingServerResponse, + }, + action.id, + ); default: console.warn("Unhandled action", action); return state; } } + +export function quizReducer(state: State, action: Action): State { + const nextState = reduce(state, action); + // Do debugging here: + // console.log(action.type); + return nextState; +} diff --git a/pages/study.tsx b/pages/study.tsx index a3d87eb..c9ee3e0 100644 --- a/pages/study.tsx +++ b/pages/study.tsx @@ -1,8 +1,8 @@ -import { PlayButton } from "@/components/play-button"; +import { PlayButton, playAudio } from "@/components/play-button"; import { RecordButton } from "@/components/record-button"; import { trpc } from "@/utils/trpc"; import { Button, Container, Grid, Header, Paper } from "@mantine/core"; -import { useReducer, useState } from "react"; +import { useEffect, useReducer, useState } from "react"; import Authed from "./_authed"; import { Quiz, @@ -24,8 +24,8 @@ type Props = { interface CurrentQuizProps { quiz: CurrentQuiz; inProgress: number; - doFail: (id: string) => void; - doFlag: (id: string) => void; + doFail: (id: number) => void; + doFlag: (id: number) => void; onRecord: (audio: string) => void; } @@ -33,8 +33,8 @@ function CurrentQuiz(props: CurrentQuizProps) { const { quiz, onRecord, doFail, doFlag, inProgress } = props; const [isRecording, setIsRecording] = useState(false); useHotkeys([ - ["x", () => doFail("" + quiz.id)], - ["z", () => doFlag("" + quiz.id)], + ["x", () => doFail(quiz.id)], + ["z", () => doFlag(quiz.id)], ]); if (!quiz) { let message = ""; @@ -68,6 +68,7 @@ function CurrentQuiz(props: CurrentQuizProps) { { setIsRecording(true); @@ -81,7 +82,7 @@ function CurrentQuiz(props: CurrentQuizProps) {