Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Vault Management UI (round 2) (#4631)
Browse files Browse the repository at this point in the history
* Add VaultMeta edit dialog

* Updated (WIP)

* Meta & password edit completed

* Added SelectionList component for selections

* Use SelectionList in DappPermisions

* AddDapps uses SelectionList

* Fix AccountCard to consistent height

* Convert Signer defaults to SelectionList

* Subtle selection border

* Display account vault information

* Allow invalid addresses to display icons (e.g. vaults)

* Display vault on edit meta

* Convert VaultAccounts to SelectionList

* Allow editing of Vault in meta

* Add tests for SectionList component

* Add tests for VaultSelector component

* Auto-focus description field (aligns with #4657)

* Apply scroll fixes from lates commit in #4621

* Remove unneeded logs

* Remove extra div, fixing ParityBar overflow

* Disable save if password don't match

* s/disabled/readOnly/

* string -> bool
  • Loading branch information
jacogr authored Feb 24, 2017
1 parent 9ff427c commit 570e6f3
Show file tree
Hide file tree
Showing 23 changed files with 1,296 additions and 65 deletions.
76 changes: 75 additions & 1 deletion js/src/modals/EditMeta/editMeta.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { newError } from '~/redux/actions';
import { Button, Form, Input, InputChip, Portal } from '~/ui';
import { Button, Form, Input, InputAddress, InputChip, Portal } from '~/ui';
import { CancelIcon, SaveIcon } from '~/ui/Icons';
import VaultStore from '~/views/Vaults/store';

import VaultSelector from '../VaultSelector';
import Store from './store';

@observer
Expand All @@ -39,6 +41,11 @@ class EditMeta extends Component {
}

store = new Store(this.context.api, this.props.account);
vaultStore = VaultStore.get(this.context.api);

componentWillMount () {
this.vaultStore.loadVaults();
}

render () {
const { description, name, nameError, tags } = this.store;
Expand All @@ -55,6 +62,7 @@ class EditMeta extends Component {
/>
}
>
{ this.renderVaultSelector() }
<Form>
<Input
autoFocus
Expand Down Expand Up @@ -102,6 +110,7 @@ class EditMeta extends Component {
onTokensChange={ this.store.setTags }
tokens={ tags.slice() }
/>
{ this.renderVault() }
</Form>
</Portal>
);
Expand Down Expand Up @@ -154,22 +163,87 @@ class EditMeta extends Component {
);
}

renderVault () {
const { isAccount, vaultName } = this.store;

if (!isAccount) {
return null;
}

return (
<InputAddress
allowCopy={ false }
allowInvalid
readOnly
hint={
<FormattedMessage
id='editMeta.vault.hint'
defaultMessage='the vault this account is attached to'
/>
}
label={
<FormattedMessage
id='editMeta.vault.label'
defaultMessage='associated vault'
/>
}
onClick={ this.toggleVaultSelector }
value={ vaultName }
/>
);
}

renderVaultSelector () {
const { isAccount, isVaultSelectorOpen, vaultName } = this.store;

if (!isAccount || !isVaultSelectorOpen) {
return null;
}

return (
<VaultSelector
onClose={ this.toggleVaultSelector }
onSelect={ this.setVaultName }
selected={ vaultName }
vaultStore={ this.vaultStore }
/>
);
}

onClose = () => {
this.props.onClose();
}

onSave = () => {
const { address, isAccount, meta, vaultName } = this.store;

if (this.store.hasError) {
return;
}

return this.store
.save()
.then(() => {
if (isAccount && (meta.vault !== vaultName)) {
return this.vaultStore.moveAccount(vaultName, address);
}

return true;
})
.then(this.onClose)
.catch((error) => {
this.props.newError(error);
});
}

setVaultName = (vaultName) => {
this.store.setVaultName(vaultName);
this.toggleVaultSelector();
}

toggleVaultSelector = () => {
this.store.toggleVaultSelector();
}
}

function mapDispatchToProps (dispatch) {
Expand Down
4 changes: 3 additions & 1 deletion js/src/modals/EditMeta/editMeta.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ function createApi () {
return {
parity: {
setAccountName: sinon.stub().resolves(),
setAccountMeta: sinon.stub().resolves()
setAccountMeta: sinon.stub().resolves(),
listVaults: sinon.stub().resolves([]),
listOpenedVaults: sinon.stub().resolves([])
}
};
}
Expand Down
17 changes: 16 additions & 1 deletion js/src/modals/EditMeta/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,30 @@ import { validateName } from '~/util/validation';
export default class Store {
@observable address = null;
@observable isAccount = false;
@observable isVaultSelectorOpen = false;
@observable description = null;
@observable meta = null;
@observable name = null;
@observable nameError = null;
@observable passwordHint = null;
@observable tags = null;
@observable vaultName = null;

constructor (api, account) {
const { address, name, meta, uuid } = account;

this._api = api;

transaction(() => {
this.isAccount = !!uuid;
this.address = address;
this.meta = meta || {};
this.name = name || '';
this.isAccount = !!uuid;

this.description = this.meta.description || '';
this.passwordHint = this.meta.passwordHint || '';
this.tags = this.meta.tags && this.meta.tags.peek() || [];
this.vaultName = this.meta.vault;
});
}

Expand Down Expand Up @@ -74,6 +77,14 @@ export default class Store {
this.tags = tags.slice();
}

@action setVaultName = (vaultName) => {
this.vaultName = vaultName;
}

@action setVaultSelectorOpen = (isOpen) => {
this.isVaultSelectorOpen = isOpen;
}

save () {
const meta = {
description: this.description,
Expand All @@ -94,4 +105,8 @@ export default class Store {
throw error;
});
}

toggleVaultSelector () {
this.setVaultSelectorOpen(!this.isVaultSelectorOpen);
}
}
61 changes: 42 additions & 19 deletions js/src/modals/EditMeta/store.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,31 +142,54 @@ describe('modals/EditMeta/Store', () => {
expect(store.tags.peek()).to.deep.equal(['taga', 'tagb']);
});
});
});

describe('save', () => {
beforeEach(() => {
createStore(ACCOUNT);
describe('setVaultName', () => {
it('sets the name', () => {
store.setVaultName('testing');
expect(store.vaultName).to.equal('testing');
});
});

it('calls parity.setAccountName with the set value', () => {
store.setName('test name');
store.save();
describe('setVaultSelectorOpen', () => {
it('sets the state', () => {
store.setVaultSelectorOpen('testing');
expect(store.isVaultSelectorOpen).to.equal('testing');
});
});
});

expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
describe('operations', () => {
describe('save', () => {
beforeEach(() => {
createStore(ACCOUNT);
});

it('calls parity.setAccountName with the set value', () => {
store.setName('test name');
store.save();

expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
});

it('calls parity.setAccountMeta with the adjusted values', () => {
store.setDescription('some new description');
store.setPasswordHint('some new passwordhint');
store.setTags(['taga']);
store.save();

expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
description: 'some new description',
passwordHint: 'some new passwordhint',
tags: ['taga']
}));
});
});
});

it('calls parity.setAccountMeta with the adjusted values', () => {
store.setDescription('some new description');
store.setPasswordHint('some new passwordhint');
store.setTags(['taga']);
store.save();

expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
description: 'some new description',
passwordHint: 'some new passwordhint',
tags: ['taga']
}));
describe('toggleVaultSelector', () => {
it('inverts the selector state', () => {
store.toggleVaultSelector();
expect(store.isVaultSelectorOpen).to.be.true;
});
});
});
36 changes: 21 additions & 15 deletions js/src/modals/VaultCreate/vaultCreate.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,30 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/

.body {
/* TODO: These styles are shared with CreateAccount - DRY up */
.passwords {
display: flex;
flex-wrap: wrap;
/* TODO: These styles are shared with CreateAccount - DRY up */
.passwords {
display: flex;
flex-wrap: wrap;

.password {
box-sizing: border-box;
flex: 0 1 50%;
width: 50%;
.password {
box-sizing: border-box;
flex: 0 1 50%;
width: 50%;

&:nth-child(odd) {
padding-right: 0.25rem;
}
&:nth-child(odd) {
padding-right: 0.25rem;
}

&:nth-child(even) {
padding-left: 0.25rem;
}
&:nth-child(even) {
padding-left: 0.25rem;
}
}
}

.disabled {
opacity: 0.25;
}

.group+.group {
margin-top: 1em;
}
2 changes: 1 addition & 1 deletion js/src/modals/VaultCreate/vaultCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class VaultCreate extends Component {
/>
}
>
<div className={ styles.body }>
<div>
<Input
error={ vaultNameError }
hint={
Expand Down
17 changes: 17 additions & 0 deletions js/src/modals/VaultMeta/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (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 './vaultMeta';
Loading

0 comments on commit 570e6f3

Please sign in to comment.