Merge pull request #150 from status-im/toolbar_cherry_picked

Add toolbar to editor
This commit is contained in:
Iuri Matias 2018-10-23 16:16:34 -04:00 committed by GitHub
commit 7eb87f1a25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 627 additions and 555 deletions

View File

@ -288,10 +288,8 @@ export const file = {
export const SAVE_FILE = createRequestTypes('SAVE_FILE');
export const saveFile = {
request: ({name, path, content}) => {
return action(SAVE_FILE[REQUEST], {name, path, content});
},
success: () => action(SAVE_FILE[SUCCESS]),
request: ({name, path, content}) => action(SAVE_FILE[REQUEST], {name, path, content}),
success: (_, {name, path, content}) => action(SAVE_FILE[SUCCESS], {name, path, content}),
failure: (error) => action(SAVE_FILE[FAILURE], {error})
};

View File

@ -10,7 +10,7 @@ const Account = ({account}) => (
<Col>
<Card>
<CardHeader>
<CardTitleIdenticon id={account.address}>Account {account.address}</CardTitleIdenticon>
<CardTitleIdenticon id={account.address}>{account.address}</CardTitleIdenticon>
</CardHeader>
<CardBody>
<dl className="row">

View File

@ -12,15 +12,15 @@ const Accounts = ({accounts}) => (
{accounts.map(account => (
<Card key={account.address}>
<CardHeader>
<Link to={`/embark/explorer/accounts/${account.address}`}>
<CardTitleIdenticon id={account.address}>Account {account.address}</CardTitleIdenticon>
</Link>
<CardTitleIdenticon id={account.address}>Account&nbsp;
<Link to={`/embark/explorer/accounts/${account.address}`}>{account.address}</Link>
</CardTitleIdenticon>
</CardHeader>
<CardBody>
<Row>
<Col>
<strong>Balance</strong>
<div>{account.balance} Wei</div>
<div>{account.balance} Ether</div>
</Col>
<Col>
<strong>Tx Count</strong>

View File

@ -12,7 +12,11 @@ const Blocks = ({blocks}) => (
{blocks.map(block => (
<Card key={block.number}>
<CardHeader>
<Link to={`/embark/explorer/blocks/${block.number}`}> <CardTitleIdenticon id={block.hash}>Block {block.number}</CardTitleIdenticon></Link>
<CardTitleIdenticon id={block.hash}>Block&nbsp;
<Link to={`/embark/explorer/blocks/${block.number}`}>
{block.number}
</Link>
</CardTitleIdenticon>
</CardHeader>
<CardBody>
<Row>

View File

@ -0,0 +1,36 @@
import PropTypes from "prop-types";
import React from 'react';
import ReactJson from "react-json-view";
import {Row, Col, Table} from "reactstrap";
import {formatContractForDisplay} from '../utils/presentation';
import CopyButton from './CopyButton';
const ContractDetail = ({contract}) => {
const contractDisplay = formatContractForDisplay(contract);
return (
<Row>
<Col>
<strong>ABI</strong>
<div className="relative">
<CopyButton text={JSON.stringify(contract.abiDefinition)}
title="Copy bytecode to clipboard"/>
{contract.abiDefinition && <ReactJson src={contract.abiDefinition} theme="monokai" sortKeys={true} collapsed={1} />}
</div>
<br />
<strong>Bytecode</strong>
<div className="text-wrap logs relative">
<CopyButton text={contract.runtimeBytecode}
title="Copy bytecode to clipboard"/>
{contract.runtimeBytecode}
</div>
</Col>
</Row>
);
};
ContractDetail.propTypes = {
contract: PropTypes.object
};
export default ContractDetail;

View File

@ -3,6 +3,7 @@ import React, {Component} from 'react';
import {
Row,
Col,
Form,
FormGroup,
Label,
Input,
@ -12,6 +13,7 @@ import {
CardHeader,
CardTitle,
CardFooter,
Collapse,
ListGroup,
ListGroupItem
} from "reactstrap";
@ -19,13 +21,17 @@ import {
class ContractFunction extends Component {
constructor(props) {
super(props);
this.state = {inputs: {}};
this.state = {inputs: {}, optionsCollapse: false, functionCollapse: false};
}
static isPureCall(method) {
return (method.mutability === 'view' || method.mutability === 'pure');
}
static isEvent(method) {
return !this.isPureCall(method) && (method.type === 'event');
}
buttonTitle() {
const {method} = this.props;
if (method.name === 'constructor') {
@ -56,30 +62,65 @@ class ContractFunction extends Component {
return this.inputsAsArray().length !== this.props.method.inputs.length;
}
toggleOptions() {
this.setState({
optionsCollapse: !this.state.optionsCollapse,
});
}
toggleFunction() {
this.setState({
functionCollapse: !this.state.functionCollapse,
});
}
render() {
return (
<Col xs={12}>
<Card>
<CardHeader>
<CardTitle>{this.props.method.name}</CardTitle>
<CardTitle className="collapsable contractFunction" onClick={() => this.toggleFunction()}>
{ContractFunction.isPureCall(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#ff4500", "border-color": "#ff4500", "float": "right" }}>call</button>
}
{ContractFunction.isEvent(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#aad450", "border-color": "#aad450", "float": "right" }}>event</button>
}
{this.props.method.name}({this.props.method.inputs.map(input => input.name).join(', ')})
</CardTitle>
</CardHeader>
<CardBody>
{this.props.method.inputs.map(input => (
<FormGroup key={input.name}>
<Label for={input.name}>{input.name}</Label>
<Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
</FormGroup>
))}
{!ContractFunction.isPureCall(this.props.method) &&
<FormGroup key="gasPrice">
<Label for="gasPrice">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/>
</FormGroup>
}
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)}>
{this.buttonTitle()}
</Button>
</CardBody>
<Collapse isOpen={this.state.functionCollapse}>
<CardBody>
<Form action="" method="post" inline>
{this.props.method.inputs.map(input => (
<FormGroup key={input.name} className="pr-1">
<Label for={input.name} className="pr-1">{input.name}: </Label>
<Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
</FormGroup>
))}
</Form>
{!ContractFunction.isPureCall(this.props.method) &&
<Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
<Row>
<strong className="collapsable" onClick={() => this.toggleOptions()}><i className={this.state.optionsCollapse ? 'fa fa-caret-down' : 'fa fa-caret-right'}/>Advanced Options</strong>
<Collapse isOpen={this.state.optionsCollapse}>
<Form action="" method="post" inline>
<FormGroup key="gasPrice" className="pr-1">
<Label for="gasPrice" className="pr-1">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/>
</FormGroup>
</Form>
</Collapse>
</Row>
</Col>
}
<div align="right">
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)} >
{this.buttonTitle()}
</Button>
</div>
</CardBody>
</Collapse>
{this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter>
<ListGroup>
{this.props.contractFunctions.map(contractFunction => (

View File

@ -3,9 +3,9 @@ import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, CardBody, CardTitle } from 'reactstrap';
import classnames from 'classnames';
import ContractOverview from '../components/ContractOverview';
import ContractDetail from '../components/ContractDetail';
import ContractLoggerContainer from '../containers/ContractLoggerContainer';
import ContractFunctionsContainer from '../containers/ContractFunctionsContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
class ContractLayout extends React.Component {
constructor(props) {
@ -44,7 +44,7 @@ class ContractLayout extends React.Component {
className={classnames({ active: this.state.activeTab === '2' })}
onClick={() => { this.toggle('2'); }}
>
Functions
Detail
</NavLink>
</NavItem>
<NavItem>
@ -58,10 +58,10 @@ class ContractLayout extends React.Component {
</Nav>
<TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1">
<ContractOverview contract={this.props.contract} />
<ContractOverviewContainer contract={this.props.contract} />
</TabPane>
<TabPane tabId="2">
<ContractFunctionsContainer contract={this.props.contract} />
<ContractDetail contract={this.props.contract} />
</TabPane>
<TabPane tabId="3">
<ContractLoggerContainer contract={this.props.contract} />

View File

@ -1,54 +1,191 @@
import PropTypes from "prop-types";
import React from 'react';
import ReactJson from "react-json-view";
import {Row, Col, Table} from "reactstrap";
import React, {Component} from 'react';
import {
Row,
Col,
Form,
FormGroup,
Label,
Input,
Button,
Card,
CardBody,
CardHeader,
CardTitle,
CardFooter,
Collapse,
ListGroup,
ListGroupItem
} from "reactstrap";
import {formatContractForDisplay} from '../utils/presentation';
import CopyButton from './CopyButton';
const Contract = ({contract}) => {
class ContractFunction extends Component {
constructor(props) {
super(props);
this.state = {inputs: {}, optionsCollapse: false, functionCollapse: false};
}
static isPureCall(method) {
return (method.mutability === 'view' || method.mutability === 'pure');
}
static isEvent(method) {
return !this.isPureCall(method) && (method.type === 'event');
}
buttonTitle() {
const {method} = this.props;
if (method.name === 'constructor') {
return 'Deploy';
}
return ContractFunction.isPureCall(method) ? 'Call' : 'Send';
}
inputsAsArray() {
return this.props.method.inputs
.map(input => this.state.inputs[input.name])
.filter(value => value);
}
handleChange(e, name) {
let newInputs = this.state.inputs;
newInputs[name] = e.target.value;
this.setState({inputs: newInputs});
}
handleCall(e) {
e.preventDefault();
this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, this.inputsAsArray(), this.state.inputs.gasPrice * 1000000000);
}
callDisabled() {
return this.inputsAsArray().length !== this.props.method.inputs.length;
}
toggleOptions() {
this.setState({
optionsCollapse: !this.state.optionsCollapse,
});
}
toggleFunction() {
this.setState({
functionCollapse: !this.state.functionCollapse,
});
}
render() {
return (
<Card>
<CardHeader>
<CardTitle className="collapsable contractFunction" onClick={() => this.toggleFunction()}>
{ContractFunction.isPureCall(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#ff4500", "border-color": "#ff4500", "float": "right" }}>call</button>
}
{ContractFunction.isEvent(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#aad450", "border-color": "#aad450", "float": "right" }}>event</button>
}
{this.props.method.name}({this.props.method.inputs.map(input => input.name).join(', ')})
</CardTitle>
</CardHeader>
<Collapse isOpen={this.state.functionCollapse}>
<CardBody>
<Form action="" method="post" inline>
{this.props.method.inputs.map(input => (
<FormGroup key={input.name} className="pr-1">
<Label for={input.name} className="pr-1">{input.name}: </Label>
<Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
</FormGroup>
))}
</Form>
{!ContractFunction.isPureCall(this.props.method) &&
<Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
<Row>
<strong className="collapsable" onClick={() => this.toggleOptions()}><i className={this.state.optionsCollapse ? 'fa fa-caret-down' : 'fa fa-caret-right'}/>Advanced Options</strong>
<Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
<Row>
<Collapse isOpen={this.state.optionsCollapse}>
<Form action="" method="post" inline>
<FormGroup key="gasPrice" className="pr-1">
<Label for="gasPrice" className="pr-1">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/>
</FormGroup>
</Form>
</Collapse>
</Row>
</Col>
</Row>
</Col>
}
<div align="right">
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)}>
{this.buttonTitle()}
</Button>
</div>
</CardBody>
</Collapse>
{this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter>
<ListGroup>
{this.props.contractFunctions.map(contractFunction => (
<ListGroupItem key={contractFunction.result}>
{contractFunction.inputs.length > 0 && <p>Inputs: {contractFunction.inputs.join(', ')}</p>}
<strong>Result: {contractFunction.result}</strong>
</ListGroupItem>
))}
</ListGroup>
</CardFooter>}
</Card>
);
}
}
ContractFunction.propTypes = {
contractProfile: PropTypes.object,
method: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object),
postContractFunction: PropTypes.func
};
const filterContractFunctions = (contractFunctions, contractName, method) => {
return contractFunctions.filter((contractFunction) => (
contractFunction.contractName === contractName && contractFunction.method === method
));
};
const ContractOverview = (props) => {
const {contractProfile, contract} = props;
const contractDisplay = formatContractForDisplay(contract);
return (
<Row>
<Col>
<Table
responsive
className="text-nowrap"
>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>State</th>
</tr>
</thead>
<tbody>
<tr className={contractDisplay.stateColor}>
<td>{contract.className}</td>
<td>{contractDisplay.address}</td>
<td>{contractDisplay.state}</td>
</tr>
</tbody>
</Table>
<h2>ABI</h2>
<div className="relative">
<CopyButton text={JSON.stringify(contract.abiDefinition)}
title="Copy bytecode to clipboard"/>
{contract.abiDefinition && <ReactJson src={contract.abiDefinition} theme="monokai" sortKeys={true} collapsed={1} />}
</div>
<h2>Bytecode</h2>
<div className="text-wrap logs relative">
<CopyButton text={contract.runtimeBytecode}
title="Copy bytecode to clipboard"/>
{contract.runtimeBytecode}
</div>
</Col>
</Row>
<div>
{(contractDisplay.state === 'Deployed') && <div>Deployed at {contractDisplay.address}</div>}
{(contractDisplay.state !== 'Deployed') && <div>{contractDisplay.address}</div>}
<br />
{contractProfile.methods
.filter((method) => {
return props.onlyConstructor ? method.type === 'constructor' : method.type !== 'constructor';
})
.map(method => <ContractFunction key={method.name}
method={method}
contractFunctions={filterContractFunctions(props.contractFunctions, contractProfile.name, method.name)}
contractProfile={contractProfile}
postContractFunction={props.postContractFunction} />)}
</div>
);
};
Contract.propTypes = {
contract: PropTypes.object
ContractOverview.propTypes = {
contract: PropTypes.object,
onlyConstructor: PropTypes.bool,
contractProfile: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object),
postContractFunction: PropTypes.func
};
export default Contract;
ContractOverview.defaultProps = {
onlyConstructor: false
};
export default ContractOverview;

View File

@ -5,7 +5,7 @@ import ContractsList from './ContractsList';
const Contracts = ({contracts, title = "Contracts"}) => (
<Row>
<Col>
<Col className="mt-3">
<Card>
<CardBody>
<Row>
@ -13,7 +13,7 @@ const Contracts = ({contracts, title = "Contracts"}) => (
<CardTitle className="mb-0">Contracts</CardTitle>
</Col>
</Row>
<div style={{ marginTop: 40 + 'px' }}>
<div className="mt-5">
<ContractsList contracts={contracts}></ContractsList>
</div>
</CardBody>

View File

@ -10,11 +10,6 @@ import TransactionsContainer from '../containers/TransactionsContainer';
const ExplorerDashboardLayout = () => (
<React.Fragment>
<Row>
<Col>
<h1 className="my-5">Explorer</h1>
</Col>
</Row>
<Row>
<Col>
<AccountsContainer />

View File

@ -1,4 +0,0 @@
.fiddle--grid {
margin-left: -30px;
margin-right: -30px;
}

View File

@ -1,29 +0,0 @@
import React from 'react';
import {Row, Col} from 'reactstrap';
import TextEditorAsideContainer from '../containers/TextEditorAsideContainer';
import TextEditorContainer from '../containers/TextEditorContainer';
import FileExplorerContainer from '../containers/FileExplorerContainer';
import './FiddleLayout.css';
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
class FiddleLayout extends React.Component {
render() {
return (
<Row noGutters className="h-100 fiddle--grid">
<Col sm={4} md={2}>
<FileExplorerContainer />
</Col>
<Col sm={8} md={6}>
<TextEditorContainer defaultFile={DEFAULT_FILE} />
</Col>
<Col sm={12} md={4}>
<TextEditorAsideContainer defaultFile={DEFAULT_FILE} />
</Col>
</Row>
);
}
}
export default FiddleLayout;

View File

@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Treebeard, decorators} from 'react-treebeard';
import {Input, Label, FormGroup} from 'reactstrap';
const style = {
tree: {
@ -107,9 +106,7 @@ decorators.Header = Header;
class FileExplorer extends React.Component {
constructor(props) {
super(props);
this.state = {
showHidden: false
};
this.state = {cursor: null};
}
onToggle(node, toggled) {
@ -122,10 +119,6 @@ class FileExplorer extends React.Component {
this.setState({cursor: node});
}
onHiddenToggle(e) {
this.setState({showHidden: e.target.checked});
}
nodeEquals(a, b) {
return a && b && a.path && b.path && a.name && b.name &&
a.path === b.path &&
@ -138,20 +131,21 @@ class FileExplorer extends React.Component {
b.path.indexOf(a.path) > -1;
}
filterHidden(nodes) {
data(nodes) {
let filtered = [];
if (!Array.isArray(nodes)) return filtered;
const cursor = this.state.cursor;
const showHidden = this.props.showHiddenFiles;
// we need a foreach to build an array instead of a
// filter to prevent mutating the original object (in props)
nodes.forEach(node => {
const {showHidden, cursor} = this.state;
if (!showHidden && node.isHidden) return;
let updatedNode = {...node};
// if it's a folder, filter the children
if (node.children) {
const children = this.filterHidden(node.children);
const children = this.data(node.children);
if (children.length) {
updatedNode.children = children;
}
@ -178,14 +172,8 @@ class FileExplorer extends React.Component {
render() {
return (
<div className="h-100 d-flex flex-column">
<FormGroup check>
<Label check>
<Input type="checkbox" onChange={this.onHiddenToggle.bind(this)} />
Show hidden files
</Label>
</FormGroup>
<Treebeard
data={this.filterHidden(this.props.files)}
data={this.data(this.props.files)}
decorators={decorators}
onToggle={this.onToggle.bind(this)}
style={style}
@ -197,7 +185,8 @@ class FileExplorer extends React.Component {
FileExplorer.propTypes = {
files: PropTypes.array,
fetchFile: PropTypes.func
fetchFile: PropTypes.func,
showHiddenFiles: PropTypes.bool,
};
export default FileExplorer;

View File

@ -131,7 +131,7 @@ class Layout extends React.Component {
<DropdownToggle nav>
<FontAwesome name='bars'/>
</DropdownToggle>
<DropdownMenu left style={{ left: 'auto' }}>
<DropdownMenu>
{HEADER_NAV_ITEMS.map((item) => (
<DropdownItem key={item.to} to={item.to} tag={Link}>
<FontAwesome className="mr-2" name={item.icon} />
@ -220,7 +220,7 @@ class Layout extends React.Component {
{searchResult.error}
</Alert>
<Container fluid className="h-100" style={{marginTop: '24px'}}>
<Container fluid className="h-100">
{children}
</Container>
</main>

View File

@ -29,7 +29,7 @@ Process.propTypes = {
};
const Processes = ({processes}) => (
<Row>
<Row className="mt-3">
{processes.map((process) => <Process key={process.name} process={process} />)}
</Row>
);

View File

@ -7,7 +7,7 @@ import './TextEditor.css';
const SUPPORTED_LANGUAGES = ['css', 'sol', 'html', 'json'];
const DEFAULT_LANGUAGE = 'javascript';
const EDITOR_ID = 'react-monaco-editor-container';
const GUTTER_GLYPH_MARGIN = 3;
const GUTTER_GLYPH_MARGIN = 2;
let editor;
@ -62,19 +62,20 @@ class TextEditor extends React.Component {
}
updateMarkers() {
const {errors, warnings} = this.props.contractCompile;
const markers = [].concat(errors).concat(warnings).filter((e) => e).map((e) => {
const {row, col} = this.extractRowCol(e.formattedMessage);
return {
startLineNumber: row,
startColumn: col,
endLineNumber: row,
endColumn: col + 1,
message: e.formattedMessage,
severity: e.severity
};
});
monaco.editor.setModelMarkers(editor.getModel(), 'test', markers);
// TODO: Fetch Result of compilation in embark, via ws
// const {errors, warnings} = this.props.contractCompile;
// const markers = [].concat(errors).concat(warnings).filter((e) => e).map((e) => {
// const {row, col} = this.extractRowCol(e.formattedMessage);
// return {
// startLineNumber: row,
// startColumn: col,
// endLineNumber: row,
// endColumn: col + 1,
// message: e.formattedMessage,
// severity: e.severity
// };
// });
// monaco.editor.setModelMarkers(editor.getModel(), 'test', markers);
}
updateLanguage() {
@ -121,6 +122,7 @@ class TextEditor extends React.Component {
this.updateDecorations();
}
this.updateLanguage();
this.handleResize();
}
render() {
@ -135,7 +137,6 @@ class TextEditor extends React.Component {
TextEditor.propTypes = {
onFileContentChange: PropTypes.func,
file: PropTypes.object,
contractCompile: PropTypes.object,
toggleBreakpoint: PropTypes.func,
breakpoints: PropTypes.array,
debuggerLine: PropTypes.number

View File

@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardHeader, CardTitle, CardBody} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import ContractFunctions from '../components/ContractFunctions';
const TextEditorContractDeploy = (props) => {
const name = Object.keys(props.result)[0];
const profile = {
name,
methods: props.result[name].abiDefinition
};
return (
<Card className="bg-success">
<CardHeader>
<CardTitle>
<FontAwesomeIcon className="mr-1" name="check"/>
Deploy Contract
</CardTitle>
</CardHeader>
<CardBody>
<ContractFunctions contractProfile={profile}
contractFunctions={props.contractDeploys}
onlyConstructor
postContractFunction={props.postContractDeploy}/>
</CardBody>
</Card>
);
};
TextEditorContractDeploy.propTypes = {
result: PropTypes.object,
postContractDeploy: PropTypes.func,
contractDeploys: PropTypes.array
};
export default TextEditorContractDeploy;

View File

@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardBody, CardHeader, CardTitle, ListGroup, ListGroupItem} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
const TextEditorContractErrors = (props) => (
<Card className="bg-danger">
<CardHeader>
<CardTitle>
<FontAwesomeIcon className="mr-1" name="minus-circle"/>
Failed to compile
</CardTitle>
</CardHeader>
<CardBody>
<ListGroup>
{props.errors.map((error, index) => <ListGroupItem key={index}>{error.formattedMessage}</ListGroupItem>)}
</ListGroup>
</CardBody>
</Card>
);
TextEditorContractErrors.propTypes = {
errors: PropTypes.array
};
export default TextEditorContractErrors;

View File

@ -1,32 +0,0 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Col, Badge} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import TextEditorToolbar from './TextEditorToolbar';
class TextEditorContractToolbar extends Component {
render(){
return (
<React.Fragment>
<TextEditorToolbar {...this.props} />
<Col md={6} className="text-right">
{this.props.compilingContract &&
<Badge color="warning"><FontAwesomeIcon name="exclamation-triangle " className="mr-1" />compiling</Badge>}
{!this.props.compilingContract && this.props.contractCompile.result &&
<Badge color="success"><FontAwesomeIcon name="check" className="mr-1" />compiled</Badge>}
</Col>
</React.Fragment>
);
}
}
TextEditorContractToolbar.propTypes = {
currentFile: PropTypes.object,
contractCompile: PropTypes.object,
compilingContract: PropTypes.bool,
deploy: PropTypes.func,
save: PropTypes.func,
remove: PropTypes.func
};
export default TextEditorContractToolbar;

View File

@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardHeader, CardBody, CardTitle, ListGroup, ListGroupItem} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
const TextEditorContractWarnings = (props) => (
<Card className="bg-warning">
<CardHeader>
<CardTitle color="warning">
<FontAwesomeIcon className="mr-1" name="exclamation-triangle"/>
Warning during compilation
</CardTitle>
</CardHeader>
<CardBody>
<ListGroup>
{props.warnings.map((warning, index) => <ListGroupItem key={index}>{warning.formattedMessage}</ListGroupItem>)}
</ListGroup>
</CardBody>
</Card>
);
TextEditorContractWarnings.propTypes = {
warnings: PropTypes.array
};
export default TextEditorContractWarnings;

View File

@ -1,21 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Col, Button} from 'reactstrap';
import {Row, Label, Col, Button} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import { AppSwitch } from '@coreui/react'
const TextEditorToolbar = (props) => (
<Col md={6}>
<strong>{props.currentFile.name}</strong>
<span className="mx-2">|</span>
<Button color="green" size="sm" icon="save" onClick={props.save}>Save</Button>
<span className="mx-2">|</span>
<Button color="red" size="sm" icon="delete" onClick={props.remove}>Delete</Button>
</Col>
<Row>
<Col sm={4} md={2}>
<Label className="mb-0 pt-1">
<AppSwitch color='success' variant='pill' size='sm' onChange={props.toggleShowHiddenFiles}/>
<span className="ml-1 align-top">Show hidden files</span>
</Label>
</Col>
<Col sm={4} md={6}>
<strong>{props.currentFile.name}</strong>
<span className="mx-2">|</span>
<Button color="success" size="sm" onClick={props.save}>
<FontAwesomeIcon className="mr-2" name="save"/>
Save
</Button>
<span className="mx-2">|</span>
<Button color="danger" size="sm" onClick={props.remove}>
<FontAwesomeIcon className="mr-2" name="trash"/>
Delete
</Button>
</Col>
<Col sm={4} md={4}>
<div className="float-right mr-2">
{props.isContract &&
<React.Fragment>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('overview')}>
Overview
</Button>
<span className="mx-2">|</span>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('detail')}>
Detail
</Button>
<span className="mx-2">|</span>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('logger')}>
Logger
</Button>
<span className="mx-2">|</span>
</React.Fragment>
}
<Button size="sm" color="primary" onClick={() => props.openAsideTab('browser')}>
Browser
</Button>
</div>
</Col>
</Row>
);
TextEditorToolbar.propTypes = {
currentFile: PropTypes.object,
isContract: PropTypes.bool,
save: PropTypes.func,
remove: PropTypes.func
remove: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func
};
export default TextEditorToolbar;

View File

@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
import Description from './Description';
import CardTitleIdenticon from './CardTitleIdenticon';
import {utils} from 'web3';
const Transaction = ({transaction}) => (
<Row>
@ -18,7 +20,7 @@ const Transaction = ({transaction}) => (
<Description label="Block" value={<Link to={`/embark/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link>} />
<Description label="From" value={transaction.from} />
<Description label="To" value={transaction.to} />
<Description label="Value" value={`${transaction.value} Wei`}/>
<Description label="Value" value={`${utils.fromWei(transaction.value)} Ether`}/>
<Description label="Input" value={transaction.input} />
<Description label="Gas" value={transaction.gas} />
<Description label="Gas price" value={`${transaction.gasPrice} Wei`} />

View File

@ -12,9 +12,11 @@ const Transactions = ({transactions}) => (
{transactions.map(transaction => (
<Card key={transaction.hash}>
<CardHeader>
<Link to={`/embark/explorer/transactions/${transaction.hash}`}>
<CardTitleIdenticon id={transaction.hash}>Transaction {transaction.hash}</CardTitleIdenticon>
</Link>
<CardTitleIdenticon id={transaction.hash}>Transaction&nbsp;
<Link to={`/embark/explorer/transactions/${transaction.hash}`}>
{transaction.hash}
</Link>
</CardTitleIdenticon>
</CardHeader>
<CardBody>
<Row>

View File

@ -1,36 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import Contract from '../components/Contract';
import DataWrapper from "../components/DataWrapper";
import {getContract} from "../reducers/selectors";
class ContractContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.contract !== undefined } {...this.props} render={({contract}) => (
<Contract contract={contract} />
)} />
);
}
}
function mapStateToProps(state, props) {
return {
contract: getContract(state, props.match.params.contractName),
error: state.errorMessage,
loading: state.loading
};
}
ContractContainer.propTypes = {
match: PropTypes.object,
contract: PropTypes.object,
error: PropTypes.string
};
export default withRouter(connect(
mapStateToProps
)(ContractContainer));

View File

@ -3,12 +3,12 @@ import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {contractProfile as contractProfileAction, contractFunction as contractFunctionAction} from '../actions';
import ContractFunctions from '../components/ContractFunctions';
import ContractOverview from '../components/ContractOverview';
import DataWrapper from "../components/DataWrapper";
import GasStationContainer from "../containers/GasStationContainer";
import {getContractProfile, getContractFunctions} from "../reducers/selectors";
class ContractFunctionsContainer extends Component {
class ContractOverviewContainer extends Component {
componentDidMount() {
this.props.fetchContractProfile(this.props.contract.className);
}
@ -19,8 +19,9 @@ class ContractFunctionsContainer extends Component {
{...this.props}
render={({contractProfile, contractFunctions, postContractFunction}) => (
<React.Fragment>
<ContractFunctions contractProfile={contractProfile}
<ContractOverview contractProfile={contractProfile}
contractFunctions={contractFunctions}
contract={this.props.contract}
postContractFunction={postContractFunction}/>
<GasStationContainer/>
@ -39,7 +40,7 @@ function mapStateToProps(state, props) {
};
}
ContractFunctionsContainer.propTypes = {
ContractOverviewContainer.propTypes = {
contract: PropTypes.object,
contractProfile: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object),
@ -54,4 +55,4 @@ export default connect(
fetchContractProfile: contractProfileAction.request,
postContractFunction: contractFunctionAction.post
}
)(ContractFunctionsContainer);
)(ContractOverviewContainer);

View File

@ -0,0 +1,4 @@
.editor--grid {
margin-left: -30px !important;
margin-right: -30px !important;
}

View File

@ -0,0 +1,103 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {Row, Col} from 'reactstrap';
import TextEditorAsideContainer from './TextEditorAsideContainer';
import TextEditorContainer from './TextEditorContainer';
import FileExplorerContainer from './FileExplorerContainer';
import TextEditorToolbarContainer from './TextEditorToolbarContainer';
import {currentFile as currentFileAction} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import './EditorContainer.css';
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
class EditorContainer extends React.Component {
constructor(props) {
super(props)
this.state = {currentAsideTab: '', showHiddenFiles: false, currentFile: this.props.currentFile}
}
componentDidMount() {
if(this.props.currentFile.content === '') {
this.props.fetchCurrentFile();
}
}
componentDidUpdate(prevProps) {
if(this.props.currentFile.path !== prevProps.currentFile.path) {
this.setState({currentFile: this.props.currentFile});
}
}
isContract() {
return this.state.currentFile.name.endsWith('.sol');
}
onFileContentChange(newContent) {
const newCurrentFile = this.state.currentFile;
newCurrentFile.content = newContent;
this.setState({currentFile: newCurrentFile});
}
toggleShowHiddenFiles() {
this.setState({showHiddenFiles: !this.state.showHiddenFiles});
}
openAsideTab(newTab) {
if (newTab === this.state.currentAsideTab) {
return this.setState({currentAsideTab: ''})
}
this.setState({currentAsideTab: newTab})
}
textEditorMdSize() {
return this.state.currentAsideTab.length ? 5 : 10
}
textEditorXsSize() {
return this.state.currentAsideTab.length ? 2 : 8
}
render() {
return (
<Row noGutters className="h-100 editor--grid">
<Col xs={12}>
<TextEditorToolbarContainer toggleShowHiddenFiles={() => this.toggleShowHiddenFiles()}
openAsideTab={(newTab) => this.openAsideTab(newTab)}
isContract={this.isContract()}
currentFile={this.props.currentFile} />
</Col>
<Col xs={4} md={2}>
<FileExplorerContainer showHiddenFiles={this.state.showHiddenFiles} />
</Col>
<Col xs={this.textEditorXsSize()} md={this.textEditorMdSize()}>
<TextEditorContainer currentFile={this.props.currentFile} onFileContentChange={(newContent)=> this.onFileContentChange(newContent)} />
</Col>
{this.state.currentAsideTab && <Col xs={6} md={5}>
<TextEditorAsideContainer currentAsideTab={this.state.currentAsideTab} currentFile={this.props.currentFile} />
</Col>}
</Row>
);
}
}
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || DEFAULT_FILE;
return {
currentFile
};
}
EditorContainer.propTypes = {
currentFile: PropTypes.object,
fetchCurrentFile: PropTypes.func
};
export default connect(
mapStateToProps,
{fetchCurrentFile: currentFileAction.request},
)(EditorContainer);

View File

@ -1,41 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {contracts as contractsAction} from '../actions';
import ContractLayout from '../components/ContractLayout';
import {getContractsByPath} from "../reducers/selectors";
class FileContractsContainer extends Component {
componentDidMount() {
this.props.fetchContracts();
}
render() {
return (
this.props.contracts.map(contract => <ContractLayout contract={contract} />)
)
}
}
function mapStateToProps(state, props) {
return {
contracts: getContractsByPath(state, props.currentFile.path),
error: state.errorMessage,
loading: state.loading
};
}
FileContractsContainer.propTypes = {
contracts: PropTypes.arrayOf(PropTypes.object),
fetchContractsByPath: PropTypes.func,
error: PropTypes.string,
loading: PropTypes.bool
};
export default connect(
mapStateToProps,
{
fetchContracts: contractsAction.request
}
)(FileContractsContainer);

View File

@ -14,21 +14,22 @@ class FileExplorerContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile}) => (
<FileExplorer files={files} fetchFile={fetchFile} />
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles}) => (
<FileExplorer files={files} fetchFile={fetchFile} showHiddenFiles={showHiddenFiles} />
)} />
);
}
}
function mapStateToProps(state) {
return {files: getFiles(state), error: state.errorMessage, loading: state.loading};
return {files: getFiles(state)};
}
FileExplorerContainer.propTypes = {
files: PropTypes.array,
fetchFiles: PropTypes.func,
fetchFile: PropTypes.func
fetchFile: PropTypes.func,
showHiddenFiles: PropTypes.bool,
};
export default connect(

View File

@ -1,59 +1,79 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {
currentFile as currentFileAction,
} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import { Card, CardBody, CardTitle } from 'reactstrap';
import Preview from '../components/Preview';
import FileContractsContainer from './FileContractsContainer';
import {contracts as contractsAction} from '../actions';
import {getContractsByPath} from "../reducers/selectors";
import ContractDetail from '../components/ContractDetail';
import ContractLoggerContainer from '../containers/ContractLoggerContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
class TextEditorAsideContainer extends Component {
constructor(props) {
super(props);
this.state = {currentFile: this.props.currentFile};
}
componentDidMount() {
this.props.fetchCurrentFile();
}
componentDidUpdate(prevProps) {
if(this.props.currentFile.path !== prevProps.currentFile.path) {
this.setState({currentFile: this.props.currentFile});
}
}
isContract() {
return this.state.currentFile.name.endsWith('.sol');
this.props.fetchContracts();
}
render() {
return this.isContract() ? <FileContractsContainer currentFile={this.state.currentFile} /> : <Preview />
switch(this.props.currentAsideTab) {
case 'browser':
return <Preview />
case 'detail':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Details</CardTitle>
<ContractDetail key={index} contract={contract} />
</CardBody>
</Card>
)
})
case 'logger':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Transactions</CardTitle>
<ContractLoggerContainer key={index} contract={contract} />)
</CardBody>
</Card>
)
})
case 'overview':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Overview</CardTitle>
<ContractOverviewContainer key={index} contract={contract} />
</CardBody>
</Card>
)
})
default:
return <React.Fragment></React.Fragment>;
}
}
}
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || props.defaultFile
return {
currentFile,
loading: state.loading,
error: state.errorMessage
contracts: getContractsByPath(state, props.currentFile.path)
};
}
TextEditorAsideContainer.propTypes = {
currentFile: PropTypes.object,
fetchCurrentFile: PropTypes.func,
loading: PropTypes.bool,
error: PropTypes.string,
defaultFile: PropTypes.object
currentAsideTab: PropTypes.string,
contract: PropTypes.array,
fetchContracts: PropTypes.func
};
export default connect(
mapStateToProps,
{
fetchCurrentFile: currentFileAction.request,
fetchContracts: contractsAction.request,
},
)(TextEditorAsideContainer);

View File

@ -1,157 +1,32 @@
import React, {Component} from 'react';
import React from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import TextEditor from '../components/TextEditor';
import TextEditorContractErrors from '../components/TextEditorContractErrors';
import TextEditorContractWarnings from '../components/TextEditorContractWarnings';
import TextEditorContractToolbar from '../components/TextEditorContractToolbar';
import TextEditorContractDeploy from '../components/TextEditorContractDeploy';
import TextEditorToolbar from '../components/TextEditorToolbar';
import {
currentFile as currentFileAction,
saveCurrentFile as saveCurrentFileAction,
saveFile as saveFileAction,
removeFile as removeFileAction,
contractCompile as contractCompileAction,
contractDeploy as postContractDeploy,
toggleBreakpoint,
} from '../actions';
import {getCurrentFile, getContractCompile, getContractDeploys, getBreakpointsByFilename} from '../reducers/selectors';
import {getBreakpointsByFilename} from '../reducers/selectors';
class TextEditorContainer extends Component {
constructor(props) {
super(props);
this.state = {currentFile: this.props.currentFile};
}
componentDidMount() {
if(this.props.currentFile.content === '') {
this.props.fetchCurrentFile();
}
}
componentDidUpdate(prevProps) {
if(this.props.currentFile.path !== prevProps.currentFile.path) {
this.setState({currentFile: this.props.currentFile});
}
}
isContract() {
return this.state.currentFile.name.endsWith('.sol');
}
onFileContentChange(newContent) {
const newCurrentFile = this.state.currentFile;
newCurrentFile.content = newContent;
this.setState({currentFile: newCurrentFile});
if (!this.isContract()) return;
this.compileTimeout = setTimeout(() => {
this.props.compileContract(newContent, this.state.currentFile.name);
}, 1000);
}
save() {
this.props.saveFile(this.state.currentFile);
this.props.saveCurrentFile(this.state.currentFile);
}
remove() {
this.props.removeFile(this.state.currentFile);
this.setState({currentFile: this.props.defaultFile});
}
renderContractFooter() {
if (!this.isContract()) return <React.Fragment />;
let components = [];
const {errors, warnings, result} = this.props.contractCompile;
if (errors && errors.length > 0) {
components.push(<TextEditorContractErrors key={1} errors={errors} />);
}
if (warnings && warnings.length > 0) {
components.push(<TextEditorContractWarnings key={2} warnings={warnings} />);
}
if (result) {
components.push(<TextEditorContractDeploy key={3}
result={result}
postContractDeploy={this.props.postContractDeploy}
contractDeploys={this.props.contractDeploys}/>);
}
return <React.Fragment>{components}</React.Fragment>;
}
renderToolbar(){
if (this.isContract()) {
return <TextEditorContractToolbar currentFile={this.state.currentFile}
contractCompile={this.props.contractCompile}
compilingContract={this.props.compilingContract}
save={() => this.save()}
remove={() => this.remove()} />;
}
return <TextEditorToolbar currentFile={this.state.currentFile}
contractCompile={this.props.contractCompile}
save={() => this.save()}
remove={() => this.remove()} />;
}
render() {
return (
<TextEditor file={this.state.currentFile}
contractCompile={this.props.contractCompile}
breakpoints={this.props.breakpoints}
toggleBreakpoint={this.props.toggleBreakpoint}
onFileContentChange={(newContent) => this.onFileContentChange(newContent)} />
);
}
}
const TextEditorContainer = (props) => (
<TextEditor file={props.currentFile}
breakpoints={props.breakpoints}
toggleBreakpoint={props.toggleBreakpoint}
onFileContentChange={props.onFileContentChange} />
)
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || props.defaultFile;
const contractCompile = getContractCompile(state, currentFile) || {};
const contractName = contractCompile.result && Object.keys(contractCompile.result)[0];
const contractDeploys = getContractDeploys(state, contractName);
const breakpoints = getBreakpointsByFilename(state, currentFile.name);
return {
compilingContract: state.compilingContract,
loading: state.loading,
error: state.errorMessage,
currentFile,
contractCompile,
contractDeploys,
breakpoints,
};
const breakpoints = getBreakpointsByFilename(state, props.currentFile.name);
return {breakpoints};
}
TextEditorContainer.propTypes = {
defaultFile: PropTypes.object,
currentFile: PropTypes.object,
contractCompile: PropTypes.object,
saveCurrentFile: PropTypes.func,
fetchCurrentFile: PropTypes.func,
saveFile: PropTypes.func,
removeFile: PropTypes.func,
postContractDeploy: PropTypes.func,
compileContract: PropTypes.func,
compilingContract: PropTypes.bool,
contractDeploys: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.string,
onFileContentChange: PropTypes.func,
toggleBreakpoints: PropTypes.func,
breakpoints: PropTypes.array,
};
export default connect(
mapStateToProps,
{
fetchCurrentFile: currentFileAction.request,
saveCurrentFile: saveCurrentFileAction.request,
saveFile: saveFileAction.request,
removeFile: removeFileAction.request,
postContractDeploy: postContractDeploy.post,
compileContract: contractCompileAction.post,
toggleBreakpoint,
},
{toggleBreakpoint},
)(TextEditorContainer);

View File

@ -0,0 +1,45 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import TextEditorToolbar from '../components/TextEditorToolbar';
import {
saveFile as saveFileAction,
removeFile as removeFileAction,
} from '../actions';
class TextEditorToolbarContainer extends Component {
save() {
this.props.saveFile(this.props.currentFile);
}
remove() {
this.props.removeFile(this.props.currentFile);
}
render() {
return <TextEditorToolbar currentFile={this.props.currentFile}
isContract={this.props.isContract}
toggleShowHiddenFiles={this.props.toggleShowHiddenFiles}
openAsideTab={this.props.openAsideTab}
save={() => this.save()}
remove={() => this.remove()} />;
}
}
TextEditorToolbarContainer.propTypes = {
currentFile: PropTypes.object,
isContract: PropTypes.bool,
saveFile: PropTypes.func,
removeFile: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func
};
export default connect(
null,
{
saveFile: saveFileAction.request,
removeFile: removeFileAction.request
},
)(TextEditorToolbarContainer);

View File

@ -1,6 +1,6 @@
.nav-tabs {
.nav-link {
color: $gray-600;
color: $white;
&:hover {
cursor: pointer;
}

View File

@ -33,7 +33,7 @@ $navbar-brand-minimized-width: 50px !default;
$navbar-brand-minimized-bg: $navbar-brand-bg !default;
$navbar-brand-minimized-border: $navbar-brand-border !default;
$navbar-color: $gray-300 !default;
$navbar-color: $white !default;
$navbar-hover-color: $gray-200 !default;
$navbar-active-color: $gray-200 !default;
$navbar-disabled-color: $gray-300 !default;

View File

@ -28,3 +28,11 @@
.react-json-view {
border-radius: .25rem;
}
.collapsable:hover {
cursor: pointer;
}
.contractFunction {
margin-bottom: 0px;
}

View File

@ -4,11 +4,11 @@ import {Route, Switch} from 'react-router-dom';
import HomeContainer from './containers/HomeContainer';
import ContractsContainer from './containers/ContractsContainer';
import ContractLayoutContainer from './containers/ContractLayoutContainer';
import EditorContainer from './containers/EditorContainer';
import DeploymentContainer from './containers/DeploymentContainer';
import NoMatch from './components/NoMatch';
import ExplorerDashboardLayout from './components/ExplorerDashboardLayout';
import ExplorerLayout from './components/ExplorerLayout';
import FiddleLayout from './components/FiddleLayout';
import UtilsLayout from './components/UtilsLayout';
const routes = (
@ -20,7 +20,7 @@ const routes = (
<Route path="/embark/deployment/" component={DeploymentContainer} />
<Route path="/embark/contracts/:contractName" component={ContractLayoutContainer} />
<Route path="/embark/contracts" component={ContractsContainer} />
<Route path="/embark/editor" component={FiddleLayout} />
<Route path="/embark/editor" component={EditorContainer} />
<Route path="/embark/utilities" component={UtilsLayout} />
<Route component={NoMatch} />
</Switch>

View File

@ -200,6 +200,10 @@ export function *watchPostFile() {
yield takeEvery(actions.SAVE_FILE[actions.REQUEST], postFile);
}
export function *watchPostFileSuccess() {
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], postCurrentFile);
}
export function *watchDeleteFile() {
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
}
@ -217,10 +221,6 @@ export function *watchFetchCurrentFile() {
yield takeEvery(actions.CURRENT_FILE[actions.REQUEST], fetchCurrentFile);
}
export function *watchPostCurrentFile() {
yield takeEvery(actions.SAVE_CURRENT_FILE[actions.REQUEST], postCurrentFile);
}
export function *watchFetchEthGas() {
yield takeEvery(actions.GAS_ORACLE[actions.REQUEST], fetchEthGas);
}
@ -433,7 +433,7 @@ export default function *root() {
fork(watchDeleteFileSuccess),
fork(watchFetchFileSuccess),
fork(watchFetchCurrentFile),
fork(watchPostCurrentFile),
fork(watchPostFileSuccess),
fork(watchFetchCredentials),
fork(watchFetchEthGas),
fork(watchAuthenticate),