feat: Implement the level view

This commit is contained in:
Alexander Polynomdivision 2018-09-06 20:05:21 +02:00
parent 8d56af4bb7
commit f4f686073f
4 changed files with 243 additions and 25 deletions

View File

@ -23,8 +23,10 @@ import AuthRoute from "../security/AuthRoute";
import Dashboard from "../pages/dashboard";
import LoginPage from "../pages/login";
import LessonsPage from "../pages/lessons";
import LevelPage from "../pages/level";
import { ILesson } from "../models/lesson";
import { IVocab } from "../models/vocab";
interface IState {
loggedIn: boolean;
@ -38,13 +40,14 @@ export default class Application extends React.Component<{}, IState> {
this.state = {
loggedIn: false,
drawerOpen: false,
};
this.login = this.login.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
}
lessons(): ILesson[] {
getLessons(): ILesson[] {
// TODO: Actually fetch them from somewhere
const lessons = [{
name: "Der Bauer auf dem Feld",
@ -61,7 +64,7 @@ export default class Application extends React.Component<{}, IState> {
return lessons;
}
learners(): ILearner[] {
getLearners(): ILearner[] {
return [{
username: "Polynomdivision",
level: 5,
@ -77,6 +80,36 @@ export default class Application extends React.Component<{}, IState> {
}];
}
getNextLesson(): ILesson {
// TODO: Actually fetch data
return {
name: "???",
desc: "Warum schreibe ich überhaupt was?dsd dddddddddddddddddddddd",
level: 2,
done: false,
};
}
getLevelVocab(id: string): IVocab[] {
// TODO: Actually implement this
// TODO: Don't fetch this when it was already fetched once.
return [{
latin: "Veni",
german: "<Wortbedeutung>",
id: 0
}, {
latin: "Vidi",
german: "<Wortbedeutung>",
id: 1
}, {
latin: "Vici",
german: "<Wortbedeutung>",
hint: "Wird \"Viki\" und nicht \"Vichi\" ausgesprochen",
mnemonic: "Merk dir das Wort mit Caesars berühmten Worten: \"Veni Vidi Vici\"; Er kam, sah und siegte",
id: 2
}] as IVocab[];
}
login(username: string, password: string): Promise<boolean> {
return new Promise((res, rej) => {
// TODO
@ -146,7 +179,9 @@ export default class Application extends React.Component<{}, IState> {
<ListItemText primary="Widerholen" />
</ListItem>
<ListItem button>
<ListItemText primary="Levelübersicht" />
<ListItemText>
Levelübersicht
</ListItemText>
</ListItem>
<Divider />
@ -168,11 +203,21 @@ export default class Application extends React.Component<{}, IState> {
<AuthRoute
isAuth={this.isAuthenticated}
path="/dashboard"
component={() => <Dashboard lessons={this.lessons()} learners={this.learners()} />} />
component={() => <Dashboard nextLesson={this.getNextLesson} learners={this.getLearners()} />} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/lessons"
component={() => <LessonsPage lessons={this.lessons()} />} />
path="/levelList"
component={() => <LessonsPage lessons={this.getLessons()} />} />
{/*We cannot use AuthRoute here, because match is undefined otherwise*/}
<Route
path="/level/:id"
component={({ match }) => {
if (this.isAuthenticated()) {
return <LevelPage id={match.params.id} levelVocab={this.getLevelVocab} />;
} else {
return <Redirect to="/login" />;
}
}} />
</div>
</div>
</BrowserRouter>;

9
src/models/vocab.ts Normal file
View File

@ -0,0 +1,9 @@
export interface IVocab {
german: string;
latin: string;
hint?: string;
mnemonic?: string;
// This number is lesson specific
id: number;
};

View File

@ -1,5 +1,7 @@
import * as React from "react";
import { Redirect } from "react-router";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
@ -11,35 +13,69 @@ import { ILesson } from "../models/lesson";
import { ILearner } from "../models/learner";
interface IProps {
lessons: ILesson[];
nextLesson: () => ILesson;
learners: ILearner[];
}
export default class Dashboard extends React.Component<{}> {
interface IState {
toLevel: number;
}
export default class Dashboard extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
toLevel: -1;
};
}
render() {
const small = window.matchMedia("(max-width: 700px)").matches;
const direction = small ? "column" : "row";
return <Grid container direction={direction} spacing={16}>
<Grid item lg={4}>
<Paper className="paper">
N&auml;chstes Level
</Paper>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
<Typography variant="title" component="p">
Rangliste: Top 10
const lesson = this.props.nextLesson();
return <div>
{
this.state.toLevel !== -1 ? (
<Redirect to={`/level/${this.state.toLevel}`} />
) : undefined
}
<Grid container direction={direction} spacing={16}>
<Grid item lg={4}>
<Paper className="paper">
<Typography variant="title">{`Level ${lesson.level}`}</Typography>
<Typography variant="title" component="p">{lesson.name}</Typography>
<br />
<Typography component="p">
{lesson.desc}
</Typography>
<Button className="lesson-card-btn"
onClick={() => {
this.setState({
toLevel: lesson.level,
});
}}>
Zum Level
</Button>
</Paper>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
<Typography variant="title" component="p">
Rangliste: Top 10
</Typography>
<Scoreboard learners={this.props.learners.slice(0, 10)} />
<Scoreboard learners={this.props.learners.slice(0, 10)} />
</Paper>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
Some stuff
</Paper>
</Grid>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
Some stuff
</Paper>
</Grid>
</Grid>;
</div>;
}
};

128
src/pages/level.tsx Normal file
View File

@ -0,0 +1,128 @@
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 Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import { IVocab } from "../models/vocab";
interface IProps {
id: string;
levelVocab: (id: string) => IVocab[];
};
interface IState {
currentVocab: IVocab;
lookedAt: number[];
};
export default class LevelPage extends React.Component<IProps, IState> {
private uid = 0;
constructor(props: any) {
super(props);
this.state = {
currentVocab: this.props.levelVocab(this.props.id)[0],
lookedAt: [0],
};
}
genUID = (): string => {
return "LEVELPAGE" + this.uid++;
}
renderVocabListItem = (vocab: IVocab): any => {
// Check if the vocab was already looked at
const lookedAt = this.state.lookedAt.find((el) => el === vocab.id) || vocab.id === 0;
// TODO: Actually update the "Vocab View" when clicking
return <ListItem button key={this.genUID()} onClick={() => {
// Prevent the user from using too much memory by always clicking on the elements
// Show the clicked at vocab word
this.setState({
currentVocab: vocab,
lookedAt: this.state.lookedAt.concat(vocab.id),
});
}}>
<ListItemText>
{`${vocab.latin} ${lookedAt ? "✔" : ""}`}
</ListItemText>
</ListItem>;
}
render() {
const { currentVocab } = this.state;
return <div>
<Grid container direction="row">
<Grid item xs={3}>
<List>
{this.props.levelVocab(this.props.id).map(this.renderVocabListItem)}
{/* TODO*/}
<ListItem button onClick={() => alert("Übung")}>
<ListItemText>
Zur Übung
</ListItemText>
</ListItem>
</List>
</Grid>
<Grid item lg={7} xs={9}>
<Grid container direction="column">
<Grid item style={{ margin: 12 }}>
<Card>
<CardContent>
<Typography gutterBottom variant="headline" component="h2">
{currentVocab.latin}
</Typography>
<Typography gutterBottom variant="headline" component="h3">
{currentVocab.german}
</Typography>
{
currentVocab.hint ? (
<div style={{
border: "dashed",
borderColor: "red",
padding: 12,
}}>
<Typography variant="subheading" component="p">
<b>Tipp:</b>
</Typography>
<Typography variant="body2">
{currentVocab.hint}
</Typography>
</div>
) : undefined
}
{
currentVocab.mnemonic ? (
<div style={{
border: "dashed",
borderColor: "#f1c40f",
marginTop: 12,
padding: 12,
}}>
<Typography variant="subheading" component="p">
<b>Eselsbrücke:</b>
</Typography>
<Typography variant="body2">
{currentVocab.mnemonic}
</Typography>
</div>
) : undefined
}
</CardContent>
{/*TODO: Maybe "next" and "prev" buttons?*/}
</Card>
</Grid>
</Grid>
</Grid>
</Grid>
</div>;
}
};