From 6fa863f2cca4427dbddd8f8f77e18ad63a65d974 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 16 Nov 2016 16:50:12 +0100 Subject: [PATCH 01/19] Make parity.js usable by Node and Browser #3471 --- js/package.json | 1 + js/src/parity.js | 34 +++++++++++++++++++++++++--------- js/webpack.libraries.js | 5 ++++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/js/package.json b/js/package.json index 200de6ee6..77d6e0351 100644 --- a/js/package.json +++ b/js/package.json @@ -138,6 +138,7 @@ "mobx-react": "^3.5.8", "mobx-react-devtools": "^4.2.9", "moment": "^2.14.1", + "node-fetch": "^1.6.3", "qs": "^6.3.0", "react": "^15.2.1", "react-ace": "^4.0.0", diff --git a/js/src/parity.js b/js/src/parity.js index 4e6184c6a..57afdb734 100644 --- a/js/src/parity.js +++ b/js/src/parity.js @@ -14,19 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import 'babel-polyfill'; -import 'whatwg-fetch'; - +import 'babel-polyfill/dist/polyfill.js'; import es6Promise from 'es6-promise'; es6Promise.polyfill(); -import Api from './api'; +try { + if (typeof self.window !== 'undefined') { + self.window.fetch = require('isomorphic-fetch'); + } +} catch (e) {} +try { + if (typeof global !== 'undefined') { + global.fetch = require('node-fetch'); + } +} catch (e) {} + +import Api from './api'; import './dev.parity.html'; -const api = new Api(new Api.Transport.Http('/rpc/')); +// commonjs +module.exports = { Api }; +// es6 default export compatibility +module.exports.default = module.exports; -window.parity = { - Api, - api -}; +if (typeof self !== 'undefined' && typeof self.window !== 'undefined') { + const api = new Api(new Api.Transport.Http('/rpc/')); + + self.window.parity = { + Api, + api + }; +} diff --git a/js/webpack.libraries.js b/js/webpack.libraries.js index bf54a933f..8e166fc54 100644 --- a/js/webpack.libraries.js +++ b/js/webpack.libraries.js @@ -26,6 +26,7 @@ const DEST = process.env.BUILD_DEST || '.build'; module.exports = { context: path.join(__dirname, './src'), + target: 'node', entry: { // library 'inject': ['./web3.js'], @@ -34,7 +35,9 @@ module.exports = { }, output: { path: path.join(__dirname, DEST), - filename: '[name].js' + filename: '[name].js', + library: '[name].js', + libraryTarget: 'umd' }, module: { loaders: [ From 3ad7e873e1bb22fcbcdd6643d5f91ff0f8e2d7b0 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 16 Nov 2016 19:16:55 +0100 Subject: [PATCH 02/19] Better use of Webpack #3471 --- js/package.json | 1 - js/parity.package.json | 3 ++- js/src/parity.js | 21 ++++++++++----------- js/webpack.libraries.js | 7 +++++++ 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/js/package.json b/js/package.json index 88063fee7..59b75c591 100644 --- a/js/package.json +++ b/js/package.json @@ -138,7 +138,6 @@ "mobx-react": "^3.5.8", "mobx-react-devtools": "^4.2.9", "moment": "^2.14.1", - "node-fetch": "^1.6.3", "qs": "^6.3.0", "react": "^15.2.1", "react-ace": "^4.0.0", diff --git a/js/parity.package.json b/js/parity.package.json index 7d18cc5ed..0974e072f 100644 --- a/js/parity.package.json +++ b/js/parity.package.json @@ -27,6 +27,7 @@ }, "dependencies": { "bignumber.js": "^2.3.0", - "js-sha3": "^0.5.2" + "js-sha3": "^0.5.2", + "node-fetch": "^1.6.3" } } diff --git a/js/src/parity.js b/js/src/parity.js index 57afdb734..23376d76e 100644 --- a/js/src/parity.js +++ b/js/src/parity.js @@ -18,17 +18,16 @@ import 'babel-polyfill/dist/polyfill.js'; import es6Promise from 'es6-promise'; es6Promise.polyfill(); -try { - if (typeof self.window !== 'undefined') { - self.window.fetch = require('isomorphic-fetch'); - } -} catch (e) {} +const isNode = typeof global !== 'undefined' && typeof global !== 'undefined'; +const isBrowser = typeof self !== 'undefined' && typeof self.window !== 'undefined'; -try { - if (typeof global !== 'undefined') { - global.fetch = require('node-fetch'); - } -} catch (e) {} +if (isBrowser) { + require('whatwg-fetch'); +} + +if (isNode) { + global.fetch = require('node-fetch'); +} import Api from './api'; import './dev.parity.html'; @@ -38,7 +37,7 @@ module.exports = { Api }; // es6 default export compatibility module.exports.default = module.exports; -if (typeof self !== 'undefined' && typeof self.window !== 'undefined') { +if (isBrowser) { const api = new Api(new Api.Transport.Http('/rpc/')); self.window.parity = { diff --git a/js/webpack.libraries.js b/js/webpack.libraries.js index 8e166fc54..07e40957e 100644 --- a/js/webpack.libraries.js +++ b/js/webpack.libraries.js @@ -39,7 +39,14 @@ module.exports = { library: '[name].js', libraryTarget: 'umd' }, + externals: { + 'node-fetch': 'node-fetch', + 'vertx': 'vertx' + }, module: { + noParse: [ + /babel-polyfill/ + ], loaders: [ { test: /\.js$/, From 4fa361c9886ef59d40bc67de240166723e5dd240 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 17 Nov 2016 08:29:28 +0800 Subject: [PATCH 03/19] Wallet names shouldn't include uuid. Fixes #3368 --- ethstore/src/ethstore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4360a39f0..4991c4714 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -86,7 +86,7 @@ impl SecretStore for EthStore { fn insert_account(&self, secret: Secret, password: &str) -> Result { let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); let id: [u8; 16] = Random::random(); - let account = SafeAccount::create(&keypair, id, password, self.iterations, UUID::from(id).into(), "{}".to_owned()); + let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); let address = account.address.clone(); try!(self.save(account)); Ok(address) From c8fadbec69e6144ff5cae7ac09fe21e7092a3376 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 17 Nov 2016 12:06:35 +0100 Subject: [PATCH 04/19] Add a new RadioButtons Component #3196 --- js/src/modals/AddContract/addContract.css | 16 ---- js/src/modals/AddContract/addContract.js | 39 +++------ js/src/ui/Form/RadioButtons/index.js | 17 ++++ js/src/ui/Form/RadioButtons/radioButtons.css | 32 +++++++ js/src/ui/Form/RadioButtons/radioButtons.js | 92 ++++++++++++++++++++ js/src/ui/Form/index.js | 4 +- js/src/ui/index.js | 3 +- 7 files changed, 160 insertions(+), 43 deletions(-) create mode 100644 js/src/ui/Form/RadioButtons/index.js create mode 100644 js/src/ui/Form/RadioButtons/radioButtons.css create mode 100644 js/src/ui/Form/RadioButtons/radioButtons.js diff --git a/js/src/modals/AddContract/addContract.css b/js/src/modals/AddContract/addContract.css index ed92a86d5..0821a180e 100644 --- a/js/src/modals/AddContract/addContract.css +++ b/js/src/modals/AddContract/addContract.css @@ -14,19 +14,3 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ - -.spaced { - margin: 0.25em 0; -} - -.typeContainer { - display: flex; - flex-direction: column; - - .desc { - font-size: 0.8em; - margin-bottom: 0.5em; - color: #ccc; - z-index: 2; - } -} diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 4c73d3da0..110a91837 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -20,13 +20,10 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; -import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; - -import { Button, Modal, Form, Input, InputAddress } from '../../ui'; +import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '../../ui'; import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; import { eip20, wallet } from '../../contracts/abi'; -import styles from './addContract.css'; const ABI_TYPES = [ { @@ -105,13 +102,12 @@ export default class AddContract extends Component { const { abiTypeIndex } = this.state; return ( - - { this.renderAbiTypes() } - + /> ); } @@ -194,20 +190,13 @@ export default class AddContract extends Component { ); } - renderAbiTypes () { - return ABI_TYPES.map((type, index) => ( - - { type.label } - { type.description } - - ) } - key={ index } - /> - )); + getAbiTypes () { + return ABI_TYPES.map((type, index) => ({ + label: type.label, + description: type.description, + key: index, + ...type + })); } onNext = () => { @@ -218,8 +207,8 @@ export default class AddContract extends Component { this.setState({ step: this.state.step - 1 }); } - onChangeABIType = (event, index) => { - const abiType = ABI_TYPES[index]; + onChangeABIType = (value, index) => { + const abiType = value || ABI_TYPES[index]; this.setState({ abiTypeIndex: index, abiType }); this.onEditAbi(abiType.value); } diff --git a/js/src/ui/Form/RadioButtons/index.js b/js/src/ui/Form/RadioButtons/index.js new file mode 100644 index 000000000..c708eb728 --- /dev/null +++ b/js/src/ui/Form/RadioButtons/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './radioButtons'; diff --git a/js/src/ui/Form/RadioButtons/radioButtons.css b/js/src/ui/Form/RadioButtons/radioButtons.css new file mode 100644 index 000000000..ed92a86d5 --- /dev/null +++ b/js/src/ui/Form/RadioButtons/radioButtons.css @@ -0,0 +1,32 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.spaced { + margin: 0.25em 0; +} + +.typeContainer { + display: flex; + flex-direction: column; + + .desc { + font-size: 0.8em; + margin-bottom: 0.5em; + color: #ccc; + z-index: 2; + } +} diff --git a/js/src/ui/Form/RadioButtons/radioButtons.js b/js/src/ui/Form/RadioButtons/radioButtons.js new file mode 100644 index 000000000..3297c4ae1 --- /dev/null +++ b/js/src/ui/Form/RadioButtons/radioButtons.js @@ -0,0 +1,92 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; + +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; + +import styles from './radioButtons.css'; + +export default class RadioButtons extends Component { + static propTypes = { + onChange: PropTypes.func.isRequired, + values: PropTypes.array.isRequired, + + value: PropTypes.any, + name: PropTypes.string + }; + + static defaultProps = { + value: 0, + name: '' + }; + + render () { + const { value, values } = this.props; + + const index = parseInt(value); + const selectedValue = typeof value !== 'object' ? values[index] : value; + const key = (typeof selectedValue !== 'string' && selectedValue.key) || index; + + return ( + + { this.renderContent() } + + ); + } + + renderContent () { + const { values } = this.props; + + return values.map((value, index) => { + const label = typeof value === 'string' ? value : value.label || ''; + const description = (typeof value !== 'string' && value.description) || null; + const key = (typeof value !== 'string' && value.key) || index; + + return ( + + { label } + { + description + ? ( + { description } + ) + : null + } + + ) } + /> + ); + }); + } + + onChange = (event, index) => { + const { onChange, values } = this.props; + + const value = values[index]; + onChange(value, index); + } +} diff --git a/js/src/ui/Form/index.js b/js/src/ui/Form/index.js index 46bb106f4..113f3424a 100644 --- a/js/src/ui/Form/index.js +++ b/js/src/ui/Form/index.js @@ -23,6 +23,7 @@ import InputAddressSelect from './InputAddressSelect'; import InputChip from './InputChip'; import InputInline from './InputInline'; import Select from './Select'; +import RadioButtons from './RadioButtons'; export default from './form'; export { @@ -34,5 +35,6 @@ export { InputAddressSelect, InputChip, InputInline, - Select + Select, + RadioButtons }; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 69a7d26c3..d443d0dbc 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -29,7 +29,7 @@ import ContextProvider from './ContextProvider'; import CopyToClipboard from './CopyToClipboard'; import Editor from './Editor'; import Errors from './Errors'; -import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form'; +import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form'; import IdentityIcon from './IdentityIcon'; import IdentityName from './IdentityName'; import MethodDecoding from './MethodDecoding'; @@ -78,6 +78,7 @@ export { muiTheme, Page, ParityBackground, + RadioButtons, SignerIcon, Tags, Tooltip, From d41efcc84e7da97c20e15b130553416c2471f981 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 17 Nov 2016 13:59:13 +0100 Subject: [PATCH 05/19] Add input from Solc compiler #3196 --- .../DeployContract/DetailsStep/detailsStep.js | 142 ++------- .../DeployContract/ParametersStep/index.js | 17 ++ .../ParametersStep/parametersStep.js | 278 ++++++++++++++++++ .../modals/DeployContract/deployContract.css | 4 + .../modals/DeployContract/deployContract.js | 99 +++++-- js/src/ui/Form/RadioButtons/radioButtons.js | 14 +- js/src/ui/Modal/modal.js | 2 +- 7 files changed, 419 insertions(+), 137 deletions(-) create mode 100644 js/src/modals/DeployContract/ParametersStep/index.js create mode 100644 js/src/modals/DeployContract/ParametersStep/parametersStep.js diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 6e23f79c9..4fd2bdcf5 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -16,11 +16,7 @@ import React, { Component, PropTypes } from 'react'; -import { AddressSelect, Form, Input, TypedInput } from '../../../ui'; -import { validateAbi } from '../../../util/validation'; -import { parseAbiType } from '../../../util/abi'; - -import styles from '../deployContract.css'; +import { AddressSelect, Form, Input, RadioButtons } from '../../../ui'; export default class DetailsStep extends Component { static contextTypes = { @@ -29,24 +25,17 @@ export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - abi: PropTypes.string, - abiError: PropTypes.string, - code: PropTypes.string, - codeError: PropTypes.string, - description: PropTypes.string, - descriptionError: PropTypes.string, + inputTypeValues: PropTypes.array.isRequired, + + onFromAddressChange: PropTypes.func.isRequired, + onNameChange: PropTypes.func.isRequired, + onInputTypeChange: PropTypes.func.isRequired, + fromAddress: PropTypes.string, fromAddressError: PropTypes.string, name: PropTypes.string, nameError: PropTypes.string, - params: PropTypes.array, - paramsError: PropTypes.array, - onAbiChange: PropTypes.func.isRequired, - onCodeChange: PropTypes.func.isRequired, - onFromAddressChange: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, - onNameChange: PropTypes.func.isRequired, - onParamsChange: PropTypes.func.isRequired, + inputType: PropTypes.object, readOnly: PropTypes.bool }; @@ -54,25 +43,9 @@ export default class DetailsStep extends Component { readOnly: false }; - state = { - inputs: [] - } - - componentDidMount () { - const { abi, code } = this.props; - - if (abi) { - this.onAbiChange(abi); - } - - if (code) { - this.onCodeChange(code); - } - } - render () { const { accounts } = this.props; - const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError, readOnly } = this.props; + const { fromAddress, fromAddressError, name, nameError } = this.props; return (
@@ -83,61 +56,40 @@ export default class DetailsStep extends Component { error={ fromAddressError } accounts={ accounts } onChange={ this.onFromAddressChange } /> + - - - { this.renderConstructorInputs() } + { this.renderChooseInputType() }
); } - renderConstructorInputs () { - const { accounts, params, paramsError } = this.props; - const { inputs } = this.state; + renderChooseInputType () { + const { readOnly } = this.props; - if (!inputs || !inputs.length) { + if (readOnly) { return null; } - return inputs.map((input, index) => { - const onChange = (value) => this.onParamChange(index, value); + const { inputTypeValues, inputType } = this.props; - const label = `${input.name ? `${input.name}: ` : ''}${input.type}`; - const value = params[index]; - const error = paramsError[index]; - const param = parseAbiType(input.type); - - return ( -
- -
- ); - }); + return ( +
+
+

Choose how ABI and Bytecode will be entered

+ +
+ ); } onFromAddressChange = (event, fromAddress) => { @@ -152,42 +104,8 @@ export default class DetailsStep extends Component { onNameChange(name); } - onParamChange = (index, value) => { - const { params, onParamsChange } = this.props; - - params[index] = value; - onParamsChange(params); - } - - onAbiChange = (abi) => { - const { api } = this.context; - const { onAbiChange, onParamsChange } = this.props; - const { abiError, abiParsed } = validateAbi(abi, api); - - if (!abiError) { - const { inputs } = abiParsed - .find((method) => method.type === 'constructor') || { inputs: [] }; - - const params = []; - - inputs.forEach((input) => { - const param = parseAbiType(input.type); - params.push(param.default); - }); - - onParamsChange(params); - this.setState({ inputs }); - } else { - onParamsChange([]); - this.setState({ inputs: [] }); - } - - onAbiChange(abi); - } - - onCodeChange = (code) => { - const { onCodeChange } = this.props; - - onCodeChange(code); + onInputTypeChange = (inputType, index) => { + const { onInputTypeChange } = this.props; + onInputTypeChange(inputType, index); } } diff --git a/js/src/modals/DeployContract/ParametersStep/index.js b/js/src/modals/DeployContract/ParametersStep/index.js new file mode 100644 index 000000000..77545b406 --- /dev/null +++ b/js/src/modals/DeployContract/ParametersStep/index.js @@ -0,0 +1,17 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './parametersStep'; diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js new file mode 100644 index 000000000..3e407f482 --- /dev/null +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -0,0 +1,278 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import { MenuItem } from 'material-ui'; + +import { Form, Input, TypedInput, Select } from '../../../ui'; +import { validateAbi } from '../../../util/validation'; +import { parseAbiType } from '../../../util/abi'; + +import styles from '../deployContract.css'; + +export default class ParametersStep extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + static propTypes = { + accounts: PropTypes.object.isRequired, + inputType: PropTypes.object.isRequired, + + onAbiChange: PropTypes.func.isRequired, + onCodeChange: PropTypes.func.isRequired, + onParamsChange: PropTypes.func.isRequired, + + abi: PropTypes.string, + abiError: PropTypes.string, + code: PropTypes.string, + codeError: PropTypes.string, + params: PropTypes.array, + paramsError: PropTypes.array, + + readOnly: PropTypes.bool + }; + + static defaultProps = { + readOnly: false + }; + + state = { + inputs: [], + solcOutput: '', + contracts: {}, + selectedContractIndex: 0 + } + + componentDidMount () { + const { abi, code } = this.props; + + if (abi) { + this.onAbiChange(abi); + } + + if (code) { + this.onCodeChange(code); + } + } + + render () { + const { abi, abiError, code, codeError, readOnly, inputType } = this.props; + + const manualInput = inputType.key === 'MANUAL'; + + return ( +
+ { this.renderFromSOLC() } + + + + { this.renderConstructorInputs() } +
+ ); + } + + renderFromSOLC () { + const { inputType } = this.props; + + if (inputType.key !== 'SOLC') { + return null; + } + + const { solcOutput, contracts } = this.state; + const error = contracts && Object.keys(contracts).length + ? null + : 'enter a valid solc output'; + + return ( +
+ + { this.renderContractSelect() } +
+ ); + } + + renderContractSelect () { + const { contracts } = this.state; + + if (!contracts || Object.keys(contracts).length === 0) { + return null; + } + + const { selectedContractIndex } = this.state; + const contractsItems = Object.keys(contracts).map((name, index) => ( + + { name } + + )); + + return ( + + ); + } + + renderConstructorInputs () { + const { accounts, params, paramsError } = this.props; + const { inputs } = this.state; + + if (!inputs || !inputs.length) { + return null; + } + + const inputsComponents = inputs.map((input, index) => { + const onChange = (value) => this.onParamChange(index, value); + + const label = `${input.name ? `${input.name}: ` : ''}${input.type}`; + const value = params[index]; + const error = paramsError[index]; + const param = parseAbiType(input.type); + + return ( +
+ +
+ ); + }); + + return ( +
+

Choose the contract parameters

+ { inputsComponents } +
+ ); + } + + onContractChange = (event, index) => { + const { contracts } = this.state; + const contractName = Object.keys(contracts)[index]; + const contract = contracts[contractName]; + + const { abi, bin } = contract; + const code = /^0x/.test(bin) ? bin : `0x${bin}`; + + this.setState({ selectedContractIndex: index }, () => { + this.onAbiChange(abi); + this.onCodeChange(code); + }); + } + + onSolcChange = (event, value) => { + try { + const solcParsed = JSON.parse(value); + + if (!solcParsed && !solcParsed.contracts) { + throw new Error('Wrong solc output'); + } + + this.setState({ contracts: solcParsed.contracts }, () => { + this.onContractChange(null, 0); + }); + } catch (e) { + this.setState({ contracts: null }); + } + + this.setState({ solcOutput: value }); + } + + onParamChange = (index, value) => { + const { params, onParamsChange } = this.props; + + params[index] = value; + onParamsChange(params); + } + + onAbiChange = (abi) => { + const { api } = this.context; + const { onAbiChange, onParamsChange } = this.props; + const { abiError, abiParsed } = validateAbi(abi, api); + + if (!abiError) { + const { inputs } = abiParsed + .find((method) => method.type === 'constructor') || { inputs: [] }; + + const params = []; + + inputs.forEach((input) => { + const param = parseAbiType(input.type); + params.push(param.default); + }); + + onParamsChange(params); + this.setState({ inputs }); + } else { + onParamsChange([]); + this.setState({ inputs: [] }); + } + + onAbiChange(abi); + } + + onCodeChange = (code) => { + const { onCodeChange } = this.props; + + onCodeChange(code); + } +} diff --git a/js/src/modals/DeployContract/deployContract.css b/js/src/modals/DeployContract/deployContract.css index 45fd7a852..90097df81 100644 --- a/js/src/modals/DeployContract/deployContract.css +++ b/js/src/modals/DeployContract/deployContract.css @@ -31,3 +31,7 @@ .funcparams { padding-left: 3em; } + +p { + color: rgba(255, 255, 255, 0.498039); +} diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 996948092..db12cf911 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -22,13 +22,32 @@ import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; import DetailsStep from './DetailsStep'; +import ParametersStep from './ParametersStep'; import ErrorStep from './ErrorStep'; import styles from './deployContract.css'; import { ERROR_CODES } from '../../api/transport/error'; -const steps = ['contract details', 'deployment', 'completed']; +const STEPS = { + CONTRACT_DETAILS: { title: 'contract details' }, + CONTRACT_PARAMETERS: { title: 'contract parameters' }, + DEPLOYMENT: { title: 'deployment' }, + COMPLETED: { title: 'completed' } +}; + +const CONTRACT_INPUT_TYPES = [ + { + key: 'MANUAL', + label: 'Manually', + description: 'Manual input of the ABI and the bytecode' + }, + { + key: 'SOLC', + label: 'From solc', + description: 'Parse the ABI and the bytecode from solc output' + } +]; export default class DeployContract extends Component { static contextTypes = { @@ -55,7 +74,6 @@ export default class DeployContract extends Component { abiError: ERRORS.invalidAbi, code: '', codeError: ERRORS.invalidCode, - deployState: '', description: '', descriptionError: null, fromAddress: Object.keys(this.props.accounts)[0], @@ -64,9 +82,12 @@ export default class DeployContract extends Component { nameError: ERRORS.invalidName, params: [], paramsError: [], - step: 0, + inputType: CONTRACT_INPUT_TYPES[0], + + deployState: '', deployError: null, - rejected: false + rejected: false, + step: 'CONTRACT_DETAILS' } componentWillMount () { @@ -97,7 +118,11 @@ export default class DeployContract extends Component { render () { const { step, deployError, rejected } = this.state; - const realSteps = deployError || rejected ? null : steps; + const realStep = Object.keys(STEPS).findIndex((k) => k === step); + const realSteps = deployError || rejected + ? null + : Object.values(STEPS).map((s) => s.title); + const title = realSteps ? null : (deployError ? 'deployment failed' : 'rejected'); @@ -105,10 +130,10 @@ export default class DeployContract extends Component { return ( { this.renderStep() } @@ -118,7 +143,8 @@ export default class DeployContract extends Component { renderDialogActions () { const { deployError, abiError, codeError, nameError, descriptionError, fromAddressError, fromAddress, step } = this.state; - const isValid = !nameError && !fromAddressError && !descriptionError && !abiError && !codeError; + const isDetailsValid = !nameError && !fromAddressError && !descriptionError; + const isParametersValid = !abiError && !codeError; const cancelBtn = (