refactor: MONOREPO

This commit is contained in:
Alexander Polynomdivision
2018-09-20 17:38:12 +02:00
parent 4c9e328ad0
commit 909149fdc7
50 changed files with 222 additions and 3 deletions

View File

@@ -0,0 +1,158 @@
import * as React from "react";
import { Link } from "react-router-dom";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import Button from "@material-ui/core/Button";
import SwipeableDrawer from "@material-ui/core/SwipeableDrawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import Avatar from "@material-ui/core/Avatar";
import MenuIcon from "@material-ui/icons/Menu";
import SettingsIcon from "@material-ui/icons/Settings";
import PersonIcon from "@material-ui/icons/Person";
import InfoIcon from "@material-ui/icons/Info";
import HomeIcon from "@material-ui/icons/Home";
import BookIcon from "@material-ui/icons/Book";
import ViewWeekIcon from "@material-ui/icons/ViewWeek";
import { IUser } from "../models/user";
interface IProps {
logout: () => void;
user: IUser;
open: boolean;
showButton: boolean;
authenticated: boolean;
setDrawer: (state: boolean) => void;
};
export default class Drawer extends React.Component<IProps> {
openDrawer = () => {
this.props.setDrawer(true);
}
closeDrawer = () => {
this.props.setDrawer(false);
}
render() {
return (
<div>
<AppBar position="static">
<Toolbar>
{
(this.props.authenticated && this.props.showButton) ? (
<IconButton
color="inherit"
onClick={this.openDrawer}>
<MenuIcon />
</IconButton>
) : undefined
}
<Typography className="flex" variant="title" color="inherit">
Lateinicus
</Typography>
{
this.props.authenticated ? (
<Button color="inherit">
{`${this.props.user.score} / 200`}
</Button>
) : undefined
}
</Toolbar>
</AppBar>
<SwipeableDrawer
anchor="left"
open={this.props.open}
onClose={this.closeDrawer}
onOpen={this.openDrawer}>
<List component="nav">
<ListItem>
<Avatar alt="{Username}" style={{ width: 80, height: 80 }} src="https://avatarfiles.alphacoders.com/105/105250.jpg" />
<ListItemText primary={"{{ PLACEHOLDER }}"} />
</ListItem>
<Divider />
<ListItem button>
<ListItemIcon>
<PersonIcon />
</ListItemIcon>
<ListItemText primary="Profil" />
</ListItem>
<ListItem button>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary="Einstellungen" />
</ListItem>
<Divider />
<ListItem
component={Link}
to="/dashboard"
onClick={this.closeDrawer}
button>
<ListItemIcon>
<HomeIcon />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem
component={Link}
to="/review/queue"
onClick={this.closeDrawer}
button>
<ListItemIcon>
<BookIcon />
</ListItemIcon>
<ListItemText>
Vokabeln üben
</ListItemText>
</ListItem>
<ListItem
component={Link}
to="/levelList"
onClick={this.closeDrawer}
button>
<ListItemIcon>
<ViewWeekIcon />
</ListItemIcon>
<ListItemText>
Levelübersicht
</ListItemText>
</ListItem>
<Divider />
<ListItem button onClick={() => {
this.closeDrawer();
this.props.logout();
}}>
<ListItemText>
Abmelden
</ListItemText>
</ListItem>
<Divider />
<ListItem button onClick={() => window.location = "https://gitlab.com/Polynomdivision/Lateinicus/tree/master"}>
<ListItemIcon>
<InfoIcon />
</ListItemIcon>
<ListItemText primary="Über" />
</ListItem>
</List>
</SwipeableDrawer>
</div>
);
}
};

View File

@@ -0,0 +1,38 @@
import * as React from "react";
import Table from "@material-ui/core/Table";
import TableHead from "@material-ui/core/TableHead";
import TableBody from "@material-ui/core/TableBody";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import { IReviewMetadata } from "../models/review";
interface IProps {
reviewMeta: () => IReviewMetadata;
}
export default class SummaryTable extends React.Component<IProps> {
render() {
const meta = this.props.reviewMeta();
return <Table>
<TableHead>
<TableRow>
<TableCell>Antworten</TableCell>
<TableCell>Anzahl</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Korrekt</TableCell>
<TableCell numeric>{meta.correct}</TableCell>
</TableRow>
<TableRow>
<TableCell>Falsch</TableCell>
<TableCell numeric>{meta.wrong}</TableCell>
</TableRow>
</TableBody>
</Table>;
}
}

View File

@@ -0,0 +1,318 @@
import * as React from "react";
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import AuthRoute from "../security/AuthRoute";
import { setSessionToken, removeSessionToken } from "../security/Token";
import Dashboard from "../pages/dashboard";
import LoginPage from "../containers/LoginPage";
import LevelListPage from "../containers/LevelList";
import LevelPage from "../containers/LevelPage";
import ReviewPage from "../containers/Review";
import SummaryPage from "../containers/SummaryPage";
import WelcomePage from "../pages/intro";
import Drawer from "../containers/Drawer";
import { BACKEND_URL } from "../config";
import { ILevel } from "../models/level";
import { ILearner } from "../models/learner";
import { IVocab, VocabType } from "../models/vocab";
import { IReviewMetadata, ReviewType } from "../models/review";
import { IUser } from "../models/user";
interface IProps {
authenticated: boolean;
setAuthenticated: (status: boolean) => void;
setUser: (user: IUser) => void;
};
// TODO: Replace the sessionStorage with localStorage?
// TODO: Cache API-Calls
// TODO: When mounting without a login, check if the sessionToken is still valid
export default class Application extends React.Component<IProps> {
getLevels(): Promise<ILevel[]> {
console.log("STUB: Application::getLevels");
return new Promise((res, rej) => {
// TODO: Actually fetch them from somewhere
setTimeout(() => {
const levels = [{
name: "Der Bauer auf dem Feld",
desc: "So fängt alles an: Du bist ein einfacher Bauer und musst dich die Karriereleiter mit deinen freshen Latein-Skills hinaufarbeiten",
level: 1,
done: true,
}, {
name: "???",
desc: "Warum schreibe ich überhaupt was?dsd dddddddddddddddddddddd",
level: 2,
done: false,
}];
res(levels);
}, 2000);
});
}
getLastReview = (): IReviewMetadata => {
console.log("STUB: Application::getLastReview");
// TODO: Actually fetch this
// TODO: Stub
return {} as IReviewMetadata;
}
setLastReview = (meta: IReviewMetadata) => {
console.log("STUB: Application::setLastReview");
// TODO: Send this to the server
this.setState({
lastReview: meta,
});
}
getReviewQueue = (): Promise<IVocab[]> => {
console.log("STUB: Application::getReviewQueue");
// TODO: Implement
return new Promise((res, rej) => {
setTimeout(() => {
res([
{
german: ["Wein"],
hint: "Worte auf '-um' sind meistens NeutrUM",
type: VocabType.NOMEN,
latin: {
grundform: "Vinum",
genitiv: "Vini",
genus: "Neutrum"
},
id: 0
}/* , {
* 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",
* type: VocabType.NOMEN,
* id: 2
}, {
* latin: "fuga",
* german: "Flucht",
* hint: "Worte auf \"-a\" sind FeminA",
* type: VocabType.NOMEN,
* id: 3
} */
]);
}, 2000);
});
}
getLearners(): ILearner[] {
console.log("STUB: Application::getLearners");
// TODO: Implement
return [{
username: "Polynomdivision",
level: 5,
score: 400,
}, {
username: "Polynomdivision2",
level: 3,
score: 500,
}, {
username: "Der eine Typ",
level: 7,
score: 100,
}];
}
getTopTenLearners(): ILearner[] {
console.log("STUB: Application::getTopTenLearners");
// TODO: Implement
return [{
username: "Polynomdivision",
level: 5,
score: 400,
}, {
username: "Polynomdivision2",
level: 3,
score: 500,
}, {
username: "Der eine Typ",
level: 7,
score: 100,
}];
}
getNextLevel(): ILevel {
console.log("STUB: Application::getNextLevel");
// TODO: Actually fetch data
return {
name: "???",
desc: "Warum schreibe ich überhaupt was?dsd dddddddddddddddddddddd",
level: 2,
done: false,
};
}
getLevelVocab(id: number): Promise<IVocab[]> {
console.log("STUB: Application::getLevelVocab");
// TODO: Actually implement this
return new Promise((res, rej) => {
setTimeout(() => {
res([{
german: ["Wein"],
hint: "Worte auf '-um' sind meistens NeutrUM",
type: VocabType.NOMEN,
latin: {
grundform: "Vinum",
genitiv: "Vini",
genus: "Neutrum"
},
id: 0
}/* , {
* 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",
* type: VocabType.NOMEN,
* id: 2
}, {
* latin: "fuga",
* german: "Flucht",
* hint: "Worte auf \"-a\" sind FeminA",
* type: VocabType.NOMEN,
* id: 3
} */]);
}, 2000);
});
}
login = (username: string, password: string): Promise<IUser | {}> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/login`, {
method: "POST",
headers: new Headers({
"Content-Type": "application/json",
}),
body: JSON.stringify({
// NOTE: We will force HTTPS, so this should not be a
// problem
username,
password,
}),
}).then(data => data.json())
.then(resp => {
if (resp.error === "0") {
// Successful login
this.props.setUser(resp.data);
setSessionToken(window, resp.data.sessionToken);
res(resp.data);
} else {
rej({});
}
});
});
}
logout = () => {
// TODO: Tell the server that we're logging ourselves out
removeSessionToken(window);
this.props.setAuthenticated(false);
}
// Checks whether the user is logged in
isAuthenticated = () => {
// TODO: Security?
// TODO: Implement
return this.props.authenticated;
}
render() {
// TODO: Show a spinner before mounting the routes, so that we can
// check if were authenticated before doing any requests
return <BrowserRouter
basename="/app/">
<div className="flex" >
<Drawer logout={this.logout} />
<div className="content">
<Route exact path="/" component={() => <Redirect to="/login" />} />
<Route exact path="/login" component={() => {
return <LoginPage login={this.login} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/dashboard"
component={() => {
return <Dashboard
nextLevel={this.getNextLevel}
getLastReview={this.getLastReview}
getTopTen={this.getTopTenLearners} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/welcome"
component={() => {
return <WelcomePage />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/levelList"
component={() => <LevelListPage
getLevels={this.getLevels} />} />
{/*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}
drawerButtonState={this.drawerButtonState}
setLastReview={this.setLastReview} />;
} else {
return <Redirect to="/login" />;
}
}} />
<Route
path="/review/level/:id"
component={({ match }) => {
if (this.isAuthenticated()) {
return <ReviewPage
reviewType={ReviewType.LEVEL}
levelId={match.params.id}
vocabByLevel={this.getLevelVocab}
setLastReview={this.setLastReview} />;
} else {
return <Redirect to="/login" />;
}
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/queue"
component={() => {
return <ReviewPage
reviewType={ReviewType.QUEUE}
vocabByQueue={this.getReviewQueue}
drawerButtonState={this.drawerButtonState}
setLastReview={this.setLastReview} />;
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/summary"
component={() => {
return <SummaryPage />
}} />
</div>
</div >
</BrowserRouter >;
}
};

View File

@@ -0,0 +1,7 @@
import * as React from "react";
export default class Loader extends React.Component<{}> {
render() {
return <div className="loader" />;
}
}

View File

@@ -0,0 +1,73 @@
import * as React from "react";
import Table from "@material-ui/core/Table";
import TableHead from "@material-ui/core/TableHead";
import TableBody from "@material-ui/core/TableBody";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import Typography from "@material-ui/core/Typography";
import { ILearner } from "../models/learner";
interface IProps {
topTen: ILearner[];
}
export default class Scoreboard extends React.Component<IProps> {
private unique = 0;
private nr = 1;
constructor(props: any) {
super(props);
this.genId = this.genId.bind(this);
this.tableRow = this.tableRow.bind(this);
}
genId() {
return "SCOREBOARD" + this.unique++;
}
tableRow(learner: ILearner) {
return <TableRow key={this.genId()}>
<TableCell>
<Typography variant="title" component="b">
{this.nr++}
</Typography>
</TableCell>
<TableCell>
<Typography component="b">{learner.username}</Typography>
</TableCell>
{/*<TableCell numeric>{learner.level}</TableCell>*/}
{/* To make this fit on both mobile and desktop, we don't use
numeric, as it would otherwise look weir otherwise look weird */}
<TableCell>{learner.score}</TableCell>
</TableRow>
}
render() {
const sortedLearners = this.props.topTen.sort((a, b) => {
if (a.score > b.score) {
return -1;
} else if (a.score < b.score) {
return 1;
}
return 0;
});
return <Table padding="none">
<TableHead>
<TableRow>
<TableCell>#</TableCell>
<TableCell>User</TableCell>
{/*<TableCell>Level</TableCell>*/}
<TableCell>Punktzahl</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedLearners.map(this.tableRow)}
</TableBody>
</Table>;
}
}