Merge branch 'master' into on-demand-les-request
This commit is contained in:
commit
62bc92ff4d
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1503,7 +1503,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#d95a7dd2cc7469dc58af77743ec3ebc65e51cf36"
|
source = "git+https://github.com/ethcore/js-precompiled.git#ebea2bf78e076916b51b04d8b24187a6a85ae440"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -378,7 +378,8 @@ impl BlockProvider for BlockChain {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(move |(index, (mut logs, tx_hash))| {
|
.flat_map(move |(index, (mut logs, tx_hash))| {
|
||||||
let current_log_index = log_index;
|
let current_log_index = log_index;
|
||||||
log_index -= logs.len();
|
let no_of_logs = logs.len();
|
||||||
|
log_index -= no_of_logs;
|
||||||
|
|
||||||
logs.reverse();
|
logs.reverse();
|
||||||
logs.into_iter()
|
logs.into_iter()
|
||||||
@ -390,6 +391,7 @@ impl BlockProvider for BlockChain {
|
|||||||
transaction_hash: tx_hash,
|
transaction_hash: tx_hash,
|
||||||
// iterating in reverse order
|
// iterating in reverse order
|
||||||
transaction_index: receipts_len - index - 1,
|
transaction_index: receipts_len - index - 1,
|
||||||
|
transaction_log_index: no_of_logs - i - 1,
|
||||||
log_index: current_log_index - i - 1,
|
log_index: current_log_index - i - 1,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1936,6 +1938,7 @@ mod tests {
|
|||||||
block_number: block1.header().number(),
|
block_number: block1.header().number(),
|
||||||
transaction_hash: tx_hash1.clone(),
|
transaction_hash: tx_hash1.clone(),
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
},
|
},
|
||||||
LocalizedLogEntry {
|
LocalizedLogEntry {
|
||||||
@ -1944,6 +1947,7 @@ mod tests {
|
|||||||
block_number: block1.header().number(),
|
block_number: block1.header().number(),
|
||||||
transaction_hash: tx_hash1.clone(),
|
transaction_hash: tx_hash1.clone(),
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 1,
|
||||||
log_index: 1,
|
log_index: 1,
|
||||||
},
|
},
|
||||||
LocalizedLogEntry {
|
LocalizedLogEntry {
|
||||||
@ -1952,6 +1956,7 @@ mod tests {
|
|||||||
block_number: block1.header().number(),
|
block_number: block1.header().number(),
|
||||||
transaction_hash: tx_hash2.clone(),
|
transaction_hash: tx_hash2.clone(),
|
||||||
transaction_index: 1,
|
transaction_index: 1,
|
||||||
|
transaction_log_index: 0,
|
||||||
log_index: 2,
|
log_index: 2,
|
||||||
},
|
},
|
||||||
LocalizedLogEntry {
|
LocalizedLogEntry {
|
||||||
@ -1960,6 +1965,7 @@ mod tests {
|
|||||||
block_number: block2.header().number(),
|
block_number: block2.header().number(),
|
||||||
transaction_hash: tx_hash3.clone(),
|
transaction_hash: tx_hash3.clone(),
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@ -1970,6 +1976,7 @@ mod tests {
|
|||||||
block_number: block2.header().number(),
|
block_number: block2.header().number(),
|
||||||
transaction_hash: tx_hash3.clone(),
|
transaction_hash: tx_hash3.clone(),
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -59,7 +59,7 @@ use client::{
|
|||||||
use client::Error as ClientError;
|
use client::Error as ClientError;
|
||||||
use env_info::EnvInfo;
|
use env_info::EnvInfo;
|
||||||
use executive::{Executive, Executed, TransactOptions, contract_address};
|
use executive::{Executive, Executed, TransactOptions, contract_address};
|
||||||
use receipt::LocalizedReceipt;
|
use receipt::{Receipt, LocalizedReceipt};
|
||||||
use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase};
|
use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase};
|
||||||
use trace;
|
use trace;
|
||||||
use trace::FlatTransactionTraces;
|
use trace::FlatTransactionTraces;
|
||||||
@ -837,7 +837,6 @@ impl snapshot::DatabaseRestore for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl BlockChainClient for Client {
|
impl BlockChainClient for Client {
|
||||||
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError> {
|
fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result<Executed, CallError> {
|
||||||
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
|
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
|
||||||
@ -1134,51 +1133,21 @@ impl BlockChainClient for Client {
|
|||||||
let chain = self.chain.read();
|
let chain = self.chain.read();
|
||||||
self.transaction_address(id)
|
self.transaction_address(id)
|
||||||
.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| {
|
.and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| {
|
||||||
let t = chain.block_body(&address.block_hash)
|
let transaction = chain.block_body(&address.block_hash)
|
||||||
.and_then(|body| {
|
.and_then(|body| body.view().localized_transaction_at(&address.block_hash, block_number, address.index));
|
||||||
body.view().localized_transaction_at(&address.block_hash, block_number, address.index)
|
|
||||||
});
|
|
||||||
|
|
||||||
let tx_and_sender = t.and_then(|tx| tx.sender().ok().map(|sender| (tx, sender)));
|
let previous_receipts = (0..address.index + 1)
|
||||||
|
.map(|index| {
|
||||||
match (tx_and_sender, chain.transaction_receipt(&address)) {
|
let mut address = address.clone();
|
||||||
(Some((tx, sender)), Some(receipt)) => {
|
address.index = index;
|
||||||
let block_hash = tx.block_hash.clone();
|
chain.transaction_receipt(&address)
|
||||||
let block_number = tx.block_number.clone();
|
|
||||||
let transaction_hash = tx.hash();
|
|
||||||
let transaction_index = tx.transaction_index;
|
|
||||||
let prior_gas_used = match tx.transaction_index {
|
|
||||||
0 => U256::zero(),
|
|
||||||
i => {
|
|
||||||
let prior_address = TransactionAddress { block_hash: address.block_hash, index: i - 1 };
|
|
||||||
let prior_receipt = chain.transaction_receipt(&prior_address).expect("Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed");
|
|
||||||
prior_receipt.gas_used
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Some(LocalizedReceipt {
|
|
||||||
transaction_hash: tx.hash(),
|
|
||||||
transaction_index: tx.transaction_index,
|
|
||||||
block_hash: tx.block_hash,
|
|
||||||
block_number: tx.block_number,
|
|
||||||
cumulative_gas_used: receipt.gas_used,
|
|
||||||
gas_used: receipt.gas_used - prior_gas_used,
|
|
||||||
contract_address: match tx.action {
|
|
||||||
Action::Call(_) => None,
|
|
||||||
Action::Create => Some(contract_address(&sender, &tx.nonce))
|
|
||||||
},
|
|
||||||
logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry {
|
|
||||||
entry: log,
|
|
||||||
block_hash: block_hash.clone(),
|
|
||||||
block_number: block_number,
|
|
||||||
transaction_hash: transaction_hash.clone(),
|
|
||||||
transaction_index: transaction_index,
|
|
||||||
log_index: i
|
|
||||||
}).collect(),
|
|
||||||
log_bloom: receipt.log_bloom,
|
|
||||||
state_root: receipt.state_root,
|
|
||||||
})
|
})
|
||||||
|
.collect();
|
||||||
|
match (transaction, previous_receipts) {
|
||||||
|
(Some(transaction), Some(previous_receipts)) => {
|
||||||
|
Some(transaction_receipt(transaction, previous_receipts))
|
||||||
},
|
},
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1535,6 +1504,49 @@ impl Drop for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `LocalizedReceipt` given `LocalizedTransaction`
|
||||||
|
/// and a vector of receipts from given block up to transaction index.
|
||||||
|
fn transaction_receipt(tx: LocalizedTransaction, mut receipts: Vec<Receipt>) -> LocalizedReceipt {
|
||||||
|
assert_eq!(receipts.len(), tx.transaction_index + 1, "All previous receipts are provided.");
|
||||||
|
|
||||||
|
let sender = tx.sender()
|
||||||
|
.expect("LocalizedTransaction is part of the blockchain; We have only valid transactions in chain; qed");
|
||||||
|
let receipt = receipts.pop().expect("Current receipt is provided; qed");
|
||||||
|
let prior_gas_used = match tx.transaction_index {
|
||||||
|
0 => 0.into(),
|
||||||
|
i => receipts.get(i - 1).expect("All previous receipts are provided; qed").gas_used,
|
||||||
|
};
|
||||||
|
let no_of_logs = receipts.into_iter().map(|receipt| receipt.logs.len()).sum::<usize>();
|
||||||
|
let transaction_hash = tx.hash();
|
||||||
|
let block_hash = tx.block_hash;
|
||||||
|
let block_number = tx.block_number;
|
||||||
|
let transaction_index = tx.transaction_index;
|
||||||
|
|
||||||
|
LocalizedReceipt {
|
||||||
|
transaction_hash: transaction_hash,
|
||||||
|
transaction_index: transaction_index,
|
||||||
|
block_hash: block_hash,
|
||||||
|
block_number:block_number,
|
||||||
|
cumulative_gas_used: receipt.gas_used,
|
||||||
|
gas_used: receipt.gas_used - prior_gas_used,
|
||||||
|
contract_address: match tx.action {
|
||||||
|
Action::Call(_) => None,
|
||||||
|
Action::Create => Some(contract_address(&sender, &tx.nonce))
|
||||||
|
},
|
||||||
|
logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry {
|
||||||
|
entry: log,
|
||||||
|
block_hash: block_hash,
|
||||||
|
block_number: block_number,
|
||||||
|
transaction_hash: transaction_hash,
|
||||||
|
transaction_index: transaction_index,
|
||||||
|
transaction_log_index: i,
|
||||||
|
log_index: no_of_logs + i,
|
||||||
|
}).collect(),
|
||||||
|
log_bloom: receipt.log_bloom,
|
||||||
|
state_root: receipt.state_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
@ -1570,4 +1582,91 @@ mod tests {
|
|||||||
|
|
||||||
assert!(client.tree_route(&genesis, &new_hash).is_none());
|
assert!(client.tree_route(&genesis, &new_hash).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_correct_log_index() {
|
||||||
|
use super::transaction_receipt;
|
||||||
|
use ethkey::KeyPair;
|
||||||
|
use log_entry::{LogEntry, LocalizedLogEntry};
|
||||||
|
use receipt::{Receipt, LocalizedReceipt};
|
||||||
|
use transaction::{Transaction, LocalizedTransaction, Action};
|
||||||
|
use util::Hashable;
|
||||||
|
|
||||||
|
// given
|
||||||
|
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||||
|
let secret = key.secret();
|
||||||
|
|
||||||
|
let block_number = 1;
|
||||||
|
let block_hash = 5.into();
|
||||||
|
let state_root = 99.into();
|
||||||
|
let gas_used = 10.into();
|
||||||
|
let raw_tx = Transaction {
|
||||||
|
nonce: 0.into(),
|
||||||
|
gas_price: 0.into(),
|
||||||
|
gas: 21000.into(),
|
||||||
|
action: Action::Call(10.into()),
|
||||||
|
value: 0.into(),
|
||||||
|
data: vec![],
|
||||||
|
};
|
||||||
|
let tx1 = raw_tx.clone().sign(secret, None);
|
||||||
|
let transaction = LocalizedTransaction {
|
||||||
|
signed: tx1.clone(),
|
||||||
|
block_number: block_number,
|
||||||
|
block_hash: block_hash,
|
||||||
|
transaction_index: 1,
|
||||||
|
};
|
||||||
|
let logs = vec![LogEntry {
|
||||||
|
address: 5.into(),
|
||||||
|
topics: vec![],
|
||||||
|
data: vec![],
|
||||||
|
}, LogEntry {
|
||||||
|
address: 15.into(),
|
||||||
|
topics: vec![],
|
||||||
|
data: vec![],
|
||||||
|
}];
|
||||||
|
let receipts = vec![Receipt {
|
||||||
|
state_root: state_root,
|
||||||
|
gas_used: 5.into(),
|
||||||
|
log_bloom: Default::default(),
|
||||||
|
logs: vec![logs[0].clone()],
|
||||||
|
}, Receipt {
|
||||||
|
state_root: state_root,
|
||||||
|
gas_used: gas_used,
|
||||||
|
log_bloom: Default::default(),
|
||||||
|
logs: logs.clone(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
// when
|
||||||
|
let receipt = transaction_receipt(transaction, receipts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(receipt, LocalizedReceipt {
|
||||||
|
transaction_hash: tx1.hash(),
|
||||||
|
transaction_index: 1,
|
||||||
|
block_hash: block_hash,
|
||||||
|
block_number: block_number,
|
||||||
|
cumulative_gas_used: gas_used,
|
||||||
|
gas_used: gas_used - 5.into(),
|
||||||
|
contract_address: None,
|
||||||
|
logs: vec![LocalizedLogEntry {
|
||||||
|
entry: logs[0].clone(),
|
||||||
|
block_hash: block_hash,
|
||||||
|
block_number: block_number,
|
||||||
|
transaction_hash: tx1.hash(),
|
||||||
|
transaction_index: 1,
|
||||||
|
transaction_log_index: 0,
|
||||||
|
log_index: 1,
|
||||||
|
}, LocalizedLogEntry {
|
||||||
|
entry: logs[1].clone(),
|
||||||
|
block_hash: block_hash,
|
||||||
|
block_number: block_number,
|
||||||
|
transaction_hash: tx1.hash(),
|
||||||
|
transaction_index: 1,
|
||||||
|
transaction_log_index: 1,
|
||||||
|
log_index: 2,
|
||||||
|
}],
|
||||||
|
log_bloom: Default::default(),
|
||||||
|
state_root: state_root,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,4 +320,3 @@ fn does_not_propagate_delayed_transactions() {
|
|||||||
assert_eq!(2, client.ready_transactions().len());
|
assert_eq!(2, client.ready_transactions().len());
|
||||||
assert_eq!(2, client.miner().pending_transactions().len());
|
assert_eq!(2, client.miner().pending_transactions().len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +97,8 @@ pub struct LocalizedLogEntry {
|
|||||||
pub transaction_index: usize,
|
pub transaction_index: usize,
|
||||||
/// Log position in the block.
|
/// Log position in the block.
|
||||||
pub log_index: usize,
|
pub log_index: usize,
|
||||||
|
/// Log position in the transaction.
|
||||||
|
pub transaction_log_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for LocalizedLogEntry {
|
impl Deref for LocalizedLogEntry {
|
||||||
|
@ -373,7 +373,7 @@ impl SignedTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Signed Transaction that is a part of canon blockchain.
|
/// Signed Transaction that is a part of canon blockchain.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "ipc", binary)]
|
#[cfg_attr(feature = "ipc", binary)]
|
||||||
pub struct LocalizedTransaction {
|
pub struct LocalizedTransaction {
|
||||||
/// Signed part.
|
/// Signed part.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.2.152",
|
"version": "0.2.165",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -26,9 +26,9 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:lib && npm run build:dll && npm run build:app",
|
"build": "npm run build:lib && npm run build:dll && npm run build:app",
|
||||||
"build:app": "webpack --config webpack/app --progress",
|
"build:app": "webpack --config webpack/app",
|
||||||
"build:lib": "webpack --config webpack/libraries --progress",
|
"build:lib": "webpack --config webpack/libraries",
|
||||||
"build:dll": "webpack --config webpack/vendor --progress",
|
"build:dll": "webpack --config webpack/vendor",
|
||||||
"ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app",
|
"ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app",
|
||||||
"ci:build:app": "NODE_ENV=production webpack --config webpack/app",
|
"ci:build:app": "NODE_ENV=production webpack --config webpack/app",
|
||||||
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
|
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
|
||||||
@ -51,19 +51,19 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "6.18.0",
|
"babel-cli": "6.18.0",
|
||||||
"babel-core": "6.20.0",
|
"babel-core": "6.21.0",
|
||||||
"babel-eslint": "7.1.1",
|
"babel-eslint": "7.1.1",
|
||||||
"babel-loader": "6.2.10",
|
"babel-loader": "6.2.10",
|
||||||
"babel-plugin-lodash": "3.2.10",
|
"babel-plugin-lodash": "3.2.11",
|
||||||
"babel-plugin-react-intl": "2.2.0",
|
"babel-plugin-react-intl": "2.2.0",
|
||||||
"babel-plugin-transform-class-properties": "6.18.0",
|
"babel-plugin-transform-class-properties": "6.19.0",
|
||||||
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
||||||
"babel-plugin-transform-object-rest-spread": "6.20.2",
|
"babel-plugin-transform-object-rest-spread": "6.20.2",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.2.11",
|
"babel-plugin-transform-react-remove-prop-types": "0.2.11",
|
||||||
"babel-plugin-transform-runtime": "6.15.0",
|
"babel-plugin-transform-runtime": "6.15.0",
|
||||||
"babel-plugin-webpack-alias": "2.1.2",
|
"babel-plugin-webpack-alias": "2.1.2",
|
||||||
"babel-polyfill": "6.20.0",
|
"babel-polyfill": "6.20.0",
|
||||||
"babel-preset-env": "1.0.2",
|
"babel-preset-env": "1.1.4",
|
||||||
"babel-preset-es2015": "6.18.0",
|
"babel-preset-es2015": "6.18.0",
|
||||||
"babel-preset-es2016": "6.16.0",
|
"babel-preset-es2016": "6.16.0",
|
||||||
"babel-preset-es2017": "6.16.0",
|
"babel-preset-es2017": "6.16.0",
|
||||||
@ -80,57 +80,58 @@
|
|||||||
"coveralls": "2.11.15",
|
"coveralls": "2.11.15",
|
||||||
"css-loader": "0.26.1",
|
"css-loader": "0.26.1",
|
||||||
"ejs-loader": "0.3.0",
|
"ejs-loader": "0.3.0",
|
||||||
"enzyme": "2.6.0",
|
"enzyme": "2.7.0",
|
||||||
"eslint": "3.11.1",
|
"eslint": "3.11.1",
|
||||||
"eslint-config-semistandard": "7.0.0",
|
"eslint-config-semistandard": "7.0.0",
|
||||||
"eslint-config-standard": "6.2.1",
|
"eslint-config-standard": "6.2.1",
|
||||||
"eslint-config-standard-react": "4.2.0",
|
"eslint-config-standard-react": "4.2.0",
|
||||||
"eslint-plugin-promise": "3.4.0",
|
"eslint-plugin-promise": "3.4.0",
|
||||||
"eslint-plugin-react": "6.7.1",
|
"eslint-plugin-react": "6.8.0",
|
||||||
"eslint-plugin-standard": "2.0.1",
|
"eslint-plugin-standard": "2.0.1",
|
||||||
"express": "4.14.0",
|
"express": "4.14.0",
|
||||||
"extract-loader": "0.1.0",
|
"extract-loader": "0.1.0",
|
||||||
"extract-text-webpack-plugin": "2.0.0-beta.4",
|
"extract-text-webpack-plugin": "2.0.0-beta.4",
|
||||||
"file-loader": "0.9.0",
|
"file-loader": "0.9.0",
|
||||||
"happypack": "3.0.0",
|
"happypack": "3.0.2",
|
||||||
"html-loader": "0.4.4",
|
"html-loader": "0.4.4",
|
||||||
"html-webpack-plugin": "2.24.1",
|
"html-webpack-plugin": "2.24.1",
|
||||||
"http-proxy-middleware": "0.17.2",
|
"http-proxy-middleware": "0.17.3",
|
||||||
"husky": "0.11.9",
|
"husky": "0.11.9",
|
||||||
"ignore-styles": "5.0.1",
|
"ignore-styles": "5.0.1",
|
||||||
"image-webpack-loader": "3.0.0",
|
"image-webpack-loader": "3.1.0",
|
||||||
"istanbul": "1.0.0-alpha.2",
|
"istanbul": "1.0.0-alpha.2",
|
||||||
"jsdom": "9.8.3",
|
"jsdom": "9.9.1",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
"mocha": "3.2.0",
|
"mocha": "3.2.0",
|
||||||
"mock-local-storage": "1.0.2",
|
"mock-local-storage": "1.0.2",
|
||||||
"mock-socket": "6.0.3",
|
"mock-socket": "6.0.4",
|
||||||
"nock": "9.0.2",
|
"nock": "9.0.2",
|
||||||
"postcss-import": "9.0.0",
|
"postcss-import": "9.0.0",
|
||||||
"postcss-loader": "1.2.0",
|
"postcss-loader": "1.2.1",
|
||||||
"postcss-nested": "1.0.0",
|
"postcss-nested": "1.0.0",
|
||||||
"postcss-simple-vars": "3.0.0",
|
"postcss-simple-vars": "3.0.0",
|
||||||
"progress": "1.1.8",
|
"progress": "1.1.8",
|
||||||
|
"progress-bar-webpack-plugin": "1.9.1",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
"react-addons-perf": "15.4.1",
|
"react-addons-perf": "15.4.1",
|
||||||
"react-addons-test-utils": "15.4.1",
|
"react-addons-test-utils": "15.4.1",
|
||||||
"react-hot-loader": "3.0.0-beta.6",
|
"react-hot-loader": "3.0.0-beta.6",
|
||||||
"react-intl-aggregate-webpack-plugin": "0.0.1",
|
"react-intl-aggregate-webpack-plugin": "0.0.1",
|
||||||
"rucksack-css": "0.9.1",
|
"rucksack-css": "0.9.1",
|
||||||
"script-ext-html-webpack-plugin": "1.3.4",
|
"script-ext-html-webpack-plugin": "1.3.5",
|
||||||
"serviceworker-webpack-plugin": "0.1.7",
|
"serviceworker-webpack-plugin": "0.1.7",
|
||||||
"sinon": "1.17.6",
|
"sinon": "1.17.6",
|
||||||
"sinon-as-promised": "4.0.2",
|
"sinon-as-promised": "4.0.2",
|
||||||
"sinon-chai": "2.8.0",
|
"sinon-chai": "2.8.0",
|
||||||
"style-loader": "0.13.1",
|
"style-loader": "0.13.1",
|
||||||
"stylelint": "7.6.0",
|
"stylelint": "7.7.0",
|
||||||
"stylelint-config-standard": "15.0.0",
|
"stylelint-config-standard": "15.0.1",
|
||||||
"url-loader": "0.5.7",
|
"url-loader": "0.5.7",
|
||||||
"webpack": "2.2.0-rc.2",
|
"webpack": "2.2.0-rc.2",
|
||||||
"webpack-dev-middleware": "1.8.4",
|
"webpack-dev-middleware": "1.9.0",
|
||||||
"webpack-error-notification": "0.1.6",
|
"webpack-error-notification": "0.1.6",
|
||||||
"webpack-hot-middleware": "2.13.2",
|
"webpack-hot-middleware": "2.14.0",
|
||||||
"websocket": "1.0.23"
|
"websocket": "1.0.24"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bignumber.js": "3.0.1",
|
"bignumber.js": "3.0.1",
|
||||||
|
@ -25,7 +25,7 @@ export default class Encoder {
|
|||||||
throw new Error('tokens should be array of Token');
|
throw new Error('tokens should be array of Token');
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediates = tokens.map((token) => Encoder.encodeToken(token));
|
const mediates = tokens.map((token, index) => Encoder.encodeToken(token, index));
|
||||||
const inits = mediates
|
const inits = mediates
|
||||||
.map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx)))
|
.map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx)))
|
||||||
.join('');
|
.join('');
|
||||||
@ -36,11 +36,12 @@ export default class Encoder {
|
|||||||
return `${inits}${closings}`;
|
return `${inits}${closings}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static encodeToken (token) {
|
static encodeToken (token, index = 0) {
|
||||||
if (!isInstanceOf(token, Token)) {
|
if (!isInstanceOf(token, Token)) {
|
||||||
throw new Error('token should be instanceof Token');
|
throw new Error('token should be instanceof Token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'address':
|
case 'address':
|
||||||
return new Mediate('raw', padAddress(token.value));
|
return new Mediate('raw', padAddress(token.value));
|
||||||
@ -64,9 +65,11 @@ export default class Encoder {
|
|||||||
case 'fixedArray':
|
case 'fixedArray':
|
||||||
case 'array':
|
case 'array':
|
||||||
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
|
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Cannot encode token #${index} [${token.type}: ${token.value}]. ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Invalid token type ${token.type} in encodeToken`);
|
throw new Error(`Invalid token type ${token.type} in encodeToken`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -41,6 +41,10 @@ export default class Interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
encodeTokens (paramTypes, values) {
|
encodeTokens (paramTypes, values) {
|
||||||
|
return Interface.encodeTokens(paramTypes, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
static encodeTokens (paramTypes, values) {
|
||||||
const createToken = function (paramType, value) {
|
const createToken = function (paramType, value) {
|
||||||
if (paramType.subtype) {
|
if (paramType.subtype) {
|
||||||
return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry)));
|
return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry)));
|
||||||
|
@ -114,7 +114,11 @@ export default class Api {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
// Don't print if the request is rejected: that's ok
|
||||||
|
if (error.type !== 'REQUEST_REJECTED') {
|
||||||
console.error('pollMethod', error);
|
console.error('pollMethod', error);
|
||||||
|
}
|
||||||
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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 Abi from '../../abi';
|
import Abi from '~/abi';
|
||||||
|
|
||||||
let nextSubscriptionId = 0;
|
let nextSubscriptionId = 0;
|
||||||
|
|
||||||
@ -53,6 +53,10 @@ export default class Contract {
|
|||||||
|
|
||||||
this._subscribedToBlock = false;
|
this._subscribedToBlock = false;
|
||||||
this._blockSubscriptionId = null;
|
this._blockSubscriptionId = null;
|
||||||
|
|
||||||
|
if (api && api.patch && api.patch.contract) {
|
||||||
|
api.patch.contract(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get address () {
|
get address () {
|
||||||
@ -90,8 +94,10 @@ export default class Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deployEstimateGas (options, values) {
|
deployEstimateGas (options, values) {
|
||||||
|
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||||
|
|
||||||
return this._api.eth
|
return this._api.eth
|
||||||
.estimateGas(this._encodeOptions(this.constructors[0], options, values))
|
.estimateGas(_options)
|
||||||
.then((gasEst) => {
|
.then((gasEst) => {
|
||||||
return [gasEst, gasEst.mul(1.2)];
|
return [gasEst, gasEst.mul(1.2)];
|
||||||
});
|
});
|
||||||
@ -115,8 +121,10 @@ export default class Contract {
|
|||||||
|
|
||||||
setState({ state: 'postTransaction', gas });
|
setState({ state: 'postTransaction', gas });
|
||||||
|
|
||||||
|
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||||
|
|
||||||
return this._api.parity
|
return this._api.parity
|
||||||
.postTransaction(this._encodeOptions(this.constructors[0], options, values))
|
.postTransaction(_options)
|
||||||
.then((requestId) => {
|
.then((requestId) => {
|
||||||
setState({ state: 'checkRequest', requestId });
|
setState({ state: 'checkRequest', requestId });
|
||||||
return this._pollCheckRequest(requestId);
|
return this._pollCheckRequest(requestId);
|
||||||
@ -199,7 +207,7 @@ export default class Contract {
|
|||||||
getCallData = (func, options, values) => {
|
getCallData = (func, options, values) => {
|
||||||
let data = options.data;
|
let data = options.data;
|
||||||
|
|
||||||
const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null;
|
const tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null;
|
||||||
const call = tokens ? func.encodeCall(tokens) : null;
|
const call = tokens ? func.encodeCall(tokens) : null;
|
||||||
|
|
||||||
if (data && data.substr(0, 2) === '0x') {
|
if (data && data.substr(0, 2) === '0x') {
|
||||||
@ -221,6 +229,8 @@ export default class Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bindFunction = (func) => {
|
_bindFunction = (func) => {
|
||||||
|
func.contract = this;
|
||||||
|
|
||||||
func.call = (options, values = []) => {
|
func.call = (options, values = []) => {
|
||||||
const callParams = this._encodeOptions(func, this._addOptionsTo(options), values);
|
const callParams = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||||
|
|
||||||
@ -233,13 +243,13 @@ export default class Contract {
|
|||||||
|
|
||||||
if (!func.constant) {
|
if (!func.constant) {
|
||||||
func.postTransaction = (options, values = []) => {
|
func.postTransaction = (options, values = []) => {
|
||||||
return this._api.parity
|
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||||
.postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values));
|
return this._api.parity.postTransaction(_options);
|
||||||
};
|
};
|
||||||
|
|
||||||
func.estimateGas = (options, values = []) => {
|
func.estimateGas = (options, values = []) => {
|
||||||
return this._api.eth
|
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||||
.estimateGas(this._encodeOptions(func, this._addOptionsTo(options), values));
|
return this._api.eth.estimateGas(_options);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,10 @@ export default class Ws extends JsonRpcBase {
|
|||||||
if (result.error) {
|
if (result.error) {
|
||||||
this.error(event.data);
|
this.error(event.data);
|
||||||
|
|
||||||
|
// Don't print error if request rejected...
|
||||||
|
if (!/rejected/.test(result.error.message)) {
|
||||||
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
const error = new TransportError(method, result.error.code, result.error.message);
|
const error = new TransportError(method, result.error.code, result.error.message);
|
||||||
reject(error);
|
reject(error);
|
||||||
|
@ -47,8 +47,6 @@ export function decodeMethodInput (methodAbi, paramdata) {
|
|||||||
throw new Error('Input to decodeMethodInput should be a hex value');
|
throw new Error('Input to decodeMethodInput should be a hex value');
|
||||||
} else if (paramdata.substr(0, 2) === '0x') {
|
} else if (paramdata.substr(0, 2) === '0x') {
|
||||||
return decodeMethodInput(methodAbi, paramdata.slice(2));
|
return decodeMethodInput(methodAbi, paramdata.slice(2));
|
||||||
} else if (paramdata.length % 64 !== 0) {
|
|
||||||
throw new Error('Parameter length in decodeMethodInput not a multiple of 64 characters');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,6 @@ describe('api/util/decode', () => {
|
|||||||
expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/);
|
expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws on invalid lengths', () => {
|
|
||||||
expect(() => decodeMethodInput({}, DATA.slice(-32))).to.throw(/not a multiple of/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly decodes valid inputs', () => {
|
it('correctly decodes valid inputs', () => {
|
||||||
expect(decodeMethodInput({
|
expect(decodeMethodInput({
|
||||||
type: 'function',
|
type: 'function',
|
||||||
|
@ -36,6 +36,7 @@ import ContextProvider from '~/ui/ContextProvider';
|
|||||||
import muiTheme from '~/ui/Theme';
|
import muiTheme from '~/ui/Theme';
|
||||||
import MainApplication from './main';
|
import MainApplication from './main';
|
||||||
|
|
||||||
|
import { patchApi } from '~/util/tx';
|
||||||
import { setApi } from '~/redux/providers/apiActions';
|
import { setApi } from '~/redux/providers/apiActions';
|
||||||
|
|
||||||
import './environment';
|
import './environment';
|
||||||
@ -60,6 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const api = new SecureApi(`ws://${parityUrl}`, token);
|
const api = new SecureApi(`ws://${parityUrl}`, token);
|
||||||
|
patchApi(api);
|
||||||
ContractInstances.create(api);
|
ContractInstances.create(api);
|
||||||
|
|
||||||
const store = initStore(api, hashHistory);
|
const store = initStore(api, hashHistory);
|
||||||
|
@ -14,264 +14,255 @@
|
|||||||
// 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 { observer } from 'mobx-react';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import { connect } from 'react-redux';
|
||||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
import { bindActionCreators } from 'redux';
|
||||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui';
|
import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui';
|
||||||
import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation';
|
import { AddIcon, CancelIcon, NextIcon, PrevIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import { eip20, wallet } from '~/contracts/abi';
|
import Store from './store';
|
||||||
|
|
||||||
const ABI_TYPES = [
|
@observer
|
||||||
{
|
class AddContract extends Component {
|
||||||
label: 'Token', readOnly: true, value: JSON.stringify(eip20),
|
|
||||||
type: 'token',
|
|
||||||
description: (<span>A standard <a href='https://github.com/ethereum/EIPs/issues/20' target='_blank'>ERC 20</a> token</span>)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Multisig Wallet', readOnly: true,
|
|
||||||
type: 'multisig',
|
|
||||||
value: JSON.stringify(wallet),
|
|
||||||
description: (<span>Official Multisig contract: <a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>see contract code</a></span>)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Custom Contract', value: '',
|
|
||||||
type: 'custom',
|
|
||||||
description: 'Contract created from custom ABI'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const STEPS = [ 'choose a contract type', 'enter contract details' ];
|
|
||||||
|
|
||||||
export default class AddContract extends Component {
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
contracts: PropTypes.object.isRequired,
|
contracts: PropTypes.object.isRequired,
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func
|
onClose: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
store = new Store(this.context.api, this.props.contracts);
|
||||||
abi: '',
|
|
||||||
abiError: ERRORS.invalidAbi,
|
|
||||||
abiType: ABI_TYPES[2],
|
|
||||||
abiTypeIndex: 2,
|
|
||||||
abiParsed: null,
|
|
||||||
address: '',
|
|
||||||
addressError: ERRORS.invalidAddress,
|
|
||||||
name: '',
|
|
||||||
nameError: ERRORS.invalidName,
|
|
||||||
description: '',
|
|
||||||
step: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.onChangeABIType(null, this.state.abiTypeIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { step } = this.state;
|
const { step } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible
|
|
||||||
actions={ this.renderDialogActions() }
|
actions={ this.renderDialogActions() }
|
||||||
steps={ STEPS }
|
|
||||||
current={ step }
|
current={ step }
|
||||||
>
|
steps={ [
|
||||||
{ this.renderStep(step) }
|
<FormattedMessage
|
||||||
|
id='addContract.title.type'
|
||||||
|
defaultMessage='choose a contract type'
|
||||||
|
key='type' />,
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.title.details'
|
||||||
|
defaultMessage='enter contract details'
|
||||||
|
key='details' />
|
||||||
|
] }
|
||||||
|
visible>
|
||||||
|
{ this.renderStep() }
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStep (step) {
|
renderStep () {
|
||||||
|
const { step } = this.store;
|
||||||
|
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 0:
|
case 0:
|
||||||
return this.renderContractTypeSelector();
|
return this.renderContractTypeSelector();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return this.renderFields();
|
return this.renderFields();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContractTypeSelector () {
|
renderContractTypeSelector () {
|
||||||
const { abiTypeIndex } = this.state;
|
const { abiTypeIndex, abiTypes } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButtons
|
<RadioButtons
|
||||||
name='contractType'
|
name='contractType'
|
||||||
value={ abiTypeIndex }
|
value={ abiTypeIndex }
|
||||||
values={ this.getAbiTypes() }
|
values={ abiTypes }
|
||||||
onChange={ this.onChangeABIType }
|
onChange={ this.onChangeABIType }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
const { addressError, nameError, step } = this.state;
|
const { step } = this.store;
|
||||||
const hasError = !!(addressError || nameError);
|
|
||||||
|
|
||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <ContentClear /> }
|
icon={ <CancelIcon /> }
|
||||||
label='Cancel'
|
key='cancel'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.button.cancel'
|
||||||
|
defaultMessage='Cancel' />
|
||||||
|
}
|
||||||
onClick={ this.onClose } />
|
onClick={ this.onClose } />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (step === 0) {
|
if (step === 0) {
|
||||||
const nextBtn = (
|
return [
|
||||||
|
cancelBtn,
|
||||||
<Button
|
<Button
|
||||||
icon={ <NavigationArrowForward /> }
|
icon={ <NextIcon /> }
|
||||||
label='Next'
|
key='next'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.button.next'
|
||||||
|
defaultMessage='Next' />
|
||||||
|
}
|
||||||
onClick={ this.onNext } />
|
onClick={ this.onNext } />
|
||||||
);
|
];
|
||||||
|
|
||||||
return [ cancelBtn, nextBtn ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevBtn = (
|
return [
|
||||||
|
cancelBtn,
|
||||||
<Button
|
<Button
|
||||||
icon={ <NavigationArrowBack /> }
|
icon={ <PrevIcon /> }
|
||||||
label='Back'
|
key='prev'
|
||||||
onClick={ this.onPrev } />
|
label={
|
||||||
);
|
<FormattedMessage
|
||||||
|
id='addContract.button.prev'
|
||||||
const addBtn = (
|
defaultMessage='Back' />
|
||||||
|
}
|
||||||
|
onClick={ this.onPrev } />,
|
||||||
<Button
|
<Button
|
||||||
icon={ <ContentAdd /> }
|
icon={ <AddIcon /> }
|
||||||
label='Add Contract'
|
key='add'
|
||||||
disabled={ hasError }
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.button.add'
|
||||||
|
defaultMessage='Add Contract' />
|
||||||
|
}
|
||||||
|
disabled={ this.store.hasError }
|
||||||
onClick={ this.onAdd } />
|
onClick={ this.onAdd } />
|
||||||
);
|
];
|
||||||
|
|
||||||
return [ cancelBtn, prevBtn, addBtn ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFields () {
|
renderFields () {
|
||||||
const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
|
const { abi, abiError, abiType, address, addressError, description, name, nameError } = this.store;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<InputAddress
|
<InputAddress
|
||||||
label='network address'
|
|
||||||
hint='the network address for the contract'
|
|
||||||
error={ addressError }
|
error={ addressError }
|
||||||
value={ address }
|
hint={
|
||||||
onSubmit={ this.onEditAddress }
|
<FormattedMessage
|
||||||
|
id='addContract.address.hint'
|
||||||
|
defaultMessage='the network address for the contract' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.address.label'
|
||||||
|
defaultMessage='network address' />
|
||||||
|
}
|
||||||
onChange={ this.onChangeAddress }
|
onChange={ this.onChangeAddress }
|
||||||
/>
|
onSubmit={ this.onEditAddress }
|
||||||
|
value={ address } />
|
||||||
<Input
|
<Input
|
||||||
label='contract name'
|
|
||||||
hint='a descriptive name for the contract'
|
|
||||||
error={ nameError }
|
error={ nameError }
|
||||||
value={ name }
|
hint={
|
||||||
onSubmit={ this.onEditName } />
|
<FormattedMessage
|
||||||
|
id='addContract.name.hint'
|
||||||
|
defaultMessage='a descriptive name for the contract' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.name.label'
|
||||||
|
defaultMessage='contract name' />
|
||||||
|
}
|
||||||
|
onSubmit={ this.onEditName }
|
||||||
|
value={ name } />
|
||||||
<Input
|
<Input
|
||||||
multiLine
|
hint={
|
||||||
rows={ 1 }
|
<FormattedMessage
|
||||||
label='(optional) contract description'
|
id='addContract.description.hint'
|
||||||
hint='an expanded description for the entry'
|
defaultMessage='an expanded description for the entry' />
|
||||||
value={ description }
|
}
|
||||||
onSubmit={ this.onEditDescription } />
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.description.label'
|
||||||
|
defaultMessage='(optional) contract description' />
|
||||||
|
}
|
||||||
|
onSubmit={ this.onEditDescription }
|
||||||
|
value={ description } />
|
||||||
<Input
|
<Input
|
||||||
label='contract abi'
|
|
||||||
hint='the abi for the contract'
|
|
||||||
error={ abiError }
|
error={ abiError }
|
||||||
value={ abi }
|
hint={
|
||||||
readOnly={ abiType.readOnly }
|
<FormattedMessage
|
||||||
|
id='addContract.abi.hint'
|
||||||
|
defaultMessage='the abi for the contract' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abi.label'
|
||||||
|
defaultMessage='contract abi' />
|
||||||
|
}
|
||||||
onSubmit={ this.onEditAbi }
|
onSubmit={ this.onEditAbi }
|
||||||
/>
|
readOnly={ abiType.readOnly }
|
||||||
|
value={ abi } />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAbiTypes () {
|
|
||||||
return ABI_TYPES.map((type, index) => ({
|
|
||||||
label: type.label,
|
|
||||||
description: type.description,
|
|
||||||
key: index,
|
|
||||||
...type
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
onNext = () => {
|
onNext = () => {
|
||||||
this.setState({ step: this.state.step + 1 });
|
this.store.nextStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPrev = () => {
|
onPrev = () => {
|
||||||
this.setState({ step: this.state.step - 1 });
|
this.store.prevStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeABIType = (value, index) => {
|
onChangeABIType = (value, index) => {
|
||||||
const abiType = value || ABI_TYPES[index];
|
this.store.setAbiTypeIndex(index);
|
||||||
this.setState({ abiTypeIndex: index, abiType });
|
|
||||||
this.onEditAbi(abiType.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAbi = (abiIn) => {
|
onEditAbi = (abi) => {
|
||||||
const { api } = this.context;
|
this.store.setAbi(abi);
|
||||||
const { abi, abiError, abiParsed } = validateAbi(abiIn, api);
|
|
||||||
|
|
||||||
this.setState({ abi, abiError, abiParsed });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeAddress = (event, value) => {
|
onChangeAddress = (event, address) => {
|
||||||
this.onEditAddress(value);
|
this.onEditAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAddress = (_address) => {
|
onEditAddress = (address) => {
|
||||||
const { contracts } = this.props;
|
this.store.setAddress(address);
|
||||||
let { address, addressError } = validateAddress(_address);
|
|
||||||
|
|
||||||
if (!addressError) {
|
|
||||||
const contract = contracts[address];
|
|
||||||
|
|
||||||
if (contract) {
|
|
||||||
addressError = ERRORS.duplicateAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
address,
|
|
||||||
addressError
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditDescription = (description) => {
|
onEditDescription = (description) => {
|
||||||
this.setState({ description });
|
this.store.setDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditName = (name) => {
|
onEditName = (name) => {
|
||||||
this.setState(validateName(name));
|
this.store.setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd = () => {
|
onAdd = () => {
|
||||||
const { api } = this.context;
|
return this.store
|
||||||
const { abiParsed, address, name, description, abiType } = this.state;
|
.addContract()
|
||||||
|
.then(() => {
|
||||||
Promise.all([
|
this.onClose();
|
||||||
api.parity.setAccountName(address, name),
|
|
||||||
api.parity.setAccountMeta(address, {
|
|
||||||
contract: true,
|
|
||||||
deleted: false,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
abi: abiParsed,
|
|
||||||
type: abiType.type,
|
|
||||||
description
|
|
||||||
})
|
})
|
||||||
]).catch((error) => {
|
.catch((error) => {
|
||||||
console.error('onAdd', error);
|
this.props.newError(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.onClose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(AddContract);
|
||||||
|
84
js/src/modals/AddContract/addContract.spec.js
Normal file
84
js/src/modals/AddContract/addContract.spec.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import AddContract from './';
|
||||||
|
|
||||||
|
import { CONTRACTS, createApi, createRedux } from './addContract.test.js';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let onClose;
|
||||||
|
let reduxStore;
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
api = createApi();
|
||||||
|
onClose = sinon.stub();
|
||||||
|
reduxStore = createRedux();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<AddContract
|
||||||
|
{ ...props }
|
||||||
|
contracts={ CONTRACTS }
|
||||||
|
onClose={ onClose } />,
|
||||||
|
{ context: { store: reduxStore } }
|
||||||
|
).find('AddContract').shallow({ context: { api } });
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/AddContract', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the defauls', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onAdd', () => {
|
||||||
|
it('calls store addContract', () => {
|
||||||
|
sinon.stub(instance.store, 'addContract').resolves(true);
|
||||||
|
return instance.onAdd().then(() => {
|
||||||
|
expect(instance.store.addContract).to.have.been.called;
|
||||||
|
instance.store.addContract.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls closes dialog on success', () => {
|
||||||
|
sinon.stub(instance.store, 'addContract').resolves(true);
|
||||||
|
return instance.onAdd().then(() => {
|
||||||
|
expect(onClose).to.have.been.called;
|
||||||
|
instance.store.addContract.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds newError on failure', () => {
|
||||||
|
sinon.stub(instance.store, 'addContract').rejects('test');
|
||||||
|
return instance.onAdd().then(() => {
|
||||||
|
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||||
|
instance.store.addContract.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
49
js/src/modals/AddContract/addContract.test.js
Normal file
49
js/src/modals/AddContract/addContract.test.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 sinon from 'sinon';
|
||||||
|
|
||||||
|
const ABI = '[{"constant":true,"inputs":[],"name":"totalDonated","outputs":[{"name":"","type":"uint256"}],"type":"function"}]';
|
||||||
|
|
||||||
|
const CONTRACTS = {
|
||||||
|
'0x1234567890123456789012345678901234567890': {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
return {
|
||||||
|
parity: {
|
||||||
|
setAccountMeta: sinon.stub().resolves(),
|
||||||
|
setAccountName: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRedux () {
|
||||||
|
return {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ABI,
|
||||||
|
CONTRACTS,
|
||||||
|
createApi,
|
||||||
|
createRedux
|
||||||
|
};
|
126
js/src/modals/AddContract/store.js
Normal file
126
js/src/modals/AddContract/store.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// 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 { action, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
|
import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation';
|
||||||
|
|
||||||
|
import { ABI_TYPES } from './types';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable abi = '';
|
||||||
|
@observable abiError = ERRORS.invalidAbi;
|
||||||
|
@observable abiParsed = null;
|
||||||
|
@observable abiTypes = ABI_TYPES;
|
||||||
|
@observable abiTypeIndex = 0;
|
||||||
|
@observable address = '';
|
||||||
|
@observable addressError = ERRORS.invalidAddress;
|
||||||
|
@observable description = '';
|
||||||
|
@observable name = '';
|
||||||
|
@observable nameError = ERRORS.invalidName;
|
||||||
|
@observable step = 0;
|
||||||
|
|
||||||
|
constructor (api, contracts) {
|
||||||
|
this._api = api;
|
||||||
|
this._contracts = contracts;
|
||||||
|
|
||||||
|
this.setAbiTypeIndex(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get abiType () {
|
||||||
|
return this.abiTypes[this.abiTypeIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get hasError () {
|
||||||
|
return !!(this.abiError || this.addressError || this.nameError);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action nextStep = () => {
|
||||||
|
this.step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action prevStep = () => {
|
||||||
|
this.step--;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAbi = (_abi) => {
|
||||||
|
const { abi, abiError, abiParsed } = validateAbi(_abi);
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
|
this.abi = abi;
|
||||||
|
this.abiError = abiError;
|
||||||
|
this.abiParsed = abiParsed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAbiTypeIndex = (abiTypeIndex) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.abiTypeIndex = abiTypeIndex;
|
||||||
|
this.setAbi(this.abiTypes[abiTypeIndex].value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAddress = (_address) => {
|
||||||
|
let { address, addressError } = validateAddress(_address);
|
||||||
|
|
||||||
|
if (!addressError) {
|
||||||
|
const contract = this._contracts[address];
|
||||||
|
|
||||||
|
if (contract) {
|
||||||
|
addressError = ERRORS.duplicateAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
|
this.address = address;
|
||||||
|
this.addressError = addressError;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDescription = (description) => {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setName = (_name) => {
|
||||||
|
const { name, nameError } = validateName(_name);
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
|
this.name = name;
|
||||||
|
this.nameError = nameError;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addContract () {
|
||||||
|
const meta = {
|
||||||
|
contract: true,
|
||||||
|
deleted: false,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
abi: this.abiParsed,
|
||||||
|
type: this.abiType.type,
|
||||||
|
description: this.description
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._api.parity.setAccountName(this.address, this.name),
|
||||||
|
this._api.parity.setAccountMeta(this.address, meta)
|
||||||
|
])
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('addContract', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
171
js/src/modals/AddContract/store.spec.js
Normal file
171
js/src/modals/AddContract/store.spec.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// 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 Store from './store';
|
||||||
|
|
||||||
|
import { ABI, CONTRACTS, createApi } from './addContract.test.js';
|
||||||
|
|
||||||
|
const INVALID_ADDR = '0x123';
|
||||||
|
const VALID_ADDR = '0x5A5eFF38DA95b0D58b6C616f2699168B480953C9';
|
||||||
|
const DUPE_ADDR = Object.keys(CONTRACTS)[0];
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createStore () {
|
||||||
|
api = createApi();
|
||||||
|
store = new Store(api, CONTRACTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/AddContract/Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createStore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('creates an instance', () => {
|
||||||
|
expect(store).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to custom ABI', () => {
|
||||||
|
expect(store.abiType.type).to.equal('custom');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@actions', () => {
|
||||||
|
describe('nextStep/prevStep', () => {
|
||||||
|
it('moves to the next/prev step', () => {
|
||||||
|
expect(store.step).to.equal(0);
|
||||||
|
store.nextStep();
|
||||||
|
expect(store.step).to.equal(1);
|
||||||
|
store.prevStep();
|
||||||
|
expect(store.step).to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setAbiTypeIndex', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setAbiTypeIndex(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes the index', () => {
|
||||||
|
expect(store.abiTypeIndex).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes the abi', () => {
|
||||||
|
expect(store.abi).to.deep.equal(store.abiTypes[1].value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setAddress', () => {
|
||||||
|
it('sets a valid address', () => {
|
||||||
|
store.setAddress(VALID_ADDR);
|
||||||
|
expect(store.address).to.equal(VALID_ADDR);
|
||||||
|
expect(store.addressError).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the error on invalid address', () => {
|
||||||
|
store.setAddress(INVALID_ADDR);
|
||||||
|
expect(store.address).to.equal(INVALID_ADDR);
|
||||||
|
expect(store.addressError).not.to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the error on suplicate address', () => {
|
||||||
|
store.setAddress(DUPE_ADDR);
|
||||||
|
expect(store.address).to.equal(DUPE_ADDR);
|
||||||
|
expect(store.addressError).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setDescription', () => {
|
||||||
|
it('sets the description', () => {
|
||||||
|
store.setDescription('test description');
|
||||||
|
expect(store.description).to.equal('test description');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setName', () => {
|
||||||
|
it('sets the name', () => {
|
||||||
|
store.setName('some name');
|
||||||
|
expect(store.name).to.equal('some name');
|
||||||
|
expect(store.nameError).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the error', () => {
|
||||||
|
store.setName('s');
|
||||||
|
expect(store.name).to.equal('s');
|
||||||
|
expect(store.nameError).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('@computed', () => {
|
||||||
|
describe('abiType', () => {
|
||||||
|
it('matches the index', () => {
|
||||||
|
expect(store.abiType).to.deep.equal(store.abiTypes[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasError', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setAddress(VALID_ADDR);
|
||||||
|
store.setName('valid name');
|
||||||
|
store.setAbi(ABI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is false with no errors', () => {
|
||||||
|
expect(store.hasError).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is true with address error', () => {
|
||||||
|
store.setAddress(DUPE_ADDR);
|
||||||
|
expect(store.hasError).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is true with name error', () => {
|
||||||
|
store.setName('s');
|
||||||
|
expect(store.hasError).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is true with abi error', () => {
|
||||||
|
store.setAbi('');
|
||||||
|
expect(store.hasError).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('interactions', () => {
|
||||||
|
describe('addContract', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setAddress(VALID_ADDR);
|
||||||
|
store.setName('valid name');
|
||||||
|
store.setAbi(ABI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the account name', () => {
|
||||||
|
return store.addContract().then(() => {
|
||||||
|
expect(api.parity.setAccountName).to.have.been.calledWith(VALID_ADDR, 'valid name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the account meta', () => {
|
||||||
|
return store.addContract().then(() => {
|
||||||
|
expect(api.parity.setAccountMeta).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
89
js/src/modals/AddContract/types.js
Normal file
89
js/src/modals/AddContract/types.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// 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 from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { eip20, wallet } from '~/contracts/abi';
|
||||||
|
|
||||||
|
const ABI_TYPES = [
|
||||||
|
{
|
||||||
|
description: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.token.description'
|
||||||
|
defaultMessage='A standard {erc20} token'
|
||||||
|
values={ {
|
||||||
|
erc20: (
|
||||||
|
<a href='https://github.com/ethereum/EIPs/issues/20' target='_blank'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.token.erc20'
|
||||||
|
defaultMessage='ERC 20' />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
} } />
|
||||||
|
),
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.token.label'
|
||||||
|
defaultMessage='Token' />
|
||||||
|
),
|
||||||
|
readOnly: true,
|
||||||
|
type: 'token',
|
||||||
|
value: JSON.stringify(eip20)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.multisigWallet.description'
|
||||||
|
defaultMessage='Ethereum Multisig contract {link}'
|
||||||
|
values={ {
|
||||||
|
link: (
|
||||||
|
<a href='https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol' target='_blank'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.multisigWallet.link'
|
||||||
|
defaultMessage='see contract code' />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
} } />
|
||||||
|
),
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.multisigWallet.label'
|
||||||
|
defaultMessage='Multisig Wallet' />
|
||||||
|
),
|
||||||
|
readOnly: true,
|
||||||
|
type: 'multisig',
|
||||||
|
value: JSON.stringify(wallet)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.custom.description'
|
||||||
|
defaultMessage='Contract created from custom ABI' />
|
||||||
|
),
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addContract.abiType.custom.label'
|
||||||
|
defaultMessage='Custom Contract' />
|
||||||
|
),
|
||||||
|
type: 'custom',
|
||||||
|
value: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export {
|
||||||
|
ABI_TYPES
|
||||||
|
};
|
@ -18,28 +18,33 @@ import React from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
noFile:
|
noFile: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noFile'
|
id='createAccount.error.noFile'
|
||||||
defaultMessage='select a valid wallet file to import' />,
|
defaultMessage='select a valid wallet file to import' />
|
||||||
|
),
|
||||||
|
|
||||||
noKey:
|
noKey: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noKey'
|
id='createAccount.error.noKey'
|
||||||
defaultMessage='you need to provide the raw private key' />,
|
defaultMessage='you need to provide the raw private key' />
|
||||||
|
),
|
||||||
|
|
||||||
noMatchPassword:
|
noMatchPassword: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noMatchPassword'
|
id='createAccount.error.noMatchPassword'
|
||||||
defaultMessage='the supplied passwords does not match' />,
|
defaultMessage='the supplied passwords does not match' />
|
||||||
|
),
|
||||||
|
|
||||||
noName:
|
noName: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.noName'
|
id='createAccount.error.noName'
|
||||||
defaultMessage='you need to specify a valid name for the account' />,
|
defaultMessage='you need to specify a valid name for the account' />
|
||||||
|
),
|
||||||
|
|
||||||
invalidKey:
|
invalidKey: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createAccount.error.invalidKey'
|
id='createAccount.error.invalidKey'
|
||||||
defaultMessage='the raw key needs to be hex, 64 characters in length and contain the prefix "0x"' />
|
defaultMessage='the raw key needs to be hex, 64 characters in length and contain the prefix "0x"' />
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// 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 { omitBy } from 'lodash';
|
||||||
|
|
||||||
import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui';
|
import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui';
|
||||||
|
|
||||||
@ -73,6 +74,9 @@ export default class WalletDetails extends Component {
|
|||||||
renderMultisigDetails () {
|
renderMultisigDetails () {
|
||||||
const { accounts, wallet, errors } = this.props;
|
const { accounts, wallet, errors } = this.props;
|
||||||
|
|
||||||
|
// Wallets cannot create contracts
|
||||||
|
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
@ -81,7 +85,7 @@ export default class WalletDetails extends Component {
|
|||||||
value={ wallet.account }
|
value={ wallet.account }
|
||||||
error={ errors.account }
|
error={ errors.account }
|
||||||
onChange={ this.onAccoutChange }
|
onChange={ this.onAccoutChange }
|
||||||
accounts={ accounts }
|
accounts={ _accounts }
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
@ -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 { pick } from 'lodash';
|
import { pick, omitBy } from 'lodash';
|
||||||
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';
|
||||||
@ -34,29 +34,33 @@ import { ERROR_CODES } from '~/api/transport/error';
|
|||||||
|
|
||||||
const STEPS = {
|
const STEPS = {
|
||||||
CONTRACT_DETAILS: {
|
CONTRACT_DETAILS: {
|
||||||
title:
|
title: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.title.details'
|
id='deployContract.title.details'
|
||||||
defaultMessage='contract details' />
|
defaultMessage='contract details' />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
CONTRACT_PARAMETERS: {
|
CONTRACT_PARAMETERS: {
|
||||||
title:
|
title: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.title.parameters'
|
id='deployContract.title.parameters'
|
||||||
defaultMessage='contract parameters' />
|
defaultMessage='contract parameters' />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
DEPLOYMENT: {
|
DEPLOYMENT: {
|
||||||
waiting: true,
|
waiting: true,
|
||||||
title:
|
title: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.title.deployment'
|
id='deployContract.title.deployment'
|
||||||
defaultMessage='deployment' />
|
defaultMessage='deployment' />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
COMPLETED: {
|
COMPLETED: {
|
||||||
title:
|
title: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.title.completed'
|
id='deployContract.title.completed'
|
||||||
defaultMessage='completed' />
|
defaultMessage='completed' />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -495,48 +499,53 @@ class DeployContract extends Component {
|
|||||||
case 'estimateGas':
|
case 'estimateGas':
|
||||||
case 'postTransaction':
|
case 'postTransaction':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState:
|
deployState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.state.preparing'
|
id='deployContract.state.preparing'
|
||||||
defaultMessage='Preparing transaction for network transmission' />
|
defaultMessage='Preparing transaction for network transmission' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'checkRequest':
|
case 'checkRequest':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState:
|
deployState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.state.waitSigner'
|
id='deployContract.state.waitSigner'
|
||||||
defaultMessage='Waiting for confirmation of the transaction in the Parity Secure Signer' />
|
defaultMessage='Waiting for confirmation of the transaction in the Parity Secure Signer' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'getTransactionReceipt':
|
case 'getTransactionReceipt':
|
||||||
this.setState({
|
this.setState({
|
||||||
txhash: data.txhash,
|
txhash: data.txhash,
|
||||||
deployState:
|
deployState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.state.waitReceipt'
|
id='deployContract.state.waitReceipt'
|
||||||
defaultMessage='Waiting for the contract deployment transaction receipt' />
|
defaultMessage='Waiting for the contract deployment transaction receipt' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'hasReceipt':
|
case 'hasReceipt':
|
||||||
case 'getCode':
|
case 'getCode':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState:
|
deployState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.state.validatingCode'
|
id='deployContract.state.validatingCode'
|
||||||
defaultMessage='Validating the deployed contract code' />
|
defaultMessage='Validating the deployed contract code' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'completed':
|
case 'completed':
|
||||||
this.setState({
|
this.setState({
|
||||||
deployState:
|
deployState: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='deployContract.state.completed'
|
id='deployContract.state.completed'
|
||||||
defaultMessage='The contract deployment has been completed' />
|
defaultMessage='The contract deployment has been completed' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -552,13 +561,19 @@ class DeployContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState, initProps) {
|
function mapStateToProps (initState, initProps) {
|
||||||
const fromAddresses = Object.keys(initProps.accounts);
|
const { accounts } = initProps;
|
||||||
|
|
||||||
|
// Skip Wallet accounts : they can't create Contracts
|
||||||
|
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||||
|
|
||||||
|
const fromAddresses = Object.keys(_accounts);
|
||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const balances = pick(state.balances.balances, fromAddresses);
|
const balances = pick(state.balances.balances, fromAddresses);
|
||||||
const { gasLimit } = state.nodeStatus;
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
accounts: _accounts,
|
||||||
balances,
|
balances,
|
||||||
gasLimit
|
gasLimit
|
||||||
};
|
};
|
||||||
|
@ -17,20 +17,24 @@
|
|||||||
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 { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import { newError } from '~/redux/actions';
|
||||||
import { Button, Form, Input, InputChip, Modal } from '~/ui';
|
import { Button, Form, Input, InputChip, Modal } from '~/ui';
|
||||||
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
import { CancelIcon, SaveIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export default class EditMeta extends Component {
|
class EditMeta extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired
|
onClose: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +89,7 @@ export default class EditMeta extends Component {
|
|||||||
defaultMessage='(optional) tags' />
|
defaultMessage='(optional) tags' />
|
||||||
}
|
}
|
||||||
onTokensChange={ this.store.setTags }
|
onTokensChange={ this.store.setTags }
|
||||||
tokens={ tags } />
|
tokens={ tags.slice() } />
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -138,6 +142,20 @@ export default class EditMeta extends Component {
|
|||||||
|
|
||||||
return this.store
|
return this.store
|
||||||
.save()
|
.save()
|
||||||
.then(() => this.props.onClose());
|
.then(() => this.props.onClose())
|
||||||
|
.catch((error) => {
|
||||||
|
this.props.newError(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({
|
||||||
|
newError
|
||||||
|
}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
null,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(EditMeta);
|
||||||
|
@ -20,30 +20,27 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import EditMeta from './';
|
import EditMeta from './';
|
||||||
|
|
||||||
import { ACCOUNT } from './editMeta.test.js';
|
import { ACCOUNT, createApi, createRedux } from './editMeta.test.js';
|
||||||
|
|
||||||
|
let api;
|
||||||
let component;
|
let component;
|
||||||
|
let instance;
|
||||||
let onClose;
|
let onClose;
|
||||||
|
let reduxStore;
|
||||||
|
|
||||||
function render (props) {
|
function render (props) {
|
||||||
|
api = createApi();
|
||||||
onClose = sinon.stub();
|
onClose = sinon.stub();
|
||||||
|
reduxStore = createRedux();
|
||||||
|
|
||||||
component = shallow(
|
component = shallow(
|
||||||
<EditMeta
|
<EditMeta
|
||||||
{ ...props }
|
{ ...props }
|
||||||
account={ ACCOUNT }
|
account={ ACCOUNT }
|
||||||
onClose={ onClose } />,
|
onClose={ onClose } />,
|
||||||
{
|
{ context: { store: reduxStore } }
|
||||||
context: {
|
).find('EditMeta').shallow({ context: { api } });
|
||||||
api: {
|
instance = component.instance();
|
||||||
parity: {
|
|
||||||
setAccountName: sinon.stub().resolves(),
|
|
||||||
setAccountMeta: sinon.stub().resolves()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@ -61,15 +58,29 @@ describe('modals/EditMeta', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('onSave', () => {
|
describe('onSave', () => {
|
||||||
it('calls store.save() & props.onClose', () => {
|
it('calls store.save', () => {
|
||||||
const instance = component.instance();
|
|
||||||
sinon.spy(instance.store, 'save');
|
sinon.spy(instance.store, 'save');
|
||||||
|
|
||||||
instance.onSave().then(() => {
|
return instance.onSave().then(() => {
|
||||||
expect(instance.store.save).to.have.been.called;
|
expect(instance.store.save).to.have.been.called;
|
||||||
|
instance.store.save.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the dialog on success', () => {
|
||||||
|
return instance.onSave().then(() => {
|
||||||
expect(onClose).to.have.been.called;
|
expect(onClose).to.have.been.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('adds newError on failure', () => {
|
||||||
|
sinon.stub(instance.store, 'save').rejects('test');
|
||||||
|
|
||||||
|
return instance.onSave().then(() => {
|
||||||
|
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||||
|
instance.store.save.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
// 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 sinon from 'sinon';
|
||||||
|
|
||||||
const ACCOUNT = {
|
const ACCOUNT = {
|
||||||
address: '0x123456789a123456789a123456789a123456789a',
|
address: '0x123456789a123456789a123456789a123456789a',
|
||||||
meta: {
|
meta: {
|
||||||
@ -39,7 +41,28 @@ const ADDRESS = {
|
|||||||
name: 'Random address'
|
name: 'Random address'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createApi () {
|
||||||
|
return {
|
||||||
|
parity: {
|
||||||
|
setAccountName: sinon.stub().resolves(),
|
||||||
|
setAccountMeta: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRedux () {
|
||||||
|
return {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
ADDRESS
|
ADDRESS,
|
||||||
|
createApi,
|
||||||
|
createRedux
|
||||||
};
|
};
|
||||||
|
@ -14,34 +14,35 @@
|
|||||||
// 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 { action, computed, observable, toJS, transaction } from 'mobx';
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
import { newError } from '~/redux/actions';
|
|
||||||
import { validateName } from '~/util/validation';
|
import { validateName } from '~/util/validation';
|
||||||
|
|
||||||
export default class Store {
|
export default class Store {
|
||||||
@observable address = null;
|
@observable address = null;
|
||||||
@observable isAccount = false;
|
@observable isAccount = false;
|
||||||
@observable description = null;
|
@observable description = null;
|
||||||
@observable meta = {};
|
@observable meta = null;
|
||||||
@observable name = null;
|
@observable name = null;
|
||||||
@observable nameError = null;
|
@observable nameError = null;
|
||||||
@observable passwordHint = null;
|
@observable passwordHint = null;
|
||||||
@observable tags = [];
|
@observable tags = null;
|
||||||
|
|
||||||
constructor (api, account) {
|
constructor (api, account) {
|
||||||
const { address, name, meta, uuid } = account;
|
const { address, name, meta, uuid } = account;
|
||||||
|
|
||||||
this._api = api;
|
this._api = api;
|
||||||
|
|
||||||
|
transaction(() => {
|
||||||
this.isAccount = !!uuid;
|
this.isAccount = !!uuid;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.meta = Object.assign({}, meta || {});
|
this.meta = meta || {};
|
||||||
this.name = name || '';
|
this.name = name || '';
|
||||||
|
|
||||||
this.description = this.meta.description || '';
|
this.description = this.meta.description || '';
|
||||||
this.passwordHint = this.meta.passwordHint || '';
|
this.passwordHint = this.meta.passwordHint || '';
|
||||||
this.tags = [].concat((meta || {}).tags || []);
|
this.tags = this.meta.tags && this.meta.tags.peek() || [];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get hasError () {
|
@computed get hasError () {
|
||||||
@ -70,7 +71,7 @@ export default class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action setTags = (tags) => {
|
@action setTags = (tags) => {
|
||||||
this.tags = [].concat(tags);
|
this.tags = tags.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
@ -86,12 +87,11 @@ export default class Store {
|
|||||||
return Promise
|
return Promise
|
||||||
.all([
|
.all([
|
||||||
this._api.parity.setAccountName(this.address, this.name),
|
this._api.parity.setAccountName(this.address, this.name),
|
||||||
this._api.parity.setAccountMeta(this.address, Object.assign({}, toJS(this.meta), meta))
|
this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta))
|
||||||
])
|
])
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('onSave', error);
|
console.error('onSave', error);
|
||||||
|
throw error;
|
||||||
newError(error);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,14 @@
|
|||||||
// 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 { toJS } from 'mobx';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
import { ACCOUNT, ADDRESS } from './editMeta.test.js';
|
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
|
||||||
|
|
||||||
let api;
|
let api;
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
function createStore (account) {
|
function createStore (account) {
|
||||||
api = {
|
api = createApi();
|
||||||
parity: {
|
|
||||||
setAccountName: sinon.stub().resolves(),
|
|
||||||
setAccountMeta: sinon.stub().resolves()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
store = new Store(api, account);
|
store = new Store(api, account);
|
||||||
|
|
||||||
@ -56,12 +48,12 @@ describe('modals/EditMeta/Store', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('extracts the tags', () => {
|
it('extracts the tags', () => {
|
||||||
expect(store.tags.peek()).to.deep.equal(ACCOUNT.meta.tags);
|
expect(store.tags).to.deep.equal(ACCOUNT.meta.tags);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('meta', () => {
|
describe('meta', () => {
|
||||||
it('extracts the full meta', () => {
|
it('extracts the full meta', () => {
|
||||||
expect(toJS(store.meta)).to.deep.equal(ACCOUNT.meta);
|
expect(store.meta).to.deep.equal(ACCOUNT.meta);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('extracts the description', () => {
|
it('extracts the description', () => {
|
||||||
|
@ -22,7 +22,7 @@ import { ContextProvider, muiTheme } from '~/ui';
|
|||||||
|
|
||||||
import DetailsStep from './';
|
import DetailsStep from './';
|
||||||
|
|
||||||
import { STORE, CONTRACT } from '../executeContract.test.js';
|
import { createApi, STORE, CONTRACT } from '../executeContract.test.js';
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let onAmountChange;
|
let onAmountChange;
|
||||||
@ -41,7 +41,7 @@ function render (props) {
|
|||||||
onValueChange = sinon.stub();
|
onValueChange = sinon.stub();
|
||||||
|
|
||||||
component = mount(
|
component = mount(
|
||||||
<ContextProvider api={ {} } muiTheme={ muiTheme } store={ STORE }>
|
<ContextProvider api={ createApi() } muiTheme={ muiTheme } store={ STORE }>
|
||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...props }
|
{ ...props }
|
||||||
contract={ CONTRACT }
|
contract={ CONTRACT }
|
||||||
|
@ -39,26 +39,31 @@ const STEP_BUSY_OR_ADVANCED = 1;
|
|||||||
const STEP_BUSY = 2;
|
const STEP_BUSY = 2;
|
||||||
|
|
||||||
const TITLES = {
|
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];
|
||||||
const STAGES_ADVANCED = [TITLES.transfer, TITLES.advanced, TITLES.sending, TITLES.complete];
|
const STAGES_ADVANCED = [TITLES.transfer, TITLES.advanced, TITLES.sending, TITLES.complete];
|
||||||
@ -384,6 +389,7 @@ class ExecuteContract extends Component {
|
|||||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
const { advancedOptions, amount, func, minBlock, 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 = {
|
||||||
gas: this.gasStore.gas,
|
gas: this.gasStore.gas,
|
||||||
gasPrice: this.gasStore.price,
|
gasPrice: this.gasStore.price,
|
||||||
@ -398,10 +404,11 @@ class ExecuteContract extends Component {
|
|||||||
.postTransaction(options, values)
|
.postTransaction(options, values)
|
||||||
.then((requestId) => {
|
.then((requestId) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
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' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
return api
|
return api
|
||||||
@ -420,10 +427,11 @@ class ExecuteContract extends Component {
|
|||||||
sending: false,
|
sending: false,
|
||||||
step: finalstep,
|
step: finalstep,
|
||||||
txhash,
|
txhash,
|
||||||
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' />
|
||||||
|
)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -20,7 +20,7 @@ import sinon from 'sinon';
|
|||||||
|
|
||||||
import ExecuteContract from './';
|
import ExecuteContract from './';
|
||||||
|
|
||||||
import { CONTRACT, STORE } from './executeContract.test.js';
|
import { createApi, CONTRACT, STORE } from './executeContract.test.js';
|
||||||
|
|
||||||
let component;
|
let component;
|
||||||
let onClose;
|
let onClose;
|
||||||
@ -36,7 +36,7 @@ function render (props) {
|
|||||||
contract={ CONTRACT }
|
contract={ CONTRACT }
|
||||||
onClose={ onClose }
|
onClose={ onClose }
|
||||||
onFromAddressChange={ onFromAddressChange } />,
|
onFromAddressChange={ onFromAddressChange } />,
|
||||||
{ context: { api: {}, store: STORE } }
|
{ context: { api: createApi(), store: STORE } }
|
||||||
).find('ExecuteContract').shallow();
|
).find('ExecuteContract').shallow();
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
|
@ -64,7 +64,19 @@ const STORE = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createApi (result = true) {
|
||||||
|
return {
|
||||||
|
parity: {
|
||||||
|
registryAddress: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
|
||||||
|
},
|
||||||
|
util: {
|
||||||
|
sha3: sinon.stub().resolves('0x0000000000000000000000000000000000000000')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
createApi,
|
||||||
CONTRACT,
|
CONTRACT,
|
||||||
STORE
|
STORE
|
||||||
};
|
};
|
||||||
|
@ -14,26 +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/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
|
||||||
import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
|
||||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
|
||||||
|
|
||||||
import { Tabs, Tab } from 'material-ui/Tabs';
|
|
||||||
import Paper from 'material-ui/Paper';
|
import Paper from 'material-ui/Paper';
|
||||||
|
import { Tabs, Tab } from 'material-ui/Tabs';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { showSnackbar } from '~/redux/providers/snackbarActions';
|
|
||||||
|
|
||||||
import Form, { Input } from '~/ui/Form';
|
import { newError, openSnackbar } from '~/redux/actions';
|
||||||
import { Button, Modal, IdentityName, IdentityIcon } from '~/ui';
|
import { Button, Modal, IdentityName, IdentityIcon } from '~/ui';
|
||||||
|
import Form, { Input } from '~/ui/Form';
|
||||||
|
import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
|
import Store, { CHANGE_ACTION, TEST_ACTION } from './store';
|
||||||
import styles from './passwordManager.css';
|
import styles from './passwordManager.css';
|
||||||
|
|
||||||
const TEST_ACTION = 'TEST_ACTION';
|
const MSG_SUCCESS_STYLE = {
|
||||||
const CHANGE_ACTION = 'CHANGE_ACTION';
|
backgroundColor: 'rgba(174, 213, 129, 0.75)'
|
||||||
|
};
|
||||||
|
const MSG_FAILURE_STYLE = {
|
||||||
|
backgroundColor: 'rgba(229, 115, 115, 0.75)'
|
||||||
|
};
|
||||||
|
const TABS_INKBAR_STYLE = {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.55)'
|
||||||
|
};
|
||||||
|
const TABS_ITEM_STYLE = {
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.05)'
|
||||||
|
};
|
||||||
|
|
||||||
|
@observer
|
||||||
class PasswordManager extends Component {
|
class PasswordManager extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
@ -41,27 +51,22 @@ class PasswordManager extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
showSnackbar: PropTypes.func.isRequired,
|
openSnackbar: PropTypes.func.isRequired,
|
||||||
|
newError: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func
|
onClose: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
store = new Store(this.context.api, this.props.account);
|
||||||
action: TEST_ACTION,
|
|
||||||
waiting: false,
|
|
||||||
showMessage: false,
|
|
||||||
message: { value: '', success: true },
|
|
||||||
currentPass: '',
|
|
||||||
newPass: '',
|
|
||||||
repeatNewPass: '',
|
|
||||||
repeatValid: true,
|
|
||||||
passwordHint: this.props.account.meta && this.props.account.meta.passwordHint || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
actions={ this.renderDialogActions() }
|
actions={ this.renderDialogActions() }
|
||||||
title='Password Manager'
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.title'
|
||||||
|
defaultMessage='Password Manager' />
|
||||||
|
}
|
||||||
visible>
|
visible>
|
||||||
{ this.renderAccount() }
|
{ this.renderAccount() }
|
||||||
{ this.renderPage() }
|
{ this.renderPage() }
|
||||||
@ -71,150 +76,168 @@ class PasswordManager extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMessage () {
|
renderMessage () {
|
||||||
const { message, showMessage } = this.state;
|
const { infoMessage } = this.store;
|
||||||
|
|
||||||
const style = message.success
|
if (!infoMessage) {
|
||||||
? {
|
return null;
|
||||||
backgroundColor: 'rgba(174, 213, 129, 0.75)'
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
backgroundColor: 'rgba(229, 115, 115, 0.75)'
|
|
||||||
};
|
|
||||||
|
|
||||||
const classes = [ styles.message ];
|
|
||||||
|
|
||||||
if (!showMessage) {
|
|
||||||
classes.push(styles.hideMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
zDepth={ 1 }
|
className={ `${styles.message}` }
|
||||||
style={ style }
|
style={
|
||||||
className={ classes.join(' ') }>
|
infoMessage.success
|
||||||
{ message.value }
|
? MSG_SUCCESS_STYLE
|
||||||
|
: MSG_FAILURE_STYLE
|
||||||
|
}
|
||||||
|
zDepth={ 1 }>
|
||||||
|
{ infoMessage.value }
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAccount () {
|
renderAccount () {
|
||||||
const { account } = this.props;
|
const { address, passwordHint } = this.store;
|
||||||
const { address, meta } = account;
|
|
||||||
|
|
||||||
const passwordHint = meta && meta.passwordHint
|
|
||||||
? (
|
|
||||||
<span className={ styles.passwordHint }>
|
|
||||||
<span className={ styles.hintLabel }>Hint </span>
|
|
||||||
{ meta.passwordHint }
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.accountContainer }>
|
<div className={ styles.accountContainer }>
|
||||||
<IdentityIcon
|
<IdentityIcon address={ address } />
|
||||||
address={ address }
|
|
||||||
/>
|
|
||||||
<div className={ styles.accountInfos }>
|
<div className={ styles.accountInfos }>
|
||||||
<IdentityName
|
<IdentityName
|
||||||
className={ styles.accountName }
|
|
||||||
address={ address }
|
address={ address }
|
||||||
unknown
|
className={ styles.accountName }
|
||||||
/>
|
unknown />
|
||||||
<span className={ styles.accountAddress }>
|
<span className={ styles.accountAddress }>
|
||||||
{ address }
|
{ address }
|
||||||
</span>
|
</span>
|
||||||
{ passwordHint }
|
<span className={ styles.passwordHint }>
|
||||||
|
<span className={ styles.hintLabel }>Hint </span>
|
||||||
|
{ passwordHint || '-' }
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPage () {
|
renderPage () {
|
||||||
const { account } = this.props;
|
const { busy, isRepeatValid, passwordHint } = this.store;
|
||||||
const { waiting, repeatValid } = this.state;
|
|
||||||
const disabled = !!waiting;
|
|
||||||
|
|
||||||
const repeatError = repeatValid
|
|
||||||
? null
|
|
||||||
: 'the two passwords differ';
|
|
||||||
|
|
||||||
const { meta } = account;
|
|
||||||
const passwordHint = meta && meta.passwordHint || '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
inkBarStyle={ {
|
inkBarStyle={ TABS_INKBAR_STYLE }
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.55)'
|
tabItemContainerStyle={ TABS_ITEM_STYLE }>
|
||||||
} }
|
|
||||||
tabItemContainerStyle={ {
|
|
||||||
backgroundColor: 'rgba(255, 255, 255, 0.05)'
|
|
||||||
} }
|
|
||||||
>
|
|
||||||
<Tab
|
<Tab
|
||||||
onActive={ this.handleTestActive }
|
label={
|
||||||
label='Test Password'
|
<FormattedMessage
|
||||||
>
|
id='passwordChange.tabTest.label'
|
||||||
<Form
|
defaultMessage='Test Password' />
|
||||||
className={ styles.form }
|
}
|
||||||
>
|
onActive={ this.onActivateTestTab }>
|
||||||
|
<Form className={ styles.form }>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label='password'
|
disabled={ busy }
|
||||||
hint='your current password for this account'
|
hint={
|
||||||
type='password'
|
<FormattedMessage
|
||||||
|
id='passwordChange.testPassword.hint'
|
||||||
|
defaultMessage='your account password' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.testPassword.label'
|
||||||
|
defaultMessage='password' />
|
||||||
|
}
|
||||||
|
onChange={ this.onEditTestPassword }
|
||||||
|
onSubmit={ this.testPassword }
|
||||||
submitOnBlur={ false }
|
submitOnBlur={ false }
|
||||||
disabled={ disabled }
|
type='password' />
|
||||||
onSubmit={ this.handleTestPassword }
|
|
||||||
onChange={ this.onEditCurrent } />
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
onActive={ this.handleChangeActive }
|
label={
|
||||||
label='Change Password'
|
<FormattedMessage
|
||||||
>
|
id='passwordChange.tabChange.label'
|
||||||
<Form
|
defaultMessage='Change Password' />
|
||||||
className={ styles.form }
|
}
|
||||||
>
|
onActive={ this.onActivateChangeTab }>
|
||||||
|
<Form className={ styles.form }>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
label='current password'
|
disabled={ busy }
|
||||||
hint='your current password for this account'
|
hint={
|
||||||
type='password'
|
<FormattedMessage
|
||||||
|
id='passwordChange.currentPassword.hint'
|
||||||
|
defaultMessage='your current password for this account' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.currentPassword.label'
|
||||||
|
defaultMessage='current password' />
|
||||||
|
}
|
||||||
|
onChange={ this.onEditCurrentPassword }
|
||||||
|
onSubmit={ this.changePassword }
|
||||||
submitOnBlur={ false }
|
submitOnBlur={ false }
|
||||||
disabled={ disabled }
|
type='password' />
|
||||||
onSubmit={ this.handleChangePassword }
|
|
||||||
onChange={ this.onEditCurrent } />
|
|
||||||
<Input
|
<Input
|
||||||
label='(optional) new password hint'
|
disabled={ busy }
|
||||||
hint='hint for the new password'
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.passwordHint.hint'
|
||||||
|
defaultMessage='hint for the new password' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.passwordHint.label'
|
||||||
|
defaultMessage='(optional) new password hint' />
|
||||||
|
}
|
||||||
|
onChange={ this.onEditNewPasswordHint }
|
||||||
|
onSubmit={ this.changePassword }
|
||||||
submitOnBlur={ false }
|
submitOnBlur={ false }
|
||||||
value={ passwordHint }
|
value={ passwordHint } />
|
||||||
disabled={ disabled }
|
|
||||||
onSubmit={ this.handleChangePassword }
|
|
||||||
onChange={ this.onEditHint } />
|
|
||||||
<div className={ styles.passwords }>
|
<div className={ styles.passwords }>
|
||||||
<div className={ styles.password }>
|
<div className={ styles.password }>
|
||||||
<Input
|
<Input
|
||||||
label='new password'
|
disabled={ busy }
|
||||||
hint='the new password for this account'
|
hint={
|
||||||
type='password'
|
<FormattedMessage
|
||||||
|
id='passwordChange.newPassword.hint'
|
||||||
|
defaultMessage='the new password for this account' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.newPassword.label'
|
||||||
|
defaultMessage='new password' />
|
||||||
|
}
|
||||||
|
onChange={ this.onEditNewPassword }
|
||||||
|
onSubmit={ this.changePassword }
|
||||||
submitOnBlur={ false }
|
submitOnBlur={ false }
|
||||||
disabled={ disabled }
|
type='password' />
|
||||||
onSubmit={ this.handleChangePassword }
|
|
||||||
onChange={ this.onEditNew } />
|
|
||||||
</div>
|
</div>
|
||||||
<div className={ styles.password }>
|
<div className={ styles.password }>
|
||||||
<Input
|
<Input
|
||||||
label='repeat new password'
|
disabled={ busy }
|
||||||
hint='repeat the new password for this account'
|
error={
|
||||||
type='password'
|
isRepeatValid
|
||||||
|
? null
|
||||||
|
: <FormattedMessage
|
||||||
|
id='passwordChange.repeatPassword.error'
|
||||||
|
defaultMessage='the supplied passwords do not match' />
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.repeatPassword.hint'
|
||||||
|
defaultMessage='repeat the new password for this account' />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.repeatPassword.label'
|
||||||
|
defaultMessage='repeat new password' />
|
||||||
|
}
|
||||||
|
onChange={ this.onEditNewPasswordRepeat }
|
||||||
|
onSubmit={ this.changePassword }
|
||||||
submitOnBlur={ false }
|
submitOnBlur={ false }
|
||||||
error={ repeatError }
|
type='password' />
|
||||||
disabled={ disabled }
|
|
||||||
onSubmit={ this.handleChangePassword }
|
|
||||||
onChange={ this.onEditRepeatNew } />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -225,172 +248,126 @@ class PasswordManager extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDialogActions () {
|
renderDialogActions () {
|
||||||
|
const { actionTab, busy, isRepeatValid } = this.store;
|
||||||
const { onClose } = this.props;
|
const { onClose } = this.props;
|
||||||
const { action, waiting, repeatValid } = this.state;
|
|
||||||
|
|
||||||
const cancelBtn = (
|
const cancelBtn = (
|
||||||
<Button
|
<Button
|
||||||
icon={ <ContentClear /> }
|
icon={ <CancelIcon /> }
|
||||||
label='Cancel'
|
key='cancel'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.button.cancel'
|
||||||
|
defaultMessage='Cancel' />
|
||||||
|
}
|
||||||
onClick={ onClose } />
|
onClick={ onClose } />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (waiting) {
|
if (busy) {
|
||||||
const waitingBtn = (
|
return [
|
||||||
|
cancelBtn,
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
label='Wait...' />
|
key='wait'
|
||||||
);
|
label={
|
||||||
|
<FormattedMessage
|
||||||
return [ cancelBtn, waitingBtn ];
|
id='passwordChange.button.wait'
|
||||||
|
defaultMessage='Wait...' />
|
||||||
|
} />
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === TEST_ACTION) {
|
if (actionTab === TEST_ACTION) {
|
||||||
const testBtn = (
|
return [
|
||||||
|
cancelBtn,
|
||||||
<Button
|
<Button
|
||||||
icon={ <CheckIcon /> }
|
icon={ <CheckIcon /> }
|
||||||
label='Test'
|
key='test'
|
||||||
onClick={ this.handleTestPassword } />
|
label={
|
||||||
);
|
<FormattedMessage
|
||||||
|
id='passwordChange.button.test'
|
||||||
return [ cancelBtn, testBtn ];
|
defaultMessage='Test' />
|
||||||
|
}
|
||||||
|
onClick={ this.testPassword } />
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeBtn = (
|
return [
|
||||||
|
cancelBtn,
|
||||||
<Button
|
<Button
|
||||||
disabled={ !repeatValid }
|
disabled={ !isRepeatValid }
|
||||||
icon={ <SendIcon /> }
|
icon={ <SendIcon /> }
|
||||||
label='Change'
|
key='change'
|
||||||
onClick={ this.handleChangePassword } />
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.button.change'
|
||||||
|
defaultMessage='Change' />
|
||||||
|
}
|
||||||
|
onClick={ this.changePassword } />
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivateChangeTab = () => {
|
||||||
|
this.store.setActionTab(CHANGE_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivateTestTab = () => {
|
||||||
|
this.store.setActionTab(TEST_ACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditCurrentPassword = (event, password) => {
|
||||||
|
this.store.setPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditNewPassword = (event, password) => {
|
||||||
|
this.store.setNewPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditNewPasswordHint = (event, passwordHint) => {
|
||||||
|
this.store.setNewPasswordHint(passwordHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditNewPasswordRepeat = (event, password) => {
|
||||||
|
this.store.setNewPasswordRepeat(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditTestPassword = (event, password) => {
|
||||||
|
this.store.setValidatePassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword = () => {
|
||||||
|
return this.store
|
||||||
|
.changePassword()
|
||||||
|
.then((result) => {
|
||||||
|
if (result) {
|
||||||
|
this.props.openSnackbar(
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.success'
|
||||||
|
defaultMessage='Your password has been successfully changed' />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
this.props.onClose();
|
||||||
return [ cancelBtn, changeBtn ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditCurrent = (event, value) => {
|
|
||||||
this.setState({
|
|
||||||
currentPass: value,
|
|
||||||
showMessage: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditNew = (event, value) => {
|
|
||||||
const repeatValid = value === this.state.repeatNewPass;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
newPass: value,
|
|
||||||
showMessage: false,
|
|
||||||
repeatValid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditRepeatNew = (event, value) => {
|
|
||||||
const repeatValid = value === this.state.newPass;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
repeatNewPass: value,
|
|
||||||
showMessage: false,
|
|
||||||
repeatValid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditHint = (event, value) => {
|
|
||||||
this.setState({
|
|
||||||
passwordHint: value,
|
|
||||||
showMessage: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTestActive = () => {
|
|
||||||
this.setState({
|
|
||||||
action: TEST_ACTION,
|
|
||||||
showMessage: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeActive = () => {
|
|
||||||
this.setState({
|
|
||||||
action: CHANGE_ACTION,
|
|
||||||
showMessage: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTestPassword = () => {
|
|
||||||
const { account } = this.props;
|
|
||||||
const { currentPass } = this.state;
|
|
||||||
|
|
||||||
this.setState({ waiting: true, showMessage: false });
|
|
||||||
|
|
||||||
this.context
|
|
||||||
.api.parity
|
|
||||||
.testPassword(account.address, currentPass)
|
|
||||||
.then(correct => {
|
|
||||||
const message = correct
|
|
||||||
? { value: 'This password is correct', success: true }
|
|
||||||
: { value: 'This password is not correct', success: false };
|
|
||||||
|
|
||||||
this.setState({ waiting: false, message, showMessage: true });
|
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((error) => {
|
||||||
console.error('passwordManager::handleTestPassword', e);
|
this.props.newError(error);
|
||||||
this.setState({ waiting: false });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangePassword = () => {
|
testPassword = () => {
|
||||||
const { account, showSnackbar, onClose } = this.props;
|
return this.store
|
||||||
const { currentPass, newPass, repeatNewPass, passwordHint } = this.state;
|
.testPassword()
|
||||||
|
.catch((error) => {
|
||||||
if (repeatNewPass !== newPass) {
|
this.props.newError(error);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ waiting: true, showMessage: false });
|
|
||||||
|
|
||||||
this.context
|
|
||||||
.api.parity
|
|
||||||
.testPassword(account.address, currentPass)
|
|
||||||
.then(correct => {
|
|
||||||
if (!correct) {
|
|
||||||
const message = {
|
|
||||||
value: 'This provided current password is not correct',
|
|
||||||
success: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState({ waiting: false, message, showMessage: true });
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = Object.assign({}, account.meta, {
|
|
||||||
passwordHint
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
this.context
|
|
||||||
.api.parity
|
|
||||||
.setAccountMeta(account.address, meta),
|
|
||||||
|
|
||||||
this.context
|
|
||||||
.api.parity
|
|
||||||
.changePassword(account.address, currentPass, newPass)
|
|
||||||
])
|
|
||||||
.then(() => {
|
|
||||||
showSnackbar(<div>Your password has been successfully changed.</div>);
|
|
||||||
this.setState({ waiting: false, showMessage: false });
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error('passwordManager::handleChangePassword', e);
|
|
||||||
this.setState({ waiting: false });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return bindActionCreators({
|
return bindActionCreators({
|
||||||
showSnackbar
|
openSnackbar,
|
||||||
|
newError
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
js/src/modals/PasswordManager/passwordManager.spec.js
Normal file
110
js/src/modals/PasswordManager/passwordManager.spec.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// 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 PasswordManager from './';
|
||||||
|
|
||||||
|
import { ACCOUNT, createApi, createRedux } from './passwordManager.test.js';
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let onClose;
|
||||||
|
let reduxStore;
|
||||||
|
|
||||||
|
function render (props) {
|
||||||
|
onClose = sinon.stub();
|
||||||
|
reduxStore = createRedux();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<PasswordManager
|
||||||
|
{ ...props }
|
||||||
|
account={ ACCOUNT }
|
||||||
|
onClose={ onClose } />,
|
||||||
|
{ context: { store: reduxStore } }
|
||||||
|
).find('PasswordManager').shallow({ context: { api: createApi() } });
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/PasswordManager', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(render()).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changePassword', () => {
|
||||||
|
it('calls store.changePassword', () => {
|
||||||
|
sinon.spy(instance.store, 'changePassword');
|
||||||
|
|
||||||
|
return instance.changePassword().then(() => {
|
||||||
|
expect(instance.store.changePassword).to.have.been.called;
|
||||||
|
instance.store.changePassword.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes the dialog on success', () => {
|
||||||
|
return instance.changePassword().then(() => {
|
||||||
|
expect(onClose).to.have.been.called;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows snackbar on success', () => {
|
||||||
|
return instance.changePassword().then(() => {
|
||||||
|
expect(reduxStore.dispatch).to.have.been.calledWithMatch({ type: 'openSnackbar' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds newError on failure', () => {
|
||||||
|
sinon.stub(instance.store, 'changePassword').rejects('test');
|
||||||
|
|
||||||
|
return instance.changePassword().then(() => {
|
||||||
|
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||||
|
instance.store.changePassword.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testPassword', () => {
|
||||||
|
it('calls store.testPassword', () => {
|
||||||
|
sinon.spy(instance.store, 'testPassword');
|
||||||
|
|
||||||
|
return instance.testPassword().then(() => {
|
||||||
|
expect(instance.store.testPassword).to.have.been.called;
|
||||||
|
instance.store.testPassword.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds newError on failure', () => {
|
||||||
|
sinon.stub(instance.store, 'testPassword').rejects('test');
|
||||||
|
|
||||||
|
return instance.testPassword().then(() => {
|
||||||
|
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||||
|
instance.store.testPassword.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
54
js/src/modals/PasswordManager/passwordManager.test.js
Normal file
54
js/src/modals/PasswordManager/passwordManager.test.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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 sinon from 'sinon';
|
||||||
|
|
||||||
|
const ACCOUNT = {
|
||||||
|
address: '0x123456789a123456789a123456789a123456789a',
|
||||||
|
meta: {
|
||||||
|
description: 'Call me bob',
|
||||||
|
passwordHint: 'some hint',
|
||||||
|
tags: ['testing']
|
||||||
|
},
|
||||||
|
name: 'Bobby',
|
||||||
|
uuid: '123-456'
|
||||||
|
};
|
||||||
|
|
||||||
|
function createApi (result = true) {
|
||||||
|
return {
|
||||||
|
parity: {
|
||||||
|
changePassword: sinon.stub().resolves(result),
|
||||||
|
setAccountMeta: sinon.stub().resolves(result),
|
||||||
|
testPassword: sinon.stub().resolves(result)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRedux () {
|
||||||
|
return {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ACCOUNT,
|
||||||
|
createApi,
|
||||||
|
createRedux
|
||||||
|
};
|
161
js/src/modals/PasswordManager/store.js
Normal file
161
js/src/modals/PasswordManager/store.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 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 { action, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
|
const CHANGE_ACTION = 'CHANGE_ACTION';
|
||||||
|
const TEST_ACTION = 'TEST_ACTION';
|
||||||
|
|
||||||
|
export default class Store {
|
||||||
|
@observable actionTab = TEST_ACTION;
|
||||||
|
@observable address = null;
|
||||||
|
@observable busy = false;
|
||||||
|
@observable infoMessage = null;
|
||||||
|
@observable meta = null;
|
||||||
|
@observable newPassword = '';
|
||||||
|
@observable newPasswordHint = '';
|
||||||
|
@observable newPasswordRepeat = '';
|
||||||
|
@observable password = '';
|
||||||
|
@observable passwordHint = '';
|
||||||
|
@observable validatePassword = '';
|
||||||
|
|
||||||
|
constructor (api, account) {
|
||||||
|
this._api = api;
|
||||||
|
|
||||||
|
this.address = account.address;
|
||||||
|
this.meta = account.meta || {};
|
||||||
|
this.passwordHint = this.meta.passwordHint || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isRepeatValid () {
|
||||||
|
return this.newPasswordRepeat === this.newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setActionTab = (actionTab) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.actionTab = actionTab;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setBusy = (busy, message) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.busy = busy;
|
||||||
|
this.setInfoMessage(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setInfoMessage = (message = null) => {
|
||||||
|
this.infoMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setPassword = (password) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.password = password;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNewPassword = (password) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.newPassword = password;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNewPasswordHint = (passwordHint) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.newPasswordHint = passwordHint;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNewPasswordRepeat = (password) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.newPasswordRepeat = password;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setValidatePassword = (password) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.validatePassword = password;
|
||||||
|
this.setInfoMessage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword = () => {
|
||||||
|
if (!this.isRepeatValid) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setBusy(true);
|
||||||
|
|
||||||
|
return this
|
||||||
|
.testPassword(this.password)
|
||||||
|
.then((result) => {
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = Object.assign({}, this.meta, {
|
||||||
|
passwordHint: this.newPasswordHint
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._api.parity.setAccountMeta(this.address, meta),
|
||||||
|
this._api.parity.changePassword(this.address, this.password, this.newPassword)
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
this.setBusy(false);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('changePassword', error);
|
||||||
|
this.setBusy(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testPassword = (password) => {
|
||||||
|
this.setBusy(false);
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
|
.testPassword(this.address, password || this.validatePassword)
|
||||||
|
.then((success) => {
|
||||||
|
this.setBusy(false, {
|
||||||
|
success,
|
||||||
|
value: success
|
||||||
|
? 'This password is correct'
|
||||||
|
: 'This password is not correct'
|
||||||
|
});
|
||||||
|
|
||||||
|
return success;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('testPassword', error);
|
||||||
|
this.setBusy(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
CHANGE_ACTION,
|
||||||
|
TEST_ACTION
|
||||||
|
};
|
103
js/src/modals/PasswordManager/store.spec.js
Normal file
103
js/src/modals/PasswordManager/store.spec.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import Store from './store';
|
||||||
|
import { ACCOUNT, createApi } from './passwordManager.test.js';
|
||||||
|
|
||||||
|
let api;
|
||||||
|
let store;
|
||||||
|
|
||||||
|
function createStore (account) {
|
||||||
|
api = createApi();
|
||||||
|
store = new Store(api, account);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('modals/PasswordManager/Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createStore(ACCOUNT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('extracts the address', () => {
|
||||||
|
expect(store.address).to.equal(ACCOUNT.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('meta', () => {
|
||||||
|
it('extracts the full meta', () => {
|
||||||
|
expect(store.meta).to.deep.equal(ACCOUNT.meta);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extracts the passwordHint', () => {
|
||||||
|
expect(store.passwordHint).to.equal(ACCOUNT.meta.passwordHint);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('operations', () => {
|
||||||
|
const CUR_PASSWORD = 'aPassW0rd';
|
||||||
|
const NEW_PASSWORD = 'br@ndNEW';
|
||||||
|
const NEW_HINT = 'something new to test';
|
||||||
|
|
||||||
|
describe('changePassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setPassword(CUR_PASSWORD);
|
||||||
|
store.setNewPasswordHint(NEW_HINT);
|
||||||
|
store.setNewPassword(NEW_PASSWORD);
|
||||||
|
store.setNewPasswordRepeat(NEW_PASSWORD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity.testPassword with current password', () => {
|
||||||
|
return store.changePassword().then(() => {
|
||||||
|
expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity.setAccountMeta with new hint', () => {
|
||||||
|
return store.changePassword().then(() => {
|
||||||
|
expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
|
||||||
|
passwordHint: NEW_HINT
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity.changePassword with the new password', () => {
|
||||||
|
return store.changePassword().then(() => {
|
||||||
|
expect(api.parity.changePassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD, NEW_PASSWORD);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('testPassword', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.setValidatePassword(CUR_PASSWORD);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls parity.testPassword', () => {
|
||||||
|
return store.testPassword().then(() => {
|
||||||
|
expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the infoMessage for success/failure', () => {
|
||||||
|
return store.testPassword().then(() => {
|
||||||
|
expect(store.infoMessage).not.to.be.null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -383,9 +383,7 @@ export default class TransferStore {
|
|||||||
|
|
||||||
const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
|
const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
|
||||||
const format = new BigNumber(senderBalance.token.format || 1);
|
const format = new BigNumber(senderBalance.token.format || 1);
|
||||||
const available = isWallet
|
const available = new BigNumber(senderBalance.value).div(format);
|
||||||
? this.api.util.fromWei(new BigNumber(senderBalance.value))
|
|
||||||
: (new BigNumber(senderBalance.value)).div(format);
|
|
||||||
|
|
||||||
let { value, valueError } = this;
|
let { value, valueError } = this;
|
||||||
let totalEth = gasTotal;
|
let totalEth = gasTotal;
|
||||||
@ -428,7 +426,6 @@ 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;
|
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||||
|
|
||||||
return this._getTransferMethod().postTransaction(options, values);
|
return this._getTransferMethod().postTransaction(options, values);
|
||||||
@ -440,18 +437,9 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
estimateGas () {
|
estimateGas () {
|
||||||
if (this.isEth || !this.isWallet) {
|
|
||||||
return this._estimateGas();
|
return this._estimateGas();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise
|
|
||||||
.all([
|
|
||||||
this._estimateGas(true),
|
|
||||||
this._estimateGas()
|
|
||||||
])
|
|
||||||
.then((results) => results[0].plus(results[1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTransferMethod (gas = false, forceToken = false) {
|
_getTransferMethod (gas = false, forceToken = false) {
|
||||||
const { isEth, isWallet } = this;
|
const { isEth, isWallet } = this;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class WalletSettings extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
wallet: PropTypes.object.isRequired,
|
wallet: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
senders: PropTypes.object.isRequired
|
senders: PropTypes.object.isRequired
|
||||||
@ -113,7 +113,7 @@ class WalletSettings extends Component {
|
|||||||
default:
|
default:
|
||||||
case 'EDIT':
|
case 'EDIT':
|
||||||
const { wallet, errors } = this.store;
|
const { wallet, errors } = this.store;
|
||||||
const { accounts, senders } = this.props;
|
const { accountsInfo, senders } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
@ -137,7 +137,7 @@ class WalletSettings extends Component {
|
|||||||
label='other wallet owners'
|
label='other wallet owners'
|
||||||
value={ wallet.owners.slice() }
|
value={ wallet.owners.slice() }
|
||||||
onChange={ this.store.onOwnersChange }
|
onChange={ this.store.onOwnersChange }
|
||||||
accounts={ accounts }
|
accounts={ accountsInfo }
|
||||||
param='address[]'
|
param='address[]'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ class WalletSettings extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderChange (change) {
|
renderChange (change) {
|
||||||
const { accounts } = this.props;
|
const { accountsInfo } = this.props;
|
||||||
|
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case 'dailylimit':
|
case 'dailylimit':
|
||||||
@ -229,7 +229,7 @@ class WalletSettings extends Component {
|
|||||||
<InputAddress
|
<InputAddress
|
||||||
disabled
|
disabled
|
||||||
value={ change.value }
|
value={ change.value }
|
||||||
accounts={ accounts }
|
accounts={ accountsInfo }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -243,7 +243,7 @@ class WalletSettings extends Component {
|
|||||||
<InputAddress
|
<InputAddress
|
||||||
disabled
|
disabled
|
||||||
value={ change.value }
|
value={ change.value }
|
||||||
accounts={ accounts }
|
accounts={ accountsInfo }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -329,7 +329,7 @@ function mapStateToProps (initState, initProps) {
|
|||||||
const senders = pick(accounts, owners);
|
const senders = pick(accounts, owners);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return { accounts: accountsInfo, senders };
|
return { accountsInfo, senders };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ const STEPS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class WalletSettingsStore {
|
export default class WalletSettingsStore {
|
||||||
|
accounts = {};
|
||||||
|
|
||||||
@observable step = null;
|
@observable step = null;
|
||||||
@observable requests = [];
|
@observable requests = [];
|
||||||
@observable deployState = '';
|
@observable deployState = '';
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { newError } from '~/ui/Errors/actions';
|
import { newError } from '~/ui/Errors/actions';
|
||||||
import { setAddressImage } from './providers/imagesActions';
|
import { setAddressImage } from './providers/imagesActions';
|
||||||
|
import { openSnackbar, showSnackbar } from './providers/snackbarActions';
|
||||||
import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions';
|
import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions';
|
||||||
import { toggleView } from '~/views/Settings/actions';
|
import { toggleView } from '~/views/Settings/actions';
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ export {
|
|||||||
newError,
|
newError,
|
||||||
clearStatusLogs,
|
clearStatusLogs,
|
||||||
setAddressImage,
|
setAddressImage,
|
||||||
|
openSnackbar,
|
||||||
|
showSnackbar,
|
||||||
toggleStatusLogs,
|
toggleStatusLogs,
|
||||||
toggleStatusRefresh,
|
toggleStatusRefresh,
|
||||||
toggleView
|
toggleView
|
||||||
|
@ -22,6 +22,7 @@ import SignerMiddleware from './providers/signerMiddleware';
|
|||||||
|
|
||||||
import statusMiddleware from '~/views/Status/middleware';
|
import statusMiddleware from '~/views/Status/middleware';
|
||||||
import CertificationsMiddleware from './providers/certifications/middleware';
|
import CertificationsMiddleware from './providers/certifications/middleware';
|
||||||
|
import ChainMiddleware from './providers/chainMiddleware';
|
||||||
|
|
||||||
export default function (api, browserHistory) {
|
export default function (api, browserHistory) {
|
||||||
const errors = new ErrorsMiddleware();
|
const errors = new ErrorsMiddleware();
|
||||||
@ -30,12 +31,14 @@ export default function (api, browserHistory) {
|
|||||||
const status = statusMiddleware();
|
const status = statusMiddleware();
|
||||||
const certifications = new CertificationsMiddleware();
|
const certifications = new CertificationsMiddleware();
|
||||||
const routeMiddleware = routerMiddleware(browserHistory);
|
const routeMiddleware = routerMiddleware(browserHistory);
|
||||||
|
const chain = new ChainMiddleware();
|
||||||
|
|
||||||
const middleware = [
|
const middleware = [
|
||||||
settings.toMiddleware(),
|
settings.toMiddleware(),
|
||||||
signer.toMiddleware(),
|
signer.toMiddleware(),
|
||||||
errors.toMiddleware(),
|
errors.toMiddleware(),
|
||||||
certifications.toMiddleware()
|
certifications.toMiddleware(),
|
||||||
|
chain.toMiddleware()
|
||||||
];
|
];
|
||||||
|
|
||||||
return middleware.concat(status, routeMiddleware, thunk);
|
return middleware.concat(status, routeMiddleware, thunk);
|
||||||
|
@ -173,17 +173,18 @@ export function fetchTokens (_tokenIds) {
|
|||||||
export function fetchBalances (_addresses) {
|
export function fetchBalances (_addresses) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal } = getState();
|
const { api, personal } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
const { visibleAccounts, accountsInfo } = personal;
|
||||||
|
|
||||||
const addresses = uniq(_addresses || visibleAccounts || []);
|
const addresses = uniq((_addresses || visibleAccounts || []).concat(Object.keys(accountsInfo)));
|
||||||
|
|
||||||
if (addresses.length === 0) {
|
if (addresses.length === 0) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With only a single account, more info will be displayed.
|
||||||
const fullFetch = addresses.length === 1;
|
const fullFetch = addresses.length === 1;
|
||||||
|
|
||||||
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(addresses);
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
||||||
|
38
js/src/redux/providers/chainMiddleware.js
Normal file
38
js/src/redux/providers/chainMiddleware.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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 { showSnackbar } from './snackbarActions';
|
||||||
|
|
||||||
|
export default class ChainMiddleware {
|
||||||
|
toMiddleware () {
|
||||||
|
return (store) => (next) => (action) => {
|
||||||
|
if (action.type === 'statusCollection') {
|
||||||
|
const { collection } = action;
|
||||||
|
|
||||||
|
if (collection && collection.netChain) {
|
||||||
|
const chain = collection.netChain;
|
||||||
|
const { nodeStatus } = store.getState();
|
||||||
|
|
||||||
|
if (chain !== nodeStatus.netChain) {
|
||||||
|
store.dispatch(showSnackbar(`Switched to ${chain}. Please reload the page.`, 5000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -14,14 +14,18 @@
|
|||||||
// 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 { isEqual } from 'lodash';
|
import { isEqual, intersection } from 'lodash';
|
||||||
|
|
||||||
import { fetchBalances } from './balancesActions';
|
import { fetchBalances } from './balancesActions';
|
||||||
import { attachWallets } from './walletActions';
|
import { attachWallets } from './walletActions';
|
||||||
|
|
||||||
|
import Contract from '~/api/contract';
|
||||||
import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore';
|
import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore';
|
||||||
|
import WalletsUtils from '~/util/wallets';
|
||||||
|
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||||
|
|
||||||
export function personalAccountsInfo (accountsInfo) {
|
export function personalAccountsInfo (accountsInfo) {
|
||||||
|
const addresses = [];
|
||||||
const accounts = {};
|
const accounts = {};
|
||||||
const contacts = {};
|
const contacts = {};
|
||||||
const contracts = {};
|
const contracts = {};
|
||||||
@ -32,6 +36,7 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
.filter((account) => account.uuid || !account.meta.deleted)
|
.filter((account) => account.uuid || !account.meta.deleted)
|
||||||
.forEach((account) => {
|
.forEach((account) => {
|
||||||
if (account.uuid) {
|
if (account.uuid) {
|
||||||
|
addresses.push(account.address);
|
||||||
accounts[account.address] = account;
|
accounts[account.address] = account;
|
||||||
} else if (account.meta.wallet) {
|
} else if (account.meta.wallet) {
|
||||||
account.wallet = true;
|
account.wallet = true;
|
||||||
@ -46,14 +51,52 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
// Load user contracts for Method Decoding
|
// Load user contracts for Method Decoding
|
||||||
MethodDecodingStore.loadContracts(contracts);
|
MethodDecodingStore.loadContracts(contracts);
|
||||||
|
|
||||||
return (dispatch) => {
|
return (dispatch, getState) => {
|
||||||
|
const { api } = getState();
|
||||||
|
|
||||||
|
const _fetchOwners = Object
|
||||||
|
.values(wallets)
|
||||||
|
.map((wallet) => {
|
||||||
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
return WalletsUtils.fetchOwners(walletContract.at(wallet.address));
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise
|
||||||
|
.all(_fetchOwners)
|
||||||
|
.then((walletsOwners) => {
|
||||||
|
return Object
|
||||||
|
.values(wallets)
|
||||||
|
.map((wallet, index) => {
|
||||||
|
wallet.owners = walletsOwners[index].map((owner) => ({
|
||||||
|
address: owner,
|
||||||
|
name: accountsInfo[owner] && accountsInfo[owner].name || owner
|
||||||
|
}));
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((_wallets) => {
|
||||||
|
_wallets.forEach((wallet) => {
|
||||||
|
const owners = wallet.owners.map((o) => o.address);
|
||||||
|
|
||||||
|
// Owners ∩ Addresses not null : Wallet is owned
|
||||||
|
// by one of the accounts
|
||||||
|
if (intersection(owners, addresses).length > 0) {
|
||||||
|
accounts[wallet.address] = wallet;
|
||||||
|
} else {
|
||||||
|
contacts[wallet.address] = wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
accounts, contacts, contracts, wallets
|
accounts, contacts, contracts
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(_personalAccountsInfo(data));
|
dispatch(_personalAccountsInfo(data));
|
||||||
dispatch(attachWallets(wallets));
|
dispatch(attachWallets(wallets));
|
||||||
|
dispatch(fetchBalances());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,14 +25,13 @@ const initialState = {
|
|||||||
hasContacts: false,
|
hasContacts: false,
|
||||||
contracts: {},
|
contracts: {},
|
||||||
hasContracts: false,
|
hasContracts: false,
|
||||||
wallet: {},
|
|
||||||
hasWallets: false,
|
|
||||||
visibleAccounts: []
|
visibleAccounts: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
personalAccountsInfo (state, action) {
|
personalAccountsInfo (state, action) {
|
||||||
const { accountsInfo, accounts, contacts, contracts, wallets } = action;
|
const accountsInfo = action.accountsInfo || state.accountsInfo;
|
||||||
|
const { accounts, contacts, contracts } = action;
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
accountsInfo,
|
accountsInfo,
|
||||||
@ -41,9 +40,7 @@ export default handleActions({
|
|||||||
contacts,
|
contacts,
|
||||||
hasContacts: Object.keys(contacts).length !== 0,
|
hasContacts: Object.keys(contacts).length !== 0,
|
||||||
contracts,
|
contracts,
|
||||||
hasContracts: Object.keys(contracts).length !== 0,
|
hasContracts: Object.keys(contracts).length !== 0
|
||||||
wallets,
|
|
||||||
hasWallets: Object.keys(wallets).length !== 0
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ export default handleActions({
|
|||||||
signerSuccessRejectRequest (state, action) {
|
signerSuccessRejectRequest (state, action) {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const rejected = Object.assign(
|
const rejected = Object.assign(
|
||||||
state.pending.find(p => p.id === id),
|
state.pending.find(p => p.id === id) || { id },
|
||||||
{ status: 'rejected' }
|
{ status: 'rejected' }
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
|
@ -20,7 +20,7 @@ export function showSnackbar (message, cooldown) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSnackbar (message, cooldown) {
|
export function openSnackbar (message, cooldown) {
|
||||||
return {
|
return {
|
||||||
type: 'openSnackbar',
|
type: 'openSnackbar',
|
||||||
message, cooldown
|
message, cooldown
|
||||||
|
@ -52,6 +52,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.75em;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.accountInfo {
|
.accountInfo {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ export default class AccountCard extends Component {
|
|||||||
const { account } = this.props;
|
const { account } = this.props;
|
||||||
const { copied } = this.state;
|
const { copied } = this.state;
|
||||||
|
|
||||||
const { address, name, meta = {} } = account;
|
const { address, name, description, meta = {} } = account;
|
||||||
|
|
||||||
const displayName = (name && name.toUpperCase()) || address;
|
const displayName = (name && name.toUpperCase()) || address;
|
||||||
const { tags = [] } = meta;
|
const { tags = [] } = meta;
|
||||||
@ -70,6 +70,7 @@ export default class AccountCard extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ this.renderTags(tags, address) }
|
{ this.renderTags(tags, address) }
|
||||||
|
{ this.renderDescription(description) }
|
||||||
{ this.renderAddress(displayName, address) }
|
{ this.renderAddress(displayName, address) }
|
||||||
{ this.renderBalance(address) }
|
{ this.renderBalance(address) }
|
||||||
</div>
|
</div>
|
||||||
@ -77,6 +78,18 @@ export default class AccountCard extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderDescription (description) {
|
||||||
|
if (!description) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.description }>
|
||||||
|
<span>{ description }</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderAddress (name, address) {
|
renderAddress (name, address) {
|
||||||
if (name === address) {
|
if (name === address) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -30,15 +30,22 @@ export default class Container extends Component {
|
|||||||
compact: PropTypes.bool,
|
compact: PropTypes.bool,
|
||||||
light: PropTypes.bool,
|
light: PropTypes.bool,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
|
tabIndex: PropTypes.number,
|
||||||
title: nodeOrStringProptype()
|
title: nodeOrStringProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, className, compact, light, style } = this.props;
|
const { children, className, compact, light, style, tabIndex } = this.props;
|
||||||
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
|
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
|
||||||
|
|
||||||
|
const props = {};
|
||||||
|
|
||||||
|
if (Number.isInteger(tabIndex)) {
|
||||||
|
props.tabIndex = tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ classes } style={ style }>
|
<div className={ classes } style={ style } { ...props }>
|
||||||
<Card className={ compact ? styles.compact : styles.padded }>
|
<Card className={ compact ? styles.compact : styles.padded }>
|
||||||
{ this.renderTitle() }
|
{ this.renderTitle() }
|
||||||
{ children }
|
{ children }
|
||||||
|
@ -19,14 +19,17 @@ import ReactDOM from 'react-dom';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import keycode, { codes } from 'keycode';
|
import keycode, { codes } from 'keycode';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
|
import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
|
||||||
|
|
||||||
import AccountCard from '~/ui/AccountCard';
|
import AccountCard from '~/ui/AccountCard';
|
||||||
import InputAddress from '~/ui/Form/InputAddress';
|
import InputAddress from '~/ui/Form/InputAddress';
|
||||||
import Portal from '~/ui/Portal';
|
import Portal from '~/ui/Portal';
|
||||||
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
import { validateAddress } from '~/util/validation';
|
import { validateAddress } from '~/util/validation';
|
||||||
|
|
||||||
|
import AddressSelectStore from './addressSelectStore';
|
||||||
import styles from './addressSelect.css';
|
import styles from './addressSelect.css';
|
||||||
|
|
||||||
const BOTTOM_BORDER_STYLE = { borderBottom: 'solid 3px' };
|
const BOTTOM_BORDER_STYLE = { borderBottom: 'solid 3px' };
|
||||||
@ -34,8 +37,11 @@ const BOTTOM_BORDER_STYLE = { borderBottom: 'solid 3px' };
|
|||||||
// Current Form ID
|
// Current Form ID
|
||||||
let currentId = 1;
|
let currentId = 1;
|
||||||
|
|
||||||
|
@observer
|
||||||
class AddressSelect extends Component {
|
class AddressSelect extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
intl: React.PropTypes.object.isRequired,
|
||||||
|
api: PropTypes.object.isRequired,
|
||||||
muiTheme: PropTypes.object.isRequired
|
muiTheme: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,29 +56,31 @@ class AddressSelect extends Component {
|
|||||||
contacts: PropTypes.object,
|
contacts: PropTypes.object,
|
||||||
contracts: PropTypes.object,
|
contracts: PropTypes.object,
|
||||||
tokens: PropTypes.object,
|
tokens: PropTypes.object,
|
||||||
wallets: PropTypes.object,
|
|
||||||
|
|
||||||
// Optional props
|
// Optional props
|
||||||
|
allowCopy: PropTypes.bool,
|
||||||
allowInput: PropTypes.bool,
|
allowInput: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
error: PropTypes.string,
|
error: nodeOrStringProptype(),
|
||||||
hint: PropTypes.string,
|
hint: nodeOrStringProptype(),
|
||||||
label: PropTypes.string,
|
label: nodeOrStringProptype(),
|
||||||
value: PropTypes.string
|
readOnly: PropTypes.bool,
|
||||||
|
value: nodeOrStringProptype()
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
value: ''
|
value: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
store = new AddressSelectStore(this.context.api);
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
focused: false,
|
focused: false,
|
||||||
focusedCat: null,
|
focusedCat: null,
|
||||||
focusedItem: null,
|
focusedItem: null,
|
||||||
inputFocused: false,
|
inputFocused: false,
|
||||||
inputValue: '',
|
inputValue: ''
|
||||||
values: []
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
@ -80,7 +88,7 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (this.values && this.values.length > 0) {
|
if (this.store.values && this.store.values.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,36 +96,7 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setValues (props = this.props) {
|
setValues (props = this.props) {
|
||||||
const { accounts = {}, contracts = {}, contacts = {}, wallets = {} } = props;
|
this.store.setValues(props);
|
||||||
|
|
||||||
const accountsN = Object.keys(accounts).length;
|
|
||||||
const contractsN = Object.keys(contracts).length;
|
|
||||||
const contactsN = Object.keys(contacts).length;
|
|
||||||
const walletsN = Object.keys(wallets).length;
|
|
||||||
|
|
||||||
if (accountsN + contractsN + contactsN + walletsN === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.values = [
|
|
||||||
{
|
|
||||||
label: 'accounts',
|
|
||||||
values: [].concat(
|
|
||||||
Object.values(wallets),
|
|
||||||
Object.values(accounts)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'contacts',
|
|
||||||
values: Object.values(contacts)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'contracts',
|
|
||||||
values: Object.values(contracts)
|
|
||||||
}
|
|
||||||
].filter((cat) => cat.values.length > 0);
|
|
||||||
|
|
||||||
this.handleChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -144,12 +123,12 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
renderInput () {
|
renderInput () {
|
||||||
const { focused } = this.state;
|
const { focused } = this.state;
|
||||||
const { accountsInfo, disabled, error, hint, label, value } = this.props;
|
const { accountsInfo, allowCopy, disabled, error, hint, label, readOnly, value } = this.props;
|
||||||
|
|
||||||
const input = (
|
const input = (
|
||||||
<InputAddress
|
<InputAddress
|
||||||
accountsInfo={ accountsInfo }
|
accountsInfo={ accountsInfo }
|
||||||
allowCopy={ false }
|
allowCopy={ allowCopy }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
error={ error }
|
error={ error }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
@ -162,7 +141,7 @@ class AddressSelect extends Component {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled || readOnly) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,14 +154,20 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
const { muiTheme } = this.context;
|
const { muiTheme } = this.context;
|
||||||
const { hint, disabled, label } = this.props;
|
const { hint, disabled, label, readOnly } = this.props;
|
||||||
const { expanded, inputFocused } = this.state;
|
const { expanded, inputFocused } = this.state;
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled || readOnly) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = `addressSelect_${++currentId}`;
|
const id = `addressSelect_${++currentId}`;
|
||||||
|
const ilHint = typeof hint === 'string' || !(hint && hint.props)
|
||||||
|
? (hint || '')
|
||||||
|
: this.context.intl.formatMessage(
|
||||||
|
hint.props,
|
||||||
|
hint.props.values || {}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
@ -197,7 +182,7 @@ class AddressSelect extends Component {
|
|||||||
<input
|
<input
|
||||||
id={ id }
|
id={ id }
|
||||||
className={ styles.input }
|
className={ styles.input }
|
||||||
placeholder={ hint }
|
placeholder={ ilHint }
|
||||||
|
|
||||||
onBlur={ this.handleInputBlur }
|
onBlur={ this.handleInputBlur }
|
||||||
onFocus={ this.handleInputFocus }
|
onFocus={ this.handleInputFocus }
|
||||||
@ -216,6 +201,7 @@ class AddressSelect extends Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ this.renderCurrentInput() }
|
{ this.renderCurrentInput() }
|
||||||
|
{ this.renderRegistryValues() }
|
||||||
{ this.renderAccounts() }
|
{ this.renderAccounts() }
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
@ -241,8 +227,28 @@ class AddressSelect extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderRegistryValues () {
|
||||||
|
const { registryValues } = this.store;
|
||||||
|
|
||||||
|
if (registryValues.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = registryValues
|
||||||
|
.map((registryValue, index) => {
|
||||||
|
const account = { ...registryValue, index: `${registryValue.address}_${index}` };
|
||||||
|
return this.renderAccountCard(account);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ accounts }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { values } = this.state;
|
const { values } = this.store;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
return (
|
return (
|
||||||
@ -257,8 +263,8 @@ class AddressSelect extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories = values.map((category) => {
|
const categories = values.map((category, index) => {
|
||||||
return this.renderCategory(category.label, category.values);
|
return this.renderCategory(category, index);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -268,7 +274,8 @@ class AddressSelect extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategory (name, values = []) {
|
renderCategory (category, index) {
|
||||||
|
const { label, key, values = [] } = category;
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
@ -292,8 +299,8 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.category } key={ name }>
|
<div className={ styles.category } key={ `${key}_${index}` }>
|
||||||
<div className={ styles.title }>{ name }</div>
|
<div className={ styles.title }>{ label }</div>
|
||||||
{ content }
|
{ content }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -306,7 +313,7 @@ class AddressSelect extends Component {
|
|||||||
const balance = balances[address];
|
const balance = balances[address];
|
||||||
const account = {
|
const account = {
|
||||||
...accountsInfo[address],
|
...accountsInfo[address],
|
||||||
address, index
|
..._account
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -325,9 +332,10 @@ class AddressSelect extends Component {
|
|||||||
this.inputRef = refId;
|
this.inputRef = refId;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCustomInput = () => {
|
validateCustomInput = () => {
|
||||||
const { allowInput } = this.props;
|
const { allowInput } = this.props;
|
||||||
const { inputValue, values } = this.state;
|
const { inputValue } = this.store;
|
||||||
|
const { values } = this.store;
|
||||||
|
|
||||||
// If input is HEX and allowInput === true, send it
|
// If input is HEX and allowInput === true, send it
|
||||||
if (allowInput && inputValue && /^(0x)?([0-9a-f])+$/i.test(inputValue)) {
|
if (allowInput && inputValue && /^(0x)?([0-9a-f])+$/i.test(inputValue)) {
|
||||||
@ -335,8 +343,8 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If only one value, select it
|
// If only one value, select it
|
||||||
if (values.length === 1 && values[0].values.length === 1) {
|
if (values.reduce((cur, cat) => cur + cat.values.length, 0) === 1) {
|
||||||
const value = values[0].values[0];
|
const value = values.find((cat) => cat.values.length > 0).values[0];
|
||||||
return this.handleClick(value.address);
|
return this.handleClick(value.address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,7 +369,7 @@ class AddressSelect extends Component {
|
|||||||
case 'enter':
|
case 'enter':
|
||||||
const index = this.state.focusedItem;
|
const index = this.state.focusedItem;
|
||||||
if (!index) {
|
if (!index) {
|
||||||
return this.handleCustomInput();
|
return this.validateCustomInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.handleDOMAction(`account_${index}`, 'click');
|
return this.handleDOMAction(`account_${index}`, 'click');
|
||||||
@ -408,10 +416,11 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNavigation = (direction, event) => {
|
handleNavigation = (direction, event) => {
|
||||||
const { focusedItem, focusedCat, values } = this.state;
|
const { focusedItem, focusedCat } = this.state;
|
||||||
|
const { values } = this.store;
|
||||||
|
|
||||||
// Don't do anything if no values
|
// Don't do anything if no values
|
||||||
if (values.length === 0) {
|
if (values.reduce((cur, cat) => cur + cat.values.length, 0) === 0) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +432,12 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const nextValues = values[focusedCat || 0];
|
const firstCat = values.findIndex((cat) => cat.values.length > 0);
|
||||||
|
const nextCat = focusedCat && values[focusedCat].values.length > 0
|
||||||
|
? focusedCat
|
||||||
|
: firstCat;
|
||||||
|
|
||||||
|
const nextValues = values[nextCat];
|
||||||
const nextFocus = nextValues ? nextValues.values[0] : null;
|
const nextFocus = nextValues ? nextValues.values[0] : null;
|
||||||
return this.focusItem(nextFocus && nextFocus.index || 1);
|
return this.focusItem(nextFocus && nextFocus.index || 1);
|
||||||
}
|
}
|
||||||
@ -457,12 +471,21 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
// If right: next category
|
// If right: next category
|
||||||
if (direction === 'right') {
|
if (direction === 'right') {
|
||||||
nextCategory = Math.min(prevCategoryIndex + 1, values.length - 1);
|
const categoryShift = values
|
||||||
|
.slice(prevCategoryIndex + 1, values.length)
|
||||||
|
.findIndex((cat) => cat.values.length > 0) + 1;
|
||||||
|
|
||||||
|
nextCategory = Math.min(prevCategoryIndex + categoryShift, values.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If right: previous category
|
// If right: previous category
|
||||||
if (direction === 'left') {
|
if (direction === 'left') {
|
||||||
nextCategory = Math.max(prevCategoryIndex - 1, 0);
|
const categoryShift = values
|
||||||
|
.slice(0, prevCategoryIndex)
|
||||||
|
.reverse()
|
||||||
|
.findIndex((cat) => cat.values.length > 0) + 1;
|
||||||
|
|
||||||
|
nextCategory = Math.max(prevCategoryIndex - categoryShift, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If left or right: try to keep the horizontal index
|
// If left or right: try to keep the horizontal index
|
||||||
@ -486,6 +509,10 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMainBlur = () => {
|
handleMainBlur = () => {
|
||||||
|
if (this.props.readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (window.document.hasFocus() && !this.state.expanded) {
|
if (window.document.hasFocus() && !this.state.expanded) {
|
||||||
this.closing = false;
|
this.closing = false;
|
||||||
this.setState({ focused: false });
|
this.setState({ focused: false });
|
||||||
@ -493,7 +520,7 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMainFocus = () => {
|
handleMainFocus = () => {
|
||||||
if (this.state.focused) {
|
if (this.state.focused || this.props.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,6 +535,12 @@ class AddressSelect extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFocus = () => {
|
handleFocus = () => {
|
||||||
|
const { disabled, readOnly } = this.props;
|
||||||
|
|
||||||
|
if (disabled || readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ expanded: true, focusedItem: null, focusedCat: null }, () => {
|
this.setState({ expanded: true, focusedItem: null, focusedCat: null }, () => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.handleDOMAction(this.inputRef, 'focus');
|
this.handleDOMAction(this.inputRef, 'focus');
|
||||||
@ -525,43 +558,6 @@ class AddressSelect extends Component {
|
|||||||
this.setState({ expanded: false });
|
this.setState({ expanded: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter the given values based on the given
|
|
||||||
* filter
|
|
||||||
*/
|
|
||||||
filterValues = (values = [], _filter = '') => {
|
|
||||||
const filter = _filter.toLowerCase();
|
|
||||||
|
|
||||||
return values
|
|
||||||
// Remove empty accounts
|
|
||||||
.filter((a) => a)
|
|
||||||
.filter((account) => {
|
|
||||||
const address = account.address.toLowerCase();
|
|
||||||
const inAddress = address.includes(filter);
|
|
||||||
|
|
||||||
if (!account.name || inAddress) {
|
|
||||||
return inAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = account.name.toLowerCase();
|
|
||||||
const inName = name.includes(filter);
|
|
||||||
const { meta = {} } = account;
|
|
||||||
|
|
||||||
if (!meta.tags || inName) {
|
|
||||||
return inName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = (meta.tags || []).join('');
|
|
||||||
return tags.includes(filter);
|
|
||||||
})
|
|
||||||
.sort((accA, accB) => {
|
|
||||||
const nameA = accA.name || accA.address;
|
|
||||||
const nameB = accB.name || accB.address;
|
|
||||||
|
|
||||||
return nameA.localeCompare(nameB);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInputBlur = () => {
|
handleInputBlur = () => {
|
||||||
this.setState({ inputFocused: false });
|
this.setState({ inputFocused: false });
|
||||||
}
|
}
|
||||||
@ -572,25 +568,10 @@ class AddressSelect extends Component {
|
|||||||
|
|
||||||
handleChange = (event = { target: {} }) => {
|
handleChange = (event = { target: {} }) => {
|
||||||
const { value = '' } = event.target;
|
const { value = '' } = event.target;
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
const values = this.values
|
this.store.handleChange(value);
|
||||||
.map((category) => {
|
|
||||||
const filteredValues = this
|
|
||||||
.filterValues(category.values, value)
|
|
||||||
.map((value) => {
|
|
||||||
index++;
|
|
||||||
return { ...value, index: parseInt(index) };
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: category.label,
|
|
||||||
values: filteredValues
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
values,
|
|
||||||
focusedItem: null,
|
focusedItem: null,
|
||||||
inputValue: value
|
inputValue: value
|
||||||
});
|
});
|
||||||
|
218
js/src/ui/Form/AddressSelect/addressSelectStore.js
Normal file
218
js/src/ui/Form/AddressSelect/addressSelectStore.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// 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 from 'react';
|
||||||
|
import { observable, action } from 'mobx';
|
||||||
|
import { flatMap } from 'lodash';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import Contracts from '~/contracts';
|
||||||
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
|
||||||
|
export default class AddressSelectStore {
|
||||||
|
|
||||||
|
@observable values = [];
|
||||||
|
@observable registryValues = [];
|
||||||
|
|
||||||
|
initValues = [];
|
||||||
|
regLookups = [];
|
||||||
|
|
||||||
|
constructor (api) {
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
const { registry } = Contracts.create(api);
|
||||||
|
|
||||||
|
registry
|
||||||
|
.getContract('emailverification')
|
||||||
|
.then((emailVerification) => {
|
||||||
|
this.regLookups.push({
|
||||||
|
lookup: (value) => {
|
||||||
|
return emailVerification
|
||||||
|
.instance
|
||||||
|
.reverse.call({}, [ sha3(value) ]);
|
||||||
|
},
|
||||||
|
describe: (value) => (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addressSelect.fromEmail'
|
||||||
|
defaultMessage='Verified using email {value}'
|
||||||
|
values={ {
|
||||||
|
value
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
registry
|
||||||
|
.getInstance()
|
||||||
|
.then((registryInstance) => {
|
||||||
|
this.regLookups.push({
|
||||||
|
lookup: (value) => {
|
||||||
|
return registryInstance
|
||||||
|
.getAddress.call({}, [ sha3(value), 'A' ]);
|
||||||
|
},
|
||||||
|
describe: (value) => (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addressSelect.fromRegistry'
|
||||||
|
defaultMessage='{value} (from registry)'
|
||||||
|
values={ {
|
||||||
|
value
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setValues (props) {
|
||||||
|
const { accounts = {}, contracts = {}, contacts = {} } = props;
|
||||||
|
|
||||||
|
const accountsN = Object.keys(accounts).length;
|
||||||
|
const contractsN = Object.keys(contracts).length;
|
||||||
|
const contactsN = Object.keys(contacts).length;
|
||||||
|
|
||||||
|
if (accountsN + contractsN + contactsN === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initValues = [
|
||||||
|
{
|
||||||
|
key: 'accounts',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addressSelect.labels.accounts'
|
||||||
|
defaultMessage='accounts'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
values: Object.values(accounts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contacts',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addressSelect.labels.contacts'
|
||||||
|
defaultMessage='contacts'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
values: Object.values(contacts)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'contracts',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id='addressSelect.labels.contracts'
|
||||||
|
defaultMessage='contracts'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
values: Object.values(contracts)
|
||||||
|
}
|
||||||
|
].filter((cat) => cat.values.length > 0);
|
||||||
|
|
||||||
|
this.handleChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action handleChange = (value = '') => {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
this.values = this.initValues
|
||||||
|
.map((category) => {
|
||||||
|
const filteredValues = this
|
||||||
|
.filterValues(category.values, value)
|
||||||
|
.map((value) => {
|
||||||
|
index++;
|
||||||
|
|
||||||
|
return {
|
||||||
|
index: parseInt(index),
|
||||||
|
...value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: category.label,
|
||||||
|
values: filteredValues
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Registries Lookup
|
||||||
|
this.registryValues = [];
|
||||||
|
|
||||||
|
const lookups = this.regLookups.map((regLookup) => regLookup.lookup(value));
|
||||||
|
|
||||||
|
Promise
|
||||||
|
.all(lookups)
|
||||||
|
.then((results) => {
|
||||||
|
return results
|
||||||
|
.map((result, index) => {
|
||||||
|
if (/^(0x)?0*$/.test(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowercaseResult = result.toLowerCase();
|
||||||
|
|
||||||
|
const account = flatMap(this.initValues, (cat) => cat.values)
|
||||||
|
.find((account) => account.address.toLowerCase() === lowercaseResult);
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: this.regLookups[index].describe(value),
|
||||||
|
address: result,
|
||||||
|
name: account && account.name || value
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((data) => data);
|
||||||
|
})
|
||||||
|
.then((registryValues) => {
|
||||||
|
this.registryValues = registryValues;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the given values based on the given
|
||||||
|
* filter
|
||||||
|
*/
|
||||||
|
filterValues = (values = [], _filter = '') => {
|
||||||
|
const filter = _filter.toLowerCase();
|
||||||
|
|
||||||
|
return values
|
||||||
|
// Remove empty accounts
|
||||||
|
.filter((a) => a)
|
||||||
|
.filter((account) => {
|
||||||
|
const address = account.address.toLowerCase();
|
||||||
|
const inAddress = address.includes(filter);
|
||||||
|
|
||||||
|
if (!account.name || inAddress) {
|
||||||
|
return inAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = account.name.toLowerCase();
|
||||||
|
const inName = name.includes(filter);
|
||||||
|
const { meta = {} } = account;
|
||||||
|
|
||||||
|
if (!meta.tags || inName) {
|
||||||
|
return inName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = (meta.tags || []).join('');
|
||||||
|
return tags.includes(filter);
|
||||||
|
})
|
||||||
|
.sort((accA, accB) => {
|
||||||
|
const nameA = accA.name || accA.address;
|
||||||
|
const nameB = accB.name || accB.address;
|
||||||
|
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -51,6 +51,7 @@ export default class Input extends Component {
|
|||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.bool
|
PropTypes.bool
|
||||||
]),
|
]),
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
@ -112,7 +113,7 @@ export default class Input extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value } = this.state;
|
const { value } = this.state;
|
||||||
const { children, className, hideUnderline, disabled, error, focused, label } = this.props;
|
const { autoFocus, children, className, hideUnderline, disabled, error, focused, label } = this.props;
|
||||||
const { hint, onClick, onFocus, multiLine, rows, type, min, max, style, tabIndex } = this.props;
|
const { hint, onClick, onFocus, multiLine, rows, type, min, max, style, tabIndex } = this.props;
|
||||||
|
|
||||||
const readOnly = this.props.readOnly || disabled;
|
const readOnly = this.props.readOnly || disabled;
|
||||||
@ -138,6 +139,7 @@ export default class Input extends Component {
|
|||||||
{ this.renderCopyButton() }
|
{ this.renderCopyButton() }
|
||||||
<TextField
|
<TextField
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
|
autoFocus={ autoFocus }
|
||||||
className={ className }
|
className={ className }
|
||||||
errorText={ error }
|
errorText={ error }
|
||||||
floatingLabelFixed
|
floatingLabelFixed
|
||||||
@ -183,7 +185,7 @@ export default class Input extends Component {
|
|||||||
|
|
||||||
const text = typeof allowCopy === 'string'
|
const text = typeof allowCopy === 'string'
|
||||||
? allowCopy
|
? allowCopy
|
||||||
: value;
|
: value.toString();
|
||||||
|
|
||||||
const style = hideUnderline
|
const style = hideUnderline
|
||||||
? {}
|
? {}
|
||||||
|
@ -78,7 +78,7 @@ class InputAddress extends Component {
|
|||||||
return (
|
return (
|
||||||
<div className={ containerClasses.join(' ') }>
|
<div className={ containerClasses.join(' ') }>
|
||||||
<Input
|
<Input
|
||||||
allowCopy={ allowCopy && (disabled ? value : false) }
|
allowCopy={ allowCopy && ((disabled || readOnly) ? value : false) }
|
||||||
className={ classes.join(' ') }
|
className={ classes.join(' ') }
|
||||||
disabled={ disabled }
|
disabled={ disabled }
|
||||||
error={ error }
|
error={ error }
|
||||||
@ -103,13 +103,13 @@ class InputAddress extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderIcon () {
|
renderIcon () {
|
||||||
const { value, disabled, label, allowCopy, hideUnderline } = this.props;
|
const { value, disabled, label, allowCopy, hideUnderline, readOnly } = this.props;
|
||||||
|
|
||||||
if (!value || !value.length || !util.isAddressValid(value)) {
|
if (!value || !value.length || !util.isAddressValid(value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = [disabled ? styles.iconDisabled : styles.icon];
|
const classes = [(disabled || readOnly) ? styles.iconDisabled : styles.icon];
|
||||||
|
|
||||||
if (!label) {
|
if (!label) {
|
||||||
classes.push(styles.noLabel);
|
classes.push(styles.noLabel);
|
||||||
|
@ -25,41 +25,44 @@ class InputAddressSelect extends Component {
|
|||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
contacts: PropTypes.object.isRequired,
|
contacts: PropTypes.object.isRequired,
|
||||||
contracts: PropTypes.object.isRequired,
|
contracts: PropTypes.object.isRequired,
|
||||||
wallets: PropTypes.object.isRequired,
|
|
||||||
|
allowCopy: PropTypes.bool,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
label: PropTypes.string,
|
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
value: PropTypes.string,
|
label: PropTypes.string,
|
||||||
onChange: PropTypes.func
|
onChange: PropTypes.func,
|
||||||
|
readOnly: PropTypes.bool,
|
||||||
|
value: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, contacts, contracts, wallets, label, hint, error, value, onChange } = this.props;
|
const { accounts, allowCopy, contacts, contracts, label, hint, error, value, onChange, readOnly } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
|
allowCopy={ allowCopy }
|
||||||
allowInput
|
allowInput
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
contacts={ contacts }
|
contacts={ contacts }
|
||||||
contracts={ contracts }
|
contracts={ contracts }
|
||||||
wallets={ wallets }
|
|
||||||
error={ error }
|
error={ error }
|
||||||
label={ label }
|
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
|
label={ label }
|
||||||
|
onChange={ onChange }
|
||||||
|
readOnly={ readOnly }
|
||||||
value={ value }
|
value={ value }
|
||||||
onChange={ onChange } />
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, contacts, contracts, wallets } = state.personal;
|
const { accounts, contacts, contracts } = state.personal;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
contacts,
|
contacts,
|
||||||
contracts,
|
contracts
|
||||||
wallets
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,19 +14,18 @@
|
|||||||
// 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 { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
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 = {
|
||||||
|
name: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
values: PropTypes.array.isRequired,
|
|
||||||
|
|
||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
name: PropTypes.string
|
values: arrayOrObjectProptype().isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -40,16 +39,16 @@ export default class RadioButtons extends Component {
|
|||||||
const index = Number.isNaN(parseInt(value))
|
const index = Number.isNaN(parseInt(value))
|
||||||
? values.findIndex((val) => val.key === value)
|
? values.findIndex((val) => val.key === value)
|
||||||
: parseInt(value);
|
: parseInt(value);
|
||||||
|
const selectedValue = typeof value !== 'object'
|
||||||
const selectedValue = typeof value !== 'object' ? values[index] : value;
|
? values[index]
|
||||||
|
: value;
|
||||||
const key = this.getKey(selectedValue, index);
|
const key = this.getKey(selectedValue, index);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RadioButtonGroup
|
<RadioButtonGroup
|
||||||
valueSelected={ key }
|
|
||||||
name={ name }
|
name={ name }
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
>
|
valueSelected={ key } >
|
||||||
{ this.renderContent() }
|
{ this.renderContent() }
|
||||||
</RadioButtonGroup>
|
</RadioButtonGroup>
|
||||||
);
|
);
|
||||||
@ -59,7 +58,9 @@ export default class RadioButtons extends Component {
|
|||||||
const { values } = this.props;
|
const { values } = this.props;
|
||||||
|
|
||||||
return values.map((value, index) => {
|
return values.map((value, index) => {
|
||||||
const label = typeof value === 'string' ? value : value.label || '';
|
const label = typeof value === 'string'
|
||||||
|
? value
|
||||||
|
: value.label || '';
|
||||||
const description = (typeof value !== 'string' && value.description) || null;
|
const description = (typeof value !== 'string' && value.description) || null;
|
||||||
const key = this.getKey(value, index);
|
const key = this.getKey(value, index);
|
||||||
|
|
||||||
@ -67,28 +68,26 @@ export default class RadioButtons extends Component {
|
|||||||
<RadioButton
|
<RadioButton
|
||||||
className={ styles.spaced }
|
className={ styles.spaced }
|
||||||
key={ index }
|
key={ index }
|
||||||
|
label={
|
||||||
value={ key }
|
|
||||||
label={ (
|
|
||||||
<div className={ styles.typeContainer }>
|
<div className={ styles.typeContainer }>
|
||||||
<span>{ label }</span>
|
<span>{ label }</span>
|
||||||
{
|
{
|
||||||
description
|
description
|
||||||
? (
|
? <span className={ styles.desc }>{ description }</span>
|
||||||
<span className={ styles.desc }>{ description }</span>
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
) }
|
}
|
||||||
/>
|
value={ key } />
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getKey (value, index) {
|
getKey (value, index) {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return typeof value.key === 'undefined' ? index : value.key;
|
return typeof value.key === 'undefined'
|
||||||
|
? index
|
||||||
|
: value.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
@ -96,8 +95,8 @@ 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((v) => v.key === index);
|
||||||
|
|
||||||
onChange(value, index);
|
onChange(value, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { MenuItem, Toggle } from 'material-ui';
|
import { MenuItem, Toggle } from 'material-ui';
|
||||||
import { range } from 'lodash';
|
import { range } from 'lodash';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import IconButton from 'material-ui/IconButton';
|
import IconButton from 'material-ui/IconButton';
|
||||||
import AddIcon from 'material-ui/svg-icons/content/add';
|
import AddIcon from 'material-ui/svg-icons/content/add';
|
||||||
@ -33,26 +34,31 @@ import styles from './typedInput.css';
|
|||||||
export default class TypedInput extends Component {
|
export default class TypedInput extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
param: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.string
|
||||||
|
]).isRequired,
|
||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
|
allowCopy: PropTypes.bool,
|
||||||
error: PropTypes.any,
|
error: PropTypes.any,
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
isEth: PropTypes.bool,
|
isEth: PropTypes.bool,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
max: PropTypes.number,
|
max: PropTypes.number,
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
param: PropTypes.oneOfType([
|
onChange: PropTypes.func,
|
||||||
PropTypes.object,
|
readOnly: PropTypes.bool,
|
||||||
PropTypes.string
|
|
||||||
]).isRequired,
|
|
||||||
value: PropTypes.any
|
value: PropTypes.any
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
allowCopy: false,
|
||||||
|
isEth: null,
|
||||||
min: null,
|
min: null,
|
||||||
max: null,
|
max: null,
|
||||||
isEth: null
|
onChange: () => {},
|
||||||
|
readOnly: false
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -61,21 +67,16 @@ export default class TypedInput extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
if (this.props.isEth && this.props.value) {
|
const { isEth, value } = this.props;
|
||||||
this.setState({ isEth: true, ethValue: fromWei(this.props.value) });
|
|
||||||
|
if (typeof isEth === 'boolean' && value) {
|
||||||
|
const ethValue = isEth ? fromWei(value) : value;
|
||||||
|
this.setState({ isEth, ethValue });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { param } = this.props;
|
const param = this.getParam();
|
||||||
|
|
||||||
if (typeof param === 'string') {
|
|
||||||
const parsedParam = parseAbiType(param);
|
|
||||||
|
|
||||||
if (parsedParam) {
|
|
||||||
return this.renderParam(parsedParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param) {
|
if (param) {
|
||||||
return this.renderParam(param);
|
return this.renderParam(param);
|
||||||
@ -86,7 +87,7 @@ export default class TypedInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderParam (param) {
|
renderParam (param) {
|
||||||
const { isEth } = this.props;
|
const { allowCopy, isEth, readOnly } = this.props;
|
||||||
const { type } = param;
|
const { type } = param;
|
||||||
|
|
||||||
if (type === ABI_TYPES.ARRAY) {
|
if (type === ABI_TYPES.ARRAY) {
|
||||||
@ -104,10 +105,12 @@ export default class TypedInput extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedInput
|
<TypedInput
|
||||||
|
accounts={ accounts }
|
||||||
|
allowCopy={ allowCopy }
|
||||||
key={ `${subtype.type}_${index}` }
|
key={ `${subtype.type}_${index}` }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
accounts={ accounts }
|
|
||||||
param={ subtype }
|
param={ subtype }
|
||||||
|
readOnly={ readOnly }
|
||||||
value={ value[index] }
|
value={ value[index] }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -234,20 +237,23 @@ export default class TypedInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderInteger (value = this.props.value, onChange = this.onChange) {
|
renderInteger (value = this.props.value, onChange = this.onChange) {
|
||||||
const { label, error, param, hint, min, max } = this.props;
|
const { allowCopy, label, error, hint, min, max, readOnly } = this.props;
|
||||||
|
const param = this.getParam();
|
||||||
|
|
||||||
const realValue = value && typeof value.toNumber === 'function'
|
const realValue = value
|
||||||
? value.toNumber()
|
? (new BigNumber(value))[readOnly ? 'toFormat' : 'toNumber']()
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
|
allowCopy={ allowCopy }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
value={ realValue }
|
value={ realValue }
|
||||||
error={ error }
|
error={ error }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
type='number'
|
readOnly={ readOnly }
|
||||||
|
type={ readOnly ? 'text' : 'number' }
|
||||||
step={ 1 }
|
step={ 1 }
|
||||||
min={ min !== null ? min : (param.signed ? null : 0) }
|
min={ min !== null ? min : (param.signed ? null : 0) }
|
||||||
max={ max !== null ? max : null }
|
max={ max !== null ? max : null }
|
||||||
@ -263,19 +269,22 @@ export default class TypedInput extends Component {
|
|||||||
* @see https://github.com/facebook/react/issues/1549
|
* @see https://github.com/facebook/react/issues/1549
|
||||||
*/
|
*/
|
||||||
renderFloat (value = this.props.value, onChange = this.onChange) {
|
renderFloat (value = this.props.value, onChange = this.onChange) {
|
||||||
const { label, error, param, hint, min, max } = this.props;
|
const { allowCopy, label, error, hint, min, max, readOnly } = this.props;
|
||||||
|
const param = this.getParam();
|
||||||
|
|
||||||
const realValue = value && typeof value.toNumber === 'function'
|
const realValue = value
|
||||||
? value.toNumber()
|
? (new BigNumber(value))[readOnly ? 'toFormat' : 'toNumber']()
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
|
allowCopy={ allowCopy }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
value={ realValue }
|
value={ realValue }
|
||||||
error={ error }
|
error={ error }
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
|
readOnly={ readOnly }
|
||||||
type='text'
|
type='text'
|
||||||
min={ min !== null ? min : (param.signed ? null : 0) }
|
min={ min !== null ? min : (param.signed ? null : 0) }
|
||||||
max={ max !== null ? max : null }
|
max={ max !== null ? max : null }
|
||||||
@ -284,37 +293,44 @@ export default class TypedInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDefault () {
|
renderDefault () {
|
||||||
const { label, value, error, hint } = this.props;
|
const { allowCopy, label, value, error, hint, readOnly } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
|
allowCopy={ allowCopy }
|
||||||
label={ label }
|
label={ label }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
value={ value }
|
value={ value }
|
||||||
error={ error }
|
error={ error }
|
||||||
onSubmit={ this.onSubmit }
|
onSubmit={ this.onSubmit }
|
||||||
|
readOnly={ readOnly }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAddress () {
|
renderAddress () {
|
||||||
const { accounts, label, value, error, hint } = this.props;
|
const { accounts, allowCopy, label, value, error, hint, readOnly } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputAddressSelect
|
<InputAddressSelect
|
||||||
|
allowCopy={ allowCopy }
|
||||||
accounts={ accounts }
|
accounts={ accounts }
|
||||||
label={ label }
|
|
||||||
hint={ hint }
|
|
||||||
value={ value }
|
|
||||||
error={ error }
|
error={ error }
|
||||||
|
hint={ hint }
|
||||||
|
label={ label }
|
||||||
onChange={ this.onChange }
|
onChange={ this.onChange }
|
||||||
editing
|
readOnly={ readOnly }
|
||||||
|
value={ value }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBoolean () {
|
renderBoolean () {
|
||||||
const { label, value, error, hint } = this.props;
|
const { allowCopy, label, value, error, hint, readOnly } = this.props;
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return this.renderDefault();
|
||||||
|
}
|
||||||
|
|
||||||
const boolitems = ['false', 'true'].map((bool) => {
|
const boolitems = ['false', 'true'].map((bool) => {
|
||||||
return (
|
return (
|
||||||
@ -329,6 +345,7 @@ export default class TypedInput extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
allowCopy={ allowCopy }
|
||||||
error={ error }
|
error={ error }
|
||||||
hint={ hint }
|
hint={ hint }
|
||||||
label={ label }
|
label={ label }
|
||||||
@ -379,7 +396,9 @@ export default class TypedInput extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAddField = () => {
|
onAddField = () => {
|
||||||
const { value, onChange, param } = this.props;
|
const { value, onChange } = this.props;
|
||||||
|
const param = this.getParam();
|
||||||
|
|
||||||
const newValues = [].concat(value, param.subtype.default);
|
const newValues = [].concat(value, param.subtype.default);
|
||||||
|
|
||||||
onChange(newValues);
|
onChange(newValues);
|
||||||
@ -392,4 +411,14 @@ export default class TypedInput extends Component {
|
|||||||
onChange(newValues);
|
onChange(newValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getParam = () => {
|
||||||
|
const { param } = this.props;
|
||||||
|
|
||||||
|
if (typeof param === 'string') {
|
||||||
|
return parseAbiType(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import LockedIcon from 'material-ui/svg-icons/action/lock-outline';
|
|||||||
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 SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
import SnoozeIcon from 'material-ui/svg-icons/av/snooze';
|
||||||
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye';
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ export {
|
|||||||
NextIcon,
|
NextIcon,
|
||||||
PrevIcon,
|
PrevIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
|
SendIcon,
|
||||||
SnoozeIcon,
|
SnoozeIcon,
|
||||||
VisibleIcon
|
VisibleIcon
|
||||||
};
|
};
|
||||||
|
@ -118,6 +118,15 @@ export default class MethodDecodingStore {
|
|||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { signature } = this.api.util.decodeCallData(input);
|
||||||
|
|
||||||
|
if (signature === CONTRACT_CREATE || transaction.creates) {
|
||||||
|
result.contract = true;
|
||||||
|
return Promise.resolve({ ...result, deploy: true });
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
.isContract(contractAddress || transaction.creates)
|
.isContract(contractAddress || transaction.creates)
|
||||||
.then((isContract) => {
|
.then((isContract) => {
|
||||||
@ -132,7 +141,7 @@ export default class MethodDecodingStore {
|
|||||||
result.params = paramdata;
|
result.params = paramdata;
|
||||||
|
|
||||||
// Contract deployment
|
// Contract deployment
|
||||||
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
if (!signature) {
|
||||||
return Promise.resolve({ ...result, deploy: true });
|
return Promise.resolve({ ...result, deploy: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +201,7 @@ export default class MethodDecodingStore {
|
|||||||
*/
|
*/
|
||||||
isContract (contractAddress) {
|
isContract (contractAddress) {
|
||||||
// If zero address, it isn't a contract
|
// If zero address, it isn't a contract
|
||||||
if (/^(0x)?0*$/.test(contractAddress)) {
|
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import { Dialog } from 'material-ui';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
@ -49,6 +50,14 @@ class Modal extends Component {
|
|||||||
waiting: PropTypes.array
|
waiting: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const element = ReactDOM.findDOMNode(this.refs.dialog);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { muiTheme } = this.context;
|
const { muiTheme } = this.context;
|
||||||
const { actions, busy, children, className, current, compact, settings, steps, title, visible, waiting } = this.props;
|
const { actions, busy, children, className, current, compact, settings, steps, title, visible, waiting } = this.props;
|
||||||
@ -85,9 +94,12 @@ class Modal extends Component {
|
|||||||
<Container
|
<Container
|
||||||
compact={ compact }
|
compact={ compact }
|
||||||
light
|
light
|
||||||
|
ref='dialog'
|
||||||
style={
|
style={
|
||||||
{ transition: 'none' }
|
{ transition: 'none' }
|
||||||
}>
|
}
|
||||||
|
tabIndex={ 0 }
|
||||||
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</Container>
|
</Container>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -15,6 +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;
|
||||||
|
$right: $left;
|
||||||
|
$bottom: $left;
|
||||||
|
$top: 20vh;
|
||||||
|
|
||||||
|
.backOverlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
z-index: -10;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transform-origin: 100% 0;
|
||||||
|
transition-property: opacity, z-index;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-timing-function: ease-out;
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 2500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.parityBackground {
|
.parityBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -28,10 +54,10 @@
|
|||||||
.overlay {
|
.overlay {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: $top;
|
||||||
left: 0;
|
left: $left;
|
||||||
width: 100vw;
|
width: calc(100vw - $left - $right);
|
||||||
height: 100vh;
|
height: calc(100vh - $top - $bottom);
|
||||||
|
|
||||||
transform-origin: 100% 0;
|
transform-origin: 100% 0;
|
||||||
transition-property: opacity, z-index;
|
transition-property: opacity, z-index;
|
||||||
@ -48,7 +74,7 @@
|
|||||||
|
|
||||||
&.expanded {
|
&.expanded {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 9999;
|
z-index: 3500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Portal from 'react-portal';
|
import ReactPortal from 'react-portal';
|
||||||
import keycode from 'keycode';
|
import keycode from 'keycode';
|
||||||
|
|
||||||
import { CloseIcon } from '~/ui/Icons';
|
import { CloseIcon } from '~/ui/Icons';
|
||||||
@ -24,7 +24,7 @@ import ParityBackground from '~/ui/ParityBackground';
|
|||||||
|
|
||||||
import styles from './portal.css';
|
import styles from './portal.css';
|
||||||
|
|
||||||
export default class Protal extends Component {
|
export default class Portal extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
@ -59,15 +59,19 @@ export default class Protal extends Component {
|
|||||||
const { children, className } = this.props;
|
const { children, className } = this.props;
|
||||||
|
|
||||||
const classes = [ styles.overlay, className ];
|
const classes = [ styles.overlay, className ];
|
||||||
|
const backClasses = [ styles.backOverlay ];
|
||||||
|
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
classes.push(styles.expanded);
|
classes.push(styles.expanded);
|
||||||
|
backClasses.push(styles.expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal isOpened onClose={ this.handleClose }>
|
<ReactPortal isOpened onClose={ this.handleClose }>
|
||||||
|
<div className={ backClasses.join(' ') } onClick={ this.handleClose }>
|
||||||
<div
|
<div
|
||||||
className={ classes.join(' ') }
|
className={ classes.join(' ') }
|
||||||
|
onClick={ this.stopEvent }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
>
|
>
|
||||||
<ParityBackground className={ styles.parityBackground } />
|
<ParityBackground className={ styles.parityBackground } />
|
||||||
@ -75,7 +79,8 @@ export default class Protal extends Component {
|
|||||||
{ this.renderCloseIcon() }
|
{ this.renderCloseIcon() }
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</div>
|
||||||
|
</ReactPortal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +98,11 @@ export default class Protal extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopEvent = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,15 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from './tags.css';
|
import styles from './tags.css';
|
||||||
|
|
||||||
export default class Tags extends Component {
|
export default class Tags extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
handleAddSearchToken: PropTypes.func,
|
handleAddSearchToken: PropTypes.func,
|
||||||
setRefs: PropTypes.func,
|
setRefs: PropTypes.func,
|
||||||
tags: PropTypes.array
|
tags: arrayOrObjectProptype()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -134,7 +134,7 @@ class TxHash extends Component {
|
|||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { hash } = this.props;
|
const { hash } = this.props;
|
||||||
|
|
||||||
if (error) {
|
if (error || !hash || /^(0x)?0*$/.test(hash)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,13 @@
|
|||||||
|
|
||||||
import { PropTypes } from 'react';
|
import { PropTypes } from 'react';
|
||||||
|
|
||||||
|
export function arrayOrObjectProptype () {
|
||||||
|
return PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.object
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export function nullableProptype (type) {
|
export function nullableProptype (type) {
|
||||||
return PropTypes.oneOfType([
|
return PropTypes.oneOfType([
|
||||||
PropTypes.oneOf([ null ]),
|
PropTypes.oneOf([ null ]),
|
||||||
|
@ -14,10 +14,93 @@
|
|||||||
// 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 WalletsUtils from '~/util/wallets';
|
||||||
|
|
||||||
const isValidReceipt = (receipt) => {
|
const isValidReceipt = (receipt) => {
|
||||||
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
|
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getTxArgs (func, options, values = []) {
|
||||||
|
const { contract } = func;
|
||||||
|
const { api } = contract;
|
||||||
|
const address = options.from;
|
||||||
|
|
||||||
|
if (!address) {
|
||||||
|
return Promise.resolve({ func, options, values });
|
||||||
|
}
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.isWallet(api, address)
|
||||||
|
.then((isWallet) => {
|
||||||
|
if (!isWallet) {
|
||||||
|
return { func, options, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
options.data = contract.getCallData(func, options, values);
|
||||||
|
options.to = options.to || contract.address;
|
||||||
|
|
||||||
|
if (!options.to) {
|
||||||
|
return { func, options, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
return WalletsUtils
|
||||||
|
.getCallArgs(api, options, values)
|
||||||
|
.then((callArgs) => {
|
||||||
|
if (!callArgs) {
|
||||||
|
return { func, options, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
return callArgs;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function estimateGas (_func, _options, _values = []) {
|
||||||
|
return getTxArgs(_func, _options, _values)
|
||||||
|
.then((callArgs) => {
|
||||||
|
const { func, options, values } = callArgs;
|
||||||
|
return func._estimateGas(options, values);
|
||||||
|
})
|
||||||
|
.then((gas) => {
|
||||||
|
return WalletsUtils
|
||||||
|
.isWallet(_func.contract.api, _options.from)
|
||||||
|
.then((isWallet) => {
|
||||||
|
if (isWallet) {
|
||||||
|
return gas.mul(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gas;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postTransaction (_func, _options, _values = []) {
|
||||||
|
return getTxArgs(_func, _options, _values)
|
||||||
|
.then((callArgs) => {
|
||||||
|
const { func, options, values } = callArgs;
|
||||||
|
return func._postTransaction(options, values);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchApi (api) {
|
||||||
|
api.patch = {
|
||||||
|
...api.patch,
|
||||||
|
contract: patchContract
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchContract (contract) {
|
||||||
|
contract._functions.forEach((func) => {
|
||||||
|
if (!func.constant) {
|
||||||
|
func._postTransaction = func.postTransaction;
|
||||||
|
func._estimateGas = func.estimateGas;
|
||||||
|
|
||||||
|
func.postTransaction = postTransaction.bind(contract, func);
|
||||||
|
func.estimateGas = estimateGas.bind(contract, func);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function checkIfTxFailed (api, tx, gasSent) {
|
export function checkIfTxFailed (api, tx, gasSent) {
|
||||||
return api.pollMethod('eth_getTransactionReceipt', tx)
|
return api.pollMethod('eth_getTransactionReceipt', tx)
|
||||||
.then((receipt) => {
|
.then((receipt) => {
|
||||||
|
@ -35,21 +35,21 @@ export const ERRORS = {
|
|||||||
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
|
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function validateAbi (abi, api) {
|
export function validateAbi (abi) {
|
||||||
let abiError = null;
|
let abiError = null;
|
||||||
let abiParsed = null;
|
let abiParsed = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
abiParsed = JSON.parse(abi);
|
abiParsed = JSON.parse(abi);
|
||||||
|
|
||||||
if (!api.util.isArray(abiParsed)) {
|
if (!util.isArray(abiParsed)) {
|
||||||
abiError = ERRORS.invalidAbi;
|
abiError = ERRORS.invalidAbi;
|
||||||
return { abi, abiError, abiParsed };
|
return { abi, abiError, abiParsed };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate each elements of the Array
|
// Validate each elements of the Array
|
||||||
const invalidIndex = abiParsed
|
const invalidIndex = abiParsed
|
||||||
.map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api) || isAbiFallback(o))
|
.map((o) => isValidAbiEvent(o) || isValidAbiFunction(o) || isAbiFallback(o))
|
||||||
.findIndex((valid) => !valid);
|
.findIndex((valid) => !valid);
|
||||||
|
|
||||||
if (invalidIndex !== -1) {
|
if (invalidIndex !== -1) {
|
||||||
@ -70,13 +70,13 @@ export function validateAbi (abi, api) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidAbiFunction (object, api) {
|
function isValidAbiFunction (object) {
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
|
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
|
||||||
(object.inputs && api.util.isArray(object.inputs));
|
(object.inputs && util.isArray(object.inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAbiFallback (object) {
|
function isAbiFallback (object) {
|
||||||
@ -87,14 +87,14 @@ function isAbiFallback (object) {
|
|||||||
return object.type === 'fallback';
|
return object.type === 'fallback';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidAbiEvent (object, api) {
|
function isValidAbiEvent (object) {
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (object.type === 'event') &&
|
return (object.type === 'event') &&
|
||||||
(object.name) &&
|
(object.name) &&
|
||||||
(object.inputs && api.util.isArray(object.inputs));
|
(object.inputs && util.isArray(object.inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateAddress (address) {
|
export function validateAddress (address) {
|
||||||
|
@ -14,13 +14,92 @@
|
|||||||
// 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 { range, uniq } from 'lodash';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { intersection, range, uniq } from 'lodash';
|
||||||
|
|
||||||
|
import Contract from '~/api/contract';
|
||||||
import { bytesToHex, toHex } from '~/api/util/format';
|
import { bytesToHex, toHex } from '~/api/util/format';
|
||||||
import { validateAddress } from '~/util/validation';
|
import { validateAddress } from '~/util/validation';
|
||||||
|
import WalletAbi from '~/contracts/abi/wallet.json';
|
||||||
|
|
||||||
|
const _cachedWalletLookup = {};
|
||||||
|
|
||||||
export default class WalletsUtils {
|
export default class WalletsUtils {
|
||||||
|
|
||||||
|
static getCallArgs (api, options, values = []) {
|
||||||
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
api.parity.accountsInfo(),
|
||||||
|
WalletsUtils.fetchOwners(walletContract.at(options.from))
|
||||||
|
];
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all(promises)
|
||||||
|
.then(([ accounts, owners ]) => {
|
||||||
|
const addresses = Object.keys(accounts);
|
||||||
|
const owner = intersection(addresses, owners).pop();
|
||||||
|
|
||||||
|
if (!owner) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner;
|
||||||
|
})
|
||||||
|
.then((owner) => {
|
||||||
|
if (!owner) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _options = Object.assign({}, options);
|
||||||
|
const { from, to, value = new BigNumber(0), data } = options;
|
||||||
|
|
||||||
|
delete _options.data;
|
||||||
|
|
||||||
|
const nextValues = [ to, value, data ];
|
||||||
|
const nextOptions = {
|
||||||
|
..._options,
|
||||||
|
from: owner,
|
||||||
|
to: from,
|
||||||
|
value: new BigNumber(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
const execFunc = walletContract.instance.execute;
|
||||||
|
|
||||||
|
return { func: execFunc, options: nextOptions, values: nextValues };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given address could be
|
||||||
|
* a Wallet. The result is cached in order not
|
||||||
|
* to make unnecessary calls on non-wallet accounts
|
||||||
|
*/
|
||||||
|
static isWallet (api, address) {
|
||||||
|
if (!_cachedWalletLookup[address]) {
|
||||||
|
const walletContract = new Contract(api, WalletAbi);
|
||||||
|
|
||||||
|
_cachedWalletLookup[address] = walletContract
|
||||||
|
.at(address)
|
||||||
|
.instance
|
||||||
|
.m_numOwners
|
||||||
|
.call()
|
||||||
|
.then((result) => {
|
||||||
|
if (!result || result.equals(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.then((bool) => {
|
||||||
|
_cachedWalletLookup[address] = Promise.resolve(bool);
|
||||||
|
return bool;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cachedWalletLookup[address];
|
||||||
|
}
|
||||||
|
|
||||||
static fetchRequire (walletContract) {
|
static fetchRequire (walletContract) {
|
||||||
return walletContract.instance.m_required.call();
|
return walletContract.instance.m_required.call();
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ export default class Header extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const { account, balance, className, children, hideName } = this.props;
|
const { account, balance, className, children, hideName } = this.props;
|
||||||
const { address, meta, uuid } = account;
|
const { address, meta, uuid } = account;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ class List extends Component {
|
|||||||
order: PropTypes.string,
|
order: PropTypes.string,
|
||||||
orderFallback: PropTypes.string,
|
orderFallback: PropTypes.string,
|
||||||
search: PropTypes.array,
|
search: PropTypes.array,
|
||||||
walletsOwners: PropTypes.object,
|
|
||||||
|
|
||||||
fetchCertifiers: PropTypes.func.isRequired,
|
fetchCertifiers: PropTypes.func.isRequired,
|
||||||
fetchCertifications: PropTypes.func.isRequired,
|
fetchCertifications: PropTypes.func.isRequired,
|
||||||
@ -58,7 +57,7 @@ class List extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
const { accounts, balances, empty, link, walletsOwners, handleAddSearchToken } = this.props;
|
const { accounts, balances, empty, link, handleAddSearchToken } = this.props;
|
||||||
|
|
||||||
if (empty) {
|
if (empty) {
|
||||||
return (
|
return (
|
||||||
@ -76,7 +75,7 @@ class List extends Component {
|
|||||||
const account = accounts[address] || {};
|
const account = accounts[address] || {};
|
||||||
const balance = balances[address] || {};
|
const balance = balances[address] || {};
|
||||||
|
|
||||||
const owners = walletsOwners && walletsOwners[address] || null;
|
const owners = account.owners || null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -157,7 +157,11 @@ export default class Summary extends Component {
|
|||||||
const { link, noLink, account, name } = this.props;
|
const { link, noLink, account, name } = this.props;
|
||||||
|
|
||||||
const { address } = account;
|
const { address } = account;
|
||||||
const viewLink = `/${link || 'accounts'}/${address}`;
|
const baseLink = account.wallet
|
||||||
|
? 'wallet'
|
||||||
|
: link || 'accounts';
|
||||||
|
|
||||||
|
const viewLink = `/${baseLink}/${address}`;
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<IdentityName address={ address } name={ name } unknown />
|
<IdentityName address={ address } name={ name } unknown />
|
||||||
|
@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
import { uniq, isEqual } from 'lodash';
|
import { uniq, isEqual, pickBy, omitBy } from 'lodash';
|
||||||
|
|
||||||
import List from './List';
|
import List from './List';
|
||||||
import { CreateAccount, CreateWallet } from '~/modals';
|
import { CreateAccount, CreateWallet } from '~/modals';
|
||||||
@ -36,9 +36,6 @@ class Accounts extends Component {
|
|||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
hasAccounts: PropTypes.bool.isRequired,
|
hasAccounts: PropTypes.bool.isRequired,
|
||||||
wallets: PropTypes.object.isRequired,
|
|
||||||
walletsOwners: PropTypes.object.isRequired,
|
|
||||||
hasWallets: PropTypes.bool.isRequired,
|
|
||||||
|
|
||||||
balances: PropTypes.object
|
balances: PropTypes.object
|
||||||
}
|
}
|
||||||
@ -62,8 +59,8 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
const prevAddresses = Object.keys({ ...this.props.accounts, ...this.props.wallets });
|
const prevAddresses = Object.keys(this.props.accounts);
|
||||||
const nextAddresses = Object.keys({ ...nextProps.accounts, ...nextProps.wallets });
|
const nextAddresses = Object.keys(nextProps.accounts);
|
||||||
|
|
||||||
if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) {
|
if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) {
|
||||||
this.setVisibleAccounts(nextProps);
|
this.setVisibleAccounts(nextProps);
|
||||||
@ -75,8 +72,8 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setVisibleAccounts (props = this.props) {
|
setVisibleAccounts (props = this.props) {
|
||||||
const { accounts, wallets, setVisibleAccounts } = props;
|
const { accounts, setVisibleAccounts } = props;
|
||||||
const addresses = Object.keys({ ...accounts, ...wallets });
|
const addresses = Object.keys(accounts);
|
||||||
setVisibleAccounts(addresses);
|
setVisibleAccounts(addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,30 +112,38 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAccounts () {
|
renderAccounts () {
|
||||||
|
const { accounts, balances } = this.props;
|
||||||
|
|
||||||
|
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||||
|
const _hasAccounts = Object.keys(_accounts).length > 0;
|
||||||
|
|
||||||
if (!this.state.show) {
|
if (!this.state.show) {
|
||||||
return this.renderLoading(this.props.accounts);
|
return this.renderLoading(_accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { accounts, hasAccounts, balances } = this.props;
|
|
||||||
const { searchValues, sortOrder } = this.state;
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List
|
<List
|
||||||
search={ searchValues }
|
search={ searchValues }
|
||||||
accounts={ accounts }
|
accounts={ _accounts }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
empty={ !hasAccounts }
|
empty={ !_hasAccounts }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken } />
|
handleAddSearchToken={ this.onAddSearchToken } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderWallets () {
|
renderWallets () {
|
||||||
|
const { accounts, balances } = this.props;
|
||||||
|
|
||||||
|
const wallets = pickBy(accounts, (a) => a.wallet);
|
||||||
|
const hasWallets = Object.keys(wallets).length > 0;
|
||||||
|
|
||||||
if (!this.state.show) {
|
if (!this.state.show) {
|
||||||
return this.renderLoading(this.props.wallets);
|
return this.renderLoading(wallets);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wallets, hasWallets, balances, walletsOwners } = this.props;
|
|
||||||
const { searchValues, sortOrder } = this.state;
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
if (!wallets || Object.keys(wallets).length === 0) {
|
if (!wallets || Object.keys(wallets).length === 0) {
|
||||||
@ -154,7 +159,6 @@ class Accounts extends Component {
|
|||||||
empty={ !hasWallets }
|
empty={ !hasWallets }
|
||||||
order={ sortOrder }
|
order={ sortOrder }
|
||||||
handleAddSearchToken={ this.onAddSearchToken }
|
handleAddSearchToken={ this.onAddSearchToken }
|
||||||
walletsOwners={ walletsOwners }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -287,34 +291,12 @@ class Accounts extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, hasAccounts, wallets, hasWallets, accountsInfo } = state.personal;
|
const { accounts, hasAccounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
const walletsInfo = state.wallet.wallets;
|
|
||||||
|
|
||||||
const walletsOwners = Object
|
|
||||||
.keys(walletsInfo)
|
|
||||||
.map((wallet) => {
|
|
||||||
const owners = walletsInfo[wallet].owners || [];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
owners: owners.map((owner) => ({
|
accounts: accounts,
|
||||||
address: owner,
|
hasAccounts: hasAccounts,
|
||||||
name: accountsInfo[owner] && accountsInfo[owner].name || owner
|
|
||||||
})),
|
|
||||||
address: wallet
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.reduce((walletsOwners, wallet) => {
|
|
||||||
walletsOwners[wallet.address] = wallet.owners;
|
|
||||||
return walletsOwners;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return {
|
|
||||||
accounts,
|
|
||||||
hasAccounts,
|
|
||||||
wallets,
|
|
||||||
walletsOwners,
|
|
||||||
hasWallets,
|
|
||||||
balances
|
balances
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -19,29 +19,31 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import LinearProgress from 'material-ui/LinearProgress';
|
import LinearProgress from 'material-ui/LinearProgress';
|
||||||
import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card';
|
import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card';
|
||||||
|
|
||||||
import { Button, Input, InputAddress, InputAddressSelect } from '~/ui';
|
import { Button, TypedInput } from '~/ui';
|
||||||
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from './queries.css';
|
import styles from './queries.css';
|
||||||
|
|
||||||
export default class InputQuery extends Component {
|
export default class InputQuery extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object
|
api: PropTypes.object
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
contract: PropTypes.object.isRequired,
|
contract: PropTypes.object.isRequired,
|
||||||
inputs: PropTypes.array.isRequired,
|
inputs: arrayOrObjectProptype().isRequired,
|
||||||
outputs: PropTypes.array.isRequired,
|
outputs: arrayOrObjectProptype().isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
signature: PropTypes.string.isRequired,
|
signature: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string
|
className: PropTypes.string
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
results: [],
|
results: [],
|
||||||
values: {}
|
values: {}
|
||||||
}
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { name, className } = this.props;
|
const { name, className } = this.props;
|
||||||
@ -89,7 +91,7 @@ export default class InputQuery extends Component {
|
|||||||
|
|
||||||
renderResults () {
|
renderResults () {
|
||||||
const { results, isLoading } = this.state;
|
const { results, isLoading } = this.state;
|
||||||
const { outputs } = this.props;
|
const { accountsInfo, outputs } = this.props;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (<LinearProgress mode='indeterminate' />);
|
return (<LinearProgress mode='indeterminate' />);
|
||||||
@ -108,25 +110,16 @@ export default class InputQuery extends Component {
|
|||||||
}))
|
}))
|
||||||
.sort((outA, outB) => outA.display.length - outB.display.length)
|
.sort((outA, outB) => outA.display.length - outB.display.length)
|
||||||
.map((out, index) => {
|
.map((out, index) => {
|
||||||
let input = null;
|
const input = (
|
||||||
if (out.type === 'address') {
|
<TypedInput
|
||||||
input = (
|
accounts={ accountsInfo }
|
||||||
<InputAddress
|
|
||||||
className={ styles.queryValue }
|
|
||||||
disabled
|
|
||||||
value={ out.display }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
input = (
|
|
||||||
<Input
|
|
||||||
className={ styles.queryValue }
|
|
||||||
readOnly
|
|
||||||
allowCopy
|
allowCopy
|
||||||
|
isEth={ false }
|
||||||
|
param={ out.type }
|
||||||
|
readOnly
|
||||||
value={ out.display }
|
value={ out.display }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={ index }>
|
<div key={ index }>
|
||||||
@ -144,8 +137,7 @@ export default class InputQuery extends Component {
|
|||||||
const { name, type } = input;
|
const { name, type } = input;
|
||||||
const label = `${name ? `${name}: ` : ''}${type}`;
|
const label = `${name ? `${name}: ` : ''}${type}`;
|
||||||
|
|
||||||
const onChange = (event, input) => {
|
const onChange = (value) => {
|
||||||
const value = event && event.target.value || input;
|
|
||||||
const { values } = this.state;
|
const { values } = this.state;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -156,28 +148,15 @@ export default class InputQuery extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'address') {
|
|
||||||
return (
|
return (
|
||||||
<div key={ name }>
|
<div key={ name }>
|
||||||
<InputAddressSelect
|
<TypedInput
|
||||||
hint={ type }
|
hint={ type }
|
||||||
label={ label }
|
label={ label }
|
||||||
value={ values[name] }
|
isEth={ false }
|
||||||
required
|
|
||||||
onChange={ onChange }
|
onChange={ onChange }
|
||||||
/>
|
param={ type }
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={ name }>
|
|
||||||
<Input
|
|
||||||
hint={ type }
|
|
||||||
label={ label }
|
|
||||||
value={ values[name] }
|
value={ values[name] }
|
||||||
required
|
|
||||||
onChange={ onChange }
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -192,7 +171,9 @@ export default class InputQuery extends Component {
|
|||||||
|
|
||||||
if (api.util.isInstanceOf(value, BigNumber)) {
|
if (api.util.isInstanceOf(value, BigNumber)) {
|
||||||
return value.toFormat(0);
|
return value.toFormat(0);
|
||||||
} else if (api.util.isArray(value)) {
|
}
|
||||||
|
|
||||||
|
if (api.util.isArray(value)) {
|
||||||
return api.util.bytesToHex(value);
|
return api.util.bytesToHex(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,11 @@
|
|||||||
// 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 React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Card, CardTitle, CardText } from 'material-ui/Card';
|
import { Card, CardTitle, CardText } from 'material-ui/Card';
|
||||||
|
|
||||||
import InputQuery from './inputQuery';
|
import InputQuery from './inputQuery';
|
||||||
import { Container, Input, InputAddress } from '~/ui';
|
import { Container, TypedInput } from '~/ui';
|
||||||
|
|
||||||
import styles from './queries.css';
|
import styles from './queries.css';
|
||||||
|
|
||||||
@ -29,6 +28,7 @@ export default class Queries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
accountsInfo: PropTypes.object.isRequired,
|
||||||
contract: PropTypes.object,
|
contract: PropTypes.object,
|
||||||
values: PropTypes.object
|
values: PropTypes.object
|
||||||
}
|
}
|
||||||
@ -74,11 +74,12 @@ export default class Queries extends Component {
|
|||||||
|
|
||||||
renderInputQuery (fn) {
|
renderInputQuery (fn) {
|
||||||
const { abi, name, signature } = fn;
|
const { abi, name, signature } = fn;
|
||||||
const { contract } = this.props;
|
const { accountsInfo, contract } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.container } key={ fn.signature }>
|
<div className={ styles.container } key={ fn.signature }>
|
||||||
<InputQuery
|
<InputQuery
|
||||||
|
accountsInfo={ accountsInfo }
|
||||||
className={ styles.method }
|
className={ styles.method }
|
||||||
inputs={ abi.inputs }
|
inputs={ abi.inputs }
|
||||||
outputs={ abi.outputs }
|
outputs={ abi.outputs }
|
||||||
@ -116,34 +117,23 @@ export default class Queries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
let valueToDisplay = null;
|
const { accountsInfo } = this.props;
|
||||||
|
|
||||||
if (api.util.isInstanceOf(value, BigNumber)) {
|
let valueToDisplay = value;
|
||||||
valueToDisplay = value.toFormat(0);
|
|
||||||
} else if (api.util.isArray(value)) {
|
if (api.util.isArray(value)) {
|
||||||
valueToDisplay = api.util.bytesToHex(value);
|
valueToDisplay = api.util.bytesToHex(value);
|
||||||
} else if (typeof value === 'boolean') {
|
} else if (typeof value === 'boolean') {
|
||||||
valueToDisplay = value ? 'true' : 'false';
|
valueToDisplay = value ? 'true' : 'false';
|
||||||
} else {
|
|
||||||
valueToDisplay = value.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'address') {
|
|
||||||
return (
|
return (
|
||||||
<InputAddress
|
<TypedInput
|
||||||
className={ styles.queryValue }
|
accounts={ accountsInfo }
|
||||||
value={ valueToDisplay }
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
className={ styles.queryValue }
|
|
||||||
value={ valueToDisplay }
|
|
||||||
readOnly
|
|
||||||
allowCopy
|
allowCopy
|
||||||
|
isEth={ false }
|
||||||
|
param={ type }
|
||||||
|
readOnly
|
||||||
|
value={ valueToDisplay }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ class Contract extends Component {
|
|||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
|
accountsInfo: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
contracts: PropTypes.object,
|
contracts: PropTypes.object,
|
||||||
isTest: PropTypes.bool,
|
isTest: PropTypes.bool,
|
||||||
@ -115,7 +116,7 @@ class Contract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { balances, contracts, params, isTest } = this.props;
|
const { accountsInfo, balances, contracts, params, isTest } = this.props;
|
||||||
const { allEvents, contract, queryValues, loadingEvents } = this.state;
|
const { allEvents, contract, queryValues, loadingEvents } = this.state;
|
||||||
const account = contracts[params.address];
|
const account = contracts[params.address];
|
||||||
const balance = balances[params.address];
|
const balance = balances[params.address];
|
||||||
@ -138,6 +139,7 @@ class Contract extends Component {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Queries
|
<Queries
|
||||||
|
accountsInfo={ accountsInfo }
|
||||||
contract={ contract }
|
contract={ contract }
|
||||||
values={ queryValues }
|
values={ queryValues }
|
||||||
/>
|
/>
|
||||||
@ -447,13 +449,14 @@ class Contract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts, contracts } = state.personal;
|
const { accounts, accountsInfo, contracts } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
const { isTest } = state.nodeStatus;
|
const { isTest } = state.nodeStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isTest,
|
isTest,
|
||||||
accounts,
|
accounts,
|
||||||
|
accountsInfo,
|
||||||
contracts,
|
contracts,
|
||||||
balances
|
balances
|
||||||
};
|
};
|
||||||
|
@ -20,6 +20,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { omitBy } from 'lodash';
|
||||||
|
|
||||||
import { AddDapps, DappPermissions } from '~/modals';
|
import { AddDapps, DappPermissions } from '~/modals';
|
||||||
import PermissionStore from '~/modals/DappPermissions/store';
|
import PermissionStore from '~/modals/DappPermissions/store';
|
||||||
@ -150,8 +151,15 @@ class Dapps extends Component {
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not show the Wallet Accounts in the Dapps
|
||||||
|
* Permissions Modal. This will come in v1.6, but
|
||||||
|
* for now it would break dApps using Web3...
|
||||||
|
*/
|
||||||
|
const _accounts = omitBy(accounts, (account) => account.wallet);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts
|
accounts: _accounts
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ export default class RequestPending extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
date: PropTypes.instanceOf(Date).isRequired,
|
date: PropTypes.instanceOf(Date).isRequired,
|
||||||
|
focus: PropTypes.bool,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
id: PropTypes.object.isRequired,
|
id: PropTypes.object.isRequired,
|
||||||
isSending: PropTypes.bool.isRequired,
|
isSending: PropTypes.bool.isRequired,
|
||||||
@ -38,6 +39,7 @@ export default class RequestPending extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
focus: false,
|
||||||
isSending: false
|
isSending: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ export default class RequestPending extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, date, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
|
const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
|
||||||
|
|
||||||
if (payload.sign) {
|
if (payload.sign) {
|
||||||
const { sign } = payload;
|
const { sign } = payload;
|
||||||
@ -58,6 +60,7 @@ export default class RequestPending extends Component {
|
|||||||
<SignRequest
|
<SignRequest
|
||||||
address={ sign.address }
|
address={ sign.address }
|
||||||
className={ className }
|
className={ className }
|
||||||
|
focus={ focus }
|
||||||
hash={ sign.hash }
|
hash={ sign.hash }
|
||||||
id={ id }
|
id={ id }
|
||||||
isFinished={ false }
|
isFinished={ false }
|
||||||
@ -75,6 +78,7 @@ export default class RequestPending extends Component {
|
|||||||
<TransactionPending
|
<TransactionPending
|
||||||
className={ className }
|
className={ className }
|
||||||
date={ date }
|
date={ date }
|
||||||
|
focus={ focus }
|
||||||
gasLimit={ gasLimit }
|
gasLimit={ gasLimit }
|
||||||
id={ id }
|
id={ id }
|
||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
|
@ -30,13 +30,19 @@ export default class SignRequest extends Component {
|
|||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
hash: PropTypes.string.isRequired,
|
hash: PropTypes.string.isRequired,
|
||||||
isFinished: PropTypes.bool.isRequired,
|
isFinished: PropTypes.bool.isRequired,
|
||||||
|
isTest: PropTypes.bool.isRequired,
|
||||||
|
store: PropTypes.object.isRequired,
|
||||||
|
|
||||||
|
className: PropTypes.string,
|
||||||
|
focus: PropTypes.bool,
|
||||||
isSending: PropTypes.bool,
|
isSending: PropTypes.bool,
|
||||||
onConfirm: PropTypes.func,
|
onConfirm: PropTypes.func,
|
||||||
onReject: PropTypes.func,
|
onReject: PropTypes.func,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string
|
||||||
className: PropTypes.string,
|
};
|
||||||
isTest: PropTypes.bool.isRequired,
|
|
||||||
store: PropTypes.object.isRequired
|
static defaultProps = {
|
||||||
|
focus: false
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
@ -81,7 +87,7 @@ export default class SignRequest extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderActions () {
|
renderActions () {
|
||||||
const { address, isFinished, status } = this.props;
|
const { address, focus, isFinished, status } = this.props;
|
||||||
|
|
||||||
if (isFinished) {
|
if (isFinished) {
|
||||||
if (status === 'confirmed') {
|
if (status === 'confirmed') {
|
||||||
@ -111,6 +117,7 @@ export default class SignRequest extends Component {
|
|||||||
return (
|
return (
|
||||||
<TransactionPendingForm
|
<TransactionPendingForm
|
||||||
address={ address }
|
address={ address }
|
||||||
|
focus={ focus }
|
||||||
isSending={ this.props.isSending }
|
isSending={ this.props.isSending }
|
||||||
onConfirm={ this.onConfirm }
|
onConfirm={ this.onConfirm }
|
||||||
onReject={ this.onReject }
|
onReject={ this.onReject }
|
||||||
|
@ -35,6 +35,7 @@ export default class TransactionPending extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
date: PropTypes.instanceOf(Date).isRequired,
|
date: PropTypes.instanceOf(Date).isRequired,
|
||||||
|
focus: PropTypes.bool,
|
||||||
gasLimit: PropTypes.object,
|
gasLimit: PropTypes.object,
|
||||||
id: PropTypes.object.isRequired,
|
id: PropTypes.object.isRequired,
|
||||||
isSending: PropTypes.bool.isRequired,
|
isSending: PropTypes.bool.isRequired,
|
||||||
@ -53,6 +54,10 @@ export default class TransactionPending extends Component {
|
|||||||
}).isRequired
|
}).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
focus: false
|
||||||
|
};
|
||||||
|
|
||||||
gasStore = new GasPriceEditor.Store(this.context.api, {
|
gasStore = new GasPriceEditor.Store(this.context.api, {
|
||||||
gas: this.props.transaction.gas.toFixed(),
|
gas: this.props.transaction.gas.toFixed(),
|
||||||
gasLimit: this.props.gasLimit,
|
gasLimit: this.props.gasLimit,
|
||||||
@ -80,7 +85,7 @@ export default class TransactionPending extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTransaction () {
|
renderTransaction () {
|
||||||
const { className, id, isSending, isTest, store, transaction } = this.props;
|
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
||||||
const { totalValue } = this.state;
|
const { totalValue } = this.state;
|
||||||
const { from, value } = transaction;
|
const { from, value } = transaction;
|
||||||
|
|
||||||
@ -100,6 +105,7 @@ export default class TransactionPending extends Component {
|
|||||||
value={ value } />
|
value={ value } />
|
||||||
<TransactionPendingForm
|
<TransactionPendingForm
|
||||||
address={ from }
|
address={ from }
|
||||||
|
focus={ focus }
|
||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
onConfirm={ this.onConfirm }
|
onConfirm={ this.onConfirm }
|
||||||
onReject={ this.onReject } />
|
onReject={ this.onReject } />
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// 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 ReactDOM from 'react-dom';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
@ -26,11 +27,16 @@ import styles from './transactionPendingFormConfirm.css';
|
|||||||
|
|
||||||
class TransactionPendingFormConfirm extends Component {
|
class TransactionPendingFormConfirm extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
account: PropTypes.object.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
isSending: PropTypes.bool.isRequired,
|
isSending: PropTypes.bool.isRequired,
|
||||||
onConfirm: PropTypes.func.isRequired
|
onConfirm: PropTypes.func.isRequired,
|
||||||
}
|
focus: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
focus: false
|
||||||
|
};
|
||||||
|
|
||||||
id = Math.random(); // for tooltip
|
id = Math.random(); // for tooltip
|
||||||
|
|
||||||
@ -40,10 +46,39 @@ class TransactionPendingFormConfirm extends Component {
|
|||||||
walletError: null
|
walletError: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (!this.props.focus && nextProps.focus) {
|
||||||
|
this.focus(nextProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properly focus on the input element when needed.
|
||||||
|
* This might be fixed some day in MaterialUI with
|
||||||
|
* an autoFocus prop.
|
||||||
|
*
|
||||||
|
* @see https://github.com/callemall/material-ui/issues/5632
|
||||||
|
*/
|
||||||
|
focus (props = this.props) {
|
||||||
|
if (props.focus) {
|
||||||
|
const textNode = ReactDOM.findDOMNode(this.refs.input);
|
||||||
|
|
||||||
|
if (!textNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputNode = textNode.querySelector('input');
|
||||||
|
inputNode && inputNode.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, address, isSending } = this.props;
|
const { account, address, isSending } = this.props;
|
||||||
const { password, wallet, walletError } = this.state;
|
const { password, wallet, walletError } = this.state;
|
||||||
const account = accounts[address] || {};
|
|
||||||
const isExternal = !account.uuid;
|
const isExternal = !account.uuid;
|
||||||
|
|
||||||
const passwordHint = account.meta && account.meta.passwordHint
|
const passwordHint = account.meta && account.meta.passwordHint
|
||||||
@ -72,8 +107,10 @@ class TransactionPendingFormConfirm extends Component {
|
|||||||
}
|
}
|
||||||
onChange={ this.onModifyPassword }
|
onChange={ this.onModifyPassword }
|
||||||
onKeyDown={ this.onKeyDown }
|
onKeyDown={ this.onKeyDown }
|
||||||
|
ref='input'
|
||||||
type='password'
|
type='password'
|
||||||
value={ password } />
|
value={ password }
|
||||||
|
/>
|
||||||
<div className={ styles.passwordHint }>
|
<div className={ styles.passwordHint }>
|
||||||
{ passwordHint }
|
{ passwordHint }
|
||||||
</div>
|
</div>
|
||||||
@ -178,11 +215,14 @@ class TransactionPendingFormConfirm extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (initState, initProps) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = initState.personal;
|
||||||
|
const { address } = initProps;
|
||||||
|
|
||||||
return {
|
const account = accounts[address] || {};
|
||||||
accounts
|
|
||||||
|
return () => {
|
||||||
|
return { account };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,12 @@ export default class TransactionPendingForm extends Component {
|
|||||||
isSending: PropTypes.bool.isRequired,
|
isSending: PropTypes.bool.isRequired,
|
||||||
onConfirm: PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
onReject: PropTypes.func.isRequired,
|
onReject: PropTypes.func.isRequired,
|
||||||
className: PropTypes.string
|
className: PropTypes.string,
|
||||||
|
focus: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
focus: false
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -47,7 +52,7 @@ export default class TransactionPendingForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderForm () {
|
renderForm () {
|
||||||
const { address, isSending, onConfirm, onReject } = this.props;
|
const { address, focus, isSending, onConfirm, onReject } = this.props;
|
||||||
|
|
||||||
if (this.state.isRejectOpen) {
|
if (this.state.isRejectOpen) {
|
||||||
return (
|
return (
|
||||||
@ -59,8 +64,10 @@ export default class TransactionPendingForm extends Component {
|
|||||||
return (
|
return (
|
||||||
<TransactionPendingFormConfirm
|
<TransactionPendingFormConfirm
|
||||||
address={ address }
|
address={ address }
|
||||||
|
focus={ focus }
|
||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
onConfirm={ onConfirm } />
|
onConfirm={ onConfirm }
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class Embedded extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPending = (data) => {
|
renderPending = (data, index) => {
|
||||||
const { actions, gasLimit, isTest } = this.props;
|
const { actions, gasLimit, isTest } = this.props;
|
||||||
const { date, id, isSending, payload } = data;
|
const { date, id, isSending, payload } = data;
|
||||||
|
|
||||||
@ -86,6 +86,7 @@ class Embedded extends Component {
|
|||||||
<RequestPending
|
<RequestPending
|
||||||
className={ styles.request }
|
className={ styles.request }
|
||||||
date={ date }
|
date={ date }
|
||||||
|
focus={ index === 0 }
|
||||||
gasLimit={ gasLimit }
|
gasLimit={ gasLimit }
|
||||||
id={ id }
|
id={ id }
|
||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
|
@ -104,7 +104,7 @@ class RequestsPage extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPending = (data) => {
|
renderPending = (data, index) => {
|
||||||
const { actions, gasLimit, isTest } = this.props;
|
const { actions, gasLimit, isTest } = this.props;
|
||||||
const { date, id, isSending, payload } = data;
|
const { date, id, isSending, payload } = data;
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ class RequestsPage extends Component {
|
|||||||
<RequestPending
|
<RequestPending
|
||||||
className={ styles.request }
|
className={ styles.request }
|
||||||
date={ date }
|
date={ date }
|
||||||
|
focus={ index === 0 }
|
||||||
gasLimit={ gasLimit }
|
gasLimit={ gasLimit }
|
||||||
id={ id }
|
id={ id }
|
||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
|
@ -55,14 +55,20 @@ export default class WalletDetails extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ownersList = owners.map((address, idx) => (
|
const ownersList = owners.map((owner, idx) => {
|
||||||
|
const address = typeof owner === 'object'
|
||||||
|
? owner.address
|
||||||
|
: owner;
|
||||||
|
|
||||||
|
return (
|
||||||
<InputAddress
|
<InputAddress
|
||||||
key={ `${idx}_${address}` }
|
key={ `${idx}_${address}` }
|
||||||
value={ address }
|
value={ address }
|
||||||
disabled
|
disabled
|
||||||
text
|
text
|
||||||
/>
|
/>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -57,12 +57,12 @@ export default class WalletTransactions extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const txRows = transactions.map((transaction) => {
|
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
||||||
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TxRow
|
<TxRow
|
||||||
key={ transactionHash }
|
key={ `${transactionHash}_${index}` }
|
||||||
tx={ {
|
tx={ {
|
||||||
hash: transactionHash,
|
hash: transactionHash,
|
||||||
input: data && bytesToHex(data) || '',
|
input: data && bytesToHex(data) || '',
|
||||||
|
@ -64,13 +64,14 @@ class Wallet extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
balance: nullableProptype(PropTypes.object.isRequired),
|
balance: nullableProptype(PropTypes.object.isRequired),
|
||||||
images: PropTypes.object.isRequired,
|
images: PropTypes.object.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
wallets: PropTypes.object.isRequired,
|
owned: PropTypes.bool.isRequired,
|
||||||
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
wallet: PropTypes.object.isRequired,
|
wallet: PropTypes.object.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired
|
walletAccount: nullableProptype(PropTypes.object).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -104,28 +105,26 @@ class Wallet extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { wallets, balance, address } = this.props;
|
const { walletAccount, balance, wallet } = this.props;
|
||||||
|
|
||||||
const wallet = (wallets || {})[address];
|
if (!walletAccount) {
|
||||||
|
|
||||||
if (!wallet) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { owners, require, dailylimit } = this.props.wallet;
|
const { owners, require, dailylimit } = wallet;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.wallet }>
|
<div className={ styles.wallet }>
|
||||||
{ this.renderEditDialog(wallet) }
|
{ this.renderEditDialog(walletAccount) }
|
||||||
{ this.renderSettingsDialog() }
|
{ this.renderSettingsDialog() }
|
||||||
{ this.renderTransferDialog() }
|
{ this.renderTransferDialog() }
|
||||||
{ this.renderDeleteDialog(wallet) }
|
{ this.renderDeleteDialog(walletAccount) }
|
||||||
{ this.renderActionbar() }
|
{ this.renderActionbar() }
|
||||||
<Page>
|
<Page>
|
||||||
<div className={ styles.info }>
|
<div className={ styles.info }>
|
||||||
<Header
|
<Header
|
||||||
className={ styles.header }
|
className={ styles.header }
|
||||||
account={ wallet }
|
account={ walletAccount }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
isContract
|
isContract
|
||||||
>
|
>
|
||||||
@ -209,32 +208,47 @@ class Wallet extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderActionbar () {
|
renderActionbar () {
|
||||||
const { balance } = this.props;
|
const { balance, owned } = this.props;
|
||||||
const showTransferButton = !!(balance && balance.tokens);
|
const showTransferButton = !!(balance && balance.tokens);
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [];
|
||||||
|
|
||||||
|
if (owned) {
|
||||||
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key='transferFunds'
|
key='transferFunds'
|
||||||
icon={ <ContentSend /> }
|
icon={ <ContentSend /> }
|
||||||
label='transfer'
|
label='transfer'
|
||||||
disabled={ !showTransferButton }
|
disabled={ !showTransferButton }
|
||||||
onClick={ this.onTransferClick } />,
|
onClick={ this.onTransferClick } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key='delete'
|
key='delete'
|
||||||
icon={ <ActionDelete /> }
|
icon={ <ActionDelete /> }
|
||||||
label='delete'
|
label='delete'
|
||||||
onClick={ this.showDeleteDialog } />,
|
onClick={ this.showDeleteDialog } />
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key='editmeta'
|
key='editmeta'
|
||||||
icon={ <ContentCreate /> }
|
icon={ <ContentCreate /> }
|
||||||
label='edit'
|
label='edit'
|
||||||
onClick={ this.onEditClick } />,
|
onClick={ this.onEditClick } />
|
||||||
|
);
|
||||||
|
|
||||||
|
if (owned) {
|
||||||
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key='settings'
|
key='settings'
|
||||||
icon={ <SettingsIcon /> }
|
icon={ <SettingsIcon /> }
|
||||||
label='settings'
|
label='settings'
|
||||||
onClick={ this.onSettingsClick } />
|
onClick={ this.onSettingsClick } />
|
||||||
];
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Actionbar
|
<Actionbar
|
||||||
@ -293,12 +307,11 @@ class Wallet extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wallets, balance, images, address } = this.props;
|
const { walletAccount, balance, images } = this.props;
|
||||||
const wallet = wallets[address];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transfer
|
<Transfer
|
||||||
account={ wallet }
|
account={ walletAccount }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
images={ images }
|
images={ images }
|
||||||
onClose={ this.onTransferClose }
|
onClose={ this.onTransferClose }
|
||||||
@ -342,20 +355,27 @@ function mapStateToProps (_, initProps) {
|
|||||||
|
|
||||||
return (state) => {
|
return (state) => {
|
||||||
const { isTest } = state.nodeStatus;
|
const { isTest } = state.nodeStatus;
|
||||||
const { wallets } = state.personal;
|
const { accountsInfo = {}, accounts = {} } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
const { images } = state;
|
const { images } = state;
|
||||||
|
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
||||||
|
|
||||||
|
if (walletAccount) {
|
||||||
|
walletAccount.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
const wallet = state.wallet.wallets[address] || {};
|
const wallet = state.wallet.wallets[address] || {};
|
||||||
const balance = balances[address] || null;
|
const balance = balances[address] || null;
|
||||||
|
const owned = !!accounts[address];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isTest,
|
address,
|
||||||
wallets,
|
|
||||||
balance,
|
balance,
|
||||||
images,
|
images,
|
||||||
address,
|
isTest,
|
||||||
wallet
|
owned,
|
||||||
|
wallet,
|
||||||
|
walletAccount
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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/>.
|
||||||
|
|
||||||
// Run with `webpack --config webpack.libraries.js --progress`
|
// Run with `webpack --config webpack.libraries.js`
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
@ -38,6 +38,13 @@ module.exports = {
|
|||||||
library: '[name].js',
|
library: '[name].js',
|
||||||
libraryTarget: 'umd'
|
libraryTarget: 'umd'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, '../src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
@ -69,6 +69,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, '../src')
|
||||||
|
},
|
||||||
modules: [
|
modules: [
|
||||||
path.resolve('./src'),
|
path.resolve('./src'),
|
||||||
path.join(__dirname, '../node_modules')
|
path.join(__dirname, '../node_modules')
|
||||||
|
@ -24,6 +24,7 @@ const postcssNested = require('postcss-nested');
|
|||||||
const postcssVars = require('postcss-simple-vars');
|
const postcssVars = require('postcss-simple-vars');
|
||||||
const rucksack = require('rucksack-css');
|
const rucksack = require('rucksack-css');
|
||||||
const CircularDependencyPlugin = require('circular-dependency-plugin');
|
const CircularDependencyPlugin = require('circular-dependency-plugin');
|
||||||
|
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||||
|
|
||||||
const ENV = process.env.NODE_ENV || 'development';
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
const isProd = ENV === 'production';
|
const isProd = ENV === 'production';
|
||||||
@ -79,6 +80,10 @@ function getPlugins (_isProd = isProd) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
|
new ProgressBarPlugin({
|
||||||
|
format: '[:msg] [:bar] ' + ':percent' + ' (:elapsed seconds)'
|
||||||
|
}),
|
||||||
|
|
||||||
// NB: HappyPack is not yet working with Webpack 2... (as of Nov. 26)
|
// NB: HappyPack is not yet working with Webpack 2... (as of Nov. 26)
|
||||||
|
|
||||||
// new HappyPack({
|
// new HappyPack({
|
||||||
|
@ -64,6 +64,13 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': path.resolve(__dirname, '../src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
path: path.resolve(__dirname, '../', `${DEST}/`),
|
path: path.resolve(__dirname, '../', `${DEST}/`),
|
||||||
|
@ -27,7 +27,7 @@ use user_defaults::UserDefaults;
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SpecType {
|
pub enum SpecType {
|
||||||
Mainnet,
|
Mainnet,
|
||||||
Testnet,
|
Morden,
|
||||||
Ropsten,
|
Ropsten,
|
||||||
Olympic,
|
Olympic,
|
||||||
Classic,
|
Classic,
|
||||||
@ -49,8 +49,8 @@ impl str::FromStr for SpecType {
|
|||||||
let spec = match s {
|
let spec = match s {
|
||||||
"frontier" | "homestead" | "mainnet" => SpecType::Mainnet,
|
"frontier" | "homestead" | "mainnet" => SpecType::Mainnet,
|
||||||
"frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic,
|
"frontier-dogmatic" | "homestead-dogmatic" | "classic" => SpecType::Classic,
|
||||||
"morden" | "testnet" | "classic-testnet" => SpecType::Testnet,
|
"morden" | "classic-testnet" => SpecType::Morden,
|
||||||
"ropsten" => SpecType::Ropsten,
|
"ropsten" | "testnet" => SpecType::Ropsten,
|
||||||
"olympic" => SpecType::Olympic,
|
"olympic" => SpecType::Olympic,
|
||||||
"expanse" => SpecType::Expanse,
|
"expanse" => SpecType::Expanse,
|
||||||
"dev" => SpecType::Dev,
|
"dev" => SpecType::Dev,
|
||||||
@ -64,7 +64,7 @@ impl SpecType {
|
|||||||
pub fn spec(&self) -> Result<Spec, String> {
|
pub fn spec(&self) -> Result<Spec, String> {
|
||||||
match *self {
|
match *self {
|
||||||
SpecType::Mainnet => Ok(ethereum::new_frontier()),
|
SpecType::Mainnet => Ok(ethereum::new_frontier()),
|
||||||
SpecType::Testnet => Ok(ethereum::new_morden()),
|
SpecType::Morden => Ok(ethereum::new_morden()),
|
||||||
SpecType::Ropsten => Ok(ethereum::new_ropsten()),
|
SpecType::Ropsten => Ok(ethereum::new_ropsten()),
|
||||||
SpecType::Olympic => Ok(ethereum::new_olympic()),
|
SpecType::Olympic => Ok(ethereum::new_olympic()),
|
||||||
SpecType::Classic => Ok(ethereum::new_classic()),
|
SpecType::Classic => Ok(ethereum::new_classic()),
|
||||||
@ -292,12 +292,12 @@ mod tests {
|
|||||||
assert_eq!(SpecType::Mainnet, "frontier".parse().unwrap());
|
assert_eq!(SpecType::Mainnet, "frontier".parse().unwrap());
|
||||||
assert_eq!(SpecType::Mainnet, "homestead".parse().unwrap());
|
assert_eq!(SpecType::Mainnet, "homestead".parse().unwrap());
|
||||||
assert_eq!(SpecType::Mainnet, "mainnet".parse().unwrap());
|
assert_eq!(SpecType::Mainnet, "mainnet".parse().unwrap());
|
||||||
assert_eq!(SpecType::Testnet, "testnet".parse().unwrap());
|
assert_eq!(SpecType::Ropsten, "testnet".parse().unwrap());
|
||||||
assert_eq!(SpecType::Testnet, "morden".parse().unwrap());
|
assert_eq!(SpecType::Morden, "morden".parse().unwrap());
|
||||||
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
|
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
|
||||||
assert_eq!(SpecType::Olympic, "olympic".parse().unwrap());
|
assert_eq!(SpecType::Olympic, "olympic".parse().unwrap());
|
||||||
assert_eq!(SpecType::Classic, "classic".parse().unwrap());
|
assert_eq!(SpecType::Classic, "classic".parse().unwrap());
|
||||||
assert_eq!(SpecType::Testnet, "classic-testnet".parse().unwrap());
|
assert_eq!(SpecType::Morden, "classic-testnet".parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -193,6 +193,7 @@ fn rpc_eth_logs() {
|
|||||||
data: vec![1,2,3],
|
data: vec![1,2,3],
|
||||||
},
|
},
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
transaction_hash: H256::default(),
|
transaction_hash: H256::default(),
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
}, LocalizedLogEntry {
|
}, LocalizedLogEntry {
|
||||||
@ -204,8 +205,9 @@ fn rpc_eth_logs() {
|
|||||||
data: vec![1,2,3],
|
data: vec![1,2,3],
|
||||||
},
|
},
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 1,
|
||||||
transaction_hash: H256::default(),
|
transaction_hash: H256::default(),
|
||||||
log_index: 0,
|
log_index: 1,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
||||||
@ -213,8 +215,8 @@ fn rpc_eth_logs() {
|
|||||||
let request2 = r#"{"jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"limit":1}], "id": 1}"#;
|
let request2 = r#"{"jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"limit":1}], "id": 1}"#;
|
||||||
let request3 = r#"{"jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"limit":0}], "id": 1}"#;
|
let request3 = r#"{"jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"limit":0}], "id": 1}"#;
|
||||||
|
|
||||||
let response1 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"},{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"id":1}"#;
|
let response1 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x0","type":"mined"},{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x1","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x1","type":"mined"}],"id":1}"#;
|
||||||
let response2 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"id":1}"#;
|
let response2 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x1","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x1","type":"mined"}],"id":1}"#;
|
||||||
let response3 = r#"{"jsonrpc":"2.0","result":[],"id":1}"#;
|
let response3 = r#"{"jsonrpc":"2.0","result":[],"id":1}"#;
|
||||||
|
|
||||||
assert_eq!(tester.io.handle_request_sync(request1), Some(response1.to_owned()));
|
assert_eq!(tester.io.handle_request_sync(request1), Some(response1.to_owned()));
|
||||||
@ -235,6 +237,7 @@ fn rpc_logs_filter() {
|
|||||||
data: vec![1,2,3],
|
data: vec![1,2,3],
|
||||||
},
|
},
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
transaction_hash: H256::default(),
|
transaction_hash: H256::default(),
|
||||||
log_index: 0,
|
log_index: 0,
|
||||||
}, LocalizedLogEntry {
|
}, LocalizedLogEntry {
|
||||||
@ -246,8 +249,9 @@ fn rpc_logs_filter() {
|
|||||||
data: vec![1,2,3],
|
data: vec![1,2,3],
|
||||||
},
|
},
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 1,
|
||||||
transaction_hash: H256::default(),
|
transaction_hash: H256::default(),
|
||||||
log_index: 0,
|
log_index: 1,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
// Register filters first
|
// Register filters first
|
||||||
@ -261,8 +265,8 @@ fn rpc_logs_filter() {
|
|||||||
|
|
||||||
let request_changes1 = r#"{"jsonrpc": "2.0", "method": "eth_getFilterChanges", "params": ["0x0"], "id": 1}"#;
|
let request_changes1 = r#"{"jsonrpc": "2.0", "method": "eth_getFilterChanges", "params": ["0x0"], "id": 1}"#;
|
||||||
let request_changes2 = r#"{"jsonrpc": "2.0", "method": "eth_getFilterChanges", "params": ["0x1"], "id": 1}"#;
|
let request_changes2 = r#"{"jsonrpc": "2.0", "method": "eth_getFilterChanges", "params": ["0x1"], "id": 1}"#;
|
||||||
let response1 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"},{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"id":1}"#;
|
let response1 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x0","type":"mined"},{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x1","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x1","type":"mined"}],"id":1}"#;
|
||||||
let response2 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x0","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"id":1}"#;
|
let response2 = r#"{"jsonrpc":"2.0","result":[{"address":"0x0000000000000000000000000000000000000000","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","data":"0x010203","logIndex":"0x1","topics":[],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x1","type":"mined"}],"id":1}"#;
|
||||||
|
|
||||||
assert_eq!(tester.io.handle_request_sync(request_changes1), Some(response1.to_owned()));
|
assert_eq!(tester.io.handle_request_sync(request_changes1), Some(response1.to_owned()));
|
||||||
assert_eq!(tester.io.handle_request_sync(request_changes2), Some(response2.to_owned()));
|
assert_eq!(tester.io.handle_request_sync(request_changes2), Some(response2.to_owned()));
|
||||||
@ -951,6 +955,7 @@ fn rpc_eth_transaction_receipt() {
|
|||||||
block_number: 0x4510c,
|
block_number: 0x4510c,
|
||||||
transaction_hash: H256::new(),
|
transaction_hash: H256::new(),
|
||||||
transaction_index: 0,
|
transaction_index: 0,
|
||||||
|
transaction_log_index: 0,
|
||||||
log_index: 1,
|
log_index: 1,
|
||||||
}],
|
}],
|
||||||
log_bloom: 0.into(),
|
log_bloom: 0.into(),
|
||||||
@ -967,7 +972,7 @@ fn rpc_eth_transaction_receipt() {
|
|||||||
"params": ["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"],
|
"params": ["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"],
|
||||||
"id": 1
|
"id": 1
|
||||||
}"#;
|
}"#;
|
||||||
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","contractAddress":null,"cumulativeGasUsed":"0x20","gasUsed":"0x10","logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","data":"0x","logIndex":"0x1","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","root":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0"},"id":1}"#;
|
let response = r#"{"jsonrpc":"2.0","result":{"blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","contractAddress":null,"cumulativeGasUsed":"0x20","gasUsed":"0x10","logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","data":"0x","logIndex":"0x1","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","transactionLogIndex":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","root":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0"},"id":1}"#;
|
||||||
|
|
||||||
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,12 @@ pub struct Log {
|
|||||||
/// Transaction Index
|
/// Transaction Index
|
||||||
#[serde(rename="transactionIndex")]
|
#[serde(rename="transactionIndex")]
|
||||||
pub transaction_index: Option<U256>,
|
pub transaction_index: Option<U256>,
|
||||||
/// Log Index
|
/// Log Index in Block
|
||||||
#[serde(rename="logIndex")]
|
#[serde(rename="logIndex")]
|
||||||
pub log_index: Option<U256>,
|
pub log_index: Option<U256>,
|
||||||
|
/// Log Index in Transaction
|
||||||
|
#[serde(rename="transactionLogIndex")]
|
||||||
|
pub transaction_log_index: Option<U256>,
|
||||||
/// Log Type
|
/// Log Type
|
||||||
#[serde(rename="type")]
|
#[serde(rename="type")]
|
||||||
pub log_type: String,
|
pub log_type: String,
|
||||||
@ -57,6 +60,7 @@ impl From<LocalizedLogEntry> for Log {
|
|||||||
transaction_hash: Some(e.transaction_hash.into()),
|
transaction_hash: Some(e.transaction_hash.into()),
|
||||||
transaction_index: Some(e.transaction_index.into()),
|
transaction_index: Some(e.transaction_index.into()),
|
||||||
log_index: Some(e.log_index.into()),
|
log_index: Some(e.log_index.into()),
|
||||||
|
transaction_log_index: Some(e.transaction_log_index.into()),
|
||||||
log_type: "mined".to_owned(),
|
log_type: "mined".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +77,7 @@ impl From<LogEntry> for Log {
|
|||||||
transaction_hash: None,
|
transaction_hash: None,
|
||||||
transaction_index: None,
|
transaction_index: None,
|
||||||
log_index: None,
|
log_index: None,
|
||||||
|
transaction_log_index: None,
|
||||||
log_type: "pending".to_owned(),
|
log_type: "pending".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,7 +91,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn log_serialization() {
|
fn log_serialization() {
|
||||||
let s = r#"{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","type":"mined"}"#;
|
let s = r#"{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","transactionLogIndex":"0x1","type":"mined"}"#;
|
||||||
|
|
||||||
let log = Log {
|
let log = Log {
|
||||||
address: H160::from_str("33990122638b9132ca29c723bdf037f1a891a70c").unwrap(),
|
address: H160::from_str("33990122638b9132ca29c723bdf037f1a891a70c").unwrap(),
|
||||||
@ -99,6 +104,7 @@ mod tests {
|
|||||||
block_number: Some(U256::from(0x4510c)),
|
block_number: Some(U256::from(0x4510c)),
|
||||||
transaction_hash: Some(H256::default()),
|
transaction_hash: Some(H256::default()),
|
||||||
transaction_index: Some(U256::default()),
|
transaction_index: Some(U256::default()),
|
||||||
|
transaction_log_index: Some(1.into()),
|
||||||
log_index: Some(U256::from(1)),
|
log_index: Some(U256::from(1)),
|
||||||
log_type: "mined".to_owned(),
|
log_type: "mined".to_owned(),
|
||||||
};
|
};
|
||||||
|
@ -109,7 +109,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn receipt_serialization() {
|
fn receipt_serialization() {
|
||||||
let s = r#"{"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","cumulativeGasUsed":"0x20","gasUsed":"0x10","contractAddress":null,"logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","type":"mined"}],"root":"0x000000000000000000000000000000000000000000000000000000000000000a","logsBloom":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f"}"#;
|
let s = r#"{"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","cumulativeGasUsed":"0x20","gasUsed":"0x10","contractAddress":null,"logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x4510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","logIndex":"0x1","transactionLogIndex":null,"type":"mined"}],"root":"0x000000000000000000000000000000000000000000000000000000000000000a","logsBloom":"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f"}"#;
|
||||||
|
|
||||||
let receipt = Receipt {
|
let receipt = Receipt {
|
||||||
transaction_hash: Some(0.into()),
|
transaction_hash: Some(0.into()),
|
||||||
@ -130,6 +130,7 @@ mod tests {
|
|||||||
block_number: Some(0x4510c.into()),
|
block_number: Some(0x4510c.into()),
|
||||||
transaction_hash: Some(0.into()),
|
transaction_hash: Some(0.into()),
|
||||||
transaction_index: Some(0.into()),
|
transaction_index: Some(0.into()),
|
||||||
|
transaction_log_index: None,
|
||||||
log_index: Some(1.into()),
|
log_index: Some(1.into()),
|
||||||
log_type: "mined".into(),
|
log_type: "mined".into(),
|
||||||
}],
|
}],
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
// 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/>.
|
|
||||||
|
|
||||||
use std::default::Default;
|
|
||||||
use sha3::*;
|
|
||||||
use hash::H256;
|
|
||||||
use bytes::*;
|
|
||||||
use rlp::*;
|
|
||||||
use hashdb::*;
|
|
||||||
|
|
||||||
/// Type of operation for the backing database - either a new node or a node deletion.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Operation {
|
|
||||||
New(H256, DBValue),
|
|
||||||
Delete(H256),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How many insertions and removals were done in an `apply` operation.
|
|
||||||
pub struct Score {
|
|
||||||
/// Number of insertions.
|
|
||||||
pub inserts: usize,
|
|
||||||
/// Number of removals.
|
|
||||||
pub removes: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A journal of operations on the backing database.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Journal (Vec<Operation>);
|
|
||||||
|
|
||||||
impl Default for Journal {
|
|
||||||
fn default() -> Self {
|
|
||||||
Journal::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Journal {
|
|
||||||
/// Create a new, empty, object.
|
|
||||||
pub fn new() -> Journal { Journal(vec![]) }
|
|
||||||
|
|
||||||
/// Given the RLP that encodes a node, append a reference to that node `out` and leave `journal`
|
|
||||||
/// such that the reference is valid, once applied.
|
|
||||||
pub fn new_node(&mut self, rlp: DBValue, out: &mut RlpStream) {
|
|
||||||
if rlp.len() >= 32 {
|
|
||||||
let rlp_sha3 = rlp.sha3();
|
|
||||||
|
|
||||||
trace!("new_node: reference node {:?} => {:?}", rlp_sha3, &*rlp);
|
|
||||||
out.append(&rlp_sha3);
|
|
||||||
self.0.push(Operation::New(rlp_sha3, rlp));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
trace!("new_node: inline node {:?}", &*rlp);
|
|
||||||
out.append_raw(&rlp, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the RLP that encodes a now-unused node, leave `journal` in such a state that it is noted.
|
|
||||||
pub fn delete_node_sha3(&mut self, old_sha3: H256) {
|
|
||||||
trace!("delete_node: {:?}", old_sha3);
|
|
||||||
self.0.push(Operation::Delete(old_sha3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register an RLP-encoded node for deletion (given a slice), if it needs to be deleted.
|
|
||||||
pub fn delete_node(&mut self, old: &[u8]) {
|
|
||||||
let r = Rlp::new(old);
|
|
||||||
if r.is_data() && r.size() == 32 {
|
|
||||||
self.delete_node_sha3(r.as_val());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply this journal to the HashDB `db` and return the number of insertions and removals done.
|
|
||||||
pub fn apply(self, db: &mut HashDB) -> Score {
|
|
||||||
trace!("applying {:?} changes", self.0.len());
|
|
||||||
let mut ret = Score{inserts: 0, removes: 0};
|
|
||||||
for d in self.0 {
|
|
||||||
match d {
|
|
||||||
Operation::Delete(h) => {
|
|
||||||
trace!("TrieDBMut::apply --- {:?}", &h);
|
|
||||||
db.remove(&h);
|
|
||||||
ret.removes += 1;
|
|
||||||
},
|
|
||||||
Operation::New(h, d) => {
|
|
||||||
trace!("TrieDBMut::apply +++ {:?} -> {:?}", &h, d.pretty());
|
|
||||||
db.emplace(h, d);
|
|
||||||
ret.inserts += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,8 +22,6 @@ use hashdb::{HashDB, DBValue};
|
|||||||
|
|
||||||
/// Export the standardmap module.
|
/// Export the standardmap module.
|
||||||
pub mod standardmap;
|
pub mod standardmap;
|
||||||
/// Export the journal module.
|
|
||||||
pub mod journal;
|
|
||||||
/// Export the node module.
|
/// Export the node module.
|
||||||
pub mod node;
|
pub mod node;
|
||||||
/// Export the triedb module.
|
/// Export the triedb module.
|
||||||
|
@ -18,7 +18,6 @@ use elastic_array::ElasticArray36;
|
|||||||
use nibbleslice::*;
|
use nibbleslice::*;
|
||||||
use bytes::*;
|
use bytes::*;
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
use super::journal::*;
|
|
||||||
use hashdb::DBValue;
|
use hashdb::DBValue;
|
||||||
|
|
||||||
/// Partial node key type.
|
/// Partial node key type.
|
||||||
@ -123,44 +122,4 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the node, adding it to `journal` if necessary and return the RLP valid for
|
|
||||||
/// insertion into a parent node.
|
|
||||||
pub fn encoded_and_added(&self, journal: &mut Journal) -> DBValue {
|
|
||||||
let mut stream = RlpStream::new();
|
|
||||||
match *self {
|
|
||||||
Node::Leaf(ref slice, ref value) => {
|
|
||||||
stream.begin_list(2);
|
|
||||||
stream.append(&&**slice);
|
|
||||||
stream.append(&&**value);
|
|
||||||
},
|
|
||||||
Node::Extension(ref slice, ref raw_rlp) => {
|
|
||||||
stream.begin_list(2);
|
|
||||||
stream.append(&&**slice);
|
|
||||||
stream.append_raw(&&**raw_rlp, 1);
|
|
||||||
},
|
|
||||||
Node::Branch(ref nodes, ref value) => {
|
|
||||||
stream.begin_list(17);
|
|
||||||
for i in 0..16 {
|
|
||||||
stream.append_raw(&*nodes[i], 1);
|
|
||||||
}
|
|
||||||
match *value {
|
|
||||||
Some(ref n) => { stream.append(&&**n); },
|
|
||||||
None => { stream.append_empty_data(); },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Node::Empty => {
|
|
||||||
stream.append_empty_data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let node = DBValue::from_slice(stream.as_raw());
|
|
||||||
match node.len() {
|
|
||||||
0 ... 31 => node,
|
|
||||||
_ => {
|
|
||||||
let mut stream = RlpStream::new();
|
|
||||||
journal.new_node(node, &mut stream);
|
|
||||||
DBValue::from_slice(stream.as_raw())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user