Merge pull request #150 from status-im/toolbar_cherry_picked
Add toolbar to editor
This commit is contained in:
commit
7eb87f1a25
|
@ -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})
|
||||
};
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
<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>
|
||||
|
|
|
@ -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
|
||||
<Link to={`/embark/explorer/blocks/${block.number}`}>
|
||||
{block.number}
|
||||
</Link>
|
||||
</CardTitleIdenticon>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Row>
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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 => (
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
.fiddle--grid {
|
||||
margin-left: -30px;
|
||||
margin-right: -30px;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,7 +29,7 @@ Process.propTypes = {
|
|||
};
|
||||
|
||||
const Processes = ({processes}) => (
|
||||
<Row>
|
||||
<Row className="mt-3">
|
||||
{processes.map((process) => <Process key={process.name} process={process} />)}
|
||||
</Row>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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`} />
|
||||
|
|
|
@ -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
|
||||
<Link to={`/embark/explorer/transactions/${transaction.hash}`}>
|
||||
{transaction.hash}
|
||||
</Link>
|
||||
</CardTitleIdenticon>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Row>
|
||||
|
|
|
@ -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));
|
|
@ -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);
|
|
@ -0,0 +1,4 @@
|
|||
.editor--grid {
|
||||
margin-left: -30px !important;
|
||||
margin-right: -30px !important;
|
||||
}
|
|
@ -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);
|
||||
|
|
@ -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);
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -1,6 +1,6 @@
|
|||
.nav-tabs {
|
||||
.nav-link {
|
||||
color: $gray-600;
|
||||
color: $white;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -28,3 +28,11 @@
|
|||
.react-json-view {
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.collapsable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contractFunction {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue