diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/About.tsx | 98 | ||||
-rw-r--r-- | src/Logo.tsx | 20 | ||||
-rw-r--r-- | src/Matrix.css (renamed from src/Grid.css) | 12 | ||||
-rw-r--r-- | src/Matrix.tsx (renamed from src/Grid.tsx) | 25 | ||||
-rw-r--r-- | src/Snow.tsx | 384 | ||||
-rw-r--r-- | src/index.tsx | 5 |
6 files changed, 274 insertions, 270 deletions
diff --git a/src/About.tsx b/src/About.tsx index 6966d6f..83eb8c1 100644 --- a/src/About.tsx +++ b/src/About.tsx @@ -31,51 +31,61 @@ function About(props: AboutProps) { const { classes } = props; return ( <div className={classes.body}> - <Typography variant="body1"> - <article> - The MIT License (MIT) - <p>Copyright 2019 Maofan "Ted" Yin</p> - - <p>Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions:</p> - - <p>The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software.</p> - - <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE.</p> - </article> - <List disablePadding> - <ListItem> - <span className={classes.infoField}>Email:</span> - 73d at tedyin dot com - </ListItem> - <ListItem> - <span className={classes.infoField}>GitHub:</span> - <Link href="https://github.com/Determinant/snow-bft-demo" target="_blank" rel="noopener"> - Determinant/snow-bft-demo - </Link> - </ListItem> - <ListItem> - <span className={classes.infoField}>Buy me a cup of coffee:</span> - <List className={classes.pre} disablePadding> - <ListItem>Ether: 0xFEeed0f0BA87824819aabfa789f41FA2dd9ad81e</ListItem> - <ListItem>Bitcoin: 1CbVBB6Gv7WP4u39wsN416SJrjmvQDjggw</ListItem> + <article> + <Typography variant="body1" paragraph> + The MIT License (MIT) + </Typography> + + <Typography variant="body1" paragraph> + Copyright 2019 Maofan "Ted" Yin + </Typography> + + <Typography variant="body1" paragraph> + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + </Typography> + + <Typography variant="body1" paragraph> + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + </Typography> + + <Typography variant="body1"> + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + </Typography> + </article> + <Typography variant="body1"> + <List disablePadding> + <ListItem> + <span className={classes.infoField}>Email:</span> + 73d at tedyin dot com + </ListItem> + <ListItem> + <span className={classes.infoField}>GitHub:</span> + <Link href="https://github.com/Determinant/snow-bft-demo" target="_blank" rel="noopener"> + Determinant/snow-bft-demo + </Link> + </ListItem> + <ListItem> + <span className={classes.infoField}>Buy me a cup of coffee:</span> + <List className={classes.pre} disablePadding> + <ListItem>Ether: 0xFEeed0f0BA87824819aabfa789f41FA2dd9ad81e</ListItem> + <ListItem>Bitcoin: 1CbVBB6Gv7WP4u39wsN416SJrjmvQDjggw</ListItem> + </List> + </ListItem> </List> - </ListItem> - </List> - </Typography> - </div> - ); + </Typography> + </div>); } export default withStyles(styles)(About); diff --git a/src/Logo.tsx b/src/Logo.tsx index 1bac2b7..09700d1 100644 --- a/src/Logo.tsx +++ b/src/Logo.tsx @@ -1,12 +1,12 @@ import React from 'react'; export default (props: {style: {[key: string]: string | number }}) => - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 97.079997 37.00" - version="1.1" - style={props.style}> -<path - d="M 20.52,0 0,36.02 H 14.85 L 28.03,13.19 Z M 13.7,34.02 H 3.45 l 17.08,-29.98 5.21,9.14 z m 8.79,2 H 41.04 L 31.82,19.85 Z m 3.46,-2 5.86,-10.16 5.79,10.16 z M 76.57,0 56.05,36.02 H 70.9 L 84.08,13.19 Z M 69.74,34.02 H 59.49 L 76.57,4.04 81.78,13.18 Z M 87.87,19.84 78.53,36.01 h 18.55 z m -0.01,4.02 5.79,10.16 H 82 Z M 42.92,0 H 28.07 L 48.59,36.02 56.1,22.84 Z M 31.51,2 h 10.25 l 12.03,20.84 -5.21,9.14 z M 69.11,0 H 50.56 L 59.9,16.17 Z M 54.02,2 h 11.65 l -5.79,10.16 z" - id="path4" - style={{fill:'#f5f1ee'}} /> -</svg> + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 97.079997 37.00" + version="1.1" + style={props.style}> + <path + d="M 20.52,0 0,36.02 H 14.85 L 28.03,13.19 Z M 13.7,34.02 H 3.45 l 17.08,-29.98 5.21,9.14 z m 8.79,2 H 41.04 L 31.82,19.85 Z m 3.46,-2 5.86,-10.16 5.79,10.16 z M 76.57,0 56.05,36.02 H 70.9 L 84.08,13.19 Z M 69.74,34.02 H 59.49 L 76.57,4.04 81.78,13.18 Z M 87.87,19.84 78.53,36.01 h 18.55 z m -0.01,4.02 5.79,10.16 H 82 Z M 42.92,0 H 28.07 L 48.59,36.02 56.1,22.84 Z M 31.51,2 h 10.25 l 12.03,20.84 -5.21,9.14 z M 69.11,0 H 50.56 L 59.9,16.17 Z M 54.02,2 h 11.65 l -5.79,10.16 z" + id="path4" + style={{fill:'#f5f1ee'}} /> + </svg>; diff --git a/src/Grid.css b/src/Matrix.css index 33c2725..18d5a4d 100644 --- a/src/Grid.css +++ b/src/Matrix.css @@ -1,8 +1,8 @@ -div.grid { +div.matrix { display: inline-block; } -.grid div { +.matrix div { -webkit-transition: background-color 0.3s ease-out; -moz-transition: background-color 0.3s ease-out; -o-transition: background-color 0.3s ease-out; @@ -10,7 +10,7 @@ div.grid { user-select: none; } -.gridCell div div { +.matrixCell div div { height: 20px; width: 20px; margin: 2px; @@ -18,7 +18,7 @@ div.grid { cursor: pointer; } -.smallGridCell div div { +.smallMatrixCell div div { height: 15px; width: 15px; margin: 1px; @@ -26,12 +26,12 @@ div.grid { cursor: pointer; } -.gridRow div { +.matrixRow div { height: 24px; text-align: center; } -.smallGridRow div { +.smallMatrixRow div { height: 17px; text-align: center; } diff --git a/src/Grid.tsx b/src/Matrix.tsx index d1fc973..53faf51 100644 --- a/src/Grid.tsx +++ b/src/Matrix.tsx @@ -1,15 +1,14 @@ import React from 'react'; import 'typeface-roboto'; -import MGrid from '@material-ui/core/Grid'; import orange from '@material-ui/core/colors/orange'; import blue from '@material-ui/core/colors/blue'; import Color from 'color'; -import './Grid.css'; +import './Matrix.css'; -interface GridProps { +interface MatrixProps { onClickNode: (i: number, j: number) => void onHoverNode: (i: number, j: number) => void - data: {d: number[], col: number}[][] + data: { d: number[], col: number }[][] } export function getNodeColor(d: number, col: number) { @@ -19,19 +18,19 @@ export function getNodeColor(d: number, col: number) { return Color.hsl(base).darken(Math.min(d / 15, 0.5)).hex(); } -class Grid extends React.Component<GridProps> { - static getColor(s: {d: number[], col: number}) { +class Matrix extends React.Component<MatrixProps> { + static getColor(s: { d: number[], col: number }) { return getNodeColor(s.d[s.col], s.col); } mouseDown = false; - mousePos = {x: -1, y: -1}; + mousePos = { x: -1, y: -1 }; render() { const { data, onClickNode, onHoverNode } = this.props; - const { gr, gc, l } = data.length <= 20 ? - { gr: 'gridRow', gc: 'gridCell', l: 24 } : - { gr: 'smallGridRow', gc: 'smallGridCell', l: 15 }; + const { mr, mc, l } = data.length <= 20 ? + { mr: 'matrixRow', mc: 'matrixCell', l: 24 } : + { mr: 'smallMatrixRow', mc: 'smallMatrixCell', l: 15 }; return ( - <div className={`grid ${gr} ${gc}`} + <div className={`matrix ${mr} ${mc}`} onMouseDown={event => { event.preventDefault(); this.mouseDown = true; @@ -58,7 +57,7 @@ class Grid extends React.Component<GridProps> { <div key={i}> {row.map((cell, j) => ( <div key={j} - style={{backgroundColor: Grid.getColor(cell)}}> + style={{backgroundColor: Matrix.getColor(cell)}}> </div> ))} </div> @@ -67,4 +66,4 @@ class Grid extends React.Component<GridProps> { } } -export default Grid; +export default Matrix; diff --git a/src/Snow.tsx b/src/Snow.tsx index 7b296e6..b053a51 100644 --- a/src/Snow.tsx +++ b/src/Snow.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import MGrid from '@material-ui/core/Grid'; +import Grid from '@material-ui/core/Grid'; import { Theme, withStyles, StyleRules } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import Table from '@material-ui/core/Table'; @@ -13,7 +13,8 @@ import Typography from '@material-ui/core/Typography'; import Link from '@material-ui/core/Link'; import { Line } from 'react-chartjs-2'; import Color from 'color'; -import Grid, {getNodeColor} from './Grid'; + +import Matrix, { getNodeColor } from './Matrix'; const styles = (theme: Theme): StyleRules => ({ inputLabel: { @@ -62,14 +63,15 @@ function getRandomInt(max: number) { return Math.floor(Math.random() * Math.floor(max)); } +/* Code from: https://stackoverflow.com/a/11935263/544806 */ function getRandomSubarray(arr: number[], size: number) { let shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index; while (i-- > min) { - index = Math.floor((i + 1) * Math.random()); - temp = shuffled[index]; - shuffled[index] = shuffled[i]; - shuffled[i] = temp; - } + index = Math.floor((i + 1) * Math.random()); + temp = shuffled[index]; + shuffled[index] = shuffled[i]; + shuffled[i] = temp; + } return shuffled.slice(min); } @@ -83,7 +85,7 @@ class Snow extends React.Component<SnowProps> { { let r = []; for (let j = 0; j < n; j++) - r.push({d: [0, 0], col: getRandomInt(2)}); + r.push({ d: [0, 0], col: getRandomInt(2) }); d.push(r); } return d; @@ -100,7 +102,7 @@ class Snow extends React.Component<SnowProps> { ticking: false, simulationSpeed: 100, dialogOpen: false, - dialogMsg: {title: '', message: ''}, + dialogMsg: { title: '', message: '' }, nError: false, kError: false, alphaError: false, @@ -128,12 +130,12 @@ class Snow extends React.Component<SnowProps> { return this.state.colorMatrix[r][c]; } - setNodeState(n: number, u: number, s: {d: number[], col: number}) { + setNodeState(n: number, u: number, s: { d: number[], col: number }) { let r = Math.floor(u / n); let c = u % n; let m = [...this.state.colorMatrix]; m[r][c] = s; - this.setState({colorMatrix: m}); + this.setState({ colorMatrix: m }); } tick(n: number, m: number) { @@ -196,7 +198,7 @@ class Snow extends React.Component<SnowProps> { } pauseTick() { - this.setState({ticking: false}); + this.setState({ ticking: false }); } startTick() { @@ -255,7 +257,7 @@ class Snow extends React.Component<SnowProps> { } autoTick() { - this.setState({ticking: true}); + this.setState({ ticking: true }); setTimeout(() => { let active = this.tick(this.config.n, this.config.nodesPerTick); this.config.iter++; @@ -291,187 +293,181 @@ class Snow extends React.Component<SnowProps> { const { classes } = this.props; return ( - <MGrid container spacing={16} style={{minWidth: 600}}> - <MGrid item lg={6} xs={12} className={classes.grid}> - <Grid - data={this.state.colorMatrix} - onClickNode={(i: number, j: number) => this.flipNode(i, j)} - onHoverNode={(i: number, j: number) => this.flipNode(i, j)} - /> - <div style={{position: 'relative', height: '40vh'}}> - <Line data={() => { - let datasets = this.state.dcnts.map((dd, c) => dd.map((line, i) => { - const base = getNodeColor(watchedD[i], c); - return { - data: line, - label: `${c == 0 ? 'A' : 'B'}(d-${watchedD[i]})`, - borderColor: base, - backgroundColor: Color(base).fade(0.5).rgb().string(), - borderWidth: 2 - }; - })).flat(); - return { - datasets, - labels: this.state.ticks - } - }} - options={{ - scales: { yAxes: [{ ticks: {min: -this.state.N, max: this.state.N}}]}, - maintainAspectRatio: false - }}/> - </div> - </MGrid> - <MGrid item lg={4} xs={12}> - <Typography variant="body1"> - <p> - This demo shows the Snowball protocol used as the core of a peer-to-peer payment system, Avalanche, introduced in - <Link href="https://avalanchelabs.org/QmT1ry38PAmnhparPUmsUNHDEGHQusBLD6T5XJh4mUUn3v.pdf" target="_blank" rel="noopener"> - this paper - </Link> - . It visualizes the process of a binary, - single-decree, probabilistic Snowball consensus that harnesses - metastability to guarantee safety. - Little squares represent different nodes, wherein - the color of each square represents its current - proposal. Darkness of the color shows the node's - conviction in that proposal. Expectedly, all nodes - will collapse to the same color in the end. - </p> - <p> - Try to click or move the mouse when clicked to flip the - color of squares. Are you able to prevent them from - going to a single color? - </p> - </Typography> - <Table> - <TableBody> - <TableRow> - <TableCell className={classes.inputLabel}> - n = - </TableCell> - <TableCell> - <TextField - inputProps={{ className: classes.inputValue, maxLength: 2 } as React.CSSProperties} - value={this.state.n} - disabled={this.state.loaded} - style={{width: 40}} - error={this.state.nError} - onChange={event => this.setState({n: event.target.value, nError: false})}/> - <sup>2</sup> - {this.state.nError && - <span className={classes.errorHint}>n must be in 2..40</span>} - </TableCell> - </TableRow> - <TableRow> - <TableCell className={classes.inputLabel}> - k = - </TableCell> - <TableCell> - <TextField - inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} - value={this.state.k} - disabled={this.state.ticking} - style={{width: 40}} - error={this.state.kError} - onChange={event => this.setState({k: event.target.value, kError: false})}/> - {this.state.kError && - <span className={classes.errorHint}>k must be in 1..(n-1)</span>} - </TableCell> - </TableRow> - <TableRow> - <TableCell className={classes.inputLabel}> - alpha = - </TableCell> - <TableCell> - <TextField - inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} - value={this.state.alpha} - disabled={this.state.ticking} - style={{width: 40}} - error={this.state.alphaError} - onChange={event => this.setState({alpha: event.target.value, alphaError: false})}/> - {this.state.alphaError && - <span className={classes.errorHint}>alpha must be in (k/2, k]</span>} - </TableCell> - </TableRow> - <TableRow> - <TableCell className={classes.inputLabel}> - nodesPerTick = - </TableCell> - <TableCell> - <TextField - inputProps={{ className: classes.inputValue, maxLength: 4 } as React.CSSProperties} - value={this.state.nodesPerTick} - disabled={this.state.ticking} - style={{width: 40}} - error={this.state.nodesPerTickError} - onChange={event => this.setState({nodesPerTick: event.target.value, nodesPerTickError: false})}/> - {this.state.nodesPerTickError && - <span className={classes.errorHint}>nodesPerTick must be in 1..n</span>} - </TableCell> - </TableRow> - <TableRow> - <TableCell className={classes.inputLabel}> - maxInactiveTicks = - </TableCell> - <TableCell> - <TextField - inputProps={{ className: classes.inputValue } as React.CSSProperties} - value={this.state.maxInactiveTicks} - disabled={this.state.ticking} - style={{width: 50}} - error={this.state.maxInactiveTicksError} - onChange={event => this.setState({maxInactiveTicks: event.target.value, maxInactiveTicksError: false})}/> - {this.state.maxInactiveTicksError && - <span className={classes.errorHint}>maxInactiveTicks must be in 1..1000000</span>} - </TableCell> - </TableRow> - <TableRow> - <TableCell className={classes.inputLabel}> - simulationSpeed - </TableCell> - <TableCell> - <Slider - classes={{ container: classes.slider }} - value={this.state.simulationSpeed} - min={1} - max={1000} - onChange={(_, value) => this.setState({simulationSpeed: value})} /> - </TableCell> - </TableRow> - </TableBody> - </Table> - <div className={classes.buttonSpacer} /> - <div className={classes.bottomButtons}> - <MGrid container item spacing={16}> - <MGrid item md={4} xs={12}> - <FormGroup> - <Button - variant="contained" color="primary" - onClick={event => this.startTick()} - disabled={this.state.ticking}>Run</Button> - </FormGroup> - </MGrid> - <MGrid item md={4} xs={12}> - <FormGroup> - <Button - variant="contained" color="primary" - onClick={event => this.pauseTick()} - disabled={!this.state.ticking}>Stop</Button> - </FormGroup> - </MGrid> - <MGrid item md={4} xs={12}> - <FormGroup> - <Button - variant="contained" color="primary" - onClick={event => this.reset()}>Reset</Button> - </FormGroup> - </MGrid> - </MGrid> - </div> - </MGrid> - </MGrid> - ); + <Grid container spacing={16} style={{minWidth: 600}}> + <Grid item lg={6} xs={12} className={classes.grid}> + <Matrix + data={this.state.colorMatrix} + onClickNode={(i: number, j: number) => this.flipNode(i, j)} + onHoverNode={(i: number, j: number) => this.flipNode(i, j)} /> + <div style={{position: 'relative', height: '40vh'}}> + <Line data={() => { + let datasets = this.state.dcnts.map((dd, c) => dd.map((line, i) => { + const base = getNodeColor(watchedD[i], c); + return { + data: line, + label: `${c == 0 ? 'A' : 'B'}(d-${watchedD[i]})`, + borderColor: base, + backgroundColor: Color(base).fade(0.5).rgb().string(), + borderWidth: 2 + }; + })).flat(); + return { + datasets, + labels: this.state.ticks + } + }} + options={{ + scales: { yAxes: [{ ticks: { min: -this.state.N, max: this.state.N }}]}, + maintainAspectRatio: false + }} /> + </div> + </Grid> + <Grid item lg={4} xs={12}> + <Typography variant="body1" paragraph> + This demo shows the Snowball protocol used as the core of a peer-to-peer payment system, Avalanche, introduced in + <Link href="https://avalanchelabs.org/QmT1ry38PAmnhparPUmsUNHDEGHQusBLD6T5XJh4mUUn3v.pdf" target="_blank" rel="noopener"> + this paper + </Link> + . It visualizes the process of a binary, + single-decree, probabilistic Snowball consensus that harnesses + metastability to guarantee safety. + Little squares represent different nodes, wherein + the color of each square represents its current + proposal. Darkness of the color shows the node's + conviction in that proposal. Expectedly, all nodes + will collapse to the same color in the end. + </Typography> + <Typography variant="body1" paragraph> + Try to click or move the mouse when clicked to flip the + color of squares. Are you able to prevent them from + going to a single color? + </Typography> + <Table> + <TableBody> + <TableRow> + <TableCell className={classes.inputLabel}> + n = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue, maxLength: 2 }} + value={this.state.n} + disabled={this.state.loaded} + style={{width: 40}} + error={this.state.nError} + onChange={event => this.setState({ n: event.target.value, nError: false })} /> + <sup>2</sup> + {this.state.nError && <span className={classes.errorHint}>n must be in 2..40</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> + k = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue, maxLength: 4 }} + value={this.state.k} + disabled={this.state.ticking} + style={{width: 40}} + error={this.state.kError} + onChange={event => this.setState({ k: event.target.value, kError: false })}/> + {this.state.kError && <span className={classes.errorHint}>k must be in 1..(n-1)</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> + alpha = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue, maxLength: 4 }} + value={this.state.alpha} + disabled={this.state.ticking} + style={{width: 40}} + error={this.state.alphaError} + onChange={event => this.setState({ alpha: event.target.value, alphaError: false })}/> + {this.state.alphaError && <span className={classes.errorHint}>alpha must be in (k/2, k]</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> + nodesPerTick = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue, maxLength: 4 }} + value={this.state.nodesPerTick} + disabled={this.state.ticking} + style={{width: 40}} + error={this.state.nodesPerTickError} + onChange={event => this.setState({ nodesPerTick: event.target.value, nodesPerTickError: false })}/> + {this.state.nodesPerTickError && <span className={classes.errorHint}>nodesPerTick must be in 1..n</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> + maxInactiveTicks = + </TableCell> + <TableCell> + <TextField + inputProps={{ className: classes.inputValue }} + value={this.state.maxInactiveTicks} + disabled={this.state.ticking} + style={{width: 50}} + error={this.state.maxInactiveTicksError} + onChange={event => this.setState({ maxInactiveTicks: event.target.value, maxInactiveTicksError: false })}/> + {this.state.maxInactiveTicksError && <span className={classes.errorHint}>maxInactiveTicks must be in 1..1000000</span>} + </TableCell> + </TableRow> + <TableRow> + <TableCell className={classes.inputLabel}> + simulationSpeed + </TableCell> + <TableCell> + <Slider + classes={{ container: classes.slider }} + value={this.state.simulationSpeed} + min={1} + max={1000} + onChange={(_, value) => this.setState({ simulationSpeed: value })} /> + </TableCell> + </TableRow> + </TableBody> + </Table> + <div className={classes.buttonSpacer} /> + <div className={classes.bottomButtons}> + <Grid container item spacing={16}> + <Grid item md={4} xs={12}> + <FormGroup> + <Button + variant="contained" color="primary" + onClick={event => this.startTick()} + disabled={this.state.ticking}>Run + </Button> + </FormGroup> + </Grid> + <Grid item md={4} xs={12}> + <FormGroup> + <Button + variant="contained" color="primary" + onClick={event => this.pauseTick()} + disabled={!this.state.ticking}>Stop + </Button> + </FormGroup> + </Grid> + <Grid item md={4} xs={12}> + <FormGroup> + <Button + variant="contained" color="primary" + onClick={event => this.reset()}>Reset + </Button> + </FormGroup> + </Grid> + </Grid> + </div> + </Grid> + </Grid>); } } diff --git a/src/index.tsx b/src/index.tsx index 36bb16c..7f51b9b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,13 +3,12 @@ import ReactDOM from 'react-dom'; import 'typeface-roboto'; import { Theme, withStyles, MuiThemeProvider } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; +import Grid from '@material-ui/core/Grid'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import Tabs from '@material-ui/core/Tabs'; -import Tab, { TabProps } from '@material-ui/core/Tab'; -import { LinkProps } from '@material-ui/core/Link'; -import Grid from '@material-ui/core/Grid'; +import Tab from '@material-ui/core/Tab'; import { HashRouter as Router, RouteComponentProps, withRouter, Route, Link, Redirect, Switch } from "react-router-dom"; import { TransitionGroup, CSSTransition } from "react-transition-group"; |