feat: Partially transition Review to Redux

This commit is contained in:
Alexander Polynomdivision
2018-09-19 16:39:02 +02:00
parent d94fa63ac7
commit 882ca5a9e3
6 changed files with 272 additions and 84 deletions

View File

@@ -8,6 +8,8 @@ import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import Popover from "@material-ui/core/Popover";
import LinearProgress from "@material-ui/core/LinearProgress";
import CircularProgress from "@material-ui/core/CircularProgress";
import Paper from "@material-ui/core/Paper";
import { Redirect } from "react-router-dom";
@@ -21,34 +23,34 @@ import { Queue } from "../utils/queue";
interface IProps {
levelId?: number;
vocabByLevel?: (level: number) => IVocab[];
vocabByQueue?: () => IVocab[];
setLastReview: (meta: IReviewMetadata) => void;
vocabByLevel?: (level: number) => Promise<IVocab[]>;
vocabByQueue?: () => Promise<IVocab[]>;
reviewType: ReviewType;
drawerButtonState: (state: boolean) => void;
}
interface IState {
input: string;
loading: boolean;
vocab: IVocab[];
current: IReviewCard;
metadata: IReviewMetadata;
toSummary: boolean;
popoverOpen: boolean;
popoverText: string;
popoverColor: string;
setSummary: (state: boolean) => void;
setPopover: (state: boolean, text: string, color: string) => void;
drawerButtonState: (state: boolean) => void;
setLastReview: (meta: IReviewMetadata) => void;
setReview: (curent: IReviewCard, meta: IReviewMetadata) => void;
setLoading: (state: boolean) => void;
}
export default class ReviewPage extends React.Component<IProps, IState> {
export default class ReviewPage extends React.Component<IProps> {
private vocab: IVocab[] = [];
private reviewQueue: Queue<IReviewCard> = undefined;
private reviewQueue: Queue<IReviewCard> = new Queue();
// Used for positioning the popover
private buttonRef: HTMLButtonElement;
private inputRef: HTMLInputElement;
private metadata: IReviewMetadata = { correct: 0, wrong: 0 };
constructor(props: any) {
super(props);
@@ -56,6 +58,17 @@ export default class ReviewPage extends React.Component<IProps, IState> {
// Hide the drawer button
this.props.drawerButtonState(false);
const vocToQueue = () => {
this.vocab.forEach((vocab) => {
vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
});
this.props.setReview(this.reviewQueue.dequeue(), {
correct: 0,
wrong: 0,
});
this.props.setLoading(false);
};
// Get the correct vocabulary
const { reviewType, vocabByLevel, levelId, vocabByQueue } = this.props;
switch (reviewType) {
@@ -63,7 +76,10 @@ export default class ReviewPage extends React.Component<IProps, IState> {
if (!vocabByLevel || !levelId) {
alert("[ReviewPage::constructor] vocabByLevel or levelId undefined");
} else {
this.vocab = vocabByLevel(levelId);
vocabByLevel(levelId).then(res => {
this.vocab = res;
vocToQueue();
});
}
break;
@@ -71,38 +87,18 @@ export default class ReviewPage extends React.Component<IProps, IState> {
if (!vocabByQueue) {
alert("[ReviewPage::constructor] vocabByQueue undefined");
} else {
this.vocab = vocabByQueue();
vocabByQueue().then(res => {
this.vocab = res;
vocToQueue();
});
}
break;
}
// Turn the vocabulary into IReviewCards and queue them
if (!this.reviewQueue) {
this.reviewQueue = new Queue();
this.vocab.forEach((vocab) => {
vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
});
}
this.state = {
input: "",
current: this.reviewQueue.dequeue(),
metadata: {
correct: 0,
wrong: 0,
},
toSummary: false,
popoverOpen: false,
popoverText: "",
popoverColor: "red",
};
}
increaseMeta = (correct: number, wrong: number): IReviewMetadata => {
const { metadata } = this.state;
const { metadata } = this;
return {
wrong: metadata.wrong + wrong,
@@ -111,21 +107,21 @@ export default class ReviewPage extends React.Component<IProps, IState> {
}
vocabFromId = (id: number) => {
return this.vocab.find((el) => el.id === this.state.current.id);
return this.vocab.find((el) => el.id === this.props.current.id);
}
checkInput = () => {
// Check if the given answer is somewhere in the german words
const { input } = this.state;
const input = this.inputRef.value || "";
// Map all possible answers to lowercase ( => ignore casing)
const answers = this.state.current.answers.map((el) => el.toLowerCase());
const answers = this.props.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);
console.log(this.reviewQueue.size());
console.log("Review Queue size:", this.reviewQueue.size());
// Check if the user's answer was correct
if (minDist === 0) {
@@ -133,57 +129,76 @@ export default class ReviewPage extends React.Component<IProps, IState> {
// Show the next vocab word
if (this.reviewQueue.size() === 0) {
// Go to the summary screen
this.setState({
toSummary: true,
}, () => {
// Update the "Last Review" data
this.props.setLastReview(this.state.metadata);
});
this.props.setLastReview(this.metadata);
this.props.setSummary(true);
} else {
// Increase the vocab
this.setState({
current: this.reviewQueue.dequeue(),
input: "",
// Add one correct answer
metadata: this.increaseMeta(1, 0),
});
this.props.setReview(this.reviewQueue.dequeue(), this.increaseMeta(1, 0));
this.inputRef.value = "";
}
} else if (minDist <= LEVENSHTEIN_MAX_DISTANCE) {
// TODO: Show a hint
console.log("Partially correct");
} else {
// Find the IVocab item
const vocab = this.vocabFromId(this.state.current.id);
const vocab = this.vocabFromId(this.props.current.id);
if (vocab) {
// Re-Add the vocabulary item to the review queue
// TODO: Only re-add when it when it's not re-queued
vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
// vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
} else {
console.log("[ReviewPage::checkInput] Could not find IVocab item for wrong IReviewCard");
}
this.setState({
popoverOpen: true,
popoverText: "Das war nicht richtig",
popoverColor: "red",
// TODO: Or maybe don't reset the text
input: "",
metadata: this.increaseMeta(0, 1),
});
this.props.setPopover(true, "Das war nicht richtig", "red");
}
// TODO(?): Show a snackbar for showing the updated score
}
render() {
const { question, qtype } = this.state.current;
if (this.props.loading) {
return <div>
{/*
* This would be the case when the user presses the "to
* review" button. That is because we need the state of loading
* to be true, when this page gets called
* TODO:?
*/}
{
this.props.toSummary ? (
<Redirect to="/review/summary" />
) : undefined
}
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justify="center"
style={{ minHeight: '100vh' }}>
<Grid item xs={12}>
<Paper className="paper">
<Grid container direction="column" spacing={8}>
<CircularProgress />
</Grid>
</Paper>
</Grid>
</Grid>
</div>;
}
const { question, qtype } = this.props.current;
const questionTitle = `${question} (${reviewQTypeToStr(qtype)})`;
// TODO:
const progress = 50;
return <div>
{
this.state.toSummary ? (
this.props.toSummary ? (
<Redirect to="/review/summary" />
) : undefined
}
@@ -198,10 +213,7 @@ export default class ReviewPage extends React.Component<IProps, IState> {
<TextField
margin="normal"
fullWidth={true}
value={this.state.input}
onChange={(ev) => this.setState({
input: ev.target.value,
})}
inputRef={node => this.inputRef = node}
onKeyPress={(ev) => {
// Allow checking of the answer by pressing Enter
if (ev.key === "Enter")
@@ -211,7 +223,7 @@ export default class ReviewPage extends React.Component<IProps, IState> {
variant="determinate"
value={progress} />
<Popover
open={this.state.popoverOpen}
open={this.props.popoverOpen}
anchorOrigin={{
vertical: "center",
horizontal: "center"
@@ -221,12 +233,10 @@ export default class ReviewPage extends React.Component<IProps, IState> {
horizontal: "center"
}}
anchorEl={this.buttonRef}
onClose={() => this.setState({
popoverOpen: false,
})}
onClose={() => this.props.setPopover(false, "", "")}
PaperProps={{
style: {
backgroundColor: this.state.popoverColor,
backgroundColor: this.props.popoverColor,
padding: 10,
color: "white"
}
@@ -234,7 +244,7 @@ export default class ReviewPage extends React.Component<IProps, IState> {
<Typography
variant="button"
color="inherit">
{this.state.popoverText}
{this.props.popoverText}
</Typography>
</Popover>
<Button