[beta] UI updates for 1.5.1 (#4429)
* s/Delete Contract/Forget Contract/ (#4237) * Adjust the location of the signer snippet (#4155) * Additional building-block UI components (#4239) * Currency WIP * Expand tests * Pass className * Add QrCode * Export new components in ~/ui * s/this.props.netSymbol/netSymbol/ * Fix import case * ui/SectionList component (#4292) * array chunking utility * add SectionList component * Add TODOs to indicate possible future work * Add missing overlay style (as used in dapps at present) * Add a Playground for the UI Components (#4301) * Playground // WIP * Linting * Add Examples with code * CSS Linting * Linting * Add Connected Currency Symbol * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * Added `renderSymbol` tests * PR grumbles * Add Eth and Btc QRCode examples * 2015-2017 * Add tests for playground * Fixing tests * Split Dapp icon into ui/DappIcon (#4308) * Add QrCode & Copy to ShapeShift (#4322) * Extract CopyIcon to ~/ui/Icons * Add copy & QrCode address * Default size 4 * Add bitcoin: link * use protocol links applicable to coin exchanged * Remove .only * Display QrCode for accounts, addresses & contracts (#4329) * Allow Portal to be used as top-level modal (#4338) * Portal * Allow Portal to be used in as both top-level and popover * modal/popover variable naming * export Portal in ~/ui * Properly handle optional onKeyDown * Add simple Playground Example * Add proper event listener to Portal (#4359) * Display AccountCard name via IdentityName (#4235) * Fix signing (#4363) * Dapp Account Selection & Defaults (#4355) * Add parity_defaultAccount RPC (with subscription) (#4383) * Default Account selector in Signer overlay (#4375) * Typo, fixes #4271 (#4391) * Fix ParityBar account selection overflows (#4405) * Available Dapp selection alignment with Permissions (Portal) (#4374) * registry dapp: make lookup use lower case (#4409) * Dapps use defaultAccount instead of own selectors (#4386) * Poll for defaultAccount to update dapp & overlay subscriptions (#4417) * Poll for defaultAccount (Fixes #4413) * Fix nextTimeout on catch * Store timers * Re-enable default updates on change detection * Add block & timestamp conditions to Signer (#4411) * Extension installation overlay (#4423) * Extension installation overlay * Pr gumbles * Spelling * Update Chrome URL * Fix for non-included jsonrpc * Extend Portal component (as per Modal) #4392
This commit is contained in:
@@ -20,8 +20,8 @@
|
||||
margin: 0.5em 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
|
||||
@@ -53,6 +53,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.75em;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
@@ -86,14 +93,10 @@
|
||||
.accountName {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.balance {
|
||||
.tag {
|
||||
margin-left: 0.5em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@keyframes copied {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -18,21 +18,20 @@ import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import keycode from 'keycode';
|
||||
|
||||
import Balance from '~/ui/Balance';
|
||||
import IdentityIcon from '~/ui/IdentityIcon';
|
||||
import IdentityName from '~/ui/IdentityName';
|
||||
import Tags from '~/ui/Tags';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
|
||||
import styles from './accountCard.css';
|
||||
|
||||
export default class AccountCard extends Component {
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onFocus: PropTypes.func.isRequired,
|
||||
|
||||
balance: PropTypes.object
|
||||
balance: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onFocus: PropTypes.func
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -40,15 +39,11 @@ export default class AccountCard extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { account, balance, className } = this.props;
|
||||
const { copied } = this.state;
|
||||
|
||||
const { address, name, description, meta = {} } = account;
|
||||
|
||||
const displayName = (name && name.toUpperCase()) || address;
|
||||
const { address, description, meta = {}, name } = account;
|
||||
const { tags = [] } = meta;
|
||||
|
||||
const classes = [ styles.account ];
|
||||
const classes = [ styles.account, className ];
|
||||
|
||||
if (copied) {
|
||||
classes.push(styles.copied);
|
||||
@@ -63,17 +58,28 @@ export default class AccountCard extends Component {
|
||||
onFocus={ this.onFocus }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
>
|
||||
<IdentityIcon address={ address } />
|
||||
<div className={ styles.accountInfo }>
|
||||
<div className={ styles.accountName }>
|
||||
<span>{ displayName }</span>
|
||||
<div className={ styles.infoContainer }>
|
||||
<IdentityIcon address={ address } />
|
||||
<div className={ styles.accountInfo }>
|
||||
<div className={ styles.accountName }>
|
||||
<IdentityName
|
||||
address={ address }
|
||||
name={ name }
|
||||
unknown
|
||||
/>
|
||||
</div>
|
||||
{ this.renderDescription(description) }
|
||||
{ this.renderAddress(address) }
|
||||
</div>
|
||||
|
||||
{ this.renderTags(tags, address) }
|
||||
{ this.renderDescription(description) }
|
||||
{ this.renderAddress(displayName, address) }
|
||||
{ this.renderBalance(address) }
|
||||
</div>
|
||||
|
||||
<Tags tags={ tags } />
|
||||
<Balance
|
||||
balance={ balance }
|
||||
className={ styles.balance }
|
||||
showOnlyEth
|
||||
showZeroValues
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -90,11 +96,7 @@ export default class AccountCard extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderAddress (name, address) {
|
||||
if (name === address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
renderAddress (address) {
|
||||
return (
|
||||
<div className={ styles.addressContainer }>
|
||||
<span
|
||||
@@ -109,40 +111,6 @@ export default class AccountCard extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderTags (tags = [], address) {
|
||||
if (tags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tags tags={ tags } />
|
||||
);
|
||||
}
|
||||
|
||||
renderBalance (address) {
|
||||
const { balance = {} } = this.props;
|
||||
|
||||
if (!balance.tokens) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ethToken = balance.tokens
|
||||
.find((tok) => tok.token && (tok.token.tag || '').toLowerCase() === 'eth');
|
||||
|
||||
if (!ethToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = fromWei(ethToken.value).toFormat(3);
|
||||
|
||||
return (
|
||||
<div className={ styles.balance }>
|
||||
<span>{ value }</span>
|
||||
<span className={ styles.tag }>ETH</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
@@ -158,6 +126,7 @@ export default class AccountCard extends Component {
|
||||
// @see https://developers.google.com/web/updates/2015/04/cut-and-copy-commands
|
||||
try {
|
||||
const range = document.createRange();
|
||||
|
||||
range.selectNode(element);
|
||||
window.getSelection().addRange(range);
|
||||
document.execCommand('copy');
|
||||
@@ -184,12 +153,14 @@ export default class AccountCard extends Component {
|
||||
|
||||
onClick = () => {
|
||||
const { account, onClick } = this.props;
|
||||
onClick(account.address);
|
||||
|
||||
onClick && onClick(account.address);
|
||||
}
|
||||
|
||||
onFocus = () => {
|
||||
const { account, onFocus } = this.props;
|
||||
onFocus(account.index);
|
||||
|
||||
onFocus && onFocus(account.index);
|
||||
}
|
||||
|
||||
preventEvent = (e) => {
|
||||
|
||||
133
js/src/ui/AccountCard/accountCard.spec.js
Normal file
133
js/src/ui/AccountCard/accountCard.spec.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import AccountCard from './';
|
||||
|
||||
const TEST_ADDRESS = '0x1234567890123456789012345678901234567890';
|
||||
const TEST_NAME = 'Jimmy';
|
||||
|
||||
let component;
|
||||
let onClick;
|
||||
let onFocus;
|
||||
|
||||
function render (props = {}) {
|
||||
if (!props.account) {
|
||||
props.account = {
|
||||
address: TEST_ADDRESS,
|
||||
description: 'testDescription',
|
||||
name: TEST_NAME,
|
||||
meta: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClick = sinon.stub();
|
||||
onFocus = sinon.stub();
|
||||
|
||||
component = shallow(
|
||||
<AccountCard
|
||||
{ ...props }
|
||||
onClick={ onClick }
|
||||
onFocus={ onFocus }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/AccountCard', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
describe('Balance', () => {
|
||||
let balance;
|
||||
|
||||
beforeEach(() => {
|
||||
balance = component.find('Connect(Balance)');
|
||||
});
|
||||
|
||||
it('renders the balance', () => {
|
||||
expect(balance.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('sets showOnlyEth & showZeroValues', () => {
|
||||
expect(balance.props().showOnlyEth).to.be.true;
|
||||
expect(balance.props().showZeroValues).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('IdentityIcon', () => {
|
||||
let icon;
|
||||
|
||||
beforeEach(() => {
|
||||
icon = component.find('Connect(IdentityIcon)');
|
||||
});
|
||||
|
||||
it('renders the icon', () => {
|
||||
expect(icon.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes the address through', () => {
|
||||
expect(icon.props().address).to.equal(TEST_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IdentityName', () => {
|
||||
let name;
|
||||
|
||||
beforeEach(() => {
|
||||
name = component.find('Connect(IdentityName)');
|
||||
});
|
||||
|
||||
it('renders the name', () => {
|
||||
expect(name.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes the address through', () => {
|
||||
expect(name.props().address).to.equal(TEST_ADDRESS);
|
||||
});
|
||||
|
||||
it('passes the name through', () => {
|
||||
expect(name.props().name).to.equal(TEST_NAME);
|
||||
});
|
||||
|
||||
it('renders unknown (no name)', () => {
|
||||
expect(name.props().unknown).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tags', () => {
|
||||
let tags;
|
||||
|
||||
beforeEach(() => {
|
||||
tags = component.find('Tags');
|
||||
});
|
||||
|
||||
it('renders the tags', () => {
|
||||
expect(tags.length).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,31 +16,46 @@
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import unknownImage from '../../../assets/images/contracts/unknown-64x64.png';
|
||||
import unknownImage from '~/../assets/images/contracts/unknown-64x64.png';
|
||||
|
||||
import styles from './balance.css';
|
||||
|
||||
class Balance extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
balance: PropTypes.object,
|
||||
images: PropTypes.object.isRequired
|
||||
}
|
||||
className: PropTypes.string,
|
||||
images: PropTypes.object.isRequired,
|
||||
showOnlyEth: PropTypes.bool,
|
||||
showZeroValues: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showOnlyEth: false,
|
||||
showZeroValues: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { api } = this.context;
|
||||
const { balance, images } = this.props;
|
||||
const { balance, className, images, showZeroValues, showOnlyEth } = this.props;
|
||||
|
||||
if (!balance) {
|
||||
if (!balance || !balance.tokens) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let body = (balance.tokens || [])
|
||||
.filter((balance) => new BigNumber(balance.value).gt(0))
|
||||
let body = balance.tokens
|
||||
.filter((balance) => {
|
||||
const hasBalance = showZeroValues || new BigNumber(balance.value).gt(0);
|
||||
const isValidToken = !showOnlyEth || (balance.token.tag || '').toLowerCase() === 'eth';
|
||||
|
||||
return hasBalance && isValidToken;
|
||||
})
|
||||
.map((balance, index) => {
|
||||
const token = balance.token;
|
||||
|
||||
@@ -92,13 +107,16 @@ class Balance extends Component {
|
||||
if (!body.length) {
|
||||
body = (
|
||||
<div className={ styles.empty }>
|
||||
There are no balances associated with this account
|
||||
<FormattedMessage
|
||||
id='ui.balance.none'
|
||||
defaultMessage='There are no balances associated with this account'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.balances }>
|
||||
<div className={ [styles.balances, className].join(' ') }>
|
||||
{ body }
|
||||
</div>
|
||||
);
|
||||
|
||||
122
js/src/ui/Balance/balance.spec.js
Normal file
122
js/src/ui/Balance/balance.spec.js
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import apiutil from '~/api/util';
|
||||
|
||||
import Balance from './';
|
||||
|
||||
const BALANCE = {
|
||||
tokens: [
|
||||
{ value: '122', token: { tag: 'ETH' } },
|
||||
{ value: '345', token: { tag: 'GAV', format: 1 } },
|
||||
{ value: '0', token: { tag: 'TST', format: 1 } }
|
||||
]
|
||||
};
|
||||
|
||||
let api;
|
||||
let component;
|
||||
let store;
|
||||
|
||||
function createApi () {
|
||||
api = {
|
||||
dappsUrl: 'http://testDapps:1234/',
|
||||
util: apiutil
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function createStore () {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
images: {}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
if (!props.balance) {
|
||||
props.balance = BALANCE;
|
||||
}
|
||||
|
||||
component = shallow(
|
||||
<Balance
|
||||
className='testClass'
|
||||
{ ...props }
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
store: createStore()
|
||||
}
|
||||
}
|
||||
).find('Balance').shallow({ context: { api: createApi() } });
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/Balance', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('passes the specified className', () => {
|
||||
expect(component.hasClass('testClass')).to.be.true;
|
||||
});
|
||||
|
||||
it('renders all the non-zero balances', () => {
|
||||
expect(component.find('img')).to.have.length(2);
|
||||
});
|
||||
|
||||
describe('render specifiers', () => {
|
||||
it('renders only the single token with showOnlyEth', () => {
|
||||
render({ showOnlyEth: true });
|
||||
expect(component.find('img')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders all the tokens with showZeroValues', () => {
|
||||
render({ showZeroValues: true });
|
||||
expect(component.find('img')).to.have.length(3);
|
||||
});
|
||||
|
||||
it('shows ETH with zero value with showOnlyEth & showZeroValues', () => {
|
||||
render({
|
||||
showOnlyEth: true,
|
||||
showZeroValues: true,
|
||||
balance: {
|
||||
tokens: [
|
||||
{ value: '0', token: { tag: 'ETH' } },
|
||||
{ value: '345', token: { tag: 'GAV', format: 1 } }
|
||||
]
|
||||
}
|
||||
});
|
||||
expect(component.find('img')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,30 +14,36 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.byline, .description {
|
||||
|
||||
$bylineColor: #aaa;
|
||||
$bylineLineHeight: 1.2rem;
|
||||
$bylineMaxHeight: 2.4rem;
|
||||
$titleLineHeight: 2rem;
|
||||
$smallFontSize: 0.75rem;
|
||||
|
||||
.byline,
|
||||
.description {
|
||||
color: $bylineColor;
|
||||
display: -webkit-box;
|
||||
line-height: $bylineLineHeight;
|
||||
max-height: $bylineMaxHeight;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: 1.2em;
|
||||
max-height: 2.4em;
|
||||
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
|
||||
color: #aaa;
|
||||
|
||||
* {
|
||||
color: #aaa !important;
|
||||
color: $bylineColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.75em;
|
||||
font-size: $smallFontSize;
|
||||
margin: 0.5em 0 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
line-height: $titleLineHeight;
|
||||
margin: 0;
|
||||
line-height: 34px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -29,29 +29,41 @@ export default class Title extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { byline, className, title } = this.props;
|
||||
|
||||
const byLine = typeof byline === 'string'
|
||||
? (
|
||||
<span title={ byline }>
|
||||
{ byline }
|
||||
</span>
|
||||
)
|
||||
: byline;
|
||||
const { className, title } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
<h3 className={ styles.title }>
|
||||
{ title }
|
||||
</h3>
|
||||
<div className={ styles.byline }>
|
||||
{ byLine }
|
||||
</div>
|
||||
{ this.renderByline() }
|
||||
{ this.renderDescription() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderByline () {
|
||||
const { byline } = this.props;
|
||||
|
||||
if (!byline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.byline }>
|
||||
{
|
||||
typeof byline === 'string'
|
||||
? (
|
||||
<span title={ byline }>
|
||||
{ byline }
|
||||
</span>
|
||||
)
|
||||
: byline
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderDescription () {
|
||||
const { description } = this.props;
|
||||
|
||||
@@ -59,17 +71,17 @@ export default class Title extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const desc = typeof description === 'string'
|
||||
? (
|
||||
<span title={ description }>
|
||||
{ description }
|
||||
</span>
|
||||
)
|
||||
: description;
|
||||
|
||||
return (
|
||||
<div className={ styles.description }>
|
||||
{ desc }
|
||||
{
|
||||
typeof description === 'string'
|
||||
? (
|
||||
<span title={ description }>
|
||||
{ description }
|
||||
</span>
|
||||
)
|
||||
: description
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,15 +29,14 @@ export default class Container extends Component {
|
||||
className: PropTypes.string,
|
||||
compact: PropTypes.bool,
|
||||
light: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
tabIndex: PropTypes.number,
|
||||
title: nodeOrStringProptype()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, className, compact, light, style, tabIndex } = this.props;
|
||||
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
|
||||
|
||||
const { children, className, compact, light, onClick, style, tabIndex } = this.props;
|
||||
const props = {};
|
||||
|
||||
if (Number.isInteger(tabIndex)) {
|
||||
@@ -45,8 +44,27 @@ export default class Container extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ classes } style={ style } { ...props }>
|
||||
<Card className={ compact ? styles.compact : styles.padded }>
|
||||
<div
|
||||
className={
|
||||
[
|
||||
styles.container,
|
||||
light
|
||||
? styles.light
|
||||
: '',
|
||||
className
|
||||
].join(' ')
|
||||
}
|
||||
style={ style }
|
||||
{ ...props }
|
||||
>
|
||||
<Card
|
||||
className={
|
||||
compact
|
||||
? styles.compact
|
||||
: styles.padded
|
||||
}
|
||||
onClick={ onClick }
|
||||
>
|
||||
{ this.renderTitle() }
|
||||
{ children }
|
||||
</Card>
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { IconButton } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import Clipboard from 'react-copy-to-clipboard';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { IconButton } from 'material-ui';
|
||||
import Clipboard from 'react-copy-to-clipboard';
|
||||
import CopyIcon from 'material-ui/svg-icons/content/content-copy';
|
||||
import Theme from '../Theme';
|
||||
|
||||
import { showSnackbar } from '~/redux/providers/snackbarActions';
|
||||
|
||||
const { textColor, disabledTextColor } = Theme.flatButton;
|
||||
import { CopyIcon } from '../Icons';
|
||||
import Theme from '../Theme';
|
||||
|
||||
import styles from './copyToClipboard.css';
|
||||
|
||||
const { textColor, disabledTextColor } = Theme.flatButton;
|
||||
|
||||
class CopyToClipboard extends Component {
|
||||
static propTypes = {
|
||||
showSnackbar: PropTypes.func.isRequired,
|
||||
|
||||
51
js/src/ui/CurrencySymbol/currencySymbol.example.js
Normal file
51
js/src/ui/CurrencySymbol/currencySymbol.example.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import PlaygroundExample from '~/playground/playgroundExample';
|
||||
|
||||
import ConnectedCurrencySymbol, { CurrencySymbol } from './currencySymbol';
|
||||
|
||||
export default class CurrencySymbolExample extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<PlaygroundExample name='Connected Currency Symbol'>
|
||||
<ConnectedCurrencySymbol />
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Simple Currency Symbol'>
|
||||
<CurrencySymbol
|
||||
netChain='testnet'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='ETC Currency Symbol'>
|
||||
<CurrencySymbol
|
||||
netChain='classic'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='EXP Currency Symbol'>
|
||||
<CurrencySymbol
|
||||
netChain='expanse'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
65
js/src/ui/CurrencySymbol/currencySymbol.js
Normal file
65
js/src/ui/CurrencySymbol/currencySymbol.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const SYMBOL_ETC = 'ETC';
|
||||
const SYMBOL_ETH = 'ETH';
|
||||
const SYMBOL_EXP = 'EXP';
|
||||
|
||||
export class CurrencySymbol extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
netChain: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<span className={ className }>{ this.renderSymbol() }</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderSymbol () {
|
||||
const { netChain } = this.props;
|
||||
|
||||
switch (netChain) {
|
||||
case 'classic':
|
||||
return SYMBOL_ETC;
|
||||
|
||||
case 'expanse':
|
||||
return SYMBOL_EXP;
|
||||
|
||||
default:
|
||||
return SYMBOL_ETH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { netChain } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
netChain
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(CurrencySymbol);
|
||||
99
js/src/ui/CurrencySymbol/currencySymbol.spec.js
Normal file
99
js/src/ui/CurrencySymbol/currencySymbol.spec.js
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import CurrencySymbol from './';
|
||||
|
||||
let component;
|
||||
let store;
|
||||
|
||||
function createRedux (netChain = 'ropsten') {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
nodeStatus: {
|
||||
netChain
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (netChain, props = {}) {
|
||||
component = shallow(
|
||||
<CurrencySymbol { ...props } />,
|
||||
{
|
||||
context: {
|
||||
store: createRedux(netChain)
|
||||
}
|
||||
}
|
||||
).find('CurrencySymbol').shallow();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/CurrencySymbol', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('passes the className as provided', () => {
|
||||
expect(render('ropsten', { className: 'test' }).find('span').hasClass('test')).to.be.true;
|
||||
});
|
||||
|
||||
describe('currencies', () => {
|
||||
it('renders ETH as default', () => {
|
||||
expect(render().text()).equal('ETH');
|
||||
});
|
||||
|
||||
it('renders ETC for classic', () => {
|
||||
expect(render('classic').text()).equal('ETC');
|
||||
});
|
||||
|
||||
it('renders EXP for expanse', () => {
|
||||
expect(render('expanse').text()).equal('EXP');
|
||||
});
|
||||
|
||||
it('renders ETH as default', () => {
|
||||
expect(render('somethingElse').text()).equal('ETH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderSymbol', () => {
|
||||
it('render defaults', () => {
|
||||
expect(render().instance().renderSymbol()).to.be.ok;
|
||||
});
|
||||
|
||||
it('render ETH as default', () => {
|
||||
expect(render().instance().renderSymbol()).equal('ETH');
|
||||
});
|
||||
|
||||
it('render ETC', () => {
|
||||
expect(render('classic').instance().renderSymbol()).equal('ETC');
|
||||
});
|
||||
|
||||
it('render EXP', () => {
|
||||
expect(render('expanse').instance().renderSymbol()).equal('EXP');
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/ui/CurrencySymbol/index.js
Normal file
17
js/src/ui/CurrencySymbol/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 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 './currencySymbol';
|
||||
41
js/src/ui/DappCard/dappCard.css
Normal file
41
js/src/ui/DappCard/dappCard.css
Normal file
@@ -0,0 +1,41 @@
|
||||
/* Copyright 2015, 2016 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/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image {
|
||||
left: 1.5em;
|
||||
position: absolute;
|
||||
top: 1.5em;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 72px;
|
||||
}
|
||||
|
||||
.title {
|
||||
mragin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.author, .version {
|
||||
font-size: 0.75em;
|
||||
opacity: 0.5;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
98
js/src/ui/DappCard/dappCard.js
Normal file
98
js/src/ui/DappCard/dappCard.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import Container, { Title as ContainerTitle } from '~/ui/Container';
|
||||
import DappIcon from '~/ui/DappIcon';
|
||||
import Tags from '~/ui/Tags';
|
||||
|
||||
import styles from './dappCard.css';
|
||||
|
||||
export default class DappCard extends Component {
|
||||
static propTypes = {
|
||||
app: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
showLink: PropTypes.bool,
|
||||
showTags: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showLink: false,
|
||||
showTags: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { app, children, className, onClick, showLink, showTags } = this.props;
|
||||
|
||||
if (!app) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={
|
||||
[styles.container, className].join(' ')
|
||||
}
|
||||
onClick={ onClick }
|
||||
>
|
||||
<DappIcon
|
||||
app={ app }
|
||||
className={ styles.image }
|
||||
/>
|
||||
<Tags
|
||||
tags={
|
||||
showTags
|
||||
? [app.type]
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<div className={ styles.description }>
|
||||
<ContainerTitle
|
||||
className={ styles.title }
|
||||
title={
|
||||
showLink
|
||||
? this.renderLink(app)
|
||||
: app.name
|
||||
}
|
||||
byline={ app.description }
|
||||
/>
|
||||
<div className={ styles.author }>
|
||||
{ app.author }, v{ app.version }
|
||||
</div>
|
||||
{ children }
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderLink (app) {
|
||||
return (
|
||||
<Link
|
||||
to={
|
||||
app.url === 'web'
|
||||
? '/web'
|
||||
: `/app/${app.id}`
|
||||
}
|
||||
>
|
||||
{ app.name }
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/ui/DappCard/index.js
Normal file
17
js/src/ui/DappCard/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 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 './dappCard';
|
||||
31
js/src/ui/DappIcon/dappIcon.css
Normal file
31
js/src/ui/DappIcon/dappIcon.css
Normal file
@@ -0,0 +1,31 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
margin: 0 0.75em 0 0;
|
||||
}
|
||||
|
||||
.normal {
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.small {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
49
js/src/ui/DappIcon/dappIcon.js
Normal file
49
js/src/ui/DappIcon/dappIcon.js
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import styles from './dappIcon.css';
|
||||
|
||||
export default class DappIcon extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
app: PropTypes.object.isRequired,
|
||||
className: PropTypes.string,
|
||||
small: PropTypes.bool
|
||||
};
|
||||
|
||||
render () {
|
||||
const { dappsUrl } = this.context.api;
|
||||
const { app, className, small } = this.props;
|
||||
|
||||
return (
|
||||
<img
|
||||
className={
|
||||
[styles.icon, styles[small ? 'small' : 'normal'], className].join(' ')
|
||||
}
|
||||
src={
|
||||
app.type === 'local'
|
||||
? `${dappsUrl}/${app.id}/${app.iconUrl}`
|
||||
: `${dappsUrl}${app.image}`
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
70
js/src/ui/DappIcon/dappIcon.spec.js
Normal file
70
js/src/ui/DappIcon/dappIcon.spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import DappIcon from './';
|
||||
|
||||
const DAPPS_URL = 'http://test';
|
||||
|
||||
let api;
|
||||
let component;
|
||||
|
||||
function createApi () {
|
||||
api = {
|
||||
dappsUrl: DAPPS_URL
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
if (!props.app) {
|
||||
props.app = {};
|
||||
}
|
||||
|
||||
component = shallow(
|
||||
<DappIcon { ...props } />,
|
||||
{
|
||||
context: { api: createApi() }
|
||||
}
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/DappIcon', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('adds specified className', () => {
|
||||
expect(render({ className: 'testClass' }).hasClass('testClass')).to.be.true;
|
||||
});
|
||||
|
||||
it('renders local apps with correct URL', () => {
|
||||
expect(render({ app: { id: 'test', type: 'local', iconUrl: 'test.img' } }).props().src).to.equal(
|
||||
`${DAPPS_URL}/test/test.img`
|
||||
);
|
||||
});
|
||||
|
||||
it('renders other apps with correct URL', () => {
|
||||
expect(render({ app: { id: 'test', image: '/test.img' } }).props().src).to.equal(
|
||||
`${DAPPS_URL}/test.img`
|
||||
);
|
||||
});
|
||||
});
|
||||
17
js/src/ui/DappIcon/index.js
Normal file
17
js/src/ui/DappIcon/index.js
Normal file
@@ -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 './dappIcon';
|
||||
@@ -73,6 +73,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 1rem 0.5rem 0.25em;
|
||||
color: rgba(255, 255, 255, 0.498039);
|
||||
@@ -102,14 +108,11 @@
|
||||
}
|
||||
|
||||
.categories {
|
||||
flex: 1;
|
||||
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
margin: 2rem 0 0;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -176,37 +176,42 @@ class AddressSelect extends Component {
|
||||
return (
|
||||
<Portal
|
||||
className={ styles.inputContainer }
|
||||
isChildModal
|
||||
onClose={ this.handleClose }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
open={ expanded }
|
||||
title={
|
||||
<div className={ styles.title }>
|
||||
<label className={ styles.label } htmlFor={ id }>
|
||||
{ label }
|
||||
</label>
|
||||
<div className={ styles.outerInput }>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
{ this.renderLoader() }
|
||||
</div>
|
||||
|
||||
<div className={ styles.underline }>
|
||||
<TextFieldUnderline
|
||||
focus={ inputFocused }
|
||||
focusStyle={ BOTTOM_BORDER_STYLE }
|
||||
muiTheme={ muiTheme }
|
||||
style={ BOTTOM_BORDER_STYLE }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ this.renderCurrentInput() }
|
||||
{ this.renderRegistryValues() }
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<label className={ styles.label } htmlFor={ id }>
|
||||
{ label }
|
||||
</label>
|
||||
<div className={ styles.outerInput }>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
{ this.renderLoader() }
|
||||
</div>
|
||||
|
||||
<div className={ styles.underline }>
|
||||
<TextFieldUnderline
|
||||
focus={ inputFocused }
|
||||
focusStyle={ BOTTOM_BORDER_STYLE }
|
||||
muiTheme={ muiTheme }
|
||||
style={ BOTTOM_BORDER_STYLE }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ this.renderCurrentInput() }
|
||||
{ this.renderRegistryValues() }
|
||||
{ this.renderAccounts() }
|
||||
</Portal>
|
||||
);
|
||||
|
||||
@@ -80,6 +80,9 @@ class InputAddress extends Component {
|
||||
props.focused = focused;
|
||||
}
|
||||
|
||||
// FIXME: The is not advisable, fixes the display issue, however the name should come from
|
||||
// a common component.
|
||||
// account.name || (value ? 'UNNAMED' : value)
|
||||
return (
|
||||
<div className={ containerClasses.join(' ') }>
|
||||
<Input
|
||||
@@ -98,7 +101,7 @@ class InputAddress extends Component {
|
||||
tabIndex={ tabIndex }
|
||||
value={
|
||||
text && account
|
||||
? account.name
|
||||
? (account.name || (value ? 'UNNAMED' : value))
|
||||
: (nullName || value)
|
||||
}
|
||||
{ ...props }
|
||||
|
||||
17
js/src/ui/Form/InputDate/index.js
Normal file
17
js/src/ui/Form/InputDate/index.js
Normal file
@@ -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 './inputDate';
|
||||
22
js/src/ui/Form/InputDate/inputDate.css
Normal file
22
js/src/ui/Form/InputDate/inputDate.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
53
js/src/ui/Form/InputDate/inputDate.js
Normal file
53
js/src/ui/Form/InputDate/inputDate.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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/>.
|
||||
|
||||
import { DatePicker } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import Label from '../Label';
|
||||
|
||||
import styles from './inputDate.css';
|
||||
|
||||
// NOTE: Has to be larger than Signer overlay Z, aligns with ../InputTime
|
||||
const DIALOG_STYLE = { zIndex: 10010 };
|
||||
|
||||
export default class InputDate extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
hint: PropTypes.node,
|
||||
label: PropTypes.node,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className, hint, label, onChange, value } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ [styles.container, className].join(' ') }>
|
||||
<Label label={ label } />
|
||||
<DatePicker
|
||||
autoOk
|
||||
className={ styles.input }
|
||||
dialogContainerStyle={ DIALOG_STYLE }
|
||||
hintText={ hint }
|
||||
onChange={ onChange }
|
||||
value={ value }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/ui/Form/InputTime/index.js
Normal file
17
js/src/ui/Form/InputTime/index.js
Normal file
@@ -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 './inputTime';
|
||||
22
js/src/ui/Form/InputTime/inputTime.css
Normal file
22
js/src/ui/Form/InputTime/inputTime.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
54
js/src/ui/Form/InputTime/inputTime.js
Normal file
54
js/src/ui/Form/InputTime/inputTime.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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/>.
|
||||
|
||||
import { TimePicker } from 'material-ui';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import Label from '../Label';
|
||||
|
||||
import styles from './inputTime.css';
|
||||
|
||||
// NOTE: Has to be larger than Signer overlay Z, aligns with ../InputDate
|
||||
const DIALOG_STYLE = { zIndex: 10010 };
|
||||
|
||||
export default class InputTime extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
hint: PropTypes.node,
|
||||
label: PropTypes.node,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, hint, label, onChange, value } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ [styles.container, className].join(' ') }>
|
||||
<Label label={ label } />
|
||||
<TimePicker
|
||||
autoOk
|
||||
className={ styles.input }
|
||||
dialogStyle={ DIALOG_STYLE }
|
||||
format='24hr'
|
||||
hintText={ hint }
|
||||
onChange={ onChange }
|
||||
value={ value }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
js/src/ui/Form/Label/index.js
Normal file
17
js/src/ui/Form/Label/index.js
Normal file
@@ -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 './label';
|
||||
24
js/src/ui/Form/Label/label.css
Normal file
24
js/src/ui/Form/Label/label.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
$labelColor: rgba(255, 255, 255, 0.5);
|
||||
$labelFontSize: 0.75rem;
|
||||
|
||||
.label {
|
||||
color: $labelColor;
|
||||
font-size: $labelFontSize;
|
||||
}
|
||||
40
js/src/ui/Form/Label/label.js
Normal file
40
js/src/ui/Form/Label/label.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import styles from './label.css';
|
||||
|
||||
export default class Label extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.node
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, label } = this.props;
|
||||
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={ [styles.label, className].join(' ') }>
|
||||
{ label }
|
||||
</label>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,23 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.spaced {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
.container {
|
||||
.label {
|
||||
}
|
||||
|
||||
.typeContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.radioButton {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #ccc;
|
||||
z-index: 2;
|
||||
.radioLabel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #ccc;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -18,10 +18,14 @@ import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||
|
||||
import Label from '../Label';
|
||||
import styles from './radioButtons.css';
|
||||
|
||||
export default class RadioButtons extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
label: PropTypes.node,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.any,
|
||||
@@ -34,10 +38,10 @@ export default class RadioButtons extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { value, values } = this.props;
|
||||
const { className, label, value, values } = this.props;
|
||||
|
||||
const index = Number.isNaN(parseInt(value))
|
||||
? values.findIndex((val) => val.key === value)
|
||||
? values.findIndex((_value) => _value.key === value)
|
||||
: parseInt(value);
|
||||
const selectedValue = typeof value !== 'object'
|
||||
? values[index]
|
||||
@@ -45,12 +49,19 @@ export default class RadioButtons extends Component {
|
||||
const key = this.getKey(selectedValue, index);
|
||||
|
||||
return (
|
||||
<RadioButtonGroup
|
||||
name={ name }
|
||||
onChange={ this.onChange }
|
||||
valueSelected={ key } >
|
||||
{ this.renderContent() }
|
||||
</RadioButtonGroup>
|
||||
<div className={ [styles.container, className].join(' ') }>
|
||||
<Label
|
||||
className={ styles.label }
|
||||
label={ label }
|
||||
/>
|
||||
<RadioButtonGroup
|
||||
name={ name }
|
||||
onChange={ this.onChange }
|
||||
valueSelected={ key }
|
||||
>
|
||||
{ this.renderContent() }
|
||||
</RadioButtonGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,19 +77,20 @@ export default class RadioButtons extends Component {
|
||||
|
||||
return (
|
||||
<RadioButton
|
||||
className={ styles.spaced }
|
||||
className={ styles.radioButton }
|
||||
key={ index }
|
||||
label={
|
||||
<div className={ styles.typeContainer }>
|
||||
<div className={ styles.radioLabel }>
|
||||
<span>{ label }</span>
|
||||
{
|
||||
description
|
||||
? <span className={ styles.desc }>{ description }</span>
|
||||
? <span className={ styles.description }>{ description }</span>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
}
|
||||
value={ key } />
|
||||
value={ key }
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -95,7 +107,7 @@ export default class RadioButtons extends Component {
|
||||
|
||||
onChange = (event, index) => {
|
||||
const { onChange, values } = this.props;
|
||||
const value = values[index] || values.find((v) => v.key === index);
|
||||
const value = values[index] || values.find((value) => value.key === index);
|
||||
|
||||
onChange(value, index);
|
||||
}
|
||||
|
||||
@@ -16,25 +16,31 @@
|
||||
|
||||
import AddressSelect from './AddressSelect';
|
||||
import FormWrap from './FormWrap';
|
||||
import TypedInput from './TypedInput';
|
||||
import Input from './Input';
|
||||
import InputAddress from './InputAddress';
|
||||
import InputAddressSelect from './InputAddressSelect';
|
||||
import InputChip from './InputChip';
|
||||
import InputDate from './InputDate';
|
||||
import InputInline from './InputInline';
|
||||
import Select from './Select';
|
||||
import InputTime from './InputTime';
|
||||
import Label from './Label';
|
||||
import RadioButtons from './RadioButtons';
|
||||
import Select from './Select';
|
||||
import TypedInput from './TypedInput';
|
||||
|
||||
export default from './form';
|
||||
export {
|
||||
AddressSelect,
|
||||
FormWrap,
|
||||
TypedInput,
|
||||
Input,
|
||||
InputAddress,
|
||||
InputAddressSelect,
|
||||
InputChip,
|
||||
InputDate,
|
||||
InputInline,
|
||||
InputTime,
|
||||
Label,
|
||||
RadioButtons,
|
||||
Select,
|
||||
RadioButtons
|
||||
TypedInput
|
||||
};
|
||||
|
||||
@@ -16,6 +16,46 @@
|
||||
*/
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.conditionContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
.input {
|
||||
flex: 0 1 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.conditionRadio {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1em;
|
||||
|
||||
&>label {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
&>div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&>div {
|
||||
width: auto !important;
|
||||
|
||||
label {
|
||||
padding-right: 1.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.graphContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -17,13 +17,44 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Input from '../Form/Input';
|
||||
import { Input, InputDate, InputTime, RadioButtons } from '../Form';
|
||||
import GasPriceSelector from '../GasPriceSelector';
|
||||
import Store from './store';
|
||||
|
||||
import Store, { CONDITIONS } from './store';
|
||||
import styles from './gasPriceEditor.css';
|
||||
|
||||
const CONDITION_VALUES = [
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.none'
|
||||
defaultMessage='No conditions'
|
||||
/>
|
||||
),
|
||||
key: CONDITIONS.NONE
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.blocknumber'
|
||||
defaultMessage='Send after BlockNumber'
|
||||
/>
|
||||
),
|
||||
key: CONDITIONS.BLOCK
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.datetime'
|
||||
defaultMessage='Send after Date & Time'
|
||||
/>
|
||||
),
|
||||
key: CONDITIONS.TIME
|
||||
}
|
||||
];
|
||||
|
||||
@observer
|
||||
export default class GasPriceEditor extends Component {
|
||||
static contextTypes = {
|
||||
@@ -41,7 +72,7 @@ export default class GasPriceEditor extends Component {
|
||||
render () {
|
||||
const { api } = this.context;
|
||||
const { children, store } = this.props;
|
||||
const { errorGas, errorPrice, errorTotal, estimated, gas, histogram, price, priceDefault, totalValue } = store;
|
||||
const { conditionType, errorGas, errorPrice, errorTotal, estimated, gas, histogram, price, priceDefault, totalValue } = store;
|
||||
|
||||
const eth = api.util.fromWei(totalValue).toFormat();
|
||||
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
||||
@@ -49,43 +80,147 @@ export default class GasPriceEditor extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.graphColumn }>
|
||||
<GasPriceSelector
|
||||
histogram={ histogram }
|
||||
onChange={ this.onEditGasPrice }
|
||||
price={ price } />
|
||||
<div className={ styles.gasPriceDesc }>
|
||||
You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.
|
||||
<RadioButtons
|
||||
className={ styles.conditionRadio }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.label'
|
||||
defaultMessage='Condition where transaction activates'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeConditionType }
|
||||
value={ conditionType }
|
||||
values={ CONDITION_VALUES }
|
||||
/>
|
||||
{ this.renderConditions() }
|
||||
|
||||
<div className={ styles.graphContainer }>
|
||||
<div className={ styles.graphColumn }>
|
||||
<GasPriceSelector
|
||||
histogram={ histogram }
|
||||
onChange={ this.onEditGasPrice }
|
||||
price={ price }
|
||||
/>
|
||||
<div className={ styles.gasPriceDesc }>
|
||||
<FormattedMessage
|
||||
id='txEditor.gas.info'
|
||||
defaultMessage='You can choose the gas price based on the distribution of recent included transaction gas prices. The lower the gas price is, the cheaper the transaction will be. The higher the gas price is, the faster it should get mined by the network.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={ styles.editColumn }>
|
||||
<div className={ styles.row }>
|
||||
<Input
|
||||
error={ errorGas }
|
||||
hint='the amount of gas to use for the transaction'
|
||||
label={ gasLabel }
|
||||
min={ 1 }
|
||||
onChange={ this.onEditGas }
|
||||
type='number'
|
||||
value={ gas }
|
||||
/>
|
||||
<Input
|
||||
error={ errorPrice }
|
||||
hint='the price of gas to use for the transaction'
|
||||
label={ priceLabel }
|
||||
min={ 1 }
|
||||
onChange={ this.onEditGasPrice }
|
||||
type='number'
|
||||
value={ price }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.row }>
|
||||
<Input
|
||||
disabled
|
||||
error={ errorTotal }
|
||||
hint='the total amount of the transaction'
|
||||
label='total transaction amount'
|
||||
value={ `${eth} ETH` }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.row }>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<div className={ styles.editColumn }>
|
||||
<div className={ styles.row }>
|
||||
renderConditions () {
|
||||
const { conditionType, condition, conditionBlockError } = this.props.store;
|
||||
|
||||
if (conditionType === CONDITIONS.NONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (conditionType === CONDITIONS.BLOCK) {
|
||||
return (
|
||||
<div className={ styles.conditionContainer }>
|
||||
<div className={ styles.input }>
|
||||
<Input
|
||||
error={ errorGas }
|
||||
hint='the amount of gas to use for the transaction'
|
||||
label={ gasLabel }
|
||||
onChange={ this.onEditGas }
|
||||
value={ gas } />
|
||||
<Input
|
||||
error={ errorPrice }
|
||||
hint='the price of gas to use for the transaction'
|
||||
label={ priceLabel }
|
||||
onChange={ this.onEditGasPrice }
|
||||
value={ price } />
|
||||
</div>
|
||||
<div className={ styles.row }>
|
||||
<Input
|
||||
disabled
|
||||
error={ errorTotal }
|
||||
hint='the total amount of the transaction'
|
||||
label='total transaction amount'
|
||||
value={ `${eth} ETH` } />
|
||||
</div>
|
||||
<div className={ styles.row }>
|
||||
{ children }
|
||||
error={ conditionBlockError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.block.hint'
|
||||
defaultMessage='The minimum block to send from'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.block.label'
|
||||
defaultMessage='Transaction send block'
|
||||
/>
|
||||
}
|
||||
min={ 1 }
|
||||
onChange={ this.onChangeConditionBlock }
|
||||
type='number'
|
||||
value={ condition.block }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.conditionContainer }>
|
||||
<div className={ styles.input }>
|
||||
<InputDate
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.date.hint'
|
||||
defaultMessage='The minimum date to send from'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.date.label'
|
||||
defaultMessage='Transaction send date'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeConditionDateTime }
|
||||
value={ condition.time }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.input }>
|
||||
<InputTime
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.time.hint'
|
||||
defaultMessage='The minimum time to send from'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='txEditor.condition.time.label'
|
||||
defaultMessage='Transaction send time'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangeConditionDateTime }
|
||||
value={ condition.time }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -103,4 +238,16 @@ export default class GasPriceEditor extends Component {
|
||||
store.setPrice(price);
|
||||
onChange && onChange('gasPrice', price);
|
||||
}
|
||||
|
||||
onChangeConditionType = (conditionType) => {
|
||||
this.props.store.setConditionType(conditionType.key);
|
||||
}
|
||||
|
||||
onChangeConditionBlock = (event, blockNumber) => {
|
||||
this.props.store.setConditionBlockNumber(blockNumber);
|
||||
}
|
||||
|
||||
onChangeConditionDateTime = (event, datetime) => {
|
||||
this.props.store.setConditionDateTime(datetime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,26 +21,64 @@ import sinon from 'sinon';
|
||||
|
||||
import GasPriceEditor from './';
|
||||
|
||||
const api = {
|
||||
util: {
|
||||
fromWei: (value) => new BigNumber(value)
|
||||
}
|
||||
};
|
||||
let api;
|
||||
let component;
|
||||
let store;
|
||||
|
||||
const store = {
|
||||
estimated: '123',
|
||||
histogram: {},
|
||||
priceDefault: '456',
|
||||
totalValue: '789',
|
||||
setGas: sinon.stub(),
|
||||
setPrice: sinon.stub()
|
||||
};
|
||||
function createApi () {
|
||||
api = {
|
||||
eth: {
|
||||
blockNumber: sinon.stub().resolves(new BigNumber(3))
|
||||
},
|
||||
util: {
|
||||
fromWei: (value) => new BigNumber(value)
|
||||
}
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function createStore () {
|
||||
createApi();
|
||||
|
||||
store = {
|
||||
_api: api,
|
||||
conditionType: 'none',
|
||||
estimated: '123',
|
||||
histogram: {},
|
||||
priceDefault: '456',
|
||||
totalValue: '789',
|
||||
setGas: sinon.stub(),
|
||||
setPrice: sinon.stub()
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
createStore();
|
||||
|
||||
component = shallow(
|
||||
<GasPriceEditor
|
||||
store={ store }
|
||||
{ ...props }
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
api
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/GasPriceEditor', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(shallow(
|
||||
<GasPriceEditor store={ store } />,
|
||||
{ context: { api } }
|
||||
)).to.be.ok;
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,17 @@ import { action, computed, observable, transaction } from 'mobx';
|
||||
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||
|
||||
const CONDITIONS = {
|
||||
NONE: 'none',
|
||||
BLOCK: 'blockNumber',
|
||||
TIME: 'timestamp'
|
||||
};
|
||||
|
||||
export default class GasPriceEditor {
|
||||
@observable blockNumber = 0;
|
||||
@observable condition = {};
|
||||
@observable conditionBlockError = null;
|
||||
@observable conditionType = CONDITIONS.NONE;
|
||||
@observable errorEstimated = null;
|
||||
@observable errorGas = null;
|
||||
@observable errorPrice = null;
|
||||
@@ -34,13 +44,23 @@ export default class GasPriceEditor {
|
||||
@observable priceDefault;
|
||||
@observable weiValue = '0';
|
||||
|
||||
constructor (api, { gas, gasLimit, gasPrice }) {
|
||||
constructor (api, { gas, gasLimit, gasPrice, condition = null }) {
|
||||
this._api = api;
|
||||
|
||||
this.gas = gas;
|
||||
this.gasLimit = gasLimit;
|
||||
this.price = gasPrice;
|
||||
|
||||
if (condition) {
|
||||
if (condition.block) {
|
||||
this.condition = { block: condition.block.toFixed(0) };
|
||||
this.conditionType = CONDITIONS.BLOCK;
|
||||
} else if (condition.time) {
|
||||
this.condition = { time: condition.time };
|
||||
this.conditionType = CONDITIONS.TIME;
|
||||
}
|
||||
}
|
||||
|
||||
if (api) {
|
||||
this.loadDefaults();
|
||||
}
|
||||
@@ -54,6 +74,39 @@ export default class GasPriceEditor {
|
||||
}
|
||||
}
|
||||
|
||||
@action setConditionType = (conditionType = CONDITIONS.NONE) => {
|
||||
transaction(() => {
|
||||
this.conditionBlockError = null;
|
||||
this.conditionType = conditionType;
|
||||
|
||||
switch (conditionType) {
|
||||
case CONDITIONS.BLOCK:
|
||||
this.condition = Object.assign({}, this.condition, { block: this.blockNumber || 1 });
|
||||
break;
|
||||
|
||||
case CONDITIONS.TIME:
|
||||
this.condition = Object.assign({}, this.condition, { time: new Date() });
|
||||
break;
|
||||
|
||||
case CONDITIONS.NONE:
|
||||
default:
|
||||
this.condition = {};
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action setConditionBlockNumber = (block) => {
|
||||
transaction(() => {
|
||||
this.conditionBlockError = validatePositiveNumber(block).numberError;
|
||||
this.condition = Object.assign({}, this.condition, { block });
|
||||
});
|
||||
}
|
||||
|
||||
@action setConditionDateTime = (time) => {
|
||||
this.condition = Object.assign({}, this.condition, { time });
|
||||
}
|
||||
|
||||
@action setEditing = (isEditing) => {
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
@@ -130,9 +183,10 @@ export default class GasPriceEditor {
|
||||
bucket_bounds: [],
|
||||
counts: []
|
||||
})),
|
||||
this._api.eth.gasPrice()
|
||||
this._api.eth.gasPrice(),
|
||||
this._api.eth.blockNumber()
|
||||
])
|
||||
.then(([histogram, _price]) => {
|
||||
.then(([histogram, _price, blockNumber]) => {
|
||||
transaction(() => {
|
||||
const price = _price.toFixed(0);
|
||||
|
||||
@@ -142,6 +196,7 @@ export default class GasPriceEditor {
|
||||
this.setHistogram(histogram);
|
||||
|
||||
this.priceDefault = price;
|
||||
this.blockNumber = blockNumber.toNumber();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -150,13 +205,37 @@ export default class GasPriceEditor {
|
||||
}
|
||||
|
||||
overrideTransaction = (transaction) => {
|
||||
if (this.errorGas || this.errorPrice) {
|
||||
if (this.errorGas || this.errorPrice || this.conditionBlockError) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
return Object.assign({}, transaction, {
|
||||
const override = {
|
||||
condition: this.condition,
|
||||
gas: new BigNumber(this.gas || DEFAULT_GAS),
|
||||
gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
|
||||
});
|
||||
};
|
||||
|
||||
const result = Object.assign({}, transaction, override);
|
||||
|
||||
switch (this.conditionType) {
|
||||
case CONDITIONS.BLOCK:
|
||||
result.condition = { block: new BigNumber(this.condition.block || 0) };
|
||||
break;
|
||||
|
||||
case CONDITIONS.TIME:
|
||||
result.condition = { time: this.condition.time };
|
||||
break;
|
||||
|
||||
case CONDITIONS.NONE:
|
||||
default:
|
||||
delete result.condition;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
CONDITIONS
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/consta
|
||||
import { ERRORS } from '~/util/validation';
|
||||
|
||||
import GasPriceEditor from './gasPriceEditor';
|
||||
import { CONDITIONS } from './store';
|
||||
|
||||
const { Store } = GasPriceEditor;
|
||||
|
||||
@@ -31,18 +32,30 @@ const HISTOGRAM = {
|
||||
counts: [3, 4]
|
||||
};
|
||||
|
||||
const api = {
|
||||
eth: {
|
||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||
},
|
||||
parity: {
|
||||
gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
|
||||
}
|
||||
};
|
||||
let api;
|
||||
|
||||
describe('ui/GasPriceEditor/store', () => {
|
||||
// TODO: share with gasPriceEditor.spec.js
|
||||
function createApi () {
|
||||
api = {
|
||||
eth: {
|
||||
blockNumber: sinon.stub().resolves(new BigNumber(2)),
|
||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||
},
|
||||
parity: {
|
||||
gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
|
||||
}
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
describe('ui/GasPriceEditor/Store', () => {
|
||||
let store = null;
|
||||
|
||||
beforeEach(() => {
|
||||
createApi();
|
||||
});
|
||||
|
||||
it('is available via GasPriceEditor.Store', () => {
|
||||
expect(new Store(null, {})).to.be.ok;
|
||||
});
|
||||
@@ -65,6 +78,7 @@ describe('ui/GasPriceEditor/store', () => {
|
||||
describe('constructor (defaults) when histogram not available', () => {
|
||||
const api = {
|
||||
eth: {
|
||||
blockNumber: sinon.stub().resolves(new BigNumber(2)),
|
||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||
},
|
||||
parity: {
|
||||
@@ -92,6 +106,67 @@ describe('ui/GasPriceEditor/store', () => {
|
||||
store = new Store(null, { gasLimit: GASLIMIT });
|
||||
});
|
||||
|
||||
describe('setConditionType', () => {
|
||||
it('sets the actual type', () => {
|
||||
store.setConditionType('testingType');
|
||||
expect(store.conditionType).to.equal('testingType');
|
||||
});
|
||||
|
||||
it('clears any block error on changing type', () => {
|
||||
store.setConditionBlockNumber(-1);
|
||||
expect(store.conditionBlockError).not.to.be.null;
|
||||
store.setConditionType(CONDITIONS.BLOCK);
|
||||
expect(store.conditionBlockError).to.be.null;
|
||||
});
|
||||
|
||||
it('sets condition.block when type === CONDITIONS.BLOCK', () => {
|
||||
store.setConditionType(CONDITIONS.BLOCK);
|
||||
expect(store.condition.block).to.be.ok;
|
||||
});
|
||||
|
||||
it('clears condition when type === CONDITIONS.NONE', () => {
|
||||
store.setConditionType(CONDITIONS.BLOCK);
|
||||
store.setConditionType(CONDITIONS.NONE);
|
||||
expect(store.condition).to.deep.equal({});
|
||||
});
|
||||
|
||||
it('sets condition.time when type === CONDITIONS.TIME', () => {
|
||||
store.setConditionType(CONDITIONS.TIME);
|
||||
expect(store.condition.time).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setConditionBlockNumber', () => {
|
||||
beforeEach(() => {
|
||||
store.setConditionBlockNumber('testingBlock');
|
||||
});
|
||||
|
||||
it('sets the blockNumber', () => {
|
||||
expect(store.condition.block).to.equal('testingBlock');
|
||||
});
|
||||
|
||||
it('sets the error on invalid numbers', () => {
|
||||
expect(store.conditionBlockError).not.to.be.null;
|
||||
});
|
||||
|
||||
it('sets the error on negative numbers', () => {
|
||||
store.setConditionBlockNumber(-1);
|
||||
expect(store.conditionBlockError).not.to.be.null;
|
||||
});
|
||||
|
||||
it('clears the error on positive numbers', () => {
|
||||
store.setConditionBlockNumber(1000);
|
||||
expect(store.conditionBlockError).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setConditionDateTime', () => {
|
||||
it('sets the datatime', () => {
|
||||
store.setConditionDateTime('testingDateTime');
|
||||
expect(store.condition.time).to.equal('testingDateTime');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEditing', () => {
|
||||
it('sets the value', () => {
|
||||
expect(store.isEditing).to.be.false;
|
||||
|
||||
@@ -21,17 +21,23 @@ import CloseIcon from 'material-ui/svg-icons/navigation/close';
|
||||
import CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
|
||||
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
||||
import ContractIcon from 'material-ui/svg-icons/action/code';
|
||||
import CopyIcon from 'material-ui/svg-icons/content/content-copy';
|
||||
import DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
||||
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||
import EditIcon from 'material-ui/svg-icons/content/create';
|
||||
import FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
|
||||
import LinkIcon from 'material-ui/svg-icons/content/link';
|
||||
import LockedIcon from 'material-ui/svg-icons/action/lock';
|
||||
import MoveIcon from 'material-ui/svg-icons/action/open-with';
|
||||
import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
import SaveIcon from 'material-ui/svg-icons/content/save';
|
||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
||||
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
||||
import StarCircleIcon from 'material-ui/svg-icons/action/stars';
|
||||
import StarIcon from 'material-ui/svg-icons/toggle/star';
|
||||
import StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
|
||||
import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
||||
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
||||
@@ -44,17 +50,23 @@ export {
|
||||
CompareIcon,
|
||||
ComputerIcon,
|
||||
ContractIcon,
|
||||
CopyIcon,
|
||||
DashboardIcon,
|
||||
DeleteIcon,
|
||||
DoneIcon,
|
||||
EditIcon,
|
||||
FingerprintIcon,
|
||||
LinkIcon,
|
||||
LockedIcon,
|
||||
MoveIcon,
|
||||
NextIcon,
|
||||
PrevIcon,
|
||||
SaveIcon,
|
||||
SendIcon,
|
||||
SnoozeIcon,
|
||||
StarIcon,
|
||||
StarCircleIcon,
|
||||
StarOutlineIcon,
|
||||
VerifyIcon,
|
||||
VisibleIcon,
|
||||
VpnIcon
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { CircularProgress } from 'material-ui';
|
||||
import moment from 'moment';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import CircularProgress from 'material-ui/CircularProgress';
|
||||
|
||||
import { TypedInput, InputAddress } from '../Form';
|
||||
import MethodDecodingStore from './methodDecodingStore';
|
||||
@@ -128,15 +129,25 @@ class MethodDecoding extends Component {
|
||||
|
||||
renderMinBlock () {
|
||||
const { historic, transaction } = this.props;
|
||||
const { minBlock } = transaction;
|
||||
const { condition } = transaction;
|
||||
|
||||
if (!minBlock || minBlock.eq(0)) {
|
||||
if (!condition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ minBlock.toFormat(0) }</span></span>
|
||||
);
|
||||
if (condition.block && condition.block.gt(0)) {
|
||||
return (
|
||||
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ condition.block.toFormat(0) }</span></span>
|
||||
);
|
||||
}
|
||||
|
||||
if (condition.time) {
|
||||
return (
|
||||
<span>, { historic ? 'Submitted' : 'Submission' } at <span className={ styles.highlight }>{ moment(condition.time).format('LLLL') }</span></span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderAction () {
|
||||
|
||||
@@ -47,20 +47,6 @@
|
||||
.title {
|
||||
background: rgba(0, 0, 0, 0.25) !important;
|
||||
padding: 1em;
|
||||
margin-bottom: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
.waiting {
|
||||
margin: 1em -1em -1em -1em;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { connect } from 'react-redux';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import Container from '../Container';
|
||||
import Title from './Title';
|
||||
import Title from '../Title';
|
||||
|
||||
const ACTIONS_STYLE = { borderStyle: 'none' };
|
||||
const TITLE_STYLE = { borderStyle: 'none' };
|
||||
@@ -63,11 +63,14 @@ class Modal extends Component {
|
||||
const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed);
|
||||
const header = (
|
||||
<Title
|
||||
activeStep={ current }
|
||||
busy={ busy }
|
||||
current={ current }
|
||||
busySteps={ waiting }
|
||||
className={ styles.title }
|
||||
steps={ steps }
|
||||
title={ title }
|
||||
waiting={ waiting } />
|
||||
waiting={ waiting }
|
||||
/>
|
||||
);
|
||||
const classes = `${styles.dialog} ${className}`;
|
||||
|
||||
|
||||
@@ -26,7 +26,12 @@ class ParityBackground extends Component {
|
||||
backgroundSeed: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func
|
||||
onClick: PropTypes.func,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
style: {}
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -65,7 +70,11 @@ class ParityBackground extends Component {
|
||||
|
||||
render () {
|
||||
const { children, className, onClick } = this.props;
|
||||
const { style } = this.state;
|
||||
|
||||
const style = {
|
||||
...this.state.style,
|
||||
...this.props.style
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -15,30 +15,32 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
$left: 1.5em;
|
||||
$right: $left;
|
||||
$bottom: $left;
|
||||
$top: 20vh;
|
||||
$modalMargin: 1.5em;
|
||||
$modalPadding: 1.5em;
|
||||
$modalBackZ: 2500;
|
||||
|
||||
/* This should be the default case, the Portal used as a stand-alone modal */
|
||||
$modalBottom: $modalMargin;
|
||||
$modalLeft: $modalMargin;
|
||||
$modalRight: $modalMargin;
|
||||
$modalTop: $modalMargin;
|
||||
$modalZ: 3500;
|
||||
|
||||
/* This is the case where popped-up over another modal, Portal or otherwise */
|
||||
$popoverBottom: $modalMargin;
|
||||
$popoverLeft: $modalMargin;
|
||||
$popoverRight: $modalMargin;
|
||||
$popoverTop: 20vh;
|
||||
$popoverZ: 3600;
|
||||
|
||||
.backOverlay {
|
||||
background-color: rgba(255, 255, 255, 0.35);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
z-index: -10;
|
||||
opacity: 0;
|
||||
|
||||
transform-origin: 100% 0;
|
||||
transition-property: opacity, z-index;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: ease-out;
|
||||
|
||||
&.expanded {
|
||||
opacity: 1;
|
||||
z-index: 2500;
|
||||
}
|
||||
z-index: $modalBackZ;
|
||||
}
|
||||
|
||||
.parityBackground {
|
||||
@@ -48,57 +50,78 @@ $top: 20vh;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: 0.25;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: calc(100vw - $left - $right);
|
||||
height: calc(100vh - $top - $bottom);
|
||||
|
||||
transform-origin: 100% 0;
|
||||
transition-property: opacity, z-index;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: ease-out;
|
||||
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
opacity: 0;
|
||||
z-index: -10;
|
||||
|
||||
padding: 1em;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: $modalPadding;
|
||||
position: fixed;
|
||||
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
opacity: 1;
|
||||
z-index: 3500;
|
||||
}
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
font-size: 4em;
|
||||
z-index: 100;
|
||||
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: ease-out;
|
||||
|
||||
&, * {
|
||||
height: 48px !important;
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
&.modal {
|
||||
bottom: $modalBottom;
|
||||
left: $modalLeft;
|
||||
right: $modalRight;
|
||||
top: $modalTop;
|
||||
z-index: $modalZ;
|
||||
}
|
||||
|
||||
&.popover {
|
||||
left: $popoverLeft;
|
||||
top: $popoverTop;
|
||||
height: calc(100vh - $popoverTop - $popoverBottom);
|
||||
width: calc(100vw - $popoverLeft - $popoverRight);
|
||||
z-index: $popoverZ;
|
||||
}
|
||||
|
||||
.buttonRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
padding: $modalPadding 0 0 0;
|
||||
|
||||
button:not([disabled]) {
|
||||
color: white !important;
|
||||
|
||||
svg {
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.childContainer {
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
font-size: 4em;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 0.5rem;
|
||||
z-index: 100;
|
||||
|
||||
&, * {
|
||||
height: 48px !important;
|
||||
width: 48px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.titleRow {
|
||||
margin-bottom: $modalPadding;
|
||||
}
|
||||
}
|
||||
|
||||
121
js/src/ui/Portal/portal.example.js
Normal file
121
js/src/ui/Portal/portal.example.js
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Button } from '~/ui';
|
||||
import PlaygroundExample from '~/playground/playgroundExample';
|
||||
|
||||
import Modal from '../Modal';
|
||||
import Portal from './portal';
|
||||
|
||||
export default class PortalExample extends Component {
|
||||
state = {
|
||||
open: []
|
||||
};
|
||||
|
||||
render () {
|
||||
const { open } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PlaygroundExample name='Standard Portal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(0) }>Open</button>
|
||||
<Portal
|
||||
open={ open[0] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the first portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Popover Portal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(1) }>Open</button>
|
||||
<Portal
|
||||
isChildModal
|
||||
open={ open[1] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the second portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Portal in Modal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(2) }>Open</button>
|
||||
|
||||
<Modal
|
||||
title='Modal'
|
||||
visible={ open[2] || false }
|
||||
>
|
||||
<button onClick={ this.handleOpen(3) }>Open</button>
|
||||
<button onClick={ this.handleClose }>Close</button>
|
||||
</Modal>
|
||||
|
||||
<Portal
|
||||
isChildModal
|
||||
open={ open[3] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the second portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Portal with Buttons'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(4) }>Open</button>
|
||||
<Portal
|
||||
activeStep={ 0 }
|
||||
buttons={ [
|
||||
<Button
|
||||
key='close'
|
||||
label='close'
|
||||
onClick={ this.handleClose }
|
||||
/>
|
||||
] }
|
||||
isChildModal
|
||||
open={ open[4] || false }
|
||||
onClose={ this.handleClose }
|
||||
steps={ [ 'step 1', 'step 2' ] }
|
||||
title='Portal with button'
|
||||
>
|
||||
<p>This is the fourth portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleOpen = (index) => {
|
||||
return () => {
|
||||
const { open } = this.state;
|
||||
const nextOpen = open.slice();
|
||||
|
||||
nextOpen[index] = true;
|
||||
this.setState({ open: nextOpen });
|
||||
};
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: [] });
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,16 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import EventListener from 'react-event-listener';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactPortal from 'react-portal';
|
||||
import keycode from 'keycode';
|
||||
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
import { CloseIcon } from '~/ui/Icons';
|
||||
import ParityBackground from '~/ui/ParityBackground';
|
||||
import Title from '~/ui/Title';
|
||||
|
||||
import styles from './portal.css';
|
||||
|
||||
@@ -29,101 +32,154 @@ export default class Portal extends Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
open: PropTypes.bool.isRequired,
|
||||
|
||||
activeStep: PropTypes.number,
|
||||
busy: PropTypes.bool,
|
||||
busySteps: PropTypes.array,
|
||||
buttons: PropTypes.array,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
onKeyDown: PropTypes.func
|
||||
hideClose: PropTypes.bool,
|
||||
isChildModal: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
steps: PropTypes.array,
|
||||
title: nodeOrStringProptype()
|
||||
};
|
||||
|
||||
state = {
|
||||
expanded: false
|
||||
componentDidMount () {
|
||||
this.setBodyOverflow(this.props.open);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.open !== nextProps.open) {
|
||||
const opening = nextProps.open;
|
||||
const closing = !opening;
|
||||
|
||||
if (opening) {
|
||||
return this.setState({ expanded: true });
|
||||
}
|
||||
|
||||
if (closing) {
|
||||
return this.setState({ expanded: false });
|
||||
}
|
||||
if (nextProps.open !== this.props.open) {
|
||||
this.setBodyOverflow(nextProps.open);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.setBodyOverflow(false);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { expanded } = this.state;
|
||||
const { children, className } = this.props;
|
||||
const { activeStep, busy, busySteps, children, className, isChildModal, open, steps, title } = this.props;
|
||||
|
||||
const classes = [ styles.overlay, className ];
|
||||
const backClasses = [ styles.backOverlay ];
|
||||
|
||||
if (expanded) {
|
||||
classes.push(styles.expanded);
|
||||
backClasses.push(styles.expanded);
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactPortal isOpened onClose={ this.handleClose }>
|
||||
<div className={ backClasses.join(' ') } onClick={ this.handleClose }>
|
||||
<ReactPortal
|
||||
isOpened
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<div
|
||||
className={ styles.backOverlay }
|
||||
onClick={ this.handleClose }
|
||||
>
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
className={
|
||||
[
|
||||
styles.overlay,
|
||||
isChildModal
|
||||
? styles.popover
|
||||
: styles.modal,
|
||||
className
|
||||
].join(' ')
|
||||
}
|
||||
onClick={ this.stopEvent }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
>
|
||||
<EventListener
|
||||
target='window'
|
||||
onKeyUp={ this.handleKeyUp }
|
||||
/>
|
||||
<ParityBackground className={ styles.parityBackground } />
|
||||
|
||||
{ this.renderCloseIcon() }
|
||||
{ children }
|
||||
{ this.renderClose() }
|
||||
<Title
|
||||
activeStep={ activeStep }
|
||||
busy={ busy }
|
||||
busySteps={ busySteps }
|
||||
className={ styles.titleRow }
|
||||
steps={ steps }
|
||||
title={ title }
|
||||
/>
|
||||
<div className={ styles.childContainer }>
|
||||
{ children }
|
||||
</div>
|
||||
{ this.renderButtons() }
|
||||
</div>
|
||||
</div>
|
||||
</ReactPortal>
|
||||
);
|
||||
}
|
||||
|
||||
renderCloseIcon () {
|
||||
const { expanded } = this.state;
|
||||
renderButtons () {
|
||||
const { buttons } = this.props;
|
||||
|
||||
if (!expanded) {
|
||||
if (!buttons) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.closeIcon } onClick={ this.handleClose }>
|
||||
<CloseIcon />
|
||||
<div className={ styles.buttonRow }>
|
||||
{ buttons }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderClose () {
|
||||
const { hideClose } = this.props;
|
||||
|
||||
if (hideClose) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CloseIcon
|
||||
className={ styles.closeIcon }
|
||||
onClick={ this.handleClose }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
stopEvent = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.props.onClose();
|
||||
const { hideClose, onClose } = this.props;
|
||||
|
||||
if (!hideClose) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
const { onKeyDown } = this.props;
|
||||
|
||||
event.persist();
|
||||
|
||||
return onKeyDown
|
||||
? onKeyDown(event)
|
||||
: false;
|
||||
}
|
||||
|
||||
handleKeyUp = (event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
switch (codeName) {
|
||||
case 'esc':
|
||||
event.preventDefault();
|
||||
return this.handleClose();
|
||||
|
||||
default:
|
||||
event.persist();
|
||||
return this.props.onKeyDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
handleDOMAction = (ref, method) => {
|
||||
const refItem = typeof ref === 'string' ? this.refs[ref] : ref;
|
||||
const element = ReactDOM.findDOMNode(refItem);
|
||||
const element = ReactDOM.findDOMNode(
|
||||
typeof ref === 'string'
|
||||
? this.refs[ref]
|
||||
: ref
|
||||
);
|
||||
|
||||
if (!element || typeof element[method] !== 'function') {
|
||||
console.warn('could not find', ref, 'or method', method);
|
||||
@@ -132,4 +188,12 @@ export default class Portal extends Component {
|
||||
|
||||
return element[method]();
|
||||
}
|
||||
|
||||
setBodyOverflow (open) {
|
||||
if (!this.props.isChildModal) {
|
||||
document.body.style.overflow = open
|
||||
? 'hidden'
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
64
js/src/ui/Portal/portal.spec.js
Normal file
64
js/src/ui/Portal/portal.spec.js
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Portal from './';
|
||||
|
||||
let component;
|
||||
let onClose;
|
||||
|
||||
function render (props = {}) {
|
||||
onClose = sinon.stub();
|
||||
component = shallow(
|
||||
<Portal
|
||||
onClose={ onClose }
|
||||
open
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/Portal', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('title rendering', () => {
|
||||
const TITLE = 'some test title';
|
||||
let title;
|
||||
|
||||
beforeEach(() => {
|
||||
title = render({ title: TITLE }).find('Title');
|
||||
});
|
||||
|
||||
it('renders the specified title', () => {
|
||||
expect(title).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders the passed title', () => {
|
||||
expect(title.props().title).to.equal(TITLE);
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/ui/QrCode/index.js
Normal file
17
js/src/ui/QrCode/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 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 './qrCode';
|
||||
63
js/src/ui/QrCode/qrCode.example.js
Normal file
63
js/src/ui/QrCode/qrCode.example.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import PlaygroundExample from '~/playground/playgroundExample';
|
||||
|
||||
import QrCode from './';
|
||||
|
||||
export default class QrCodeExample extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<PlaygroundExample name='Simple QRCode'>
|
||||
<QrCode
|
||||
value='this is a test'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Simple QRCode with margin'>
|
||||
<QrCode
|
||||
margin={ 10 }
|
||||
value='this is a test'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Ethereum Address QRCode'>
|
||||
<QrCode
|
||||
margin={ 10 }
|
||||
value='0x8c30393085C8C3fb4C1fB16165d9fBac5D86E1D9'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Bitcoin Address QRCode'>
|
||||
<QrCode
|
||||
margin={ 10 }
|
||||
value='3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Big QRCode'>
|
||||
<QrCode
|
||||
size={ 10 }
|
||||
value='this is a test'
|
||||
/>
|
||||
</PlaygroundExample>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
83
js/src/ui/QrCode/qrCode.js
Normal file
83
js/src/ui/QrCode/qrCode.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
// https://github.com/cmanzana/qrcode-npm packaging the standard
|
||||
// https://github.com/kazuhikoarase/qrcode-generator
|
||||
import { qrcode } from 'qrcode-npm';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
const QROPTS = {
|
||||
CODE_TYPE: 4,
|
||||
ERROR_LEVEL: 'M'
|
||||
};
|
||||
|
||||
export default class QrCode extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
margin: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
value: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
margin: 2,
|
||||
size: 4
|
||||
};
|
||||
|
||||
state = {
|
||||
image: null
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.generateCode(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const hasChanged = nextProps.value !== this.props.value ||
|
||||
nextProps.size !== this.props.size ||
|
||||
nextProps.margin !== this.props.margin;
|
||||
|
||||
if (hasChanged) {
|
||||
this.generateCode(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className } = this.props;
|
||||
const { image } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ className }
|
||||
dangerouslySetInnerHTML={ {
|
||||
__html: image
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
generateCode (props) {
|
||||
const { margin, size, value } = props;
|
||||
const qr = qrcode(QROPTS.CODE_TYPE, QROPTS.ERROR_LEVEL);
|
||||
|
||||
qr.addData(value);
|
||||
qr.make();
|
||||
|
||||
this.setState({
|
||||
image: qr.createImgTag(size, margin)
|
||||
});
|
||||
}
|
||||
}
|
||||
108
js/src/ui/QrCode/qrCode.spec.js
Normal file
108
js/src/ui/QrCode/qrCode.spec.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import QrCode from './';
|
||||
|
||||
const DEFAULT_PROPS = {
|
||||
margin: 1,
|
||||
size: 4,
|
||||
value: 'someTestValue'
|
||||
};
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<QrCode
|
||||
{ ...DEFAULT_PROPS }
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/QrCode', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.spy(instance, 'generateCode');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.generateCode.restore();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('lifecycle', () => {
|
||||
describe('componentWillMount', () => {
|
||||
it('generates the image on mount', () => {
|
||||
instance.componentWillMount();
|
||||
expect(instance.generateCode).to.have.been.calledWith(DEFAULT_PROPS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentWillReceiveProps', () => {
|
||||
it('does not re-generate when no props changed', () => {
|
||||
instance.componentWillReceiveProps(DEFAULT_PROPS);
|
||||
expect(instance.generateCode).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('does not re-generate when className changed', () => {
|
||||
const nextProps = Object.assign({}, DEFAULT_PROPS, { className: 'test' });
|
||||
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
expect(instance.generateCode).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('does not re-generate when additional property changed', () => {
|
||||
const nextProps = Object.assign({}, DEFAULT_PROPS, { something: 'test' });
|
||||
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
expect(instance.generateCode).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('does re-generate when value changed', () => {
|
||||
const nextProps = Object.assign({}, DEFAULT_PROPS, { value: 'somethingElse' });
|
||||
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
expect(instance.generateCode).to.have.been.calledWith(nextProps);
|
||||
});
|
||||
|
||||
it('does re-generate when size changed', () => {
|
||||
const nextProps = Object.assign({}, DEFAULT_PROPS, { size: 10 });
|
||||
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
expect(instance.generateCode).to.have.been.calledWith(nextProps);
|
||||
});
|
||||
|
||||
it('does re-generate when margin changed', () => {
|
||||
const nextProps = Object.assign({}, DEFAULT_PROPS, { margin: 10 });
|
||||
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
expect(instance.generateCode).to.have.been.calledWith(nextProps);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
17
js/src/ui/SectionList/index.js
Normal file
17
js/src/ui/SectionList/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 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 './sectionList';
|
||||
84
js/src/ui/SectionList/sectionList.css
Normal file
84
js/src/ui/SectionList/sectionList.css
Normal file
@@ -0,0 +1,84 @@
|
||||
/* Copyright 2015, 2016 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/>.
|
||||
*/
|
||||
|
||||
.section {
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 1.5em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 199;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow-x: hidden;
|
||||
|
||||
/* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */
|
||||
/* case where <> 3 columns are required should the need arrise from a UI pov. */
|
||||
.item {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 0 1 33.33%;
|
||||
opacity: 0.75;
|
||||
overflow-x: hidden;
|
||||
padding: 0.25em;
|
||||
transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
|
||||
/* TODO: The hover and no-hover states can be improved to not "just appear" */
|
||||
&:not(:hover) {
|
||||
& [data-hover="hide"] {
|
||||
}
|
||||
|
||||
& [data-hover="show"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
z-index: 100;
|
||||
|
||||
& [data-hover="hide"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& [data-hover="show"] {
|
||||
}
|
||||
}
|
||||
|
||||
&.stretch-on:hover {
|
||||
flex: 0 0 50%;
|
||||
}
|
||||
|
||||
&.stretch-off:hover {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section+.section {
|
||||
margin-top: 1em;
|
||||
}
|
||||
94
js/src/ui/SectionList/sectionList.example.js
Normal file
94
js/src/ui/SectionList/sectionList.example.js
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import PlaygroundExample from '~/playground/playgroundExample';
|
||||
import SectionList from './';
|
||||
|
||||
const ITEM_STYLE = {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||
padding: '1em'
|
||||
};
|
||||
|
||||
const items = [
|
||||
{ name: 'Jack', desc: 'Item number 1' },
|
||||
{ name: 'Paul', desc: 'Item number 2' },
|
||||
{ name: 'Matt', desc: 'Item number 3' },
|
||||
{ name: 'Titi', desc: 'Item number 4' }
|
||||
];
|
||||
|
||||
export default class SectionListExample extends Component {
|
||||
state = {
|
||||
showOverlay: true
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<PlaygroundExample name='Simple Usage'>
|
||||
{ this.renderSimple() }
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='With Overlay'>
|
||||
{ this.renderWithOverlay() }
|
||||
</PlaygroundExample>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSimple () {
|
||||
return (
|
||||
<SectionList
|
||||
items={ items }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderWithOverlay () {
|
||||
const { showOverlay } = this.state;
|
||||
const overlay = (
|
||||
<div>
|
||||
<p>Overlay</p>
|
||||
<button onClick={ this.hideOverlay }>hide</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
items={ items }
|
||||
overlay={ showOverlay ? overlay : null }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem (item, index) {
|
||||
const { desc, name } = item;
|
||||
|
||||
return (
|
||||
<div style={ ITEM_STYLE }>
|
||||
<h3>{ name }</h3>
|
||||
<h3 data-hover='show'>{ desc }</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
hideOverlay = () => {
|
||||
this.setState({ showOverlay: false });
|
||||
}
|
||||
}
|
||||
103
js/src/ui/SectionList/sectionList.js
Normal file
103
js/src/ui/SectionList/sectionList.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { chunkArray } from '~/util/array';
|
||||
import { arrayOrObjectProptype, nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from './sectionList.css';
|
||||
|
||||
// TODO: We probably want this to be passed via props - additional work required in that case to
|
||||
// support the styling for both the hover and no-hover CSS for the pre/post sizes. Future work only
|
||||
// if/when required.
|
||||
const ITEMS_PER_ROW = 3;
|
||||
|
||||
export default class SectionList extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
items: arrayOrObjectProptype().isRequired,
|
||||
renderItem: PropTypes.func.isRequired,
|
||||
noStretch: PropTypes.bool,
|
||||
overlay: nodeOrStringProptype()
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
noStretch: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className, items } = this.props;
|
||||
|
||||
if (!items || !items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={ [styles.section, className].join(' ') }>
|
||||
{ this.renderOverlay() }
|
||||
{ chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) }
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
renderOverlay () {
|
||||
const { overlay } = this.props;
|
||||
|
||||
if (!overlay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.overlay }>
|
||||
{ overlay }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderRow = (row, index) => {
|
||||
return (
|
||||
<div
|
||||
className={ styles.row }
|
||||
key={ `row_${index}` }
|
||||
>
|
||||
{ row.map(this.renderItem) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderItem = (item, index) => {
|
||||
const { noStretch, renderItem } = this.props;
|
||||
|
||||
// NOTE: Any children that is to be showed or hidden (depending on hover state)
|
||||
// should have the data-hover="show|hide" attributes. For the current implementation
|
||||
// this does the trick, however there may be a case for adding a hover attribute
|
||||
// to an item (mouseEnter/mouseLeave events) and then adjusting the styling with
|
||||
// :root[hover]/:root:not[hover] for the tragetted elements. Currently it is a
|
||||
// CSS-only solution to let the browser do all the work via selectors.
|
||||
return (
|
||||
<div
|
||||
className={ [
|
||||
styles.item,
|
||||
styles[`stretch-${noStretch ? 'off' : 'on'}`]
|
||||
].join(' ') }
|
||||
key={ `item_${index}` }
|
||||
>
|
||||
{ renderItem(item, index) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
103
js/src/ui/SectionList/sectionList.spec.js
Normal file
103
js/src/ui/SectionList/sectionList.spec.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2015, 2016 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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import SectionList from './';
|
||||
|
||||
const ITEMS = ['itemA', 'itemB', 'itemC', 'itemD', 'itemE'];
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let renderItem;
|
||||
|
||||
function render (props = {}) {
|
||||
renderItem = sinon.stub();
|
||||
component = shallow(
|
||||
<SectionList
|
||||
className='testClass'
|
||||
items={ ITEMS }
|
||||
renderItem={ renderItem }
|
||||
section='testSection'
|
||||
/>
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('SectionList', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('adds className as specified', () => {
|
||||
expect(component.hasClass('testClass')).to.be.true;
|
||||
});
|
||||
|
||||
describe('instance methods', () => {
|
||||
describe('renderRow', () => {
|
||||
let row;
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(instance, 'renderItem');
|
||||
row = instance.renderRow(['testA', 'testB']);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.renderItem.restore();
|
||||
});
|
||||
|
||||
it('renders a row', () => {
|
||||
expect(row).to.be.ok;
|
||||
});
|
||||
|
||||
it('adds a key for the row', () => {
|
||||
expect(row.key).to.be.ok;
|
||||
});
|
||||
|
||||
it('calls renderItem for the items', () => {
|
||||
expect(instance.renderItem).to.have.been.calledTwice;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderItem', () => {
|
||||
let item;
|
||||
|
||||
beforeEach(() => {
|
||||
item = instance.renderItem('testItem', 50);
|
||||
});
|
||||
|
||||
it('renders an item', () => {
|
||||
expect(item).to.be.ok;
|
||||
});
|
||||
|
||||
it('adds a key for the item', () => {
|
||||
expect(item.key).to.be.ok;
|
||||
});
|
||||
|
||||
it('calls the external renderer', () => {
|
||||
expect(renderItem).to.have.been.calledWith('testItem', 50);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,14 +28,21 @@ export default class Tags extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
return (<div className={ styles.tags }>
|
||||
{ this.renderTags() }
|
||||
</div>);
|
||||
const { tags } = this.props;
|
||||
|
||||
if (!tags || tags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.tags }>
|
||||
{ this.renderTags() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTags () {
|
||||
const { handleAddSearchToken, setRefs } = this.props;
|
||||
const tags = this.props.tags || [];
|
||||
const { handleAddSearchToken, setRefs, tags } = this.props;
|
||||
|
||||
const tagClasses = handleAddSearchToken
|
||||
? [ styles.tag, styles.tagClickable ]
|
||||
@@ -47,14 +54,14 @@ export default class Tags extends Component {
|
||||
|
||||
return tags
|
||||
.sort()
|
||||
.map((tag, idx) => {
|
||||
.map((tag, index) => {
|
||||
const onClick = handleAddSearchToken
|
||||
? () => handleAddSearchToken(tag)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={ idx }
|
||||
key={ `tag_${index}` }
|
||||
className={ tagClasses.join(' ') }
|
||||
onClick={ onClick }
|
||||
ref={ setRef }
|
||||
|
||||
26
js/src/ui/Title/title.css
Normal file
26
js/src/ui/Title/title.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
.title {
|
||||
.steps {
|
||||
margin: -0.5em 0 -1em 0;
|
||||
}
|
||||
|
||||
.waiting {
|
||||
margin: 1em -1em -1em -1em;
|
||||
}
|
||||
}
|
||||
@@ -14,35 +14,49 @@
|
||||
// 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 { LinearProgress } from 'material-ui';
|
||||
import { Step, Stepper, StepLabel } from 'material-ui/Stepper';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
// TODO: It would make sense (going forward) to replace all uses of
|
||||
// ContainerTitle with this component. In that case the styles for the
|
||||
// h3 (title) can be pulled from there. (As it stands the duplication
|
||||
// between the 2 has been removed, but as a short-term DRY only)
|
||||
import { Title as ContainerTitle } from '~/ui/Container';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from '../modal.css';
|
||||
import styles from './title.css';
|
||||
|
||||
export default class Title extends Component {
|
||||
static propTypes = {
|
||||
activeStep: PropTypes.number,
|
||||
busy: PropTypes.bool,
|
||||
current: PropTypes.number,
|
||||
busySteps: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
steps: PropTypes.array,
|
||||
waiting: PropTypes.array,
|
||||
title: nodeOrStringProptype()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { current, steps, title } = this.props;
|
||||
const { activeStep, className, steps, title } = this.props;
|
||||
|
||||
if (!title && !steps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.title }>
|
||||
<h3>
|
||||
{
|
||||
<div
|
||||
className={
|
||||
[styles.title, className].join(' ')
|
||||
}
|
||||
>
|
||||
<ContainerTitle
|
||||
title={
|
||||
steps
|
||||
? steps[current]
|
||||
? steps[activeStep || 0]
|
||||
: title
|
||||
}
|
||||
</h3>
|
||||
/>
|
||||
{ this.renderSteps() }
|
||||
{ this.renderWaiting() }
|
||||
</div>
|
||||
@@ -50,7 +64,7 @@ export default class Title extends Component {
|
||||
}
|
||||
|
||||
renderSteps () {
|
||||
const { current, steps } = this.props;
|
||||
const { activeStep, steps } = this.props;
|
||||
|
||||
if (!steps) {
|
||||
return;
|
||||
@@ -58,8 +72,7 @@ export default class Title extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.steps }>
|
||||
<Stepper
|
||||
activeStep={ current }>
|
||||
<Stepper activeStep={ activeStep }>
|
||||
{ this.renderTimeline() }
|
||||
</Stepper>
|
||||
</div>
|
||||
@@ -82,8 +95,8 @@ export default class Title extends Component {
|
||||
}
|
||||
|
||||
renderWaiting () {
|
||||
const { current, busy, waiting } = this.props;
|
||||
const isWaiting = busy || (waiting || []).includes(current);
|
||||
const { activeStep, busy, busySteps } = this.props;
|
||||
const isWaiting = busy || (busySteps || []).includes(activeStep);
|
||||
|
||||
if (!isWaiting) {
|
||||
return null;
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import AccountCard from './AccountCard';
|
||||
import Actionbar from './Actionbar';
|
||||
import ActionbarExport from './Actionbar/Export';
|
||||
import ActionbarImport from './Actionbar/Import';
|
||||
@@ -28,9 +29,12 @@ import ConfirmDialog from './ConfirmDialog';
|
||||
import Container, { Title as ContainerTitle } from './Container';
|
||||
import ContextProvider from './ContextProvider';
|
||||
import CopyToClipboard from './CopyToClipboard';
|
||||
import CurrencySymbol from './CurrencySymbol';
|
||||
import DappCard from './DappCard';
|
||||
import DappIcon from './DappIcon';
|
||||
import Editor from './Editor';
|
||||
import Errors from './Errors';
|
||||
import Form, { AddressSelect, FormWrap, TypedInput, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select, RadioButtons } from './Form';
|
||||
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
|
||||
import GasPriceEditor from './GasPriceEditor';
|
||||
import GasPriceSelector from './GasPriceSelector';
|
||||
import Icons from './Icons';
|
||||
@@ -44,15 +48,20 @@ import muiTheme from './Theme';
|
||||
import Page from './Page';
|
||||
import ParityBackground from './ParityBackground';
|
||||
import PasswordStrength from './Form/PasswordStrength';
|
||||
import Portal from './Portal';
|
||||
import QrCode from './QrCode';
|
||||
import SectionList from './SectionList';
|
||||
import ShortenedHash from './ShortenedHash';
|
||||
import SignerIcon from './SignerIcon';
|
||||
import Tags from './Tags';
|
||||
import Title from './Title';
|
||||
import Tooltips, { Tooltip } from './Tooltips';
|
||||
import TxHash from './TxHash';
|
||||
import TxList from './TxList';
|
||||
import Warning from './Warning';
|
||||
|
||||
export {
|
||||
AccountCard,
|
||||
Actionbar,
|
||||
ActionbarExport,
|
||||
ActionbarImport,
|
||||
@@ -69,6 +78,9 @@ export {
|
||||
ContainerTitle,
|
||||
ContextProvider,
|
||||
CopyToClipboard,
|
||||
CurrencySymbol,
|
||||
DappIcon,
|
||||
DappCard,
|
||||
Editor,
|
||||
Errors,
|
||||
Form,
|
||||
@@ -80,9 +92,12 @@ export {
|
||||
InputAddress,
|
||||
InputAddressSelect,
|
||||
InputChip,
|
||||
InputDate,
|
||||
InputInline,
|
||||
InputTime,
|
||||
IdentityIcon,
|
||||
IdentityName,
|
||||
Label,
|
||||
LanguageSelector,
|
||||
Loading,
|
||||
MethodDecoding,
|
||||
@@ -93,11 +108,15 @@ export {
|
||||
Page,
|
||||
ParityBackground,
|
||||
PasswordStrength,
|
||||
Portal,
|
||||
QrCode,
|
||||
RadioButtons,
|
||||
ShortenedHash,
|
||||
Select,
|
||||
ShortenedHash,
|
||||
SectionList,
|
||||
SignerIcon,
|
||||
Tags,
|
||||
Title,
|
||||
Tooltip,
|
||||
Tooltips,
|
||||
TxHash,
|
||||
|
||||
Reference in New Issue
Block a user