From 4a7aac1f0ed51dc684dd76e75a0db3fcfe1fba69 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 17 Oct 2016 14:23:50 +0200 Subject: [PATCH 01/14] Added tag to the editMeta Modal (#2643) --- js/package.json | 1 + js/src/modals/EditMeta/editMeta.js | 15 +++++++++++++++ js/src/views/Accounts/Summary/summary.js | 9 +++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/js/package.json b/js/package.json index 04c29ac7a93..6da4e126fc0 100644 --- a/js/package.json +++ b/js/package.json @@ -119,6 +119,7 @@ "lodash": "^4.11.1", "marked": "^0.3.6", "material-ui": "^0.15.4", + "material-ui-chip-input": "^0.7.0", "moment": "^2.14.1", "react": "^15.2.1", "react-addons-css-transition-group": "^15.2.1", diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index 5822c1a3c9d..e811aaefe8e 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -17,6 +17,7 @@ import React, { Component, PropTypes } from 'react'; import ContentClear from 'material-ui/svg-icons/content/clear'; import ContentSave from 'material-ui/svg-icons/content/save'; +import ChipInput from 'material-ui-chip-input'; import { Button, Form, Input, Modal } from '../../ui'; import { validateName } from '../../util/validation'; @@ -55,6 +56,7 @@ export default class EditMeta extends Component { error={ nameError } onSubmit={ this.onNameChange } /> { this.renderMetaFields() } + { this.renderTags() } ); @@ -96,6 +98,19 @@ export default class EditMeta extends Component { }); } + renderTags () { + const { meta } = this.state; + const { tags } = meta || []; + const onChange = (chips) => this.onMetaChange('tags', chips); + + return (); + } + onNameChange = (name) => { this.setState(validateName(name)); } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 0bfb41a20d9..8dbcd829e1d 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -42,15 +42,16 @@ export default class Summary extends Component { return null; } - const viewLink = `/${link || 'account'}/${account.address}`; + const { address } = account; + const viewLink = `/${link || 'account'}/${address}`; return ( + address={ address } /> { } } - byline={ account.address } /> + title={ { } } + byline={ address } /> { children } From f535103ae986e601188fb20e16a0a3a4faa3999b Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 17 Oct 2016 15:02:52 +0200 Subject: [PATCH 02/14] Added Tags to ui and to contract/address/account Header (#2643) --- js/src/modals/EditMeta/editMeta.js | 2 ++ js/src/ui/Tags/index.js | 17 +++++++++++ js/src/ui/Tags/tags.css | 11 +++++++ js/src/ui/Tags/tags.js | 40 ++++++++++++++++++++++++++ js/src/ui/index.js | 2 ++ js/src/views/Account/Header/header.css | 2 +- js/src/views/Account/Header/header.js | 5 +++- 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 js/src/ui/Tags/index.js create mode 100644 js/src/ui/Tags/tags.css create mode 100644 js/src/ui/Tags/tags.js diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index e811aaefe8e..b53ff8a6435 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -107,6 +107,8 @@ export default class EditMeta extends Component { defaultValue={ tags } onChange={ onChange } floatingLabelText='(optional) tags' + hintText='enter tags here' + floatingLabelFixed fullWidth />); } diff --git a/js/src/ui/Tags/index.js b/js/src/ui/Tags/index.js new file mode 100644 index 00000000000..71cd4962429 --- /dev/null +++ b/js/src/ui/Tags/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './tags'; diff --git a/js/src/ui/Tags/tags.css b/js/src/ui/Tags/tags.css new file mode 100644 index 00000000000..7ed42271b2c --- /dev/null +++ b/js/src/ui/Tags/tags.css @@ -0,0 +1,11 @@ +.tags { + display: flex; + flex-wrap: wrap; +} + +.tag { + background: rgba(255, 255, 255, 0.07); + border-radius: 16px; + margin: 0.75em 0.5em 0 0; + padding: 0.25em 1em; +} diff --git a/js/src/ui/Tags/tags.js b/js/src/ui/Tags/tags.js new file mode 100644 index 00000000000..636fd203fbb --- /dev/null +++ b/js/src/ui/Tags/tags.js @@ -0,0 +1,40 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import Chip from 'material-ui/Chip'; + +import styles from './tags.css'; + +export default class Tags extends Component { + static propTypes = { + tags: PropTypes.array + } + + render () { + return (
+ { this.renderTags() } +
); + } + + renderTags () { + const tags = this.props.tags || []; + + return tags.map((tag, idx) => ( +
{ tag }
+ )); + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 3a324bcebcc..bfb431e5afc 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -31,6 +31,7 @@ import muiTheme from './Theme'; import Page from './Page'; import ParityBackground from './ParityBackground'; import SignerIcon from './SignerIcon'; +import Tags from './Tags'; import Tooltips, { Tooltip } from './Tooltips'; import TxHash from './TxHash'; @@ -62,6 +63,7 @@ export { Page, ParityBackground, SignerIcon, + Tags, Tooltip, Tooltips, TxHash diff --git a/js/src/views/Account/Header/header.css b/js/src/views/Account/Header/header.css index 35ce0a700db..7fb254a6a34 100644 --- a/js/src/views/Account/Header/header.css +++ b/js/src/views/Account/Header/header.css @@ -14,7 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ -.balances { +.balances, .tags { clear: both; } diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index d23fd50812b..0abf3c79726 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -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 { { this.renderTxCount() } +
+ +
Date: Mon, 17 Oct 2016 15:20:06 +0200 Subject: [PATCH 03/14] Added tags to summary (#2643) --- js/src/ui/Tags/tags.js | 1 - js/src/ui/index.js | 2 +- js/src/views/Accounts/Summary/summary.js | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/js/src/ui/Tags/tags.js b/js/src/ui/Tags/tags.js index 636fd203fbb..4d2084013ff 100644 --- a/js/src/ui/Tags/tags.js +++ b/js/src/ui/Tags/tags.js @@ -15,7 +15,6 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import Chip from 'material-ui/Chip'; import styles from './tags.css'; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index bfb431e5afc..7de83d08ec0 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -31,7 +31,7 @@ import muiTheme from './Theme'; import Page from './Page'; import ParityBackground from './ParityBackground'; import SignerIcon from './SignerIcon'; -import Tags from './Tags'; +import Tags from './Tags'; import Tooltips, { Tooltip } from './Tooltips'; import TxHash from './TxHash'; diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 8dbcd829e1d..e75afb2054a 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -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 = { @@ -37,6 +37,7 @@ export default class Summary extends Component { render () { const { account, balance, children, link } = this.props; + const { tags } = account.meta; if (!account) { return null; @@ -52,6 +53,7 @@ export default class Summary extends Component { { } } byline={ address } /> + { children } From 95a2d9d8b2bd4f44e43da66a09b9f9db6eecf348 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Mon, 17 Oct 2016 17:17:10 +0200 Subject: [PATCH 04/14] Added Search capabilities to contracts/address book/accounts from tokens (#2643) --- js/src/ui/Actionbar/Search/index.js | 17 +++++ js/src/ui/Actionbar/Search/search.css | 26 +++++++ js/src/ui/Actionbar/Search/search.js | 100 ++++++++++++++++++++++++++ js/src/ui/index.js | 2 + js/src/views/Accounts/List/list.js | 18 ++++- js/src/views/Accounts/accounts.js | 21 +++++- js/src/views/Addresses/addresses.js | 21 +++++- js/src/views/Contracts/contracts.js | 21 +++++- 8 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 js/src/ui/Actionbar/Search/index.js create mode 100644 js/src/ui/Actionbar/Search/search.css create mode 100644 js/src/ui/Actionbar/Search/search.js diff --git a/js/src/ui/Actionbar/Search/index.js b/js/src/ui/Actionbar/Search/index.js new file mode 100644 index 00000000000..f51d38a6fa9 --- /dev/null +++ b/js/src/ui/Actionbar/Search/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './search'; diff --git a/js/src/ui/Actionbar/Search/search.css b/js/src/ui/Actionbar/Search/search.css new file mode 100644 index 00000000000..daf1709ea78 --- /dev/null +++ b/js/src/ui/Actionbar/Search/search.css @@ -0,0 +1,26 @@ +/* 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 . +*/ + +.searchButton { + min-width: 50px !important; +} + +.searchInput { + transition: width 450ms !important; + transition: visibility 0 450ms !important; + white-space: nowrap; +} diff --git a/js/src/ui/Actionbar/Search/search.js b/js/src/ui/Actionbar/Search/search.js new file mode 100644 index 00000000000..940e8623e82 --- /dev/null +++ b/js/src/ui/Actionbar/Search/search.js @@ -0,0 +1,100 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import TextField from 'material-ui/TextField'; +import ActionSearch from 'material-ui/svg-icons/action/search'; + +import { Button } from '../../'; + +import styles from './search.css'; + +export default class ActionbarSearch extends Component { + static propTypes = { + onChange: PropTypes.func.isRequired + }; + + state = { + showSearch: false, + searchValue: '', + stateChanging: false + } + + render () { + const { onChange } = this.props; + const { showSearch } = this.state; + + const onSearchClick = () => { + this.handleOpenSearch(!this.state.showSearch); + }; + + const onSearchBlur = () => { + if (this.state.searchValue.length === 0) { + this.handleOpenSearch(false); + } + }; + + const onSearchChange = (event) => { + const searchValue = event.target.value; + + this.setState({ searchValue }); + onChange(searchValue); + }; + + const searchInputStyle = {}; + + if (!showSearch) { + searchInputStyle.width = 0; + } + + return (
+ + +
); + } + + handleOpenSearch = (showSearch) => { + if (this.state.stateChanging) return false; + + this.setState({ + showSearch: showSearch, + stateChanging: true + }); + + if (showSearch) { + this.refs.searchInput.focus(); + } + + window.setTimeout(() => { + this.setState({ stateChanging: false }); + }, 450); + } +} diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 7de83d08ec0..1a3692165d5 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import Actionbar from './Actionbar'; +import ActionbarSearch from './Actionbar/Search'; import Badge from './Badge'; import Balance from './Balance'; import Button from './Button'; @@ -37,6 +38,7 @@ import TxHash from './TxHash'; export { Actionbar, + ActionbarSearch, AddressSelect, Badge, Balance, diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 5c09d0e3488..266c584301f 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -26,6 +26,7 @@ export default class List extends Component { accounts: PropTypes.object, balances: PropTypes.object, link: PropTypes.string, + search: PropTypes.string, empty: PropTypes.bool }; @@ -38,7 +39,7 @@ export default class List extends Component { } renderAccounts () { - const { accounts, balances, link, empty } = this.props; + const { accounts, balances, link, empty, search } = this.props; if (empty) { return ( @@ -50,7 +51,20 @@ export default class List extends Component { ); } - return Object.keys(accounts).map((address, idx) => { + const searchValue = (search || '').toLowerCase(); + const filteredAddresses = (searchValue && searchValue.length > 0) + ? Object.keys(accounts) + .filter((address) => { + const account = accounts[address]; + const tags = account.meta.tags || []; + + return tags + .filter((tag) => tag.toLowerCase().indexOf(searchValue) >= 0) + .length > 0; + }) + : Object.keys(accounts); + + return filteredAddresses.map((address, idx) => { const account = accounts[address] || {}; const balance = balances[address] || {}; diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index b8f5cba1644..db96b4277cb 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -21,7 +21,7 @@ import ContentAdd from 'material-ui/svg-icons/content/add'; import List from './List'; import { CreateAccount } from '../../modals'; -import { Actionbar, Button, Page, Tooltip } from '../../ui'; +import { Actionbar, ActionbarSearch, Button, Page, Tooltip } from '../../ui'; import styles from './accounts.css'; @@ -38,11 +38,13 @@ class Accounts extends Component { state = { addressBook: false, - newDialog: false + newDialog: false, + searchValue: '' } render () { const { accounts, hasAccounts, balances } = this.props; + const { searchValue } = this.state; return (
@@ -50,6 +52,7 @@ class Accounts extends Component { { this.renderActionbar() } @@ -61,13 +64,25 @@ class Accounts extends Component { ); } + renderSearchButton () { + const onChange = (value) => { + this.setState({ searchValue: value }); + }; + + return (); + } + renderActionbar () { const buttons = [
+ ); + } - this.setState({ searchValue }); - onChange(searchValue); - }; + handleTokenAdd = (value) => { + const { searchValues } = this.state; - const searchInputStyle = {}; + const newSearchValues = uniq([].concat(searchValues, value)); - if (!showSearch) { - searchInputStyle.width = 0; - } + this.setState({ + searchValues: newSearchValues + }); + + this.handleSearchChange(newSearchValues); + } + + handleTokenDelete = (value) => { + const { searchValues } = this.state; + + const newSearchValues = [] + .concat(searchValues) + .filter(v => v !== value); + + this.setState({ + searchValues: newSearchValues + }); + + this.handleSearchChange(newSearchValues); + } + + handleInputChange = (value) => { + const { searchValues } = this.state; + + const newSearchValues = uniq([].concat(searchValues, value)); + + this.handleSearchChange(newSearchValues); + this.setState({ inputValue: value }); + } + + handleSearchChange = (searchValues) => { + const { onChange } = this.props; + const newSearchValues = searchValues.filter(v => v.length > 0); - return (
- - -
); + onChange(newSearchValues); + } + + handleSearchClick = () => { + const { showSearch } = this.state; + + this.handleOpenSearch(!showSearch); + } + + handleSearchBlur = () => { + const { searchValues, inputValue } = this.state; + + if (searchValues.length === 0 && inputValue.length === 0) { + this.handleOpenSearch(false); + } } handleOpenSearch = (showSearch) => { @@ -91,6 +141,8 @@ export default class ActionbarSearch extends Component { if (showSearch) { this.refs.searchInput.focus(); + } else { + this.refs.searchInput.getInputNode().blur(); } window.setTimeout(() => { diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index 890f9d93427..14a0179ec8e 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -15,13 +15,15 @@ /* along with Parity. If not, see . */ .container { - padding: 0em + padding: 0em; } .padded { padding: 1.5em; background: rgba(0, 0, 0, 0.8) !important; border-radius: 0 !important; + position: relative; + overflow: auto; } .light .padded { diff --git a/js/src/ui/Tags/tags.css b/js/src/ui/Tags/tags.css index 7ed42271b2c..153815cc8f6 100644 --- a/js/src/ui/Tags/tags.css +++ b/js/src/ui/Tags/tags.css @@ -1,9 +1,13 @@ .tags { display: flex; flex-wrap: wrap; + position: absolute; + right: 0.25rem; + top: 0; } .tag { + font-size: 0.9rem; background: rgba(255, 255, 255, 0.07); border-radius: 16px; margin: 0.75em 0.5em 0 0; diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 266c584301f..6908b6db4d8 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -26,7 +26,7 @@ export default class List extends Component { accounts: PropTypes.object, balances: PropTypes.object, link: PropTypes.string, - search: PropTypes.string, + search: PropTypes.array, empty: PropTypes.bool }; @@ -39,7 +39,7 @@ export default class List extends Component { } renderAccounts () { - const { accounts, balances, link, empty, search } = this.props; + const { accounts, balances, link, empty } = this.props; if (empty) { return ( @@ -51,20 +51,9 @@ export default class List extends Component { ); } - const searchValue = (search || '').toLowerCase(); - const filteredAddresses = (searchValue && searchValue.length > 0) - ? Object.keys(accounts) - .filter((address) => { - const account = accounts[address]; - const tags = account.meta.tags || []; + const addresses = this.getFilteredAddresses(); - return tags - .filter((tag) => tag.toLowerCase().indexOf(searchValue) >= 0) - .length > 0; - }) - : Object.keys(accounts); - - return filteredAddresses.map((address, idx) => { + return addresses.map((address, idx) => { const account = accounts[address] || {}; const balance = balances[address] || {}; @@ -80,4 +69,35 @@ export default class List extends Component { ); }); } + + 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; + }); + } } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index e75afb2054a..0ba542ee78d 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -48,12 +48,12 @@ export default class Summary extends Component { return ( + { } } byline={ address } /> - { children } diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index db96b4277cb..5c3b9fd7333 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -39,12 +39,12 @@ class Accounts extends Component { state = { addressBook: false, newDialog: false, - searchValue: '' + searchValues: [] } render () { const { accounts, hasAccounts, balances } = this.props; - const { searchValue } = this.state; + const { searchValues } = this.state; return (
@@ -52,7 +52,7 @@ class Accounts extends Component { { this.renderActionbar() } @@ -65,8 +65,8 @@ class Accounts extends Component { } renderSearchButton () { - const onChange = (value) => { - this.setState({ searchValue: value }); + const onChange = (searchValues) => { + this.setState({ searchValues }); }; return ( @@ -52,7 +52,7 @@ class Addresses extends Component { @@ -62,8 +62,8 @@ class Addresses extends Component { } renderSearchButton () { - const onChange = (value) => { - this.setState({ searchValue: value }); + const onChange = (values) => { + this.setState({ searchValues: values }); }; return ( @@ -57,7 +57,7 @@ class Contracts extends Component { @@ -67,8 +67,8 @@ class Contracts extends Component { } renderSearchButton () { - const onChange = (value) => { - this.setState({ searchValue: value }); + const onChange = (values) => { + this.setState({ searchValues: values }); }; return ( Date: Tue, 18 Oct 2016 16:39:28 +0200 Subject: [PATCH 07/14] Add search tokens, clickable from List (#2643) --- js/src/ui/Actionbar/Search/search.css | 17 ++++- js/src/ui/Actionbar/Search/search.js | 84 +++++++++++++----------- js/src/ui/Tags/tags.js | 27 ++++++-- js/src/ui/Tags/{tags.css => tags.less} | 8 ++- js/src/views/Accounts/List/list.js | 8 ++- js/src/views/Accounts/Summary/summary.js | 7 +- js/src/views/Accounts/accounts.js | 11 +++- js/src/views/Addresses/addresses.js | 15 ++++- js/src/views/Contracts/contracts.js | 15 ++++- js/webpack.config.js | 16 +++-- 10 files changed, 143 insertions(+), 65 deletions(-) rename js/src/ui/Tags/{tags.css => tags.less} (73%) diff --git a/js/src/ui/Actionbar/Search/search.css b/js/src/ui/Actionbar/Search/search.css index 594e34b9c9a..956a9e2e354 100644 --- a/js/src/ui/Actionbar/Search/search.css +++ b/js/src/ui/Actionbar/Search/search.css @@ -23,8 +23,21 @@ min-width: 50px !important; } -.searchInput { - transition: width 450ms !important; +.input { + width: 500px !important; +} + +.inputContainer { + transition: width 450ms ease-in-out 0ms, height 0ms ease-in-out 0ms; white-space: nowrap; overflow: hidden; + width: 500px; + height: 100%; + position: relative; +} + +.inputContainerShown { + transition: width 450ms ease-in-out 0ms, height 0ms ease-in-out 400ms; + width: 0; + height: 0; } diff --git a/js/src/ui/Actionbar/Search/search.js b/js/src/ui/Actionbar/Search/search.js index 55dbf7cd62b..5cafd34d9aa 100644 --- a/js/src/ui/Actionbar/Search/search.js +++ b/js/src/ui/Actionbar/Search/search.js @@ -17,6 +17,7 @@ import React, { Component, PropTypes } from 'react'; // import ChipInput from 'material-ui-chip-input'; import ChipInput from 'material-ui-chip-input/src/ChipInput'; +// import ChipInput from '../../../../../../../material-ui-chip-input/src/ChipInput'; import ActionSearch from 'material-ui/svg-icons/action/search'; import { uniq } from 'lodash'; @@ -26,45 +27,53 @@ import styles from './search.css'; export default class ActionbarSearch extends Component { static propTypes = { - onChange: PropTypes.func.isRequired + onChange: PropTypes.func.isRequired, + tokens: PropTypes.array }; state = { showSearch: false, - searchValues: [], stateChanging: false, inputValue: '' } + componentWillReceiveProps (nextProps) { + const { tokens } = nextProps; + + if (tokens.length > 0 && this.props.tokens.length === 0) { + this.handleOpenSearch(true, true); + } + } + render () { - const { showSearch, searchValues } = this.state; + const { showSearch } = this.state; + const { tokens } = this.props; - const searchInputStyle = { width: 500 }; + const inputContainerClasses = [ styles.inputContainer ]; if (!showSearch) { - searchInputStyle.width = 0; + inputContainerClasses.push(styles.inputContainerShown); } return (
- +
+ +
); }); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 0ba542ee78d..241806722a5 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -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,7 +37,7 @@ 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) { @@ -48,7 +49,7 @@ export default class Summary extends Component { return ( - + + empty={ !hasAccounts } + handleAddSearchToken={ this.onAddSearchToken } /> @@ -71,6 +73,7 @@ class Accounts extends Component { return (); } @@ -114,6 +117,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 diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index 8f7169c2206..03d0ba31c21 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -18,6 +18,7 @@ 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'; @@ -55,19 +56,21 @@ class Addresses extends Component { search={ searchValues } accounts={ contacts } balances={ balances } - empty={ !hasContacts } /> + empty={ !hasContacts } + handleAddSearchToken={ this.onAddSearchToken } />
); } renderSearchButton () { - const onChange = (values) => { - this.setState({ searchValues: values }); + const onChange = (searchValues) => { + this.setState({ searchValues }); }; return (); } @@ -105,6 +108,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 diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index 4c6e411adc6..cf82746ad39 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -18,6 +18,7 @@ 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, ActionbarSearch, Button, Page } from '../../ui'; import { AddContract, DeployContract } from '../../modals'; @@ -60,19 +61,21 @@ class Contracts extends Component { search={ searchValues } accounts={ contracts } balances={ balances } - empty={ !hasContracts } /> + empty={ !hasContracts } + handleAddSearchToken={ this.onAddSearchToken } />
); } renderSearchButton () { - const onChange = (values) => { - this.setState({ searchValues: values }); + const onChange = (searchValues) => { + this.setState({ searchValues }); }; return (); } @@ -130,6 +133,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 }); } diff --git a/js/webpack.config.js b/js/webpack.config.js index 78f8ebfb75d..3dea2953775 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -82,11 +82,7 @@ module.exports = { }, { test: /\.less$/, - loaders: [ - 'style', - 'css', - 'less' - ] + loaders: [ 'happypack/loader?id=less' ] }, { test: /\.(png|jpg|)$/, @@ -135,6 +131,16 @@ module.exports = { 'postcss' ] }), + new HappyPack({ + id: 'less', + threads: 4, + loaders: [ + 'style', + 'css?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', + 'less', + 'postcss' + ] + }), new HappyPack({ id: 'js', threads: 4, From 08c831aea2eb1b8ee85e773bb5b7577c24da9d51 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Tue, 18 Oct 2016 17:34:19 +0200 Subject: [PATCH 08/14] Add sort capabilities to Accounts / Addresses / Contracts (#2643) --- js/src/ui/Actionbar/Sort/index.js | 17 +++++++ js/src/ui/Actionbar/Sort/sort.css | 20 ++++++++ js/src/ui/Actionbar/Sort/sort.js | 72 +++++++++++++++++++++++++++++ js/src/ui/Tags/tags.less | 2 +- js/src/ui/index.js | 2 + js/src/views/Accounts/List/list.js | 41 +++++++++++++++- js/src/views/Accounts/accounts.js | 20 ++++++-- js/src/views/Addresses/addresses.js | 20 ++++++-- js/src/views/Contracts/contracts.js | 20 ++++++-- 9 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 js/src/ui/Actionbar/Sort/index.js create mode 100644 js/src/ui/Actionbar/Sort/sort.css create mode 100644 js/src/ui/Actionbar/Sort/sort.js diff --git a/js/src/ui/Actionbar/Sort/index.js b/js/src/ui/Actionbar/Sort/index.js new file mode 100644 index 00000000000..82855931c74 --- /dev/null +++ b/js/src/ui/Actionbar/Sort/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './sort'; diff --git a/js/src/ui/Actionbar/Sort/sort.css b/js/src/ui/Actionbar/Sort/sort.css new file mode 100644 index 00000000000..ff592ca2f3f --- /dev/null +++ b/js/src/ui/Actionbar/Sort/sort.css @@ -0,0 +1,20 @@ +/* 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 . +*/ + +.sortButton { + min-width: 50px !important; +} diff --git a/js/src/ui/Actionbar/Sort/sort.js b/js/src/ui/Actionbar/Sort/sort.js new file mode 100644 index 00000000000..4084a515c4d --- /dev/null +++ b/js/src/ui/Actionbar/Sort/sort.js @@ -0,0 +1,72 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import IconMenu from 'material-ui/IconMenu'; +import MenuItem from 'material-ui/MenuItem'; + +import SortIcon from 'material-ui/svg-icons/content/sort'; + +import { Button } from '../../'; + +import styles from './sort.css'; + +export default class ActionbarSort extends Component { + static propTypes = { + onChange: PropTypes.func.isRequired, + order: PropTypes.string + }; + + state = { + menuOpen: false + } + + render () { + return ( + } + onClick={ this.handleMenuOpen } + /> + } + open={ this.state.menuOpen } + onRequestChange={ this.handleMenuChange } + onItemTouchTap={ this.handleSortChange } + targetOrigin={ { horizontal: 'right', vertical: 'top' } } + anchorOrigin={ { horizontal: 'right', vertical: 'top' } } + > + + + + ); + } + + handleSortChange = (event, child) => { + const order = child.props.value; + this.props.onChange(order); + } + + handleMenuOpen = () => { + this.setState({ menuOpen: true }); + } + + handleMenuChange = (open) => { + this.setState({ menuOpen: open }); + } +} diff --git a/js/src/ui/Tags/tags.less b/js/src/ui/Tags/tags.less index d7016acc1d9..cac74869429 100644 --- a/js/src/ui/Tags/tags.less +++ b/js/src/ui/Tags/tags.less @@ -7,7 +7,7 @@ } :local(.tag) { - font-size: 0.9rem; + font-size: 0.8rem; background: rgba(255, 255, 255, 0.07); border-radius: 16px; margin: 0.75em 0.5em 0 0; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 1a3692165d5..8c7117a5fc0 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -16,6 +16,7 @@ import Actionbar from './Actionbar'; import ActionbarSearch from './Actionbar/Search'; +import ActionbarSort from './Actionbar/Sort'; import Badge from './Badge'; import Balance from './Balance'; import Button from './Button'; @@ -39,6 +40,7 @@ import TxHash from './TxHash'; export { Actionbar, ActionbarSearch, + ActionbarSort, AddressSelect, Badge, Balance, diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 1a73d0f3845..81d83b60cec 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -28,6 +28,7 @@ export default class List extends Component { link: PropTypes.string, search: PropTypes.array, empty: PropTypes.bool, + order: PropTypes.string, handleAddSearchToken: PropTypes.func }; @@ -52,7 +53,7 @@ export default class List extends Component { ); } - const addresses = this.getFilteredAddresses(); + const addresses = this.getAddresses(); return addresses.map((address, idx) => { const account = accounts[address] || {}; @@ -72,6 +73,44 @@ export default class List extends Component { }); } + 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()); diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index f05fa77d050..5dab8ef4fae 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -22,7 +22,7 @@ import { uniq } from 'lodash'; import List from './List'; import { CreateAccount } from '../../modals'; -import { Actionbar, ActionbarSearch, Button, Page, Tooltip } from '../../ui'; +import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui'; import styles from './accounts.css'; @@ -40,12 +40,13 @@ class Accounts extends Component { state = { addressBook: false, newDialog: false, + sortOrder: '', searchValues: [] } render () { const { accounts, hasAccounts, balances } = this.props; - const { searchValues } = this.state; + const { searchValues, sortOrder } = this.state; return (
@@ -57,6 +58,7 @@ class Accounts extends Component { accounts={ accounts } balances={ balances } empty={ !hasAccounts } + order={ sortOrder } handleAddSearchToken={ this.onAddSearchToken } /> ); } + renderSortButton () { + const onChange = (sortOrder) => { + this.setState({ sortOrder }); + }; + + return (); + } + renderActionbar () { const buttons = [