diff --git a/.babelrc b/.babelrc index ba11916..b8a0eba 100644 --- a/.babelrc +++ b/.babelrc @@ -6,7 +6,10 @@ "stage-2", ], "plugins": [ + "transform-async-to-generator", "transform-class-properties", + "transform-es2015-classes", "transform-function-bind", + "transform-object-rest-spread", ] } diff --git a/.eslintrc.js b/.eslintrc.js index b10fd46..c1e1590 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,12 @@ module.exports = { extends: 'eslint-config-airbnb', + parser: 'babel-eslint', + parserOptions: { + sourceType: 'module', + ecmaVersion: 8, + }, rules: { + 'react/sort-comp': 0, 'import/extensions': 0, 'import/no-extraneous-dependencies': 0, 'import/no-unresolved': [2, {ignore: ['electron']}], diff --git a/package.json b/package.json index 388c2e7..65462a3 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@commitlint/travis-cli": "^8.0.0", "@semantic-release/github": "^5.4.0", "babel-cli": "^6.26.0", + "babel-eslint": "10.0.2", "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-es2015-classes": "^6.24.1", diff --git a/src/app.jsx b/src/app.jsx index 3ad109c..f153a6d 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -1,17 +1,21 @@ import Container from '@material-ui/core/Container'; import CssBaseline from '@material-ui/core/CssBaseline'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import FormGroup from '@material-ui/core/FormGroup'; import Grid from '@material-ui/core/Grid'; import LinearProgress from '@material-ui/core/LinearProgress'; import PropTypes from 'prop-types'; import React from 'react'; import Slider from '@material-ui/lab/Slider'; +import Switch from '@material-ui/core/Switch'; import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; import log from 'electron-log'; import {Connection} from '@solana/web3.js'; -import {withStyles} from '@material-ui/core/styles'; -import {ReactThemes, ReactTerminal} from 'react-terminal-component'; import {EmulatorState, OutputFactory, Outputs} from 'javascript-terminal'; +import {ReactThemes, ReactTerminal} from 'react-terminal-component'; +import {withStyles} from '@material-ui/core/styles'; +import Store from 'electron-store'; import {url} from './url'; import {Replicator} from './replicator'; @@ -38,11 +42,11 @@ const styles = theme => ({ marginTop: theme.spacing(5), }, progressBar: { - marginTop: theme.spacing(1), + marginTop: theme.spacing(2), }, footer: { - marginTop: 'auto', - backgroundColor: 'white', + marginTop: theme.spacing(1), + backgroundColor: 'lightgray', }, }); @@ -51,10 +55,19 @@ class App extends React.Component { super(props); this.terminalHeight = 25; + const storeSchema = { + enabled: { + type: 'boolean', + default: false, + }, + }; + this.store = new Store({schema: storeSchema}); + this.state = { transactionCount: 0, totalMined: 0, totalSupply: 0, + enabled: this.store.get('enabled'), }; this.connection = new Connection(url); log.info('connection:', url); @@ -63,7 +76,11 @@ class App extends React.Component { this.addTerminalText(`Cluster entrypoint: ${url}...`); this.replicator = new Replicator(this.connection, this); - this.replicator.start(); + if (this.state.enabled) { + this.replicator.start(); + } else { + this.addTerminalText('Mining disabled'); + } } componentDidMount() { @@ -150,6 +167,17 @@ class App extends React.Component { } } + onEnabledSwitch = event => { + const enabled = event.target.checked; + this.store.set('enabled', enabled); + this.setState({enabled}); + if (enabled) { + this.replicator.start(); + } else { + this.replicator.stop(); + } + }; + render() { const {classes} = this.props; @@ -181,6 +209,19 @@ class App extends React.Component { + + + } + label="Enable" + /> + +

Gigabytes of ledger to store: @@ -229,7 +270,11 @@ class App extends React.Component { - + { const devModeExtra = isDevMode ? 200 : 0; mainWindow = new BrowserWindow({ width: 1000 + devModeExtra, - height: 750, + height: 800, resizable: isDevMode, }); diff --git a/src/replicator.js b/src/replicator.js index 7de4dae..55d2047 100644 --- a/src/replicator.js +++ b/src/replicator.js @@ -11,6 +11,8 @@ export class Replicator { this.connection = connection; this.terminalOutput = terminalOutput; this.running = false; + this.mainPromise = Promise.resolve(); + this.cmdCancel = () => undefined; const userDataPath = electron.remote.app.getPath('userData'); this.solanaInstallBinDir = path.join( @@ -30,29 +32,31 @@ export class Replicator { this.solanaInstallDataDir = path.join(userDataPath, 'install'); } - start() { + async start() { if (this.running) { log.warn('Replicator already running, ignoring start()'); return; } + await this.mainPromise; this.running = true; - this.main(); + this.mainPromise = this.main(); } - stop() { + async stop() { if (!this.running) { return; } - this.terminalOutput.addTerminalText('^C'); this.running = false; - // TODO: signal main to exit... + this.terminalOutput.addTerminalText('^C'); + this.cmdCancel(); + await this.mainPromise; } - restart() { + async restart() { if (!this.running) { return; } - this.stop(); + await this.stop(); this.start(); } @@ -63,6 +67,7 @@ export class Replicator { this.terminalOutput.addTerminalCommand(`${command} ${args.join(' ')}`); const env = Object.assign({}, {RUST_LOG: 'solana=info'}, process.env); const child = spawn(command, args, {env}); + log.info(`pid ${child.pid}`); child.stdout.on('data', data => this.terminalOutput.addTerminalText(data.toString()), @@ -70,7 +75,16 @@ export class Replicator { child.stderr.on('data', data => this.terminalOutput.addTerminalText(data.toString()), ); - return child; + return Promise.race([ + child, + new Promise((_, reject) => { + this.cmdCancel = () => { + log.info(`cmd cancelled, killing pid ${child.pid}`); + child.kill(); + reject(new Error('User abort')); + }; + }), + ]); } /** @@ -142,5 +156,12 @@ export class Replicator { } catch (err) { this.terminalOutput.addTerminalError(err.message); } + + if (!this.running) { + log.info('main: not running anymore'); + return Promise.resolve(); + } + log.info('main: still running, restarting'); + return this.main(); } } diff --git a/yarn.lock b/yarn.lock index 76df6c3..2115afc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1331,6 +1331,18 @@ babel-core@^6.13.2, babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.7" +babel-eslint@10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456" + integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + babel-generator@^6.26.0: version "6.26.1" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" @@ -4449,6 +4461,14 @@ eslint-restricted-globals@^0.1.1: resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" integrity sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc= +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^3.7.1: version "3.7.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535"