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:
committed by
Gav Wood
parent
dadd6b1e7c
commit
cc10f412dc
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user