mirror of
https://github.com/status-im/embark-area-51.git
synced 2025-02-23 12:38:18 +00:00
Add toolbar to editor
Rename overview to detail React to save file for local storage Add switch for hidden files move units to ether instead of wei separate description from link itself which makes UI cleaner remove unneded explorer header switch nav menu color to white replace contract address table with simple paragraph instead improve contract functions page improve contract functions view style/fix functions tab fix rebase issue re-style contracts sidebar improve contract detail sidebar
This commit is contained in:
parent
f30803946c
commit
00a8d9baea
@ -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>
|
||||
|
@ -6,4 +6,4 @@ const CardTitleIdenticon = ({id, children}) => (
|
||||
<CardTitle><Blockies seed={id} className="rounded"/><span className="ml-2 align-top">{children}</span></CardTitle>
|
||||
)
|
||||
|
||||
export default CardTitleIdenticon
|
||||
export default CardTitleIdenticon
|
||||
|
36
embark-ui/src/components/ContractDetail.js
Normal file
36
embark-ui/src/components/ContractDetail.js
Normal 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;
|
||||
|
@ -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);
|
4
embark-ui/src/containers/EditorContainer.css
Normal file
4
embark-ui/src/containers/EditorContainer.css
Normal file
@ -0,0 +1,4 @@
|
||||
.editor--grid {
|
||||
margin-left: -30px !important;
|
||||
margin-right: -30px !important;
|
||||
}
|
103
embark-ui/src/containers/EditorContainer.js
Normal file
103
embark-ui/src/containers/EditorContainer.js
Normal 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);
|
||||
|
@ -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);
|
||||
|
45
embark-ui/src/containers/TextEditorToolbarContainer.js
Normal file
45
embark-ui/src/containers/TextEditorToolbarContainer.js
Normal 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);
|
@ -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;
|
||||
|
@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
word-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.relative {
|
||||
@ -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…
x
Reference in New Issue
Block a user