on master help sign in
Merge pull request #100 from urbit/chain-dropdown

Add chain ID dropdown + custom chain ID input

commit7604fc279b97c9837592a08fb51761633837b8fe authorChristopher Johnson <chris@cjohnson.io> dateTue 9 Apr 2019, 15:32 (8 years ago) parentbf5e9bb6d86c68
5 files changed+257−242
src/bridge/components/InnerLabelDropdown.js+88−45 src/bridge/components/StatelessTransaction.js+86−15 src/bridge/style/extend.css+8 src/bridge/views/Network.js+43−98 src/bridge/views/Wallet.js+32−84
src/bridge/components/InnerLabelDropdown.js+88−45
@@ -1,71 +1,114 @@
11import React from 'react'
22import PropTypes from 'prop-types';
33
4+import { DropdownItem, DropdownDivider } from '../components/Base'
45
5−const InnerLabelDropdown = props => {
6−
7− const menuVisibility = props.isOpen === true
8− ? 'visible'
9− : 'hidden';
10−
11− const selectedClasses = props.isOpen === true
12− ? 'br-blue'
13− : 'br-gray-50';
14−
15− return (
16− <div className={'rel table'}>
17− <div
18− className={`fix full o-0 ${menuVisibility}`}
19− onClick={props.handleClose}
20− />
21−
22− <button
23− className={`h-10 mp-0 bg-white outline-blue sans ${props.className}`}
24− onClick={props.handleToggle}
25− disabled={props.disabled}>
26− <div className={'flex'}>
27− <div className={`ph-4 flex-center-all b-2 br-0 b-solid h-10 ${selectedClasses}`}>
28− <div className={'text-400 mr-2'}>{props.title}</div>
29− <div className={'text-600'}>{props.currentSelectionTitle}</div>
6+class InnerLabelDropdown extends React.Component {
7+ constructor(props) {
8+ super(props)
9+
10+ this.state = {
11+ isOpen: false
12+ }
13+
14+ this.toggle = this.toggle.bind(this)
15+ this.close = this.close.bind(this)
16+ }
17+
18+ toggle() {
19+ this.setState({
20+ isOpen: !this.state.isOpen
21+ })
22+ }
23+
24+ close() {
25+ this.setState({
26+ isOpen: false
27+ })
28+ }
29+
30+ selectOption(opt) {
31+ this.close()
32+ this.props.handleUpdate(opt)
33+ }
34+
35+ getOptionElems() {
36+ return this.props.options.map(opt => {
37+ if (opt.type === "divider") {
38+ return <DropdownDivider />
39+ } else {
40+ return (
41+ <DropdownItem onClick={() => this.selectOption(opt.value)}>
42+ { opt.title }
43+ </DropdownItem>
44+ )
45+ }
46+ })
47+ }
48+
49+ render() {
50+ const { props, state } = this
51+
52+ const optionElems = this.getOptionElems()
53+
54+ const menuVisibility = state.isOpen === true
55+ ? 'visible'
56+ : 'hidden';
57+
58+ const selectedClasses = state.isOpen === true
59+ ? 'br-blue'
60+ : 'br-gray-50';
61+
62+ const fullWidthClass = props.fullWidth ? 'full-width' : ''
63+
64+ return (
65+ <div className={`rel table ${fullWidthClass}`}>
66+ <div
67+ className={`fix full o-0 ${menuVisibility}`}
68+ onClick={this.close}
69+ />
70+
71+ <button
72+ className={`h-10 mp-0 bg-white outline-blue sans ${props.className} ${fullWidthClass}`}
73+ onClick={this.toggle}
74+ disabled={props.disabled}>
75+ <div className={'flex'}>
76+ <div className={`ph-4 flex align-center b-2 br-0 b-solid h-10 ${selectedClasses} ${fullWidthClass}`}>
77+ <div className={'text-400 mr-2'}>{props.title}</div>
78+ <div className={'text-600'}>{props.currentSelectionTitle}</div>
79+ </div>
80+ <div className={'white bg-blue s-10 flex-center-all text-sm'}>{'▼'}</div>
3081 </div>
31− <div className={'white bg-blue s-10 flex-center-all text-sm'}>{'▼'}</div>
32− </div>
33− </button>
82+ </button>
3483
35− <div className={`abs flex-column bg-white t-12 l-0 w-100 ${menuVisibility}`}>
36− <div className={'pv-1 b-solid b-gray-50 b-2 bb-0'} />
84+ <div className={`abs flex-column bg-white t-12 l-0 w-100 z-1 ${menuVisibility}`}>
85+ <div className={'pv-1 b-solid b-gray-50 b-2 bb-0'} />
3786
38− { props.children }
87+ { optionElems }
3988
40− <div className={'pv-1 b-solid b-gray-50 b-2 bt-0'} />
41− </div>
89+ <div className={'pv-1 b-solid b-gray-50 b-2 bt-0'} />
90+ </div>
4291
43− </div>
44− )
92+ </div>
93+ )
94+ }
4595}
4696
4797InnerLabelDropdown.propTypes = {
4898 disabled: PropTypes.bool,
49− isOpen: PropTypes.bool,
50− handleToggle: PropTypes.func,
51− handleClose: PropTypes.func,
5299 className: PropTypes.string,
53100 title: PropTypes.string,
54− children: PropTypes.node,
55101 style: PropTypes.object,
56102 currentSelectionTitle: PropTypes.string,
103+ children: PropTypes.node,
57104};
58105
59−
60106InnerLabelDropdown.defaultProps = {
61− isOpen: false,
107+ disabled: false,
62108 className: '',
63109 title: '',
64− currentSelectionTitle: '',
65− handleToggle: () => {},
66− handleClose: () => {},
67− disabled: false,
68110 style: {},
111+ currentSelectionTitle: '',
69112};
70113
71114export default InnerLabelDropdown
src/bridge/components/StatelessTransaction.js+86−15
@@ -3,7 +3,8 @@ import React from 'react'
33
44import { Code, H3 } from './Base'
55import { Button } from './Base'
6−import { CheckboxButton, Input, InnerLabel } from './Base'
6+import { CheckboxButton, Input, InnerLabel,
7+ InnerLabelDropdown } from './Base'
78import { Warning } from '../components/Base'
89
910import { addressFromSecp256k1Public } from '../lib/wallet'
@@ -28,10 +29,11 @@ class StatelessTransaction extends React.Component {
2829 showGasDetails: false,
2930 userApproval: false,
3031 chainId: '',
32+ customChain: false,
3133 nonce: '',
3234 stx: Nothing(),
3335 txn: Nothing(),
34− txError: Nothing(),
36+ txError: Nothing()
3537 }
3638
3739 this.createUnsignedTxn = this.createUnsignedTxn.bind(this)
@@ -46,6 +48,7 @@ class StatelessTransaction extends React.Component {
4648 this.setGasLimit = this.setGasLimit.bind(this)
4749 this.rangeChange = this.rangeChange.bind(this)
4850 this.toggleGasDetails = this.toggleGasDetails.bind(this)
51+ this.handleChainUpdate = this.handleChainUpdate.bind(this)
4952 }
5053
5154 componentDidMount() {
@@ -106,6 +109,19 @@ class StatelessTransaction extends React.Component {
106109 })
107110 }
108111
112+ handleChainUpdate(chainId) {
113+ if (chainId === "custom") {
114+ this.setState({
115+ customChain: true
116+ })
117+ } else {
118+ this.setState({
119+ customChain: false,
120+ chainId
121+ })
122+ }
123+ }
124+
109125 setUserApproval(){
110126 const {state} = this
111127 this.setState({ userApproval: !state.userApproval })
@@ -185,13 +201,53 @@ class StatelessTransaction extends React.Component {
185201 this.submit()
186202 }
187203
204+ getChainTitle(chainId) {
205+ let map = {
206+ "1": "Mainnet - 1",
207+ "2": "Morden - 2",
208+ "3": "Ropsten - 3",
209+ "4": "Goerli - 4",
210+ "42": "Kovan - 42",
211+ "1337": "Geth private chains - 1337",
212+ "custom": "Custom"
213+ }
214+
215+ return map[chainId]
216+ }
217+
218+ getChainOptions() {
219+ return [{
220+ title: this.getChainTitle("1"),
221+ value: "1"
222+ }, {
223+ title: this.getChainTitle("2"),
224+ value: "2"
225+ }, {
226+ title: this.getChainTitle("3"),
227+ value: "3"
228+ }, {
229+ title: this.getChainTitle("4"),
230+ value: "4"
231+ }, {
232+ title: this.getChainTitle("42"),
233+ value: "42"
234+ }, {
235+ title: this.getChainTitle("1337"),
236+ value: "1337"
237+ }, {
238+ title: this.getChainTitle("custom"),
239+ value: "custom"
240+ }]
241+ }
242+
188243 render() {
189244 const { web3, canGenerate } = this.props
190245 const { gasPrice, gasLimit, nonce, chainId,
191− txn, stx, userApproval, showGasDetails } = this.state
246+ txn, stx, userApproval, showGasDetails,
247+ customChain } = this.state
192248
193249 const { setNonce, setChainId, setGasLimit, setGasPrice, toggleGasDetails,
194− setUserApproval, sendTxn, createUnsignedTxn } = this
250+ setUserApproval, sendTxn, createUnsignedTxn, handleChainUpdate } = this
195251
196252 const { state } = this
197253
@@ -199,6 +255,8 @@ class StatelessTransaction extends React.Component {
199255 const canApprove = Just.hasInstance(stx)
200256 const canSend = Just.hasInstance(stx) && userApproval === true
201257
258+ const chainOptions = this.getChainOptions()
259+
202260 const generateButtonColor =
203261 Nothing.hasInstance(txn)
204262 ? 'blue'
@@ -294,7 +352,7 @@ class StatelessTransaction extends React.Component {
294352
295353 const nonceDialogue =
296354 <Input
297− className={ 'mono mt-4' }
355+ className={ 'mono mt-4 mb-4' }
298356 prop-size={ 'md' }
299357 prop-format={ 'innerLabel' }
300358 value={ nonce }
@@ -305,18 +363,30 @@ class StatelessTransaction extends React.Component {
305363 </InnerLabel>
306364 </Input>
307365
366+ const chainDialogueTitle = customChain ? "Custom" : this.getChainTitle(chainId)
308367 const chainDialogue =
309− <Input
310− className={ 'mono mt-4 mb-8' }
311− prop-size={ 'md' }
312− prop-format={ 'innerLabel' }
313− value={ chainId }
314− onChange={ setChainId }
368+ <InnerLabelDropdown
369+ className={'mt-6'}
370+ fullWidth={true}
371+ title={'Chain ID'}
372+ options={chainOptions}
373+ handleUpdate={handleChainUpdate}
374+ currentSelectionTitle={chainDialogueTitle}
315375 >
316− <InnerLabel>
317− { 'Chain ID' }
318− </InnerLabel>
319− </Input>
376+ </InnerLabelDropdown>
377+
378+ const customChainDialogue = !customChain ? null :
379+ <Input
380+ className={ 'mono mt-4 mb-8' }
381+ prop-size={ 'md' }
382+ prop-format={ 'innerLabel' }
383+ value={ chainId }
384+ onChange={ setChainId }
385+ >
386+ <InnerLabel>
387+ { 'Chain ID' }
388+ </InnerLabel>
389+ </Input>
320390
321391 const onlineParamsDialogue = web3.matchWith({
322392 Just: _ => <div />,
@@ -324,6 +394,7 @@ class StatelessTransaction extends React.Component {
324394 <React.Fragment>
325395 { nonceDialogue }
326396 { chainDialogue }
397+ { customChainDialogue }
327398 </React.Fragment>
328399 })
329400
src/bridge/style/extend.css+8
@@ -477,3 +477,11 @@ code {
477477 padding: 1rem 2rem;
478478 margin-bottom: 4rem;
479479}
480+
481+.full-width {
482+ width: 100%;
483+}
484+
485+.z-1 {
486+ z-index: 1
487+}
src/bridge/views/Network.js+43−98
@@ -1,11 +1,7 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
33import { Button } from '../components/Base'
4−import {
5− DropdownItem,
6− InnerLabelDropdown,
7− DropdownDivider,
8−} from '../components/Base'
4+import { InnerLabelDropdown } from '../components/Base'
95import { Row, Col, H1, P } from '../components/Base'
106import * as azimuth from 'azimuth-js'
117import Web3 from 'web3'
@@ -19,21 +15,9 @@ class Network extends React.Component {
1915 constructor(props) {
2016 super(props)
2117
22− this.state = {
23− dropdownOpen: false
24− };
25−
26− this.toggle = this.toggle.bind(this);
27− this.close = this.close.bind(this);
2818 this.handleNetworkChange = this.handleNetworkChange.bind(this);
2919 }
3020
31− toggle() {
32− this.setState((state, _) => ({
33− dropdownOpen: !state.dropdownOpen
34− }))
35− }
36−
3721 handleNetworkChange(network) {
3822 const { setNetworkType, setNetwork } = this.props
3923
@@ -99,97 +83,58 @@ class Network extends React.Component {
9983 }
10084 }
10185
102− selectClose(f) {
103− f()
104− this.setState({ dropdownOpen: false })
105− }
106−
107−
108− close () {
109− this.setState({ dropdownOpen: false })
86+ getNetworkOptions() {
87+ return [{
88+ title: 'Main Network (default)',
89+ value: NETWORK_NAMES.MAINNET
90+ }, {
91+ title: 'Local Node',
92+ value: NETWORK_NAMES.LOCAL
93+ }, {
94+ title: 'Ropsten',
95+ value: NETWORK_NAMES.ROPSTEN
96+ }, {
97+ type: 'divider'
98+ }, {
99+ title: 'Offline',
100+ value: NETWORK_NAMES.OFFLINE
101+ }]
110102 }
111103
112104 render() {
113− const { dropdownOpen } = this.state
114105 const { networkType, pushRoute } = this.props
106+ const networkOptions = this.getNetworkOptions()
115107
116108 return (
117109 <Row>
118110 <Col>
119111 <H1>{ 'Select Network' }</H1>
120112
121−
122− <P>
123− {
124− "Please select the Ethereum node you'd like to send " +
125− "transactions to. For highly valuable keys, please select " +
126− "offline mode."
127− }
128− </P>
129−
130−
131− <InnerLabelDropdown
132− className={'mt-6'}
133− handleToggle={this.toggle}
134− handleClose={this.close}
135− isOpen={dropdownOpen}
136− title={'Node:'}
137− currentSelectionTitle={renderNetworkType(networkType)}
138− >
139−
140− <DropdownItem
141− isSelected={ false }
142− onClick={
143− () => this.selectClose(() => this.handleNetworkChange(NETWORK_NAMES.MAINNET))
144− }
145− >
146− { 'Main Network (default)' }
147− </DropdownItem>
148−
149− <DropdownItem
150− isSelected={false}
151− onClick={
152− () => this.selectClose(() => this.handleNetworkChange(NETWORK_NAMES.LOCAL))
153− }
154− >
155− { //'Ganache Node @ Port 8545 (Default)'
156− }
157− {'Local Node'}
158− </DropdownItem>
159−
160− <DropdownItem
161− // disabled={ true }
162− isSelected={ false }
163− onClick={
164− () => this.selectClose(() => this.handleNetworkChange(NETWORK_NAMES.ROPSTEN))
165− }
166− >
167− { 'Ropsten' }
168− </DropdownItem>
169−
170− <DropdownDivider />
171−
172− <DropdownItem
173− // disabled={ true }
174− isSelected={ false }
175− onClick={
176− () => this.selectClose(() => this.handleNetworkChange(NETWORK_NAMES.OFFLINE))
177− }
178− >
179− { 'Offline' }
180− </DropdownItem>
181−
182− </InnerLabelDropdown>
183−
184− <Button
185− className={'mt-10'}
186− onClick={ () => pushRoute(ROUTE_NAMES.WALLET) }
187− >
188− { 'Continue →' }
189− </Button>
190−
191− </Col>
192− </Row>
113+ <P>
114+ {
115+ "Please select the Ethereum node you'd like to send " +
116+ "transactions to. For highly valuable keys, please select " +
117+ "offline mode."
118+ }
119+ </P>
120+
121+ <InnerLabelDropdown
122+ className={'mt-6'}
123+ title={'Node:'}
124+ handleUpdate={this.handleNetworkChange}
125+ options={networkOptions}
126+ currentSelectionTitle={renderNetworkType(networkType)}>
127+ </InnerLabelDropdown>
128+
129+ <Button
130+ className={'mt-10'}
131+ onClick={ () => pushRoute(ROUTE_NAMES.WALLET) }
132+ >
133+ { 'Continue →' }
134+ </Button>
135+
136+ </Col>
137+ </Row>
193138 )
194139 }
195140}
src/bridge/views/Wallet.js+32−84
@@ -1,52 +1,50 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
33import { Button, H1, P } from '../components/Base'
4−import {
5− InnerLabelDropdown,
6− DropdownItem,
7− DropdownDivider
8−} from '../components/Base'
4+import { InnerLabelDropdown } from '../components/Base'
95import { Row, Col } from '../components/Base'
106
117import { ROUTE_NAMES } from '../lib/router'
128import { WALLET_NAMES, renderWalletType } from '../lib/wallet'
139
1410class Wallet extends React.Component {
15−
16− constructor(props) {
17− super(props)
18−
19− this.state = {
20− dropdownOpen: false
21− }
22−
23− this.toggle = this.toggle.bind(this)
24− this.close = this.close.bind(this)
25−
26− }
27−
2811 componentDidMount() {
2912 const { setWallet } = this.props
3013 setWallet(Maybe.Nothing())
3114 }
3215
33− toggle() {
34− this.setState((state, _) => ({
35− dropdownOpen: !state.dropdownOpen
36− }))
37− }
38−
39− selectClose(f) {
40− f()
41− this.setState({ dropdownOpen: false })
42− }
43−
44− close () {
45− this.setState({ dropdownOpen: false })
16+ getWalletOptions() {
17+ return [{
18+ title: 'Urbit Master Ticket',
19+ value: WALLET_NAMES.TICKET
20+ }, {
21+ title: 'Urbit Master Ticket (Shards)',
22+ value: WALLET_NAMES.SHARDS
23+ }, {
24+ type: 'divider'
25+ }, {
26+ title: 'Ledger',
27+ value: WALLET_NAMES.LEDGER
28+ }, {
29+ title: 'Trezor',
30+ value: WALLET_NAMES.TREZOR
31+ }, {
32+ type: 'divider'
33+ }, {
34+ title: 'BIP39 Mnemonic',
35+ value: WALLET_NAMES.MNEMONIC
36+ }, {
37+ title: 'Ethereum Private Key',
38+ value: WALLET_NAMES.PRIVATE_KEY
39+ }, {
40+ title: 'Ethereum Keystore File',
41+ value: WALLET_NAMES.KEYSTORE
42+ }]
4643 }
4744
4845 render() {
49− const { props, state } = this
46+ const { props } = this
47+ const walletOptions = this.getWalletOptions()
5048
5149 return (
5250 <Row>
@@ -63,60 +61,10 @@ class Wallet extends React.Component {
6361
6462 <InnerLabelDropdown
6563 className={'mb-10 mt-6'}
66− handleToggle={this.toggle}
67− handleClose={this.close}
68− isOpen={state.dropdownOpen}
6964 title={'Wallet Type:'}
65+ options={walletOptions}
66+ handleUpdate={props.setWalletType}
7067 currentSelectionTitle={renderWalletType(props.walletType)}>
71−
72− <DropdownItem
73− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.TICKET)) }
74− >
75− { 'Urbit Master Ticket' }
76− </DropdownItem>
77−
78− <DropdownItem
79− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.SHARDS)) }
80− >
81− { 'Urbit Master Ticket (Shards)' }
82− </DropdownItem>
83−
84− <DropdownDivider />
85−
86− <DropdownItem
87− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.LEDGER)) }
88− >
89− { 'Ledger' }
90− </DropdownItem>
91−
92− <DropdownItem
93− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.TREZOR)) }
94− >
95− { 'Trezor' }
96− </DropdownItem>
97−
98− <DropdownDivider />
99−
100− <DropdownItem
101− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.MNEMONIC)) }
102− >
103− { 'BIP39 Mnemonic' }
104− </DropdownItem>
105−
106− <DropdownItem
107− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.PRIVATE_KEY)) }
108− >
109− { 'Ethereum Private Key' }
110− </DropdownItem>
111−
112− <DropdownItem
113− onClick={ () => this.selectClose(() => props.setWalletType(WALLET_NAMES.KEYSTORE)) }
114− >
115− { 'Ethereum Keystore File' }
116− </DropdownItem>
117−
118−
119−
12068 </InnerLabelDropdown>
12169
12270 <Button