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:
Nicolas Gotchac
2016-12-02 15:21:01 +01:00
committed by Jaco Greeff
parent bd2e2b630c
commit c892a4f7ae
18 changed files with 865 additions and 642 deletions

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 &nbsp;</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> &nbsp; </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;

View File

@@ -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;