fix: Transition the drawer to redux

This commit is contained in:
Alexander Polynomdivision 2018-09-18 18:06:08 +02:00
parent 2cadcec370
commit 2f7e468285
8 changed files with 268 additions and 158 deletions

37
package-lock.json generated
View File

@ -7880,8 +7880,12 @@
"lodash": { "lodash": {
"version": "4.17.10", "version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
"dev": true },
"lodash-es": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz",
"integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q=="
}, },
"lodash.clone": { "lodash.clone": {
"version": "4.5.0", "version": "4.5.0",
@ -10521,6 +10525,19 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-redux": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz",
"integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==",
"requires": {
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.0.0",
"lodash": "^4.17.5",
"lodash-es": "^4.17.5",
"loose-envify": "^1.1.0",
"prop-types": "^15.6.0"
}
},
"react-router": { "react-router": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
@ -10691,6 +10708,22 @@
} }
} }
}, },
"redux": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz",
"integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==",
"requires": {
"loose-envify": "^1.1.0",
"symbol-observable": "^1.2.0"
},
"dependencies": {
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
}
}
},
"regenerate": { "regenerate": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",

View File

@ -14,7 +14,9 @@
"@material-ui/icons": "^2.0.3", "@material-ui/icons": "^2.0.3",
"react": "^16.4.2", "react": "^16.4.2",
"react-dom": "^16.4.2", "react-dom": "^16.4.2",
"react-router-dom": "^4.3.1" "react-redux": "^5.0.7",
"react-router-dom": "^4.3.1",
"redux": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^23.3.2", "@types/jest": "^23.3.2",

15
src/actions/index.ts Normal file
View File

@ -0,0 +1,15 @@
export const SET_DRAWER = "SET_DRAWER";
export function setDrawer(state: boolean) {
return {
type: SET_DRAWER,
show: state,
};
};
export const SET_DRAWER_BUTTON = "SET_DRAWER_BUTTON";
export function setDrawerButton(state: boolean) {
return {
type: SET_DRAWER_BUTTON,
show: state,
};
};

146
src/components/Drawer.tsx Normal file
View File

@ -0,0 +1,146 @@
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 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";
interface IProps {
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>
</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={() => {
// Remove the auth token from the SessionStorage
// window.sessionStorage.removeItem("authKey");
// TODO: Perform 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

@ -1,25 +1,6 @@
import * as React from "react"; import * as React from "react";
import AppBar from "@material-ui/core/AppBar"; import { BrowserRouter, Route, Redirect } from "react-router-dom";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
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 { BrowserRouter, Route, Redirect, Link } from "react-router-dom";
import AuthRoute from "../security/AuthRoute"; import AuthRoute from "../security/AuthRoute";
@ -31,6 +12,8 @@ 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";
import Drawer from "../containers/Drawer";
import { BACKEND_URL } from "../config"; import { BACKEND_URL } from "../config";
import { ILevel } from "../models/level"; import { ILevel } from "../models/level";
@ -39,20 +22,10 @@ import { IVocab, VocabType } from "../models/vocab";
import { IReviewMetadata, ReviewType } from "../models/review"; import { IReviewMetadata, ReviewType } from "../models/review";
import { IUser } from "../models/user"; import { IUser } from "../models/user";
interface IState {
loggedIn: boolean;
user: IUser | {};
lastReview: IReviewMetadata;
drawerOpen: boolean;
showDrawerButton: boolean;
}
// TODO: Replace the sessionStorage with localStorage? // TODO: Replace the sessionStorage with localStorage?
// TODO: Cache API-Calls // TODO: Cache API-Calls
// TODO: When mounting without a login, check if the sessionToken is still valid // TODO: When mounting without a login, check if the sessionToken is still valid
export default class Application extends React.Component<{}, IState> { export default class Application extends React.Component<{}> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -61,20 +34,6 @@ export default class Application extends React.Component<{}, IState> {
// TODO // TODO
const loggedIn = authKey !== null; const loggedIn = authKey !== null;
// TODO: Fetch the last review
this.state = {
loggedIn,
user: {},
lastReview: {
correct: 0,
wrong: 0,
},
drawerOpen: false,
showDrawerButton: true,
};
this.login = this.login.bind(this); this.login = this.login.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this); this.isAuthenticated = this.isAuthenticated.bind(this);
} }
@ -101,7 +60,8 @@ export default class Application extends React.Component<{}, IState> {
console.log("STUB: Application::getLastReview"); console.log("STUB: Application::getLastReview");
// TODO: Actually fetch this // TODO: Actually fetch this
return this.state.lastReview; // TODO: Stub
return {} as IReviewMetadata;
} }
setLastReview = (meta: IReviewMetadata) => { setLastReview = (meta: IReviewMetadata) => {
@ -256,7 +216,8 @@ export default class Application extends React.Component<{}, IState> {
// Checks whether the user is logged in // Checks whether the user is logged in
isAuthenticated() { isAuthenticated() {
// TODO: Security? // TODO: Security?
return this.state.loggedIn === true; // TODO: Implement
return true;
} }
closeDrawer = () => { closeDrawer = () => {
@ -279,113 +240,7 @@ export default class Application extends React.Component<{}, IState> {
return <BrowserRouter return <BrowserRouter
basename="/app/"> basename="/app/">
<div className="flex"> <div className="flex">
<AppBar position="static"> <Drawer />
<Toolbar>
{
(this.isAuthenticated() && this.state.showDrawerButton) ? (
<IconButton
color="inherit"
onClick={() => this.setState({ drawerOpen: true })}>
<MenuIcon />
</IconButton>
) : undefined
}
<Typography className="flex" variant="title" color="inherit">
Lateinicus
</Typography>
</Toolbar>
</AppBar>
<SwipeableDrawer
anchor="left"
open={this.state.drawerOpen}
onClose={() => this.setState({ drawerOpen: false, })}
onOpen={() => this.setState({ drawerOpen: true })}>
<List component="nav">
<ListItem>
<Avatar alt="{Username}" style={{ width: 80, height: 80 }} src="https://avatarfiles.alphacoders.com/105/105250.jpg" />
<ListItemText primary={this.state.user.username} />
</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={() => {
// Remove the auth token from the SessionStorage
window.sessionStorage.removeItem("authKey");
// Set the loggedIn State to false
this.setState({
loggedIn: false,
// In case the drawer was open
drawerOpen: false,
});
}}>
<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 className="content"> <div className="content">
<Route exact path="/" component={() => <Redirect to="/login" />} /> <Route exact path="/" component={() => <Redirect to="/login" />} />
<Route exact path="/login" component={() => { <Route exact path="/login" component={() => {

22
src/containers/Drawer.ts Normal file
View File

@ -0,0 +1,22 @@
import { connect } from "react-redux";
import { setDrawer } from "../actions";
import Drawer from "../components/Drawer";
const mapStateToProps = state => {
return {
open: state.drawer,
authenticated: state.authenticated,
showButton: state.drawerButton,
};
};
const mapDispatchToProps = dispatch => {
return {
setDrawer: (show: boolean) => dispatch(setDrawer(show)),
};
};
const DrawerContainer = connect(mapStateToProps,
mapDispatchToProps)(Drawer);
export default DrawerContainer;

View File

@ -1,6 +1,17 @@
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { LateinicusApp } from "./reducers";
import Application from "./components/app"; import Application from "./components/app";
ReactDOM.render(<Application />, document.getElementById("app")); const store = createStore(LateinicusApp);
ReactDOM.render((
<Provider store={store}>
<Application />
</Provider>
), document.getElementById("app"));

26
src/reducers/index.ts Normal file
View File

@ -0,0 +1,26 @@
import * as Actions from "../actions";
const initialState = {
// Show the drawer?
drawer: false,
// Should we show the button to open the drawer?
drawerButton: true,
// Is the user authenticated?
// TODO: Set this to false
authenticated: true,
};
export function LateinicusApp(state = initialState, action: any) {
switch (action.type) {
case Actions.SET_DRAWER:
return Object.assign({}, state, {
drawer: action.show,
});
case Actions.SET_DRAWER_BUTTON:
return Object.assign({}, state, {
drawerButton: action.show,
});
default:
return state;
}
};