refactor: Move the LevelPage to Redux

This commit is contained in:
Alexander Polynomdivision 2018-09-18 20:20:26 +02:00
parent 95fc93f690
commit 3136c92687
5 changed files with 227 additions and 55 deletions

View File

@ -1,3 +1,6 @@
import { IVocab } from "../models/vocab";
import { IUser } from "../models/user";
export const SET_DRAWER = "SET_DRAWER"; export const SET_DRAWER = "SET_DRAWER";
export function setDrawer(state: boolean) { export function setDrawer(state: boolean) {
return { return {
@ -46,3 +49,43 @@ export function setUser(user: IUser) {
user, user,
}; };
} }
export const LEVEL_SET_REVIEW = "LEVEL_SET_REVIEW";
export function setLevelReview(state: boolean) {
return {
type: LEVEL_SET_REVIEW,
state,
};
};
export const LEVEL_SET_LOOKEDAT = "LEVEL_SET_LOOKEDAT";
export function setLevelLookedAt(ids: number[]) {
return {
type: LEVEL_SET_REVIEW,
lookedAt: ids,
};
};
export const LEVEL_SET_CUR_VOCAB = "LEVEL_SET_CUR_VOCAB";
export function setLevelCurrentVocab(vocab: IVocab) {
return {
type: LEVEL_SET_CUR_VOCAB,
vocab,
};
};
export const LEVEL_SET_VOCAB = "LEVEL_SET_VOCAB";
export function setLevelVocab(vocab: IVocab[]) {
return {
type: LEVEL_SET_VOCAB,
vocab,
};
};
export const LEVEL_SET_LOADING = "LEVEL_SET_LOADING";
export function setLevelLoading(state: boolean) {
return {
type: LEVEL_SET_LOADING,
state,
};
};

View File

@ -7,7 +7,7 @@ import AuthRoute from "../security/AuthRoute";
import Dashboard from "../pages/dashboard"; import Dashboard from "../pages/dashboard";
import LoginPage from "../containers/LoginPage"; import LoginPage from "../containers/LoginPage";
import LevelListPage from "../pages/levelList"; import LevelListPage from "../pages/levelList";
import LevelPage from "../pages/level"; import LevelPage from "../containers/LevelPage";
import ReviewPage from "../pages/review"; import ReviewPage from "../pages/review";
import SummaryPage from "../pages/summary"; import SummaryPage from "../pages/summary";
import WelcomePage from "../pages/intro"; import WelcomePage from "../pages/intro";
@ -146,12 +146,13 @@ export default class Application extends React.Component<IProps> {
}; };
} }
getLevelVocab(id: number): IVocab[] { getLevelVocab(id: number): Promise<IVocab[]> {
console.log("STUB: Application::getLevelVocab"); console.log("STUB: Application::getLevelVocab");
// TODO: Actually implement this // TODO: Actually implement this
// TODO: Don't fetch this when it was already fetched once. return new Promise((res, rej) => {
return [{ setTimeout(() => {
res([{
german: ["Wein"], german: ["Wein"],
hint: "Worte auf '-um' sind meistens NeutrUM", hint: "Worte auf '-um' sind meistens NeutrUM",
type: VocabType.NOMEN, type: VocabType.NOMEN,
@ -174,7 +175,9 @@ export default class Application extends React.Component<IProps> {
* hint: "Worte auf \"-a\" sind FeminA", * hint: "Worte auf \"-a\" sind FeminA",
* type: VocabType.NOMEN, * type: VocabType.NOMEN,
* id: 3 * id: 3
} */] as IVocab[]; } */]);
}, 2000);
});
} }
login = (username: string, password: string): Promise<IUser | {}> => { login = (username: string, password: string): Promise<IUser | {}> => {

View File

@ -0,0 +1,37 @@
import { connect } from "react-redux";
import {
setDrawerButton, setLevelReview, setLevelLookedAt,
setLevelCurrentVocab, setLevelVocab, setLevelLoading
} from "../actions";
import { IVocab } from "../models/vocab";
import LevelPage from "../pages/level";
const mapStateToProps = state => {
const { currentVocab, lookedAt, toReview, vocab, loading } = state.level;
return {
currentVocab,
lookedAt,
toReview,
vocab,
loading,
};
};
const mapDispatchToProps = dispatch => {
return {
drawerButtonState: (state: boolean) => dispatch(setDrawerButton(state)),
setReview: (state: boolean) => dispatch(setLevelReview(state)),
setLookedAt: (ids: number[]) => dispatch(setLevelLookedAt(ids)),
setCurrentVocab: (vocab: IVocab) => dispatch(setLevelCurrentVocab(vocab)),
setVocab: (vocab: IVocab[]) => dispatch(setLevelVocab(vocab)),
setLoading: (state: boolean) => dispatch(setLevelLoading(state)),
};
};
const LevelPageContainer = connect(mapStateToProps,
mapDispatchToProps)(LevelPage);
export default LevelPageContainer;

View File

@ -5,8 +5,11 @@ import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText"; import ListItemText from "@material-ui/core/ListItemText";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import CircularProgress from "@material-ui/core/CircularProgress";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
@ -14,35 +17,42 @@ import { IVocab } from "../models/vocab";
interface IProps { interface IProps {
id: string; id: string;
levelVocab: (id: string) => Promise<IVocab[]>;
levelVocab: (id: string) => IVocab[]; loading: boolean;
setLoading: (state: boolean) => void;
vocab: IVocab[];
setVocab: (vocab: IVocab[]) => void;
setLookedAt: (ids: number[]) => void;
setCurrentVocab: (vocab: IVocab) => void;
setReview: (state: boolean) => void;
drawerButtonState: (state: boolean) => void; drawerButtonState: (state: boolean) => void;
};
interface IState {
currentVocab: IVocab; currentVocab: IVocab;
lookedAt: number[]; lookedAt: number[];
toReview: boolean; toReview: boolean;
}; };
export default class LevelPage extends React.Component<IProps, IState> { export default class LevelPage extends React.Component<IProps> {
private uid = 0; private uid = 0;
// To prevent React from redrawing the vocabulary list and prematurely // To prevent React from redrawing the vocabulary list and prematurely
// cancelling the animation // cancelling the animation
private uids: { [key: string]: string } = {}; private uids: { [key: string]: string } = {};
constructor(props: any) { componentDidMount() {
super(props);
// Hide the drawer // Hide the drawer
this.props.drawerButtonState(false); this.props.drawerButtonState(false);
this.state = { // Fetch the vocabulary
currentVocab: this.props.levelVocab(this.props.id)[0], this.props.setLoading(true);
lookedAt: [0],
toReview: false, // TODO: Error handling
}; this.props.levelVocab(this.props.id).then(vocab => {
this.props.setVocab(vocab);
this.props.setCurrentVocab(vocab[0]);
this.props.setLoading(false);
});
} }
genUID = (vocab: IVocab): string => { genUID = (vocab: IVocab): string => {
@ -57,16 +67,16 @@ export default class LevelPage extends React.Component<IProps, IState> {
renderVocabListItem = (vocab: IVocab): any => { renderVocabListItem = (vocab: IVocab): any => {
// Check if the vocab was already looked at // Check if the vocab was already looked at
const lookedAt = this.state.lookedAt.find((el) => el === vocab.id) || vocab.id === 0; const lookedAt = this.props.lookedAt.find((el) => el === vocab.id) || vocab.id === 0;
return <ListItem button key={this.genUID(vocab)} onClick={() => { return <ListItem button key={this.genUID(vocab)} onClick={() => {
// Prevent the user from using too much memory by always clicking on the elements // Prevent the user from using too much memory by always clicking on the elements
// Show the clicked at vocab word // Show the clicked at vocab word
this.setState({ this.props.setCurrentVocab(vocab);
currentVocab: vocab, this.props.setLookedAt(lookedAt ? (
lookedAt: lookedAt ? this.state.lookedAt : this.state.lookedAt.concat(vocab.id), this.props.lookedAt
}); ) : this.props.lookedAt.concat(vocab.id));
}}> }}>
<ListItemText> <ListItemText>
{`${vocab.latin.grundform} ${lookedAt ? "✔" : ""}`} {`${vocab.latin.grundform} ${lookedAt ? "✔" : ""}`}
@ -75,23 +85,52 @@ export default class LevelPage extends React.Component<IProps, IState> {
} }
toReview = () => { toReview = () => {
const { levelVocab, id } = this.props; const { vocab, lookedAt } = this.props;
// Only go to the review if all vocabulary item have been looked at // Only go to the review if all vocabulary item have been looked at
if (levelVocab(id).length === this.state.lookedAt.length) { if (vocab.length === lookedAt.length) {
this.setState({ this.props.setReview(true);
toReview: true,
});
} }
} }
render() { render() {
const { currentVocab } = this.state; 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.toReview ? (
<Redirect to={`/review/level/${this.props.id}`} />
) : 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 { currentVocab } = this.props;
return <div> return <div>
<Grid container direction="row"> <Grid container direction="row">
<Grid item xs={3}> <Grid item xs={3}>
<List> <List>
{this.props.levelVocab(this.props.id) {this.props.vocab
.map(this.renderVocabListItem)} .map(this.renderVocabListItem)}
{/* TODO*/} {/* TODO*/}
<ListItem button onClick={this.toReview}> <ListItem button onClick={this.toReview}>
@ -102,7 +141,7 @@ export default class LevelPage extends React.Component<IProps, IState> {
</List> </List>
</Grid> </Grid>
{ {
this.state.toReview ? ( this.props.toReview ? (
<Redirect to={`/review/level/${this.props.id}`} /> <Redirect to={`/review/level/${this.props.id}`} />
) : undefined ) : undefined
} }

View File

@ -2,6 +2,7 @@ import * as Actions from "../actions";
import { ILearner } from "../models/learner"; import { ILearner } from "../models/learner";
import { IUser } from "../models/user"; import { IUser } from "../models/user";
import { IVocab } from "../models/vocab";
interface IState { interface IState {
drawer: boolean; drawer: boolean;
@ -17,6 +18,14 @@ interface IState {
snackOpen: boolean; snackOpen: boolean;
}; };
level: {
currentVocab: IVocab;
lookedAt: number[];
toReview: boolean;
vocab: IVocab[];
loading: boolean;
};
topTen: ILearner[]; topTen: ILearner[];
}; };
@ -27,7 +36,7 @@ const initialState: IState = {
drawerButton: true, drawerButton: true,
// Is the user authenticated? // Is the user authenticated?
// TODO: Set this to false // TODO: Set this to false
authenticated: false, authenticated: true,
user: {}, user: {},
@ -37,6 +46,14 @@ const initialState: IState = {
snackOpen: false, snackOpen: false,
}, },
level: {
currentVocab: {} as IVocab,
lookedAt: [0],
toReview: false,
vocab: [],
loading: true,
},
// The top ten // The top ten
topTen: [], topTen: [],
}; };
@ -75,6 +92,39 @@ export function LateinicusApp(state: IState = initialState, action: any) {
return Object.assign({}, state, { return Object.assign({}, state, {
user: action.user, user: action.user,
}); });
case Actions.LEVEL_SET_REVIEW:
return Object.assign({}, state, {
level: Object.assign({}, state.level, {
toReview: action.state,
// Make sure that we are in a "loading mode", when the page gets
// called the next time
loading: true,
}),
});
case Actions.LEVEL_SET_LOOKEDAT:
return Object.assign({}, state, {
level: Object.assign({}, state.level, {
lookedAt: action.lookedAt,
}),
});
case Actions.LEVEL_SET_CUR_VOCAB:
return Object.assign({}, state, {
level: Object.assign({}, state.level, {
currentVocab: action.vocab,
}),
});
case Actions.LEVEL_SET_VOCAB:
return Object.assign({}, state, {
level: Object.assign({}, state.level, {
vocab: action.vocab,
}),
});
case Actions.LEVEL_SET_LOADING:
return Object.assign({}, state, {
level: Object.assign({}, state.level, {
loading: action.state,
}),
});
default: default:
return state; return state;
} }