feat: Rework the Level UI
This commit is contained in:
parent
ede271f604
commit
d9ec095d5c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "seminarfach",
|
"name": "seminarfach",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -85,6 +85,30 @@ export function setLevelLoading(state: boolean) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LEVEL_SET_STEPPER = "LEVEL_SET_STEPPER";
|
||||||
|
export function setLevelStepper(index: number) {
|
||||||
|
return {
|
||||||
|
type: LEVEL_SET_STEPPER,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LEVEL_SET_REVIEW_DIAG = "LEVEL_SET_REVIEW_DIAG";
|
||||||
|
export function setLevelReviewDiag(state: boolean) {
|
||||||
|
return {
|
||||||
|
type: LEVEL_SET_REVIEW_DIAG,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LEVEL_SET_LEAVE_DIAG = "LEVEL_SET_LEAVE_DIAG";
|
||||||
|
export function setLevelLeaveDiag(state: boolean) {
|
||||||
|
return {
|
||||||
|
type: LEVEL_SET_LEAVE_DIAG,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const SET_LEVELS = "SET_LEVELS";
|
export const SET_LEVELS = "SET_LEVELS";
|
||||||
export function setLevels(levels: ILevel[]) {
|
export function setLevels(levels: ILevel[]) {
|
||||||
return {
|
return {
|
||||||
|
@ -363,7 +363,7 @@ export default class Application extends React.Component<IProps> {
|
|||||||
basename="/app/">
|
basename="/app/">
|
||||||
<div className="flex" >
|
<div className="flex" >
|
||||||
<Drawer logout={this.logout} />
|
<Drawer logout={this.logout} />
|
||||||
<div className="content">
|
<div>
|
||||||
<Route exact path="/" component={() => <Redirect to="/login" />} />
|
<Route exact path="/" component={() => <Redirect to="/login" />} />
|
||||||
<Route exact path="/login" component={() => {
|
<Route exact path="/login" component={() => {
|
||||||
return <LoginPage login={this.login} />
|
return <LoginPage login={this.login} />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setDrawerButton, setLevelLookedAt,
|
setDrawerButton, setLevelLookedAt, setLevelStepper, setLevelReviewDiag,
|
||||||
setLevelCurrentVocab, setLevelVocab, setLevelLoading
|
setLevelCurrentVocab, setLevelVocab, setLevelLoading, setLevelLeaveDiag
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
|
|
||||||
import { IVocab } from "../models/vocab";
|
import { IVocab } from "../models/vocab";
|
||||||
@ -10,13 +10,19 @@ import { IVocab } from "../models/vocab";
|
|||||||
import LevelPage from "../pages/level";
|
import LevelPage from "../pages/level";
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { currentVocab, lookedAt, vocab, loading } = state.level;
|
const {
|
||||||
|
currentVocab, lookedAt, vocab, loading, stepper,
|
||||||
|
reviewDialog, leaveDialog
|
||||||
|
} = state.level;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentVocab,
|
currentVocab,
|
||||||
lookedAt,
|
lookedAt,
|
||||||
vocab,
|
vocab,
|
||||||
loading,
|
loading,
|
||||||
|
stepperIndex: stepper,
|
||||||
|
reviewDialog,
|
||||||
|
leaveDialog,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,6 +33,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
setCurrentVocab: (vocab: IVocab) => dispatch(setLevelCurrentVocab(vocab)),
|
setCurrentVocab: (vocab: IVocab) => dispatch(setLevelCurrentVocab(vocab)),
|
||||||
setVocab: (vocab: IVocab[]) => dispatch(setLevelVocab(vocab)),
|
setVocab: (vocab: IVocab[]) => dispatch(setLevelVocab(vocab)),
|
||||||
setLoading: (state: boolean) => dispatch(setLevelLoading(state)),
|
setLoading: (state: boolean) => dispatch(setLevelLoading(state)),
|
||||||
|
setStepper: (index: number) => dispatch(setLevelStepper(index)),
|
||||||
|
setReviewDialog: (state: boolean) => dispatch(setLevelReviewDiag(state)),
|
||||||
|
setLeaveDialog: (state: boolean) => dispatch(setLevelLeaveDiag(state))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,3 +67,24 @@ body {
|
|||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
right: -12px;
|
right: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.level-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 700px) {
|
||||||
|
.level-card {
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-stepper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
|
/* We otherwise go larger than the page */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import List from "@material-ui/core/List";
|
|
||||||
import ListItem from "@material-ui/core/ListItem";
|
|
||||||
import ListItemText from "@material-ui/core/ListItemText";
|
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import Paper from "@material-ui/core/Paper";
|
import Paper from "@material-ui/core/Paper";
|
||||||
import Grid from "@material-ui/core/Grid";
|
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 MobileStepper from '@material-ui/core/MobileStepper';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
|
||||||
|
import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
|
import DialogActions from "@material-ui/core/DialogActions";
|
||||||
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
|
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||||
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
|
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
|
|
||||||
@ -22,15 +28,21 @@ interface IProps {
|
|||||||
|
|
||||||
history: any;
|
history: any;
|
||||||
|
|
||||||
|
stepperIndex: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
setLoading: (state: boolean) => void;
|
|
||||||
vocab: IVocab[];
|
vocab: IVocab[];
|
||||||
|
lookedAt: number[];
|
||||||
|
currentVocab: IVocab;
|
||||||
|
leaveDialog: boolean;
|
||||||
|
reviewDialog: boolean;
|
||||||
setVocab: (vocab: IVocab[]) => void;
|
setVocab: (vocab: IVocab[]) => void;
|
||||||
setLookedAt: (ids: number[]) => void;
|
setLookedAt: (ids: number[]) => void;
|
||||||
setCurrentVocab: (vocab: IVocab) => void;
|
setCurrentVocab: (vocab: IVocab) => void;
|
||||||
drawerButtonState: (state: boolean) => void;
|
drawerButtonState: (state: boolean) => void;
|
||||||
currentVocab: IVocab;
|
setLoading: (state: boolean) => void;
|
||||||
lookedAt: number[];
|
setStepper: (index: number) => void;
|
||||||
|
setReviewDialog: (state: boolean) => void;
|
||||||
|
setLeaveDialog: (state: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LevelPageWithRouter = withRouter(
|
const LevelPageWithRouter = withRouter(
|
||||||
@ -52,10 +64,9 @@ const LevelPageWithRouter = withRouter(
|
|||||||
this.props.setVocab(vocab);
|
this.props.setVocab(vocab);
|
||||||
this.props.setCurrentVocab(vocab[0]);
|
this.props.setCurrentVocab(vocab[0]);
|
||||||
this.props.setLookedAt([vocab[0].id]);
|
this.props.setLookedAt([vocab[0].id]);
|
||||||
|
this.props.setStepper(0);
|
||||||
this.props.setLoading(false);
|
this.props.setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
genUID = (vocab: IVocab): string => {
|
genUID = (vocab: IVocab): string => {
|
||||||
@ -68,32 +79,61 @@ const LevelPageWithRouter = withRouter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderVocabListItem = (vocab: IVocab): any => {
|
toReview = () => {
|
||||||
// Check if the vocab was already looked at
|
this.props.setLoading(true);
|
||||||
const lookedAt = this.props.lookedAt.find((el) => el === vocab.id);
|
this.props.setStepper(0);
|
||||||
|
this.props.history.push(`/review/level/${this.props.id}`);
|
||||||
return <ListItem button key={this.genUID(vocab)} onClick={() => {
|
|
||||||
// Prevent the user from using too much memory by always clicking on the elements
|
|
||||||
// Show the clicked at vocab word
|
|
||||||
|
|
||||||
this.props.setCurrentVocab(vocab);
|
|
||||||
this.props.setLookedAt(lookedAt ? (
|
|
||||||
this.props.lookedAt
|
|
||||||
) : this.props.lookedAt.concat(vocab.id));
|
|
||||||
}}>
|
|
||||||
<ListItemText>
|
|
||||||
{`${vocab.latin.grundform} ${lookedAt ? "✔" : ""}`}
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toReview = () => {
|
openReview = () => {
|
||||||
const { vocab, lookedAt, id } = this.props;
|
this.props.setReviewDialog(true);
|
||||||
|
}
|
||||||
|
closeReview = () => {
|
||||||
|
this.props.setReviewDialog(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Only go to the review if all vocabulary item have been looked at
|
openLeave = () => {
|
||||||
if (vocab.length === lookedAt.length) {
|
this.props.setLeaveDialog(true);
|
||||||
this.props.setLoading(true);
|
}
|
||||||
this.props.history.push(`/review/level/${id}`);
|
closeLeave = () => {
|
||||||
|
this.props.setLeaveDialog(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelLevel = () => {
|
||||||
|
this.closeLeave();
|
||||||
|
this.props.history.push("/dashboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
nextVocab = () => {
|
||||||
|
// Get the next vocab item
|
||||||
|
const { stepperIndex, vocab } = this.props;
|
||||||
|
|
||||||
|
// When we are about to go out of bounds, then we want to
|
||||||
|
// ask whether the user wants to review
|
||||||
|
if (stepperIndex + 1 >= vocab.length) {
|
||||||
|
this.openReview();
|
||||||
|
} else {
|
||||||
|
const newVocab = vocab[stepperIndex + 1];
|
||||||
|
|
||||||
|
// Set the stepperIndex and the currentVocab
|
||||||
|
this.props.setStepper(stepperIndex + 1);
|
||||||
|
this.props.setCurrentVocab(newVocab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevVocab = () => {
|
||||||
|
// Get the next vocab item
|
||||||
|
const { stepperIndex, vocab } = this.props;
|
||||||
|
|
||||||
|
// Prevent going out of bounds
|
||||||
|
if (stepperIndex - 1 < 0) {
|
||||||
|
this.openLeave();
|
||||||
|
} else {
|
||||||
|
const newVocab = vocab[stepperIndex - 1];
|
||||||
|
|
||||||
|
// Set the stepperIndex and the currentVocab
|
||||||
|
this.props.setStepper(stepperIndex - 1);
|
||||||
|
this.props.setCurrentVocab(newVocab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,25 +165,15 @@ const LevelPageWithRouter = withRouter(
|
|||||||
[VocabType.ADJEKTIV]: "Adjektiv",
|
[VocabType.ADJEKTIV]: "Adjektiv",
|
||||||
[VocabType.ADVERB]: "Adverb",
|
[VocabType.ADVERB]: "Adverb",
|
||||||
};
|
};
|
||||||
|
const { currentVocab, vocab, stepperIndex } = this.props;
|
||||||
const { currentVocab } = this.props;
|
|
||||||
return <div>
|
return <div>
|
||||||
<Grid container direction="row">
|
<Grid container justify="center">
|
||||||
<Grid item xs={3}>
|
<Grid item>
|
||||||
<List>
|
|
||||||
{this.props.vocab
|
|
||||||
.map(this.renderVocabListItem)}
|
|
||||||
<ListItem button onClick={this.toReview}>
|
|
||||||
<ListItemText>
|
|
||||||
Zur Übung
|
|
||||||
</ListItemText>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
</Grid>
|
|
||||||
<Grid item lg={7} xs={9}>
|
|
||||||
<Grid container direction="column">
|
<Grid container direction="column">
|
||||||
<Grid item style={{ margin: 12 }}>
|
<Grid item className="level-card">
|
||||||
<Card>
|
<Card
|
||||||
|
square={true}
|
||||||
|
style={{ margin: 12 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography gutterBottom variant="headline" component="h2">
|
<Typography gutterBottom variant="headline" component="h2">
|
||||||
{`${currentVocab.latin.grundform} (${vocabTypeToStr[currentVocab.type]})`}
|
{`${currentVocab.latin.grundform} (${vocabTypeToStr[currentVocab.type]})`}
|
||||||
@ -153,8 +183,72 @@ const LevelPageWithRouter = withRouter(
|
|||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<MobileStepper
|
||||||
|
steps={vocab.length + 1}
|
||||||
|
position="static"
|
||||||
|
activeStep={stepperIndex}
|
||||||
|
className="level-stepper"
|
||||||
|
nextButton={
|
||||||
|
<Button
|
||||||
|
onClick={this.nextVocab}
|
||||||
|
disabled={stepperIndex >= vocab.length + 1}>
|
||||||
|
<KeyboardArrowRight />
|
||||||
|
Nächste
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
backButton={
|
||||||
|
<Button onClick={this.prevVocab}>
|
||||||
|
<KeyboardArrowLeft />
|
||||||
|
{
|
||||||
|
stepperIndex === 0 ? (
|
||||||
|
`Abbrechen`
|
||||||
|
) : (
|
||||||
|
`Zurück`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
} />
|
||||||
|
|
||||||
|
{/*The leave and the "to review" dialog*/}
|
||||||
|
<Dialog
|
||||||
|
open={this.props.reviewDialog}
|
||||||
|
onClose={this.closeReview}>
|
||||||
|
<DialogTitle>Willst du zur Übung?</DialogTitle>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={this.toReview}>
|
||||||
|
Zur Übung
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={this.closeReview}>
|
||||||
|
Noch nicht
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
open={this.props.leaveDialog}
|
||||||
|
onClose={this.closeLeave}>
|
||||||
|
<DialogTitle>Willst du das Level abbrechen?</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Wenn du jetzt abbricht, dann geht dein Fortschritt
|
||||||
|
in diesem Level verloren.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={this.closeLeave}>
|
||||||
|
Zurück zum Level
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={this.cancelLevel}>
|
||||||
|
Level abbrechen
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ interface IState {
|
|||||||
lookedAt: number[];
|
lookedAt: number[];
|
||||||
vocab: IVocab[];
|
vocab: IVocab[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
stepper: number;
|
||||||
|
leaveDialog: boolean;
|
||||||
|
reviewDialog: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
levelList: {
|
levelList: {
|
||||||
@ -95,6 +98,9 @@ const initialState: IState = {
|
|||||||
lookedAt: [0],
|
lookedAt: [0],
|
||||||
vocab: [],
|
vocab: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
stepper: 0,
|
||||||
|
leaveDialog: false,
|
||||||
|
reviewDialog: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
levelList: {
|
levelList: {
|
||||||
@ -289,6 +295,24 @@ export function LateinicusApp(state: IState = initialState, action: any) {
|
|||||||
loading: action.state,
|
loading: action.state,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
case Actions.LEVEL_SET_STEPPER:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
level: Object.assign({}, state.level, {
|
||||||
|
stepper: action.index,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
case Actions.LEVEL_SET_REVIEW_DIAG:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
level: Object.assign({}, state.level, {
|
||||||
|
reviewDialog: action.state
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
case Actions.LEVEL_SET_LEAVE_DIAG:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
level: Object.assign({}, state.level, {
|
||||||
|
leaveDialog: action.state,
|
||||||
|
}),
|
||||||
|
});
|
||||||
default:
|
default:
|
||||||
// Ignore the initialization call to the reducer. By that we can
|
// Ignore the initialization call to the reducer. By that we can
|
||||||
// catch all actions that are not implemented
|
// catch all actions that are not implemented
|
||||||
|
Reference in New Issue
Block a user