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]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
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 = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -1564,7 +1564,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn secret() -> Secret {
|
fn secret() -> Secret {
|
||||||
Secret::from_slice(&"".sha3()).unwrap()
|
"".sha3().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -39,7 +39,6 @@ use super::signer::EngineSigner;
|
|||||||
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||||
|
|
||||||
/// `AuthorityRound` params.
|
/// `AuthorityRound` params.
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct AuthorityRoundParams {
|
pub struct AuthorityRoundParams {
|
||||||
/// Gas limit divisor.
|
/// Gas limit divisor.
|
||||||
pub gas_limit_bound_divisor: U256,
|
pub gas_limit_bound_divisor: U256,
|
||||||
@ -52,7 +51,7 @@ pub struct AuthorityRoundParams {
|
|||||||
/// Starting step,
|
/// Starting step,
|
||||||
pub start_step: Option<u64>,
|
pub start_step: Option<u64>,
|
||||||
/// Valid validators.
|
/// Valid validators.
|
||||||
pub validators: ethjson::spec::ValidatorSet,
|
pub validators: Box<ValidatorSet>,
|
||||||
/// Chain score validation transition block.
|
/// Chain score validation transition block.
|
||||||
pub validate_score_transition: u64,
|
pub validate_score_transition: u64,
|
||||||
/// Number of first block where EIP-155 rules are validated.
|
/// Number of first block where EIP-155 rules are validated.
|
||||||
@ -66,7 +65,7 @@ impl From<ethjson::spec::AuthorityRoundParams> for AuthorityRoundParams {
|
|||||||
AuthorityRoundParams {
|
AuthorityRoundParams {
|
||||||
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
||||||
step_duration: Duration::from_secs(p.step_duration.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),
|
block_reward: p.block_reward.map_or_else(U256::zero, Into::into),
|
||||||
registrar: p.registrar.map_or_else(Address::new, Into::into),
|
registrar: p.registrar.map_or_else(Address::new, Into::into),
|
||||||
start_step: p.start_step.map(Into::into),
|
start_step: p.start_step.map(Into::into),
|
||||||
@ -209,7 +208,7 @@ impl AuthorityRound {
|
|||||||
proposed: AtomicBool::new(false),
|
proposed: AtomicBool::new(false),
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
signer: Default::default(),
|
signer: Default::default(),
|
||||||
validators: new_validator_set(our_params.validators),
|
validators: our_params.validators,
|
||||||
validate_score_transition: our_params.validate_score_transition,
|
validate_score_transition: our_params.validate_score_transition,
|
||||||
eip155_transition: our_params.eip155_transition,
|
eip155_transition: our_params.eip155_transition,
|
||||||
validate_step_transition: our_params.validate_step_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() })));
|
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)?;
|
let parent_step = header_step(parent)?;
|
||||||
|
// Ensure header is from the step after parent.
|
||||||
if step == parent_step
|
if step == parent_step
|
||||||
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
|| (header.number() >= self.validate_step_transition && step <= parent_step) {
|
||||||
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step);
|
||||||
self.validators.report_malicious(header.author(), header.number(), Default::default());
|
self.validators.report_malicious(header.author(), header.number(), Default::default());
|
||||||
Err(EngineError::DoubleVote(header.author().clone()))?;
|
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 gas_limit_divisor = self.gas_limit_bound_divisor;
|
||||||
let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
use util::*;
|
use util::*;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use error::{Error, BlockError};
|
use error::{Error, BlockError};
|
||||||
use ethkey::Secret;
|
|
||||||
use rlp::encode;
|
use rlp::encode;
|
||||||
use block::*;
|
use block::*;
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::Seal;
|
use engines::{Seal, Engine};
|
||||||
|
use engines::validator_set::TestSet;
|
||||||
|
use super::{AuthorityRoundParams, AuthorityRound};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn has_valid_metadata() {
|
fn has_valid_metadata() {
|
||||||
@ -513,8 +522,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn generates_seal_and_does_not_double_propose() {
|
fn generates_seal_and_does_not_double_propose() {
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
let tap = Arc::new(AccountProvider::transient_provider());
|
||||||
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
|
let addr1 = tap.insert_account("1".sha3().into(), "1").unwrap();
|
||||||
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
|
let addr2 = tap.insert_account("2".sha3().into(), "2").unwrap();
|
||||||
|
|
||||||
let spec = Spec::new_test_round();
|
let spec = Spec::new_test_round();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
@ -545,7 +554,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn proposer_switching() {
|
fn proposer_switching() {
|
||||||
let tap = AccountProvider::transient_provider();
|
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();
|
let mut parent_header: Header = Header::default();
|
||||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||||
@ -570,7 +579,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rejects_future_block() {
|
fn rejects_future_block() {
|
||||||
let tap = AccountProvider::transient_provider();
|
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();
|
let mut parent_header: Header = Header::default();
|
||||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||||
@ -596,7 +605,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rejects_step_backwards() {
|
fn rejects_step_backwards() {
|
||||||
let tap = AccountProvider::transient_provider();
|
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();
|
let mut parent_header: Header = Header::default();
|
||||||
parent_header.set_seal(vec![encode(&4usize).to_vec()]);
|
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()]);
|
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());
|
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 error::{BlockError, Error};
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use ethkey::Secret;
|
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use engines::Seal;
|
use engines::Seal;
|
||||||
@ -281,7 +280,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn can_generate_seal() {
|
fn can_generate_seal() {
|
||||||
let tap = AccountProvider::transient_provider();
|
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 spec = new_test_authority();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
@ -299,7 +298,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn seals_internally() {
|
fn seals_internally() {
|
||||||
let tap = AccountProvider::transient_provider();
|
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;
|
let engine = new_test_authority().engine;
|
||||||
assert!(!engine.seals_internally().unwrap());
|
assert!(!engine.seals_internally().unwrap());
|
||||||
|
@ -200,7 +200,6 @@ pub fn message_hash(vote_step: VoteStep, block_hash: H256) -> H256 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use util::*;
|
use util::*;
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
use ethkey::Secret;
|
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use super::super::Step;
|
use super::super::Step;
|
||||||
@ -250,7 +249,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn generate_and_verify() {
|
fn generate_and_verify() {
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
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();
|
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||||
|
|
||||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
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 state::CleanupMode;
|
||||||
use io::IoService;
|
use io::IoService;
|
||||||
use super::signer::EngineSigner;
|
use super::signer::EngineSigner;
|
||||||
use super::validator_set::{ValidatorSet, new_validator_set};
|
use super::validator_set::ValidatorSet;
|
||||||
use super::transition::TransitionHandler;
|
use super::transition::TransitionHandler;
|
||||||
use super::vote_collector::VoteCollector;
|
use super::vote_collector::VoteCollector;
|
||||||
use self::message::*;
|
use self::message::*;
|
||||||
@ -124,7 +124,7 @@ impl Tendermint {
|
|||||||
proposal: RwLock::new(None),
|
proposal: RwLock::new(None),
|
||||||
proposal_parent: Default::default(),
|
proposal_parent: Default::default(),
|
||||||
last_proposed: 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));
|
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
|
||||||
engine.step_service.register_handler(Arc::new(handler))?;
|
engine.step_service.register_handler(Arc::new(handler))?;
|
||||||
@ -659,7 +659,6 @@ mod tests {
|
|||||||
use block::*;
|
use block::*;
|
||||||
use error::{Error, BlockError};
|
use error::{Error, BlockError};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use ethkey::Secret;
|
|
||||||
use client::chain_notify::ChainNotify;
|
use client::chain_notify::ChainNotify;
|
||||||
use miner::MinerService;
|
use miner::MinerService;
|
||||||
use tests::helpers::*;
|
use tests::helpers::*;
|
||||||
@ -708,7 +707,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
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();
|
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||||
addr
|
addr
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,16 @@
|
|||||||
use ethjson;
|
use ethjson;
|
||||||
use util::{U256, Uint, Address};
|
use util::{U256, Uint, Address};
|
||||||
use time::Duration;
|
use time::Duration;
|
||||||
|
use super::super::validator_set::{ValidatorSet, new_validator_set};
|
||||||
use super::super::transition::Timeouts;
|
use super::super::transition::Timeouts;
|
||||||
use super::Step;
|
use super::Step;
|
||||||
|
|
||||||
/// `Tendermint` params.
|
/// `Tendermint` params.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TendermintParams {
|
pub struct TendermintParams {
|
||||||
/// Gas limit divisor.
|
/// Gas limit divisor.
|
||||||
pub gas_limit_bound_divisor: U256,
|
pub gas_limit_bound_divisor: U256,
|
||||||
/// List of validators.
|
/// List of validators.
|
||||||
pub validators: ethjson::spec::ValidatorSet,
|
pub validators: Box<ValidatorSet>,
|
||||||
/// Timeout durations for different steps.
|
/// Timeout durations for different steps.
|
||||||
pub timeouts: TendermintTimeouts,
|
pub timeouts: TendermintTimeouts,
|
||||||
/// Block reward.
|
/// Block reward.
|
||||||
@ -82,7 +82,7 @@ impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
|||||||
let dt = TendermintTimeouts::default();
|
let dt = TendermintTimeouts::default();
|
||||||
TendermintParams {
|
TendermintParams {
|
||||||
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
|
||||||
validators: p.validators,
|
validators: new_validator_set(p.validators),
|
||||||
timeouts: TendermintTimeouts {
|
timeouts: TendermintTimeouts {
|
||||||
propose: p.timeout_propose.map_or(dt.propose, to_duration),
|
propose: p.timeout_propose.map_or(dt.propose, to_duration),
|
||||||
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
|
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
|
||||||
|
@ -116,7 +116,6 @@ impl ValidatorSet for ValidatorContract {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use util::*;
|
use util::*;
|
||||||
use rlp::encode;
|
use rlp::encode;
|
||||||
use ethkey::Secret;
|
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use account_provider::AccountProvider;
|
use account_provider::AccountProvider;
|
||||||
@ -140,7 +139,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn reports_validators() {
|
fn reports_validators() {
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
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()));
|
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone()));
|
||||||
client.engine().register_client(Arc::downgrade(&client));
|
client.engine().register_client(Arc::downgrade(&client));
|
||||||
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
|
let validator_contract = Address::from_str("0000000000000000000000000000000000000005").unwrap();
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
/// Validator lists.
|
/// Validator lists.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
mod simple_list;
|
mod simple_list;
|
||||||
mod safe_contract;
|
mod safe_contract;
|
||||||
mod contract;
|
mod contract;
|
||||||
@ -28,6 +30,8 @@ use ethjson::spec::ValidatorSet as ValidatorSpec;
|
|||||||
use client::Client;
|
use client::Client;
|
||||||
use header::{Header, BlockNumber};
|
use header::{Header, BlockNumber};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub use self::test::TestSet;
|
||||||
pub use self::simple_list::SimpleList;
|
pub use self::simple_list::SimpleList;
|
||||||
use self::contract::ValidatorContract;
|
use self::contract::ValidatorContract;
|
||||||
use self::safe_contract::ValidatorSafeContract;
|
use self::safe_contract::ValidatorSafeContract;
|
||||||
|
@ -166,9 +166,9 @@ mod tests {
|
|||||||
fn uses_current_set() {
|
fn uses_current_set() {
|
||||||
::env_logger::init().unwrap();
|
::env_logger::init().unwrap();
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
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 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));
|
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
|
||||||
client.engine().register_client(Arc::downgrade(&client));
|
client.engine().register_client(Arc::downgrade(&client));
|
||||||
|
|
||||||
|
@ -294,9 +294,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn knows_validators() {
|
fn knows_validators() {
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
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 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 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));
|
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap));
|
||||||
client.engine().register_client(Arc::downgrade(&client));
|
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;
|
use types::executed::CallType;
|
||||||
|
|
||||||
fn secret() -> Secret {
|
fn secret() -> Secret {
|
||||||
Secret::from_slice(&"".sha3()).unwrap()
|
"".sha3().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -27,7 +27,7 @@ use devtools::*;
|
|||||||
use miner::Miner;
|
use miner::Miner;
|
||||||
use spec::Spec;
|
use spec::Spec;
|
||||||
use views::BlockView;
|
use views::BlockView;
|
||||||
use ethkey::{KeyPair, Secret};
|
use ethkey::KeyPair;
|
||||||
use transaction::{PendingTransaction, Transaction, Action, Condition};
|
use transaction::{PendingTransaction, Transaction, Action, Condition};
|
||||||
use miner::MinerService;
|
use miner::MinerService;
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ fn change_history_size() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn does_not_propagate_delayed_transactions() {
|
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 secret = key.secret();
|
||||||
let tx0 = PendingTransaction::new(Transaction {
|
let tx0 = PendingTransaction::new(Transaction {
|
||||||
nonce: 0.into(),
|
nonce: 0.into(),
|
||||||
|
@ -113,7 +113,7 @@ impl HeapSizeOf for Transaction {
|
|||||||
impl From<ethjson::state::Transaction> for SignedTransaction {
|
impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||||
fn from(t: ethjson::state::Transaction) -> Self {
|
fn from(t: ethjson::state::Transaction) -> Self {
|
||||||
let to: Option<ethjson::hash::Address> = t.to.into();
|
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 {
|
let tx = Transaction {
|
||||||
nonce: t.nonce.into(),
|
nonce: t.nonce.into(),
|
||||||
gas_price: t.gas_price.into(),
|
gas_price: t.gas_price.into(),
|
||||||
|
@ -192,7 +192,7 @@ pub mod ecdh {
|
|||||||
let sec = key::SecretKey::from_slice(context, &secret)?;
|
let sec = key::SecretKey::from_slice(context, &secret)?;
|
||||||
let shared = ecdh::SharedSecret::new_raw(context, &publ, &sec);
|
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))
|
.map_err(|_| Error::Secp(SecpError::InvalidSecretKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ impl Generator for Brain {
|
|||||||
match i > 16384 {
|
match i > 16384 {
|
||||||
false => i += 1,
|
false => i += 1,
|
||||||
true => {
|
true => {
|
||||||
if let Ok(secret) = Secret::from_slice(&secret) {
|
if let Ok(secret) = Secret::from_unsafe_slice(&secret) {
|
||||||
let result = KeyPair::from_secret(secret);
|
let result = KeyPair::from_secret(secret);
|
||||||
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
if result.as_ref().ok().map_or(false, |r| r.address()[0] == 0) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -99,8 +99,7 @@ impl ExtendedSecret {
|
|||||||
pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label {
|
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_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
|
||||||
|
|
||||||
let derived_secret = Secret::from_slice(&*derived_key)
|
let derived_secret = Secret::from_slice(&*derived_key);
|
||||||
.expect("Derivation always produced a valid private key; qed");
|
|
||||||
|
|
||||||
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
ExtendedSecret::with_code(derived_secret, next_chain_code)
|
||||||
}
|
}
|
||||||
@ -181,7 +180,7 @@ impl ExtendedKeyPair {
|
|||||||
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
|
||||||
let (master_key, chain_code) = derivation::seed_pair(seed);
|
let (master_key, chain_code) = derivation::seed_pair(seed);
|
||||||
Ok(ExtendedKeyPair::with_secret(
|
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,
|
chain_code,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -402,7 +401,7 @@ mod tests {
|
|||||||
|
|
||||||
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
|
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
|
||||||
let (private_seed, chain_code) = master_chain_basic();
|
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);
|
let derived = f(extended_secret);
|
||||||
assert_eq!(**derived.as_raw(), test_private);
|
assert_eq!(**derived.as_raw(), test_private);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ impl KeyPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_secret_slice(slice: &[u8]) -> Result<KeyPair, Error> {
|
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 {
|
pub fn from_keypair(sec: key::SecretKey, publ: key::PublicKey) -> Self {
|
||||||
|
@ -33,7 +33,7 @@ impl fmt::Debug for Secret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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");
|
assert_eq!(32, key.len(), "Caller should provide 32-byte length slice");
|
||||||
|
|
||||||
let mut h = H256::default();
|
let mut h = H256::default();
|
||||||
@ -41,11 +41,17 @@ impl Secret {
|
|||||||
Secret { inner: h }
|
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)?;
|
let secret = key::SecretKey::from_slice(&super::SECP256K1, key)?;
|
||||||
Ok(secret.into())
|
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)
|
/// Inplace add one secret key to another (scalar + scalar)
|
||||||
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
pub fn add(&mut self, other: &Secret) -> Result<(), Error> {
|
||||||
let mut key_secret = self.to_secp256k1_secret()?;
|
let mut key_secret = self.to_secp256k1_secret()?;
|
||||||
@ -121,14 +127,25 @@ impl Secret {
|
|||||||
impl FromStr for Secret {
|
impl FromStr for Secret {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let hash = H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?;
|
Ok(H256::from_str(s).map_err(|e| Error::Custom(format!("{:?}", e)))?.into())
|
||||||
Self::from_slice(&hash)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl From<key::SecretKey> for Secret {
|
||||||
fn from(key: key::SecretKey) -> Self {
|
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)?;
|
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
|
/// Try to decrypt and return result as is
|
||||||
|
@ -90,7 +90,7 @@ pub trait VaultKeyDirectory: KeyDirectory {
|
|||||||
fn set_meta(&self, meta: &str) -> Result<(), Error>;
|
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::memory::MemoryDirectory;
|
||||||
pub use self::vault::VaultDiskDirectory;
|
pub use self::vault::VaultDiskDirectory;
|
||||||
|
|
||||||
|
@ -15,10 +15,29 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
use ethkey::Address;
|
use ethkey::Address;
|
||||||
use dir::{paths, KeyDirectory, RootDiskDirectory};
|
use dir::{paths, KeyDirectory, RootDiskDirectory, DiskKeyFileManager, KeyFileManager};
|
||||||
use Error;
|
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.
|
/// Import all accounts from one directory to the other.
|
||||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
||||||
let accounts = src.load()?;
|
let accounts = src.load()?;
|
||||||
|
@ -57,7 +57,7 @@ mod secret_store;
|
|||||||
pub use self::account::{SafeAccount, Crypto};
|
pub use self::account::{SafeAccount, Crypto};
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
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::json::OpaqueKeyFile as KeyFile;
|
||||||
pub use self::presale::PresaleWallet;
|
pub use self::presale::PresaleWallet;
|
||||||
pub use self::secret_store::{
|
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 len = crypto::aes::decrypt_cbc(&derived_key, &self.iv, &self.ciphertext, &mut key).map_err(|_| Error::InvalidPassword)?;
|
||||||
let unpadded = &key[..len];
|
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 let Ok(kp) = KeyPair::from_secret(secret) {
|
||||||
if kp.address() == self.address {
|
if kp.address() == self.address {
|
||||||
return Ok(kp)
|
return Ok(kp)
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
"transform-decorators-legacy",
|
"transform-decorators-legacy",
|
||||||
"transform-class-properties",
|
"transform-class-properties",
|
||||||
"transform-object-rest-spread",
|
"transform-object-rest-spread",
|
||||||
|
"transform-es2015-modules-commonjs",
|
||||||
|
"transform-runtime",
|
||||||
"lodash",
|
"lodash",
|
||||||
"recharts"
|
"recharts"
|
||||||
],
|
],
|
||||||
@ -25,7 +27,6 @@
|
|||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-runtime",
|
|
||||||
[ "babel-plugin-webpack-alias", { "config": "webpack/test.js" } ]
|
[ "babel-plugin-webpack-alias", { "config": "webpack/test.js" } ]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "1.7.80",
|
"version": "1.7.82",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -77,11 +77,11 @@
|
|||||||
"babel-plugin-recharts": "1.1.0",
|
"babel-plugin-recharts": "1.1.0",
|
||||||
"babel-plugin-transform-class-properties": "6.23.0",
|
"babel-plugin-transform-class-properties": "6.23.0",
|
||||||
"babel-plugin-transform-decorators-legacy": "1.3.4",
|
"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-object-rest-spread": "6.23.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.3.2",
|
"babel-plugin-transform-react-remove-prop-types": "0.3.2",
|
||||||
"babel-plugin-transform-runtime": "6.23.0",
|
"babel-plugin-transform-runtime": "6.23.0",
|
||||||
"babel-plugin-webpack-alias": "2.1.2",
|
"babel-plugin-webpack-alias": "2.1.2",
|
||||||
"babel-polyfill": "6.23.0",
|
|
||||||
"babel-preset-env": "1.1.9",
|
"babel-preset-env": "1.1.9",
|
||||||
"babel-preset-es2015": "6.22.0",
|
"babel-preset-es2015": "6.22.0",
|
||||||
"babel-preset-es2016": "6.22.0",
|
"babel-preset-es2016": "6.22.0",
|
||||||
@ -183,6 +183,7 @@
|
|||||||
"promise-worker": "1.1.1",
|
"promise-worker": "1.1.1",
|
||||||
"react": "15.4.2",
|
"react": "15.4.2",
|
||||||
"react-dom": "15.4.2",
|
"react-dom": "15.4.2",
|
||||||
|
"react-inspector": "paritytech/react-inspector",
|
||||||
"react-intl": "2.1.5",
|
"react-intl": "2.1.5",
|
||||||
"react-router": "3.0.0",
|
"react-router": "3.0.0",
|
||||||
"react-router-redux": "4.0.7",
|
"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 Subscriptions from './subscriptions';
|
||||||
import util from './util';
|
import util from './util';
|
||||||
import { isFunction } from './util/types';
|
import { isFunction } from './util/types';
|
||||||
import { LocalAccountsMiddleware } from './local';
|
// import { LocalAccountsMiddleware } from './local';
|
||||||
|
|
||||||
export default class Api extends EventEmitter {
|
export default class Api extends EventEmitter {
|
||||||
constructor (transport, allowSubscriptions = true) {
|
constructor (transport, allowSubscriptions = true) {
|
||||||
@ -54,9 +54,9 @@ export default class Api extends EventEmitter {
|
|||||||
const middleware = this.parity
|
const middleware = this.parity
|
||||||
.nodeKind()
|
.nodeKind()
|
||||||
.then((nodeKind) => {
|
.then((nodeKind) => {
|
||||||
if (nodeKind.availability === 'public') {
|
// if (nodeKind.availability === 'public') {
|
||||||
return LocalAccountsMiddleware;
|
// return LocalAccountsMiddleware;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return null;
|
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;
|
color: #333;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
font-weight: 300 !important;
|
font-weight: 300;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
@ -39,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root * {
|
:root * {
|
||||||
font-weight: 300 !important;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root :global(#container) > div {
|
:root :global(#container) > div {
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill/dist/polyfill.js';
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
es6Promise.polyfill();
|
es6Promise.polyfill();
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill/dist/polyfill.js';
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
es6Promise.polyfill();
|
es6Promise.polyfill();
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill/dist/polyfill.js';
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
es6Promise.polyfill();
|
es6Promise.polyfill();
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill';
|
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
|
@ -8,6 +8,17 @@
|
|||||||
"visible": false,
|
"visible": false,
|
||||||
"noselect": 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",
|
"id": "0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f",
|
||||||
"url": "tokendeploy",
|
"url": "tokendeploy",
|
||||||
@ -85,17 +96,5 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"skipBuild": true,
|
"skipBuild": true,
|
||||||
"skipHistory": 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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill';
|
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'babel-polyfill';
|
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
import es6Promise from 'es6-promise';
|
import es6Promise from 'es6-promise';
|
||||||
|
@ -56,9 +56,6 @@ module.exports = {
|
|||||||
'node-fetch': 'node-fetch'
|
'node-fetch': 'node-fetch'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
noParse: [
|
|
||||||
/babel-polyfill/
|
|
||||||
],
|
|
||||||
rules: [
|
rules: [
|
||||||
rulesParity,
|
rulesParity,
|
||||||
rulesEs6,
|
rulesEs6,
|
||||||
|
@ -23,7 +23,6 @@ const ENV = process.env.NODE_ENV || 'development';
|
|||||||
const DEST = process.env.BUILD_DEST || '.build';
|
const DEST = process.env.BUILD_DEST || '.build';
|
||||||
|
|
||||||
let modules = [
|
let modules = [
|
||||||
'babel-polyfill',
|
|
||||||
'bignumber.js',
|
'bignumber.js',
|
||||||
'blockies',
|
'blockies',
|
||||||
'brace',
|
'brace',
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
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::dir::RootDiskDirectory;
|
||||||
use ethcore::ethstore::SecretVaultRef;
|
use ethcore::ethstore::SecretVaultRef;
|
||||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||||
@ -94,16 +94,16 @@ fn new(n: NewAccount) -> Result<String, String> {
|
|||||||
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
||||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
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))?;
|
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> {
|
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||||
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
||||||
let secret_store = Box::new(secret_store(dir, None)?);
|
let secret_store = Box::new(secret_store(dir, None)?);
|
||||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
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()
|
let result = accounts.into_iter()
|
||||||
.map(|a| format!("{:?}", a))
|
.map(|a| format!("0x{:?}", a))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
@ -113,10 +113,18 @@ fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
|||||||
fn import(i: ImportAccounts) -> Result<String, String> {
|
fn import(i: ImportAccounts) -> Result<String, String> {
|
||||||
let to = keys_dir(i.to, i.spec)?;
|
let to = keys_dir(i.to, i.spec)?;
|
||||||
let mut imported = 0;
|
let mut imported = 0;
|
||||||
|
|
||||||
for path in &i.from {
|
for path in &i.from {
|
||||||
let from = RootDiskDirectory::at(path);
|
let path = PathBuf::from(path);
|
||||||
imported += import_accounts(&from, &to).map_err(|_| "Importing accounts failed.")?.len();
|
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))
|
Ok(format!("{} account(s) imported", imported))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,7 +695,7 @@ impl Configuration {
|
|||||||
ret.listen_address = listen.map(|l| format!("{}", l));
|
ret.listen_address = listen.map(|l| format!("{}", l));
|
||||||
ret.public_address = public.map(|p| format!("{}", p));
|
ret.public_address = public.map(|p| format!("{}", p));
|
||||||
ret.use_secret = match self.args.flag_node_key.as_ref()
|
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,
|
None => None,
|
||||||
Some(Ok(key)) => Some(key),
|
Some(Ok(key)) => Some(key),
|
||||||
|
@ -30,6 +30,7 @@ use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
|||||||
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
||||||
use ethcore::snapshot;
|
use ethcore::snapshot;
|
||||||
use ethcore::verification::queue::VerifierSettings;
|
use ethcore::verification::queue::VerifierSettings;
|
||||||
|
use ethcore::ethstore::ethkey;
|
||||||
use light::Cache as LightDataCache;
|
use light::Cache as LightDataCache;
|
||||||
use ethsync::SyncConfig;
|
use ethsync::SyncConfig;
|
||||||
use informant::Informant;
|
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)
|
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.
|
// Construct an error `String` with an adaptive hint on how to create an account.
|
||||||
fn build_create_account_hint(spec: &SpecType, keys: &str) -> String {
|
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)
|
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(
|
StratumServer::start(
|
||||||
&SocketAddr::new(
|
&SocketAddr::new(
|
||||||
IpAddr::from_str(&service_config.listen_addr)
|
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);
|
println!("Fatal: invalid listen address: '{}' ({:?})", &service_config.listen_addr, e);
|
||||||
std::process::exit(1)
|
std::process::exit(1)
|
||||||
),
|
}),
|
||||||
service_config.port,
|
service_config.port,
|
||||||
),
|
),
|
||||||
job_dispatcher.service().clone(),
|
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> {
|
fn new_account_from_secret(&self, secret: RpcH256, pass: String) -> Result<RpcH160, Error> {
|
||||||
let store = self.account_provider()?;
|
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))?;
|
.map_err(|e| errors::account("Could not create account.", e))?;
|
||||||
store.insert_account(secret, &pass)
|
store.insert_account(secret, &pass)
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
|
@ -58,7 +58,7 @@ impl SecretStoreClient {
|
|||||||
/// Decrypt secret key using account' private key
|
/// Decrypt secret key using account' private key
|
||||||
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
|
fn decrypt_secret(&self, address: H160, password: String, key: Bytes) -> Result<Secret, Error> {
|
||||||
self.decrypt_key(address, password, key)
|
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() {
|
fn rpc_eth_sign() {
|
||||||
let tester = EthTester::default();
|
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();
|
tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap();
|
||||||
let _message = "0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f".from_hex().unwrap();
|
let _message = "0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f".from_hex().unwrap();
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ fn should_sign_if_account_is_unlocked() {
|
|||||||
// given
|
// given
|
||||||
let tester = eth_signing();
|
let tester = eth_signing();
|
||||||
let data = vec![5u8];
|
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();
|
tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
|
16
rustfmt.toml
16
rustfmt.toml
@ -1,17 +1,15 @@
|
|||||||
verbose=false
|
verbose=false
|
||||||
max_width=1000
|
max_width=1000
|
||||||
ideal_width=1000
|
comment_width=1000
|
||||||
tabs_spaces=4
|
tab_spaces=4
|
||||||
fn_call_width=1000
|
fn_call_width=1000
|
||||||
struct_lit_width=32
|
struct_lit_width=32
|
||||||
fn_arg_indent="Tabbed"
|
fn_call_style="Visual"
|
||||||
single_line_if_else=true
|
single_line_if_else_max_width=100
|
||||||
where_indent="Visual"
|
trailing_comma="Vertical"
|
||||||
where_trailing_comma=true
|
chain_indent="Visual"
|
||||||
chain_base_indent="Inherit"
|
chain_one_line_max=100
|
||||||
chain_indent="Inherit"
|
|
||||||
reorder_imports=true
|
reorder_imports=true
|
||||||
format_strings=false
|
format_strings=false
|
||||||
chain_overflow_last=false
|
|
||||||
hard_tabs=true
|
hard_tabs=true
|
||||||
wrap_match_arms=false
|
wrap_match_arms=false
|
||||||
|
@ -1089,7 +1089,7 @@ mod tests {
|
|||||||
use ethcrypto::DEFAULT_MAC;
|
use ethcrypto::DEFAULT_MAC;
|
||||||
use ethcrypto::ecies::decrypt;
|
use ethcrypto::ecies::decrypt;
|
||||||
let decrypt_shadows: Vec<_> = decrypted_secret.decrypt_shadows.unwrap().into_iter()
|
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();
|
.collect();
|
||||||
let decrypted_secret = math::decrypt_with_shadow_coefficients(decrypted_secret.decrypted_secret, decrypted_secret.common_point.unwrap(), decrypt_shadows).unwrap();
|
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());
|
assert_eq!(decrypted_secret, SECRET_PLAIN.into());
|
||||||
|
4
test.sh
4
test.sh
@ -13,6 +13,10 @@ case $1 in
|
|||||||
OPTIONS=""
|
OPTIONS=""
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-run)
|
||||||
|
OPTIONS="--no-run"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
# unknown option
|
# unknown option
|
||||||
;;
|
;;
|
||||||
|
@ -1242,7 +1242,7 @@ fn load_key(path: &Path) -> Option<Secret> {
|
|||||||
fn key_save_load() {
|
fn key_save_load() {
|
||||||
use ::devtools::RandomTempPath;
|
use ::devtools::RandomTempPath;
|
||||||
let temp_path = RandomTempPath::create_dir();
|
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);
|
save_key(temp_path.as_path(), &key);
|
||||||
let r = load_key(temp_path.as_path());
|
let r = load_key(temp_path.as_path());
|
||||||
assert_eq!(key, r.unwrap());
|
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]) {
|
pub fn put(&mut self, col: Option<u32>, key: &[u8], value: &[u8]) {
|
||||||
let mut ekey = ElasticArray32::new();
|
let mut ekey = ElasticArray32::new();
|
||||||
ekey.append_slice(key);
|
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) {
|
pub fn put_vec(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
||||||
let mut ekey = ElasticArray32::new();
|
let mut ekey = ElasticArray32::new();
|
||||||
ekey.append_slice(key);
|
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
|
/// Value will be RLP-compressed on flush
|
||||||
pub fn put_compressed(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
pub fn put_compressed(&mut self, col: Option<u32>, key: &[u8], value: Bytes) {
|
||||||
let mut ekey = ElasticArray32::new();
|
let mut ekey = ElasticArray32::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user