aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/About.tsx98
-rw-r--r--src/Logo.tsx20
-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.tsx384
-rw-r--r--src/index.tsx5
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&nbsp;
- <Link href="https://avalanchelabs.org/QmT1ry38PAmnhparPUmsUNHDEGHQusBLD6T5XJh4mUUn3v.pdf" target="_blank" rel="noopener">
- this paper
- </Link>
- &nbsp;. 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&nbsp;
+ <Link href="https://avalanchelabs.org/QmT1ry38PAmnhparPUmsUNHDEGHQusBLD6T5XJh4mUUn3v.pdf" target="_blank" rel="noopener">
+ this paper
+ </Link>
+ &nbsp;. 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";