Components for Contract UI
This commit is contained in:
parent
6c88ebd874
commit
3de60ef0c7
|
@ -0,0 +1,102 @@
|
||||||
|
h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario p {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario p.note {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario p.error {
|
||||||
|
color: #ff0000;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background: #dedeff;
|
||||||
|
padding: 0px 15px;
|
||||||
|
border: 1px dashed #a0a0ff;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 10px 3px 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code button:hover,
|
||||||
|
.code select:hover,
|
||||||
|
.code input:hover {
|
||||||
|
background: #a0a0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code select {
|
||||||
|
background: none;
|
||||||
|
border: 1px dashed #a0a0ff;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code input {
|
||||||
|
background: none;
|
||||||
|
border: 1px dashed #a0a0ff;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenario {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
code .Highlight-boolean {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-class {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-comment {
|
||||||
|
color: #969896;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-constant {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-function {
|
||||||
|
color: #795da3;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-keyword {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-number {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-operator {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-punctuation {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
code .Highlight-string {
|
||||||
|
color: #df5000;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Contract UI</title>
|
||||||
|
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
|
||||||
|
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
|
||||||
|
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.4.0/languages/javascript.min.js"></script>
|
||||||
|
<script src="./contracts-section.js"></script>
|
||||||
|
</head>
|
||||||
|
<body class="container-fluid">
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,22 @@
|
||||||
|
import 'bootstrap';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import './contracts-section.css';
|
||||||
|
|
||||||
|
import EmbarkJS from 'Embark/EmbarkJS';
|
||||||
|
import IdentityFactory from 'Embark/contracts/IdentityFactory'; // Import all contracts
|
||||||
|
|
||||||
|
import ContractUI from './contracts/contract-ui';
|
||||||
|
|
||||||
|
|
||||||
|
__embarkContext.execWhenReady(function(){
|
||||||
|
|
||||||
|
// Each contract should be available on window
|
||||||
|
window["IdentityFactory"] = IdentityFactory;
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<ContractUI name="IdentityFactory" contract={IdentityFactory} sourceURL="https://raw.githubusercontent.com/status-im/contracts/contracts-ui-demo/contracts/identity/IdentityFactory.sol" />,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,46 @@
|
||||||
|
import ContractContext from './contract-context';
|
||||||
|
|
||||||
|
class AccountList extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
error: false,
|
||||||
|
errorMessage: "",
|
||||||
|
accounts: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async handleClick(e, updateAccountsCallback){
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateAccountsCallback();
|
||||||
|
} catch(err) {
|
||||||
|
this.setState({
|
||||||
|
error: true,
|
||||||
|
errorMessage: e.name + ': ' + e.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return <ContractContext.Consumer>
|
||||||
|
{(context) => (
|
||||||
|
<div>
|
||||||
|
<h3>Get Accounts</h3>
|
||||||
|
<div className="scenario">
|
||||||
|
<div id="getAccounts" className="code">
|
||||||
|
await web3.eth.getAccounts(); <button onClick={event => this.handleClick(event, context.updateAccounts)}>⏎</button>
|
||||||
|
</div>
|
||||||
|
<p className="note"><tt>accounts</tt> variable is available in the console</p>
|
||||||
|
{this.state.error ? '<p className="error">' + this.state.errorMessage + '</p>' : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ContractContext.Consumer>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountList;
|
|
@ -0,0 +1,9 @@
|
||||||
|
const ContractContext = React.createContext({
|
||||||
|
accounts: [],
|
||||||
|
instances: [],
|
||||||
|
updateAccounts: () => {},
|
||||||
|
updateInstances: (_instance) => {}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ContractContext;
|
|
@ -0,0 +1,92 @@
|
||||||
|
import Tab from './tab';
|
||||||
|
import AccountList from './account-list';
|
||||||
|
import SourceArea from './source-area';
|
||||||
|
import InstanceSelector from './instance-selector';
|
||||||
|
import FunctionArea from './function-area';
|
||||||
|
import ContractContext from './contract-context';
|
||||||
|
|
||||||
|
|
||||||
|
class ContractUI extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.updateInstances = this.updateInstances.bind(this);
|
||||||
|
this.updateAccounts = this.updateAccounts.bind(this);
|
||||||
|
this.handleInstanceSelection = this.handleInstanceSelection.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
accounts: [],
|
||||||
|
instances: [],
|
||||||
|
selectedInstance: null,
|
||||||
|
updateAccounts: this.updateAccounts,
|
||||||
|
updateInstances: this.updateInstances
|
||||||
|
};
|
||||||
|
|
||||||
|
if(props.contract.options.address != null){
|
||||||
|
this.state.instances = [props.contract.options.address];
|
||||||
|
this.state.selectedInstance = props.contract.options.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
this.updateAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccounts(){
|
||||||
|
let accounts = await web3.eth.getAccounts();
|
||||||
|
window.accounts = accounts;
|
||||||
|
|
||||||
|
console.log("%cawait web3.eth.getAccounts()", 'font-weight: bold');
|
||||||
|
console.log(accounts);
|
||||||
|
|
||||||
|
this.setState({accounts: accounts});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInstances(_instance){
|
||||||
|
this.state.instances.push(_instance);
|
||||||
|
this.setState({
|
||||||
|
instances: this.state.instances
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInstanceSelection(_instance){
|
||||||
|
this.props.contract.options.address = _instance;
|
||||||
|
this.setState({
|
||||||
|
selectedInstance: _instance
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ContractContext.Provider value={this.state}>
|
||||||
|
<div>
|
||||||
|
<h1><span>{this.props.name}</span> <small>contract</small></h1>
|
||||||
|
<p>Open your browser's console: <code>Tools > Developer Tools</code></p>
|
||||||
|
<p>Remix: <a href="https://remix.ethereum.org">http://remix.ethereum.org</a></p>
|
||||||
|
<ul className="nav nav-tabs" role="tablist" id="myTabs">
|
||||||
|
<li role="presentation" className="active"><a href="#deploy" role="tab" data-toggle="tab">Instance</a></li>
|
||||||
|
<li role="presentation"><a href="#functions" role="tab" data-toggle="tab">Functions</a></li>
|
||||||
|
<li role="presentation"><a href="#contract" role="tab" data-toggle="tab">Contract</a></li>
|
||||||
|
</ul>
|
||||||
|
<div className="tab-content">
|
||||||
|
<Tab id="deploy" name="Deployment / Utils" active={true}>
|
||||||
|
<AccountList accountUpdate={this.updateAccounts} />
|
||||||
|
<h3>Deploy</h3>
|
||||||
|
<FunctionArea contractName={this.props.name} contract={this.props.contract} type="constructor" />
|
||||||
|
</Tab>
|
||||||
|
<Tab id="functions" name="Functions">
|
||||||
|
<InstanceSelector selectedInstance={this.state.selectedInstance} instanceUpdate={this.handleInstanceSelection} />
|
||||||
|
<FunctionArea contractName={this.props.name} contract={this.props.contract} type="function" />
|
||||||
|
<FunctionArea contractName={this.props.name} contract={this.props.contract} type="fallback" />
|
||||||
|
</Tab>
|
||||||
|
<Tab id="contract" name="Contract">
|
||||||
|
<SourceArea sourceURL={this.props.sourceURL} />
|
||||||
|
</Tab>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ContractContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContractUI;
|
|
@ -0,0 +1,24 @@
|
||||||
|
import FunctionForm from './function-form';
|
||||||
|
|
||||||
|
class FunctionArea extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
const type = this.props.type;
|
||||||
|
const contract = this.props.contract;
|
||||||
|
const contractName = this.props.contractName;
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
|
{
|
||||||
|
this.props.contract.options.jsonInterface
|
||||||
|
.filter(item => item.type == type)
|
||||||
|
.map((item, i) => <FunctionForm key={i} contract={contract} contractName={contractName} abi={item} instanceUpdate={this.props.instanceUpdate} />)
|
||||||
|
}
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FunctionArea;
|
|
@ -0,0 +1,88 @@
|
||||||
|
import Function from './function';
|
||||||
|
|
||||||
|
class FunctionForm extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
fields: {},
|
||||||
|
error: false,
|
||||||
|
message: null,
|
||||||
|
receipt: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showResults = this.showResults.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFunctionParamFields(elem){
|
||||||
|
if(this.props.abi.type == 'fallback') return '';
|
||||||
|
|
||||||
|
return '(' + this.props.abi.inputs
|
||||||
|
.map((input, i) => <input type="text" data-var-type={input.type} data-type="inputParam" data-name={input.name} placeholder={input.name} title={input.type + ' ' + input.name} size={input.name.length} />)
|
||||||
|
.join(', ') + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMethodType(elem){
|
||||||
|
return (this.props.abi.constant == true || this.props.abi.stateMutability == 'view' || this.props.abi.stateMutability == 'pure') ? 'call' : 'send';
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
const functionName = this.props.abi.name;
|
||||||
|
const isDuplicated = this.props.contract.options.jsonInterface.filter(x => x.name == functionName).length > 1;
|
||||||
|
const contract = this.props.contract;
|
||||||
|
const receipt = this.state.receipt;
|
||||||
|
|
||||||
|
return <div className="function">
|
||||||
|
<h4>{this.props.abi.type == 'function' ? this.props.abi.name : (this.props.abi.type == 'fallback' ? '(fallback)' : this.props.abi.name)}</h4>
|
||||||
|
<div className="scenario">
|
||||||
|
<div className="code">
|
||||||
|
<Function contract={this.props.contract} contractName={this.props.contractName} duplicated={isDuplicated} abi={this.props.abi} resultHandler={this.showResults} />
|
||||||
|
</div>
|
||||||
|
{ receipt != null ?
|
||||||
|
<ul>
|
||||||
|
<li>Status: {receipt.status}</li>
|
||||||
|
<li>Transaction Hash: {receipt.transactionHash}</li>
|
||||||
|
{
|
||||||
|
receipt.events != null ?
|
||||||
|
<li>Events:
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
Object.keys(receipt.events).map(function(ev, index) {
|
||||||
|
if(!isNaN(ev)) return null;
|
||||||
|
const eventAbi = contract.options.jsonInterface.filter(x => x.name == ev)[0];
|
||||||
|
let props = [];
|
||||||
|
for(let prop in receipt.events[ev].returnValues){
|
||||||
|
if(isNaN(prop)){
|
||||||
|
let input = eventAbi.inputs.filter(x => x.name == prop)[0];
|
||||||
|
props.push(prop + ': '
|
||||||
|
+ (input.type.indexOf('int') == -1 ? '"' : '')
|
||||||
|
+ receipt.events[ev].returnValues[prop]
|
||||||
|
+ (input.type.indexOf('int') == -1 ? '"' : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <li key={index}>{ev}({props.join(', ')})</li>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
{this.state.error ? <p className="error">{this.state.message}</p> : '' }
|
||||||
|
{!this.state.error && this.state.message != null ? <p className="note">{this.state.message}</p> : '' }
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
showResults(_error, _message, _receipt){
|
||||||
|
this.setState({
|
||||||
|
error: _error,
|
||||||
|
message: _message,
|
||||||
|
receipt: _receipt
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FunctionForm;
|
|
@ -0,0 +1,219 @@
|
||||||
|
import ContractContext from './contract-context';
|
||||||
|
|
||||||
|
class Function extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
onRequest: false,
|
||||||
|
fields: {},
|
||||||
|
methodFields: {
|
||||||
|
from: '',
|
||||||
|
to: '',
|
||||||
|
value: 0,
|
||||||
|
data: '',
|
||||||
|
gasLimit: '7000000'
|
||||||
|
},
|
||||||
|
receipt: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleParameterChange = this.handleParameterChange.bind(this);
|
||||||
|
this.handleMethodFieldChange = this.handleMethodFieldChange.bind(this);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleClick(e, instanceUpdateCallback){
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.setState({onRequest: true, receipt: null});
|
||||||
|
|
||||||
|
this.props.resultHandler(false, null, null);
|
||||||
|
|
||||||
|
let executionParams = {
|
||||||
|
from: this.state.methodFields.from,
|
||||||
|
gasLimit: this.state.methodFields.gasLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.props.abi.payable)
|
||||||
|
executionParams.value = this.state.methodFields.value;
|
||||||
|
|
||||||
|
if(this.props.abi.type == 'fallback'){
|
||||||
|
executionParams.data = this.state.methodFields.data;
|
||||||
|
executionParams.to = this.props.contract.options.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields = this.state.fields;
|
||||||
|
|
||||||
|
let functionLabel = this._getFunctionLabel();
|
||||||
|
let functionParams = this._getFunctionParamString();
|
||||||
|
let methodParams = this._getMethodString();
|
||||||
|
if(this.props.abi.type == "constructor")
|
||||||
|
functionParams = `{arguments: [${functionParams}]}`;
|
||||||
|
|
||||||
|
console.log(`%cawait ${functionLabel}(${functionParams})${this.props.abi.type != 'fallback' ? '.' + this._getMethodType() : ''}${methodParams}`, 'font-weight: bold');
|
||||||
|
|
||||||
|
let _receipt;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(this.props.abi.type == 'constructor'){
|
||||||
|
let contractInstance = await this.props.contract.deploy({arguments: Object.keys(fields).map(val => fields[val])}).send(executionParams);
|
||||||
|
instanceUpdateCallback(contractInstance.options.address);
|
||||||
|
this.setState({onRequest: false});
|
||||||
|
console.log(contractInstance.options.address);
|
||||||
|
this.props.resultHandler(false, 'New instance: ' + contractInstance.options.address);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if(this.props.abi.type == 'fallback')
|
||||||
|
_receipt = await this.web3.eth.sendTransaction(executionParams);
|
||||||
|
else
|
||||||
|
_receipt = await this.props.contract
|
||||||
|
.methods[this.props.abi.name + '(' + this.props.abi.inputs.map(input => input.type).join(',') + ')']
|
||||||
|
.apply(null, Object.keys(fields).map(val => fields[val]))
|
||||||
|
[this._getMethodType()](executionParams)
|
||||||
|
|
||||||
|
if(this._getMethodType() == 'call'){
|
||||||
|
this.props.resultHandler(false, _receipt, null);
|
||||||
|
} else {
|
||||||
|
this.props.resultHandler(false, null, _receipt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({onRequest: false, receipt: _receipt});
|
||||||
|
console.log(_receipt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('%s: %s', e.name, e.message);
|
||||||
|
this.setState({onRequest: false});
|
||||||
|
this.props.resultHandler(true, e.name + ": " + e.message, _receipt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleParameterChange(e){
|
||||||
|
let newState = this.state;
|
||||||
|
newState.fields[e.target.getAttribute('data-name')] = e.target.value;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMethodFieldChange(e){
|
||||||
|
let newState = this.state;
|
||||||
|
newState.methodFields[e.target.getAttribute('data-param')] = e.target.value;
|
||||||
|
|
||||||
|
if(e.target.getAttribute('data-param') == 'from'){
|
||||||
|
newState.selectedAccount = e.target.options[e.target.selectedIndex].text;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFunctionLabel(){
|
||||||
|
if(this.props.abi.type == 'function')
|
||||||
|
if(!this.props.duplicated)
|
||||||
|
return `${this.props.contractName}.methods.${this.props.abi.name}`;
|
||||||
|
else {
|
||||||
|
return `${this.props.contractName}.methods['${this.props.abi.name + '(' + (this.props.abi.inputs != null ? this.props.abi.inputs.map(input => input.type).join(',') : '') + ')'}']`;
|
||||||
|
}
|
||||||
|
else if(this.props.abi.type == 'fallback'){
|
||||||
|
return `web3.eth.sendTransaction`;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return `${this.props.contractName}.deploy`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMethodType(){
|
||||||
|
return (this.props.abi.constant == true || this.props.abi.stateMutability == 'view' || this.props.abi.stateMutability == 'pure') ? 'call' : 'send';
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMethodFields(accounts){
|
||||||
|
let methodParams;
|
||||||
|
return <React.Fragment>
|
||||||
|
from: <select data-param="from" disabled={accounts.length == 0} value={this.state.from} onChange={this.handleMethodFieldChange}>
|
||||||
|
<option>-</option>
|
||||||
|
{
|
||||||
|
accounts.map(function (item, i) {
|
||||||
|
return <option key={i} value={item}>{`accounts[${i}]`}</option>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
{
|
||||||
|
this.props.abi.payable ?
|
||||||
|
<span>, value:
|
||||||
|
<input type="text" data-param="value" value={this.state.methodFields.value} size="6" onChange={this.handleMethodFieldChange} />
|
||||||
|
</span>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this._getMethodType() == 'send' ?
|
||||||
|
<span>, gasLimit:
|
||||||
|
<input type="text" data-param="gasLimit" value={this.state.methodFields.gasLimit} size="6" onChange={this.handleMethodFieldChange} />
|
||||||
|
</span>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this._getMethodType() == 'send' && this.props.abi.type == 'fallback' ?
|
||||||
|
<span>, data:
|
||||||
|
<input type="text" data-param="data" value={this.state.methodFields.data} size="6" onChange={this.handleMethodFieldChange} />
|
||||||
|
</span>
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_getFunctionParamFields(){
|
||||||
|
return <React.Fragment>
|
||||||
|
{
|
||||||
|
this.props.abi.inputs
|
||||||
|
.map((input, i) => <input key={i} type="text" data-var-type={input.type} data-type="inputParam" data-name={input.name} placeholder={input.name} title={input.type + ' ' + input.name} size={input.name.length} value={this.state.fields[input.name] || ''} size="6" onChange={this.handleParameterChange} />)
|
||||||
|
.reduce((accu, elem) => {
|
||||||
|
return accu === null ? [elem] : [...accu, ', ', elem]
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFunctionParamString(){
|
||||||
|
if(this.props.abi.type == 'fallback') return '';
|
||||||
|
return this.props.abi.inputs
|
||||||
|
.map((input, i) => (input.type.indexOf('int') == -1 ? '"' : '') + this.state.fields[input.name] + (input.type.indexOf('int') == -1 ? '"' : ''))
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
_getMethodString(elem){
|
||||||
|
let methodParams = "({";
|
||||||
|
|
||||||
|
methodParams += `from: ` + this.state.selectedAccount;
|
||||||
|
if(this._getMethodType() == 'send'){
|
||||||
|
methodParams += ', gasLimit: ' + this.state.methodFields.gasLimit
|
||||||
|
if(this.props.abi.payable){
|
||||||
|
methodParams += ', value: ' + this.state.methodFields.value
|
||||||
|
}
|
||||||
|
if(this.props.abi.type == 'fallback'){
|
||||||
|
methodParams += ', data: "' + this.state.methodFields.data + '", to: "' + this.state.methodFields.to + '"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methodParams + "})";
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return <ContractContext.Consumer>
|
||||||
|
{ (context) => (
|
||||||
|
<React.Fragment>
|
||||||
|
await {this._getFunctionLabel()}
|
||||||
|
{ this.props.abi.type != 'fallback' ? '(' : '' }
|
||||||
|
{ this.props.abi.type != 'fallback' ? this._getFunctionParamFields() : '' }
|
||||||
|
{ this.props.abi.type != 'fallback' ? ')' : '' }
|
||||||
|
{ this.props.abi.type != 'fallback' ? '.' + this._getMethodType() : '' }
|
||||||
|
({ this._getMethodFields(context.accounts) })
|
||||||
|
<button onClick={event => this.handleClick(event, context.updateInstances)} disabled={this.state.onRequest}>⏎</button>
|
||||||
|
{ this.state.onRequest ?
|
||||||
|
<img src="images/loading.gif" className="loading" alt="" />
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</ContractContext.Consumer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Function;
|
|
@ -0,0 +1,105 @@
|
||||||
|
import ContractContext from './contract-context';
|
||||||
|
|
||||||
|
class InstanceSelector extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
showInstances: false,
|
||||||
|
showCustomAddressField: false,
|
||||||
|
selectedInstance: props.selectedInstance,
|
||||||
|
customInstance: "",
|
||||||
|
error: false,
|
||||||
|
errorMessage: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleShowInstances = this.handleShowInstances.bind(this);
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
this.handleTextChange = this.handleTextChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextChange(e){
|
||||||
|
this.setState({customInstance: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShowInstances(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.setState({
|
||||||
|
showInstances: !this.state.showInstances
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(e){
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let instance;
|
||||||
|
if(this.state.selectedInstance == "custom"){
|
||||||
|
instance = this.state.customInstance;
|
||||||
|
} else {
|
||||||
|
instance = this.state.selectedInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!/^0x[0-9a-f]{40}$/i.test(instance)){
|
||||||
|
this.setState({error: true, errorMessage: 'Not a valid Ethereum address.'});
|
||||||
|
console.log(this.state.errorMessage);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.setState({error: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.instanceUpdate(instance);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showInstances: false,
|
||||||
|
showCustomAddressField: false,
|
||||||
|
selectedInstance: instance,
|
||||||
|
customInstance: this.state.selectedInstance == "custom" ? this.state.customInstance : ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e){
|
||||||
|
this.setState({
|
||||||
|
showCustomAddressField: e.target.value == "custom",
|
||||||
|
selectedInstance: e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
|
||||||
|
return <ContractContext.Consumer>
|
||||||
|
{ (context) => (<div className="contractSelection">
|
||||||
|
<h5>
|
||||||
|
<b>Instance Selected:</b> <span><b>{this.props.selectedInstance != null ? this.props.selectedInstance : 'none'}</b></span>
|
||||||
|
{!this.state.showInstances ? <a href="#" onClick={this.handleShowInstances}>Change</a> : <a href="#" onClick={this.handleShowInstances}>Cancel</a> }
|
||||||
|
</h5>
|
||||||
|
{this.state.showInstances ?
|
||||||
|
<div className="form-group control-group error">
|
||||||
|
<select className="form-control" id="contracts" value={this.state.selectedInstance} onChange={this.handleChange}>
|
||||||
|
<option value="custom">Specific contract address</option>
|
||||||
|
{
|
||||||
|
context.instances.map(function (item, i) {
|
||||||
|
return <option key={i} value={item}>{item}</option>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
{
|
||||||
|
this.state.showCustomAddressField ?
|
||||||
|
<input type="text" className="form-control" id="specificAddress" onChange={this.handleTextChange} placeholder="0x" />
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
<button className="btn btn-default" onClick={this.handleClick}>Change</button>
|
||||||
|
{
|
||||||
|
this.state.error ?
|
||||||
|
<p className="error">{this.state.errorMessage}</p>
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</div> : "" }
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ContractContext.Consumer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InstanceSelector;
|
|
@ -0,0 +1,27 @@
|
||||||
|
class SourceArea extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
sourceCode: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
fetch(this.props.sourceURL)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(text => {
|
||||||
|
let colorCodedText = hljs.highlight('javascript', text, true).value;
|
||||||
|
this.setState({sourceCode: colorCodedText});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
return <React.Fragment>
|
||||||
|
<h3 className="filename">{this.props.sourceURL.split('\\').pop().split('/').pop()}</h3>
|
||||||
|
<small className="url">{this.props.sourceURL}</small>
|
||||||
|
<pre dangerouslySetInnerHTML={{__html: this.state.sourceCode}}></pre>
|
||||||
|
</React.Fragment>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SourceArea;
|
|
@ -0,0 +1,10 @@
|
||||||
|
class Tab extends React.Component {
|
||||||
|
render(){
|
||||||
|
return <div role="tabpanel" className={this.props.active || false ? 'tab-pane active' : 'tab-pane'} id={this.props.id}>
|
||||||
|
<h2>{this.props.name}</h2>
|
||||||
|
{ this.props.children }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tab;
|
Loading…
Reference in New Issue