Refactoring Transfer Modal (#3705)
* Better Token Select in Transfer > Details * Better Autocomplete * Crete MobX store for Transfer modal * Remove unused var * Update Webpack Conf * Small changes... * Optional gas in MethodDecoding + better input * New Contract `getAll` method // TxList Row component * Method Decoding selections * Rename `getAll` to `getAllLogs`
This commit is contained in:
committed by
Jaco Greeff
parent
bd2e2b630c
commit
c892a4f7ae
@@ -16,7 +16,6 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { MenuItem } from 'material-ui';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import AutoComplete from '../AutoComplete';
|
||||
import IdentityIcon from '../../IdentityIcon';
|
||||
@@ -64,13 +63,6 @@ export default class AddressSelect extends Component {
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
const entries = this.entriesFromProps();
|
||||
const addresses = Object.keys(entries).sort();
|
||||
|
||||
if (!isEqual(addresses, this.state.addresses)) {
|
||||
this.setState({ entries, addresses });
|
||||
}
|
||||
|
||||
if (newProps.value !== this.props.value) {
|
||||
this.setState({ value: newProps.value });
|
||||
}
|
||||
@@ -127,31 +119,33 @@ export default class AddressSelect extends Component {
|
||||
}
|
||||
|
||||
renderItem = (entry) => {
|
||||
const { address, name } = entry;
|
||||
|
||||
return {
|
||||
text: entry.name && entry.name.toUpperCase() || entry.address,
|
||||
value: this.renderSelectEntry(entry),
|
||||
address: entry.address
|
||||
text: name && name.toUpperCase() || address,
|
||||
value: this.renderMenuItem(address),
|
||||
address
|
||||
};
|
||||
}
|
||||
|
||||
renderSelectEntry = (entry) => {
|
||||
renderMenuItem (address) {
|
||||
const item = (
|
||||
<div className={ styles.account }>
|
||||
<IdentityIcon
|
||||
className={ styles.image }
|
||||
inline center
|
||||
address={ entry.address } />
|
||||
address={ address } />
|
||||
<IdentityName
|
||||
className={ styles.name }
|
||||
address={ entry.address } />
|
||||
address={ address } />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={ styles.menuItem }
|
||||
key={ entry.address }
|
||||
value={ entry.address }
|
||||
key={ address }
|
||||
value={ address }
|
||||
label={ item }>
|
||||
{ item }
|
||||
</MenuItem>
|
||||
|
||||
@@ -19,6 +19,8 @@ import keycode from 'keycode';
|
||||
import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui';
|
||||
import { PopoverAnimationVertical } from 'material-ui/Popover';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
export default class AutoComplete extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
@@ -42,12 +44,28 @@ export default class AutoComplete extends Component {
|
||||
lastChangedValue: undefined,
|
||||
entry: null,
|
||||
open: false,
|
||||
fakeBlur: false
|
||||
fakeBlur: false,
|
||||
dataSource: []
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const dataSource = this.getDataSource();
|
||||
this.setState({ dataSource });
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const prevEntries = Object.keys(this.props.entries || {}).sort();
|
||||
const nextEntries = Object.keys(nextProps.entries || {}).sort();
|
||||
|
||||
if (!isEqual(prevEntries, nextEntries)) {
|
||||
const dataSource = this.getDataSource(nextProps);
|
||||
this.setState({ dataSource });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props;
|
||||
const { open } = this.state;
|
||||
const { open, dataSource } = this.state;
|
||||
|
||||
return (
|
||||
<MUIAutoComplete
|
||||
@@ -68,7 +86,7 @@ export default class AutoComplete extends Component {
|
||||
menuCloseDelay={ 0 }
|
||||
fullWidth
|
||||
floatingLabelFixed
|
||||
dataSource={ this.getDataSource() }
|
||||
dataSource={ dataSource }
|
||||
menuProps={ { maxHeight: 400 } }
|
||||
ref='muiAutocomplete'
|
||||
onKeyDown={ this.onKeyDown }
|
||||
@@ -76,8 +94,8 @@ export default class AutoComplete extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
getDataSource () {
|
||||
const { renderItem, entries } = this.props;
|
||||
getDataSource (props = this.props) {
|
||||
const { renderItem, entries } = props;
|
||||
const entriesArray = (entries instanceof Array)
|
||||
? entries
|
||||
: Object.values(entries);
|
||||
|
||||
@@ -18,6 +18,20 @@
|
||||
.container {
|
||||
}
|
||||
|
||||
.clickable {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.4);
|
||||
padding: 0.1em 0.3em;
|
||||
margin: 0.1em 0.1em;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.noSelect {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -54,7 +54,9 @@ class MethodDecoding extends Component {
|
||||
isContract: false,
|
||||
isDeploy: false,
|
||||
isReceived: false,
|
||||
isLoading: true
|
||||
isLoading: true,
|
||||
expandInput: false,
|
||||
inputType: 'auto'
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
@@ -94,6 +96,11 @@ class MethodDecoding extends Component {
|
||||
renderGas () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { gas, gasPrice } = transaction;
|
||||
|
||||
if (!gas || !gasPrice) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gasValue = gas.mul(gasPrice);
|
||||
|
||||
return (
|
||||
@@ -132,24 +139,54 @@ class MethodDecoding extends Component {
|
||||
: this.renderValueTransfer();
|
||||
}
|
||||
|
||||
renderInputValue () {
|
||||
getAscii () {
|
||||
const { api } = this.context;
|
||||
const { transaction } = this.props;
|
||||
|
||||
const ascii = api.util.hex2Ascii(transaction.input);
|
||||
return { value: ascii, valid: ASCII_INPUT.test(ascii) };
|
||||
}
|
||||
|
||||
renderInputValue () {
|
||||
const { transaction } = this.props;
|
||||
const { expandInput, inputType } = this.state;
|
||||
|
||||
if (!/^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(transaction.input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ascii = api.util.hex2Ascii(transaction.input);
|
||||
const ascii = this.getAscii();
|
||||
const type = inputType === 'auto'
|
||||
? (ascii.valid ? 'ascii' : 'raw')
|
||||
: inputType;
|
||||
|
||||
const text = ASCII_INPUT.test(ascii)
|
||||
? ascii
|
||||
const text = type === 'ascii'
|
||||
? ascii.value
|
||||
: transaction.input;
|
||||
|
||||
const expandable = text.length > 50;
|
||||
const textToShow = expandInput || !expandable
|
||||
? text
|
||||
: text.slice(0, 50) + '...';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>with the input </span>
|
||||
<code className={ styles.inputData }>{ text }</code>
|
||||
<span>with the </span>
|
||||
<span
|
||||
onClick={ this.toggleInputType }
|
||||
className={ [ styles.clickable, styles.noSelect ].join(' ') }
|
||||
>
|
||||
{ type === 'ascii' ? 'input' : 'data' }
|
||||
</span>
|
||||
<span> </span>
|
||||
<span
|
||||
onClick={ this.toggleInputExpand }
|
||||
className={ expandable ? styles.clickable : '' }
|
||||
>
|
||||
<code className={ styles.inputData }>
|
||||
{ textToShow }
|
||||
</code>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -373,6 +410,31 @@ class MethodDecoding extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
toggleInputExpand = () => {
|
||||
if (window.getSelection && window.getSelection().type === 'Range') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
expandInput: !this.state.expandInput
|
||||
});
|
||||
}
|
||||
|
||||
toggleInputType = () => {
|
||||
const { inputType } = this.state;
|
||||
|
||||
if (inputType !== 'auto') {
|
||||
return this.setState({
|
||||
inputType: this.state.inputType === 'raw' ? 'ascii' : 'raw'
|
||||
});
|
||||
}
|
||||
|
||||
const ascii = this.getAscii();
|
||||
return this.setState({
|
||||
inputType: ascii.valid ? 'raw' : 'ascii'
|
||||
});
|
||||
}
|
||||
|
||||
lookup () {
|
||||
const { transaction } = this.props;
|
||||
|
||||
|
||||
@@ -29,79 +29,49 @@ import Store from './store';
|
||||
|
||||
import styles from './txList.css';
|
||||
|
||||
@observer
|
||||
class TxList extends Component {
|
||||
export class TxRow extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
tx: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
hashes: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
]).isRequired,
|
||||
isTest: PropTypes.bool.isRequired
|
||||
}
|
||||
isTest: PropTypes.bool.isRequired,
|
||||
|
||||
store = new Store(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
this.store.loadTransactions(this.props.hashes);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.store.unsubscribe();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
this.store.loadTransactions(newProps.hashes);
|
||||
}
|
||||
block: PropTypes.object
|
||||
};
|
||||
|
||||
render () {
|
||||
const { tx, address, isTest } = this.props;
|
||||
|
||||
return (
|
||||
<table className={ styles.transactions }>
|
||||
<tbody>
|
||||
{ this.renderRows() }
|
||||
</tbody>
|
||||
</table>
|
||||
<tr>
|
||||
{ this.renderBlockNumber(tx.blockNumber) }
|
||||
{ this.renderAddress(tx.from) }
|
||||
<td className={ styles.transaction }>
|
||||
{ this.renderEtherValue(tx.value) }
|
||||
<div>⇒</div>
|
||||
<div>
|
||||
<a
|
||||
className={ styles.link }
|
||||
href={ txLink(tx.hash, isTest) }
|
||||
target='_blank'>
|
||||
{ `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` }
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
{ this.renderAddress(tx.to) }
|
||||
<td className={ styles.method }>
|
||||
<MethodDecoding
|
||||
historic
|
||||
address={ address }
|
||||
transaction={ tx } />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
renderRows () {
|
||||
const { address, isTest } = this.props;
|
||||
|
||||
return this.store.sortedHashes.map((txhash) => {
|
||||
const tx = this.store.transactions[txhash];
|
||||
|
||||
return (
|
||||
<tr key={ tx.hash }>
|
||||
{ this.renderBlockNumber(tx.blockNumber) }
|
||||
{ this.renderAddress(tx.from) }
|
||||
<td className={ styles.transaction }>
|
||||
{ this.renderEtherValue(tx.value) }
|
||||
<div>⇒</div>
|
||||
<div>
|
||||
<a
|
||||
className={ styles.link }
|
||||
href={ txLink(tx.hash, isTest) }
|
||||
target='_blank'>
|
||||
{ `${tx.hash.substr(2, 6)}...${tx.hash.slice(-6)}` }
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
{ this.renderAddress(tx.to) }
|
||||
<td className={ styles.method }>
|
||||
<MethodDecoding
|
||||
historic
|
||||
address={ address }
|
||||
transaction={ tx } />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderAddress (address) {
|
||||
const { isTest } = this.props;
|
||||
|
||||
@@ -148,8 +118,8 @@ class TxList extends Component {
|
||||
}
|
||||
|
||||
renderBlockNumber (_blockNumber) {
|
||||
const { block } = this.props;
|
||||
const blockNumber = _blockNumber.toNumber();
|
||||
const block = this.store.blocks[blockNumber];
|
||||
|
||||
return (
|
||||
<td className={ styles.timestamp }>
|
||||
@@ -160,6 +130,66 @@ class TxList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class TxList extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
hashes: PropTypes.oneOfType([
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
]).isRequired,
|
||||
isTest: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
store = new Store(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
this.store.loadTransactions(this.props.hashes);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.store.unsubscribe();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
this.store.loadTransactions(newProps.hashes);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<table className={ styles.transactions }>
|
||||
<tbody>
|
||||
{ this.renderRows() }
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderRows () {
|
||||
const { address, isTest } = this.props;
|
||||
|
||||
return this.store.sortedHashes.map((txhash) => {
|
||||
const tx = this.store.transactions[txhash];
|
||||
const blockNumber = tx.blockNumber.toNumber();
|
||||
const block = this.store.blocks[blockNumber];
|
||||
|
||||
return (
|
||||
<TxRow
|
||||
key={ tx.hash }
|
||||
tx={ tx }
|
||||
block={ block }
|
||||
address={ address }
|
||||
isTest={ isTest }
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { isTest } = state.nodeStatus;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user