Skip to content

Commit

Permalink
September 9th Updates (#6)
Browse files Browse the repository at this point in the history
* Update the quiz failure UI
* Refactor/simplify quiz reducer.
* Improve grading prompts.
  • Loading branch information
RickCarlino authored Sep 9, 2023
1 parent 216dca2 commit 3dc9c30
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 170 deletions.
4 changes: 2 additions & 2 deletions components/record-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function blobToBase64(blob: Blob): Promise<string> {
}
type Props = {
onRecord: (data: string) => void;
quizType: string;
lessonType: string;
};
export function RecordButton(props: Props) {
const { isRecording, stop, start } = useVoiceRecorder(async (data) => {
Expand All @@ -121,7 +121,7 @@ export function RecordButton(props: Props) {
],
]);
let buttonText = "Recor[d]";
switch (props.quizType) {
switch (props.lessonType) {
case "dictation":
buttonText = "Dictate Card";
break;
Expand Down
100 changes: 30 additions & 70 deletions pages/_study_reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { playAudio } from "@/components/play-button";
import { shuffle } from "radash";

export type Quiz = {
id: number;
ko: string;
Expand All @@ -16,14 +13,13 @@ export type Quiz = {
type State = {
numQuizzesAwaitingServerResponse: number;
currentQuizIndex: number;
currentLessonType: LessonType;
errors: string[];
quizIDsForLesson: string[];
failedQuizzes: Set<string>;
phrasesById: Record<string, Quiz>;
};

type LessonType = "dictation" | "listening" | "speaking";
type LessonType = keyof Quiz["audio"];

type QuizResult = "error" | "failure" | "success";

Expand All @@ -34,64 +30,26 @@ type Action =
| { type: "FLAG_QUIZ"; id: string }
| { type: "DID_GRADE"; id: string; result: QuizResult };

const proceedToNextLessonType = (state: State): State => {
let remainingQuizIDs = state.quizIDsForLesson;
let currentLessonType = state.currentLessonType;
remainingQuizIDs = shuffle(
Object.keys(state.phrasesById).filter((id) => {
return !state.failedQuizzes.has(id);
}),
);
switch (currentLessonType) {
case "dictation":
currentLessonType = "listening";
break;
case "listening":
currentLessonType = "speaking";
break;
case "speaking":
// TODO We could potentially restart quizzes here for
// second round (failed items only).
remainingQuizIDs = []; // No more quizzes
break;
default:
throw new Error("Invalid quizType");
}
return {
...state,
currentLessonType: currentLessonType,
quizIDsForLesson: remainingQuizIDs,
currentQuizIndex: 0,
};
export type CurrentQuiz = {
id: number;
en: string;
ko: string;
quizAudio: string;
lessonType: "dictation" | "speaking" | "listening";
repetitions: number;
};

export function gotoNextQuiz(state: State): State {
let nextQuizIndex = state.currentQuizIndex + 1;
let nextID = state.quizIDsForLesson[nextQuizIndex];

// If the current quiz type is "dictation", check for repetitions count < 3
if (state.currentLessonType === "dictation") {
while (nextID && state.phrasesById[nextID].repetitions > 3) {
nextQuizIndex++;
nextID = state.quizIDsForLesson[nextQuizIndex];
}
}

let nextState: State;
if (nextID) {
nextState = { ...state, currentQuizIndex: nextQuizIndex };
} else {
nextState = proceedToNextLessonType(state);
}

return nextState;
return {
...state,
currentQuizIndex: state.currentQuizIndex + 1
};
}

export const newQuizState = (state: Partial<State> = {}): State => {
const phrasesById = state.phrasesById || {};
const remainingQuizIDs = Object.keys(phrasesById);
return {
currentLessonType: "dictation",
currentQuizIndex: 0,
numQuizzesAwaitingServerResponse: 0,
phrasesById,
Expand All @@ -102,25 +60,30 @@ export const newQuizState = (state: Partial<State> = {}): State => {
};
};

export type CurrentQuiz = {
id: number;
en: string;
ko: string;
quizAudio: string;
quizType: "dictation" | "speaking" | "listening";
};

export function currentQuiz(state: State): CurrentQuiz | undefined {
const quizID = state.quizIDsForLesson[state.currentQuizIndex];
const quizType = state.currentLessonType;
const quiz = state.phrasesById[quizID];
if (!quiz) {
return undefined;
}
let lessonType: LessonType;
// TODO: Calculating the lessonType on the frontend does not
// make sense any more and is an artifact of a previous architecture.
// In the future we should calculate this on the backend and only
// send audio for the appropriate quiz.
if (quiz.repetitions < 4) {
lessonType = "dictation";
} else {
lessonType = quiz.repetitions % 2 === 0 ? "listening" : "speaking";
}
return (
quiz && {
id: quiz.id,
en: quiz.en,
ko: quiz.ko,
quizAudio: quiz.audio[quizType],
quizType,
quizAudio: quiz.audio[lessonType],
lessonType,
repetitions: quiz.repetitions,
}
);
}
Expand Down Expand Up @@ -164,7 +127,7 @@ export function quizReducer(state: State, action: Action): State {
const nextState = {
...state,
numQuizzesAwaitingServerResponse,
}
};
switch (action.result) {
case "failure":
return gotoNextQuiz({
Expand All @@ -177,10 +140,7 @@ export function quizReducer(state: State, action: Action): State {
// and try again later.
return gotoNextQuiz({
...nextState,
quizIDsForLesson: [
...state.quizIDsForLesson,
action.id,
]
quizIDsForLesson: [...state.quizIDsForLesson, action.id],
});
case "success":
return gotoNextQuiz({ ...nextState });
Expand Down
36 changes: 26 additions & 10 deletions pages/study.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function CurrentQuiz(props: CurrentQuizProps) {
<PlayButton dataURI={quiz.quizAudio} />
</Grid.Col>
<Grid.Col span={4}>
<RecordButton quizType={quiz.quizType} onRecord={onRecord} />
<RecordButton lessonType={quiz.lessonType} onRecord={onRecord} />
</Grid.Col>
<Grid.Col span={4}>
<Button onClick={() => doFail("" + quiz.id)} fullWidth>
Expand All @@ -80,7 +80,7 @@ function CurrentQuiz(props: CurrentQuizProps) {
function CardOverview({ quiz }: { quiz: CurrentQuiz }) {
let term = "";
let def = "";
switch (quiz.quizType) {
switch (quiz.lessonType) {
case "dictation":
term = quiz.ko;
def = quiz.en;
Expand All @@ -101,14 +101,22 @@ function CardOverview({ quiz }: { quiz: CurrentQuiz }) {

function Failure(props: {
id: number;
ko: string;
en: string;
lessonType: string;
userTranscription: string;
rejectionText: string;
}) {
return <div>
<p>Incorrect!</p>
<p>What you said: {props.userTranscription}</p>
<p>Why it's wrong: {props.rejectionText}</p>
</div>
return (
<div>
<p>You answered the last question incorrectly:</p>
<p>Quiz type: {props.lessonType}</p>
<p>Korean: {props.ko}</p>
<p>English: {props.en}</p>
<p>What you said: {props.userTranscription}</p>
<p>Why it's wrong: {props.rejectionText}</p>
</div>
);
}

function Study({ quizzes }: Props) {
Expand All @@ -123,6 +131,9 @@ function Study({ quizzes }: Props) {
const flagPhrase = trpc.flagPhrase.useMutation();
const [failure, setFailure] = useState<{
id: number;
ko: string;
en: string;
lessonType: string;
userTranscription: string;
rejectionText: string;
} | null>(null);
Expand All @@ -132,11 +143,13 @@ function Study({ quizzes }: Props) {
};
const quiz = currentQuiz(state);
if (!quiz) {
const reload = confirm("Session complete. Study more?");
reload && location.reload();
return <div>Session complete.</div>;
}
const header = (() => {
if (!quiz) return <span></span>;
return <span>🫣 Card #{quiz.id}</span>;
return <span>🫣 Card #{quiz.id}, {quiz.repetitions} repetitions</span>;
})();

return (
Expand Down Expand Up @@ -164,10 +177,10 @@ function Study({ quizzes }: Props) {
.catch(needBetterErrorHandler);
}}
onRecord={(audio) => {
const { id, quizType } = quiz;
const { id, lessonType } = quiz;
dispatch({ type: "WILL_GRADE" });
performExam
.mutateAsync({ id, audio, quizType })
.mutateAsync({ id, audio, lessonType })
.then((data) => {
setFailure(null);
switch (data.result) {
Expand All @@ -186,6 +199,9 @@ function Study({ quizzes }: Props) {
});
setFailure({
id,
ko: quiz.ko,
en: quiz.en,
lessonType: quiz.lessonType,
userTranscription: data.userTranscription,
rejectionText: data.rejectionText,
});
Expand Down
Loading

0 comments on commit 3dc9c30

Please sign in to comment.