feat: Show training wheels

This commit is contained in:
Alexander Polynomdivision 2018-10-03 14:02:20 +02:00
parent 4ba879c531
commit 7fc1d8a058
5 changed files with 135 additions and 2 deletions

View File

@ -200,3 +200,11 @@ export function setLevelListSnackbar(state: boolean) {
state,
};
};
export const REVIEW_SET_HELP = "REVIEW_SET_HELP";
export function setReviewHelp(state: boolean) {
return {
type: REVIEW_SET_HELP,
state,
};
};

View File

@ -6,3 +6,6 @@ export const BACKEND_URL = "https://192.168.178.100";
// How often a wrongly answered group will be readded to the review queue
export const MAX_ERROR_THRESHOLD = 2;
// After how many failed attempts should we help the user
export const REVIEW_HELP_MOD = 3;

View File

@ -2,7 +2,7 @@ import { connect } from "react-redux";
import {
setDrawerButton, setReviewPopover, setReviewSummary,
setReview, setReviewLoading, setReviewDialog
setReview, setReviewLoading, setReviewDialog, setReviewHelp
} from "../actions";
import { IReviewMetadata } from "../models/review";
@ -20,6 +20,7 @@ const mapStateToProps = state => {
popoverColor: state.review.popoverColor,
popoverTextColor: state.review.popoverTextColor,
loading: state.review.loading,
showHelp: state.review.showHelp,
};
};
const mapDispatchToProps = dispatch => {
@ -30,6 +31,7 @@ const mapDispatchToProps = dispatch => {
setReview: (current: IVocab, meta: IReviewMetadata) => dispatch(setReview(current, meta)),
setLoading: (state: boolean) => dispatch(setReviewLoading(state)),
setReviewDialog: (state: boolean) => dispatch(setReviewDialog(state)),
setShowHelp: (state: boolean) => dispatch(setReviewHelp(state)),
};
};

View File

@ -27,7 +27,10 @@ import {
import { ReviewType, IReviewMetadata } from "../models/review";
import { levW } from "../algorithms/levenshtein";
import { LEVENSHTEIN_MAX_DISTANCE, MAX_ERROR_THRESHOLD } from "../config";
import {
LEVENSHTEIN_MAX_DISTANCE, MAX_ERROR_THRESHOLD,
REVIEW_HELP_MOD
} from "../config";
import { Queue } from "../utils/queue";
@ -49,6 +52,7 @@ interface IProps {
popoverText: string;
popoverColor: string;
popoverTextColor: string;
showHelp: boolean;
setReviewDialog: (state: boolean) => void;
setSummary: (state: boolean) => void;
@ -56,6 +60,7 @@ interface IProps {
drawerButtonState: (state: boolean) => void;
setReview: (curent: IReviewCard, meta: IReviewMetadata) => void;
setLoading: (state: boolean) => void;
setShowHelp: (state: boolean) => void;
}
const ReviewPageWithRouter = withRouter(
@ -132,6 +137,10 @@ const ReviewPageWithRouter = withRouter(
this.props.history.push("/dashboard");
}
closeHelp = () => {
this.props.setShowHelp(false);
}
increaseMeta = (correct: number, wrong: number): IReviewMetadata => {
const { metadata } = this.props;
@ -239,6 +248,9 @@ const ReviewPageWithRouter = withRouter(
// NOTE: We don't need to force a re-render, as the state
// will be updated since we need to show the popover.
vocabToReviewCard(vocab).forEach(this.reviewQueue.enqueue);
} else if (this.error_data[current.id] % REVIEW_HELP_MOD === 0) {
// Help the user
this.props.setShowHelp(true);
}
this.props.setReview(this.props.current, this.increaseMeta(0, 1));
@ -246,6 +258,101 @@ const ReviewPageWithRouter = withRouter(
}
}
vocabSpecificInformation(vocab: IVocab) {
switch (vocab.type) {
case VocabType.NOMEN:
const nData = vocab.latin as INomenData;
return <div>
<Typography variant="subheading" component="p">
<b>Genitiv:</b> {nData.genitiv}
</Typography>
<Typography variant="subheading" component="p">
<b>Genus:</b> {nData.genus}
</Typography>
</div>;
case VocabType.VERB:
const vData = vocab.latin as IVerbData;
return <div>
<Typography variant="subheading" component="p">
<b>1. Person Präsens:</b> {vData.praesens}
</Typography>
<Typography variant="subheading" component="p">
<b>1. Person Perfekt:</b> {vData.perfekt}
</Typography>
</div>;
case VocabType.ADJEKTIV:
const aData = vocab.latin as IAdjektivData;
return <div>
<Typography variant="subheading" component="p">
<b>Endung feminin:</b> {aData.endung_f}
</Typography>
<Typography variant="subheading" component="p">
<b>Endung neutrum:</b> {aData.endung_n}
</Typography>
</div>;
case VocabType.ADVERB:
return <div />;
}
}
helpDialog = () => {
// Find the vocabulary
// TODO: if (!vocab)
const vocab = this.vocab.find(el => el.id === this.props.current.id) as IVocab;
// TODO: This should be shared with LevelPage
return <Dialog
open={this.props.showHelp}
onClose={this.closeHelp}>
<DialogTitle>Wiederholung von {vocab.latin.grundform}</DialogTitle>
<DialogContent>
<Typography gutterBottom variant="headline" component="h3">
{vocab.german.join(", ")}
</Typography>
{this.vocabSpecificInformation(vocab)}
{
vocab.hint ? (
<div style={{
border: "dashed",
borderColor: "red",
padding: 12,
}}>
<Typography variant="subheading" component="p">
<b>Tipp:</b>
</Typography>
<Typography variant="body2">
{vocab.hint}
</Typography>
</div>
) : undefined
}
{
vocab.mnemonic ? (
<div style={{
border: "dashed",
borderColor: "#f1c40f",
marginTop: 12,
padding: 12,
}}>
<Typography variant="subheading" component="p">
<b>Eselsbrücke:</b>
</Typography>
<Typography variant="body2">
{vocab.mnemonic}
</Typography>
</div>
) : undefined
}
</DialogContent>
<DialogActions>
<Button
onClick={this.closeHelp}>
Zurück zur Wiederholung
</Button>
</DialogActions>
</Dialog>;
}
render() {
if (this.props.loading) {
return <div>
@ -368,6 +475,11 @@ const ReviewPageWithRouter = withRouter(
<CloseIcon />
</Button>
</Tooltip>
{
this.props.showHelp ? (
this.helpDialog()
) : undefined
}
<Dialog
open={this.props.dialogOpen}
onClose={this.closeDialog}>

View File

@ -52,6 +52,7 @@ interface IState {
popoverText: string;
popoverColor: string;
popoverTextColor: string;
showHelp: boolean;
};
topTen: ILearner[];
@ -110,6 +111,7 @@ const initialState: IState = {
popoverText: "",
popoverColor: "",
popoverTextColor: "",
showHelp: false,
},
nextLevel: {} as ILevel,
@ -256,6 +258,12 @@ export function LateinicusApp(state: IState = initialState, action: any) {
snackbar: action.state,
}),
});
case Actions.REVIEW_SET_HELP:
return Object.assign({}, state, {
review: Object.assign({}, state.review, {
showHelp: action.state,
}),
});
default:
// Ignore the initialization call to the reducer. By that we can
// catch all actions that are not implemented