[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:
parent
f76b94c2c5
commit
fb817fcdca
@ -163,6 +163,7 @@
|
|||||||
"phoneformat.js": "1.0.3",
|
"phoneformat.js": "1.0.3",
|
||||||
"promise-worker": "1.1.1",
|
"promise-worker": "1.1.1",
|
||||||
"push.js": "0.0.11",
|
"push.js": "0.0.11",
|
||||||
|
"qrcode-npm": "0.0.3",
|
||||||
"qs": "6.3.0",
|
"qs": "6.3.0",
|
||||||
"react": "15.4.1",
|
"react": "15.4.1",
|
||||||
"react-ace": "4.1.0",
|
"react-ace": "4.1.0",
|
||||||
@ -170,6 +171,8 @@
|
|||||||
"react-copy-to-clipboard": "4.2.3",
|
"react-copy-to-clipboard": "4.2.3",
|
||||||
"react-dom": "15.4.1",
|
"react-dom": "15.4.1",
|
||||||
"react-dropzone": "3.7.3",
|
"react-dropzone": "3.7.3",
|
||||||
|
"react-element-to-jsx-string": "6.0.0",
|
||||||
|
"react-event-listener": "0.4.1",
|
||||||
"react-intl": "2.1.5",
|
"react-intl": "2.1.5",
|
||||||
"react-portal": "3.0.0",
|
"react-portal": "3.0.0",
|
||||||
"react-redux": "4.4.6",
|
"react-redux": "4.4.6",
|
||||||
@ -185,6 +188,7 @@
|
|||||||
"scryptsy": "2.0.0",
|
"scryptsy": "2.0.0",
|
||||||
"solc": "ngotchac/solc-js",
|
"solc": "ngotchac/solc-js",
|
||||||
"store": "1.3.20",
|
"store": "1.3.20",
|
||||||
|
"useragent.js": "0.5.6",
|
||||||
"utf8": "2.1.2",
|
"utf8": "2.1.2",
|
||||||
"valid-url": "1.0.9",
|
"valid-url": "1.0.9",
|
||||||
"validator": "6.2.0",
|
"validator": "6.2.0",
|
||||||
|
@ -127,6 +127,18 @@ export function inNumber16 (number) {
|
|||||||
return inHex(bn.toString(16));
|
return inHex(bn.toString(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function inOptionsCondition (condition) {
|
||||||
|
if (condition) {
|
||||||
|
if (condition.block) {
|
||||||
|
condition.block = condition.block ? inNumber10(condition.block) : null;
|
||||||
|
} else if (condition.time) {
|
||||||
|
condition.time = inNumber10(Math.floor(condition.time.getTime() / 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
export function inOptions (options) {
|
export function inOptions (options) {
|
||||||
if (options) {
|
if (options) {
|
||||||
Object.keys(options).forEach((key) => {
|
Object.keys(options).forEach((key) => {
|
||||||
@ -136,6 +148,10 @@ export function inOptions (options) {
|
|||||||
options[key] = inAddress(options[key]);
|
options[key] = inAddress(options[key]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'condition':
|
||||||
|
options[key] = inOptionsCondition(options[key]);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'gas':
|
case 'gas':
|
||||||
case 'gasPrice':
|
case 'gasPrice':
|
||||||
options[key] = inNumber16((new BigNumber(options[key])).round());
|
options[key] = inNumber16((new BigNumber(options[key])).round());
|
||||||
|
@ -200,6 +200,18 @@ export function outSyncing (syncing) {
|
|||||||
return syncing;
|
return syncing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function outTransactionCondition (condition) {
|
||||||
|
if (condition) {
|
||||||
|
if (condition.block) {
|
||||||
|
condition.block = outNumber(condition.block);
|
||||||
|
} else if (condition.time) {
|
||||||
|
condition.time = outDate(condition.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
export function outTransaction (tx) {
|
export function outTransaction (tx) {
|
||||||
if (tx) {
|
if (tx) {
|
||||||
Object.keys(tx).forEach((key) => {
|
Object.keys(tx).forEach((key) => {
|
||||||
@ -213,8 +225,14 @@ export function outTransaction (tx) {
|
|||||||
tx[key] = outNumber(tx[key]);
|
tx[key] = outNumber(tx[key]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'condition':
|
||||||
|
tx[key] = outTransactionCondition(tx[key]);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'minBlock':
|
case 'minBlock':
|
||||||
tx[key] = tx[key] ? outNumber(tx[key]) : null;
|
tx[key] = tx[key]
|
||||||
|
? outNumber(tx[key])
|
||||||
|
: null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'creates':
|
case 'creates':
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions } from '../../format/input';
|
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
|
||||||
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output';
|
import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output';
|
||||||
|
|
||||||
export default class Parity {
|
export default class Parity {
|
||||||
@ -76,6 +76,17 @@ export default class Parity {
|
|||||||
.execute('parity_dappsInterface');
|
.execute('parity_dappsInterface');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decryptMessage (address, data) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_decryptMessage', inAddress(address), inHex(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultAccount () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_defaultAccount')
|
||||||
|
.then(outAddress);
|
||||||
|
}
|
||||||
|
|
||||||
defaultExtraData () {
|
defaultExtraData () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_defaultExtraData');
|
.execute('parity_defaultExtraData');
|
||||||
@ -101,6 +112,11 @@ export default class Parity {
|
|||||||
.execute('parity_enode');
|
.execute('parity_enode');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encryptMessage (pubkey, data) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_encryptMessage', inHex(pubkey), inHex(data));
|
||||||
|
}
|
||||||
|
|
||||||
executeUpgrade () {
|
executeUpgrade () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_executeUpgrade');
|
.execute('parity_executeUpgrade');
|
||||||
@ -111,6 +127,17 @@ export default class Parity {
|
|||||||
.execute('parity_extraData');
|
.execute('parity_extraData');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
futureTransactions () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_futureTransactions');
|
||||||
|
}
|
||||||
|
|
||||||
|
gasCeilTarget () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_gasCeilTarget')
|
||||||
|
.then(outNumber);
|
||||||
|
}
|
||||||
|
|
||||||
gasFloorTarget () {
|
gasFloorTarget () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_gasFloorTarget')
|
.execute('parity_gasFloorTarget')
|
||||||
@ -156,11 +183,22 @@ export default class Parity {
|
|||||||
.execute('parity_killAccount', inAddress(account), password);
|
.execute('parity_killAccount', inAddress(account), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listAccounts (count, offset = null, blockNumber = 'latest') {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_listAccounts', count, inAddress(offset), inBlockNumber(blockNumber))
|
||||||
|
.then((accounts) => (accounts || []).map(outAddress));
|
||||||
|
}
|
||||||
|
|
||||||
listRecentDapps () {
|
listRecentDapps () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_listRecentDapps');
|
.execute('parity_listRecentDapps');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listStorageKeys (address, count, hash = null, blockNumber = 'latest') {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_listStorageKeys', inAddress(address), count, inHex(hash), inBlockNumber(blockNumber));
|
||||||
|
}
|
||||||
|
|
||||||
removeAddress (address) {
|
removeAddress (address) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_removeAddress', inAddress(address));
|
.execute('parity_removeAddress', inAddress(address));
|
||||||
@ -265,6 +303,11 @@ export default class Parity {
|
|||||||
.then(outAddress);
|
.then(outAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postSign (address, hash) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_postSign', inAddress(address), inHex(hash));
|
||||||
|
}
|
||||||
|
|
||||||
postTransaction (options) {
|
postTransaction (options) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_postTransaction', inOptions(options));
|
.execute('parity_postTransaction', inOptions(options));
|
||||||
@ -311,16 +354,31 @@ export default class Parity {
|
|||||||
.execute('parity_setDappsAddresses', dappId, inAddresses(addresses));
|
.execute('parity_setDappsAddresses', dappId, inAddresses(addresses));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEngineSigner (address, password) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setEngineSigner', inAddress(address), password);
|
||||||
|
}
|
||||||
|
|
||||||
setExtraData (data) {
|
setExtraData (data) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_setExtraData', inData(data));
|
.execute('parity_setExtraData', inData(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGasCeilTarget (quantity) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setGasCeilTarget', inNumber16(quantity));
|
||||||
|
}
|
||||||
|
|
||||||
setGasFloorTarget (quantity) {
|
setGasFloorTarget (quantity) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_setGasFloorTarget', inNumber16(quantity));
|
.execute('parity_setGasFloorTarget', inNumber16(quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMaxTransactionGas (quantity) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setMaxTransactionGas', inNumber16(quantity));
|
||||||
|
}
|
||||||
|
|
||||||
setMinGasPrice (quantity) {
|
setMinGasPrice (quantity) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_setMinGasPrice', inNumber16(quantity));
|
.execute('parity_setMinGasPrice', inNumber16(quantity));
|
||||||
|
@ -23,6 +23,7 @@ export default class Eth {
|
|||||||
this._started = false;
|
this._started = false;
|
||||||
|
|
||||||
this._lastBlock = new BigNumber(-1);
|
this._lastBlock = new BigNumber(-1);
|
||||||
|
this._pollTimerId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isStarted () {
|
get isStarted () {
|
||||||
@ -37,7 +38,7 @@ export default class Eth {
|
|||||||
|
|
||||||
_blockNumber = () => {
|
_blockNumber = () => {
|
||||||
const nextTimeout = (timeout = 1000) => {
|
const nextTimeout = (timeout = 1000) => {
|
||||||
setTimeout(() => {
|
this._pollTimerId = setTimeout(() => {
|
||||||
this._blockNumber();
|
this._blockNumber();
|
||||||
}, timeout);
|
}, timeout);
|
||||||
};
|
};
|
||||||
@ -57,6 +58,6 @@ export default class Eth {
|
|||||||
|
|
||||||
nextTimeout();
|
nextTimeout();
|
||||||
})
|
})
|
||||||
.catch(nextTimeout);
|
.catch(() => nextTimeout());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ const events = {
|
|||||||
'logging': { module: 'logging' },
|
'logging': { module: 'logging' },
|
||||||
'eth_blockNumber': { module: 'eth' },
|
'eth_blockNumber': { module: 'eth' },
|
||||||
'parity_allAccountsInfo': { module: 'personal' },
|
'parity_allAccountsInfo': { module: 'personal' },
|
||||||
|
'parity_defaultAccount': { module: 'personal' },
|
||||||
'eth_accounts': { module: 'personal' },
|
'eth_accounts': { module: 'personal' },
|
||||||
'signer_requestsToConfirm': { module: 'signer' }
|
'signer_requestsToConfirm': { module: 'signer' }
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -20,6 +20,9 @@ export default class Personal {
|
|||||||
this._api = api;
|
this._api = api;
|
||||||
this._updateSubscriptions = updateSubscriptions;
|
this._updateSubscriptions = updateSubscriptions;
|
||||||
this._started = false;
|
this._started = false;
|
||||||
|
|
||||||
|
this._lastDefaultAccount = '0x0';
|
||||||
|
this._pollTimerId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isStarted () {
|
get isStarted () {
|
||||||
@ -30,12 +33,44 @@ export default class Personal {
|
|||||||
this._started = true;
|
this._started = true;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
|
this._defaultAccount(),
|
||||||
this._listAccounts(),
|
this._listAccounts(),
|
||||||
this._accountsInfo(),
|
this._accountsInfo(),
|
||||||
this._loggingSubscribe()
|
this._loggingSubscribe()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Because of the different API instances, the "wait for valid changes" approach
|
||||||
|
// doesn't work. Since the defaultAccount is critical to operation, we poll in exactly
|
||||||
|
// same way we do in ../eth (ala same as eth_blockNumber) and update. This should be moved
|
||||||
|
// to pub-sub as it becomes available
|
||||||
|
_defaultAccount = (timerDisabled = false) => {
|
||||||
|
const nextTimeout = (timeout = 1000) => {
|
||||||
|
if (!timerDisabled) {
|
||||||
|
this._pollTimerId = setTimeout(() => {
|
||||||
|
this._defaultAccount();
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this._api.transport.isConnected) {
|
||||||
|
nextTimeout(500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.defaultAccount()
|
||||||
|
.then((defaultAccount) => {
|
||||||
|
if (this._lastDefaultAccount !== defaultAccount) {
|
||||||
|
this._lastDefaultAccount = defaultAccount;
|
||||||
|
this._updateSubscriptions('parity_defaultAccount', null, defaultAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTimeout();
|
||||||
|
})
|
||||||
|
.catch(() => nextTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
_listAccounts = () => {
|
_listAccounts = () => {
|
||||||
return this._api.eth
|
return this._api.eth
|
||||||
.accounts()
|
.accounts()
|
||||||
@ -46,9 +81,19 @@ export default class Personal {
|
|||||||
|
|
||||||
_accountsInfo = () => {
|
_accountsInfo = () => {
|
||||||
return this._api.parity
|
return this._api.parity
|
||||||
.allAccountsInfo()
|
.accountsInfo()
|
||||||
.then((info) => {
|
.then((info) => {
|
||||||
this._updateSubscriptions('parity_allAccountsInfo', null, info);
|
this._updateSubscriptions('parity_accountsInfo', null, info);
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.allAccountsInfo()
|
||||||
|
.catch(() => {
|
||||||
|
// NOTE: This fails on non-secure APIs, swallow error
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
.then((allInfo) => {
|
||||||
|
this._updateSubscriptions('parity_allAccountsInfo', null, allInfo);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +118,11 @@ export default class Personal {
|
|||||||
case 'parity_setAccountMeta':
|
case 'parity_setAccountMeta':
|
||||||
this._accountsInfo();
|
this._accountsInfo();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'parity_setDappsAddresses':
|
||||||
|
case 'parity_setNewDappsWhitelist':
|
||||||
|
this._defaultAccount(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -18,31 +18,51 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import Personal from './personal';
|
import Personal from './personal';
|
||||||
|
|
||||||
|
const TEST_DEFAULT = '0xfa64203C044691aA57251aF95f4b48d85eC00Dd5';
|
||||||
const TEST_INFO = {
|
const TEST_INFO = {
|
||||||
'0xfa64203C044691aA57251aF95f4b48d85eC00Dd5': {
|
[TEST_DEFAULT]: {
|
||||||
name: 'test'
|
name: 'test'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const TEST_LIST = ['0xfa64203C044691aA57251aF95f4b48d85eC00Dd5'];
|
const TEST_LIST = [TEST_DEFAULT];
|
||||||
|
|
||||||
function stubApi (accounts, info) {
|
function stubApi (accounts, info) {
|
||||||
const _calls = {
|
const _calls = {
|
||||||
|
accountsInfo: [],
|
||||||
allAccountsInfo: [],
|
allAccountsInfo: [],
|
||||||
listAccounts: []
|
listAccounts: [],
|
||||||
|
defaultAccount: []
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_calls,
|
_calls,
|
||||||
|
transport: {
|
||||||
|
isConnected: true
|
||||||
|
},
|
||||||
parity: {
|
parity: {
|
||||||
|
accountsInfo: () => {
|
||||||
|
const stub = sinon.stub().resolves(info || TEST_INFO)();
|
||||||
|
|
||||||
|
_calls.accountsInfo.push(stub);
|
||||||
|
return stub;
|
||||||
|
},
|
||||||
allAccountsInfo: () => {
|
allAccountsInfo: () => {
|
||||||
const stub = sinon.stub().resolves(info || TEST_INFO)();
|
const stub = sinon.stub().resolves(info || TEST_INFO)();
|
||||||
|
|
||||||
_calls.allAccountsInfo.push(stub);
|
_calls.allAccountsInfo.push(stub);
|
||||||
return stub;
|
return stub;
|
||||||
|
},
|
||||||
|
defaultAccount: () => {
|
||||||
|
const stub = sinon.stub().resolves(Object.keys(info || TEST_INFO)[0])();
|
||||||
|
|
||||||
|
_calls.defaultAccount.push(stub);
|
||||||
|
return stub;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
eth: {
|
eth: {
|
||||||
accounts: () => {
|
accounts: () => {
|
||||||
const stub = sinon.stub().resolves(accounts || TEST_LIST)();
|
const stub = sinon.stub().resolves(accounts || TEST_LIST)();
|
||||||
|
|
||||||
_calls.listAccounts.push(stub);
|
_calls.listAccounts.push(stub);
|
||||||
return stub;
|
return stub;
|
||||||
}
|
}
|
||||||
@ -85,6 +105,10 @@ describe('api/subscriptions/personal', () => {
|
|||||||
expect(personal.isStarted).to.be.true;
|
expect(personal.isStarted).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calls parity_accountsInfo', () => {
|
||||||
|
expect(api._calls.accountsInfo.length).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
it('calls parity_allAccountsInfo', () => {
|
it('calls parity_allAccountsInfo', () => {
|
||||||
expect(api._calls.allAccountsInfo.length).to.be.ok;
|
expect(api._calls.allAccountsInfo.length).to.be.ok;
|
||||||
});
|
});
|
||||||
@ -94,8 +118,10 @@ describe('api/subscriptions/personal', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates subscribers', () => {
|
it('updates subscribers', () => {
|
||||||
expect(cb.firstCall).to.have.been.calledWith('eth_accounts', null, TEST_LIST);
|
expect(cb).to.have.been.calledWith('parity_defaultAccount', null, TEST_DEFAULT);
|
||||||
expect(cb.secondCall).to.have.been.calledWith('parity_allAccountsInfo', null, TEST_INFO);
|
expect(cb).to.have.been.calledWith('eth_accounts', null, TEST_LIST);
|
||||||
|
expect(cb).to.have.been.calledWith('parity_accountsInfo', null, TEST_INFO);
|
||||||
|
expect(cb).to.have.been.calledWith('parity_allAccountsInfo', null, TEST_INFO);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -110,7 +136,15 @@ describe('api/subscriptions/personal', () => {
|
|||||||
expect(personal.isStarted).to.be.true;
|
expect(personal.isStarted).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calls parity_defaultAccount', () => {
|
||||||
|
expect(api._calls.defaultAccount.length).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
it('calls personal_accountsInfo', () => {
|
it('calls personal_accountsInfo', () => {
|
||||||
|
expect(api._calls.accountsInfo.length).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls personal_allAccountsInfo', () => {
|
||||||
expect(api._calls.allAccountsInfo.length).to.be.ok;
|
expect(api._calls.allAccountsInfo.length).to.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.attachInstance();
|
return this.attachInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -80,12 +80,12 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachInstance () {
|
attachInstance () {
|
||||||
Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
attachInstances(),
|
api.parity.accountsInfo(),
|
||||||
api.parity.accountsInfo()
|
attachInstances()
|
||||||
])
|
])
|
||||||
.then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => {
|
.then(([accountsInfo, { managerInstance, registryInstance, tokenregInstance }]) => {
|
||||||
accountsInfo = accountsInfo || {};
|
accountsInfo = accountsInfo || {};
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -17,7 +17,6 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { api } from '../../parity';
|
import { api } from '../../parity';
|
||||||
import AddressSelect from '../../AddressSelect';
|
|
||||||
import Container from '../../Container';
|
import Container from '../../Container';
|
||||||
import styles from './deployment.css';
|
import styles from './deployment.css';
|
||||||
|
|
||||||
@ -122,41 +121,20 @@ export default class Deployment extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm () {
|
renderForm () {
|
||||||
const { accounts } = this.context;
|
|
||||||
const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
|
const { baseText, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
|
||||||
const hasError = !!(nameError || tlaError || totalSupplyError);
|
const hasError = !!(nameError || tlaError || totalSupplyError);
|
||||||
const error = `${styles.input} ${styles.error}`;
|
const error = `${styles.input} ${styles.error}`;
|
||||||
const addresses = Object.keys(accounts);
|
|
||||||
|
|
||||||
// <div className={ styles.input }>
|
|
||||||
// <label>global registration</label>
|
|
||||||
// <select onChange={ this.onChangeRegistrar }>
|
|
||||||
// <option value='no'>No, only for me</option>
|
|
||||||
// <option value='yes'>Yes, for everybody</option>
|
|
||||||
// </select>
|
|
||||||
// <div className={ styles.hint }>
|
|
||||||
// register on network (fee: { globalFeeText }ETH)
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className={ styles.form }>
|
<div className={ styles.form }>
|
||||||
<div className={ styles.input }>
|
|
||||||
<label>deployment account</label>
|
|
||||||
<AddressSelect
|
|
||||||
addresses={ addresses }
|
|
||||||
onChange={ this.onChangeFrom } />
|
|
||||||
<div className={ styles.hint }>
|
|
||||||
the owner account to deploy from
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={ nameError ? error : styles.input }>
|
<div className={ nameError ? error : styles.input }>
|
||||||
<label>token name</label>
|
<label>token name</label>
|
||||||
<input
|
<input
|
||||||
value={ name }
|
value={ name }
|
||||||
name='name'
|
name='name'
|
||||||
onChange={ this.onChangeName } />
|
onChange={ this.onChangeName }
|
||||||
|
/>
|
||||||
<div className={ styles.hint }>
|
<div className={ styles.hint }>
|
||||||
{ nameError || 'an identifying name for the token' }
|
{ nameError || 'an identifying name for the token' }
|
||||||
</div>
|
</div>
|
||||||
@ -167,7 +145,8 @@ export default class Deployment extends Component {
|
|||||||
className={ styles.small }
|
className={ styles.small }
|
||||||
name='tla'
|
name='tla'
|
||||||
value={ tla }
|
value={ tla }
|
||||||
onChange={ this.onChangeTla } />
|
onChange={ this.onChangeTla }
|
||||||
|
/>
|
||||||
<div className={ styles.hint }>
|
<div className={ styles.hint }>
|
||||||
{ tlaError || 'unique network acronym for this token' }
|
{ tlaError || 'unique network acronym for this token' }
|
||||||
</div>
|
</div>
|
||||||
@ -180,7 +159,8 @@ export default class Deployment extends Component {
|
|||||||
max='999999999999'
|
max='999999999999'
|
||||||
name='totalSupply'
|
name='totalSupply'
|
||||||
value={ totalSupply }
|
value={ totalSupply }
|
||||||
onChange={ this.onChangeSupply } />
|
onChange={ this.onChangeSupply }
|
||||||
|
/>
|
||||||
<div className={ styles.hint }>
|
<div className={ styles.hint }>
|
||||||
{ totalSupplyError || `number of tokens (base: ${baseText})` }
|
{ totalSupplyError || `number of tokens (base: ${baseText})` }
|
||||||
</div>
|
</div>
|
||||||
@ -191,7 +171,8 @@ export default class Deployment extends Component {
|
|||||||
<div
|
<div
|
||||||
className={ styles.button }
|
className={ styles.button }
|
||||||
disabled={ hasError }
|
disabled={ hasError }
|
||||||
onClick={ this.onDeploy }>
|
onClick={ this.onDeploy }
|
||||||
|
>
|
||||||
Deploy Token
|
Deploy Token
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -201,12 +182,6 @@ export default class Deployment extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeFrom = (event) => {
|
|
||||||
const fromAddress = event.target.value;
|
|
||||||
|
|
||||||
this.setState({ fromAddress });
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeName = (event) => {
|
onChangeName = (event) => {
|
||||||
const name = event.target.value;
|
const name = event.target.value;
|
||||||
const nameError = name && (name.length > 2) && (name.length < 32)
|
const nameError = name && (name.length > 2) && (name.length < 32)
|
||||||
@ -266,7 +241,7 @@ export default class Deployment extends Component {
|
|||||||
|
|
||||||
onDeploy = () => {
|
onDeploy = () => {
|
||||||
const { managerInstance, registryInstance, tokenregInstance } = this.context;
|
const { managerInstance, registryInstance, tokenregInstance } = this.context;
|
||||||
const { base, deployBusy, fromAddress, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
|
const { base, deployBusy, globalReg, globalFee, name, nameError, tla, tlaError, totalSupply, totalSupplyError } = this.state;
|
||||||
const hasError = !!(nameError || tlaError || totalSupplyError);
|
const hasError = !!(nameError || tlaError || totalSupplyError);
|
||||||
|
|
||||||
if (hasError || deployBusy) {
|
if (hasError || deployBusy) {
|
||||||
@ -276,18 +251,23 @@ export default class Deployment extends Component {
|
|||||||
const tokenreg = (globalReg ? tokenregInstance : registryInstance).address;
|
const tokenreg = (globalReg ? tokenregInstance : registryInstance).address;
|
||||||
const values = [base.mul(totalSupply), tla, name, tokenreg];
|
const values = [base.mul(totalSupply), tla, name, tokenreg];
|
||||||
const options = {
|
const options = {
|
||||||
from: fromAddress,
|
|
||||||
value: globalReg ? globalFee : 0
|
value: globalReg ? globalFee : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' });
|
this.setState({ deployBusy: true, deployState: 'Estimating gas for the transaction' });
|
||||||
|
|
||||||
managerInstance
|
return api.parity
|
||||||
.deploy.estimateGas(options, values)
|
.defaultAccount()
|
||||||
|
.then((defaultAddress) => {
|
||||||
|
options.from = defaultAddress;
|
||||||
|
|
||||||
|
return managerInstance.deploy.estimateGas(options, values);
|
||||||
|
})
|
||||||
.then((gas) => {
|
.then((gas) => {
|
||||||
this.setState({ deployState: 'Gas estimated, Posting transaction to the network' });
|
this.setState({ deployState: 'Gas estimated, Posting transaction to the network' });
|
||||||
|
|
||||||
const gasPassed = gas.mul(1.2);
|
const gasPassed = gas.mul(1.2);
|
||||||
|
|
||||||
options.gas = gasPassed.toFixed(0);
|
options.gas = gasPassed.toFixed(0);
|
||||||
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ let registryInstance;
|
|||||||
|
|
||||||
const registries = {};
|
const registries = {};
|
||||||
const subscriptions = {};
|
const subscriptions = {};
|
||||||
|
|
||||||
|
let defaultSubscriptionId;
|
||||||
let nextSubscriptionId = 1000;
|
let nextSubscriptionId = 1000;
|
||||||
let isTest = false;
|
let isTest = false;
|
||||||
|
|
||||||
@ -65,6 +67,20 @@ export function unsubscribeEvents (subscriptionId) {
|
|||||||
delete subscriptions[subscriptionId];
|
delete subscriptions[subscriptionId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function subscribeDefaultAddress (callback) {
|
||||||
|
return api
|
||||||
|
.subscribe('parity_defaultAccount', callback)
|
||||||
|
.then((subscriptionId) => {
|
||||||
|
defaultSubscriptionId = subscriptionId;
|
||||||
|
|
||||||
|
return defaultSubscriptionId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsubscribeDefaultAddress () {
|
||||||
|
return api.unsubscribe(defaultSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
function pollEvents () {
|
function pollEvents () {
|
||||||
const loop = Object.values(subscriptions);
|
const loop = Object.values(subscriptions);
|
||||||
const timeout = () => setTimeout(pollEvents, 1000);
|
const timeout = () => setTimeout(pollEvents, 1000);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -17,10 +17,9 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
import { api } from '../parity';
|
import { api } from '../parity';
|
||||||
import { attachInterface } from '../services';
|
import { attachInterface, subscribeDefaultAddress, unsubscribeDefaultAddress } from '../services';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import Events from '../Events';
|
import Events from '../Events';
|
||||||
import IdentityIcon from '../IdentityIcon';
|
|
||||||
import Loading from '../Loading';
|
import Loading from '../Loading';
|
||||||
|
|
||||||
import styles from './application.css';
|
import styles from './application.css';
|
||||||
@ -32,7 +31,7 @@ let nextEventId = 0;
|
|||||||
|
|
||||||
export default class Application extends Component {
|
export default class Application extends Component {
|
||||||
state = {
|
state = {
|
||||||
fromAddress: null,
|
defaultAddress: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
url: '',
|
url: '',
|
||||||
urlError: null,
|
urlError: null,
|
||||||
@ -47,19 +46,32 @@ export default class Application extends Component {
|
|||||||
registerType: 'file',
|
registerType: 'file',
|
||||||
repo: '',
|
repo: '',
|
||||||
repoError: null,
|
repoError: null,
|
||||||
|
subscriptionId: null,
|
||||||
events: {},
|
events: {},
|
||||||
eventIds: []
|
eventIds: []
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
attachInterface()
|
return Promise
|
||||||
.then((state) => {
|
.all([
|
||||||
this.setState(state, () => {
|
attachInterface(),
|
||||||
this.setState({ loading: false });
|
subscribeDefaultAddress((error, defaultAddress) => {
|
||||||
});
|
if (!error) {
|
||||||
|
this.setState({ defaultAddress });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.then(([state]) => {
|
||||||
|
this.setState(Object.assign({}, state, {
|
||||||
|
loading: false
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
return unsubscribeDefaultAddress();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
|
||||||
@ -75,16 +87,20 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPage () {
|
renderPage () {
|
||||||
const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state;
|
const { defaultAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state;
|
||||||
|
|
||||||
let hashClass = null;
|
let hashClass = null;
|
||||||
|
|
||||||
if (contentHashError) {
|
if (contentHashError) {
|
||||||
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning;
|
hashClass = contentHashOwner !== defaultAddress
|
||||||
|
? styles.hashError
|
||||||
|
: styles.hashWarning;
|
||||||
} else if (contentHash) {
|
} else if (contentHash) {
|
||||||
hashClass = styles.hashOk;
|
hashClass = styles.hashOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
let valueInputs = null;
|
let valueInputs = null;
|
||||||
|
|
||||||
if (registerType === 'content') {
|
if (registerType === 'content') {
|
||||||
valueInputs = [
|
valueInputs = [
|
||||||
<div className={ styles.capture } key='repo'>
|
<div className={ styles.capture } key='repo'>
|
||||||
@ -94,7 +110,8 @@ export default class Application extends Component {
|
|||||||
disabled={ registerBusy }
|
disabled={ registerBusy }
|
||||||
value={ repo }
|
value={ repo }
|
||||||
className={ repoError ? styles.error : null }
|
className={ repoError ? styles.error : null }
|
||||||
onChange={ this.onChangeRepo } />
|
onChange={ this.onChangeRepo }
|
||||||
|
/>
|
||||||
</div>,
|
</div>,
|
||||||
<div className={ styles.capture } key='hash'>
|
<div className={ styles.capture } key='hash'>
|
||||||
<input
|
<input
|
||||||
@ -103,7 +120,8 @@ export default class Application extends Component {
|
|||||||
disabled={ registerBusy }
|
disabled={ registerBusy }
|
||||||
value={ commit }
|
value={ commit }
|
||||||
className={ commitError ? styles.error : null }
|
className={ commitError ? styles.error : null }
|
||||||
onChange={ this.onChangeCommit } />
|
onChange={ this.onChangeCommit }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
@ -115,7 +133,8 @@ export default class Application extends Component {
|
|||||||
disabled={ registerBusy }
|
disabled={ registerBusy }
|
||||||
value={ url }
|
value={ url }
|
||||||
className={ urlError ? styles.error : null }
|
className={ urlError ? styles.error : null }
|
||||||
onChange={ this.onChangeUrl } />
|
onChange={ this.onChangeUrl }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -128,11 +147,17 @@ export default class Application extends Component {
|
|||||||
<Button
|
<Button
|
||||||
disabled={ registerBusy }
|
disabled={ registerBusy }
|
||||||
invert={ registerType !== 'file' }
|
invert={ registerType !== 'file' }
|
||||||
onClick={ this.onClickTypeNormal }>File Link</Button>
|
onClick={ this.onClickTypeNormal }
|
||||||
|
>
|
||||||
|
File Link
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={ registerBusy }
|
disabled={ registerBusy }
|
||||||
invert={ registerType !== 'content' }
|
invert={ registerType !== 'content' }
|
||||||
onClick={ this.onClickTypeContent }>Content Bundle</Button>
|
onClick={ this.onClickTypeContent }
|
||||||
|
>
|
||||||
|
Content Bundle
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.box }>
|
<div className={ styles.box }>
|
||||||
<div className={ styles.description }>
|
<div className={ styles.description }>
|
||||||
@ -148,26 +173,21 @@ export default class Application extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<Events
|
<Events
|
||||||
eventIds={ this.state.eventIds }
|
eventIds={ this.state.eventIds }
|
||||||
events={ this.state.events } />
|
events={ this.state.events }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderButtons () {
|
renderButtons () {
|
||||||
const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
|
const { defaultAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
|
||||||
const account = accounts[fromAddress];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.buttons }>
|
<div className={ styles.buttons }>
|
||||||
<div className={ styles.addressSelect }>
|
|
||||||
<Button invert onClick={ this.onSelectFromAddress }>
|
|
||||||
<IdentityIcon address={ account.address } />
|
|
||||||
<div>{ account.name || account.address }</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={ this.onClickRegister }
|
onClick={ this.onClickRegister }
|
||||||
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError }>register url</Button>
|
disabled={ (contentHashError && contentHashOwner !== defaultAddress) || urlError || repoError || commitError }
|
||||||
|
>register url</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -264,6 +284,7 @@ export default class Application extends Component {
|
|||||||
// TODO: field validation
|
// TODO: field validation
|
||||||
if (!urlError) {
|
if (!urlError) {
|
||||||
const parts = url.split('/');
|
const parts = url.split('/');
|
||||||
|
|
||||||
hasContent = parts.length !== 0;
|
hasContent = parts.length !== 0;
|
||||||
|
|
||||||
if (parts[2] === 'github.com' || parts[2] === 'raw.githubusercontent.com') {
|
if (parts[2] === 'github.com' || parts[2] === 'raw.githubusercontent.com') {
|
||||||
@ -280,11 +301,11 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClickRegister = () => {
|
onClickRegister = () => {
|
||||||
const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state;
|
const { defaultAddress, commit, commitError, contentHashError, contentHashOwner, url, urlError, registerType, repo, repoError } = this.state;
|
||||||
|
|
||||||
// TODO: No errors are currently set, validation to be expanded and added for each
|
// TODO: No errors are currently set, validation to be expanded and added for each
|
||||||
// field (query is fast to pick up the issues, so not burning atm)
|
// field (query is fast to pick up the issues, so not burning atm)
|
||||||
if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) {
|
if ((contentHashError && contentHashOwner !== defaultAddress) || repoError || urlError || commitError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,12 +375,15 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerContent (contentRepo, contentCommit) {
|
registerContent (contentRepo, contentCommit) {
|
||||||
const { contentHash, fromAddress, instance } = this.state;
|
const { defaultAddress, contentHash, instance } = this.state;
|
||||||
contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`;
|
|
||||||
|
contentCommit = contentCommit.substr(0, 2) === '0x'
|
||||||
|
? contentCommit
|
||||||
|
: `0x${contentCommit}`;
|
||||||
|
|
||||||
const eventId = nextEventId++;
|
const eventId = nextEventId++;
|
||||||
const values = [contentHash, contentRepo, contentCommit];
|
const values = [contentHash, contentRepo, contentCommit];
|
||||||
const options = { from: fromAddress };
|
const options = { from: defaultAddress };
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
eventIds: [eventId].concat(this.state.eventIds),
|
eventIds: [eventId].concat(this.state.eventIds),
|
||||||
@ -368,7 +392,7 @@ export default class Application extends Component {
|
|||||||
contentHash,
|
contentHash,
|
||||||
contentRepo,
|
contentRepo,
|
||||||
contentCommit,
|
contentCommit,
|
||||||
fromAddress,
|
defaultAddress,
|
||||||
registerBusy: true,
|
registerBusy: true,
|
||||||
registerState: 'Estimating gas for the transaction',
|
registerState: 'Estimating gas for the transaction',
|
||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
@ -396,6 +420,7 @@ export default class Application extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const gasPassed = gas.mul(1.2);
|
const gasPassed = gas.mul(1.2);
|
||||||
|
|
||||||
options.gas = gasPassed.toFixed(0);
|
options.gas = gasPassed.toFixed(0);
|
||||||
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
||||||
|
|
||||||
@ -405,11 +430,11 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerUrl (contentUrl) {
|
registerUrl (contentUrl) {
|
||||||
const { contentHash, fromAddress, instance } = this.state;
|
const { contentHash, defaultAddress, instance } = this.state;
|
||||||
|
|
||||||
const eventId = nextEventId++;
|
const eventId = nextEventId++;
|
||||||
const values = [contentHash, contentUrl];
|
const values = [contentHash, contentUrl];
|
||||||
const options = { from: fromAddress };
|
const options = { from: defaultAddress };
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
eventIds: [eventId].concat(this.state.eventIds),
|
eventIds: [eventId].concat(this.state.eventIds),
|
||||||
@ -417,7 +442,7 @@ export default class Application extends Component {
|
|||||||
[eventId]: {
|
[eventId]: {
|
||||||
contentHash,
|
contentHash,
|
||||||
contentUrl,
|
contentUrl,
|
||||||
fromAddress,
|
defaultAddress,
|
||||||
registerBusy: true,
|
registerBusy: true,
|
||||||
registerState: 'Estimating gas for the transaction',
|
registerState: 'Estimating gas for the transaction',
|
||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
@ -445,6 +470,7 @@ export default class Application extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const gasPassed = gas.mul(1.2);
|
const gasPassed = gas.mul(1.2);
|
||||||
|
|
||||||
options.gas = gasPassed.toFixed(0);
|
options.gas = gasPassed.toFixed(0);
|
||||||
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
|
||||||
|
|
||||||
@ -453,25 +479,6 @@ export default class Application extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectFromAddress = () => {
|
|
||||||
const { accounts, fromAddress } = this.state;
|
|
||||||
const addresses = Object.keys(accounts);
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
addresses.forEach((address, _index) => {
|
|
||||||
if (address === fromAddress) {
|
|
||||||
index = _index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (index >= addresses.length) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ fromAddress: addresses[index] });
|
|
||||||
}
|
|
||||||
|
|
||||||
lookupHash (url) {
|
lookupHash (url) {
|
||||||
const { instance } = this.state;
|
const { instance } = this.state;
|
||||||
|
|
||||||
|
@ -17,48 +17,44 @@
|
|||||||
import * as abis from '~/contracts/abi';
|
import * as abis from '~/contracts/abi';
|
||||||
import { api } from './parity';
|
import { api } from './parity';
|
||||||
|
|
||||||
|
let defaultSubscriptionId;
|
||||||
|
|
||||||
export function attachInterface () {
|
export function attachInterface () {
|
||||||
return api.parity
|
return api.parity
|
||||||
.registryAddress()
|
.registryAddress()
|
||||||
.then((registryAddress) => {
|
.then((registryAddress) => {
|
||||||
console.log(`the registry was found at ${registryAddress}`);
|
console.log(`the registry was found at ${registryAddress}`);
|
||||||
|
|
||||||
const registry = api.newContract(abis.registry, registryAddress).instance;
|
return api
|
||||||
|
.newContract(abis.registry, registryAddress).instance
|
||||||
return Promise
|
.getAddress.call({}, [api.util.sha3('githubhint'), 'A']);
|
||||||
.all([
|
|
||||||
registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']),
|
|
||||||
api.parity.accountsInfo()
|
|
||||||
]);
|
|
||||||
})
|
})
|
||||||
.then(([address, accountsInfo]) => {
|
.then((address) => {
|
||||||
console.log(`githubhint was found at ${address}`);
|
console.log(`githubhint was found at ${address}`);
|
||||||
|
|
||||||
const contract = api.newContract(abis.githubhint, address);
|
const contract = api.newContract(abis.githubhint, address);
|
||||||
const accounts = Object
|
|
||||||
.keys(accountsInfo)
|
|
||||||
.reduce((obj, address) => {
|
|
||||||
const account = accountsInfo[address];
|
|
||||||
|
|
||||||
return Object.assign(obj, {
|
|
||||||
[address]: {
|
|
||||||
address,
|
|
||||||
name: account.name
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, {});
|
|
||||||
const fromAddress = Object.keys(accounts)[0];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
|
||||||
address,
|
address,
|
||||||
accountsInfo,
|
|
||||||
contract,
|
contract,
|
||||||
instance: contract.instance,
|
instance: contract.instance
|
||||||
fromAddress
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('attachInterface', error);
|
console.error('attachInterface', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function subscribeDefaultAddress (callback) {
|
||||||
|
return api
|
||||||
|
.subscribe('parity_defaultAccount', callback)
|
||||||
|
.then((subscriptionId) => {
|
||||||
|
defaultSubscriptionId = subscriptionId;
|
||||||
|
|
||||||
|
return defaultSubscriptionId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsubscribeDefaultAddress () {
|
||||||
|
return api.unsubscribe(defaultSubscriptionId);
|
||||||
|
}
|
||||||
|
@ -22,7 +22,7 @@ import { api } from '../parity';
|
|||||||
|
|
||||||
import styles from './transaction.css';
|
import styles from './transaction.css';
|
||||||
|
|
||||||
import IdentityIcon from '../../githubhint/IdentityIcon';
|
import IdentityIcon from '../IdentityIcon';
|
||||||
|
|
||||||
class BaseTransaction extends Component {
|
class BaseTransaction extends Component {
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ export const ownerLookup = (name) => (dispatch, getState) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name = name.toLowerCase();
|
||||||
dispatch(ownerLookupStart(name));
|
dispatch(ownerLookupStart(name));
|
||||||
|
|
||||||
return getOwner(contract, name)
|
return getOwner(contract, name)
|
||||||
|
@ -70,7 +70,7 @@ class Lookup extends Component {
|
|||||||
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
||||||
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
||||||
<MenuItem value='reverse' primaryText='reverse – find a name for an address' />
|
<MenuItem value='reverse' primaryText='reverse – find a name for an address' />
|
||||||
<MenuItem value='owner' primaryText='owner – find a the owner' />
|
<MenuItem value='owner' primaryText='owner – find the owner' />
|
||||||
</DropDownMenu>
|
</DropDownMenu>
|
||||||
<RaisedButton
|
<RaisedButton
|
||||||
label='Lookup'
|
label='Lookup'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -30,7 +30,6 @@ export default class Application extends Component {
|
|||||||
state = {
|
state = {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
address: null,
|
address: null,
|
||||||
fromAddress: null,
|
|
||||||
accountsInfo: {},
|
accountsInfo: {},
|
||||||
blockNumber: new BigNumber(0),
|
blockNumber: new BigNumber(0),
|
||||||
contract: null,
|
contract: null,
|
||||||
@ -41,11 +40,9 @@ export default class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
attachInterface()
|
return attachInterface()
|
||||||
.then((state) => {
|
.then((state) => {
|
||||||
this.setState(state, () => {
|
this.setState(Object.assign({}, state, { loading: false }));
|
||||||
this.setState({ loading: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
return attachBlockNumber(state.instance, (state) => {
|
return attachBlockNumber(state.instance, (state) => {
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
@ -80,22 +77,21 @@ export default class Application extends Component {
|
|||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
blockNumber={ blockNumber }
|
blockNumber={ blockNumber }
|
||||||
totalSignatures={ totalSignatures } />
|
totalSignatures={ totalSignatures }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderImport () {
|
renderImport () {
|
||||||
const { accounts, fromAddress, instance, showImport } = this.state;
|
const { instance, showImport } = this.state;
|
||||||
|
|
||||||
if (showImport) {
|
if (showImport) {
|
||||||
return (
|
return (
|
||||||
<Import
|
<Import
|
||||||
accounts={ accounts }
|
|
||||||
fromAddress={ fromAddress }
|
|
||||||
instance={ instance }
|
instance={ instance }
|
||||||
visible={ showImport }
|
visible={ showImport }
|
||||||
onClose={ this.toggleImport }
|
onClose={ this.toggleImport }
|
||||||
onSetFromAddress={ this.setFromAddress } />
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +108,8 @@ export default class Application extends Component {
|
|||||||
return (
|
return (
|
||||||
<Events
|
<Events
|
||||||
accountsInfo={ accountsInfo }
|
accountsInfo={ accountsInfo }
|
||||||
contract={ contract } />
|
contract={ contract }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,10 +118,4 @@ export default class Application extends Component {
|
|||||||
showImport: !this.state.showImport
|
showImport: !this.state.showImport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromAddress = (fromAddress) => {
|
|
||||||
this.setState({
|
|
||||||
fromAddress
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,14 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { api } from '../parity';
|
import { api } from '../parity';
|
||||||
import { callRegister, postRegister } from '../services';
|
import { callRegister, postRegister } from '../services';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import IdentityIcon from '../IdentityIcon';
|
|
||||||
|
|
||||||
import styles from './import.css';
|
import styles from './import.css';
|
||||||
|
|
||||||
export default class Import extends Component {
|
export default class Import extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
|
||||||
fromAddress: PropTypes.string.isRequired,
|
|
||||||
instance: PropTypes.object.isRequired,
|
instance: PropTypes.object.isRequired,
|
||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired
|
||||||
onSetFromAddress: PropTypes.func.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -83,21 +79,12 @@ export default class Import extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRegister () {
|
renderRegister () {
|
||||||
const { accounts, fromAddress } = this.props;
|
|
||||||
|
|
||||||
const account = accounts[fromAddress];
|
|
||||||
const count = this.countFunctions();
|
const count = this.countFunctions();
|
||||||
let buttons = null;
|
let buttons = null;
|
||||||
|
|
||||||
if (count) {
|
if (count) {
|
||||||
buttons = (
|
buttons = (
|
||||||
<div className={ styles.buttonrow }>
|
<div className={ styles.buttonrow }>
|
||||||
<div className={ styles.addressSelect }>
|
|
||||||
<Button invert onClick={ this.onSelectFromAddress }>
|
|
||||||
<IdentityIcon address={ account.address } />
|
|
||||||
<div>{ account.name || account.address }</div>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Button onClick={ this.onRegister }>
|
<Button onClick={ this.onRegister }>
|
||||||
register functions
|
register functions
|
||||||
</Button>
|
</Button>
|
||||||
@ -197,15 +184,15 @@ export default class Import extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRegister = () => {
|
onRegister = () => {
|
||||||
const { instance, fromAddress, onClose } = this.props;
|
const { instance, onClose } = this.props;
|
||||||
const { functions, fnstate } = this.state;
|
const { functions, fnstate } = this.state;
|
||||||
|
|
||||||
Promise
|
return Promise
|
||||||
.all(
|
.all(
|
||||||
functions
|
functions
|
||||||
.filter((fn) => !fn.constant)
|
.filter((fn) => !fn.constant)
|
||||||
.filter((fn) => fnstate[fn.signature] === 'fntodo')
|
.filter((fn) => fnstate[fn.signature] === 'fntodo')
|
||||||
.map((fn) => postRegister(instance, fn.id, { from: fromAddress }))
|
.map((fn) => postRegister(instance, fn.id, {}))
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onClose();
|
onClose();
|
||||||
@ -214,23 +201,4 @@ export default class Import extends Component {
|
|||||||
console.error('onRegister', error);
|
console.error('onRegister', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectFromAddress = () => {
|
|
||||||
const { accounts, fromAddress, onSetFromAddress } = this.props;
|
|
||||||
const addresses = Object.keys(accounts);
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
addresses.forEach((address, _index) => {
|
|
||||||
if (address === fromAddress) {
|
|
||||||
index = _index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (index >= addresses.length) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetFromAddress(addresses[index]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -166,8 +166,13 @@ export function callRegister (instance, id, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function postRegister (instance, id, options = {}) {
|
export function postRegister (instance, id, options = {}) {
|
||||||
return instance.register
|
return api.parity
|
||||||
.estimateGas(options, [id])
|
.defaultAccount()
|
||||||
|
.then((defaultAddress) => {
|
||||||
|
options.from = defaultAddress;
|
||||||
|
|
||||||
|
return instance.register.estimateGas(options, [id]);
|
||||||
|
})
|
||||||
.then((gas) => {
|
.then((gas) => {
|
||||||
options.gas = gas.mul(1.2).toFixed(0);
|
options.gas = gas.mul(1.2).toFixed(0);
|
||||||
console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`);
|
console.log('postRegister', `gas estimated at ${gas.toFormat(0)}, setting to ${gas.mul(1.2).toFormat(0)}`);
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
import { Address, Data, Hash, Quantity } from '../types';
|
import { Address, Data, Hash, Quantity } from '../types';
|
||||||
|
|
||||||
|
// DUMMY for beta
|
||||||
|
const SECTION_ACCOUNTS = null;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
acceptNonReservedPeers: {
|
acceptNonReservedPeers: {
|
||||||
desc: '?',
|
desc: '?',
|
||||||
@ -135,6 +138,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
defaultAccount: {
|
||||||
|
section: SECTION_ACCOUNTS,
|
||||||
|
desc: 'Returns the defaultAccount that is to be used with transactions',
|
||||||
|
params: [],
|
||||||
|
returns: {
|
||||||
|
type: Address,
|
||||||
|
desc: 'The account address',
|
||||||
|
example: '0x63Cf90D3f0410092FC0fca41846f596223979195'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
defaultExtraData: {
|
defaultExtraData: {
|
||||||
desc: 'Returns the default extra data',
|
desc: 'Returns the default extra data',
|
||||||
params: [],
|
params: [],
|
||||||
|
@ -15,15 +15,23 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-top: .5em !important;
|
margin-top: .5em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
padding: 0.5em 0;
|
||||||
margin: 0 -1.5em;
|
|
||||||
padding: 0.5em 1.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -37,3 +45,26 @@
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selectIcon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.5em;
|
||||||
|
top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected,
|
||||||
|
.unselected {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
background: rgba(0, 0, 0, 0.4) !important;
|
||||||
|
|
||||||
|
.selectIcon {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -14,14 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { Checkbox } from 'material-ui';
|
|
||||||
import { List, ListItem } from 'material-ui/List';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Modal, Button } from '~/ui';
|
import { DappCard, Portal, SectionList } from '~/ui';
|
||||||
import { DoneIcon } from '~/ui/Icons';
|
import { CheckIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './addDapps.css';
|
import styles from './addDapps.css';
|
||||||
|
|
||||||
@ -39,61 +37,61 @@ export default class AddDapps extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Portal
|
||||||
actions={ [
|
className={ styles.modal }
|
||||||
<Button
|
onClose={ store.closeModal }
|
||||||
icon={ <DoneIcon /> }
|
open
|
||||||
key='done'
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='dapps.add.button.done'
|
|
||||||
defaultMessage='Done' />
|
|
||||||
}
|
|
||||||
onClick={ store.closeModal } />
|
|
||||||
] }
|
|
||||||
compact
|
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='dapps.add.label'
|
id='dapps.add.label'
|
||||||
defaultMessage='visible applications' />
|
defaultMessage='visible applications'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
visible>
|
>
|
||||||
<div className={ styles.warning } />
|
<div className={ styles.container }>
|
||||||
{
|
<div className={ styles.warning } />
|
||||||
this.renderList(store.sortedLocal,
|
{
|
||||||
<FormattedMessage
|
this.renderList(store.sortedLocal, store.displayApps,
|
||||||
id='dapps.add.local.label'
|
<FormattedMessage
|
||||||
defaultMessage='Applications locally available' />,
|
id='dapps.add.local.label'
|
||||||
<FormattedMessage
|
defaultMessage='Applications locally available'
|
||||||
id='dapps.add.local.desc'
|
/>,
|
||||||
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.' />
|
<FormattedMessage
|
||||||
)
|
id='dapps.add.local.desc'
|
||||||
}
|
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
|
||||||
{
|
/>
|
||||||
this.renderList(store.sortedBuiltin,
|
)
|
||||||
<FormattedMessage
|
}
|
||||||
id='dapps.add.builtin.label'
|
{
|
||||||
defaultMessage='Applications bundled with Parity' />,
|
this.renderList(store.sortedBuiltin, store.displayApps,
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='dapps.add.builtin.desc'
|
id='dapps.add.builtin.label'
|
||||||
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.' />
|
defaultMessage='Applications bundled with Parity'
|
||||||
)
|
/>,
|
||||||
}
|
<FormattedMessage
|
||||||
{
|
id='dapps.add.builtin.desc'
|
||||||
this.renderList(store.sortedNetwork,
|
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.'
|
||||||
<FormattedMessage
|
/>
|
||||||
id='dapps.add.network.label'
|
)
|
||||||
defaultMessage='Applications on the global network' />,
|
}
|
||||||
<FormattedMessage
|
{
|
||||||
id='dapps.add.network.desc'
|
this.renderList(store.sortedNetwork, store.displayApps,
|
||||||
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.' />
|
<FormattedMessage
|
||||||
)
|
id='dapps.add.network.label'
|
||||||
}
|
defaultMessage='Applications on the global network'
|
||||||
</Modal>
|
/>,
|
||||||
|
<FormattedMessage
|
||||||
|
id='dapps.add.network.desc'
|
||||||
|
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList (items, header, byline) {
|
renderList (items, visibleItems, header, byline) {
|
||||||
if (!items || !items.length) {
|
if (!items || !items.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -104,41 +102,40 @@ export default class AddDapps extends Component {
|
|||||||
<div className={ styles.header }>{ header }</div>
|
<div className={ styles.header }>{ header }</div>
|
||||||
<div className={ styles.byline }>{ byline }</div>
|
<div className={ styles.byline }>{ byline }</div>
|
||||||
</div>
|
</div>
|
||||||
<List>
|
<SectionList
|
||||||
{ items.map(this.renderApp) }
|
items={ items }
|
||||||
</List>
|
noStretch
|
||||||
|
renderItem={ this.renderApp }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderApp = (app) => {
|
renderApp = (app) => {
|
||||||
const { store } = this.props;
|
const { store } = this.props;
|
||||||
const isHidden = !store.displayApps[app.id].visible;
|
const isVisible = store.displayApps[app.id].visible;
|
||||||
|
|
||||||
const onCheck = () => {
|
const onClick = () => {
|
||||||
if (isHidden) {
|
if (isVisible) {
|
||||||
store.showApp(app.id);
|
|
||||||
} else {
|
|
||||||
store.hideApp(app.id);
|
store.hideApp(app.id);
|
||||||
|
} else {
|
||||||
|
store.showApp(app.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<DappCard
|
||||||
|
app={ app }
|
||||||
|
className={
|
||||||
|
isVisible
|
||||||
|
? styles.selected
|
||||||
|
: styles.unselected
|
||||||
|
}
|
||||||
key={ app.id }
|
key={ app.id }
|
||||||
leftCheckbox={
|
onClick={ onClick }
|
||||||
<Checkbox
|
>
|
||||||
checked={ !isHidden }
|
<CheckIcon className={ styles.selectIcon } />
|
||||||
onCheck={ onCheck }
|
</DappCard>
|
||||||
/>
|
|
||||||
}
|
|
||||||
primaryText={ app.name }
|
|
||||||
secondaryText={
|
|
||||||
<div className={ styles.description }>
|
|
||||||
{ app.description }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ describe('modals/AddDapps', () => {
|
|||||||
|
|
||||||
it('does not render the modal with modalOpen = false', () => {
|
it('does not render the modal with modalOpen = false', () => {
|
||||||
expect(
|
expect(
|
||||||
renderShallow({ modalOpen: false }).find('Connect(Modal)')
|
renderShallow({ modalOpen: false }).find('Portal')
|
||||||
).to.have.length(0);
|
).to.have.length(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does render the modal with modalOpen = true', () => {
|
it('does render the modal with modalOpen = true', () => {
|
||||||
expect(
|
expect(
|
||||||
renderShallow({ modalOpen: true }).find('Connect(Modal)')
|
renderShallow({ modalOpen: true }).find('Portal')
|
||||||
).to.have.length(1);
|
).to.have.length(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -15,33 +15,54 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
.info {
|
display: flex;
|
||||||
display: inline-block;
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.address {
|
.overlay {
|
||||||
opacity: 0.75;
|
position: absolute;
|
||||||
}
|
right: 0.5em;
|
||||||
|
top: 0.5em;
|
||||||
.description {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected, .unselected {
|
.selected, .unselected {
|
||||||
margin-bottom: 0.25em;
|
margin-bottom: 0.25em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
background: rgba(0, 0, 0, 0.4) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15) !important;
|
||||||
|
|
||||||
|
&.default {
|
||||||
|
background: rgba(255, 255, 255, 0.35) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.unselected {
|
.unselected {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconDisabled {
|
||||||
|
opacity: 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
opacity: 0.75;
|
||||||
|
|
||||||
|
span {
|
||||||
|
line-height: 24px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -14,14 +14,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { Checkbox } from 'material-ui';
|
|
||||||
import { List, ListItem } from 'material-ui/List';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Button, IdentityIcon, Modal } from '~/ui';
|
import { AccountCard, Portal, SectionList } from '~/ui';
|
||||||
import { DoneIcon } from '~/ui/Icons';
|
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './dappPermissions.css';
|
import styles from './dappPermissions.css';
|
||||||
|
|
||||||
@ -39,74 +37,80 @@ export default class DappPermissions extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Portal
|
||||||
actions={ [
|
buttons={
|
||||||
<Button
|
<div className={ styles.legend }>
|
||||||
icon={ <DoneIcon /> }
|
<FormattedMessage
|
||||||
key='done'
|
id='dapps.permissions.description'
|
||||||
label={
|
defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account'
|
||||||
<FormattedMessage
|
values={ {
|
||||||
id='dapps.permissions.button.done'
|
activeIcon: <CheckIcon />,
|
||||||
defaultMessage='Done' />
|
defaultIcon: <StarIcon />
|
||||||
}
|
} }
|
||||||
onClick={ store.closeModal } />
|
/>
|
||||||
] }
|
</div>
|
||||||
compact
|
}
|
||||||
|
onClose={ store.closeModal }
|
||||||
|
open
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='dapps.permissions.label'
|
id='dapps.permissions.label'
|
||||||
defaultMessage='visible dapp accounts' />
|
defaultMessage='visible dapp accounts'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
visible>
|
>
|
||||||
<List>
|
<div className={ styles.container }>
|
||||||
{ this.renderListItems() }
|
<SectionList
|
||||||
</List>
|
items={ store.accounts }
|
||||||
</Modal>
|
noStretch
|
||||||
|
renderItem={ this.renderAccount }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderListItems () {
|
renderAccount = (account) => {
|
||||||
const { store } = this.props;
|
const { store } = this.props;
|
||||||
|
|
||||||
return store.accounts.map((account) => {
|
const onMakeDefault = () => {
|
||||||
const onCheck = () => {
|
store.setDefaultAccount(account.address);
|
||||||
store.selectAccount(account.address);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Once new modal & account selection is in, this should be updated
|
const onSelect = () => {
|
||||||
// to conform to the new (as of this code WIP) look & feel for selection.
|
store.selectAccount(account.address);
|
||||||
// For now in the current/old style, not as pretty but consistent.
|
};
|
||||||
return (
|
|
||||||
<ListItem
|
let className;
|
||||||
className={
|
|
||||||
|
if (account.checked) {
|
||||||
|
className = account.default
|
||||||
|
? `${styles.selected} ${styles.default}`
|
||||||
|
: styles.selected;
|
||||||
|
} else {
|
||||||
|
className = styles.unselected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.item }>
|
||||||
|
<AccountCard
|
||||||
|
account={ account }
|
||||||
|
className={ className }
|
||||||
|
onClick={ onSelect }
|
||||||
|
/>
|
||||||
|
<div className={ styles.overlay }>
|
||||||
|
{
|
||||||
|
account.checked && account.default
|
||||||
|
? <StarIcon />
|
||||||
|
: <StarOutlineIcon className={ styles.iconDisabled } onClick={ onMakeDefault } />
|
||||||
|
}
|
||||||
|
{
|
||||||
account.checked
|
account.checked
|
||||||
? styles.selected
|
? <CheckIcon onClick={ onSelect } />
|
||||||
: styles.unselected
|
: <CheckIcon className={ styles.iconDisabled } onClick={ onSelect } />
|
||||||
}
|
}
|
||||||
key={ account.address }
|
</div>
|
||||||
leftCheckbox={
|
</div>
|
||||||
<Checkbox
|
);
|
||||||
checked={ account.checked }
|
|
||||||
onCheck={ onCheck }
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
primaryText={
|
|
||||||
<div className={ styles.item }>
|
|
||||||
<IdentityIcon address={ account.address } />
|
|
||||||
<div className={ styles.info }>
|
|
||||||
<h3 className={ styles.name }>
|
|
||||||
{ account.name }
|
|
||||||
</h3>
|
|
||||||
<div className={ styles.address }>
|
|
||||||
{ account.address }
|
|
||||||
</div>
|
|
||||||
<div className={ styles.description }>
|
|
||||||
{ account.description }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ describe('modals/DappPermissions', () => {
|
|||||||
|
|
||||||
it('does not render the modal with modalOpen = false', () => {
|
it('does not render the modal with modalOpen = false', () => {
|
||||||
expect(
|
expect(
|
||||||
renderShallow({ modalOpen: false }).find('Connect(Modal)')
|
renderShallow({ modalOpen: false }).find('Portal')
|
||||||
).to.have.length(0);
|
).to.have.length(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does render the modal with modalOpen = true', () => {
|
it('does render the modal with modalOpen = true', () => {
|
||||||
expect(
|
expect(
|
||||||
renderShallow({ modalOpen: true, accounts: [] }).find('Connect(Modal)')
|
renderShallow({ modalOpen: true, accounts: [] }).find('Portal')
|
||||||
).to.have.length(1);
|
).to.have.length(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,12 +29,17 @@ export default class Store {
|
|||||||
|
|
||||||
@action closeModal = () => {
|
@action closeModal = () => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
const accounts = this.accounts
|
let addresses = null;
|
||||||
.filter((account) => account.checked)
|
const checkedAccounts = this.accounts.filter((account) => account.checked);
|
||||||
.map((account) => account.address);
|
|
||||||
|
if (checkedAccounts.length) {
|
||||||
|
addresses = checkedAccounts.filter((account) => account.default)
|
||||||
|
.concat(checkedAccounts.filter((account) => !account.default))
|
||||||
|
.map((account) => account.address);
|
||||||
|
}
|
||||||
|
|
||||||
this.modalOpen = false;
|
this.modalOpen = false;
|
||||||
this.updateWhitelist(accounts.length === this.accounts.length ? null : accounts);
|
this.updateWhitelist(addresses);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +47,15 @@ export default class Store {
|
|||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.accounts = Object
|
this.accounts = Object
|
||||||
.values(accounts)
|
.values(accounts)
|
||||||
.map((account) => {
|
.map((account, index) => {
|
||||||
return {
|
return {
|
||||||
address: account.address,
|
address: account.address,
|
||||||
checked: this.whitelist
|
checked: this.whitelist
|
||||||
? this.whitelist.includes(account.address)
|
? this.whitelist.includes(account.address)
|
||||||
: true,
|
: true,
|
||||||
|
default: this.whitelist
|
||||||
|
? this.whitelist[0] === account.address
|
||||||
|
: index === 0,
|
||||||
description: account.meta.description,
|
description: account.meta.description,
|
||||||
name: account.name
|
name: account.name
|
||||||
};
|
};
|
||||||
@ -57,9 +65,31 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action selectAccount = (address) => {
|
@action selectAccount = (address) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.accounts = this.accounts.map((account) => {
|
||||||
|
if (account.address === address) {
|
||||||
|
account.checked = !account.checked;
|
||||||
|
account.default = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return account;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setDefaultAccount((
|
||||||
|
this.accounts.find((account) => account.default) ||
|
||||||
|
this.accounts.find((account) => account.checked) ||
|
||||||
|
{}
|
||||||
|
).address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDefaultAccount = (address) => {
|
||||||
this.accounts = this.accounts.map((account) => {
|
this.accounts = this.accounts.map((account) => {
|
||||||
if (account.address === address) {
|
if (account.address === address) {
|
||||||
account.checked = !account.checked;
|
account.checked = true;
|
||||||
|
account.default = true;
|
||||||
|
} else if (account.default) {
|
||||||
|
account.default = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
|
@ -23,21 +23,25 @@ const ACCOUNTS = {
|
|||||||
'456': { address: '456', name: '456', meta: { description: '456' } },
|
'456': { address: '456', name: '456', meta: { description: '456' } },
|
||||||
'789': { address: '789', name: '789', meta: { description: '789' } }
|
'789': { address: '789', name: '789', meta: { description: '789' } }
|
||||||
};
|
};
|
||||||
const WHITELIST = ['123', '456'];
|
const WHITELIST = ['456', '789'];
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function create () {
|
||||||
|
api = {
|
||||||
|
parity: {
|
||||||
|
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
||||||
|
setNewDappsWhitelist: sinon.stub().resolves(true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
store = new Store(api);
|
||||||
|
}
|
||||||
|
|
||||||
describe('modals/DappPermissions/store', () => {
|
describe('modals/DappPermissions/store', () => {
|
||||||
let api;
|
|
||||||
let store;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
api = {
|
create();
|
||||||
parity: {
|
|
||||||
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
|
||||||
setNewDappsWhitelist: sinon.stub().resolves(true)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
store = new Store(api);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('constructor', () => {
|
describe('constructor', () => {
|
||||||
@ -51,49 +55,71 @@ describe('modals/DappPermissions/store', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('@actions', () => {
|
describe('@actions', () => {
|
||||||
describe('openModal', () => {
|
beforeEach(() => {
|
||||||
beforeEach(() => {
|
store.openModal(ACCOUNTS);
|
||||||
store.openModal(ACCOUNTS);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
describe('openModal', () => {
|
||||||
it('sets the modalOpen status', () => {
|
it('sets the modalOpen status', () => {
|
||||||
expect(store.modalOpen).to.be.true;
|
expect(store.modalOpen).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets accounts with checked interfaces', () => {
|
it('sets accounts with checked interfaces', () => {
|
||||||
expect(store.accounts.peek()).to.deep.equal([
|
expect(store.accounts.peek()).to.deep.equal([
|
||||||
{ address: '123', name: '123', description: '123', checked: true },
|
{ address: '123', name: '123', description: '123', default: false, checked: false },
|
||||||
{ address: '456', name: '456', description: '456', checked: true },
|
{ address: '456', name: '456', description: '456', default: true, checked: true },
|
||||||
{ address: '789', name: '789', description: '789', checked: false }
|
{ address: '789', name: '789', description: '789', default: false, checked: true }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('closeModal', () => {
|
describe('closeModal', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.openModal(ACCOUNTS);
|
store.setDefaultAccount('789');
|
||||||
store.selectAccount('789');
|
|
||||||
store.closeModal();
|
store.closeModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls setNewDappsWhitelist', () => {
|
it('calls setNewDappsWhitelist', () => {
|
||||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
|
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('has the default account in first position', () => {
|
||||||
|
expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('selectAccount', () => {
|
describe('selectAccount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.openModal(ACCOUNTS);
|
|
||||||
store.selectAccount('123');
|
store.selectAccount('123');
|
||||||
store.selectAccount('789');
|
store.selectAccount('789');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unselects previous selected accounts', () => {
|
it('unselects previous selected accounts', () => {
|
||||||
expect(store.accounts.find((account) => account.address === '123').checked).to.be.false;
|
expect(store.accounts.find((account) => account.address === '123').checked).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('selects previous unselected accounts', () => {
|
it('selects previous unselected accounts', () => {
|
||||||
expect(store.accounts.find((account) => account.address === '789').checked).to.be.true;
|
expect(store.accounts.find((account) => account.address === '789').checked).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets a new default when default was unselected', () => {
|
||||||
|
store.selectAccount('456');
|
||||||
|
expect(store.accounts.find((account) => account.address === '456').default).to.be.false;
|
||||||
|
expect(store.accounts.find((account) => account.address === '123').default).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setDefaultAccount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setDefaultAccount('789');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unselects previous default', () => {
|
||||||
|
expect(store.accounts.find((account) => account.address === '456').default).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('selects new default', () => {
|
||||||
|
expect(store.accounts.find((account) => account.address === '789').default).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -15,42 +15,22 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { Input, GasPriceEditor } from '~/ui';
|
import { GasPriceEditor } from '~/ui';
|
||||||
|
|
||||||
import styles from '../executeContract.css';
|
import styles from '../executeContract.css';
|
||||||
|
|
||||||
export default class AdvancedStep extends Component {
|
export default class AdvancedStep extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
gasStore: PropTypes.object.isRequired,
|
gasStore: PropTypes.object.isRequired
|
||||||
minBlock: PropTypes.string,
|
|
||||||
minBlockError: PropTypes.string,
|
|
||||||
onMinBlockChange: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
|
const { gasStore } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={ styles.gaseditor }>
|
||||||
<Input
|
<GasPriceEditor store={ gasStore } />
|
||||||
error={ minBlockError }
|
|
||||||
hint={
|
|
||||||
<FormattedMessage
|
|
||||||
id='executeContract.advanced.minBlock.hint'
|
|
||||||
defaultMessage='Only post the transaction after this block' />
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='executeContract.advanced.minBlock.label'
|
|
||||||
defaultMessage='BlockNumber to send from' />
|
|
||||||
}
|
|
||||||
value={ minBlock }
|
|
||||||
onSubmit={ onMinBlockChange } />
|
|
||||||
<div className={ styles.gaseditor }>
|
|
||||||
<GasPriceEditor store={ gasStore } />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
@ -41,27 +40,32 @@ const TITLES = {
|
|||||||
transfer: (
|
transfer: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.steps.transfer'
|
id='executeContract.steps.transfer'
|
||||||
defaultMessage='function details' />
|
defaultMessage='function details'
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
sending: (
|
sending: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.steps.sending'
|
id='executeContract.steps.sending'
|
||||||
defaultMessage='sending' />
|
defaultMessage='sending'
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
complete: (
|
complete: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.steps.complete'
|
id='executeContract.steps.complete'
|
||||||
defaultMessage='complete' />
|
defaultMessage='complete'
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
advanced: (
|
advanced: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.steps.advanced'
|
id='executeContract.steps.advanced'
|
||||||
defaultMessage='advanced options' />
|
defaultMessage='advanced options'
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
rejected: (
|
rejected: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.steps.rejected'
|
id='executeContract.steps.rejected'
|
||||||
defaultMessage='rejected' />
|
defaultMessage='rejected'
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||||
@ -95,8 +99,6 @@ class ExecuteContract extends Component {
|
|||||||
fromAddressError: null,
|
fromAddressError: null,
|
||||||
func: null,
|
func: null,
|
||||||
funcError: null,
|
funcError: null,
|
||||||
minBlock: '0',
|
|
||||||
minBlockError: null,
|
|
||||||
rejected: false,
|
rejected: false,
|
||||||
sending: false,
|
sending: false,
|
||||||
step: STEP_DETAILS,
|
step: STEP_DETAILS,
|
||||||
@ -139,7 +141,8 @@ class ExecuteContract extends Component {
|
|||||||
advancedOptions
|
advancedOptions
|
||||||
? [STEP_BUSY]
|
? [STEP_BUSY]
|
||||||
: [STEP_BUSY_OR_ADVANCED]
|
: [STEP_BUSY_OR_ADVANCED]
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
{ this.renderExceptionWarning() }
|
{ this.renderExceptionWarning() }
|
||||||
{ this.renderStep() }
|
{ this.renderStep() }
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -161,8 +164,8 @@ class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
const { onClose, fromAddress } = this.props;
|
const { onClose, fromAddress } = this.props;
|
||||||
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
|
const { advancedOptions, sending, step, fromAddressError, valuesError } = this.state;
|
||||||
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
|
const hasError = fromAddressError || valuesError.find((error) => error);
|
||||||
|
|
||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
@ -170,10 +173,12 @@ class ExecuteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.button.cancel'
|
id='executeContract.button.cancel'
|
||||||
defaultMessage='cancel' />
|
defaultMessage='cancel'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
icon={ <CancelIcon /> }
|
icon={ <CancelIcon /> }
|
||||||
onClick={ onClose } />
|
onClick={ onClose }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
const postBtn = (
|
const postBtn = (
|
||||||
<Button
|
<Button
|
||||||
@ -181,11 +186,13 @@ class ExecuteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.button.post'
|
id='executeContract.button.post'
|
||||||
defaultMessage='post transaction' />
|
defaultMessage='post transaction'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
disabled={ !!(sending || hasError) }
|
disabled={ !!(sending || hasError) }
|
||||||
icon={ <IdentityIcon address={ fromAddress } button /> }
|
icon={ <IdentityIcon address={ fromAddress } button /> }
|
||||||
onClick={ this.postTransaction } />
|
onClick={ this.postTransaction }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
const nextBtn = (
|
const nextBtn = (
|
||||||
<Button
|
<Button
|
||||||
@ -193,10 +200,12 @@ class ExecuteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.button.next'
|
id='executeContract.button.next'
|
||||||
defaultMessage='next' />
|
defaultMessage='next'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
icon={ <NextIcon /> }
|
icon={ <NextIcon /> }
|
||||||
onClick={ this.onNextClick } />
|
onClick={ this.onNextClick }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
const prevBtn = (
|
const prevBtn = (
|
||||||
<Button
|
<Button
|
||||||
@ -204,10 +213,12 @@ class ExecuteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.button.prev'
|
id='executeContract.button.prev'
|
||||||
defaultMessage='prev' />
|
defaultMessage='prev'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
icon={ <PrevIcon /> }
|
icon={ <PrevIcon /> }
|
||||||
onClick={ this.onPrevClick } />
|
onClick={ this.onPrevClick }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (step === STEP_DETAILS) {
|
if (step === STEP_DETAILS) {
|
||||||
@ -233,16 +244,18 @@ class ExecuteContract extends Component {
|
|||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.button.done'
|
id='executeContract.button.done'
|
||||||
defaultMessage='done' />
|
defaultMessage='done'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
icon={ <DoneIcon /> }
|
icon={ <DoneIcon /> }
|
||||||
onClick={ onClose } />
|
onClick={ onClose }
|
||||||
|
/>
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { onFromAddressChange } = this.props;
|
const { onFromAddressChange } = this.props;
|
||||||
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
|
const { advancedOptions, step, busyState, txhash, rejected } = this.state;
|
||||||
|
|
||||||
if (rejected) {
|
if (rejected) {
|
||||||
return (
|
return (
|
||||||
@ -250,13 +263,16 @@ class ExecuteContract extends Component {
|
|||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.rejected.title'
|
id='executeContract.rejected.title'
|
||||||
defaultMessage='The execution has been rejected' />
|
defaultMessage='The execution has been rejected'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
state={
|
state={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.rejected.state'
|
id='executeContract.rejected.state'
|
||||||
defaultMessage='You can safely close this window, the function execution will not occur.' />
|
defaultMessage='You can safely close this window, the function execution will not occur.'
|
||||||
} />
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +285,8 @@ class ExecuteContract extends Component {
|
|||||||
onFromAddressChange={ onFromAddressChange }
|
onFromAddressChange={ onFromAddressChange }
|
||||||
onFuncChange={ this.onFuncChange }
|
onFuncChange={ this.onFuncChange }
|
||||||
onAdvancedClick={ this.onAdvancedClick }
|
onAdvancedClick={ this.onAdvancedClick }
|
||||||
onValueChange={ this.onValueChange } />
|
onValueChange={ this.onValueChange }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
|
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
|
||||||
return (
|
return (
|
||||||
@ -277,17 +294,15 @@ class ExecuteContract extends Component {
|
|||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.busy.title'
|
id='executeContract.busy.title'
|
||||||
defaultMessage='The function execution is in progress' />
|
defaultMessage='The function execution is in progress'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
state={ busyState } />
|
state={ busyState }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
||||||
return (
|
return (
|
||||||
<AdvancedStep
|
<AdvancedStep gasStore={ this.gasStore } />
|
||||||
gasStore={ this.gasStore }
|
|
||||||
minBlock={ minBlock }
|
|
||||||
minBlockError={ minBlockError }
|
|
||||||
onMinBlockChange={ this.onMinBlockChange } />
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,6 +321,7 @@ class ExecuteContract extends Component {
|
|||||||
onFuncChange = (event, func) => {
|
onFuncChange = (event, func) => {
|
||||||
const values = (func.abi.inputs || []).map((input) => {
|
const values = (func.abi.inputs || []).map((input) => {
|
||||||
const parsedType = parseAbiType(input.type);
|
const parsedType = parseAbiType(input.type);
|
||||||
|
|
||||||
return parsedType.default;
|
return parsedType.default;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,15 +331,6 @@ class ExecuteContract extends Component {
|
|||||||
}, this.estimateGas);
|
}, this.estimateGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMinBlockChange = (minBlock) => {
|
|
||||||
const minBlockError = validateUint(minBlock).valueError;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
minBlock,
|
|
||||||
minBlockError
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onValueChange = (event, index, _value) => {
|
onValueChange = (event, index, _value) => {
|
||||||
const { func, values, valuesError } = this.state;
|
const { func, values, valuesError } = this.state;
|
||||||
const input = func.inputs.find((input, _index) => index === _index);
|
const input = func.inputs.find((input, _index) => index === _index);
|
||||||
@ -385,17 +392,14 @@ class ExecuteContract extends Component {
|
|||||||
postTransaction = () => {
|
postTransaction = () => {
|
||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { fromAddress } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
const { advancedOptions, amount, func, values } = this.state;
|
||||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||||
const finalstep = steps.length - 1;
|
const finalstep = steps.length - 1;
|
||||||
|
|
||||||
const options = {
|
const options = this.gasStore.overrideTransaction({
|
||||||
gas: this.gasStore.gas,
|
|
||||||
gasPrice: this.gasStore.price,
|
|
||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
|
|
||||||
value: api.util.toWei(amount || 0)
|
value: api.util.toWei(amount || 0)
|
||||||
};
|
});
|
||||||
|
|
||||||
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
||||||
|
|
||||||
@ -406,7 +410,8 @@ class ExecuteContract extends Component {
|
|||||||
busyState: (
|
busyState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.busy.waitAuth'
|
id='executeContract.busy.waitAuth'
|
||||||
defaultMessage='Waiting for authorization in the Parity Signer' />
|
defaultMessage='Waiting for authorization in the Parity Signer'
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -429,7 +434,8 @@ class ExecuteContract extends Component {
|
|||||||
busyState: (
|
busyState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='executeContract.busy.posted'
|
id='executeContract.busy.posted'
|
||||||
defaultMessage='Your transaction has been posted to the network' />
|
defaultMessage='Your transaction has been posted to the network'
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,8 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { CopyToClipboard, QrCode } from '~/ui';
|
||||||
|
|
||||||
import Value from '../Value';
|
import Value from '../Value';
|
||||||
import styles from '../shapeshift.css';
|
import styles from '../shapeshift.css';
|
||||||
|
|
||||||
@ -59,9 +61,7 @@ export default class AwaitingDepositStep extends Component {
|
|||||||
typeSymbol
|
typeSymbol
|
||||||
} } />
|
} } />
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.hero }>
|
{ this.renderAddress(depositAddress, coinSymbol) }
|
||||||
{ depositAddress }
|
|
||||||
</div>
|
|
||||||
<div className={ styles.price }>
|
<div className={ styles.price }>
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -76,4 +76,42 @@ export default class AwaitingDepositStep extends Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderAddress (depositAddress, coinSymbol) {
|
||||||
|
const qrcode = (
|
||||||
|
<QrCode
|
||||||
|
className={ styles.qrcode }
|
||||||
|
value={ depositAddress }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
let protocolLink = null;
|
||||||
|
|
||||||
|
// TODO: Expand for other coins where protocols are available
|
||||||
|
switch (coinSymbol) {
|
||||||
|
case 'BTC':
|
||||||
|
protocolLink = `bitcoin:${depositAddress}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.addressInfo }>
|
||||||
|
{
|
||||||
|
protocolLink
|
||||||
|
? (
|
||||||
|
<a
|
||||||
|
href={ protocolLink }
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
{ qrcode }
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
: qrcode
|
||||||
|
}
|
||||||
|
<div className={ styles.address }>
|
||||||
|
<CopyToClipboard data={ depositAddress } />
|
||||||
|
<span>{ depositAddress }</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,10 @@ import React from 'react';
|
|||||||
|
|
||||||
import AwaitingDepositStep from './';
|
import AwaitingDepositStep from './';
|
||||||
|
|
||||||
|
const TEST_ADDRESS = '0x123456789123456789123456789123456789';
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
|
let instance;
|
||||||
|
|
||||||
function render () {
|
function render () {
|
||||||
component = shallow(
|
component = shallow(
|
||||||
@ -29,6 +32,7 @@ function render () {
|
|||||||
price: { rate: 0.001, minimum: 0, limit: 1.999 }
|
price: { rate: 0.001, minimum: 0, limit: 1.999 }
|
||||||
} } />
|
} } />
|
||||||
);
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@ -47,4 +51,61 @@ describe('modals/Shapeshift/AwaitingDepositStep', () => {
|
|||||||
render({ depositAddress: 'xyz' });
|
render({ depositAddress: 'xyz' });
|
||||||
expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/);
|
expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('renderAddress', () => {
|
||||||
|
let address;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
address = shallow(instance.renderAddress(TEST_ADDRESS));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the address', () => {
|
||||||
|
expect(address.text()).to.contain(TEST_ADDRESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CopyToClipboard', () => {
|
||||||
|
let copy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
copy = address.find('Connect(CopyToClipboard)');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the copy', () => {
|
||||||
|
expect(copy.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the address', () => {
|
||||||
|
expect(copy.props().data).to.equal(TEST_ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('QrCode', () => {
|
||||||
|
let qr;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
qr = address.find('QrCode');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the QrCode', () => {
|
||||||
|
expect(qr.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passed the address', () => {
|
||||||
|
expect(qr.props().value).to.equal(TEST_ADDRESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('protocol link', () => {
|
||||||
|
it('does not render a protocol link (unlinked type)', () => {
|
||||||
|
expect(address.find('a')).to.have.length(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders protocol link for BTC', () => {
|
||||||
|
address = shallow(instance.renderAddress(TEST_ADDRESS, 'BTC'));
|
||||||
|
expect(address.find('a').props().href).to.equal(`bitcoin:${TEST_ADDRESS}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,9 +14,28 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addressInfo {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.address {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
margin: 0.75em 0;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 0.75em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode {
|
||||||
|
margin: 0.75em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.shapeshift {
|
.shapeshift {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0.5em;
|
bottom: 0.5em;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -27,37 +27,22 @@ export default class Extras extends Component {
|
|||||||
dataError: PropTypes.string,
|
dataError: PropTypes.string,
|
||||||
gasStore: PropTypes.object.isRequired,
|
gasStore: PropTypes.object.isRequired,
|
||||||
isEth: PropTypes.bool,
|
isEth: PropTypes.bool,
|
||||||
minBlock: PropTypes.string,
|
|
||||||
minBlockError: PropTypes.string,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
total: PropTypes.string,
|
total: PropTypes.string,
|
||||||
totalError: PropTypes.string
|
totalError: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { gasStore, minBlock, minBlockError, onChange } = this.props;
|
const { gasStore, onChange } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{ this.renderData() }
|
{ this.renderData() }
|
||||||
<Input
|
|
||||||
error={ minBlockError }
|
|
||||||
hint={
|
|
||||||
<FormattedMessage
|
|
||||||
id='transferModal.minBlock.hint'
|
|
||||||
defaultMessage='Only post the transaction after this block' />
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='transferModal.minBlock.label'
|
|
||||||
defaultMessage='BlockNumber to send from' />
|
|
||||||
}
|
|
||||||
value={ minBlock }
|
|
||||||
onChange={ this.onEditMinBlock } />
|
|
||||||
<div className={ styles.gaseditor }>
|
<div className={ styles.gaseditor }>
|
||||||
<GasPriceEditor
|
<GasPriceEditor
|
||||||
store={ gasStore }
|
store={ gasStore }
|
||||||
onChange={ onChange } />
|
onChange={ onChange }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
@ -76,23 +61,22 @@ export default class Extras extends Component {
|
|||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='transfer.advanced.data.hint'
|
id='transfer.advanced.data.hint'
|
||||||
defaultMessage='the data to pass through with the transaction' />
|
defaultMessage='the data to pass through with the transaction'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='transfer.advanced.data.label'
|
id='transfer.advanced.data.label'
|
||||||
defaultMessage='transaction data' />
|
defaultMessage='transaction data'
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
onChange={ this.onEditData }
|
onChange={ this.onEditData }
|
||||||
value={ data } />
|
value={ data }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditData = (event) => {
|
onEditData = (event) => {
|
||||||
this.props.onChange('data', event.target.value);
|
this.props.onChange('data', event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditMinBlock = (event) => {
|
|
||||||
this.props.onChange('minBlock', event.target.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
// This file is part of Parity.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -52,9 +52,6 @@ export default class TransferStore {
|
|||||||
@observable data = '';
|
@observable data = '';
|
||||||
@observable dataError = null;
|
@observable dataError = null;
|
||||||
|
|
||||||
@observable minBlock = '0';
|
|
||||||
@observable minBlockError = null;
|
|
||||||
|
|
||||||
@observable recipient = '';
|
@observable recipient = '';
|
||||||
@observable recipientError = ERRORS.requireRecipient;
|
@observable recipientError = ERRORS.requireRecipient;
|
||||||
|
|
||||||
@ -78,6 +75,30 @@ export default class TransferStore {
|
|||||||
|
|
||||||
gasStore = null;
|
gasStore = null;
|
||||||
|
|
||||||
|
constructor (api, props) {
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
const { account, balance, gasLimit, senders, newError, sendersBalances } = props;
|
||||||
|
|
||||||
|
this.account = account;
|
||||||
|
this.balance = balance;
|
||||||
|
this.isWallet = account && account.wallet;
|
||||||
|
this.newError = newError;
|
||||||
|
|
||||||
|
this.gasStore = new GasPriceStore(api, { gasLimit });
|
||||||
|
|
||||||
|
if (this.isWallet) {
|
||||||
|
this.wallet = props.wallet;
|
||||||
|
this.walletContract = new Contract(this.api, walletAbi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senders) {
|
||||||
|
this.senders = senders;
|
||||||
|
this.sendersBalances = sendersBalances;
|
||||||
|
this.senderError = ERRORS.requireSender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@computed get steps () {
|
@computed get steps () {
|
||||||
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
||||||
|
|
||||||
@ -90,7 +111,7 @@ export default class TransferStore {
|
|||||||
|
|
||||||
@computed get isValid () {
|
@computed get isValid () {
|
||||||
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
||||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
|
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.gasStore.conditionBlockError && !this.totalError;
|
||||||
const verifyValid = !this.passwordError;
|
const verifyValid = !this.passwordError;
|
||||||
|
|
||||||
switch (this.stage) {
|
switch (this.stage) {
|
||||||
@ -111,29 +132,6 @@ export default class TransferStore {
|
|||||||
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
|
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (api, props) {
|
|
||||||
this.api = api;
|
|
||||||
|
|
||||||
const { account, balance, gasLimit, senders, newError, sendersBalances } = props;
|
|
||||||
this.account = account;
|
|
||||||
this.balance = balance;
|
|
||||||
this.isWallet = account && account.wallet;
|
|
||||||
this.newError = newError;
|
|
||||||
|
|
||||||
this.gasStore = new GasPriceStore(api, { gasLimit });
|
|
||||||
|
|
||||||
if (this.isWallet) {
|
|
||||||
this.wallet = props.wallet;
|
|
||||||
this.walletContract = new Contract(this.api, walletAbi);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (senders) {
|
|
||||||
this.senders = senders;
|
|
||||||
this.sendersBalances = sendersBalances;
|
|
||||||
this.senderError = ERRORS.requireSender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action onNext = () => {
|
@action onNext = () => {
|
||||||
this.stage += 1;
|
this.stage += 1;
|
||||||
}
|
}
|
||||||
@ -163,9 +161,6 @@ export default class TransferStore {
|
|||||||
case 'gasPrice':
|
case 'gasPrice':
|
||||||
return this._onUpdateGasPrice(value);
|
return this._onUpdateGasPrice(value);
|
||||||
|
|
||||||
case 'minBlock':
|
|
||||||
return this._onUpdateMinBlock(value);
|
|
||||||
|
|
||||||
case 'recipient':
|
case 'recipient':
|
||||||
return this._onUpdateRecipient(value);
|
return this._onUpdateRecipient(value);
|
||||||
|
|
||||||
@ -283,14 +278,6 @@ export default class TransferStore {
|
|||||||
this.recalculate();
|
this.recalculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action _onUpdateMinBlock = (minBlock) => {
|
|
||||||
console.log('minBlock', minBlock);
|
|
||||||
transaction(() => {
|
|
||||||
this.minBlock = minBlock;
|
|
||||||
this.minBlockError = this._validatePositiveNumber(minBlock);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@action _onUpdateGasPrice = (gasPrice) => {
|
@action _onUpdateGasPrice = (gasPrice) => {
|
||||||
this.recalculate();
|
this.recalculate();
|
||||||
}
|
}
|
||||||
@ -588,7 +575,7 @@ export default class TransferStore {
|
|||||||
|
|
||||||
send () {
|
send () {
|
||||||
const { options, values } = this._getTransferParams();
|
const { options, values } = this._getTransferParams();
|
||||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
|
||||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||||
|
|
||||||
return this._getTransferMethod().postTransaction(options, values);
|
return this._getTransferMethod().postTransaction(options, values);
|
||||||
@ -596,6 +583,7 @@ export default class TransferStore {
|
|||||||
|
|
||||||
_estimateGas (forceToken = false) {
|
_estimateGas (forceToken = false) {
|
||||||
const { options, values } = this._getTransferParams(true, forceToken);
|
const { options, values } = this._getTransferParams(true, forceToken);
|
||||||
|
|
||||||
return this._getTransferMethod(true, forceToken).estimateGas(options, values);
|
return this._getTransferMethod(true, forceToken).estimateGas(options, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,15 +624,12 @@ export default class TransferStore {
|
|||||||
const to = (isEth && !isWallet) ? this.recipient
|
const to = (isEth && !isWallet) ? this.recipient
|
||||||
: (this.isWallet ? this.wallet.address : this.token.address);
|
: (this.isWallet ? this.wallet.address : this.token.address);
|
||||||
|
|
||||||
const options = {
|
const options = this.gasStore.overrideTransaction({
|
||||||
from: this.sender || this.account.address,
|
from: this.sender || this.account.address,
|
||||||
to
|
to
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!gas) {
|
if (gas) {
|
||||||
options.gas = this.gasStore.gas;
|
|
||||||
options.gasPrice = this.gasStore.price;
|
|
||||||
} else {
|
|
||||||
options.gas = MAX_GAS_ESTIMATION;
|
options.gas = MAX_GAS_ESTIMATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -681,6 +666,7 @@ export default class TransferStore {
|
|||||||
_validatePositiveNumber (num) {
|
_validatePositiveNumber (num) {
|
||||||
try {
|
try {
|
||||||
const v = new BigNumber(num);
|
const v = new BigNumber(num);
|
||||||
|
|
||||||
if (v.lt(0)) {
|
if (v.lt(0)) {
|
||||||
return ERRORS.invalidAmount;
|
return ERRORS.invalidAmount;
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ class Transfer extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
|
const { isEth, data, dataError, total, totalError } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Extras
|
<Extras
|
||||||
@ -214,8 +214,6 @@ class Transfer extends Component {
|
|||||||
dataError={ dataError }
|
dataError={ dataError }
|
||||||
gasStore={ this.store.gasStore }
|
gasStore={ this.store.gasStore }
|
||||||
isEth={ isEth }
|
isEth={ isEth }
|
||||||
minBlock={ minBlock }
|
|
||||||
minBlockError={ minBlockError }
|
|
||||||
onChange={ this.store.onUpdateDetails }
|
onChange={ this.store.onUpdateDetails }
|
||||||
total={ total }
|
total={ total }
|
||||||
totalError={ totalError } />
|
totalError={ totalError } />
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
import AddAddress from './AddAddress';
|
import AddAddress from './AddAddress';
|
||||||
import AddContract from './AddContract';
|
import AddContract from './AddContract';
|
||||||
import AddDapps from './AddDapps';
|
|
||||||
import CreateAccount from './CreateAccount';
|
import CreateAccount from './CreateAccount';
|
||||||
import CreateWallet from './CreateWallet';
|
import CreateWallet from './CreateWallet';
|
||||||
import DappPermissions from './DappPermissions';
|
import DappPermissions from './DappPermissions';
|
||||||
|
import DappsVisible from './AddDapps';
|
||||||
import DeleteAccount from './DeleteAccount';
|
import DeleteAccount from './DeleteAccount';
|
||||||
import DeployContract from './DeployContract';
|
import DeployContract from './DeployContract';
|
||||||
import EditMeta from './EditMeta';
|
import EditMeta from './EditMeta';
|
||||||
@ -37,10 +37,10 @@ import WalletSettings from './WalletSettings';
|
|||||||
export {
|
export {
|
||||||
AddAddress,
|
AddAddress,
|
||||||
AddContract,
|
AddContract,
|
||||||
AddDapps,
|
|
||||||
CreateAccount,
|
CreateAccount,
|
||||||
CreateWallet,
|
CreateWallet,
|
||||||
DappPermissions,
|
DappPermissions,
|
||||||
|
DappsVisible,
|
||||||
DeleteAccount,
|
DeleteAccount,
|
||||||
DeployContract,
|
DeployContract,
|
||||||
EditMeta,
|
EditMeta,
|
||||||
|
17
js/src/playground/index.js
Normal file
17
js/src/playground/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 './playground';
|
90
js/src/playground/playground.css
Normal file
90
js/src/playground/playground.css
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$codeBackground: #002b36;
|
||||||
|
$codeColor: #93a1a1;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.examples {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.25em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-family: monospace;
|
||||||
|
display: inline-block;
|
||||||
|
height: 1.5em;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
color: #555;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.exampleContainer {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.25em;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.example {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.code {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #$codeBackground;
|
||||||
|
color: $codeColor;
|
||||||
|
font-size: 0.75em;
|
||||||
|
|
||||||
|
code {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.component {
|
||||||
|
flex: 3;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
90
js/src/playground/playground.js
Normal file
90
js/src/playground/playground.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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 { observer } from 'mobx-react';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example';
|
||||||
|
import QrCode from '~/ui/QrCode/qrCode.example';
|
||||||
|
import SectionList from '~/ui/SectionList/sectionList.example';
|
||||||
|
import Portal from '~/ui/Portal/portal.example';
|
||||||
|
|
||||||
|
import PlaygroundStore from './store';
|
||||||
|
import styles from './playground.css';
|
||||||
|
|
||||||
|
PlaygroundStore.register(<CurrencySymbol />);
|
||||||
|
PlaygroundStore.register(<QrCode />);
|
||||||
|
PlaygroundStore.register(<SectionList />);
|
||||||
|
PlaygroundStore.register(<Portal />);
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Playground extends Component {
|
||||||
|
state = {
|
||||||
|
selectedIndex: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
store = PlaygroundStore.get();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className={ styles.container }>
|
||||||
|
<div className={ styles.title }>
|
||||||
|
<span>Playground > </span>
|
||||||
|
<select
|
||||||
|
className={ styles.select }
|
||||||
|
onChange={ this.handleChange }
|
||||||
|
>
|
||||||
|
{ this.renderOptions() }
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={ styles.examples }>
|
||||||
|
{ this.renderComponent() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOptions () {
|
||||||
|
const { components } = this.store;
|
||||||
|
|
||||||
|
return components.map((element, index) => {
|
||||||
|
const name = element.type.displayName || element.type.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={ `${name}_${index}` }
|
||||||
|
value={ index }
|
||||||
|
>
|
||||||
|
{ name }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComponent () {
|
||||||
|
const { components } = this.store;
|
||||||
|
const { selectedIndex } = this.state;
|
||||||
|
|
||||||
|
return components[selectedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
this.setState({ selectedIndex: value });
|
||||||
|
}
|
||||||
|
}
|
47
js/src/playground/playground.spec.js
Normal file
47
js/src/playground/playground.spec.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 Playground from './playground';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let options;
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
component = shallow(
|
||||||
|
<Playground />
|
||||||
|
);
|
||||||
|
|
||||||
|
options = component.find('option');
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('playground', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders multiple options', () => {
|
||||||
|
expect(options.length).to.be.greaterThan(2);
|
||||||
|
});
|
||||||
|
});
|
55
js/src/playground/playgroundExample.js
Normal file
55
js/src/playground/playgroundExample.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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 reactElementToJSXString from 'react-element-to-jsx-string';
|
||||||
|
|
||||||
|
import styles from './playground.css';
|
||||||
|
|
||||||
|
export default class PlaygroundExample extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
name: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, name } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.exampleContainer }>
|
||||||
|
{ this.renderName(name) }
|
||||||
|
<div className={ styles.example }>
|
||||||
|
<div className={ styles.code }>
|
||||||
|
<code>{ reactElementToJSXString(children) }</code>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.component }>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderName (name) {
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p>{ name }</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
51
js/src/playground/store.js
Normal file
51
js/src/playground/store.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 { action, observable } from 'mobx';
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
export default class PlaygroundStore {
|
||||||
|
@observable components = [];
|
||||||
|
|
||||||
|
static get () {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new PlaygroundStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static register (component) {
|
||||||
|
PlaygroundStore.get().add(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
add (component) {
|
||||||
|
const name = component.type.displayName || component.type.name;
|
||||||
|
const hasComponent = this.components.find((c) => {
|
||||||
|
const cName = c.type.displayName || c.type.name;
|
||||||
|
|
||||||
|
return name && cName && cName === name;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.components.push(component);
|
||||||
|
}
|
||||||
|
}
|
41
js/src/playground/store.spec.js
Normal file
41
js/src/playground/store.spec.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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 from 'react';
|
||||||
|
|
||||||
|
import QrCode from '~/ui/QrCode/qrCode.example';
|
||||||
|
|
||||||
|
import PlaygroundStore from './store';
|
||||||
|
|
||||||
|
describe('playground/store', () => {
|
||||||
|
let store = PlaygroundStore.get();
|
||||||
|
|
||||||
|
it('is available', () => {
|
||||||
|
expect(PlaygroundStore.get()).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds new Components', () => {
|
||||||
|
PlaygroundStore.register(<QrCode />);
|
||||||
|
expect(store.components.length).greaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds new Components only once', () => {
|
||||||
|
PlaygroundStore.register(<QrCode />);
|
||||||
|
PlaygroundStore.register(<QrCode />);
|
||||||
|
|
||||||
|
expect(store.components.filter((c) => /QrCode/i.test(c.type.name)).length).equal(1);
|
||||||
|
});
|
||||||
|
});
|
@ -52,7 +52,7 @@ export default class SignerMiddleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onConfirmStart = (store, action) => {
|
onConfirmStart = (store, action) => {
|
||||||
const { gas, gasPrice, id, password, payload, wallet } = action.payload;
|
const { condition, gas = 0, gasPrice = 0, id, password, payload, wallet } = action.payload;
|
||||||
|
|
||||||
const handlePromise = (promise) => {
|
const handlePromise = (promise) => {
|
||||||
promise
|
promise
|
||||||
@ -119,7 +119,7 @@ export default class SignerMiddleware {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password));
|
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice, condition }, password));
|
||||||
}
|
}
|
||||||
|
|
||||||
onRejectStart = (store, action) => {
|
onRejectStart = (store, action) => {
|
||||||
|
@ -78,45 +78,57 @@ const routes = [
|
|||||||
|
|
||||||
{ path: '/', onEnter: redirectTo('/accounts') },
|
{ path: '/', onEnter: redirectTo('/accounts') },
|
||||||
{ path: '/auth', onEnter: redirectTo('/accounts') },
|
{ path: '/auth', onEnter: redirectTo('/accounts') },
|
||||||
{ path: '/settings', onEnter: redirectTo('/settings/views') },
|
{ path: '/settings', onEnter: redirectTo('/settings/views') }
|
||||||
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: Application,
|
|
||||||
childRoutes: [
|
|
||||||
{
|
|
||||||
path: 'accounts',
|
|
||||||
indexRoute: { component: Accounts },
|
|
||||||
childRoutes: accountsRoutes
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'addresses',
|
|
||||||
indexRoute: { component: Addresses },
|
|
||||||
childRoutes: addressesRoutes
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'contracts',
|
|
||||||
indexRoute: { component: Contracts },
|
|
||||||
childRoutes: contractsRoutes
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'status',
|
|
||||||
indexRoute: { component: Status },
|
|
||||||
childRoutes: statusRoutes
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'settings',
|
|
||||||
component: Settings,
|
|
||||||
childRoutes: settingsRoutes
|
|
||||||
},
|
|
||||||
|
|
||||||
{ path: 'apps', component: Dapps },
|
|
||||||
{ path: 'app/:id', component: Dapp },
|
|
||||||
{ path: 'web', component: Web },
|
|
||||||
{ path: 'web/:url', component: Web },
|
|
||||||
{ path: 'signer', component: Signer }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const appRoutes = [
|
||||||
|
{
|
||||||
|
path: 'accounts',
|
||||||
|
indexRoute: { component: Accounts },
|
||||||
|
childRoutes: accountsRoutes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'addresses',
|
||||||
|
indexRoute: { component: Addresses },
|
||||||
|
childRoutes: addressesRoutes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'contracts',
|
||||||
|
indexRoute: { component: Contracts },
|
||||||
|
childRoutes: contractsRoutes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'status',
|
||||||
|
indexRoute: { component: Status },
|
||||||
|
childRoutes: statusRoutes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
component: Settings,
|
||||||
|
childRoutes: settingsRoutes
|
||||||
|
},
|
||||||
|
|
||||||
|
{ path: 'apps', component: Dapps },
|
||||||
|
{ path: 'app/:id', component: Dapp },
|
||||||
|
{ path: 'web', component: Web },
|
||||||
|
{ path: 'web/:url', component: Web },
|
||||||
|
{ path: 'signer', component: Signer }
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO : use ES6 imports when supported
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const Playground = require('./playground').default;
|
||||||
|
|
||||||
|
appRoutes.push({
|
||||||
|
path: 'playground',
|
||||||
|
component: Playground
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.push({
|
||||||
|
path: '/',
|
||||||
|
component: Application,
|
||||||
|
childRoutes: appRoutes
|
||||||
|
});
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
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 {
|
.description {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
@ -86,14 +93,10 @@
|
|||||||
.accountName {
|
.accountName {
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance {
|
.balance {
|
||||||
.tag {
|
margin-top: 0;
|
||||||
margin-left: 0.5em;
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes copied {
|
@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.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// 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 ReactDOM from 'react-dom';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
|
|
||||||
|
import Balance from '~/ui/Balance';
|
||||||
import IdentityIcon from '~/ui/IdentityIcon';
|
import IdentityIcon from '~/ui/IdentityIcon';
|
||||||
|
import IdentityName from '~/ui/IdentityName';
|
||||||
import Tags from '~/ui/Tags';
|
import Tags from '~/ui/Tags';
|
||||||
|
|
||||||
import { fromWei } from '~/api/util/wei';
|
|
||||||
|
|
||||||
import styles from './accountCard.css';
|
import styles from './accountCard.css';
|
||||||
|
|
||||||
export default class AccountCard extends Component {
|
export default class AccountCard extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
balance: PropTypes.object,
|
||||||
onFocus: PropTypes.func.isRequired,
|
className: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
balance: PropTypes.object
|
onFocus: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -40,15 +39,11 @@ export default class AccountCard extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account } = this.props;
|
const { account, balance, className } = this.props;
|
||||||
const { copied } = this.state;
|
const { copied } = this.state;
|
||||||
|
const { address, description, meta = {}, name } = account;
|
||||||
const { address, name, description, meta = {} } = account;
|
|
||||||
|
|
||||||
const displayName = (name && name.toUpperCase()) || address;
|
|
||||||
const { tags = [] } = meta;
|
const { tags = [] } = meta;
|
||||||
|
const classes = [ styles.account, className ];
|
||||||
const classes = [ styles.account ];
|
|
||||||
|
|
||||||
if (copied) {
|
if (copied) {
|
||||||
classes.push(styles.copied);
|
classes.push(styles.copied);
|
||||||
@ -63,17 +58,28 @@ export default class AccountCard extends Component {
|
|||||||
onFocus={ this.onFocus }
|
onFocus={ this.onFocus }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
>
|
>
|
||||||
<IdentityIcon address={ address } />
|
<div className={ styles.infoContainer }>
|
||||||
<div className={ styles.accountInfo }>
|
<IdentityIcon address={ address } />
|
||||||
<div className={ styles.accountName }>
|
<div className={ styles.accountInfo }>
|
||||||
<span>{ displayName }</span>
|
<div className={ styles.accountName }>
|
||||||
|
<IdentityName
|
||||||
|
address={ address }
|
||||||
|
name={ name }
|
||||||
|
unknown
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ this.renderDescription(description) }
|
||||||
|
{ this.renderAddress(address) }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ this.renderTags(tags, address) }
|
|
||||||
{ this.renderDescription(description) }
|
|
||||||
{ this.renderAddress(displayName, address) }
|
|
||||||
{ this.renderBalance(address) }
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Tags tags={ tags } />
|
||||||
|
<Balance
|
||||||
|
balance={ balance }
|
||||||
|
className={ styles.balance }
|
||||||
|
showOnlyEth
|
||||||
|
showZeroValues
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -90,11 +96,7 @@ export default class AccountCard extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAddress (name, address) {
|
renderAddress (address) {
|
||||||
if (name === address) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.addressContainer }>
|
<div className={ styles.addressContainer }>
|
||||||
<span
|
<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) => {
|
handleKeyDown = (event) => {
|
||||||
const codeName = keycode(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
|
// @see https://developers.google.com/web/updates/2015/04/cut-and-copy-commands
|
||||||
try {
|
try {
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
|
|
||||||
range.selectNode(element);
|
range.selectNode(element);
|
||||||
window.getSelection().addRange(range);
|
window.getSelection().addRange(range);
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
@ -184,12 +153,14 @@ export default class AccountCard extends Component {
|
|||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
const { account, onClick } = this.props;
|
const { account, onClick } = this.props;
|
||||||
onClick(account.address);
|
|
||||||
|
onClick && onClick(account.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocus = () => {
|
onFocus = () => {
|
||||||
const { account, onFocus } = this.props;
|
const { account, onFocus } = this.props;
|
||||||
onFocus(account.index);
|
|
||||||
|
onFocus && onFocus(account.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
preventEvent = (e) => {
|
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 BigNumber from 'bignumber.js';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
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';
|
import styles from './balance.css';
|
||||||
|
|
||||||
class Balance extends Component {
|
class Balance extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object
|
api: PropTypes.object
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balance: PropTypes.object,
|
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 () {
|
render () {
|
||||||
const { api } = this.context;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = (balance.tokens || [])
|
let body = balance.tokens
|
||||||
.filter((balance) => new BigNumber(balance.value).gt(0))
|
.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) => {
|
.map((balance, index) => {
|
||||||
const token = balance.token;
|
const token = balance.token;
|
||||||
|
|
||||||
@ -92,13 +107,16 @@ class Balance extends Component {
|
|||||||
if (!body.length) {
|
if (!body.length) {
|
||||||
body = (
|
body = (
|
||||||
<div className={ styles.empty }>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.balances }>
|
<div className={ [styles.balances, className].join(' ') }>
|
||||||
{ body }
|
{ body }
|
||||||
</div>
|
</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
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
.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;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: 1.2em;
|
|
||||||
max-height: 2.4em;
|
|
||||||
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
|
|
||||||
color: #aaa;
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
color: #aaa !important;
|
color: $bylineColor !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
font-size: 0.75em;
|
font-size: $smallFontSize;
|
||||||
margin: 0.5em 0 0;
|
margin: 0.5em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-transform: uppercase;
|
line-height: $titleLineHeight;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 34px;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
@ -29,29 +29,41 @@ export default class Title extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { byline, className, title } = this.props;
|
const { className, title } = this.props;
|
||||||
|
|
||||||
const byLine = typeof byline === 'string'
|
|
||||||
? (
|
|
||||||
<span title={ byline }>
|
|
||||||
{ byline }
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: byline;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ className }>
|
<div className={ className }>
|
||||||
<h3 className={ styles.title }>
|
<h3 className={ styles.title }>
|
||||||
{ title }
|
{ title }
|
||||||
</h3>
|
</h3>
|
||||||
<div className={ styles.byline }>
|
{ this.renderByline() }
|
||||||
{ byLine }
|
|
||||||
</div>
|
|
||||||
{ this.renderDescription() }
|
{ this.renderDescription() }
|
||||||
</div>
|
</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 () {
|
renderDescription () {
|
||||||
const { description } = this.props;
|
const { description } = this.props;
|
||||||
|
|
||||||
@ -59,17 +71,17 @@ export default class Title extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const desc = typeof description === 'string'
|
|
||||||
? (
|
|
||||||
<span title={ description }>
|
|
||||||
{ description }
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: description;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.description }>
|
<div className={ styles.description }>
|
||||||
{ desc }
|
{
|
||||||
|
typeof description === 'string'
|
||||||
|
? (
|
||||||
|
<span title={ description }>
|
||||||
|
{ description }
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
: description
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,15 +29,14 @@ export default class Container extends Component {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
compact: PropTypes.bool,
|
compact: PropTypes.bool,
|
||||||
light: PropTypes.bool,
|
light: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
tabIndex: PropTypes.number,
|
tabIndex: PropTypes.number,
|
||||||
title: nodeOrStringProptype()
|
title: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, className, compact, light, style, tabIndex } = this.props;
|
const { children, className, compact, light, onClick, style, tabIndex } = this.props;
|
||||||
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
|
|
||||||
|
|
||||||
const props = {};
|
const props = {};
|
||||||
|
|
||||||
if (Number.isInteger(tabIndex)) {
|
if (Number.isInteger(tabIndex)) {
|
||||||
@ -45,8 +44,27 @@ export default class Container extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ classes } style={ style } { ...props }>
|
<div
|
||||||
<Card className={ compact ? styles.compact : styles.padded }>
|
className={
|
||||||
|
[
|
||||||
|
styles.container,
|
||||||
|
light
|
||||||
|
? styles.light
|
||||||
|
: '',
|
||||||
|
className
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
style={ style }
|
||||||
|
{ ...props }
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
className={
|
||||||
|
compact
|
||||||
|
? styles.compact
|
||||||
|
: styles.padded
|
||||||
|
}
|
||||||
|
onClick={ onClick }
|
||||||
|
>
|
||||||
{ this.renderTitle() }
|
{ this.renderTitle() }
|
||||||
{ children }
|
{ children }
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -14,21 +14,21 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { IconButton } from 'material-ui';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import Clipboard from 'react-copy-to-clipboard';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from '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';
|
import { showSnackbar } from '~/redux/providers/snackbarActions';
|
||||||
|
|
||||||
const { textColor, disabledTextColor } = Theme.flatButton;
|
import { CopyIcon } from '../Icons';
|
||||||
|
import Theme from '../Theme';
|
||||||
|
|
||||||
import styles from './copyToClipboard.css';
|
import styles from './copyToClipboard.css';
|
||||||
|
|
||||||
|
const { textColor, disabledTextColor } = Theme.flatButton;
|
||||||
|
|
||||||
class CopyToClipboard extends Component {
|
class CopyToClipboard extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
showSnackbar: PropTypes.func.isRequired,
|
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';
|
@ -16,17 +16,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
|
left: 1.5em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1.5em;
|
top: 1.5em;
|
||||||
left: 1.5em;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
@ -17,74 +17,80 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import { Container, ContainerTitle, Tags } from '~/ui';
|
import Container, { Title as ContainerTitle } from '~/ui/Container';
|
||||||
|
import DappIcon from '~/ui/DappIcon';
|
||||||
|
import Tags from '~/ui/Tags';
|
||||||
|
|
||||||
import styles from './summary.css';
|
import styles from './dappCard.css';
|
||||||
|
|
||||||
export default class Summary extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
api: React.PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class DappCard extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
app: PropTypes.object.isRequired,
|
app: PropTypes.object.isRequired,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
}
|
className: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
showLink: PropTypes.bool,
|
||||||
|
showTags: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
showLink: false,
|
||||||
|
showTags: false
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { dappsUrl } = this.context.api;
|
const { app, children, className, onClick, showLink, showTags } = this.props;
|
||||||
const { app } = this.props;
|
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = this.renderImage(dappsUrl, app);
|
|
||||||
const link = this.renderLink(app);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={ styles.container }>
|
<Container
|
||||||
{ image }
|
className={
|
||||||
<Tags tags={ [app.type] } />
|
[styles.container, className].join(' ')
|
||||||
|
}
|
||||||
|
onClick={ onClick }
|
||||||
|
>
|
||||||
|
<DappIcon
|
||||||
|
app={ app }
|
||||||
|
className={ styles.image }
|
||||||
|
/>
|
||||||
|
<Tags
|
||||||
|
tags={
|
||||||
|
showTags
|
||||||
|
? [app.type]
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
<div className={ styles.description }>
|
<div className={ styles.description }>
|
||||||
<ContainerTitle
|
<ContainerTitle
|
||||||
className={ styles.title }
|
className={ styles.title }
|
||||||
title={ link }
|
title={
|
||||||
|
showLink
|
||||||
|
? this.renderLink(app)
|
||||||
|
: app.name
|
||||||
|
}
|
||||||
byline={ app.description }
|
byline={ app.description }
|
||||||
/>
|
/>
|
||||||
<div className={ styles.author }>
|
<div className={ styles.author }>
|
||||||
{ app.author }, v{ app.version }
|
{ app.author }, v{ app.version }
|
||||||
</div>
|
</div>
|
||||||
{ this.props.children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderImage (dappsUrl, app) {
|
|
||||||
if (app.type === 'local') {
|
|
||||||
return (
|
|
||||||
<img src={ `${dappsUrl}/${app.id}/${app.iconUrl}` } className={ styles.image } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img src={ `${dappsUrl}${app.image}` } className={ styles.image } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLink (app) {
|
renderLink (app) {
|
||||||
// Special case for web dapp
|
|
||||||
if (app.url === 'web') {
|
|
||||||
return (
|
|
||||||
<Link to={ `/web` }>
|
|
||||||
{ app.name }
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={ `/app/${app.id}` }>
|
<Link
|
||||||
|
to={
|
||||||
|
app.url === 'web'
|
||||||
|
? '/web'
|
||||||
|
: `/app/${app.id}`
|
||||||
|
}
|
||||||
|
>
|
||||||
{ app.name }
|
{ app.name }
|
||||||
</Link>
|
</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 {
|
.label {
|
||||||
margin: 1rem 0.5rem 0.25em;
|
margin: 1rem 0.5rem 0.25em;
|
||||||
color: rgba(255, 255, 255, 0.498039);
|
color: rgba(255, 255, 255, 0.498039);
|
||||||
@ -102,14 +108,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.categories {
|
.categories {
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
margin: 2rem 0 0;
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
@ -176,37 +176,42 @@ class AddressSelect extends Component {
|
|||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
className={ styles.inputContainer }
|
className={ styles.inputContainer }
|
||||||
|
isChildModal
|
||||||
onClose={ this.handleClose }
|
onClose={ this.handleClose }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
open={ expanded }
|
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() }
|
{ this.renderAccounts() }
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
|
@ -80,6 +80,9 @@ class InputAddress extends Component {
|
|||||||
props.focused = focused;
|
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 (
|
return (
|
||||||
<div className={ containerClasses.join(' ') }>
|
<div className={ containerClasses.join(' ') }>
|
||||||
<Input
|
<Input
|
||||||
@ -98,7 +101,7 @@ class InputAddress extends Component {
|
|||||||
tabIndex={ tabIndex }
|
tabIndex={ tabIndex }
|
||||||
value={
|
value={
|
||||||
text && account
|
text && account
|
||||||
? account.name
|
? (account.name || (value ? 'UNNAMED' : value))
|
||||||
: (nullName || value)
|
: (nullName || value)
|
||||||
}
|
}
|
||||||
{ ...props }
|
{ ...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/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.spaced {
|
.container {
|
||||||
margin: 0.25em 0;
|
.label {
|
||||||
}
|
}
|
||||||
|
|
||||||
.typeContainer {
|
.radioButton {
|
||||||
display: flex;
|
margin: 0.25em 0;
|
||||||
flex-direction: column;
|
}
|
||||||
|
|
||||||
.desc {
|
.radioLabel {
|
||||||
font-size: 0.8em;
|
display: flex;
|
||||||
margin-bottom: 0.5em;
|
flex-direction: column;
|
||||||
color: #ccc;
|
|
||||||
z-index: 2;
|
.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.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// 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 React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { arrayOrObjectProptype } from '~/util/proptypes';
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
|
import Label from '../Label';
|
||||||
import styles from './radioButtons.css';
|
import styles from './radioButtons.css';
|
||||||
|
|
||||||
export default class RadioButtons extends Component {
|
export default class RadioButtons extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
label: PropTypes.node,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
@ -34,10 +38,10 @@ export default class RadioButtons extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, values } = this.props;
|
const { className, label, value, values } = this.props;
|
||||||
|
|
||||||
const index = Number.isNaN(parseInt(value))
|
const index = Number.isNaN(parseInt(value))
|
||||||
? values.findIndex((val) => val.key === value)
|
? values.findIndex((_value) => _value.key === value)
|
||||||
: parseInt(value);
|
: parseInt(value);
|
||||||
const selectedValue = typeof value !== 'object'
|
const selectedValue = typeof value !== 'object'
|
||||||
? values[index]
|
? values[index]
|
||||||
@ -45,12 +49,19 @@ export default class RadioButtons extends Component {
|
|||||||
const key = this.getKey(selectedValue, index);
|
const key = this.getKey(selectedValue, index);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButtonGroup
|
<div className={ [styles.container, className].join(' ') }>
|
||||||
name={ name }
|
<Label
|
||||||
onChange={ this.onChange }
|
className={ styles.label }
|
||||||
valueSelected={ key } >
|
label={ label }
|
||||||
{ this.renderContent() }
|
/>
|
||||||
</RadioButtonGroup>
|
<RadioButtonGroup
|
||||||
|
name={ name }
|
||||||
|
onChange={ this.onChange }
|
||||||
|
valueSelected={ key }
|
||||||
|
>
|
||||||
|
{ this.renderContent() }
|
||||||
|
</RadioButtonGroup>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,19 +77,20 @@ export default class RadioButtons extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButton
|
<RadioButton
|
||||||
className={ styles.spaced }
|
className={ styles.radioButton }
|
||||||
key={ index }
|
key={ index }
|
||||||
label={
|
label={
|
||||||
<div className={ styles.typeContainer }>
|
<div className={ styles.radioLabel }>
|
||||||
<span>{ label }</span>
|
<span>{ label }</span>
|
||||||
{
|
{
|
||||||
description
|
description
|
||||||
? <span className={ styles.desc }>{ description }</span>
|
? <span className={ styles.description }>{ description }</span>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={ key } />
|
value={ key }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -95,7 +107,7 @@ export default class RadioButtons extends Component {
|
|||||||
|
|
||||||
onChange = (event, index) => {
|
onChange = (event, index) => {
|
||||||
const { onChange, values } = this.props;
|
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);
|
onChange(value, index);
|
||||||
}
|
}
|
||||||
|
@ -16,25 +16,31 @@
|
|||||||
|
|
||||||
import AddressSelect from './AddressSelect';
|
import AddressSelect from './AddressSelect';
|
||||||
import FormWrap from './FormWrap';
|
import FormWrap from './FormWrap';
|
||||||
import TypedInput from './TypedInput';
|
|
||||||
import Input from './Input';
|
import Input from './Input';
|
||||||
import InputAddress from './InputAddress';
|
import InputAddress from './InputAddress';
|
||||||
import InputAddressSelect from './InputAddressSelect';
|
import InputAddressSelect from './InputAddressSelect';
|
||||||
import InputChip from './InputChip';
|
import InputChip from './InputChip';
|
||||||
|
import InputDate from './InputDate';
|
||||||
import InputInline from './InputInline';
|
import InputInline from './InputInline';
|
||||||
import Select from './Select';
|
import InputTime from './InputTime';
|
||||||
|
import Label from './Label';
|
||||||
import RadioButtons from './RadioButtons';
|
import RadioButtons from './RadioButtons';
|
||||||
|
import Select from './Select';
|
||||||
|
import TypedInput from './TypedInput';
|
||||||
|
|
||||||
export default from './form';
|
export default from './form';
|
||||||
export {
|
export {
|
||||||
AddressSelect,
|
AddressSelect,
|
||||||
FormWrap,
|
FormWrap,
|
||||||
TypedInput,
|
|
||||||
Input,
|
Input,
|
||||||
InputAddress,
|
InputAddress,
|
||||||
InputAddressSelect,
|
InputAddressSelect,
|
||||||
InputChip,
|
InputChip,
|
||||||
|
InputDate,
|
||||||
InputInline,
|
InputInline,
|
||||||
|
InputTime,
|
||||||
|
Label,
|
||||||
|
RadioButtons,
|
||||||
Select,
|
Select,
|
||||||
RadioButtons
|
TypedInput
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
.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;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
position: relative;
|
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.
|
// This file is part of Parity.
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
@ -17,13 +17,44 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from '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 GasPriceSelector from '../GasPriceSelector';
|
||||||
import Store from './store';
|
|
||||||
|
|
||||||
|
import Store, { CONDITIONS } from './store';
|
||||||
import styles from './gasPriceEditor.css';
|
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
|
@observer
|
||||||
export default class GasPriceEditor extends Component {
|
export default class GasPriceEditor extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -41,7 +72,7 @@ export default class GasPriceEditor extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { children, store } = this.props;
|
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 eth = api.util.fromWei(totalValue).toFormat();
|
||||||
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
const gasLabel = `gas (estimated: ${new BigNumber(estimated).toFormat()})`;
|
||||||
@ -49,43 +80,147 @@ export default class GasPriceEditor extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container }>
|
<div className={ styles.container }>
|
||||||
<div className={ styles.graphColumn }>
|
<RadioButtons
|
||||||
<GasPriceSelector
|
className={ styles.conditionRadio }
|
||||||
histogram={ histogram }
|
label={
|
||||||
onChange={ this.onEditGasPrice }
|
<FormattedMessage
|
||||||
price={ price } />
|
id='txEditor.condition.label'
|
||||||
<div className={ styles.gasPriceDesc }>
|
defaultMessage='Condition where transaction activates'
|
||||||
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.
|
/>
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<div className={ styles.editColumn }>
|
renderConditions () {
|
||||||
<div className={ styles.row }>
|
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
|
<Input
|
||||||
error={ errorGas }
|
error={ conditionBlockError }
|
||||||
hint='the amount of gas to use for the transaction'
|
hint={
|
||||||
label={ gasLabel }
|
<FormattedMessage
|
||||||
onChange={ this.onEditGas }
|
id='txEditor.condition.block.hint'
|
||||||
value={ gas } />
|
defaultMessage='The minimum block to send from'
|
||||||
<Input
|
/>
|
||||||
error={ errorPrice }
|
}
|
||||||
hint='the price of gas to use for the transaction'
|
label={
|
||||||
label={ priceLabel }
|
<FormattedMessage
|
||||||
onChange={ this.onEditGasPrice }
|
id='txEditor.condition.block.label'
|
||||||
value={ price } />
|
defaultMessage='Transaction send block'
|
||||||
</div>
|
/>
|
||||||
<div className={ styles.row }>
|
}
|
||||||
<Input
|
min={ 1 }
|
||||||
disabled
|
onChange={ this.onChangeConditionBlock }
|
||||||
error={ errorTotal }
|
type='number'
|
||||||
hint='the total amount of the transaction'
|
value={ condition.block }
|
||||||
label='total transaction amount'
|
/>
|
||||||
value={ `${eth} ETH` } />
|
|
||||||
</div>
|
|
||||||
<div className={ styles.row }>
|
|
||||||
{ children }
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -103,4 +238,16 @@ export default class GasPriceEditor extends Component {
|
|||||||
store.setPrice(price);
|
store.setPrice(price);
|
||||||
onChange && onChange('gasPrice', 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 './';
|
import GasPriceEditor from './';
|
||||||
|
|
||||||
const api = {
|
let api;
|
||||||
util: {
|
let component;
|
||||||
fromWei: (value) => new BigNumber(value)
|
let store;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const store = {
|
function createApi () {
|
||||||
estimated: '123',
|
api = {
|
||||||
histogram: {},
|
eth: {
|
||||||
priceDefault: '456',
|
blockNumber: sinon.stub().resolves(new BigNumber(3))
|
||||||
totalValue: '789',
|
},
|
||||||
setGas: sinon.stub(),
|
util: {
|
||||||
setPrice: sinon.stub()
|
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', () => {
|
describe('ui/GasPriceEditor', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
expect(shallow(
|
expect(component).to.be.ok;
|
||||||
<GasPriceEditor store={ store } />,
|
|
||||||
{ context: { api } }
|
|
||||||
)).to.be.ok;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,17 @@ import { action, computed, observable, transaction } from 'mobx';
|
|||||||
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
import { ERRORS, validatePositiveNumber } from '~/util/validation';
|
||||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '~/util/constants';
|
||||||
|
|
||||||
|
const CONDITIONS = {
|
||||||
|
NONE: 'none',
|
||||||
|
BLOCK: 'blockNumber',
|
||||||
|
TIME: 'timestamp'
|
||||||
|
};
|
||||||
|
|
||||||
export default class GasPriceEditor {
|
export default class GasPriceEditor {
|
||||||
|
@observable blockNumber = 0;
|
||||||
|
@observable condition = {};
|
||||||
|
@observable conditionBlockError = null;
|
||||||
|
@observable conditionType = CONDITIONS.NONE;
|
||||||
@observable errorEstimated = null;
|
@observable errorEstimated = null;
|
||||||
@observable errorGas = null;
|
@observable errorGas = null;
|
||||||
@observable errorPrice = null;
|
@observable errorPrice = null;
|
||||||
@ -34,13 +44,23 @@ export default class GasPriceEditor {
|
|||||||
@observable priceDefault;
|
@observable priceDefault;
|
||||||
@observable weiValue = '0';
|
@observable weiValue = '0';
|
||||||
|
|
||||||
constructor (api, { gas, gasLimit, gasPrice }) {
|
constructor (api, { gas, gasLimit, gasPrice, condition = null }) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
|
|
||||||
this.gas = gas;
|
this.gas = gas;
|
||||||
this.gasLimit = gasLimit;
|
this.gasLimit = gasLimit;
|
||||||
this.price = gasPrice;
|
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) {
|
if (api) {
|
||||||
this.loadDefaults();
|
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) => {
|
@action setEditing = (isEditing) => {
|
||||||
this.isEditing = isEditing;
|
this.isEditing = isEditing;
|
||||||
}
|
}
|
||||||
@ -130,9 +183,10 @@ export default class GasPriceEditor {
|
|||||||
bucket_bounds: [],
|
bucket_bounds: [],
|
||||||
counts: []
|
counts: []
|
||||||
})),
|
})),
|
||||||
this._api.eth.gasPrice()
|
this._api.eth.gasPrice(),
|
||||||
|
this._api.eth.blockNumber()
|
||||||
])
|
])
|
||||||
.then(([histogram, _price]) => {
|
.then(([histogram, _price, blockNumber]) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
const price = _price.toFixed(0);
|
const price = _price.toFixed(0);
|
||||||
|
|
||||||
@ -142,6 +196,7 @@ export default class GasPriceEditor {
|
|||||||
this.setHistogram(histogram);
|
this.setHistogram(histogram);
|
||||||
|
|
||||||
this.priceDefault = price;
|
this.priceDefault = price;
|
||||||
|
this.blockNumber = blockNumber.toNumber();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -150,13 +205,37 @@ export default class GasPriceEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
overrideTransaction = (transaction) => {
|
overrideTransaction = (transaction) => {
|
||||||
if (this.errorGas || this.errorPrice) {
|
if (this.errorGas || this.errorPrice || this.conditionBlockError) {
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, transaction, {
|
const override = {
|
||||||
|
condition: this.condition,
|
||||||
gas: new BigNumber(this.gas || DEFAULT_GAS),
|
gas: new BigNumber(this.gas || DEFAULT_GAS),
|
||||||
gasPrice: new BigNumber(this.price || DEFAULT_GASPRICE)
|
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 { ERRORS } from '~/util/validation';
|
||||||
|
|
||||||
import GasPriceEditor from './gasPriceEditor';
|
import GasPriceEditor from './gasPriceEditor';
|
||||||
|
import { CONDITIONS } from './store';
|
||||||
|
|
||||||
const { Store } = GasPriceEditor;
|
const { Store } = GasPriceEditor;
|
||||||
|
|
||||||
@ -31,18 +32,30 @@ const HISTOGRAM = {
|
|||||||
counts: [3, 4]
|
counts: [3, 4]
|
||||||
};
|
};
|
||||||
|
|
||||||
const api = {
|
let api;
|
||||||
eth: {
|
|
||||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
|
||||||
},
|
|
||||||
parity: {
|
|
||||||
gasPriceHistogram: sinon.stub().resolves(HISTOGRAM)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
let store = null;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
createApi();
|
||||||
|
});
|
||||||
|
|
||||||
it('is available via GasPriceEditor.Store', () => {
|
it('is available via GasPriceEditor.Store', () => {
|
||||||
expect(new Store(null, {})).to.be.ok;
|
expect(new Store(null, {})).to.be.ok;
|
||||||
});
|
});
|
||||||
@ -65,6 +78,7 @@ describe('ui/GasPriceEditor/store', () => {
|
|||||||
describe('constructor (defaults) when histogram not available', () => {
|
describe('constructor (defaults) when histogram not available', () => {
|
||||||
const api = {
|
const api = {
|
||||||
eth: {
|
eth: {
|
||||||
|
blockNumber: sinon.stub().resolves(new BigNumber(2)),
|
||||||
gasPrice: sinon.stub().resolves(GASPRICE)
|
gasPrice: sinon.stub().resolves(GASPRICE)
|
||||||
},
|
},
|
||||||
parity: {
|
parity: {
|
||||||
@ -92,6 +106,67 @@ describe('ui/GasPriceEditor/store', () => {
|
|||||||
store = new Store(null, { gasLimit: GASLIMIT });
|
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', () => {
|
describe('setEditing', () => {
|
||||||
it('sets the value', () => {
|
it('sets the value', () => {
|
||||||
expect(store.isEditing).to.be.false;
|
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 CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
|
||||||
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
|
||||||
import ContractIcon from 'material-ui/svg-icons/action/code';
|
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 DashboardIcon from 'material-ui/svg-icons/action/dashboard';
|
||||||
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||||
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
import DoneIcon from 'material-ui/svg-icons/action/done-all';
|
||||||
import EditIcon from 'material-ui/svg-icons/content/create';
|
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 LinkIcon from 'material-ui/svg-icons/content/link';
|
||||||
import LockedIcon from 'material-ui/svg-icons/action/lock';
|
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 NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
|
import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
|
||||||
import SaveIcon from 'material-ui/svg-icons/content/save';
|
import SaveIcon from 'material-ui/svg-icons/content/save';
|
||||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
import SendIcon from 'material-ui/svg-icons/content/send';
|
||||||
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
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 VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
||||||
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||||
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock';
|
||||||
@ -44,17 +50,23 @@ export {
|
|||||||
CompareIcon,
|
CompareIcon,
|
||||||
ComputerIcon,
|
ComputerIcon,
|
||||||
ContractIcon,
|
ContractIcon,
|
||||||
|
CopyIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
DoneIcon,
|
DoneIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
|
FingerprintIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
LockedIcon,
|
LockedIcon,
|
||||||
|
MoveIcon,
|
||||||
NextIcon,
|
NextIcon,
|
||||||
PrevIcon,
|
PrevIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
SnoozeIcon,
|
SnoozeIcon,
|
||||||
|
StarIcon,
|
||||||
|
StarCircleIcon,
|
||||||
|
StarOutlineIcon,
|
||||||
VerifyIcon,
|
VerifyIcon,
|
||||||
VisibleIcon,
|
VisibleIcon,
|
||||||
VpnIcon
|
VpnIcon
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { CircularProgress } from 'material-ui';
|
||||||
|
import moment from 'moment';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import CircularProgress from 'material-ui/CircularProgress';
|
|
||||||
|
|
||||||
import { TypedInput, InputAddress } from '../Form';
|
import { TypedInput, InputAddress } from '../Form';
|
||||||
import MethodDecodingStore from './methodDecodingStore';
|
import MethodDecodingStore from './methodDecodingStore';
|
||||||
@ -128,15 +129,25 @@ class MethodDecoding extends Component {
|
|||||||
|
|
||||||
renderMinBlock () {
|
renderMinBlock () {
|
||||||
const { historic, transaction } = this.props;
|
const { historic, transaction } = this.props;
|
||||||
const { minBlock } = transaction;
|
const { condition } = transaction;
|
||||||
|
|
||||||
if (!minBlock || minBlock.eq(0)) {
|
if (!condition) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (condition.block && condition.block.gt(0)) {
|
||||||
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ minBlock.toFormat(0) }</span></span>
|
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 () {
|
renderAction () {
|
||||||
|
@ -47,20 +47,6 @@
|
|||||||
.title {
|
.title {
|
||||||
background: rgba(0, 0, 0, 0.25) !important;
|
background: rgba(0, 0, 0, 0.25) !important;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps {
|
|
||||||
margin-bottom: -1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.waiting {
|
|
||||||
margin: 1em -1em -1em -1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
|
@ -22,7 +22,7 @@ import { connect } from 'react-redux';
|
|||||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import Container from '../Container';
|
import Container from '../Container';
|
||||||
import Title from './Title';
|
import Title from '../Title';
|
||||||
|
|
||||||
const ACTIONS_STYLE = { borderStyle: 'none' };
|
const ACTIONS_STYLE = { borderStyle: 'none' };
|
||||||
const TITLE_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 contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed);
|
||||||
const header = (
|
const header = (
|
||||||
<Title
|
<Title
|
||||||
|
activeStep={ current }
|
||||||
busy={ busy }
|
busy={ busy }
|
||||||
current={ current }
|
busySteps={ waiting }
|
||||||
|
className={ styles.title }
|
||||||
steps={ steps }
|
steps={ steps }
|
||||||
title={ title }
|
title={ title }
|
||||||
waiting={ waiting } />
|
waiting={ waiting }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
const classes = `${styles.dialog} ${className}`;
|
const classes = `${styles.dialog} ${className}`;
|
||||||
|
|
||||||
|
@ -26,7 +26,12 @@ class ParityBackground extends Component {
|
|||||||
backgroundSeed: PropTypes.string,
|
backgroundSeed: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onClick: PropTypes.func
|
onClick: PropTypes.func,
|
||||||
|
style: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
style: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -65,7 +70,11 @@ class ParityBackground extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, className, onClick } = this.props;
|
const { children, className, onClick } = this.props;
|
||||||
const { style } = this.state;
|
|
||||||
|
const style = {
|
||||||
|
...this.state.style,
|
||||||
|
...this.props.style
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -15,30 +15,32 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$left: 1.5em;
|
$modalMargin: 1.5em;
|
||||||
$right: $left;
|
$modalPadding: 1.5em;
|
||||||
$bottom: $left;
|
$modalBackZ: 2500;
|
||||||
$top: 20vh;
|
|
||||||
|
/* 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 {
|
.backOverlay {
|
||||||
|
background-color: rgba(255, 255, 255, 0.35);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: rgba(255, 255, 255, 0.25);
|
z-index: $modalBackZ;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.parityBackground {
|
.parityBackground {
|
||||||
@ -48,57 +50,78 @@ $top: 20vh;
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
.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);
|
background-color: rgba(0, 0, 0, 1);
|
||||||
opacity: 0;
|
|
||||||
z-index: -10;
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: $modalPadding;
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.expanded {
|
&.modal {
|
||||||
opacity: 1;
|
bottom: $modalBottom;
|
||||||
z-index: 3500;
|
left: $modalLeft;
|
||||||
}
|
right: $modalRight;
|
||||||
}
|
top: $modalTop;
|
||||||
|
z-index: $modalZ;
|
||||||
.closeIcon {
|
}
|
||||||
position: absolute;
|
|
||||||
top: 0.5rem;
|
&.popover {
|
||||||
right: 1rem;
|
left: $popoverLeft;
|
||||||
font-size: 4em;
|
top: $popoverTop;
|
||||||
z-index: 100;
|
height: calc(100vh - $popoverTop - $popoverBottom);
|
||||||
|
width: calc(100vw - $popoverLeft - $popoverRight);
|
||||||
transition-property: opacity;
|
z-index: $popoverZ;
|
||||||
transition-duration: 0.25s;
|
}
|
||||||
transition-timing-function: ease-out;
|
|
||||||
|
.buttonRow {
|
||||||
&, * {
|
display: flex;
|
||||||
height: 48px !important;
|
flex-direction: row;
|
||||||
width: 48px !important;
|
flex-wrap: nowrap;
|
||||||
}
|
justify-content: flex-end;
|
||||||
|
padding: $modalPadding 0 0 0;
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
button:not([disabled]) {
|
||||||
opacity: 0.5;
|
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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import EventListener from 'react-event-listener';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import ReactPortal from 'react-portal';
|
import ReactPortal from 'react-portal';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
|
|
||||||
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
import { CloseIcon } from '~/ui/Icons';
|
import { CloseIcon } from '~/ui/Icons';
|
||||||
import ParityBackground from '~/ui/ParityBackground';
|
import ParityBackground from '~/ui/ParityBackground';
|
||||||
|
import Title from '~/ui/Title';
|
||||||
|
|
||||||
import styles from './portal.css';
|
import styles from './portal.css';
|
||||||
|
|
||||||
@ -29,101 +32,154 @@ export default class Portal extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
open: PropTypes.bool.isRequired,
|
open: PropTypes.bool.isRequired,
|
||||||
|
activeStep: PropTypes.number,
|
||||||
|
busy: PropTypes.bool,
|
||||||
|
busySteps: PropTypes.array,
|
||||||
|
buttons: PropTypes.array,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onKeyDown: PropTypes.func
|
hideClose: PropTypes.bool,
|
||||||
|
isChildModal: PropTypes.bool,
|
||||||
|
onKeyDown: PropTypes.func,
|
||||||
|
steps: PropTypes.array,
|
||||||
|
title: nodeOrStringProptype()
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
componentDidMount () {
|
||||||
expanded: false
|
this.setBodyOverflow(this.props.open);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (this.props.open !== nextProps.open) {
|
if (nextProps.open !== this.props.open) {
|
||||||
const opening = nextProps.open;
|
this.setBodyOverflow(nextProps.open);
|
||||||
const closing = !opening;
|
|
||||||
|
|
||||||
if (opening) {
|
|
||||||
return this.setState({ expanded: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closing) {
|
|
||||||
return this.setState({ expanded: false });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.setBodyOverflow(false);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { expanded } = this.state;
|
const { activeStep, busy, busySteps, children, className, isChildModal, open, steps, title } = this.props;
|
||||||
const { children, className } = this.props;
|
|
||||||
|
|
||||||
const classes = [ styles.overlay, className ];
|
if (!open) {
|
||||||
const backClasses = [ styles.backOverlay ];
|
return null;
|
||||||
|
|
||||||
if (expanded) {
|
|
||||||
classes.push(styles.expanded);
|
|
||||||
backClasses.push(styles.expanded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactPortal isOpened onClose={ this.handleClose }>
|
<ReactPortal
|
||||||
<div className={ backClasses.join(' ') } onClick={ this.handleClose }>
|
isOpened
|
||||||
|
onClose={ this.handleClose }
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={ styles.backOverlay }
|
||||||
|
onClick={ this.handleClose }
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={ classes.join(' ') }
|
className={
|
||||||
|
[
|
||||||
|
styles.overlay,
|
||||||
|
isChildModal
|
||||||
|
? styles.popover
|
||||||
|
: styles.modal,
|
||||||
|
className
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
onClick={ this.stopEvent }
|
onClick={ this.stopEvent }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
>
|
>
|
||||||
|
<EventListener
|
||||||
|
target='window'
|
||||||
|
onKeyUp={ this.handleKeyUp }
|
||||||
|
/>
|
||||||
<ParityBackground className={ styles.parityBackground } />
|
<ParityBackground className={ styles.parityBackground } />
|
||||||
|
{ this.renderClose() }
|
||||||
{ this.renderCloseIcon() }
|
<Title
|
||||||
{ children }
|
activeStep={ activeStep }
|
||||||
|
busy={ busy }
|
||||||
|
busySteps={ busySteps }
|
||||||
|
className={ styles.titleRow }
|
||||||
|
steps={ steps }
|
||||||
|
title={ title }
|
||||||
|
/>
|
||||||
|
<div className={ styles.childContainer }>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
{ this.renderButtons() }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ReactPortal>
|
</ReactPortal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCloseIcon () {
|
renderButtons () {
|
||||||
const { expanded } = this.state;
|
const { buttons } = this.props;
|
||||||
|
|
||||||
if (!expanded) {
|
if (!buttons) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.closeIcon } onClick={ this.handleClose }>
|
<div className={ styles.buttonRow }>
|
||||||
<CloseIcon />
|
{ buttons }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderClose () {
|
||||||
|
const { hideClose } = this.props;
|
||||||
|
|
||||||
|
if (hideClose) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CloseIcon
|
||||||
|
className={ styles.closeIcon }
|
||||||
|
onClick={ this.handleClose }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
stopEvent = (event) => {
|
stopEvent = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
this.props.onClose();
|
const { hideClose, onClose } = this.props;
|
||||||
|
|
||||||
|
if (!hideClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = (event) => {
|
handleKeyDown = (event) => {
|
||||||
|
const { onKeyDown } = this.props;
|
||||||
|
|
||||||
|
event.persist();
|
||||||
|
|
||||||
|
return onKeyDown
|
||||||
|
? onKeyDown(event)
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp = (event) => {
|
||||||
const codeName = keycode(event);
|
const codeName = keycode(event);
|
||||||
|
|
||||||
switch (codeName) {
|
switch (codeName) {
|
||||||
case 'esc':
|
case 'esc':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return this.handleClose();
|
return this.handleClose();
|
||||||
|
|
||||||
default:
|
|
||||||
event.persist();
|
|
||||||
return this.props.onKeyDown(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDOMAction = (ref, method) => {
|
handleDOMAction = (ref, method) => {
|
||||||
const refItem = typeof ref === 'string' ? this.refs[ref] : ref;
|
const element = ReactDOM.findDOMNode(
|
||||||
const element = ReactDOM.findDOMNode(refItem);
|
typeof ref === 'string'
|
||||||
|
? this.refs[ref]
|
||||||
|
: ref
|
||||||
|
);
|
||||||
|
|
||||||
if (!element || typeof element[method] !== 'function') {
|
if (!element || typeof element[method] !== 'function') {
|
||||||
console.warn('could not find', ref, 'or method', method);
|
console.warn('could not find', ref, 'or method', method);
|
||||||
@ -132,4 +188,12 @@ export default class Portal extends Component {
|
|||||||
|
|
||||||
return element[method]();
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -14,4 +14,4 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default from './summary';
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user