feat: Implement the level view
This commit is contained in:
parent
8d56af4bb7
commit
f4f686073f
@ -23,8 +23,10 @@ import AuthRoute from "../security/AuthRoute";
|
|||||||
import Dashboard from "../pages/dashboard";
|
import Dashboard from "../pages/dashboard";
|
||||||
import LoginPage from "../pages/login";
|
import LoginPage from "../pages/login";
|
||||||
import LessonsPage from "../pages/lessons";
|
import LessonsPage from "../pages/lessons";
|
||||||
|
import LevelPage from "../pages/level";
|
||||||
|
|
||||||
import { ILesson } from "../models/lesson";
|
import { ILesson } from "../models/lesson";
|
||||||
|
import { IVocab } from "../models/vocab";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
loggedIn: boolean;
|
loggedIn: boolean;
|
||||||
@ -38,13 +40,14 @@ export default class Application extends React.Component<{}, IState> {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
|
drawerOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.login = this.login.bind(this);
|
this.login = this.login.bind(this);
|
||||||
this.isAuthenticated = this.isAuthenticated.bind(this);
|
this.isAuthenticated = this.isAuthenticated.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
lessons(): ILesson[] {
|
getLessons(): ILesson[] {
|
||||||
// TODO: Actually fetch them from somewhere
|
// TODO: Actually fetch them from somewhere
|
||||||
const lessons = [{
|
const lessons = [{
|
||||||
name: "Der Bauer auf dem Feld",
|
name: "Der Bauer auf dem Feld",
|
||||||
@ -61,7 +64,7 @@ export default class Application extends React.Component<{}, IState> {
|
|||||||
return lessons;
|
return lessons;
|
||||||
}
|
}
|
||||||
|
|
||||||
learners(): ILearner[] {
|
getLearners(): ILearner[] {
|
||||||
return [{
|
return [{
|
||||||
username: "Polynomdivision",
|
username: "Polynomdivision",
|
||||||
level: 5,
|
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> {
|
login(username: string, password: string): Promise<boolean> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
// TODO
|
// TODO
|
||||||
@ -146,7 +179,9 @@ export default class Application extends React.Component<{}, IState> {
|
|||||||
<ListItemText primary="Widerholen" />
|
<ListItemText primary="Widerholen" />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem button>
|
<ListItem button>
|
||||||
<ListItemText primary="Levelübersicht" />
|
<ListItemText>
|
||||||
|
Levelübersicht
|
||||||
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -168,11 +203,21 @@ export default class Application extends React.Component<{}, IState> {
|
|||||||
<AuthRoute
|
<AuthRoute
|
||||||
isAuth={this.isAuthenticated}
|
isAuth={this.isAuthenticated}
|
||||||
path="/dashboard"
|
path="/dashboard"
|
||||||
component={() => <Dashboard lessons={this.lessons()} learners={this.learners()} />} />
|
component={() => <Dashboard nextLesson={this.getNextLesson} learners={this.getLearners()} />} />
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
isAuth={this.isAuthenticated}
|
isAuth={this.isAuthenticated}
|
||||||
path="/lessons"
|
path="/levelList"
|
||||||
component={() => <LessonsPage lessons={this.lessons()} />} />
|
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>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>;
|
</BrowserRouter>;
|
||||||
|
9
src/models/vocab.ts
Normal file
9
src/models/vocab.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface IVocab {
|
||||||
|
german: string;
|
||||||
|
latin: string;
|
||||||
|
hint?: string;
|
||||||
|
mnemonic?: string;
|
||||||
|
|
||||||
|
// This number is lesson specific
|
||||||
|
id: number;
|
||||||
|
};
|
@ -1,5 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
|
||||||
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 Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
@ -11,35 +13,69 @@ import { ILesson } from "../models/lesson";
|
|||||||
import { ILearner } from "../models/learner";
|
import { ILearner } from "../models/learner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
lessons: ILesson[];
|
nextLesson: () => ILesson;
|
||||||
learners: ILearner[];
|
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() {
|
render() {
|
||||||
const small = window.matchMedia("(max-width: 700px)").matches;
|
const small = window.matchMedia("(max-width: 700px)").matches;
|
||||||
const direction = small ? "column" : "row";
|
const direction = small ? "column" : "row";
|
||||||
|
|
||||||
return <Grid container direction={direction} spacing={16}>
|
const lesson = this.props.nextLesson();
|
||||||
<Grid item lg={4}>
|
|
||||||
<Paper className="paper">
|
return <div>
|
||||||
Nächstes Level
|
{
|
||||||
</Paper>
|
this.state.toLevel !== -1 ? (
|
||||||
</Grid>
|
<Redirect to={`/level/${this.state.toLevel}`} />
|
||||||
<Grid item lg={4}>
|
) : undefined
|
||||||
<Paper className="paper">
|
}
|
||||||
<Typography variant="title" component="p">
|
<Grid container direction={direction} spacing={16}>
|
||||||
Rangliste: Top 10
|
<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>
|
</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>
|
</Paper>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item lg={4}>
|
</div>;
|
||||||
<Paper className="paper">
|
|
||||||
Some stuff
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
128
src/pages/level.tsx
Normal file
128
src/pages/level.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
};
|
Reference in New Issue
Block a user