new InputAddressSelect component (#3071)
* basic address autocomplete * validate input, propagate changes * show IdentityIcon in menu * show IdentityIcon next to input * refactoring, better variable names, linting * show default IdentityIcon if search by name * port #3065 over * show accounts in the beginning * show accounts before contacts * filter deleted accounts * UX improvements - limit number of search results shown - hint text * only render identity icon if valid address * UX improvements - align IdentityIcon - better hint text * align label & error with other inputs This probably needs to be changed soon again. Therefore this ugly hack has been put in place. * Align component with coding style for app * Use standard/tested AddressAutocmplete (WIP) * Address selection & inputs operational * Update TODOs, remove unused CSS * only handle input changes when editing * Simplify * Cleanup unused modules * Add contracts to address search * Updates Address Selector to handle valid input address #3071 * Added Address Selector to contracts read queries
This commit is contained in:
parent
0f9451efe8
commit
5ae737f307
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { MenuItem } from 'material-ui';
|
import { MenuItem } from 'material-ui';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import AutoComplete from '../AutoComplete';
|
import AutoComplete from '../AutoComplete';
|
||||||
import IdentityIcon from '../../IdentityIcon';
|
import IdentityIcon from '../../IdentityIcon';
|
||||||
@ -24,57 +25,82 @@ import IdentityName from '../../IdentityName';
|
|||||||
import styles from './addressSelect.css';
|
import styles from './addressSelect.css';
|
||||||
|
|
||||||
export default class AddressSelect extends Component {
|
export default class AddressSelect extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
api: PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
|
contracts: PropTypes.object,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
tokens: PropTypes.object,
|
tokens: PropTypes.object,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired,
|
||||||
|
allowInput: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
entries: {},
|
entries: {},
|
||||||
|
addresses: [],
|
||||||
value: ''
|
value: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entriesFromProps (props = this.props) {
|
||||||
|
const { accounts, contacts, contracts } = props;
|
||||||
|
const entries = Object.assign({}, accounts || {}, contacts || {}, contracts || {});
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const { accounts, contacts, value } = this.props;
|
const { value } = this.props;
|
||||||
const entries = Object.assign({}, accounts || {}, contacts || {});
|
const entries = this.entriesFromProps();
|
||||||
this.setState({ entries, value });
|
const addresses = Object.keys(entries).sort();
|
||||||
|
|
||||||
|
this.setState({ entries, addresses, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
const { accounts, contacts } = newProps;
|
const entries = this.entriesFromProps();
|
||||||
const entries = Object.assign({}, accounts || {}, contacts || {});
|
const addresses = Object.keys(entries).sort();
|
||||||
this.setState({ entries });
|
|
||||||
|
if (!isEqual(addresses, this.state.addresses)) {
|
||||||
|
this.setState({ entries, addresses });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newProps.value !== this.props.value) {
|
||||||
|
this.setState({ value: newProps.value });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { disabled, error, hint, label } = this.props;
|
const { allowInput, disabled, error, hint, label } = this.props;
|
||||||
const { entries } = this.state;
|
const { entries, value } = this.state;
|
||||||
const value = this.getSearchText();
|
|
||||||
|
const searchText = this.getSearchText();
|
||||||
|
const icon = this.renderIdentityIcon(value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
className={ (error || !value) ? '' : styles.paddedInput }
|
className={ !icon ? '' : styles.paddedInput }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint ? `search for ${hint}` : 'search for an address' }
|
hint={ hint ? `search for ${hint}` : 'search for an address' }
|
||||||
error={ error }
|
error={ error }
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
value={ value }
|
onBlur={ this.onBlur }
|
||||||
|
onUpdateInput={ allowInput && this.onUpdateInput }
|
||||||
|
value={ searchText }
|
||||||
filter={ this.handleFilter }
|
filter={ this.handleFilter }
|
||||||
entries={ entries }
|
entries={ entries }
|
||||||
entry={ this.getEntry() || {} }
|
entry={ this.getEntry() || {} }
|
||||||
renderItem={ this.renderItem }
|
renderItem={ this.renderItem }
|
||||||
/>
|
/>
|
||||||
|
{ icon }
|
||||||
{ this.renderIdentityIcon(value) }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -82,7 +108,7 @@ export default class AddressSelect extends Component {
|
|||||||
renderIdentityIcon (inputValue) {
|
renderIdentityIcon (inputValue) {
|
||||||
const { error, value } = this.props;
|
const { error, value } = this.props;
|
||||||
|
|
||||||
if (error || !inputValue) {
|
if (error || !inputValue || value.length !== 42) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +122,9 @@ export default class AddressSelect extends Component {
|
|||||||
|
|
||||||
renderItem = (entry) => {
|
renderItem = (entry) => {
|
||||||
return {
|
return {
|
||||||
text: entry.address,
|
text: entry.name && entry.name.toUpperCase() || entry.address,
|
||||||
value: this.renderSelectEntry(entry)
|
value: this.renderSelectEntry(entry),
|
||||||
|
address: entry.address
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,32 +154,48 @@ export default class AddressSelect extends Component {
|
|||||||
|
|
||||||
getSearchText () {
|
getSearchText () {
|
||||||
const entry = this.getEntry();
|
const entry = this.getEntry();
|
||||||
if (!entry) return '';
|
const { value } = this.state;
|
||||||
|
|
||||||
return entry.name ? entry.name.toUpperCase() : '';
|
return entry && entry.name
|
||||||
|
? entry.name.toUpperCase()
|
||||||
|
: value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntry () {
|
getEntry () {
|
||||||
const { value } = this.props;
|
const { entries, value } = this.state;
|
||||||
if (!value) return '';
|
return value ? entries[value] : null;
|
||||||
|
|
||||||
const { entries } = this.state;
|
|
||||||
return entries[value];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFilter = (searchText, address) => {
|
handleFilter = (searchText, name, item) => {
|
||||||
|
const { address } = item;
|
||||||
const entry = this.state.entries[address];
|
const entry = this.state.entries[address];
|
||||||
const lowCaseSearch = searchText.toLowerCase();
|
const lowCaseSearch = searchText.toLowerCase();
|
||||||
|
|
||||||
return [ entry.name, entry.address ]
|
return [entry.name, entry.address]
|
||||||
.some(text => text.toLowerCase().indexOf(lowCaseSearch) !== -1);
|
.some(text => text.toLowerCase().indexOf(lowCaseSearch) !== -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (entry, empty) => {
|
onChange = (entry, empty) => {
|
||||||
|
const { allowInput } = this.props;
|
||||||
|
const { value } = this.state;
|
||||||
|
|
||||||
const address = entry && entry.address
|
const address = entry && entry.address
|
||||||
? entry.address
|
? entry.address
|
||||||
: (empty ? '' : this.state.value);
|
: ((empty && !allowInput) ? '' : value);
|
||||||
|
|
||||||
this.props.onChange(null, address);
|
this.props.onChange(null, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUpdateInput = (query, choices) => {
|
||||||
|
const { api } = this.context;
|
||||||
|
|
||||||
|
const address = query.trim();
|
||||||
|
|
||||||
|
if (!/^0x/.test(address) && api.util.isAddressValid(`0x${address}`)) {
|
||||||
|
const checksumed = api.util.toChecksumAddress(`0x${address}`);
|
||||||
|
return this.props.onChange(null, checksumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onChange(null, address);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import { PopoverAnimationVertical } from 'material-ui/Popover';
|
|||||||
export default class AutoComplete extends Component {
|
export default class AutoComplete extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
onUpdateInput: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
@ -43,7 +44,7 @@ export default class AutoComplete extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { disabled, error, hint, label, value, className, filter } = this.props;
|
const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props;
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -54,11 +55,11 @@ export default class AutoComplete extends Component {
|
|||||||
hintText={ hint }
|
hintText={ hint }
|
||||||
errorText={ error }
|
errorText={ error }
|
||||||
onNewRequest={ this.onChange }
|
onNewRequest={ this.onChange }
|
||||||
|
onUpdateInput={ onUpdateInput }
|
||||||
searchText={ value }
|
searchText={ value }
|
||||||
onFocus={ this.onFocus }
|
onFocus={ this.onFocus }
|
||||||
onBlur={ this.onBlur }
|
onBlur={ this.onBlur }
|
||||||
animation={ PopoverAnimationVertical }
|
animation={ PopoverAnimationVertical }
|
||||||
|
|
||||||
filter={ filter }
|
filter={ filter }
|
||||||
popoverProps={ { open } }
|
popoverProps={ { open } }
|
||||||
openOnFocus
|
openOnFocus
|
||||||
@ -108,11 +109,17 @@ export default class AutoComplete extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBlur = () => {
|
onBlur = () => {
|
||||||
window.setTimeout(() => {
|
const { onUpdateInput } = this.props;
|
||||||
const { entry } = this.state;
|
|
||||||
|
|
||||||
this.handleOnChange(entry);
|
// TODO: Handle blur gracefully where we use onUpdateInput (currently replaces input
|
||||||
}, 100);
|
// input where text is allowed with the last selected value from the dropdown)
|
||||||
|
if (!onUpdateInput) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const { entry } = this.state;
|
||||||
|
|
||||||
|
this.handleOnChange(entry);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocus = () => {
|
onFocus = () => {
|
||||||
@ -131,5 +138,4 @@ export default class AutoComplete extends Component {
|
|||||||
this.props.onChange(value, empty);
|
this.props.onChange(value, empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
/* 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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
.inputselect {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputselect svg {
|
|
||||||
padding-right: 84px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
position: absolute !important;
|
|
||||||
top: 38px;
|
|
||||||
right: 0;
|
|
||||||
display: inline-block !important;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
@ -17,103 +17,46 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { Toggle } from 'material-ui';
|
|
||||||
|
|
||||||
import AddressSelect from '../AddressSelect';
|
import AddressSelect from '../AddressSelect';
|
||||||
import InputAddress from '../InputAddress';
|
|
||||||
|
|
||||||
import styles from './inputAddressSelect.css';
|
|
||||||
|
|
||||||
class InputAddressSelect extends Component {
|
class InputAddressSelect extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object.isRequired,
|
||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object.isRequired,
|
||||||
disabled: PropTypes.bool,
|
contracts: PropTypes.object.isRequired,
|
||||||
editing: PropTypes.bool,
|
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
tokens: PropTypes.object,
|
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
editing: this.props.editing || false,
|
|
||||||
entries: []
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { editing } = this.state;
|
const { accounts, contacts, contracts, label, hint, error, value, onChange } = this.props;
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={ styles.inputselect }>
|
|
||||||
{ editing ? this.renderInput() : this.renderSelect() }
|
|
||||||
<Toggle
|
|
||||||
className={ styles.toggle }
|
|
||||||
label='Edit'
|
|
||||||
labelPosition='right'
|
|
||||||
toggled={ editing }
|
|
||||||
onToggle={ this.onToggle } />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInput () {
|
|
||||||
const { disabled, error, hint, label, value, tokens } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InputAddress
|
|
||||||
disabled={ disabled }
|
|
||||||
error={ error }
|
|
||||||
hint={ hint }
|
|
||||||
label={ label }
|
|
||||||
value={ value }
|
|
||||||
tokens={ tokens }
|
|
||||||
onChange={ this.onChangeInput } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSelect () {
|
|
||||||
const { accounts, contacts, disabled, error, hint, label, value, tokens } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
|
allowInput
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
contacts={ contacts }
|
contacts={ contacts }
|
||||||
disabled={ disabled }
|
contracts={ contracts }
|
||||||
|
error={ error }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
error={ error }
|
|
||||||
value={ value }
|
value={ value }
|
||||||
tokens={ tokens }
|
onChange={ onChange } />
|
||||||
onChange={ this.onChangeSelect } />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggle = () => {
|
|
||||||
const { editing } = this.state;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
editing: !editing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeInput = (event, value) => {
|
|
||||||
this.props.onChange(event, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeSelect = (event, value) => {
|
|
||||||
this.props.onChange(event, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, contacts } = state.personal;
|
const { accounts, contacts, contracts } = state.personal;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
contacts
|
contacts,
|
||||||
|
contracts
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import LinearProgress from 'material-ui/LinearProgress';
|
import LinearProgress from 'material-ui/LinearProgress';
|
||||||
import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card';
|
import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card';
|
||||||
|
|
||||||
import { Button, Input } from '../../../ui';
|
import { Button, Input, InputAddressSelect } from '../../../ui';
|
||||||
|
|
||||||
import styles from './queries.css';
|
import styles from './queries.css';
|
||||||
|
|
||||||
@ -124,8 +124,8 @@ export default class InputQuery extends Component {
|
|||||||
const { name, type } = input;
|
const { name, type } = input;
|
||||||
const label = `${name ? `${name}: ` : ''}${type}`;
|
const label = `${name ? `${name}: ` : ''}${type}`;
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event, input) => {
|
||||||
const value = event.target.value;
|
const value = event && event.target.value || input;
|
||||||
const { values } = this.state;
|
const { values } = this.state;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -136,6 +136,19 @@ export default class InputQuery extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (type === 'address') {
|
||||||
|
return (
|
||||||
|
<div key={ name }>
|
||||||
|
<InputAddressSelect
|
||||||
|
hint={ type }
|
||||||
|
label={ label }
|
||||||
|
required
|
||||||
|
onChange={ onChange }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={ name }>
|
<div key={ name }>
|
||||||
<Input
|
<Input
|
||||||
|
Loading…
Reference in New Issue
Block a user