Merge branch 'master' into ui-2
This commit is contained in:
commit
368e3d1f51
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1784,7 +1784,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
version = "1.4.0"
|
||||
source = "git+https://github.com/paritytech/js-precompiled.git#69ff5bc9fa4c64a0657308a64a45a95b3d984950"
|
||||
source = "git+https://github.com/paritytech/js-precompiled.git#6597fc70499226546fdcb35e7c09f9347f4f3c07"
|
||||
dependencies = [
|
||||
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
@ -1564,7 +1564,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
"".sha3().into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -39,7 +39,6 @@ use super::signer::EngineSigner;
|
||||
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||
|
||||
/// `AuthorityRound` params.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AuthorityRoundParams {
|
||||
/// Gas limit divisor.
|
||||
pub gas_limit_bound_divisor: U256,
|
||||
@ -52,7 +51,7 @@ pub struct AuthorityRoundParams {
|
||||
/// Starting step,
|
||||
pub start_step: Option<u64>,
|
||||
/// Valid validators.
|
||||
pub validators: ethjson::spec::ValidatorSet,
|
||||
pub validators: Box<ValidatorSet>,
|
||||
/// Chain score validation transition block.
|
||||
pub validate_score_transition: u64,
|
||||
/// Number of first block where EIP-155 rules are validated.
|
||||
@ -66,7 +65,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
||||
AuthorityRoundParams {
|
||||
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
||||
step_duration: Duration::from_secs(p.step_duration.into()),
|
||||
validators: p.validators,
|
||||
validators: new_validator_set(p.validators),
|
||||
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
|
||||
registrar: p.registrar.map_or_else(Address::new, Into::into),
|
||||
start_step: p.start_step.map(Into::into),
|
||||
@ -209,7 +208,7 @@ impl AuthorityRound {
|
||||
proposed: AtomicBool::new(false),
|
||||
client: RwLock::new(None),
|
||||
signer: Default::default(),
|
||||
validators: new_validator_set(our_params.validators),
|
||||
validators: our_params.validators,
|
||||
validate_score_transition: our_params.validate_score_transition,
|
||||
eip155_transition: our_params.eip155_transition,
|
||||
validate_step_transition: our_params.validate_step_transition,
|
||||
@ -382,14 +381,22 @@ impl Engine for AuthorityRound {
|
||||
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||
}
|
||||
|
||||
// Ensure header is from the step after parent.
|
||||
let parent_step = header_step(parent)?;
|
||||
// Ensure header is from the step after parent.
|
||||
if step == parent_step
|
||||
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
||||
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
||||
self.validators.report_malicious(header.author(), header.number(), Default::default());
|
||||
Err(EngineError::DoubleVote(header.author().clone()))?;
|
||||
}
|
||||
// Report skipped primaries.
|
||||
if step > parent_step + 1 {
|
||||
for s in parent_step + 1..step {
|
||||
let skipped_primary = self.step_proposer(&parent.hash(), s);
|
||||
trace!(target: "engine", "Author {} did not build his block on top of the intermediate designated primary {}.", header.author(), skipped_primary);
|
||||
self.validators.report_benign(&skipped_primary, header.number());
|
||||
}
|
||||
}
|
||||
|
||||
let gas_limit_divisor = self.gas_limit_bound_divisor;
|
||||
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor;
|
||||
@ -460,16 +467,18 @@ impl Engine for AuthorityRound {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
use util::*;
|
||||
use header::Header;
|
||||
use error::{Error, BlockError};
|
||||
use ethkey::Secret;
|
||||
use rlp::encode;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
use account_provider::AccountProvider;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
use engines::{Seal, Engine};
|
||||
use engines::validator_set::TestSet;
|
||||
use super::{AuthorityRoundParams, AuthorityRound};
|
||||
|
||||
#[test]
|
||||
fn has_valid_metadata() {
|
||||
@ -513,8 +522,8 @@ mod tests {
|
||||
#[test]
|
||||
fn generates_seal_and_does_not_double_propose() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
|
||||
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
|
||||
let addr1 = tap.insert_account("1".sha3().into(), "1").unwrap();
|
||||
let addr2 = tap.insert_account("2".sha3().into(), "2").unwrap();
|
||||
|
||||
let spec = Spec::new_test_round();
|
||||
let engine = &*spec.engine;
|
||||
@ -545,7 +554,7 @@ mod tests {
|
||||
#[test]
|
||||
fn proposer_switching() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
@ -570,7 +579,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_future_block() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||
@ -596,7 +605,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_step_backwards() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&4usize).to_vec()]);
|
||||
@ -616,4 +625,32 @@ mod tests {
|
||||
header.set_seal(vec![encode(&3usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_skipped() {
|
||||
let last_benign = Arc::new(AtomicUsize::new(0));
|
||||
let params = AuthorityRoundParams {
|
||||
gas_limit_bound_divisor: U256::from_str("400").unwrap(),
|
||||
step_duration: Default::default(),
|
||||
block_reward: Default::default(),
|
||||
registrar: Default::default(),
|
||||
start_step: Some(1),
|
||||
validators: Box::new(TestSet::new(Default::default(), last_benign.clone())),
|
||||
validate_score_transition: 0,
|
||||
validate_step_transition: 0,
|
||||
eip155_transition: 0,
|
||||
};
|
||||
let aura = AuthorityRound::new(Default::default(), params, Default::default()).unwrap();
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&1usize).to_vec()]);
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
let mut header: Header = Header::default();
|
||||
header.set_number(1);
|
||||
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
header.set_seal(vec![encode(&3usize).to_vec()]);
|
||||
|
||||
assert!(aura.verify_block_family(&header, &parent_header, None).is_ok());
|
||||
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 1);
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +229,6 @@ mod tests {
|
||||
use error::{BlockError, Error};
|
||||
use tests::helpers::*;
|
||||
use account_provider::AccountProvider;
|
||||
use ethkey::Secret;
|
||||
use header::Header;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
@ -281,7 +280,7 @@ mod tests {
|
||||
#[test]
|
||||
fn can_generate_seal() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
let addr = tap.insert_account("".sha3().into(), "").unwrap();
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &*spec.engine;
|
||||
@ -299,7 +298,7 @@ mod tests {
|
||||
#[test]
|
||||
fn seals_internally() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
let authority = tap.insert_account("".sha3().into(), "").unwrap();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.seals_internally().unwrap());
|
||||
|
@ -200,7 +200,6 @@ pub fn message_hash(vote_step: VoteStep, block_hash: H256) -> H256 {
|
||||
mod tests {
|
||||
use util::*;
|
||||
use rlp::*;
|
||||
use ethkey::Secret;
|
||||
use account_provider::AccountProvider;
|
||||
use header::Header;
|
||||
use super::super::Step;
|
||||
@ -250,7 +249,7 @@ mod tests {
|
||||
#[test]
|
||||
fn generate_and_verify() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
let addr = tap.insert_account("0".sha3().into(), "0").unwrap();
|
||||
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||
|
||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
||||
|
@ -42,7 +42,7 @@ use evm::Schedule;
|
||||
use state::CleanupMode;
|
||||
use io::IoService;
|
||||
use super::signer::EngineSigner;
|
||||
use super::validator_set::{ValidatorSet, new_validator_set};
|
||||
use super::validator_set::ValidatorSet;
|
||||
use super::transition::TransitionHandler;
|
||||
use super::vote_collector::VoteCollector;
|
||||
use self::message::*;
|
||||
@ -124,7 +124,7 @@ impl Tendermint {
|
||||
proposal: RwLock::new(None),
|
||||
proposal_parent: Default::default(),
|
||||
last_proposed: Default::default(),
|
||||
validators: new_validator_set(our_params.validators),
|
||||
validators: our_params.validators,
|
||||
});
|
||||
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
|
||||
engine.step_service.register_handler(Arc::new(handler))?;
|
||||
@ -659,7 +659,6 @@ mod tests {
|
||||
use block::*;
|
||||
use error::{Error, BlockError};
|
||||
use header::Header;
|
||||
use ethkey::Secret;
|
||||
use client::chain_notify::ChainNotify;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::*;
|
||||
@ -708,7 +707,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
||||
let addr = tap.insert_account(Secret::from_slice(&acc.sha3()).unwrap(), acc).unwrap();
|
||||
let addr = tap.insert_account(acc.sha3().into(), acc).unwrap();
|
||||
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||
addr
|
||||
}
|
||||
|
@ -19,16 +19,16 @@
|
||||
use ethjson;
|
||||
use util::{U256, Uint, Address};
|
||||
use time::Duration;
|
||||
use super::super::validator_set::{ValidatorSet, new_validator_set};
|
||||
use super::super::transition::Timeouts;
|
||||
use super::Step;
|
||||
|
||||
/// `Tendermint` params.
|
||||
#[derive(Debug)]
|
||||
pub struct TendermintParams {
|
||||
/// Gas limit divisor.
|
||||
pub gas_limit_bound_divisor: U256,
|
||||
/// List of validators.
|
||||
pub validators: ethjson::spec::ValidatorSet,
|
||||
pub validators: Box<ValidatorSet>,
|
||||
/// Timeout durations for different steps.
|
||||
pub timeouts: TendermintTimeouts,
|
||||
/// Block reward.
|
||||
@ -82,7 +82,7 @@ impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
||||
let dt = TendermintTimeouts::default();
|
||||
TendermintParams {
|
||||
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
||||
validators: p.validators,
|
||||
validators: new_validator_set(p.validators),
|
||||
timeouts: TendermintTimeouts {
|
||||
propose: p.timeout_propose.map_or(dt.propose, to_duration),
|
||||
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
|
||||
|
@ -116,7 +116,6 @@ impl ValidatorSet for ValidatorContract {
|
||||
mod tests {
|
||||
use util::*;
|
||||
use rlp::encode;
|
||||
use ethkey::Secret;
|
||||
use spec::Spec;
|
||||
use header::Header;
|
||||
use account_provider::AccountProvider;
|
||||
@ -140,7 +139,7 @@ mod tests {
|
||||
#[test]
|
||||
fn reports_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
|
||||
let v1 = tap.insert_account("1".sha3().into(), "").unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone()));
|
||||
client.engine().register_client(Arc::downgrade(&client));
|
||||
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
/// Validator lists.
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod simple_list;
|
||||
mod safe_contract;
|
||||
mod contract;
|
||||
@ -28,6 +30,8 @@ use ethjson::spec::ValidatorSet as ValidatorSpec;
|
||||
use client::Client;
|
||||
use header::{Header, BlockNumber};
|
||||
|
||||
#[cfg(test)]
|
||||
pub use self::test::TestSet;
|
||||
pub use self::simple_list::SimpleList;
|
||||
use self::contract::ValidatorContract;
|
||||
use self::safe_contract::ValidatorSafeContract;
|
||||
|
@ -166,9 +166,9 @@ mod tests {
|
||||
fn uses_current_set() {
|
||||
::env_logger::init().unwrap();
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let s0 = Secret::from_slice(&"0".sha3()).unwrap();
|
||||
let s0: Secret = "0".sha3().into();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
|
||||
let v1 = tap.insert_account("1".sha3().into(), "").unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
|
||||
client.engine().register_client(Arc::downgrade(&client));
|
||||
|
||||
|
@ -294,9 +294,9 @@ mod tests {
|
||||
#[test]
|
||||
fn knows_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
|
||||
let s0: Secret = "1".sha3().into();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap();
|
||||
let v1 = tap.insert_account("0".sha3().into(), "").unwrap();
|
||||
let network_id = Spec::new_validator_safe_contract().network_id();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap));
|
||||
client.engine().register_client(Arc::downgrade(&client));
|
||||
|
88
ethcore/src/engines/validator_set/test.rs
Normal file
88
ethcore/src/engines/validator_set/test.rs
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// Used for Engine testing.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
use util::{Arc, Bytes, H256, Address, HeapSizeOf};
|
||||
|
||||
use engines::Call;
|
||||
use header::{Header, BlockNumber};
|
||||
use super::{ValidatorSet, SimpleList};
|
||||
|
||||
/// Set used for testing with a single validator.
|
||||
pub struct TestSet {
|
||||
validator: SimpleList,
|
||||
last_malicious: Arc<AtomicUsize>,
|
||||
last_benign: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl TestSet {
|
||||
pub fn new(last_malicious: Arc<AtomicUsize>, last_benign: Arc<AtomicUsize>) -> Self {
|
||||
TestSet {
|
||||
validator: SimpleList::new(vec![Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()]),
|
||||
last_malicious: last_malicious,
|
||||
last_benign: last_benign,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for TestSet {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.validator.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorSet for TestSet {
|
||||
fn default_caller(&self, _block_id: ::ids::BlockId) -> Box<Call> {
|
||||
Box::new(|_, _| Err("Test set doesn't require calls.".into()))
|
||||
}
|
||||
|
||||
fn is_epoch_end(&self, _header: &Header, _block: Option<&[u8]>, _receipts: Option<&[::receipt::Receipt]>)
|
||||
-> ::engines::EpochChange
|
||||
{
|
||||
::engines::EpochChange::No
|
||||
}
|
||||
|
||||
fn epoch_proof(&self, _header: &Header, _caller: &Call) -> Result<Vec<u8>, String> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn epoch_set(&self, _header: &Header, _: &[u8]) -> Result<(u64, SimpleList), ::error::Error> {
|
||||
Ok((0, self.validator.clone()))
|
||||
}
|
||||
|
||||
fn contains_with_caller(&self, bh: &H256, address: &Address, _: &Call) -> bool {
|
||||
self.validator.contains(bh, address)
|
||||
}
|
||||
|
||||
fn get_with_caller(&self, bh: &H256, nonce: usize, _: &Call) -> Address {
|
||||
self.validator.get(bh, nonce)
|
||||
}
|
||||
|
||||
fn count_with_caller(&self, _bh: &H256, _: &Call) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn report_malicious(&self, _validator: &Address, block: BlockNumber, _proof: Bytes) {
|
||||
self.last_malicious.store(block as usize, AtomicOrdering::SeqCst)
|
||||
}
|
||||
|
||||
fn report_benign(&self, _validator: &Address, block: BlockNumber) {
|
||||
self.last_benign.store(block as usize, AtomicOrdering::SeqCst)
|
||||
}
|
||||
}
|
@ -962,7 +962,7 @@ mod tests {
|
||||
use types::executed::CallType;
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
"".sha3().into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -27,7 +27,7 @@ use devtools::*;
|
||||
use miner::Miner;
|
||||
use spec::Spec;
|
||||
use views::BlockView;
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use ethkey::KeyPair;
|
||||
use transaction::{PendingTransaction, Transaction, Action, Condition};
|
||||
use miner::MinerService;
|
||||
|
||||
@ -296,7 +296,7 @@ fn change_history_size() {
|
||||
|
||||
#[test]
|
||||
fn does_not_propagate_delayed_transactions() {
|
||||
let key = KeyPair::from_secret(Secret::from_slice(&"test".sha3()).unwrap()).unwrap();
|
||||
let key = KeyPair::from_secret("test".sha3().into()).unwrap();
|
||||
let secret = key.secret();
|
||||
let tx0 = PendingTransaction::new(Transaction {
|
||||
nonce: 0.into(),
|
||||
|
@ -113,7 +113,7 @@ impl HeapSizeOf for Transaction {
|
||||
impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
fn from(t: ethjson::state::Transaction) -> Self {
|
||||
let to: Option<ethjson::hash::Address> = t.to.into();
|
||||
let secret = t.secret.map(|s| Secret::from_slice(&s.0).expect("Valid secret expected."));
|
||||
let secret = t.secret.map(|s| Secret::from_slice(&s.0));
|
||||
let tx = Transaction {
|
||||
nonce: t.nonce.into(),
|
||||
gas_price: t.gas_price.into(),
|
||||
|
@ -192,7 +192,7 @@ pub mod ecdh {
|
||||
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
||||
|
||||
Secret::from_slice(&shared[0..32])
|
||||
Secret::from_unsafe_slice(&shared[0..32])
|
||||
.map_err(|_| Error::Secp(SecpError::InvalidSecretKey))
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ impl Generator for Brain {
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
if let Ok(secret) = Secret::from_slice(&secret) {
|
||||
if let Ok(secret) = Secret::from_unsafe_slice(&secret) {
|
||||
let result = KeyPair::from_secret(secret);
|
||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||
return result;
|
||||
|
@ -99,8 +99,7 @@ impl ExtendedSecret {
|
||||
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label {
|
||||
let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
|
||||
|
||||
let derived_secret = Secret::from_slice(&*derived_key)
|
||||
.expect("Derivation always produced a valid private key; qed");
|
||||
let derived_secret = Secret::from_slice(&*derived_key);
|
||||
|
||||
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
||||
}
|
||||
@ -181,7 +180,7 @@ impl ExtendedKeyPair {
|
||||
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
||||
let (master_key, chain_code) = derivation::seed_pair(seed);
|
||||
Ok(ExtendedKeyPair::with_secret(
|
||||
Secret::from_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
|
||||
Secret::from_unsafe_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
|
||||
chain_code,
|
||||
))
|
||||
}
|
||||
@ -402,7 +401,7 @@ mod tests {
|
||||
|
||||
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
|
||||
let (private_seed, chain_code) = master_chain_basic();
|
||||
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code);
|
||||
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed), chain_code);
|
||||
let derived = f(extended_secret);
|
||||
assert_eq!(**derived.as_raw(), test_private);
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ impl KeyPair {
|
||||
}
|
||||
|
||||
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
||||
Self::from_secret(Secret::from_slice(slice)?)
|
||||
Self::from_secret(Secret::from_unsafe_slice(slice)?)
|
||||
}
|
||||
|
||||
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||
|
@ -33,7 +33,7 @@ impl fmt::Debug for Secret {
|
||||
}
|
||||
|
||||
impl Secret {
|
||||
fn from_slice_unchecked(key: &[u8]) -> Self {
|
||||
pub fn from_slice(key: &[u8]) -> Self {
|
||||
assert_eq!(32, key.len(), "Caller should provide 32-byte length slice");
|
||||
|
||||
let mut h = H256::default();
|
||||
@ -41,11 +41,17 @@ impl Secret {
|
||||
Secret { inner: h }
|
||||
}
|
||||
|
||||
pub fn from_slice(key: &[u8]) -> Result<Self, Error> {
|
||||
/// Imports and validates the key.
|
||||
pub fn from_unsafe_slice(key: &[u8]) -> Result<Self, Error> {
|
||||
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
|
||||
Ok(secret.into())
|
||||
}
|
||||
|
||||
/// Checks validity of this key.
|
||||
pub fn check_validity(&self) -> Result<(), Error> {
|
||||
self.to_secp256k1_secret().map(|_| ())
|
||||
}
|
||||
|
||||
/// Inplace add one secret key to another (scalar + scalar)
|
||||
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
||||
let mut key_secret = self.to_secp256k1_secret()?;
|
||||
@ -121,14 +127,25 @@ impl Secret {
|
||||
impl FromStr for Secret {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let hash = H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
||||
Self::from_slice(&hash)
|
||||
Ok(H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<H256> for Secret {
|
||||
fn from(s: H256) -> Self {
|
||||
Secret::from_slice(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Secret {
|
||||
fn from(s: &'static str) -> Self {
|
||||
s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<key::SecretKey> for Secret {
|
||||
fn from(key: key::SecretKey) -> Self {
|
||||
Self::from_slice_unchecked(&key[0..32])
|
||||
Self::from_slice(&key[0..32])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ impl Crypto {
|
||||
}
|
||||
|
||||
let secret = self.do_decrypt(password, 32)?;
|
||||
Ok(Secret::from_slice(&secret)?)
|
||||
Ok(Secret::from_unsafe_slice(&secret)?)
|
||||
}
|
||||
|
||||
/// Try to decrypt and return result as is
|
||||
|
@ -90,7 +90,7 @@ pub trait VaultKeyDirectory: KeyDirectory {
|
||||
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub use self::disk::RootDiskDirectory;
|
||||
pub use self::disk::{RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
||||
pub use self::memory::MemoryDirectory;
|
||||
pub use self::vault::VaultDiskDirectory;
|
||||
|
||||
|
@ -15,10 +15,29 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use ethkey::Address;
|
||||
use dir::{paths, KeyDirectory, RootDiskDirectory};
|
||||
use dir::{paths, KeyDirectory, RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
||||
use Error;
|
||||
|
||||
/// Import an account from a file.
|
||||
pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result<Address, Error> {
|
||||
let key_manager = DiskKeyFileManager;
|
||||
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
|
||||
let filename = path.file_name().and_then(|n| n.to_str()).map(|f| f.to_owned());
|
||||
let account = fs::File::open(&path)
|
||||
.map_err(Into::into)
|
||||
.and_then(|file| key_manager.read(filename, file))?;
|
||||
|
||||
let address = account.address.clone();
|
||||
if !existing_accounts.contains(&address) {
|
||||
dst.insert(account)?;
|
||||
}
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Import all accounts from one directory to the other.
|
||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
||||
let accounts = src.load()?;
|
||||
|
@ -57,7 +57,7 @@ mod secret_store;
|
||||
pub use self::account::{SafeAccount, Crypto};
|
||||
pub use self::error::Error;
|
||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
||||
pub use self::import::{import_accounts, read_geth_accounts};
|
||||
pub use self::import::{import_account, import_accounts, read_geth_accounts};
|
||||
pub use self::json::OpaqueKeyFile as KeyFile;
|
||||
pub use self::presale::PresaleWallet;
|
||||
pub use self::secret_store::{
|
||||
|
@ -50,7 +50,7 @@ impl PresaleWallet {
|
||||
let len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?;
|
||||
let unpadded = &key[..len];
|
||||
|
||||
let secret = Secret::from_slice(&unpadded.keccak256())?;
|
||||
let secret = Secret::from_unsafe_slice(&unpadded.keccak256())?;
|
||||
if let Ok(kp) = KeyPair::from_secret(secret) {
|
||||
if kp.address() == self.address {
|
||||
return Ok(kp)
|
||||
|
@ -7,6 +7,8 @@
|
||||
"transform-decorators-legacy",
|
||||
"transform-class-properties",
|
||||
"transform-object-rest-spread",
|
||||
"transform-es2015-modules-commonjs",
|
||||
"transform-runtime",
|
||||
"lodash",
|
||||
"recharts"
|
||||
],
|
||||
@ -25,7 +27,6 @@
|
||||
},
|
||||
"test": {
|
||||
"plugins": [
|
||||
"transform-runtime",
|
||||
[ "babel-plugin-webpack-alias", { "config": "webpack/test.js" } ]
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parity.js",
|
||||
"version": "1.7.80",
|
||||
"version": "1.7.82",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@ -77,11 +77,11 @@
|
||||
"babel-plugin-recharts": "1.1.0",
|
||||
"babel-plugin-transform-class-properties": "6.23.0",
|
||||
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "6.23.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "0.3.2",
|
||||
"babel-plugin-transform-runtime": "6.23.0",
|
||||
"babel-plugin-webpack-alias": "2.1.2",
|
||||
"babel-polyfill": "6.23.0",
|
||||
"babel-preset-env": "1.1.9",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-es2016": "6.22.0",
|
||||
@ -183,6 +183,7 @@
|
||||
"promise-worker": "1.1.1",
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2",
|
||||
"react-inspector": "paritytech/react-inspector",
|
||||
"react-intl": "2.1.5",
|
||||
"react-router": "3.0.0",
|
||||
"react-router-redux": "4.0.7",
|
||||
|
@ -23,7 +23,7 @@ import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'
|
||||
import Subscriptions from './subscriptions';
|
||||
import util from './util';
|
||||
import { isFunction } from './util/types';
|
||||
import { LocalAccountsMiddleware } from './local';
|
||||
// import { LocalAccountsMiddleware } from './local';
|
||||
|
||||
export default class Api extends EventEmitter {
|
||||
constructor (transport, allowSubscriptions = true) {
|
||||
@ -54,9 +54,9 @@ export default class Api extends EventEmitter {
|
||||
const middleware = this.parity
|
||||
.nodeKind()
|
||||
.then((nodeKind) => {
|
||||
if (nodeKind.availability === 'public') {
|
||||
return LocalAccountsMiddleware;
|
||||
}
|
||||
// if (nodeKind.availability === 'public') {
|
||||
// return LocalAccountsMiddleware;
|
||||
// }
|
||||
|
||||
return null;
|
||||
})
|
||||
|
59
js/src/dapps/console.js
Normal file
59
js/src/dapps/console.js
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import 'codemirror/addon/dialog/dialog';
|
||||
import 'codemirror/addon/dialog/dialog.css';
|
||||
import 'codemirror/addon/hint/javascript-hint';
|
||||
import 'codemirror/addon/hint/show-hint';
|
||||
import 'codemirror/addon/hint/show-hint.css';
|
||||
import 'codemirror/addon/search/match-highlighter';
|
||||
import 'codemirror/addon/search/search';
|
||||
import 'codemirror/addon/search/searchcursor';
|
||||
import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
// Custom codemirror style
|
||||
import './console/codemirror.css';
|
||||
|
||||
import Application from './console/Application';
|
||||
|
||||
import '../../assets/fonts/Roboto/font.css';
|
||||
import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./console/Application/index.js', () => {
|
||||
require('./console/Application/index.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
65
js/src/dapps/console/Application/application.css
Normal file
65
js/src/dapps/console/Application/application.css
Normal file
@ -0,0 +1,65 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
textarea,
|
||||
input {
|
||||
font-family: dejavu sans mono, monospace;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-family: dejavu sans mono, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.view {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.eval {
|
||||
flex: 0 1 auto;
|
||||
font-family: dejavu sans mono, monospace;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.input {
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.status {
|
||||
flex: 0 0 auto;
|
||||
font-family: dejavu sans mono, monospace;
|
||||
}
|
94
js/src/dapps/console/Application/application.js
Normal file
94
js/src/dapps/console/Application/application.js
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { api } from '../parity';
|
||||
|
||||
import Console from '../Console';
|
||||
import Header from '../Header';
|
||||
import Input from '../Input';
|
||||
import Settings from '../Settings';
|
||||
import Snippets from '../Snippets';
|
||||
import Watches from '../Watches';
|
||||
|
||||
import ApplicationStore from './application.store';
|
||||
import WatchesStore from '../Watches/watches.store';
|
||||
|
||||
import styles from './application.css';
|
||||
|
||||
@observer
|
||||
export default class Application extends Component {
|
||||
application = ApplicationStore.get();
|
||||
watches = WatchesStore.get();
|
||||
|
||||
componentWillMount () {
|
||||
this.watches.add('time', () => new Date());
|
||||
this.watches.add('blockNumber', api.eth.blockNumber, api);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.app }>
|
||||
<div className={ styles.header }>
|
||||
<Header />
|
||||
</div>
|
||||
|
||||
{ this.renderView() }
|
||||
|
||||
<div className={ styles.status }>
|
||||
<Watches />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderView () {
|
||||
const { view } = this.application;
|
||||
|
||||
if (view === 'console') {
|
||||
return (
|
||||
<div className={ styles.view }>
|
||||
<div className={ styles.eval }>
|
||||
<Console />
|
||||
</div>
|
||||
<div className={ styles.input }>
|
||||
<Input />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === 'settings') {
|
||||
return (
|
||||
<div className={ styles.view }>
|
||||
<Settings />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === 'snippets') {
|
||||
return (
|
||||
<div className={ styles.view }>
|
||||
<Snippets />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
42
js/src/dapps/console/Application/application.store.js
Normal file
42
js/src/dapps/console/Application/application.store.js
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class ApplicationStore {
|
||||
@observable view = this.views[0].id;
|
||||
|
||||
views = [
|
||||
{ label: 'Console', id: 'console' },
|
||||
{ label: 'Snippets', id: 'snippets' },
|
||||
{ label: 'Settings', id: 'settings' }
|
||||
];
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new ApplicationStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@action
|
||||
setView (view) {
|
||||
this.view = view;
|
||||
}
|
||||
}
|
17
js/src/dapps/console/Application/index.js
Normal file
17
js/src/dapps/console/Application/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './application';
|
55
js/src/dapps/console/Autocomplete/autocomplete.css
Normal file
55
js/src/dapps/console/Autocomplete/autocomplete.css
Normal file
@ -0,0 +1,55 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
background: #f8f8f8;
|
||||
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
|
||||
font-family: dejavu sans mono, monospace;
|
||||
left: 20px;
|
||||
position: absolute;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.item {
|
||||
background-color: white;
|
||||
padding: 0.25em 0.25em 0.25em 0.35em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&.selected {
|
||||
background-color: rgb(64, 115, 244);
|
||||
|
||||
&,
|
||||
.proto {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
background-color: rgb(230, 236, 255);
|
||||
}
|
||||
|
||||
.proto {
|
||||
color: gray;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
96
js/src/dapps/console/Autocomplete/autocomplete.js
Normal file
96
js/src/dapps/console/Autocomplete/autocomplete.js
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import AutocompleteStore from './autocomplete.store';
|
||||
|
||||
import styles from './autocomplete.css';
|
||||
|
||||
@observer
|
||||
export default class Autocomplete extends Component {
|
||||
autocompleteStore = AutocompleteStore.get();
|
||||
|
||||
render () {
|
||||
if (!this.autocompleteStore.show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.container }
|
||||
style={ this.autocompleteStore.position }
|
||||
>
|
||||
{ this.renderAutocompletes() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAutocompletes () {
|
||||
const { selected, values } = this.autocompleteStore;
|
||||
const displayedProto = {};
|
||||
|
||||
return values.map((autocomplete, index) => {
|
||||
const { name, prototypeName } = autocomplete;
|
||||
const onClick = () => this.handleClick(index);
|
||||
const setRef = (node) => this.setRef(index, node);
|
||||
|
||||
const proto = !displayedProto[prototypeName]
|
||||
? (
|
||||
<span className={ styles.proto }>
|
||||
{ prototypeName }
|
||||
</span>
|
||||
)
|
||||
: null;
|
||||
|
||||
if (!displayedProto[prototypeName]) {
|
||||
displayedProto[prototypeName] = true;
|
||||
}
|
||||
|
||||
const classes = [ styles.item ];
|
||||
|
||||
if (index === selected) {
|
||||
classes.push(styles.selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ index }
|
||||
onClick={ onClick }
|
||||
ref={ setRef }
|
||||
>
|
||||
<span>
|
||||
{ name }
|
||||
</span>
|
||||
{ proto }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
handleClick = (index) => {
|
||||
this.autocompleteStore.select(index);
|
||||
};
|
||||
|
||||
setRef = (index, node) => {
|
||||
const element = ReactDOM.findDOMNode(node);
|
||||
|
||||
this.autocompleteStore.setElement(index, element);
|
||||
};
|
||||
}
|
234
js/src/dapps/console/Autocomplete/autocomplete.store.js
Normal file
234
js/src/dapps/console/Autocomplete/autocomplete.store.js
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { evaluate } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class AutocompleteStore {
|
||||
@observable values = [];
|
||||
@observable position = {};
|
||||
@observable show = false;
|
||||
@observable selected = null;
|
||||
|
||||
elements = {};
|
||||
inputNode = null;
|
||||
lastObject = null;
|
||||
lastObjectPropertyNames = [];
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new AutocompleteStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
get hasSelected () {
|
||||
return this.selected !== null;
|
||||
}
|
||||
|
||||
clearCache () {
|
||||
this.lastObject = null;
|
||||
this.lastObjectPropertyNames = null;
|
||||
}
|
||||
|
||||
@action
|
||||
focus (offset = 1) {
|
||||
if (this.values.length === 0) {
|
||||
this.selected = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.selected = this.selected === null
|
||||
? (
|
||||
offset === 1
|
||||
? 0
|
||||
: this.values.length - 1
|
||||
)
|
||||
: (this.values.length + this.selected + offset) % (this.values.length);
|
||||
|
||||
if (this.isVisible(this.selected)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = this.elements[this.selected];
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.scrollIntoView(offset === -1);
|
||||
}
|
||||
|
||||
focusOnInput () {
|
||||
if (!this.inputNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.inputNode.focus();
|
||||
}
|
||||
|
||||
@action
|
||||
hide () {
|
||||
this.show = false;
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
isVisible (index) {
|
||||
const element = this.elements[index];
|
||||
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const eBoundings = element.getBoundingClientRect();
|
||||
const pBoundings = element.parentElement.getBoundingClientRect();
|
||||
|
||||
if (eBoundings.top < pBoundings.top || eBoundings.bottom > pBoundings.bottom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
select (inputStore, _index = this.selected) {
|
||||
const index = _index === null
|
||||
? 0
|
||||
: _index;
|
||||
|
||||
if (!this.values[index]) {
|
||||
console.warn(`autocomplete::select has been called on AutocompleteStore with wrong value ${index}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { name } = this.values[index];
|
||||
const { input } = inputStore;
|
||||
const objects = input.split('.');
|
||||
|
||||
objects[objects.length - 1] = name;
|
||||
const nextInput = objects.join('.');
|
||||
|
||||
this.hide();
|
||||
this.focusOnInput();
|
||||
return inputStore.updateInput(nextInput, false);
|
||||
}
|
||||
|
||||
setElement (index, element) {
|
||||
this.elements[index] = element;
|
||||
}
|
||||
|
||||
setInputNode (node) {
|
||||
this.inputNode = node;
|
||||
}
|
||||
|
||||
@action
|
||||
setPosition () {
|
||||
if (!this.inputNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputBoundings = this.inputNode.getBoundingClientRect();
|
||||
const bodyBoundings = document.body.getBoundingClientRect();
|
||||
|
||||
// display on bottom of input
|
||||
if (inputBoundings.top < bodyBoundings.height / 2) {
|
||||
const nextPosition = {
|
||||
top: 20
|
||||
};
|
||||
|
||||
this.position = nextPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
// display on top of input
|
||||
const nextPosition = {
|
||||
bottom: inputBoundings.height
|
||||
};
|
||||
|
||||
this.position = nextPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
@action
|
||||
setValues (values) {
|
||||
this.values = values;
|
||||
this.selected = null;
|
||||
const show = values.length > 0;
|
||||
|
||||
// Reveal autocomplete
|
||||
if (!this.show && show) {
|
||||
this.setPosition();
|
||||
}
|
||||
|
||||
this.show = show;
|
||||
}
|
||||
|
||||
update (input) {
|
||||
if (input.length === 0) {
|
||||
return this.setValues([]);
|
||||
}
|
||||
|
||||
const objects = input.split('.');
|
||||
const suffix = objects.pop().toLowerCase();
|
||||
const prefix = objects.join('.');
|
||||
const object = prefix.length > 0
|
||||
? prefix
|
||||
: 'window';
|
||||
|
||||
if (object !== this.lastObject) {
|
||||
const evalResult = evaluate(object);
|
||||
|
||||
if (evalResult.error) {
|
||||
this.lastObjectProperties = [];
|
||||
} else {
|
||||
this.lastObjectProperties = getAllProperties(evalResult.result);
|
||||
}
|
||||
|
||||
this.lastObject = object;
|
||||
}
|
||||
|
||||
const autocompletes = this.lastObjectProperties.filter((property) => {
|
||||
return property.name.toLowerCase().includes(suffix);
|
||||
});
|
||||
|
||||
return this.setValues(autocompletes);
|
||||
}
|
||||
}
|
||||
|
||||
function getAllProperties (object) {
|
||||
const propertyNames = {};
|
||||
|
||||
while (object) {
|
||||
const prototypeName = object && object.constructor && object.constructor.name || '';
|
||||
|
||||
Object.getOwnPropertyNames(object)
|
||||
.sort()
|
||||
.forEach((name) => {
|
||||
if (Object.prototype.hasOwnProperty.call(propertyNames, name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
propertyNames[name] = { name, prototypeName };
|
||||
});
|
||||
|
||||
object = Object.getPrototypeOf(object);
|
||||
}
|
||||
|
||||
return Object.values(propertyNames);
|
||||
}
|
17
js/src/dapps/console/Autocomplete/index.js
Normal file
17
js/src/dapps/console/Autocomplete/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './autocomplete';
|
58
js/src/dapps/console/Console/console.css
Normal file
58
js/src/dapps/console/Console/console.css
Normal file
@ -0,0 +1,58 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.result {
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
font-family: dejavu sans mono, monospace;
|
||||
padding: 0.35em 0.25em;
|
||||
|
||||
&.error {
|
||||
background-color: hsl(0, 100%, 97%);
|
||||
|
||||
.text {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
&.warn {
|
||||
background-color: hsl(50, 100%, 95%);
|
||||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
font-weight: bold !important;
|
||||
font-size: 8pt;
|
||||
padding: 0 0.5em 0 0.25em;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: gray;
|
||||
padding: 0 1em 0 0.5em;
|
||||
}
|
||||
|
||||
.token {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.text .token:not(:first-child) {
|
||||
margin-left: 0.5em;
|
||||
}
|
118
js/src/dapps/console/Console/console.js
Normal file
118
js/src/dapps/console/Console/console.js
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ObjectInspector } from 'react-inspector';
|
||||
|
||||
import ConsoleStore from './console.store';
|
||||
import SettingsStore from '../Settings/settings.store';
|
||||
|
||||
import styles from './console.css';
|
||||
|
||||
const ICONS = {
|
||||
debug: ' ',
|
||||
error: '✖',
|
||||
info: 'ℹ',
|
||||
input: '>',
|
||||
log: ' ',
|
||||
result: '<',
|
||||
warn: '⚠'
|
||||
};
|
||||
|
||||
@observer
|
||||
export default class Console extends Component {
|
||||
consoleStore = ConsoleStore.get();
|
||||
settingsStore = SettingsStore.get();
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div ref={ this.setRef }>
|
||||
{ this.renderResults() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderResults () {
|
||||
const { logs } = this.consoleStore;
|
||||
|
||||
return logs.map((data, index) => {
|
||||
const { type, timestamp } = data;
|
||||
const values = this.consoleStore.logValues[index];
|
||||
const classes = [ styles.result, styles[type] ];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ index }
|
||||
>
|
||||
<span
|
||||
className={ styles.type }
|
||||
dangerouslySetInnerHTML={ { __html: ICONS[type] || '' } }
|
||||
/>
|
||||
{ this.renderTimestamp(timestamp) }
|
||||
<span className={ styles.text }>
|
||||
{
|
||||
values.map((value, valueIndex) => (
|
||||
<span
|
||||
className={ styles.token }
|
||||
key={ valueIndex }
|
||||
>
|
||||
{ this.toString(value) }
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderTimestamp (timestamp) {
|
||||
const { displayTimestamps } = this.settingsStore;
|
||||
|
||||
if (!displayTimestamps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={ styles.time }>
|
||||
{ new Date(timestamp).toISOString().slice(11, 23) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
setRef = (node) => {
|
||||
const element = ReactDOM.findDOMNode(node);
|
||||
|
||||
this.consoleStore.setNode(element);
|
||||
};
|
||||
|
||||
toString (value) {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof Error) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return (
|
||||
<ObjectInspector data={ value } />
|
||||
);
|
||||
}
|
||||
}
|
126
js/src/dapps/console/Console/console.store.js
Normal file
126
js/src/dapps/console/Console/console.store.js
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import AutocompleteStore from '../Autocomplete/autocomplete.store';
|
||||
import { evaluate } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class ConsoleStore {
|
||||
@observable logs = [];
|
||||
|
||||
autocompleteStore = AutocompleteStore.get();
|
||||
logValues = [];
|
||||
node = null;
|
||||
|
||||
constructor () {
|
||||
this.attachConsole();
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new ConsoleStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
attachConsole () {
|
||||
['debug', 'error', 'info', 'log', 'warn'].forEach((level) => {
|
||||
const old = window.console[level].bind(window.console);
|
||||
|
||||
window.console[level] = (...args) => {
|
||||
old(...args);
|
||||
this.log({ type: level, values: args });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
clear () {
|
||||
this.logs = [];
|
||||
this.logValues = [];
|
||||
}
|
||||
|
||||
evaluate (input) {
|
||||
this.log({ type: 'input', value: input });
|
||||
|
||||
setTimeout(() => {
|
||||
const { result, error } = evaluate(input);
|
||||
let value = error || result;
|
||||
const type = error
|
||||
? 'error'
|
||||
: 'result';
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = `"${value}"`;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object' && typeof value.then === 'function') {
|
||||
return value
|
||||
.then((result) => {
|
||||
this.log({ type: 'result', value: result });
|
||||
})
|
||||
.catch((error) => {
|
||||
this.log({ type: 'error', value: error });
|
||||
});
|
||||
}
|
||||
|
||||
this.log({ type, value });
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
log ({ type, value, values }) {
|
||||
this.logs.push({
|
||||
type,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
if (values) {
|
||||
this.logValues.push(values);
|
||||
} else {
|
||||
this.logValues.push([ value ]);
|
||||
}
|
||||
|
||||
this.autocompleteStore.setPosition();
|
||||
this.scroll();
|
||||
}
|
||||
|
||||
setNode (node) {
|
||||
this.node = node;
|
||||
this.scroll();
|
||||
}
|
||||
|
||||
scroll () {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.node.children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll to the last child
|
||||
this.node
|
||||
.children[this.node.children.length - 1]
|
||||
.scrollIntoView(false);
|
||||
}, 50);
|
||||
}
|
||||
}
|
17
js/src/dapps/console/Console/index.js
Normal file
17
js/src/dapps/console/Console/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './console';
|
51
js/src/dapps/console/Header/header.css
Normal file
51
js/src/dapps/console/Header/header.css
Normal file
@ -0,0 +1,51 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
background-color: #f3f3f3;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 12px;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
line-height: 15px;
|
||||
margin-top: 2px;
|
||||
padding: 2px 6px 2px 4px;
|
||||
|
||||
&:hover,
|
||||
&.active:hover {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
65
js/src/dapps/console/Header/header.js
Normal file
65
js/src/dapps/console/Header/header.js
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import ApplicationStore from '../Application/application.store';
|
||||
|
||||
import styles from './header.css';
|
||||
|
||||
@observer
|
||||
export default class Header extends Component {
|
||||
application = ApplicationStore.get();
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.tabs }>
|
||||
{ this.renderTabs() }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabs () {
|
||||
const { view } = this.application;
|
||||
|
||||
return this.application.views.map((tab) => {
|
||||
const { label, id } = tab;
|
||||
const classes = [ styles.tab ];
|
||||
const onClick = () => this.handleClickTab(id);
|
||||
|
||||
if (id === view) {
|
||||
classes.push(styles.active);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ id }
|
||||
onClick={ onClick }
|
||||
>
|
||||
{ label }
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
handleClickTab = (id) => {
|
||||
this.application.setView(id);
|
||||
};
|
||||
}
|
17
js/src/dapps/console/Header/index.js
Normal file
17
js/src/dapps/console/Header/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './header';
|
17
js/src/dapps/console/Input/index.js
Normal file
17
js/src/dapps/console/Input/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './input';
|
46
js/src/dapps/console/Input/input.css
Normal file
46
js/src/dapps/console/Input/input.css
Normal file
@ -0,0 +1,46 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.type {
|
||||
color: #59f;
|
||||
font-weight: bold !important;
|
||||
font-size: 11px;
|
||||
padding: 0 0.5em 0 0.25em;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: black;
|
||||
height: 100%;
|
||||
font-size: 11px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-top: 1px solid lightgray;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 0.25em;
|
||||
position: relative;
|
||||
}
|
145
js/src/dapps/console/Input/input.js
Normal file
145
js/src/dapps/console/Input/input.js
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import keycode from 'keycode';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import Autocomplete from '../Autocomplete';
|
||||
|
||||
import AutocompleteStore from '../Autocomplete/autocomplete.store';
|
||||
import ConsoleStore from '../Console/console.store';
|
||||
import InputStore from './input.store';
|
||||
import SettingsStore from '../Settings/settings.store';
|
||||
|
||||
import styles from './input.css';
|
||||
|
||||
@observer
|
||||
export default class Input extends Component {
|
||||
autocompleteStore = AutocompleteStore.get();
|
||||
consoleStore = ConsoleStore.get();
|
||||
inputStore = InputStore.get();
|
||||
settingsStore = SettingsStore.get();
|
||||
|
||||
render () {
|
||||
const { input } = this.inputStore;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<Autocomplete />
|
||||
<span className={ styles.type }>></span>
|
||||
<div className={ styles.inputContainer }>
|
||||
<textarea
|
||||
autoFocus
|
||||
className={ styles.input }
|
||||
onChange={ this.handleChange }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
ref={ this.setRef }
|
||||
rows={ input.split('\n').length }
|
||||
type='text'
|
||||
value={ input }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
this.inputStore.updateInput(value);
|
||||
};
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
const { executeOnEnter } = this.settingsStore;
|
||||
const { input } = this.inputStore;
|
||||
const codeName = keycode(event);
|
||||
const multilines = input.split('\n').length > 1;
|
||||
|
||||
// Clear console with CTRL+L
|
||||
if (codeName === 'l' && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return this.consoleStore.clear();
|
||||
}
|
||||
|
||||
if (codeName === 'esc') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return this.autocompleteStore.hide();
|
||||
}
|
||||
|
||||
if (codeName === 'enter') {
|
||||
if (event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not execute on enter: execute on
|
||||
// enter + CTRL
|
||||
if (!executeOnEnter && !event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.autocompleteStore.hasSelected) {
|
||||
return this.autocompleteStore.select(this.inputStore);
|
||||
}
|
||||
|
||||
if (input.length > 0) {
|
||||
return this.inputStore.execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (codeName === 'up' && !multilines) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.autocompleteStore.show) {
|
||||
return this.autocompleteStore.focus(-1);
|
||||
}
|
||||
|
||||
return this.inputStore.selectHistory(-1);
|
||||
}
|
||||
|
||||
if (codeName === 'down' && !multilines) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.autocompleteStore.show) {
|
||||
return this.autocompleteStore.focus(1);
|
||||
}
|
||||
|
||||
return this.inputStore.selectHistory(1);
|
||||
}
|
||||
|
||||
if (codeName === 'left' && this.autocompleteStore.show) {
|
||||
return this.autocompleteStore.hide();
|
||||
}
|
||||
|
||||
if (codeName === 'right' && this.autocompleteStore.show) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return this.autocompleteStore.select(this.inputStore);
|
||||
}
|
||||
};
|
||||
|
||||
setRef = (node) => {
|
||||
this.inputStore.setInputNode(ReactDOM.findDOMNode(node));
|
||||
};
|
||||
}
|
124
js/src/dapps/console/Input/input.store.js
Normal file
124
js/src/dapps/console/Input/input.store.js
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
import AutocompleteStore from '../Autocomplete/autocomplete.store';
|
||||
import ConsoleStore from '../Console/console.store';
|
||||
|
||||
const LS_HISTORY_KEY = '_console::history';
|
||||
const MAX_HISTORY_LINES = 5;
|
||||
|
||||
let instance;
|
||||
|
||||
export default class InputStore {
|
||||
@observable input = '';
|
||||
|
||||
autocompleteStore = AutocompleteStore.get();
|
||||
consoleStore = ConsoleStore.get();
|
||||
history = [];
|
||||
historyOffset = null;
|
||||
inputNode = null;
|
||||
lastInput = '';
|
||||
|
||||
constructor () {
|
||||
this.loadHistory();
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new InputStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
setInputNode (node) {
|
||||
this.inputNode = node;
|
||||
this.autocompleteStore.setInputNode(node);
|
||||
}
|
||||
|
||||
@action
|
||||
updateInput (nextValue = '', updateAutocomplete = true) {
|
||||
this.input = nextValue;
|
||||
const multilines = nextValue.split('\n').length > 1;
|
||||
|
||||
if (updateAutocomplete && !multilines) {
|
||||
this.autocompleteStore.update(nextValue);
|
||||
}
|
||||
}
|
||||
|
||||
selectHistory (_offset) {
|
||||
// No history
|
||||
if (this.history.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.historyOffset === null) {
|
||||
// Can't go down if no history selected
|
||||
if (_offset === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.historyOffset = this.history.length - 1;
|
||||
this.lastInput = this.input;
|
||||
return this.updateInput(this.history[this.historyOffset], false);
|
||||
}
|
||||
|
||||
if (_offset === 1 && this.historyOffset === this.history.length - 1) {
|
||||
this.historyOffset = null;
|
||||
return this.updateInput(this.lastInput);
|
||||
}
|
||||
|
||||
this.historyOffset = Math.max(0, this.historyOffset + _offset);
|
||||
const nextInput = this.history[this.historyOffset];
|
||||
|
||||
this.updateInput(nextInput, false);
|
||||
}
|
||||
|
||||
execute () {
|
||||
const { input } = this;
|
||||
|
||||
this.pushToHistory(input);
|
||||
this.consoleStore.evaluate(input);
|
||||
this.updateInput('');
|
||||
this.historyOffset = null;
|
||||
this.autocompleteStore.clearCache();
|
||||
}
|
||||
|
||||
pushToHistory (input) {
|
||||
// Don't stack twice the same input in
|
||||
// history
|
||||
if (this.history[this.history.length - 1] !== input) {
|
||||
this.history.push(input);
|
||||
}
|
||||
|
||||
this.saveHistory();
|
||||
}
|
||||
|
||||
loadHistory () {
|
||||
this.history = store.get(LS_HISTORY_KEY) || [];
|
||||
}
|
||||
|
||||
saveHistory () {
|
||||
if (this.history.length > MAX_HISTORY_LINES) {
|
||||
this.history = this.history.slice(-1 * MAX_HISTORY_LINES);
|
||||
}
|
||||
|
||||
store.set(LS_HISTORY_KEY, this.history.slice());
|
||||
}
|
||||
}
|
17
js/src/dapps/console/Settings/index.js
Normal file
17
js/src/dapps/console/Settings/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './settings';
|
32
js/src/dapps/console/Settings/settings.css
Normal file
32
js/src/dapps/console/Settings/settings.css
Normal file
@ -0,0 +1,32 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.option {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 50%;
|
||||
flex-direction: row;
|
||||
margin: 0.5em 0;
|
||||
}
|
70
js/src/dapps/console/Settings/settings.js
Normal file
70
js/src/dapps/console/Settings/settings.js
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import SettingsStore from './settings.store';
|
||||
|
||||
import styles from './settings.css';
|
||||
|
||||
@observer
|
||||
export default class Settings extends Component {
|
||||
settingsStore = SettingsStore.get();
|
||||
|
||||
render () {
|
||||
const { displayTimestamps, executeOnEnter } = this.settingsStore;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.option }>
|
||||
<input
|
||||
checked={ executeOnEnter }
|
||||
id='executeOnEnter'
|
||||
onChange={ this.handleExecuteOnEnterChange }
|
||||
type='checkbox'
|
||||
/>
|
||||
<label htmlFor='executeOnEnter'>
|
||||
Execute on <code>Enter</code>
|
||||
</label>
|
||||
</div>
|
||||
<div className={ styles.option }>
|
||||
<input
|
||||
checked={ displayTimestamps }
|
||||
id='displayTimestamps'
|
||||
onChange={ this.handleDisplayTimestampsChange }
|
||||
type='checkbox'
|
||||
/>
|
||||
<label htmlFor='displayTimestamps'>
|
||||
Show timestamps
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleDisplayTimestampsChange = (event) => {
|
||||
const { checked } = event.target;
|
||||
|
||||
this.settingsStore.setDisplayTimestamps(checked);
|
||||
};
|
||||
|
||||
handleExecuteOnEnterChange = (event) => {
|
||||
const { checked } = event.target;
|
||||
|
||||
this.settingsStore.setExecuteOnEnter(checked);
|
||||
};
|
||||
}
|
71
js/src/dapps/console/Settings/settings.store.js
Normal file
71
js/src/dapps/console/Settings/settings.store.js
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
const LS_SETTINGS_KEY = '_console::settings';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class SettingsStore {
|
||||
@observable displayTimestamps = true;
|
||||
@observable executeOnEnter = true;
|
||||
|
||||
constructor () {
|
||||
this.load();
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new SettingsStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
load () {
|
||||
const settings = store.get(LS_SETTINGS_KEY) || {};
|
||||
const { executeOnEnter, displayTimestamps } = settings;
|
||||
|
||||
if (executeOnEnter !== undefined) {
|
||||
this.setExecuteOnEnter(executeOnEnter);
|
||||
}
|
||||
|
||||
if (displayTimestamps !== undefined) {
|
||||
this.setDisplayTimestamps(displayTimestamps);
|
||||
}
|
||||
}
|
||||
|
||||
save () {
|
||||
const { executeOnEnter, displayTimestamps } = this;
|
||||
const settings = { executeOnEnter, displayTimestamps };
|
||||
|
||||
store.set(LS_SETTINGS_KEY, settings);
|
||||
}
|
||||
|
||||
@action
|
||||
setDisplayTimestamps (value) {
|
||||
this.displayTimestamps = value;
|
||||
this.save();
|
||||
}
|
||||
|
||||
@action
|
||||
setExecuteOnEnter (value) {
|
||||
this.executeOnEnter = value;
|
||||
this.save();
|
||||
}
|
||||
}
|
17
js/src/dapps/console/Snippets/index.js
Normal file
17
js/src/dapps/console/Snippets/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './snippets';
|
122
js/src/dapps/console/Snippets/snippets.css
Normal file
122
js/src/dapps/console/Snippets/snippets.css
Normal file
@ -0,0 +1,122 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-right: 1px solid lightgray;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.add {
|
||||
align-items: center;
|
||||
background-color: #fcfcfc;
|
||||
border-bottom: 1px solid lightgray;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
padding: 0.5em 1em;
|
||||
|
||||
.plus {
|
||||
font-size: 15px;
|
||||
font-weight: bold !important;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.code {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.console {
|
||||
border-top: 1px solid lightgray;
|
||||
max-height: 200px;
|
||||
flex: 0 0 0;
|
||||
|
||||
> * {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:global(.CodeMirror) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.file {
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
padding: 0.5em 0.5em 0.5em 1em;
|
||||
|
||||
&.selected {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(230, 236, 255);
|
||||
}
|
||||
|
||||
.pristine {
|
||||
font-size: 20px;
|
||||
margin-right: 3px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.remove {
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
font-size: 14px;
|
||||
margin-left: -0.25em;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
background-color: white;
|
||||
border: solid 1px #d8d8d8;
|
||||
margin-right: 0.5em;
|
||||
padding: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
border: none;
|
||||
font: 11px Arial;
|
||||
width: 100%;
|
||||
}
|
221
js/src/dapps/console/Snippets/snippets.js
Normal file
221
js/src/dapps/console/Snippets/snippets.js
Normal file
@ -0,0 +1,221 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import keycode from 'keycode';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import CodeMirror from 'react-codemirror';
|
||||
import EventListener from 'react-event-listener';
|
||||
|
||||
import Console from '../Console';
|
||||
import SnippetsStore from './snippets.store';
|
||||
|
||||
import styles from './snippets.css';
|
||||
|
||||
@observer
|
||||
export default class Snippets extends Component {
|
||||
snippetsStore = SnippetsStore.get();
|
||||
|
||||
render () {
|
||||
const { code } = this.snippetsStore;
|
||||
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<EventListener
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
target='window'
|
||||
/>
|
||||
<div className={ styles.panel }>
|
||||
<div
|
||||
className={ styles.add }
|
||||
onClick={ this.handleAddFile }
|
||||
>
|
||||
<span className={ styles.plus }>+</span>
|
||||
<span>New Snippet</span>
|
||||
</div>
|
||||
<div className={ styles.list }>
|
||||
{ this.renderFiles() }
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.code }>
|
||||
<CodeMirror
|
||||
ref={ this.setRef }
|
||||
onChange={ this.handleChange }
|
||||
options={ {
|
||||
autofocus: true,
|
||||
extraKeys: {
|
||||
'Ctrl-Space': 'autocomplete'
|
||||
},
|
||||
keyMap: 'sublime',
|
||||
highlightSelectionMatches: {
|
||||
delay: 0,
|
||||
showToken: false
|
||||
},
|
||||
lineNumbers: true,
|
||||
mode: 'javascript'
|
||||
} }
|
||||
value={ code }
|
||||
/>
|
||||
<div className={ styles.console }>
|
||||
<Console />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderFiles () {
|
||||
const { files } = this.snippetsStore;
|
||||
|
||||
return files
|
||||
.values()
|
||||
.sort((fa, fb) => fa.name.localeCompare(fb.name))
|
||||
.map((file) => this.renderFile(file));
|
||||
}
|
||||
|
||||
renderFile (file) {
|
||||
const { nextName, renaming, selected } = this.snippetsStore;
|
||||
const { id, name } = file;
|
||||
const classes = [ styles.file ];
|
||||
|
||||
if (renaming === id) {
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ id }
|
||||
>
|
||||
<EventListener
|
||||
onClick={ this.handleSaveName }
|
||||
target='window'
|
||||
/>
|
||||
<div className={ styles.inputContainer }>
|
||||
<input
|
||||
autoFocus
|
||||
className={ styles.input }
|
||||
onClick={ this.stopPropagation }
|
||||
onChange={ this.handleNameChange }
|
||||
onKeyDown={ this.handleRenameKeyDown }
|
||||
type='text'
|
||||
value={ nextName }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const onClick = () => this.handleSelectFile(id);
|
||||
const onDoubleClick = () => this.handleRenameFile(id);
|
||||
const onRemove = (event) => this.handleRemove(id, event);
|
||||
|
||||
if (selected === id) {
|
||||
classes.push(styles.selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ id }
|
||||
onClick={ onClick }
|
||||
onDoubleClick={ onDoubleClick }
|
||||
>
|
||||
<span
|
||||
className={ styles.remove }
|
||||
onClick={ onRemove }
|
||||
title={ `Remove ${name}` }
|
||||
>
|
||||
✖
|
||||
</span>
|
||||
<span className={ styles.pristine }>
|
||||
{
|
||||
file.isPristine
|
||||
? null
|
||||
: '*'
|
||||
}
|
||||
</span>
|
||||
<span>
|
||||
{ name }
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleAddFile = () => {
|
||||
this.snippetsStore.create();
|
||||
};
|
||||
|
||||
handleSaveName = (event) => {
|
||||
this.snippetsStore.saveName();
|
||||
return event;
|
||||
};
|
||||
|
||||
handleChange = (value) => {
|
||||
this.snippetsStore.edit(value);
|
||||
};
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
if (codeName === 's' && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return this.snippetsStore.save();
|
||||
}
|
||||
};
|
||||
|
||||
handleNameChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
this.snippetsStore.updateName(value);
|
||||
};
|
||||
|
||||
handleRemove = (id, event) => {
|
||||
this.snippetsStore.remove(id);
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
handleRenameFile = (id) => {
|
||||
this.snippetsStore.startRename(id);
|
||||
};
|
||||
|
||||
handleRenameKeyDown = (event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
if (codeName === 'enter') {
|
||||
return this.snippetsStore.saveName();
|
||||
}
|
||||
|
||||
if (codeName === 'esc') {
|
||||
return this.snippetsStore.cancelRename();
|
||||
}
|
||||
};
|
||||
|
||||
handleSelectFile = (id) => {
|
||||
this.snippetsStore.select(id);
|
||||
};
|
||||
|
||||
setRef = (node) => {
|
||||
const codeMirror = node
|
||||
? node.getCodeMirror()
|
||||
: null;
|
||||
|
||||
this.snippetsStore.setCodeMirror(codeMirror);
|
||||
};
|
||||
|
||||
stopPropagation = (event) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
}
|
249
js/src/dapps/console/Snippets/snippets.store.js
Normal file
249
js/src/dapps/console/Snippets/snippets.store.js
Normal file
@ -0,0 +1,249 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import keycode from 'keycode';
|
||||
import { action, computed, map, observable, transaction } from 'mobx';
|
||||
import store from 'store';
|
||||
|
||||
import { evaluate } from '../utils';
|
||||
|
||||
const LS_SNIPPETS_KEY = '_console::snippets';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class SnippetsStore {
|
||||
@observable files = map();
|
||||
@observable nextName = null;
|
||||
@observable renaming = null;
|
||||
@observable selected = null;
|
||||
|
||||
codeMirror = null;
|
||||
|
||||
constructor () {
|
||||
this.load();
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new SnippetsStore();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@computed
|
||||
get code () {
|
||||
if (!this.selected) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.files.get(this.selected).content;
|
||||
}
|
||||
|
||||
@action
|
||||
cancelRename () {
|
||||
if (!this.renaming || !this.nextName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renaming = null;
|
||||
this.nextName = null;
|
||||
}
|
||||
|
||||
clearCodeHistory () {
|
||||
if (this.codeMirror) {
|
||||
this.codeMirror.doc.clearHistory();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
create () {
|
||||
const id = this.getNewId();
|
||||
const file = {
|
||||
content: '',
|
||||
isPristine: false,
|
||||
name: `Snippet #${id}`,
|
||||
id
|
||||
};
|
||||
|
||||
transaction(() => {
|
||||
this.files.set(id, file);
|
||||
this.select(id);
|
||||
});
|
||||
}
|
||||
|
||||
edit (value) {
|
||||
if (!this.selected) {
|
||||
this.create();
|
||||
}
|
||||
|
||||
const file = this.files.get(this.selected);
|
||||
|
||||
file.content = value;
|
||||
this.updateFile(file);
|
||||
}
|
||||
|
||||
evaluate () {
|
||||
const code = this.code;
|
||||
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { result, error } = evaluate(code);
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
console.log(result);
|
||||
}
|
||||
}
|
||||
|
||||
getFromStorage () {
|
||||
return store.get(LS_SNIPPETS_KEY) || [];
|
||||
}
|
||||
|
||||
getNewId () {
|
||||
if (this.files.size === 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const ids = this.files.values().map((file) => file.id);
|
||||
|
||||
return Math.max(...ids) + 1;
|
||||
}
|
||||
|
||||
load () {
|
||||
const files = this.getFromStorage();
|
||||
|
||||
transaction(() => {
|
||||
files.forEach((file) => {
|
||||
this.files.set(file.id, file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
remove (id) {
|
||||
transaction(() => {
|
||||
if (id === this.selected) {
|
||||
this.selected = null;
|
||||
}
|
||||
|
||||
this.files.delete(id);
|
||||
|
||||
const files = this.getFromStorage()
|
||||
.filter((f) => f.id !== id);
|
||||
|
||||
return store.set(LS_SNIPPETS_KEY, files);
|
||||
});
|
||||
}
|
||||
|
||||
save (_file) {
|
||||
let file;
|
||||
|
||||
if (!_file) {
|
||||
if (!this.selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = this.files.get(this.selected);
|
||||
} else {
|
||||
file = _file;
|
||||
}
|
||||
|
||||
file.savedContent = file.content;
|
||||
this.updateFile(file);
|
||||
this.saveToStorage(file);
|
||||
}
|
||||
|
||||
saveName () {
|
||||
if (!this.renaming || !this.nextName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.files.get(this.renaming);
|
||||
|
||||
file.name = this.nextName;
|
||||
|
||||
this.save(file);
|
||||
this.cancelRename();
|
||||
}
|
||||
|
||||
saveToStorage (file) {
|
||||
const files = this.getFromStorage();
|
||||
const index = files.findIndex((f) => file.id === f.id);
|
||||
|
||||
if (index === -1) {
|
||||
files.push(file);
|
||||
} else {
|
||||
files[index] = file;
|
||||
}
|
||||
|
||||
return store.set(LS_SNIPPETS_KEY, files);
|
||||
}
|
||||
|
||||
@action
|
||||
select (id) {
|
||||
this.selected = id;
|
||||
|
||||
// Wait for the file content to be loaded
|
||||
setTimeout(() => {
|
||||
this.clearCodeHistory();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
setCodeMirror (codeMirror) {
|
||||
this.codeMirror = codeMirror;
|
||||
|
||||
if (!codeMirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.codeMirror
|
||||
.on('keydown', (_, event) => {
|
||||
const codeName = keycode(event);
|
||||
|
||||
if (codeName === 'enter' && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return this.evaluate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
startRename (id) {
|
||||
const file = this.files.get(id);
|
||||
|
||||
transaction(() => {
|
||||
this.renaming = id;
|
||||
this.nextName = file.name;
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
updateFile (file) {
|
||||
file.isPristine = (file.content === file.savedContent);
|
||||
|
||||
this.files.set(file.id, file);
|
||||
}
|
||||
|
||||
@action
|
||||
updateName (value) {
|
||||
this.nextName = value;
|
||||
}
|
||||
}
|
17
js/src/dapps/console/Watches/index.js
Normal file
17
js/src/dapps/console/Watches/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './watches';
|
107
js/src/dapps/console/Watches/watches.css
Normal file
107
js/src/dapps/console/Watches/watches.css
Normal file
@ -0,0 +1,107 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
background: #eee;
|
||||
border-top: 1px solid lightgray;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.25em;
|
||||
font-size: 8pt;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.watch {
|
||||
align-items: flex-end;
|
||||
border-left: 1px solid #ccc;
|
||||
display: flex;
|
||||
padding: 0 1em;
|
||||
|
||||
&.error .result {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.result {
|
||||
color: #00f;
|
||||
font-weight: bold;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
color: green;
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
font-weight: bold !important;
|
||||
padding: 0 1em;
|
||||
|
||||
&:hover {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgb(230, 236, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.addContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.addForm {
|
||||
bottom: 100%;
|
||||
display: flex;
|
||||
padding: 0.5em 0;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
|
||||
.inputContainer {
|
||||
border: solid 1px #d8d8d8;
|
||||
margin-right: 0.5em;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.input {
|
||||
border: none;
|
||||
font: 12px Arial;
|
||||
|
||||
&.big {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
color: black;
|
||||
font: 12px Arial;
|
||||
padding: 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.remove {
|
||||
cursor: default;
|
||||
display: inline-flex;
|
||||
font-size: 14px;
|
||||
margin-right: 0.75em;
|
||||
|
||||
* {
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
}
|
186
js/src/dapps/console/Watches/watches.js
Normal file
186
js/src/dapps/console/Watches/watches.js
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import WatchesStore from './watches.store';
|
||||
|
||||
import styles from './watches.css';
|
||||
|
||||
@observer
|
||||
export default class Watches extends Component {
|
||||
watchesStore = WatchesStore.get();
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
{ this.renderAddWatch() }
|
||||
{ this.renderWatches() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddForm () {
|
||||
const { showAdd } = this.watchesStore;
|
||||
|
||||
if (!showAdd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.addForm }>
|
||||
<div className={ styles.inputContainer }>
|
||||
<input
|
||||
className={ styles.input }
|
||||
onChange={ this.handleAddNameChange }
|
||||
placeholder='Name'
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.inputContainer }>
|
||||
<input
|
||||
className={ [ styles.input, styles.big ].join(' ') }
|
||||
onChange={ this.handleAddFunctionChange }
|
||||
placeholder='Function'
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.inputContainer }>
|
||||
<input
|
||||
className={ styles.input }
|
||||
onChange={ this.handleAddContextChange }
|
||||
placeholder='Context'
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={ styles.button }
|
||||
onClick={ this.handleAddWatch }
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddWatch () {
|
||||
const { showAdd } = this.watchesStore;
|
||||
const classes = [ styles.add ];
|
||||
|
||||
if (showAdd) {
|
||||
classes.push(styles.selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.addContainer }>
|
||||
{ this.renderAddForm() }
|
||||
<span
|
||||
className={ classes.join(' ') }
|
||||
onClick={ this.handleToggleAdd }
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderWatches () {
|
||||
const { names } = this.watchesStore;
|
||||
|
||||
return names.map((name) => {
|
||||
const { result, error } = this.watchesStore.get(name);
|
||||
const classes = [ styles.watch ];
|
||||
const resultStr = error
|
||||
? error.toString()
|
||||
: this.toString(result);
|
||||
|
||||
const onClick = () => this.handleRemoveWatch(name);
|
||||
|
||||
if (error) {
|
||||
classes.push(styles.error);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
key={ name }
|
||||
>
|
||||
<span
|
||||
className={ styles.remove }
|
||||
onClick={ onClick }
|
||||
title={ `Remove "${name}" watch` }
|
||||
>
|
||||
<span>✖</span>
|
||||
</span>
|
||||
|
||||
<span>{ name }</span>
|
||||
<span className={ styles.result }>{ resultStr.toString() }</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
handleAddFunctionChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
this.watchesStore.updateAddFunction(value);
|
||||
};
|
||||
|
||||
handleAddContextChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
this.watchesStore.updateAddContext(value);
|
||||
};
|
||||
|
||||
handleAddNameChange = (event) => {
|
||||
const { value } = event.target;
|
||||
|
||||
this.watchesStore.updateAddName(value);
|
||||
};
|
||||
|
||||
handleAddWatch = () => {
|
||||
this.watchesStore.addWatch();
|
||||
};
|
||||
|
||||
handleRemoveWatch = (name) => {
|
||||
this.watchesStore.remove(name);
|
||||
};
|
||||
|
||||
handleToggleAdd = () => {
|
||||
this.watchesStore.toggleAdd();
|
||||
};
|
||||
|
||||
toString (result) {
|
||||
if (result === undefined) {
|
||||
return 'undefined';
|
||||
}
|
||||
|
||||
if (result === null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if (typeof result.toFormat === 'function') {
|
||||
return result.toFormat();
|
||||
}
|
||||
|
||||
if (typeof result.toString === 'function') {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
148
js/src/dapps/console/Watches/watches.store.js
Normal file
148
js/src/dapps/console/Watches/watches.store.js
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, map, observable } from 'mobx';
|
||||
|
||||
import { api } from '../parity';
|
||||
import { evaluate } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
export default class WatchesStore {
|
||||
@observable showAdd = false;
|
||||
@observable watches = map();
|
||||
|
||||
watchesFunctions = {};
|
||||
|
||||
constructor () {
|
||||
api.subscribe('eth_blockNumber', () => {
|
||||
this.refreshWatches();
|
||||
});
|
||||
}
|
||||
|
||||
static get () {
|
||||
if (!instance) {
|
||||
instance = new WatchesStore();
|
||||
|
||||
window.watch = instance.add.bind(instance);
|
||||
window.unwatch = instance.remove.bind(instance);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@action
|
||||
add (name, func, context) {
|
||||
if (!func || (typeof func !== 'function' && typeof func.then !== 'function')) {
|
||||
return console.error(Error(`cannot watch ${name} ; not a Function/Promise given`));
|
||||
}
|
||||
|
||||
this.watchesFunctions[name] = { func, context };
|
||||
this.watches.set(name, {});
|
||||
this.refreshWatches();
|
||||
}
|
||||
|
||||
addWatch () {
|
||||
this.toggleAdd();
|
||||
|
||||
const { addContext, addFunction, addName } = this;
|
||||
|
||||
const evaluatedFunction = evaluate(addFunction);
|
||||
const evaluatedContext = addContext
|
||||
? evaluate(addContext)
|
||||
: {};
|
||||
|
||||
this.add(addName, evaluatedFunction.result, evaluatedContext.result);
|
||||
}
|
||||
|
||||
get (name) {
|
||||
return this.watches.get(name);
|
||||
}
|
||||
|
||||
get names () {
|
||||
return this.watches.keys();
|
||||
}
|
||||
|
||||
refreshWatches () {
|
||||
const names = this.watches.keys();
|
||||
const promises = names
|
||||
.map((name) => {
|
||||
const { context, func } = this.watchesFunctions[name];
|
||||
let result;
|
||||
|
||||
try {
|
||||
if (typeof func === 'function') {
|
||||
result = func.apply(context || this);
|
||||
} else {
|
||||
result = func;
|
||||
}
|
||||
|
||||
return Promise.resolve(result);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
})
|
||||
.map((promise, index) => {
|
||||
const name = names[index];
|
||||
|
||||
return promise
|
||||
.then((result) => {
|
||||
this.updateWatch(name, result);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.updateWatch(name, error, true);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
@action
|
||||
remove (name) {
|
||||
this.watches.delete(name);
|
||||
delete this.watchesFunctions[name];
|
||||
}
|
||||
|
||||
@action
|
||||
toggleAdd () {
|
||||
this.showAdd = !this.showAdd;
|
||||
}
|
||||
|
||||
updateAddContext (value) {
|
||||
this.addContext = value;
|
||||
}
|
||||
|
||||
updateAddFunction (value) {
|
||||
this.addFunction = value;
|
||||
}
|
||||
|
||||
updateAddName (value) {
|
||||
this.addName = value;
|
||||
}
|
||||
|
||||
@action
|
||||
updateWatch (name, result, isError = false) {
|
||||
const next = {};
|
||||
|
||||
if (isError) {
|
||||
next.error = result;
|
||||
} else {
|
||||
next.result = result;
|
||||
}
|
||||
|
||||
this.watches.set(name, { ...next });
|
||||
}
|
||||
}
|
36
js/src/dapps/console/codemirror.css
Normal file
36
js/src/dapps/console/codemirror.css
Normal file
@ -0,0 +1,36 @@
|
||||
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
:global {
|
||||
.cm-matchhighlight {
|
||||
background-color: rgba(255, 255, 0, 0.4);
|
||||
}
|
||||
|
||||
.CodeMirror-hints {
|
||||
font-family: dejavu sans mono, monospace;
|
||||
font-size: 11px;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
border-radius: 0;
|
||||
cursor: default;
|
||||
padding: 0.25em 0.25em 0.25em 0.35em;
|
||||
}
|
||||
}
|
335
js/src/dapps/console/parity.js
Normal file
335
js/src/dapps/console/parity.js
Normal file
File diff suppressed because one or more lines are too long
36
js/src/dapps/console/utils.js
Normal file
36
js/src/dapps/console/utils.js
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable no-eval */
|
||||
|
||||
export function evaluate (input) {
|
||||
try {
|
||||
const result = eval.apply(window, [ `${input}` ]);
|
||||
|
||||
return { result };
|
||||
} catch (err) {
|
||||
try {
|
||||
const result = eval.apply(window, [ `(function () {
|
||||
var x = ${input};
|
||||
return x;
|
||||
})()` ]);
|
||||
|
||||
return { result };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
}
|
0
js/src/dapps/static/.gitkeep
Normal file
0
js/src/dapps/static/.gitkeep
Normal file
@ -1,221 +0,0 @@
|
||||
#full-screen {
|
||||
}
|
||||
|
||||
textarea:focus, input:focus{
|
||||
outline: none;
|
||||
}
|
||||
|
||||
* { padding: 0; margin: 0; }
|
||||
|
||||
html, body, #full-screen {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#full-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#history-wrap {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#history {
|
||||
flex: 1 1 auto;
|
||||
|
||||
display: flex;
|
||||
min-height: min-content;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.entry, #input {
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type {
|
||||
color: #ccc;
|
||||
font-weight: bold;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 11pt;
|
||||
padding: 0 0.5em 0 0.25em;
|
||||
}
|
||||
|
||||
.command .type {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.result .type {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
#input .type {
|
||||
color: #59f;
|
||||
}
|
||||
|
||||
.addwatch {
|
||||
background-color: #cfc;
|
||||
border-top: 1px solid #6e6;
|
||||
}
|
||||
.addwatch .type {
|
||||
color: #040;
|
||||
}
|
||||
.addwatch .text {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
.log.logLevel .type {
|
||||
color: blue;
|
||||
}
|
||||
.log.debugLevel .text {
|
||||
color: blue;
|
||||
}
|
||||
.log.infoLevel .type {
|
||||
color: #00c;
|
||||
}
|
||||
.log.warnLevel {
|
||||
background-color: #fff8dd;
|
||||
border-top: 1px solid #fe8;
|
||||
border-bottom: 1px solid #fe8;
|
||||
}
|
||||
.log.warnLevel .text {
|
||||
color: #440;
|
||||
}
|
||||
.log.warnLevel .type {
|
||||
color: #880;
|
||||
}
|
||||
.log.errorLevel, .error {
|
||||
background-color: #fee;
|
||||
border-top: 1px solid #fbb;
|
||||
border-bottom: 1px solid #fbb;
|
||||
}
|
||||
.log.errorLevel .text, .error .text {
|
||||
color: #c00;
|
||||
}
|
||||
.log.errorLevel .type, .error .type {
|
||||
color: #800;
|
||||
}
|
||||
|
||||
|
||||
span.text {
|
||||
color: black;
|
||||
}
|
||||
.text {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: black;
|
||||
font-weight: 1000;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 11pt;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
div.error {
|
||||
background-color: #fee;
|
||||
border-top: 1px solid #fbb;
|
||||
color: red;
|
||||
}
|
||||
div.command {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
div#input {
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#status {
|
||||
background: #eee;
|
||||
padding: 0.25em;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
font-size: 8pt;
|
||||
color: #888;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#status {
|
||||
display: flex;
|
||||
}
|
||||
.watch {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
.watch:not(:first-child) {
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
.expr {
|
||||
color: #888;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.res {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.stringType {
|
||||
color: #c00;
|
||||
}
|
||||
.numberType {
|
||||
color: #00f;
|
||||
}
|
||||
.eObject {
|
||||
color: #00f;
|
||||
}
|
||||
.objectType {
|
||||
font-style: italic;
|
||||
}
|
||||
.fieldType {
|
||||
color: #808;
|
||||
}
|
||||
.undefinedType {
|
||||
color: #888;
|
||||
}
|
||||
.functionType {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#autocomplete-anchor {
|
||||
position: relative;
|
||||
}
|
||||
#autocomplete {
|
||||
position: absolute;
|
||||
left: 1.5em;
|
||||
bottom: 0;
|
||||
background: #f8f8f8;
|
||||
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
|
||||
max-height: 20em;
|
||||
overflow: scroll;
|
||||
}
|
||||
#autocomplete div {
|
||||
font-size: small;
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
padding: 0.1em 1em 0.1em 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.ac-already {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ac-new {
|
||||
color: #666;
|
||||
}
|
||||
.ac-selected {
|
||||
background-color: #ccddff;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JS Console dapp</title>
|
||||
<link href="console.css" rel='stylesheet' type='text/css'>
|
||||
<script src="/web3.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="full-screen">
|
||||
<div id="eval"></div>
|
||||
<div id="history-wrap"><div id="history"></div></div>
|
||||
<div id="autocomplete-anchor"><div id="autocomplete"></div></div>
|
||||
<div id="input"><span class="type">></span><input type="text" class="text" id="command" list="input-datalist"></div>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
<script src="console.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
@ -22,7 +22,7 @@
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300 !important;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
@ -39,7 +39,7 @@
|
||||
}
|
||||
|
||||
:root * {
|
||||
font-weight: 300 !important;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
:root :global(#container) > div {
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill/dist/polyfill.js';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill/dist/polyfill.js';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill/dist/polyfill.js';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import es6Promise from 'es6-promise';
|
||||
|
@ -8,6 +8,17 @@
|
||||
"visible": false,
|
||||
"noselect": false
|
||||
},
|
||||
{
|
||||
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
|
||||
"url": "console",
|
||||
"name": "Parity/Web3 console",
|
||||
"description": "A Javascript development console complete with web3 and parity objects",
|
||||
"version": "0.3",
|
||||
"author": "Gav Wood <gavin@parity.io>",
|
||||
"position": "top-right",
|
||||
"visible": true,
|
||||
"secure": true
|
||||
},
|
||||
{
|
||||
"id": "0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f",
|
||||
"url": "tokendeploy",
|
||||
@ -85,17 +96,5 @@
|
||||
"visible": true,
|
||||
"skipBuild": true,
|
||||
"skipHistory": true
|
||||
},
|
||||
{
|
||||
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
|
||||
"url": "console",
|
||||
"name": "Parity/Web3 console",
|
||||
"description": "A Javascript development console complete with web3 and parity objects",
|
||||
"version": "0.3",
|
||||
"author": "Gav Wood <gavin@parity.io>",
|
||||
"position": "top-right",
|
||||
"visible": true,
|
||||
"secure": true,
|
||||
"skipBuild": true
|
||||
}
|
||||
]
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import es6Promise from 'es6-promise';
|
||||
|
@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import 'babel-polyfill';
|
||||
import 'whatwg-fetch';
|
||||
|
||||
import es6Promise from 'es6-promise';
|
||||
|
@ -56,9 +56,6 @@ module.exports = {
|
||||
'node-fetch': 'node-fetch'
|
||||
},
|
||||
module: {
|
||||
noParse: [
|
||||
/babel-polyfill/
|
||||
],
|
||||
rules: [
|
||||
rulesParity,
|
||||
rulesEs6,
|
||||
|
@ -23,7 +23,6 @@ const ENV = process.env.NODE_ENV || 'development';
|
||||
const DEST = process.env.BUILD_DEST || '.build';
|
||||
|
||||
let modules = [
|
||||
'babel-polyfill',
|
||||
'bignumber.js',
|
||||
'blockies',
|
||||
'brace',
|
||||
|
@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use ethcore::ethstore::{EthStore, SecretStore, import_accounts, read_geth_accounts};
|
||||
use ethcore::ethstore::{EthStore, SecretStore, import_account, import_accounts, read_geth_accounts};
|
||||
use ethcore::ethstore::dir::RootDiskDirectory;
|
||||
use ethcore::ethstore::SecretVaultRef;
|
||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
@ -51,10 +51,10 @@ pub struct ImportAccounts {
|
||||
pub spec: SpecType,
|
||||
}
|
||||
|
||||
/// Parameters for geth accounts' import
|
||||
/// Parameters for geth accounts' import
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ImportFromGethAccounts {
|
||||
/// import mainnet (false) or testnet (true) accounts
|
||||
/// import mainnet (false) or testnet (true) accounts
|
||||
pub testnet: bool,
|
||||
/// directory to import accounts to
|
||||
pub to: String,
|
||||
@ -80,7 +80,7 @@ fn keys_dir(path: String, spec: SpecType) -> Result<RootDiskDirectory, String> {
|
||||
fn secret_store(dir: Box<RootDiskDirectory>, iterations: Option<u32>) -> Result<EthStore, String> {
|
||||
match iterations {
|
||||
Some(i) => EthStore::open_with_iterations(dir, i),
|
||||
_ => EthStore::open(dir)
|
||||
_ => EthStore::open(dir)
|
||||
}.map_err(|e| format!("Could not open keys store: {}", e))
|
||||
}
|
||||
|
||||
@ -94,16 +94,16 @@ fn new(n: NewAccount) -> Result<String, String> {
|
||||
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let new_account = acc_provider.new_account(&password).map_err(|e| format!("Could not create new account: {}", e))?;
|
||||
Ok(format!("{:?}", new_account))
|
||||
Ok(format!("0x{:?}", new_account))
|
||||
}
|
||||
|
||||
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
||||
let secret_store = Box::new(secret_store(dir, None)?);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let accounts = acc_provider.accounts();
|
||||
let accounts = acc_provider.accounts().map_err(|e| format!("{}", e))?;
|
||||
let result = accounts.into_iter()
|
||||
.map(|a| format!("{:?}", a))
|
||||
.map(|a| format!("0x{:?}", a))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
@ -113,10 +113,18 @@ fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||
fn import(i: ImportAccounts) -> Result<String, String> {
|
||||
let to = keys_dir(i.to, i.spec)?;
|
||||
let mut imported = 0;
|
||||
|
||||
for path in &i.from {
|
||||
let from = RootDiskDirectory::at(path);
|
||||
imported += import_accounts(&from, &to).map_err(|_| "Importing accounts failed.")?.len();
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_dir() {
|
||||
let from = RootDiskDirectory::at(&path);
|
||||
imported += import_accounts(&from, &to).map_err(|e| format!("Importing accounts from {:?} failed: {}", path, e))?.len();
|
||||
} else if path.is_file() {
|
||||
import_account(&path, &to).map_err(|e| format!("Importing account from {:?} failed: {}", path, e))?;
|
||||
imported += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(format!("{} account(s) imported", imported))
|
||||
}
|
||||
|
||||
|
@ -695,7 +695,7 @@ impl Configuration {
|
||||
ret.listen_address = listen.map(|l| format!("{}", l));
|
||||
ret.public_address = public.map(|p| format!("{}", p));
|
||||
ret.use_secret = match self.args.flag_node_key.as_ref()
|
||||
.map(|s| s.parse::<Secret>().or_else(|_| Secret::from_slice(&s.sha3())).map_err(|e| format!("Invalid key: {:?}", e))
|
||||
.map(|s| s.parse::<Secret>().or_else(|_| Secret::from_unsafe_slice(&s.sha3())).map_err(|e| format!("Invalid key: {:?}", e))
|
||||
) {
|
||||
None => None,
|
||||
Some(Ok(key)) => Some(key),
|
||||
|
@ -30,6 +30,7 @@ use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
||||
use ethcore::snapshot;
|
||||
use ethcore::verification::queue::VerifierSettings;
|
||||
use ethcore::ethstore::ethkey;
|
||||
use light::Cache as LightDataCache;
|
||||
use ethsync::SyncConfig;
|
||||
use informant::Informant;
|
||||
@ -820,9 +821,31 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str,
|
||||
}
|
||||
}
|
||||
|
||||
// Add development account if running dev chain:
|
||||
if let SpecType::Dev = *spec {
|
||||
insert_dev_account(&account_provider);
|
||||
}
|
||||
|
||||
Ok(account_provider)
|
||||
}
|
||||
|
||||
fn insert_dev_account(account_provider: &AccountProvider) {
|
||||
let secret: ethkey::Secret = "4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7".into();
|
||||
let dev_account = ethkey::KeyPair::from_secret(secret.clone()).expect("Valid secret produces valid key;qed");
|
||||
if let Ok(false) = account_provider.has_account(dev_account.address()) {
|
||||
match account_provider.insert_account(secret, "") {
|
||||
Err(e) => warn!("Unable to add development account: {}", e),
|
||||
Ok(address) => {
|
||||
let _ = account_provider.set_account_name(address.clone(), "Development Account".into());
|
||||
let _ = account_provider.set_account_meta(address, ::serde_json::to_string(&(vec![
|
||||
("description", "Never use this account outside of develoopment chain!"),
|
||||
("passwordHint","Password is empty string"),
|
||||
].into_iter().collect::<::std::collections::HashMap<_,_>>())).expect("Serialization of hashmap does not fail."));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct an error `String` with an adaptive hint on how to create an account.
|
||||
fn build_create_account_hint(spec: &SpecType, keys: &str) -> String {
|
||||
format!("You can create an account via RPC, UI or `parity account new --chain {} --keys-path {}`.", spec, keys)
|
||||
|
@ -65,10 +65,10 @@ pub fn main() {
|
||||
StratumServer::start(
|
||||
&SocketAddr::new(
|
||||
IpAddr::from_str(&service_config.listen_addr)
|
||||
.unwrap_or_else(|e|
|
||||
.unwrap_or_else(|e| {
|
||||
println!("Fatal: invalid listen address: '{}' ({:?})", &service_config.listen_addr, e);
|
||||
std::process::exit(1)
|
||||
),
|
||||
}),
|
||||
service_config.port,
|
||||
),
|
||||
job_dispatcher.service().clone(),
|
||||
|
@ -93,7 +93,7 @@ impl ParityAccounts for ParityAccountsClient {
|
||||
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
|
||||
let store = self.account_provider()?;
|
||||
|
||||
let secret = Secret::from_slice(&secret.0)
|
||||
let secret = Secret::from_unsafe_slice(&secret.0)
|
||||
.map_err(|e| errors::account("Could not create account.", e))?;
|
||||
store.insert_account(secret, &pass)
|
||||
.map(Into::into)
|
||||
|
@ -58,7 +58,7 @@ impl SecretStoreClient {
|
||||
/// Decrypt secret key using account' private key
|
||||
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
|
||||
self.decrypt_key(address, password, key)
|
||||
.and_then(|s| Secret::from_slice(&s).map_err(|e| errors::account("invalid secret", e)))
|
||||
.and_then(|s| Secret::from_unsafe_slice(&s).map_err(|e| errors::account("invalid secret", e)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +302,7 @@ fn rpc_eth_submit_hashrate() {
|
||||
fn rpc_eth_sign() {
|
||||
let tester = EthTester::default();
|
||||
|
||||
let account = tester.accounts_provider.insert_account(Secret::from_slice(&[69u8; 32]).unwrap(), "abcd").unwrap();
|
||||
let account = tester.accounts_provider.insert_account(Secret::from_slice(&[69u8; 32]), "abcd").unwrap();
|
||||
tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap();
|
||||
let _message = "0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f".from_hex().unwrap();
|
||||
|
||||
|
@ -200,7 +200,7 @@ fn should_sign_if_account_is_unlocked() {
|
||||
// given
|
||||
let tester = eth_signing();
|
||||
let data = vec![5u8];
|
||||
let acc = tester.accounts.insert_account(Secret::from_slice(&[69u8; 32]).unwrap(), "test").unwrap();
|
||||
let acc = tester.accounts.insert_account(Secret::from_slice(&[69u8; 32]), "test").unwrap();
|
||||
tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
|
||||
|
||||
// when
|
||||
|
16
rustfmt.toml
16
rustfmt.toml
@ -1,17 +1,15 @@
|
||||
verbose=false
|
||||
max_width=1000
|
||||
ideal_width=1000
|
||||
tabs_spaces=4
|
||||
comment_width=1000
|
||||
tab_spaces=4
|
||||
fn_call_width=1000
|
||||
struct_lit_width=32
|
||||
fn_arg_indent="Tabbed"
|
||||
single_line_if_else=true
|
||||
where_indent="Visual"
|
||||
where_trailing_comma=true
|
||||
chain_base_indent="Inherit"
|
||||
chain_indent="Inherit"
|
||||
fn_call_style="Visual"
|
||||
single_line_if_else_max_width=100
|
||||
trailing_comma="Vertical"
|
||||
chain_indent="Visual"
|
||||
chain_one_line_max=100
|
||||
reorder_imports=true
|
||||
format_strings=false
|
||||
chain_overflow_last=false
|
||||
hard_tabs=true
|
||||
wrap_match_arms=false
|
||||
|
@ -1089,7 +1089,7 @@ mod tests {
|
||||
use ethcrypto::DEFAULT_MAC;
|
||||
use ethcrypto::ecies::decrypt;
|
||||
let decrypt_shadows: Vec<_> = decrypted_secret.decrypt_shadows.unwrap().into_iter()
|
||||
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()).unwrap())
|
||||
.map(|c| Secret::from_slice(&decrypt(key_pair.secret(), &DEFAULT_MAC, &c).unwrap()))
|
||||
.collect();
|
||||
let decrypted_secret = math::decrypt_with_shadow_coefficients(decrypted_secret.decrypted_secret, decrypted_secret.common_point.unwrap(), decrypt_shadows).unwrap();
|
||||
assert_eq!(decrypted_secret, SECRET_PLAIN.into());
|
||||
|
4
test.sh
4
test.sh
@ -13,6 +13,10 @@ case $1 in
|
||||
OPTIONS=""
|
||||
shift
|
||||
;;
|
||||
--no-run)
|
||||
OPTIONS="--no-run"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# unknown option
|
||||
;;
|
||||
|
@ -1242,7 +1242,7 @@ fn load_key(path: &Path) -> Option<Secret> {
|
||||
fn key_save_load() {
|
||||
use ::devtools::RandomTempPath;
|
||||
let temp_path = RandomTempPath::create_dir();
|
||||
let key = Secret::from_slice(&H256::random()).unwrap();
|
||||
let key = H256::random().into();
|
||||
save_key(temp_path.as_path(), &key);
|
||||
let r = load_key(temp_path.as_path());
|
||||
assert_eq!(key, r.unwrap());
|
||||
|
@ -77,7 +77,7 @@ impl DBTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
|
||||
pub fn put(&mut self, col: Option<u32>, key: &[u8], value: &[u8]) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
ekey.append_slice(key);
|
||||
@ -88,7 +88,7 @@ impl DBTransaction {
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
|
||||
pub fn put_vec(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
ekey.append_slice(key);
|
||||
@ -99,7 +99,7 @@ impl DBTransaction {
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten upon write.
|
||||
/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
|
||||
/// Value will be RLP-compressed on flush
|
||||
pub fn put_compressed(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
||||
let mut ekey = ElasticArray32::new();
|
||||
|
Loading…
Reference in New Issue
Block a user