cmd, dashboard: dashboard using React, Material-UI, Recharts (#15393)
* cmd, dashboard: dashboard using React, Material-UI, Recharts * cmd, dashboard, metrics: initial proof of concept dashboard * dashboard: delete blobs * dashboard: gofmt -s -w . * dashboard: minor text and code polishes
This commit is contained in:
committed by
Péter Szilágyi
parent
984c25ac40
commit
ba62215d9e
52
dashboard/assets/components/Common.jsx
Normal file
52
dashboard/assets/components/Common.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// isNullOrUndefined returns true if the given variable is null or undefined.
|
||||
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
|
||||
|
||||
export const LIMIT = {
|
||||
memory: 200, // Maximum number of memory data samples.
|
||||
traffic: 200, // Maximum number of traffic data samples.
|
||||
log: 200, // Maximum number of logs.
|
||||
};
|
||||
// The sidebar menu and the main content are rendered based on these elements.
|
||||
export const TAGS = (() => {
|
||||
const T = {
|
||||
home: { title: "Home", },
|
||||
chain: { title: "Chain", },
|
||||
transactions: { title: "Transactions", },
|
||||
network: { title: "Network", },
|
||||
system: { title: "System", },
|
||||
logs: { title: "Logs", },
|
||||
};
|
||||
// Using the key is circumstantial in some cases, so it is better to insert it also as a value.
|
||||
// This way the mistyping is prevented.
|
||||
for(let key in T) {
|
||||
T[key]['id'] = key;
|
||||
}
|
||||
return T;
|
||||
})();
|
||||
|
||||
export const DATA_KEYS = (() => {
|
||||
const DK = {};
|
||||
["memory", "traffic", "logs"].map(key => {
|
||||
DK[key] = key;
|
||||
});
|
||||
return DK;
|
||||
})();
|
||||
|
||||
// Temporary - taken from Material-UI
|
||||
export const DRAWER_WIDTH = 240;
|
169
dashboard/assets/components/Dashboard.jsx
Normal file
169
dashboard/assets/components/Dashboard.jsx
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withStyles} from 'material-ui/styles';
|
||||
|
||||
import SideBar from './SideBar.jsx';
|
||||
import Header from './Header.jsx';
|
||||
import Main from "./Main.jsx";
|
||||
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
|
||||
|
||||
// Styles for the Dashboard component.
|
||||
const styles = theme => ({
|
||||
appFrame: {
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
});
|
||||
|
||||
// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages.
|
||||
// When there is an incoming message, updates the page's content correspondingly.
|
||||
class Dashboard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
active: TAGS.home.id, // active menu
|
||||
sideBar: true, // true if the sidebar is opened
|
||||
memory: [],
|
||||
traffic: [],
|
||||
logs: [],
|
||||
shouldUpdate: {},
|
||||
};
|
||||
}
|
||||
|
||||
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
|
||||
componentDidMount() {
|
||||
this.reconnect();
|
||||
}
|
||||
|
||||
// reconnect establishes a websocket connection with the server, listens for incoming messages
|
||||
// and tries to reconnect on connection loss.
|
||||
reconnect = () => {
|
||||
const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
|
||||
|
||||
server.onmessage = event => {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (isNullOrUndefined(msg)) {
|
||||
return;
|
||||
}
|
||||
this.update(msg);
|
||||
};
|
||||
|
||||
server.onclose = () => {
|
||||
setTimeout(this.reconnect, 3000);
|
||||
};
|
||||
};
|
||||
|
||||
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
||||
update = msg => {
|
||||
console.log(msg);
|
||||
this.setState(prevState => {
|
||||
let newState = [];
|
||||
newState.shouldUpdate = {};
|
||||
const insert = (key, values, limit) => {
|
||||
newState[key] = [...prevState[key], ...values];
|
||||
while (newState[key].length > limit) {
|
||||
newState[key].shift();
|
||||
}
|
||||
newState.shouldUpdate[key] = true;
|
||||
};
|
||||
// (Re)initialize the state with the past data.
|
||||
if (!isNullOrUndefined(msg.history)) {
|
||||
const memory = DATA_KEYS.memory;
|
||||
const traffic = DATA_KEYS.traffic;
|
||||
newState[memory] = [];
|
||||
newState[traffic] = [];
|
||||
if (!isNullOrUndefined(msg.history.memorySamples)) {
|
||||
newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
||||
while (newState[memory].length > LIMIT.memory) {
|
||||
newState[memory].shift();
|
||||
}
|
||||
newState.shouldUpdate[memory] = true;
|
||||
}
|
||||
if (!isNullOrUndefined(msg.history.trafficSamples)) {
|
||||
newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
||||
while (newState[traffic].length > LIMIT.traffic) {
|
||||
newState[traffic].shift();
|
||||
}
|
||||
newState.shouldUpdate[traffic] = true;
|
||||
}
|
||||
}
|
||||
// Insert the new data samples.
|
||||
if (!isNullOrUndefined(msg.memory)) {
|
||||
insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory);
|
||||
}
|
||||
if (!isNullOrUndefined(msg.traffic)) {
|
||||
insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic);
|
||||
}
|
||||
if (!isNullOrUndefined(msg.log)) {
|
||||
insert(DATA_KEYS.logs, [msg.log], LIMIT.log);
|
||||
}
|
||||
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
// The change of the active label on the SideBar component will trigger a new render in the Main component.
|
||||
changeContent = active => {
|
||||
this.setState(prevState => prevState.active !== active ? {active: active} : {});
|
||||
};
|
||||
|
||||
openSideBar = () => {
|
||||
this.setState({sideBar: true});
|
||||
};
|
||||
|
||||
closeSideBar = () => {
|
||||
this.setState({sideBar: false});
|
||||
};
|
||||
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.appFrame}>
|
||||
<Header
|
||||
opened={this.state.sideBar}
|
||||
open={this.openSideBar}
|
||||
/>
|
||||
<SideBar
|
||||
opened={this.state.sideBar}
|
||||
close={this.closeSideBar}
|
||||
changeContent={this.changeContent}
|
||||
/>
|
||||
<Main
|
||||
opened={this.state.sideBar}
|
||||
active={this.state.active}
|
||||
memory={this.state.memory}
|
||||
traffic={this.state.traffic}
|
||||
logs={this.state.logs}
|
||||
shouldUpdate={this.state.shouldUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Dashboard);
|
87
dashboard/assets/components/Header.jsx
Normal file
87
dashboard/assets/components/Header.jsx
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {withStyles} from 'material-ui/styles';
|
||||
import AppBar from 'material-ui/AppBar';
|
||||
import Toolbar from 'material-ui/Toolbar';
|
||||
import Typography from 'material-ui/Typography';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import MenuIcon from 'material-ui-icons/Menu';
|
||||
|
||||
import {DRAWER_WIDTH} from './Common.jsx';
|
||||
|
||||
// Styles for the Header component.
|
||||
const styles = theme => ({
|
||||
appBar: {
|
||||
position: 'absolute',
|
||||
transition: theme.transitions.create(['margin', 'width'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
},
|
||||
appBarShift: {
|
||||
marginLeft: DRAWER_WIDTH,
|
||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
||||
transition: theme.transitions.create(['margin', 'width'], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: 12,
|
||||
marginRight: 20,
|
||||
},
|
||||
hide: {
|
||||
display: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
// Header renders a header, which contains a sidebar opener icon when that is closed.
|
||||
class Header extends Component {
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
|
||||
return (
|
||||
<AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}>
|
||||
<Toolbar disableGutters={!this.props.opened}>
|
||||
<IconButton
|
||||
color="contrast"
|
||||
aria-label="open drawer"
|
||||
onClick={this.props.open}
|
||||
className={classNames(classes.menuButton, this.props.opened && classes.hide)}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography type="title" color="inherit" noWrap>
|
||||
Go Ethereum Dashboard
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
open: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Header);
|
89
dashboard/assets/components/Home.jsx
Normal file
89
dashboard/assets/components/Home.jsx
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Grid from 'material-ui/Grid';
|
||||
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts';
|
||||
import {withTheme} from 'material-ui/styles';
|
||||
|
||||
import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx";
|
||||
|
||||
// ChartGrid renders a grid container for responsive charts.
|
||||
// The children are Recharts components extended with the Material-UI's xs property.
|
||||
class ChartGrid extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Grid container spacing={this.props.spacing}>
|
||||
{
|
||||
React.Children.map(this.props.children, child => (
|
||||
<Grid item xs={child.props.xs}>
|
||||
<ResponsiveContainer width="100%" height={child.props.height}>
|
||||
{React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})}
|
||||
</ResponsiveContainer>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChartGrid.propTypes = {
|
||||
spacing: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
// Home renders the home component.
|
||||
class Home extends Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
|
||||
!isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const memoryColor = theme.palette.primary[300];
|
||||
const trafficColor = theme.palette.secondary[300];
|
||||
|
||||
return (
|
||||
<ChartGrid spacing={24}>
|
||||
<AreaChart xs={6} height={300} values={this.props.memory}>
|
||||
<YAxis />
|
||||
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
|
||||
</AreaChart>
|
||||
<LineChart xs={6} height={300} values={this.props.traffic}>
|
||||
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
|
||||
</LineChart>
|
||||
<LineChart xs={6} height={300} values={this.props.memory}>
|
||||
<YAxis />
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
||||
<Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} />
|
||||
</LineChart>
|
||||
<AreaChart xs={6} height={300} values={this.props.traffic}>
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
||||
<Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} />
|
||||
</AreaChart>
|
||||
</ChartGrid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Home.propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withTheme()(Home);
|
109
dashboard/assets/components/Main.jsx
Normal file
109
dashboard/assets/components/Main.jsx
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {withStyles} from 'material-ui/styles';
|
||||
|
||||
import {TAGS, DRAWER_WIDTH} from "./Common.jsx";
|
||||
import Home from './Home.jsx';
|
||||
|
||||
// ContentSwitch chooses and renders the proper page content.
|
||||
class ContentSwitch extends Component {
|
||||
render() {
|
||||
switch(this.props.active) {
|
||||
case TAGS.home.id:
|
||||
return <Home memory={this.props.memory} traffic={this.props.traffic} shouldUpdate={this.props.shouldUpdate} />;
|
||||
case TAGS.chain.id:
|
||||
return null;
|
||||
case TAGS.transactions.id:
|
||||
return null;
|
||||
case TAGS.network.id:
|
||||
// Only for testing.
|
||||
return null;
|
||||
case TAGS.system.id:
|
||||
return null;
|
||||
case TAGS.logs.id:
|
||||
return <div>{this.props.logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ContentSwitch.propTypes = {
|
||||
active: PropTypes.string.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
// styles contains the styles for the Main component.
|
||||
const styles = theme => ({
|
||||
content: {
|
||||
width: '100%',
|
||||
marginLeft: -DRAWER_WIDTH,
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing.unit * 3,
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
marginTop: 56,
|
||||
overflow: 'auto',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
content: {
|
||||
height: 'calc(100% - 64px)',
|
||||
marginTop: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
contentShift: {
|
||||
marginLeft: 0,
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Main renders a component for the page content.
|
||||
class Main extends Component {
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
|
||||
return (
|
||||
<main className={classNames(classes.content, this.props.opened && classes.contentShift)}>
|
||||
<ContentSwitch
|
||||
active={this.props.active}
|
||||
memory={this.props.memory}
|
||||
traffic={this.props.traffic}
|
||||
logs={this.props.logs}
|
||||
shouldUpdate={this.props.shouldUpdate}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Main.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
active: PropTypes.string.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Main);
|
106
dashboard/assets/components/SideBar.jsx
Normal file
106
dashboard/assets/components/SideBar.jsx
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withStyles} from 'material-ui/styles';
|
||||
import Drawer from 'material-ui/Drawer';
|
||||
import {IconButton} from "material-ui";
|
||||
import List, {ListItem, ListItemText} from 'material-ui/List';
|
||||
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
||||
|
||||
import {TAGS, DRAWER_WIDTH} from './Common.jsx';
|
||||
|
||||
// Styles for the SideBar component.
|
||||
const styles = theme => ({
|
||||
drawerPaper: {
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
width: DRAWER_WIDTH,
|
||||
},
|
||||
drawerHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '0 8px',
|
||||
...theme.mixins.toolbar,
|
||||
transitionDuration: {
|
||||
enter: theme.transitions.duration.enteringScreen,
|
||||
exit: theme.transitions.duration.leavingScreen,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// SideBar renders a sidebar component.
|
||||
class SideBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// clickOn contains onClick event functions for the menu items.
|
||||
// Instantiate only once, and reuse the existing functions to prevent the creation of
|
||||
// new function instances every time the render method is triggered.
|
||||
this.clickOn = {};
|
||||
for(let key in TAGS) {
|
||||
const id = TAGS[key].id;
|
||||
this.clickOn[id] = event => {
|
||||
event.preventDefault();
|
||||
console.log(event.target.key);
|
||||
this.props.changeContent(id);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
type="persistent"
|
||||
classes={{paper: classes.drawerPaper,}}
|
||||
open={this.props.opened}
|
||||
>
|
||||
<div>
|
||||
<div className={classes.drawerHeader}>
|
||||
<IconButton onClick={this.props.close}>
|
||||
<ChevronLeftIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<List>
|
||||
{
|
||||
Object.values(TAGS).map(tag => {
|
||||
return (
|
||||
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]}>
|
||||
<ListItemText primary={tag.title} />
|
||||
</ListItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SideBar.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
changeContent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SideBar);
|
Reference in New Issue
Block a user