diff --git a/src/models/vocab.ts b/src/models/vocab.ts index 6076cd5..1975b29 100644 --- a/src/models/vocab.ts +++ b/src/models/vocab.ts @@ -39,12 +39,12 @@ export interface IVocab { type: VocabType; latin: INomenData | IVerbData | IAdjektivData; - // This number is lesson specific + // This number is unique across all vocabulary items id: number; }; // What kind of question should be answered -export enum IReviewQType { +export enum ReviewQType { GERMAN, NOMEN_GENITIV, @@ -63,9 +63,36 @@ export interface IReviewCard { // If a question can have multiple answers answers: string[]; - qtype: IReviewQType; + qtype: ReviewQType; + + // Identical to its corresponding IVocab item + id: number; }; +export function reviewQTypeToStr(type: ReviewQType): string { + switch (type) { + case ReviewQType.GERMAN: + return "Übersetzung"; + case ReviewQType.NOMEN_GENITIV: + return "Genitiv"; + case ReviewQType.NOMEN_GENUS: + return "Genus"; + case ReviewQType.ADJ_NOM_A: + // TODO + return "Nominativ A"; + case ReviewQType.ADJ_NOM_B: + // TODO + return "Nominativ B"; + case ReviewQType.VERB_PRAESENS: + return "1. Person Präsens"; + case ReviewQType.VERB_PERFEKT: + return "1. Person Perfekt"; + case ReviewQType.VERB_PPP: + // TODO + return "PPP"; + } +} + // Turn a vocabulaty item into a series of questions about the item export function vocabToReviewCard(vocab: IVocab): IReviewCard[] { switch (vocab.type) { @@ -75,24 +102,27 @@ export function vocabToReviewCard(vocab: IVocab): IReviewCard[] { // Latin -> German question: latin.grundform, answers: vocab.german, - qtype: IReviewQType.GERMAN, + qtype: ReviewQType.GERMAN, + id: vocab.id, }, { // Latin -> Genitiv question: latin.grundform, answers: [latin.genitiv], - qtype: IReviewQType.NOMEN_GENITIV, + qtype: ReviewQType.NOMEN_GENITIV, + id: vocab.id, }, { // Latin -> Genus question: latin.grundform, answers: [latin.genus], - qtype: IReviewQType.NOMEN_GENUS, + qtype: ReviewQType.NOMEN_GENUS, + id: vocab.id, }]; default: return []; } } -export const typeToPoints = (VocabType type) => { +export function typeToPoints(type: VocabType) { switch (type) { // Nomen: 2P + 1 (Wenn richtig) // diff --git a/src/pages/review.tsx b/src/pages/review.tsx index 6395bc5..ad0d7a0 100644 --- a/src/pages/review.tsx +++ b/src/pages/review.tsx @@ -7,16 +7,17 @@ import Grid from "@material-ui/core/Grid"; import Button from "@material-ui/core/Button"; import Typography from "@material-ui/core/Typography"; import Popover from "@material-ui/core/Popover"; -import Paper from "@material-ui/core/Paper"; import { Redirect } from "react-router-dom"; -import { IVocab, ReviewMode, VocabType } from "../models/vocab"; +import { IVocab, IReviewCard, vocabToReviewCard, reviewQTypeToStr } from "../models/vocab"; import { ReviewType } from "../models/review"; import { levW } from "../algorithms/levenshtein"; import { LEVENSHTEIN_MAX_DISTANCE } from "../config"; +import { Queue } from "../utils/queue"; + interface IProps { levelId?: number; vocabByLevel?: (level: number) => IVocab[]; @@ -29,7 +30,7 @@ interface IProps { interface IState { input: string; - current: number; + current: IReviewCard; toSummary: boolean; @@ -40,23 +41,13 @@ interface IState { export default class ReviewPage extends React.Component { private vocab: IVocab[] = []; + private reviewQueue: Queue = new Queue(); // Used for positioning the popover private buttonRef: HTMLButtonElement; constructor(props: any) { super(props); - this.state = { - input: "", - current: 0, - - toSummary: false, - - popoverOpen: false, - popoverText: "", - popoverColor: "red", - }; - // Hide the drawer button this.props.drawerButtonState(false); @@ -80,26 +71,44 @@ export default class ReviewPage extends React.Component { break; } + + // Turn the vocabulary into IReviewCards and queue them + this.vocab.forEach((vocab) => { + vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue); + }); + + this.state = { + input: "", + current: this.reviewQueue.dequeue(), + + toSummary: false, + + popoverOpen: false, + popoverText: "", + popoverColor: "red", + }; } - currentVocab = () => { - return this.vocab[this.state.current]; + currentVocab = (id: number) => { + return this.vocab.find((el) => el.id === this.state.current.id); } checkInput = () => { - const current = this.currentVocab(); - // Check if the given answer is somewhere in the german words const { input } = this.state; - const german = current.german.map((str) => str.toLowerCase()); - const dists = german.map((ger) => levW(input.toLowerCase(), ger)); + + // Map all possible answers to lowercase ( => ignore casing) + const answers = this.state.current.answers.map((el) => el.toLowerCase()); + // Calculate the distances to all possible answers + const dists = answers.map((el) => levW(input.toLowerCase(), el)); + // Find the lowest distance const minDist = Math.min(...dists); // Check if the user's answer was correct if (minDist === 0) { // TODO: Show it's correct? // Show the next vocab word - if (this.state.current + 1 >= this.vocab.length) { + if (this.reviewQueue.size() === 0) { // TODO: Set some data that the summary screen will show this.setState({ toSummary: true, @@ -107,7 +116,7 @@ export default class ReviewPage extends React.Component { } else { // Increase the vocab this.setState({ - current: this.state.current + 1, + current: this.reviewQueue.dequeue(), input: "", }); } @@ -128,6 +137,8 @@ export default class ReviewPage extends React.Component { } render() { + const { question, qtype } = this.state.current; + const questionTitle = `${question} (${reviewQTypeToStr(qtype)})`; return
{ this.state.toSummary ? ( @@ -140,7 +151,7 @@ export default class ReviewPage extends React.Component { - {this.currentVocab().latin.grundform} + {questionTitle} { open={this.state.popoverOpen} anchorOrigin={{ vertical: "center", - horizontal: "left" + horizontal: "center" }} transformOrigin={{ vertical: "bottom", - horizontal: "left" + horizontal: "center" }} anchorEl={this.buttonRef} onClose={() => this.setState({ @@ -175,11 +186,15 @@ export default class ReviewPage extends React.Component { color: "white" } }}> - + {this.state.popoverText} -