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:
parent
dadd6b1e7c
commit
cc10f412dc
@ -82,8 +82,6 @@
|
|||||||
"istanbul": "^1.0.0-alpha.2",
|
"istanbul": "^1.0.0-alpha.2",
|
||||||
"jsdom": "9.2.1",
|
"jsdom": "9.2.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"less": "^2.7.1",
|
|
||||||
"less-loader": "^2.2.3",
|
|
||||||
"mocha": "^3.0.0-1",
|
"mocha": "^3.0.0-1",
|
||||||
"mock-local-storage": "1.0.2",
|
"mock-local-storage": "1.0.2",
|
||||||
"mock-socket": "^3.0.1",
|
"mock-socket": "^3.0.1",
|
||||||
@ -119,6 +117,7 @@
|
|||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"marked": "^0.3.6",
|
"marked": "^0.3.6",
|
||||||
"material-ui": "^0.15.4",
|
"material-ui": "^0.15.4",
|
||||||
|
"material-ui-chip-input": "^0.8.0",
|
||||||
"moment": "^2.14.1",
|
"moment": "^2.14.1",
|
||||||
"react": "^15.2.1",
|
"react": "^15.2.1",
|
||||||
"react-addons-css-transition-group": "^15.2.1",
|
"react-addons-css-transition-group": "^15.2.1",
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
import ContentSave from 'material-ui/svg-icons/content/save';
|
import ContentSave from 'material-ui/svg-icons/content/save';
|
||||||
|
// import ChipInput from 'material-ui-chip-input';
|
||||||
|
import ChipInput from 'material-ui-chip-input/src/ChipInput';
|
||||||
|
|
||||||
import { Button, Form, Input, Modal } from '../../ui';
|
import { Button, Form, Input, Modal } from '../../ui';
|
||||||
import { validateName } from '../../util/validation';
|
import { validateName } from '../../util/validation';
|
||||||
@ -55,6 +57,7 @@ export default class EditMeta extends Component {
|
|||||||
error={ nameError }
|
error={ nameError }
|
||||||
onSubmit={ this.onNameChange } />
|
onSubmit={ this.onNameChange } />
|
||||||
{ this.renderMetaFields() }
|
{ this.renderMetaFields() }
|
||||||
|
{ this.renderTags() }
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -96,6 +99,23 @@ export default class EditMeta extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderTags () {
|
||||||
|
const { meta } = this.state;
|
||||||
|
const { tags } = meta || [];
|
||||||
|
const onChange = (chips) => this.onMetaChange('tags', chips);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChipInput
|
||||||
|
defaultValue={ tags }
|
||||||
|
onChange={ onChange }
|
||||||
|
floatingLabelText='(optional) tags'
|
||||||
|
hintText='press <Enter> to add a tag'
|
||||||
|
floatingLabelFixed
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onNameChange = (name) => {
|
onNameChange = (name) => {
|
||||||
this.setState(validateName(name));
|
this.setState(validateName(name));
|
||||||
}
|
}
|
||||||
|
17
js/src/ui/Actionbar/Search/index.js
Normal file
17
js/src/ui/Actionbar/Search/index.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './search';
|
43
js/src/ui/Actionbar/Search/search.css
Normal file
43
js/src/ui/Actionbar/Search/search.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/* 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/>.
|
||||||
|
*/
|
||||||
|
.searchcontainer {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchButton {
|
||||||
|
min-width: 50px !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;
|
||||||
|
}
|
176
js/src/ui/Actionbar/Search/search.js
Normal file
176
js/src/ui/Actionbar/Search/search.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
// import ChipInput from 'material-ui-chip-input';
|
||||||
|
import ChipInput from 'material-ui-chip-input/src/ChipInput';
|
||||||
|
import ActionSearch from 'material-ui/svg-icons/action/search';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
|
||||||
|
import { Button } from '../../';
|
||||||
|
|
||||||
|
import styles from './search.css';
|
||||||
|
|
||||||
|
export default class ActionbarSearch extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
tokens: PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showSearch: false,
|
||||||
|
stateChanging: false,
|
||||||
|
inputValue: '',
|
||||||
|
timeoutIds: []
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
const { tokens } = nextProps;
|
||||||
|
|
||||||
|
if (tokens.length > 0 && this.props.tokens.length === 0) {
|
||||||
|
this.handleOpenSearch(true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
const { timeoutIds } = this.state;
|
||||||
|
|
||||||
|
if (timeoutIds.length > 0) {
|
||||||
|
timeoutIds.map(id => window.clearTimeout(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { showSearch } = this.state;
|
||||||
|
const { tokens } = this.props;
|
||||||
|
|
||||||
|
const inputContainerClasses = [ styles.inputContainer ];
|
||||||
|
|
||||||
|
if (!showSearch) {
|
||||||
|
inputContainerClasses.push(styles.inputContainerShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={ styles.searchcontainer }
|
||||||
|
key='searchAccount'>
|
||||||
|
<div className={ inputContainerClasses.join(' ') }>
|
||||||
|
<ChipInput
|
||||||
|
clearOnBlur={ false }
|
||||||
|
className={ styles.input }
|
||||||
|
hintText='Enter search input...'
|
||||||
|
hintStyle={ {
|
||||||
|
transition: 'none'
|
||||||
|
} }
|
||||||
|
ref='searchInput'
|
||||||
|
value={ tokens }
|
||||||
|
onBlur={ this.handleSearchBlur }
|
||||||
|
onRequestAdd={ this.handleTokenAdd }
|
||||||
|
onRequestDelete={ this.handleTokenDelete }
|
||||||
|
onUpdateInput={ this.handleInputChange } />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={ styles.searchButton }
|
||||||
|
icon={ <ActionSearch /> }
|
||||||
|
label=''
|
||||||
|
onClick={ this.handleSearchClick } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTokenAdd = (value) => {
|
||||||
|
const { tokens } = this.props;
|
||||||
|
|
||||||
|
const newSearchValues = uniq([].concat(tokens, value));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inputValue: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleSearchChange(newSearchValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTokenDelete = (value) => {
|
||||||
|
const { tokens } = this.props;
|
||||||
|
|
||||||
|
const newSearchValues = []
|
||||||
|
.concat(tokens)
|
||||||
|
.filter(v => v !== value);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inputValue: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleSearchChange(newSearchValues);
|
||||||
|
this.refs.searchInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (value) => {
|
||||||
|
this.setState({ inputValue: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchChange = (searchValues) => {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
const newSearchValues = searchValues.filter(v => v.length > 0);
|
||||||
|
|
||||||
|
onChange(newSearchValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchClick = () => {
|
||||||
|
const { showSearch } = this.state;
|
||||||
|
|
||||||
|
this.handleOpenSearch(!showSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchBlur = () => {
|
||||||
|
const timeoutId = window.setTimeout(() => {
|
||||||
|
const { inputValue } = this.state;
|
||||||
|
const { tokens } = this.props;
|
||||||
|
|
||||||
|
if (tokens.length === 0 && inputValue.length === 0) {
|
||||||
|
this.handleOpenSearch(false);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timeoutIds: [].concat(this.state.timeoutIds, timeoutId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOpenSearch = (showSearch, force) => {
|
||||||
|
if (this.state.stateChanging && !force) return false;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showSearch: showSearch,
|
||||||
|
stateChanging: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (showSearch) {
|
||||||
|
this.refs.searchInput.focus();
|
||||||
|
} else {
|
||||||
|
this.refs.searchInput.getInputNode().blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeoutId = window.setTimeout(() => {
|
||||||
|
this.setState({ stateChanging: false });
|
||||||
|
}, 450);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timeoutIds: [].concat(this.state.timeoutIds, timeoutId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
17
js/src/ui/Actionbar/Sort/index.js
Normal file
17
js/src/ui/Actionbar/Sort/index.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './sort';
|
20
js/src/ui/Actionbar/Sort/sort.css
Normal file
20
js/src/ui/Actionbar/Sort/sort.css
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.sortButton {
|
||||||
|
min-width: 50px !important;
|
||||||
|
}
|
73
js/src/ui/Actionbar/Sort/sort.js
Normal file
73
js/src/ui/Actionbar/Sort/sort.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<IconMenu
|
||||||
|
iconButtonElement={
|
||||||
|
<Button
|
||||||
|
className={ styles.sortButton }
|
||||||
|
label=''
|
||||||
|
icon={ <SortIcon /> }
|
||||||
|
onClick={ this.handleMenuOpen }
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
open={ this.state.menuOpen }
|
||||||
|
onRequestChange={ this.handleMenuChange }
|
||||||
|
onItemTouchTap={ this.handleSortChange }
|
||||||
|
targetOrigin={ { horizontal: 'right', vertical: 'top' } }
|
||||||
|
anchorOrigin={ { horizontal: 'right', vertical: 'top' } }
|
||||||
|
>
|
||||||
|
<MenuItem value='' primaryText='Default' />
|
||||||
|
<MenuItem value='tags' primaryText='Sort by tags' />
|
||||||
|
<MenuItem value='name' primaryText='Sort by name' />
|
||||||
|
</IconMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSortChange = (event, child) => {
|
||||||
|
const order = child.props.value;
|
||||||
|
this.props.onChange(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMenuOpen = () => {
|
||||||
|
this.setState({ menuOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMenuChange = (open) => {
|
||||||
|
this.setState({ menuOpen: open });
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,15 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.container {
|
.container {
|
||||||
padding: 0em
|
padding: 0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.padded {
|
.padded {
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
background: rgba(0, 0, 0, 0.8) !important;
|
background: rgba(0, 0, 0, 0.8) !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.light .padded {
|
.light .padded {
|
||||||
|
17
js/src/ui/Tags/index.js
Normal file
17
js/src/ui/Tags/index.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './tags';
|
36
js/src/ui/Tags/tags.css
Normal file
36
js/src/ui/Tags/tags.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.25rem;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
border-radius: 16px;
|
||||||
|
margin: 0.75em 0.5em 0 0;
|
||||||
|
padding: 0.25em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagClickable:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
56
js/src/ui/Tags/tags.js
Normal file
56
js/src/ui/Tags/tags.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import styles from './tags.css';
|
||||||
|
|
||||||
|
export default class Tags extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
tags: PropTypes.array,
|
||||||
|
handleAddSearchToken: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (<div className={ styles.tags }>
|
||||||
|
{ this.renderTags() }
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTags () {
|
||||||
|
const { handleAddSearchToken } = this.props;
|
||||||
|
const tags = this.props.tags || [];
|
||||||
|
|
||||||
|
const tagClasses = handleAddSearchToken
|
||||||
|
? [ styles.tag, styles.tagClickable ]
|
||||||
|
: [ styles.tag ];
|
||||||
|
|
||||||
|
return tags.map((tag, idx) => {
|
||||||
|
const onClick = handleAddSearchToken
|
||||||
|
? () => handleAddSearchToken(tag)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={ idx }
|
||||||
|
className={ tagClasses.join(' ') }
|
||||||
|
onClick={ onClick }>
|
||||||
|
{ tag }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Actionbar from './Actionbar';
|
import Actionbar from './Actionbar';
|
||||||
|
import ActionbarSearch from './Actionbar/Search';
|
||||||
|
import ActionbarSort from './Actionbar/Sort';
|
||||||
import Badge from './Badge';
|
import Badge from './Badge';
|
||||||
import Balance from './Balance';
|
import Balance from './Balance';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
@ -31,11 +33,14 @@ import muiTheme from './Theme';
|
|||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import ParityBackground from './ParityBackground';
|
import ParityBackground from './ParityBackground';
|
||||||
import SignerIcon from './SignerIcon';
|
import SignerIcon from './SignerIcon';
|
||||||
|
import Tags from './Tags';
|
||||||
import Tooltips, { Tooltip } from './Tooltips';
|
import Tooltips, { Tooltip } from './Tooltips';
|
||||||
import TxHash from './TxHash';
|
import TxHash from './TxHash';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Actionbar,
|
Actionbar,
|
||||||
|
ActionbarSearch,
|
||||||
|
ActionbarSort,
|
||||||
AddressSelect,
|
AddressSelect,
|
||||||
Badge,
|
Badge,
|
||||||
Balance,
|
Balance,
|
||||||
@ -62,6 +67,7 @@ export {
|
|||||||
Page,
|
Page,
|
||||||
ParityBackground,
|
ParityBackground,
|
||||||
SignerIcon,
|
SignerIcon,
|
||||||
|
Tags,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Tooltips,
|
Tooltips,
|
||||||
TxHash
|
TxHash
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.balances {
|
.balances, .tags {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
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';
|
import styles from './header.css';
|
||||||
|
|
||||||
@ -70,6 +70,9 @@ export default class Header extends Component {
|
|||||||
</div>
|
</div>
|
||||||
{ this.renderTxCount() }
|
{ this.renderTxCount() }
|
||||||
</div>
|
</div>
|
||||||
|
<div className={ styles.tags }>
|
||||||
|
<Tags tags={ meta.tags } />
|
||||||
|
</div>
|
||||||
<div className={ styles.balances }>
|
<div className={ styles.balances }>
|
||||||
<Balance
|
<Balance
|
||||||
account={ account }
|
account={ account }
|
||||||
|
@ -26,7 +26,10 @@ export default class List extends Component {
|
|||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
link: PropTypes.string,
|
link: PropTypes.string,
|
||||||
empty: PropTypes.bool
|
search: PropTypes.array,
|
||||||
|
empty: PropTypes.bool,
|
||||||
|
order: PropTypes.string,
|
||||||
|
handleAddSearchToken: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -38,7 +41,7 @@ export default class List extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { accounts, balances, link, empty } = this.props;
|
const { accounts, balances, link, empty, handleAddSearchToken } = this.props;
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return (
|
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 account = accounts[address] || {};
|
||||||
const balance = balances[address] || {};
|
const balance = balances[address] || {};
|
||||||
|
|
||||||
@ -61,9 +66,79 @@ export default class List extends Component {
|
|||||||
<Summary
|
<Summary
|
||||||
link={ link }
|
link={ link }
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance } />
|
balance={ balance }
|
||||||
|
handleAddSearchToken={ handleAddSearchToken } />
|
||||||
</div>
|
</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 React, { Component, PropTypes } from 'react';
|
||||||
import { Link } from 'react-router';
|
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 {
|
export default class Summary extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -28,7 +28,8 @@ export default class Summary extends Component {
|
|||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
balance: PropTypes.object.isRequired,
|
balance: PropTypes.object.isRequired,
|
||||||
link: PropTypes.string,
|
link: PropTypes.string,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
handleAddSearchToken: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -36,21 +37,24 @@ export default class Summary extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, balance, children, link } = this.props;
|
const { account, balance, children, link, handleAddSearchToken } = this.props;
|
||||||
|
const { tags } = account.meta;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewLink = `/${link || 'account'}/${account.address}`;
|
const { address } = account;
|
||||||
|
const viewLink = `/${link || 'account'}/${address}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
<Tags tags={ tags } handleAddSearchToken={ handleAddSearchToken } />
|
||||||
<IdentityIcon
|
<IdentityIcon
|
||||||
address={ account.address } />
|
address={ address } />
|
||||||
<ContainerTitle
|
<ContainerTitle
|
||||||
title={ <Link to={ viewLink }>{ <IdentityName address={ account.address } unknown /> }</Link> }
|
title={ <Link to={ viewLink }>{ <IdentityName address={ address } unknown /> }</Link> }
|
||||||
byline={ account.address } />
|
byline={ address } />
|
||||||
<Balance
|
<Balance
|
||||||
balance={ balance } />
|
balance={ balance } />
|
||||||
{ children }
|
{ children }
|
||||||
|
@ -18,10 +18,11 @@ 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 ContentAdd from 'material-ui/svg-icons/content/add';
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
|
||||||
import List from './List';
|
import List from './List';
|
||||||
import { CreateAccount } from '../../modals';
|
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';
|
import styles from './accounts.css';
|
||||||
|
|
||||||
@ -38,11 +39,14 @@ class Accounts extends Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
addressBook: false,
|
addressBook: false,
|
||||||
newDialog: false
|
newDialog: false,
|
||||||
|
sortOrder: '',
|
||||||
|
searchValues: []
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, hasAccounts, balances } = this.props;
|
const { accounts, hasAccounts, balances } = this.props;
|
||||||
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.accounts }>
|
<div className={ styles.accounts }>
|
||||||
@ -50,9 +54,12 @@ class Accounts extends Component {
|
|||||||
{ this.renderActionbar() }
|
{ this.renderActionbar() }
|
||||||
<Page>
|
<Page>
|
||||||
<List
|
<List
|
||||||
|
search={ searchValues }
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
empty={ !hasAccounts } />
|
empty={ !hasAccounts }
|
||||||
|
order={ sortOrder }
|
||||||
|
handleAddSearchToken={ this.onAddSearchToken } />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className={ styles.accountTooltip }
|
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' />
|
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 () {
|
renderActionbar () {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
key='newAccount'
|
key='newAccount'
|
||||||
icon={ <ContentAdd /> }
|
icon={ <ContentAdd /> }
|
||||||
label='new account'
|
label='new account'
|
||||||
onClick={ this.onNewAccountClick } />
|
onClick={ this.onNewAccountClick } />,
|
||||||
|
|
||||||
|
this.renderSearchButton(),
|
||||||
|
this.renderSortButton()
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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 = () => {
|
onNewAccountClick = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
newDialog: !this.state.newDialog
|
newDialog: !this.state.newDialog
|
||||||
|
@ -18,10 +18,11 @@ 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 ContentAdd from 'material-ui/svg-icons/content/add';
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
|
import { uniq } from 'lodash';
|
||||||
|
|
||||||
import List from '../Accounts/List';
|
import List from '../Accounts/List';
|
||||||
import { AddAddress } from '../../modals';
|
import { AddAddress } from '../../modals';
|
||||||
import { Actionbar, Button, Page } from '../../ui';
|
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
|
||||||
|
|
||||||
import styles from './addresses.css';
|
import styles from './addresses.css';
|
||||||
|
|
||||||
@ -37,11 +38,14 @@ class Addresses extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
showAdd: false
|
showAdd: false,
|
||||||
|
sortOrder: '',
|
||||||
|
searchValues: []
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { balances, contacts, hasContacts } = this.props;
|
const { balances, contacts, hasContacts } = this.props;
|
||||||
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.addresses }>
|
<div className={ styles.addresses }>
|
||||||
@ -50,21 +54,53 @@ class Addresses extends Component {
|
|||||||
<Page>
|
<Page>
|
||||||
<List
|
<List
|
||||||
link='address'
|
link='address'
|
||||||
|
search={ searchValues }
|
||||||
accounts={ contacts }
|
accounts={ contacts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
empty={ !hasContacts } />
|
empty={ !hasContacts }
|
||||||
|
order={ sortOrder }
|
||||||
|
handleAddSearchToken={ this.onAddSearchToken } />
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</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 () {
|
renderActionbar () {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
key='newAddress'
|
key='newAddress'
|
||||||
icon={ <ContentAdd /> }
|
icon={ <ContentAdd /> }
|
||||||
label='new address'
|
label='new address'
|
||||||
onClick={ this.onOpenAdd } />
|
onClick={ this.onOpenAdd } />,
|
||||||
|
|
||||||
|
this.renderSearchButton(),
|
||||||
|
this.renderSortButton()
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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 = () => {
|
onOpenAdd = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showAdd: true
|
showAdd: true
|
||||||
|
@ -18,8 +18,9 @@ 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 ContentAdd from 'material-ui/svg-icons/content/add';
|
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 { AddContract, DeployContract } from '../../modals';
|
||||||
|
|
||||||
import List from '../Accounts/List';
|
import List from '../Accounts/List';
|
||||||
@ -40,11 +41,14 @@ class Contracts extends Component {
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
addContract: false,
|
addContract: false,
|
||||||
deployContract: false
|
deployContract: false,
|
||||||
|
sortOrder: '',
|
||||||
|
searchValues: []
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { contracts, hasContracts, balances } = this.props;
|
const { contracts, hasContracts, balances } = this.props;
|
||||||
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.contracts }>
|
<div className={ styles.contracts }>
|
||||||
@ -55,14 +59,43 @@ class Contracts extends Component {
|
|||||||
<Page>
|
<Page>
|
||||||
<List
|
<List
|
||||||
link='contract'
|
link='contract'
|
||||||
|
search={ searchValues }
|
||||||
accounts={ contracts }
|
accounts={ contracts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
empty={ !hasContracts } />
|
empty={ !hasContracts }
|
||||||
|
order={ sortOrder }
|
||||||
|
handleAddSearchToken={ this.onAddSearchToken } />
|
||||||
</Page>
|
</Page>
|
||||||
</div>
|
</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 () {
|
renderActionbar () {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
<Button
|
<Button
|
||||||
@ -74,7 +107,10 @@ class Contracts extends Component {
|
|||||||
key='deployContract'
|
key='deployContract'
|
||||||
icon={ <ContentAdd /> }
|
icon={ <ContentAdd /> }
|
||||||
label='deploy contract'
|
label='deploy contract'
|
||||||
onClick={ this.onDeployContract } />
|
onClick={ this.onDeployContract } />,
|
||||||
|
|
||||||
|
this.renderSearchButton(),
|
||||||
|
this.renderSortButton()
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
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 = () => {
|
onDeployContractClose = () => {
|
||||||
this.setState({ deployContract: false });
|
this.setState({ deployContract: false });
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,11 @@ module.exports = {
|
|||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
loaders: [ 'happypack/loader?id=js' ]
|
loaders: [ 'happypack/loader?id=js' ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
include: /node_modules\/material-ui-chip-input/,
|
||||||
|
loader: 'babel'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.json$/,
|
test: /\.json$/,
|
||||||
loaders: ['json']
|
loaders: ['json']
|
||||||
@ -76,14 +81,6 @@ module.exports = {
|
|||||||
exclude: [/src/],
|
exclude: [/src/],
|
||||||
loader: 'style!css'
|
loader: 'style!css'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
loaders: [
|
|
||||||
'style',
|
|
||||||
'css',
|
|
||||||
'less'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg|)$/,
|
test: /\.(png|jpg|)$/,
|
||||||
loader: 'file-loader'
|
loader: 'file-loader'
|
||||||
|
Loading…
Reference in New Issue
Block a user