Allow tags for Accounts, Addresses and Contracts (#2712)

* Added tag to the editMeta Modal (#2643)

* Added Tags to ui and to contract/address/account Header (#2643)

* Added tags to summary (#2643)

* Added Search capabilities to contracts/address book/accounts from tokens
(#2643)

* fixes eslint

* Using Chips/Tokens for search (#2643)

* Add search tokens, clickable from List (#2643)

* Add sort capabilities to Accounts / Addresses / Contracts (#2643)

* Fixes formatting issues + state updates after component unmount bug
(#2643)

* Remove unused import

* Small fixes for PR #2697

* Added default sort order for Contracts/Addresses/Accounts

* Using official `material-ui-chip-input` NPM package

* Removed LESS from webpack
This commit is contained in:
Nicolas Gotchac
2016-10-19 10:51:02 +01:00
committed by Gav Wood
parent dadd6b1e7c
commit cc10f412dc
21 changed files with 723 additions and 36 deletions

View File

@@ -14,7 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.balances {
.balances, .tags {
clear: both;
}

View File

@@ -16,7 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName } from '../../../ui';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags } from '../../../ui';
import styles from './header.css';
@@ -70,6 +70,9 @@ export default class Header extends Component {
</div>
{ this.renderTxCount() }
</div>
<div className={ styles.tags }>
<Tags tags={ meta.tags } />
</div>
<div className={ styles.balances }>
<Balance
account={ account }

View File

@@ -26,7 +26,10 @@ export default class List extends Component {
accounts: PropTypes.object,
balances: PropTypes.object,
link: PropTypes.string,
empty: PropTypes.bool
search: PropTypes.array,
empty: PropTypes.bool,
order: PropTypes.string,
handleAddSearchToken: PropTypes.func
};
render () {
@@ -38,7 +41,7 @@ export default class List extends Component {
}
renderAccounts () {
const { accounts, balances, link, empty } = this.props;
const { accounts, balances, link, empty, handleAddSearchToken } = this.props;
if (empty) {
return (
@@ -50,7 +53,9 @@ export default class List extends Component {
);
}
return Object.keys(accounts).map((address, idx) => {
const addresses = this.getAddresses();
return addresses.map((address, idx) => {
const account = accounts[address] || {};
const balance = balances[address] || {};
@@ -61,9 +66,79 @@ export default class List extends Component {
<Summary
link={ link }
account={ account }
balance={ balance } />
balance={ balance }
handleAddSearchToken={ handleAddSearchToken } />
</div>
);
});
}
getAddresses () {
const filteredAddresses = this.getFilteredAddresses();
return this.sortAddresses(filteredAddresses);
}
sortAddresses (addresses) {
const { order } = this.props;
if (!order || ['tags', 'name'].indexOf(order) === -1) {
return addresses;
}
const { accounts } = this.props;
return addresses.sort((addressA, addressB) => {
const accountA = accounts[addressA];
const accountB = accounts[addressB];
if (order === 'name') {
return accountA.name.localeCompare(accountB.name);
}
if (order === 'tags') {
const tagsA = [].concat(accountA.meta.tags)
.filter(t => t)
.sort();
const tagsB = [].concat(accountB.meta.tags)
.filter(t => t)
.sort();
if (tagsA.length === 0) return 1;
if (tagsB.length === 0) return -1;
return tagsA.join('').localeCompare(tagsB.join(''));
}
});
}
getFilteredAddresses () {
const { accounts, search } = this.props;
const searchValues = (search || []).map(v => v.toLowerCase());
if (searchValues.length === 0) {
return Object.keys(accounts);
}
return Object.keys(accounts)
.filter((address) => {
const account = accounts[address];
const tags = account.meta.tags || [];
const name = account.name || '';
const values = []
.concat(tags, name)
.map(v => v.toLowerCase());
return values
.filter((value) => {
return searchValues
.map(searchValue => value.indexOf(searchValue) >= 0)
// `current && truth, true` => use tokens as AND
// `current || truth, false` => use tokens as OR
.reduce((current, truth) => current || truth, false);
})
.length > 0;
});
}
}

View File

@@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName } from '../../../ui';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags } from '../../../ui';
export default class Summary extends Component {
static contextTypes = {
@@ -28,7 +28,8 @@ export default class Summary extends Component {
account: PropTypes.object.isRequired,
balance: PropTypes.object.isRequired,
link: PropTypes.string,
children: PropTypes.node
children: PropTypes.node,
handleAddSearchToken: PropTypes.func
}
state = {
@@ -36,21 +37,24 @@ export default class Summary extends Component {
}
render () {
const { account, balance, children, link } = this.props;
const { account, balance, children, link, handleAddSearchToken } = this.props;
const { tags } = account.meta;
if (!account) {
return null;
}
const viewLink = `/${link || 'account'}/${account.address}`;
const { address } = account;
const viewLink = `/${link || 'account'}/${address}`;
return (
<Container>
<Tags tags={ tags } handleAddSearchToken={ handleAddSearchToken } />
<IdentityIcon
address={ account.address } />
address={ address } />
<ContainerTitle
title={ <Link to={ viewLink }>{ <IdentityName address={ account.address } unknown /> }</Link> }
byline={ account.address } />
title={ <Link to={ viewLink }>{ <IdentityName address={ address } unknown /> }</Link> }
byline={ address } />
<Balance
balance={ balance } />
{ children }

View File

@@ -18,10 +18,11 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add';
import { uniq } from 'lodash';
import List from './List';
import { CreateAccount } from '../../modals';
import { Actionbar, Button, Page, Tooltip } from '../../ui';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui';
import styles from './accounts.css';
@@ -38,11 +39,14 @@ class Accounts extends Component {
state = {
addressBook: false,
newDialog: false
newDialog: false,
sortOrder: '',
searchValues: []
}
render () {
const { accounts, hasAccounts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<div className={ styles.accounts }>
@@ -50,9 +54,12 @@ class Accounts extends Component {
{ this.renderActionbar() }
<Page>
<List
search={ searchValues }
accounts={ accounts }
balances={ balances }
empty={ !hasAccounts } />
empty={ !hasAccounts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
<Tooltip
className={ styles.accountTooltip }
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
@@ -61,13 +68,42 @@ class Accounts extends Component {
);
}
renderSearchButton () {
const onChange = (searchValues) => {
this.setState({ searchValues });
};
return (
<ActionbarSearch
key='searchAccount'
tokens={ this.state.searchValues }
onChange={ onChange } />
);
}
renderSortButton () {
const onChange = (sortOrder) => {
this.setState({ sortOrder });
};
return (
<ActionbarSort
key='sortAccounts'
order={ this.state.sortOrder }
onChange={ onChange } />
);
}
renderActionbar () {
const buttons = [
<Button
key='newAccount'
icon={ <ContentAdd /> }
label='new account'
onClick={ this.onNewAccountClick } />
onClick={ this.onNewAccountClick } />,
this.renderSearchButton(),
this.renderSortButton()
];
return (
@@ -99,6 +135,12 @@ class Accounts extends Component {
);
}
onAddSearchToken = (token) => {
const { searchValues } = this.state;
const newSearchValues = uniq([].concat(searchValues, token));
this.setState({ searchValues: newSearchValues });
}
onNewAccountClick = () => {
this.setState({
newDialog: !this.state.newDialog

View File

@@ -18,10 +18,11 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add';
import { uniq } from 'lodash';
import List from '../Accounts/List';
import { AddAddress } from '../../modals';
import { Actionbar, Button, Page } from '../../ui';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
import styles from './addresses.css';
@@ -37,11 +38,14 @@ class Addresses extends Component {
}
state = {
showAdd: false
showAdd: false,
sortOrder: '',
searchValues: []
}
render () {
const { balances, contacts, hasContacts } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<div className={ styles.addresses }>
@@ -50,21 +54,53 @@ class Addresses extends Component {
<Page>
<List
link='address'
search={ searchValues }
accounts={ contacts }
balances={ balances }
empty={ !hasContacts } />
empty={ !hasContacts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
</Page>
</div>
);
}
renderSortButton () {
const onChange = (sortOrder) => {
this.setState({ sortOrder });
};
return (
<ActionbarSort
key='sortAccounts'
order={ this.state.sortOrder }
onChange={ onChange } />
);
}
renderSearchButton () {
const onChange = (searchValues) => {
this.setState({ searchValues });
};
return (
<ActionbarSearch
key='searchAddress'
tokens={ this.state.searchValues }
onChange={ onChange } />
);
}
renderActionbar () {
const buttons = [
<Button
key='newAddress'
icon={ <ContentAdd /> }
label='new address'
onClick={ this.onOpenAdd } />
onClick={ this.onOpenAdd } />,
this.renderSearchButton(),
this.renderSortButton()
];
return (
@@ -90,6 +126,12 @@ class Addresses extends Component {
);
}
onAddSearchToken = (token) => {
const { searchValues } = this.state;
const newSearchValues = uniq([].concat(searchValues, token));
this.setState({ searchValues: newSearchValues });
}
onOpenAdd = () => {
this.setState({
showAdd: true

View File

@@ -18,8 +18,9 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add';
import { uniq } from 'lodash';
import { Actionbar, Button, Page } from '../../ui';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
import { AddContract, DeployContract } from '../../modals';
import List from '../Accounts/List';
@@ -40,11 +41,14 @@ class Contracts extends Component {
state = {
addContract: false,
deployContract: false
deployContract: false,
sortOrder: '',
searchValues: []
}
render () {
const { contracts, hasContracts, balances } = this.props;
const { searchValues, sortOrder } = this.state;
return (
<div className={ styles.contracts }>
@@ -55,14 +59,43 @@ class Contracts extends Component {
<Page>
<List
link='contract'
search={ searchValues }
accounts={ contracts }
balances={ balances }
empty={ !hasContracts } />
empty={ !hasContracts }
order={ sortOrder }
handleAddSearchToken={ this.onAddSearchToken } />
</Page>
</div>
);
}
renderSortButton () {
const onChange = (sortOrder) => {
this.setState({ sortOrder });
};
return (
<ActionbarSort
key='sortAccounts'
order={ this.state.sortOrder }
onChange={ onChange } />
);
}
renderSearchButton () {
const onChange = (searchValues) => {
this.setState({ searchValues });
};
return (
<ActionbarSearch
key='searchContract'
tokens={ this.state.searchValues }
onChange={ onChange } />
);
}
renderActionbar () {
const buttons = [
<Button
@@ -74,7 +107,10 @@ class Contracts extends Component {
key='deployContract'
icon={ <ContentAdd /> }
label='deploy contract'
onClick={ this.onDeployContract } />
onClick={ this.onDeployContract } />,
this.renderSearchButton(),
this.renderSortButton()
];
return (
@@ -115,6 +151,12 @@ class Contracts extends Component {
);
}
onAddSearchToken = (token) => {
const { searchValues } = this.state;
const newSearchValues = uniq([].concat(searchValues, token));
this.setState({ searchValues: newSearchValues });
}
onDeployContractClose = () => {
this.setState({ deployContract: false });
}