Merge remote-tracking branch 'origin/master' into check-updates
This commit is contained in:
commit
4c9bb5aa25
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1265,7 +1265,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#f46188126257e03c775e76a3cea82b5f70549400"
|
source = "git+https://github.com/ethcore/js-precompiled.git#55741cc9850ad6dcbcb7e0a7ac16e805df85de9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -580,29 +580,29 @@ impl Miner {
|
|||||||
let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into();
|
let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into();
|
||||||
let best_block_header: Header = ::rlp::decode(&chain.best_block_header());
|
let best_block_header: Header = ::rlp::decode(&chain.best_block_header());
|
||||||
transactions.into_iter()
|
transactions.into_iter()
|
||||||
.filter(|tx| match self.engine.verify_transaction_basic(tx, &best_block_header) {
|
.map(|tx| {
|
||||||
Ok(()) => true,
|
match self.engine.verify_transaction_basic(&tx, &best_block_header) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e);
|
debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e);
|
||||||
false
|
Err(e)
|
||||||
}
|
},
|
||||||
}
|
Ok(()) => {
|
||||||
)
|
let origin = accounts.as_ref().and_then(|accounts| {
|
||||||
.map(|tx| {
|
tx.sender().ok().and_then(|sender| match accounts.contains(&sender) {
|
||||||
let origin = accounts.as_ref().and_then(|accounts| {
|
true => Some(TransactionOrigin::Local),
|
||||||
tx.sender().ok().and_then(|sender| match accounts.contains(&sender) {
|
false => None,
|
||||||
true => Some(TransactionOrigin::Local),
|
})
|
||||||
false => None,
|
}).unwrap_or(default_origin);
|
||||||
})
|
|
||||||
}).unwrap_or(default_origin);
|
match origin {
|
||||||
|
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
|
||||||
match origin {
|
transaction_queue.add(tx, origin, &fetch_account, &gas_required)
|
||||||
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
|
},
|
||||||
transaction_queue.add(tx, origin, &fetch_account, &gas_required)
|
TransactionOrigin::External => {
|
||||||
|
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
TransactionOrigin::External => {
|
|
||||||
transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -35,6 +35,9 @@ pub mod kind;
|
|||||||
const MIN_MEM_LIMIT: usize = 16384;
|
const MIN_MEM_LIMIT: usize = 16384;
|
||||||
const MIN_QUEUE_LIMIT: usize = 512;
|
const MIN_QUEUE_LIMIT: usize = 512;
|
||||||
|
|
||||||
|
// maximum possible number of verification threads.
|
||||||
|
const MAX_VERIFIERS: usize = 8;
|
||||||
|
|
||||||
/// Type alias for block queue convenience.
|
/// Type alias for block queue convenience.
|
||||||
pub type BlockQueue = VerificationQueue<self::kind::Blocks>;
|
pub type BlockQueue = VerificationQueue<self::kind::Blocks>;
|
||||||
|
|
||||||
@ -61,6 +64,37 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct VerifierHandle {
|
||||||
|
deleting: Arc<AtomicBool>,
|
||||||
|
sleep: Arc<AtomicBool>,
|
||||||
|
thread: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerifierHandle {
|
||||||
|
// signal to the verifier thread that it should sleep.
|
||||||
|
fn sleep(&self) {
|
||||||
|
self.sleep.store(true, AtomicOrdering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal to the verifier thread that it should wake up.
|
||||||
|
fn wake_up(&self) {
|
||||||
|
self.sleep.store(false, AtomicOrdering::SeqCst);
|
||||||
|
self.thread.thread().unpark();
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal to the verifier thread that it should conclude its
|
||||||
|
// operations.
|
||||||
|
fn conclude(&self) {
|
||||||
|
self.wake_up();
|
||||||
|
self.deleting.store(true, AtomicOrdering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
// join the verifier thread.
|
||||||
|
fn join(self) {
|
||||||
|
self.thread.join().expect("Verifier thread panicked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An item which is in the process of being verified.
|
/// An item which is in the process of being verified.
|
||||||
pub struct Verifying<K: Kind> {
|
pub struct Verifying<K: Kind> {
|
||||||
hash: H256,
|
hash: H256,
|
||||||
@ -97,11 +131,12 @@ pub struct VerificationQueue<K: Kind> {
|
|||||||
engine: Arc<Engine>,
|
engine: Arc<Engine>,
|
||||||
more_to_verify: Arc<SCondvar>,
|
more_to_verify: Arc<SCondvar>,
|
||||||
verification: Arc<Verification<K>>,
|
verification: Arc<Verification<K>>,
|
||||||
verifiers: Vec<JoinHandle<()>>,
|
verifiers: Mutex<(Vec<VerifierHandle>, usize)>,
|
||||||
deleting: Arc<AtomicBool>,
|
deleting: Arc<AtomicBool>,
|
||||||
ready_signal: Arc<QueueSignal>,
|
ready_signal: Arc<QueueSignal>,
|
||||||
empty: Arc<SCondvar>,
|
empty: Arc<SCondvar>,
|
||||||
processing: RwLock<HashSet<H256>>,
|
processing: RwLock<HashSet<H256>>,
|
||||||
|
ticks_since_adjustment: AtomicUsize,
|
||||||
max_queue_size: usize,
|
max_queue_size: usize,
|
||||||
max_mem_use: usize,
|
max_mem_use: usize,
|
||||||
}
|
}
|
||||||
@ -157,6 +192,7 @@ struct Verification<K: Kind> {
|
|||||||
more_to_verify: SMutex<()>,
|
more_to_verify: SMutex<()>,
|
||||||
empty: SMutex<()>,
|
empty: SMutex<()>,
|
||||||
sizes: Sizes,
|
sizes: Sizes,
|
||||||
|
check_seal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Kind> VerificationQueue<K> {
|
impl<K: Kind> VerificationQueue<K> {
|
||||||
@ -173,7 +209,8 @@ impl<K: Kind> VerificationQueue<K> {
|
|||||||
unverified: AtomicUsize::new(0),
|
unverified: AtomicUsize::new(0),
|
||||||
verifying: AtomicUsize::new(0),
|
verifying: AtomicUsize::new(0),
|
||||||
verified: AtomicUsize::new(0),
|
verified: AtomicUsize::new(0),
|
||||||
}
|
},
|
||||||
|
check_seal: check_seal,
|
||||||
});
|
});
|
||||||
let more_to_verify = Arc::new(SCondvar::new());
|
let more_to_verify = Arc::new(SCondvar::new());
|
||||||
let deleting = Arc::new(AtomicBool::new(false));
|
let deleting = Arc::new(AtomicBool::new(false));
|
||||||
@ -185,44 +222,82 @@ impl<K: Kind> VerificationQueue<K> {
|
|||||||
let empty = Arc::new(SCondvar::new());
|
let empty = Arc::new(SCondvar::new());
|
||||||
let panic_handler = PanicHandler::new_in_arc();
|
let panic_handler = PanicHandler::new_in_arc();
|
||||||
|
|
||||||
let mut verifiers: Vec<JoinHandle<()>> = Vec::new();
|
let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS);
|
||||||
let thread_count = max(::num_cpus::get(), 3) - 2;
|
let default_amount = max(::num_cpus::get(), 3) - 2;
|
||||||
for i in 0..thread_count {
|
let mut verifiers = Vec::with_capacity(max_verifiers);
|
||||||
let verification = verification.clone();
|
|
||||||
let engine = engine.clone();
|
debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount);
|
||||||
let more_to_verify = more_to_verify.clone();
|
|
||||||
let ready_signal = ready_signal.clone();
|
for i in 0..max_verifiers {
|
||||||
let empty = empty.clone();
|
debug!(target: "verification", "Adding verification thread #{}", i);
|
||||||
|
|
||||||
let deleting = deleting.clone();
|
let deleting = deleting.clone();
|
||||||
let panic_handler = panic_handler.clone();
|
let panic_handler = panic_handler.clone();
|
||||||
verifiers.push(
|
let verification = verification.clone();
|
||||||
thread::Builder::new()
|
let engine = engine.clone();
|
||||||
.name(format!("Verifier #{}", i))
|
let wait = more_to_verify.clone();
|
||||||
.spawn(move || {
|
let ready = ready_signal.clone();
|
||||||
panic_handler.catch_panic(move || {
|
let empty = empty.clone();
|
||||||
VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty, check_seal)
|
|
||||||
}).unwrap()
|
// enable only the first few verifiers.
|
||||||
})
|
let sleep = if i < default_amount {
|
||||||
.expect("Error starting block verification thread")
|
Arc::new(AtomicBool::new(false))
|
||||||
);
|
} else {
|
||||||
|
Arc::new(AtomicBool::new(true))
|
||||||
|
};
|
||||||
|
|
||||||
|
verifiers.push(VerifierHandle {
|
||||||
|
deleting: deleting.clone(),
|
||||||
|
sleep: sleep.clone(),
|
||||||
|
thread: thread::Builder::new()
|
||||||
|
.name(format!("Verifier #{}", i))
|
||||||
|
.spawn(move || {
|
||||||
|
panic_handler.catch_panic(move || {
|
||||||
|
VerificationQueue::verify(verification, engine, wait, ready, deleting, empty, sleep)
|
||||||
|
}).unwrap()
|
||||||
|
})
|
||||||
|
.expect("Failed to create verifier thread.")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
VerificationQueue {
|
VerificationQueue {
|
||||||
engine: engine,
|
engine: engine,
|
||||||
panic_handler: panic_handler,
|
panic_handler: panic_handler,
|
||||||
ready_signal: ready_signal.clone(),
|
ready_signal: ready_signal,
|
||||||
more_to_verify: more_to_verify.clone(),
|
more_to_verify: more_to_verify,
|
||||||
verification: verification.clone(),
|
verification: verification,
|
||||||
verifiers: verifiers,
|
verifiers: Mutex::new((verifiers, default_amount)),
|
||||||
deleting: deleting.clone(),
|
deleting: deleting,
|
||||||
processing: RwLock::new(HashSet::new()),
|
processing: RwLock::new(HashSet::new()),
|
||||||
empty: empty.clone(),
|
empty: empty,
|
||||||
|
ticks_since_adjustment: AtomicUsize::new(0),
|
||||||
max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT),
|
max_queue_size: max(config.max_queue_size, MIN_QUEUE_LIMIT),
|
||||||
max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT),
|
max_mem_use: max(config.max_mem_use, MIN_MEM_LIMIT),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(verification: Arc<Verification<K>>, engine: Arc<Engine>, wait: Arc<SCondvar>, ready: Arc<QueueSignal>, deleting: Arc<AtomicBool>, empty: Arc<SCondvar>, check_seal: bool) {
|
fn verify(
|
||||||
|
verification: Arc<Verification<K>>,
|
||||||
|
engine: Arc<Engine>,
|
||||||
|
wait: Arc<SCondvar>,
|
||||||
|
ready: Arc<QueueSignal>,
|
||||||
|
deleting: Arc<AtomicBool>,
|
||||||
|
empty: Arc<SCondvar>,
|
||||||
|
sleep: Arc<AtomicBool>,
|
||||||
|
) {
|
||||||
while !deleting.load(AtomicOrdering::Acquire) {
|
while !deleting.load(AtomicOrdering::Acquire) {
|
||||||
|
{
|
||||||
|
while sleep.load(AtomicOrdering::SeqCst) {
|
||||||
|
trace!(target: "verification", "Verifier sleeping");
|
||||||
|
::std::thread::park();
|
||||||
|
trace!(target: "verification", "Verifier waking up");
|
||||||
|
|
||||||
|
if deleting.load(AtomicOrdering::Acquire) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut more_to_verify = verification.more_to_verify.lock().unwrap();
|
let mut more_to_verify = verification.more_to_verify.lock().unwrap();
|
||||||
|
|
||||||
@ -255,7 +330,7 @@ impl<K: Kind> VerificationQueue<K> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hash = item.hash();
|
let hash = item.hash();
|
||||||
let is_ready = match K::verify(item, &*engine, check_seal) {
|
let is_ready = match K::verify(item, &*engine, verification.check_seal) {
|
||||||
Ok(verified) => {
|
Ok(verified) => {
|
||||||
let mut verifying = verification.verifying.lock();
|
let mut verifying = verification.verifying.lock();
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
@ -302,9 +377,15 @@ impl<K: Kind> VerificationQueue<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drain_verifying(verifying: &mut VecDeque<Verifying<K>>, verified: &mut VecDeque<K::Verified>, bad: &mut HashSet<H256>, sizes: &Sizes) {
|
fn drain_verifying(
|
||||||
|
verifying: &mut VecDeque<Verifying<K>>,
|
||||||
|
verified: &mut VecDeque<K::Verified>,
|
||||||
|
bad: &mut HashSet<H256>,
|
||||||
|
sizes: &Sizes,
|
||||||
|
) {
|
||||||
let mut removed_size = 0;
|
let mut removed_size = 0;
|
||||||
let mut inserted_size = 0;
|
let mut inserted_size = 0;
|
||||||
|
|
||||||
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
|
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
|
||||||
assert!(verifying.pop_front().is_some());
|
assert!(verifying.pop_front().is_some());
|
||||||
let size = output.heap_size_of_children();
|
let size = output.heap_size_of_children();
|
||||||
@ -487,14 +568,85 @@ impl<K: Kind> VerificationQueue<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimise memory footprint of the heap fields.
|
/// Optimise memory footprint of the heap fields, and adjust the number of threads
|
||||||
|
/// to better suit the workload.
|
||||||
pub fn collect_garbage(&self) {
|
pub fn collect_garbage(&self) {
|
||||||
{
|
// number of ticks to average queue stats over
|
||||||
self.verification.unverified.lock().shrink_to_fit();
|
// when deciding whether to change the number of verifiers.
|
||||||
|
#[cfg(not(test))]
|
||||||
|
const READJUSTMENT_PERIOD: usize = 12;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
const READJUSTMENT_PERIOD: usize = 1;
|
||||||
|
|
||||||
|
let (u_len, v_len) = {
|
||||||
|
let u_len = {
|
||||||
|
let mut q = self.verification.unverified.lock();
|
||||||
|
q.shrink_to_fit();
|
||||||
|
q.len()
|
||||||
|
};
|
||||||
self.verification.verifying.lock().shrink_to_fit();
|
self.verification.verifying.lock().shrink_to_fit();
|
||||||
self.verification.verified.lock().shrink_to_fit();
|
|
||||||
}
|
let v_len = {
|
||||||
|
let mut q = self.verification.verified.lock();
|
||||||
|
q.shrink_to_fit();
|
||||||
|
q.len()
|
||||||
|
};
|
||||||
|
|
||||||
|
(u_len as isize, v_len as isize)
|
||||||
|
};
|
||||||
|
|
||||||
self.processing.write().shrink_to_fit();
|
self.processing.write().shrink_to_fit();
|
||||||
|
|
||||||
|
if self.ticks_since_adjustment.fetch_add(1, AtomicOrdering::SeqCst) + 1 >= READJUSTMENT_PERIOD {
|
||||||
|
self.ticks_since_adjustment.store(0, AtomicOrdering::SeqCst);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = self.verifiers.lock().1;
|
||||||
|
|
||||||
|
let diff = (v_len - u_len).abs();
|
||||||
|
let total = v_len + u_len;
|
||||||
|
|
||||||
|
self.scale_verifiers(
|
||||||
|
if u_len < 20 {
|
||||||
|
1
|
||||||
|
} else if diff <= total / 10 {
|
||||||
|
current
|
||||||
|
} else if v_len > u_len {
|
||||||
|
current - 1
|
||||||
|
} else {
|
||||||
|
current + 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wake up or sleep verifiers to get as close to the target as
|
||||||
|
// possible, never going over the amount of initially allocated threads
|
||||||
|
// or below 1.
|
||||||
|
fn scale_verifiers(&self, target: usize) {
|
||||||
|
let mut verifiers = self.verifiers.lock();
|
||||||
|
let &mut (ref mut verifiers, ref mut verifier_count) = &mut *verifiers;
|
||||||
|
|
||||||
|
let target = min(verifiers.len(), target);
|
||||||
|
let target = max(1, target);
|
||||||
|
|
||||||
|
debug!(target: "verification", "Scaling from {} to {} verifiers", verifier_count, target);
|
||||||
|
|
||||||
|
// scaling up
|
||||||
|
for i in *verifier_count..target {
|
||||||
|
debug!(target: "verification", "Waking up verifier {}", i);
|
||||||
|
verifiers[i].wake_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
// scaling down.
|
||||||
|
for i in target..*verifier_count {
|
||||||
|
debug!(target: "verification", "Putting verifier {} to sleep", i);
|
||||||
|
verifiers[i].sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
*verifier_count = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,10 +661,23 @@ impl<K: Kind> Drop for VerificationQueue<K> {
|
|||||||
trace!(target: "shutdown", "[VerificationQueue] Closing...");
|
trace!(target: "shutdown", "[VerificationQueue] Closing...");
|
||||||
self.clear();
|
self.clear();
|
||||||
self.deleting.store(true, AtomicOrdering::Release);
|
self.deleting.store(true, AtomicOrdering::Release);
|
||||||
self.more_to_verify.notify_all();
|
|
||||||
for t in self.verifiers.drain(..) {
|
let mut verifiers = self.verifiers.get_mut();
|
||||||
t.join().unwrap();
|
let mut verifiers = &mut verifiers.0;
|
||||||
|
|
||||||
|
// first pass to signal conclusion. must be done before
|
||||||
|
// notify or deadlock possible.
|
||||||
|
for handle in verifiers.iter() {
|
||||||
|
handle.conclude();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.more_to_verify.notify_all();
|
||||||
|
|
||||||
|
// second pass to join.
|
||||||
|
for handle in verifiers.drain(..) {
|
||||||
|
handle.join();
|
||||||
|
}
|
||||||
|
|
||||||
trace!(target: "shutdown", "[VerificationQueue] Closed.");
|
trace!(target: "shutdown", "[VerificationQueue] Closed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -611,4 +776,56 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert!(queue.queue_info().is_full());
|
assert!(queue.queue_info().is_full());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scaling_limits() {
|
||||||
|
use super::MAX_VERIFIERS;
|
||||||
|
|
||||||
|
let queue = get_test_queue();
|
||||||
|
queue.scale_verifiers(MAX_VERIFIERS + 1);
|
||||||
|
|
||||||
|
assert!(queue.verifiers.lock().1 < MAX_VERIFIERS + 1);
|
||||||
|
|
||||||
|
queue.scale_verifiers(0);
|
||||||
|
|
||||||
|
assert!(queue.verifiers.lock().1 == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn readjust_verifiers() {
|
||||||
|
let queue = get_test_queue();
|
||||||
|
|
||||||
|
// put all the verifiers to sleep to ensure
|
||||||
|
// the test isn't timing sensitive.
|
||||||
|
let num_verifiers = {
|
||||||
|
let verifiers = queue.verifiers.lock();
|
||||||
|
for i in 0..verifiers.1 {
|
||||||
|
verifiers.0[i].sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
verifiers.1
|
||||||
|
};
|
||||||
|
|
||||||
|
for block in get_good_dummy_block_seq(5000) {
|
||||||
|
queue.import(Unverified::new(block)).expect("Block good by definition; qed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// almost all unverified == bump verifier count.
|
||||||
|
queue.collect_garbage();
|
||||||
|
assert_eq!(queue.verifiers.lock().1, num_verifiers + 1);
|
||||||
|
|
||||||
|
// wake them up again and verify everything.
|
||||||
|
{
|
||||||
|
let verifiers = queue.verifiers.lock();
|
||||||
|
for i in 0..verifiers.1 {
|
||||||
|
verifiers.0[i].wake_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.flush();
|
||||||
|
|
||||||
|
// nothing to verify == use minimum number of verifiers.
|
||||||
|
queue.collect_garbage();
|
||||||
|
assert_eq!(queue.verifiers.lock().1, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "parity.js",
|
||||||
"version": "0.2.61",
|
"version": "0.2.67",
|
||||||
"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>",
|
||||||
@ -102,6 +102,7 @@
|
|||||||
"postcss-nested": "^1.0.0",
|
"postcss-nested": "^1.0.0",
|
||||||
"postcss-simple-vars": "^3.0.0",
|
"postcss-simple-vars": "^3.0.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
|
"react-addons-perf": "~15.3.2",
|
||||||
"react-addons-test-utils": "~15.3.2",
|
"react-addons-test-utils": "~15.3.2",
|
||||||
"react-copy-to-clipboard": "^4.2.3",
|
"react-copy-to-clipboard": "^4.2.3",
|
||||||
"react-dom": "~15.3.2",
|
"react-dom": "~15.3.2",
|
||||||
|
26
js/src/3rdparty/shapeshift/shapeshift.js
vendored
26
js/src/3rdparty/shapeshift/shapeshift.js
vendored
@ -15,7 +15,8 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export default function (rpc) {
|
export default function (rpc) {
|
||||||
const subscriptions = [];
|
let subscriptions = [];
|
||||||
|
let pollStatusIntervalId = null;
|
||||||
|
|
||||||
function getCoins () {
|
function getCoins () {
|
||||||
return rpc.get('getcoins');
|
return rpc.get('getcoins');
|
||||||
@ -45,6 +46,24 @@ export default function (rpc) {
|
|||||||
callback,
|
callback,
|
||||||
idx
|
idx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only poll if there are subscriptions...
|
||||||
|
if (!pollStatusIntervalId) {
|
||||||
|
pollStatusIntervalId = setInterval(_pollStatus, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe (depositAddress) {
|
||||||
|
const newSubscriptions = []
|
||||||
|
.concat(subscriptions)
|
||||||
|
.filter((sub) => sub.depositAddress !== depositAddress);
|
||||||
|
|
||||||
|
subscriptions = newSubscriptions;
|
||||||
|
|
||||||
|
if (subscriptions.length === 0) {
|
||||||
|
clearInterval(pollStatusIntervalId);
|
||||||
|
pollStatusIntervalId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getSubscriptionStatus (subscription) {
|
function _getSubscriptionStatus (subscription) {
|
||||||
@ -81,13 +100,12 @@ export default function (rpc) {
|
|||||||
subscriptions.forEach(_getSubscriptionStatus);
|
subscriptions.forEach(_getSubscriptionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(_pollStatus, 2000);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCoins,
|
getCoins,
|
||||||
getMarketInfo,
|
getMarketInfo,
|
||||||
getStatus,
|
getStatus,
|
||||||
shift,
|
shift,
|
||||||
subscribe
|
subscribe,
|
||||||
|
unsubscribe
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,11 @@ export default class Contract {
|
|||||||
this._instance[fn.signature] = fn;
|
this._instance[fn.signature] = fn;
|
||||||
});
|
});
|
||||||
|
|
||||||
this._sendSubscriptionChanges();
|
this._subscribedToPendings = false;
|
||||||
|
this._pendingsSubscriptionId = null;
|
||||||
|
|
||||||
|
this._subscribedToBlock = false;
|
||||||
|
this._blockSubscriptionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get address () {
|
get address () {
|
||||||
@ -239,44 +243,71 @@ export default class Contract {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe (eventName = null, options = {}, callback) {
|
_findEvent (eventName = null) {
|
||||||
return new Promise((resolve, reject) => {
|
const event = eventName
|
||||||
let event = null;
|
? this._events.find((evt) => evt.name === eventName)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (eventName) {
|
if (eventName && !event) {
|
||||||
event = this._events.find((evt) => evt.name === eventName);
|
const events = this._events.map((evt) => evt.name).join(', ');
|
||||||
|
throw new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!event) {
|
return event;
|
||||||
const events = this._events.map((evt) => evt.name).join(', ');
|
}
|
||||||
reject(new Error(`${eventName} is not a valid eventName, subscribe using one of ${events} (or null to include all)`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._subscribe(event, options, callback).then(resolve).catch(reject);
|
_createEthFilter (event = null, _options) {
|
||||||
|
const optionTopics = _options.topics || [];
|
||||||
|
const signature = event && event.signature || null;
|
||||||
|
|
||||||
|
// If event provided, remove the potential event signature
|
||||||
|
// as the first element of the topics
|
||||||
|
const topics = signature
|
||||||
|
? [ signature ].concat(optionTopics.filter((t, idx) => idx > 0 || t !== signature))
|
||||||
|
: optionTopics;
|
||||||
|
|
||||||
|
const options = Object.assign({}, _options, {
|
||||||
|
address: this._address,
|
||||||
|
topics
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return this._api.eth.newFilter(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe (eventName = null, options = {}, callback) {
|
||||||
|
try {
|
||||||
|
const event = this._findEvent(eventName);
|
||||||
|
return this._subscribe(event, options, callback);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribe (event = null, _options, callback) {
|
_subscribe (event = null, _options, callback) {
|
||||||
const subscriptionId = nextSubscriptionId++;
|
const subscriptionId = nextSubscriptionId++;
|
||||||
const options = Object.assign({}, _options, {
|
const { skipInitFetch } = _options;
|
||||||
address: this._address,
|
delete _options['skipInitFetch'];
|
||||||
topics: [event ? event.signature : null]
|
|
||||||
});
|
|
||||||
|
|
||||||
return this._api.eth
|
return this
|
||||||
.newFilter(options)
|
._createEthFilter(event, _options)
|
||||||
.then((filterId) => {
|
.then((filterId) => {
|
||||||
|
this._subscriptions[subscriptionId] = {
|
||||||
|
options: _options,
|
||||||
|
callback,
|
||||||
|
filterId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (skipInitFetch) {
|
||||||
|
this._subscribeToChanges();
|
||||||
|
return subscriptionId;
|
||||||
|
}
|
||||||
|
|
||||||
return this._api.eth
|
return this._api.eth
|
||||||
.getFilterLogs(filterId)
|
.getFilterLogs(filterId)
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
callback(null, this.parseEventLogs(logs));
|
callback(null, this.parseEventLogs(logs));
|
||||||
this._subscriptions[subscriptionId] = {
|
|
||||||
options,
|
|
||||||
callback,
|
|
||||||
filterId
|
|
||||||
};
|
|
||||||
|
|
||||||
|
this._subscribeToChanges();
|
||||||
return subscriptionId;
|
return subscriptionId;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -285,19 +316,89 @@ export default class Contract {
|
|||||||
unsubscribe (subscriptionId) {
|
unsubscribe (subscriptionId) {
|
||||||
return this._api.eth
|
return this._api.eth
|
||||||
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
|
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
|
||||||
.then(() => {
|
|
||||||
delete this._subscriptions[subscriptionId];
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('unsubscribe', error);
|
console.error('unsubscribe', error);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
delete this._subscriptions[subscriptionId];
|
||||||
|
this._unsubscribeFromChanges();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendSubscriptionChanges = () => {
|
_subscribeToChanges = () => {
|
||||||
const subscriptions = Object.values(this._subscriptions);
|
const subscriptions = Object.values(this._subscriptions);
|
||||||
const timeout = () => setTimeout(this._sendSubscriptionChanges, 1000);
|
|
||||||
|
|
||||||
Promise
|
const pendingSubscriptions = subscriptions
|
||||||
|
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
|
||||||
|
|
||||||
|
const otherSubscriptions = subscriptions
|
||||||
|
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
|
||||||
|
|
||||||
|
if (pendingSubscriptions.length > 0 && !this._subscribedToPendings) {
|
||||||
|
this._subscribedToPendings = true;
|
||||||
|
this._subscribeToPendings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherSubscriptions.length > 0 && !this._subscribedToBlock) {
|
||||||
|
this._subscribedToBlock = true;
|
||||||
|
this._subscribeToBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_unsubscribeFromChanges = () => {
|
||||||
|
const subscriptions = Object.values(this._subscriptions);
|
||||||
|
|
||||||
|
const pendingSubscriptions = subscriptions
|
||||||
|
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
|
||||||
|
|
||||||
|
const otherSubscriptions = subscriptions
|
||||||
|
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
|
||||||
|
|
||||||
|
if (pendingSubscriptions.length === 0 && this._subscribedToPendings) {
|
||||||
|
this._subscribedToPendings = false;
|
||||||
|
clearTimeout(this._pendingsSubscriptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherSubscriptions.length === 0 && this._subscribedToBlock) {
|
||||||
|
this._subscribedToBlock = false;
|
||||||
|
this._api.unsubscribe(this._blockSubscriptionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscribeToBlock = () => {
|
||||||
|
this._api
|
||||||
|
.subscribe('eth_blockNumber', (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('::_subscribeToBlock', error, error && error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptions = Object.values(this._subscriptions)
|
||||||
|
.filter((s) => !(s.options.toBlock && s.options.toBlock === 'pending'));
|
||||||
|
|
||||||
|
this._sendSubscriptionChanges(subscriptions);
|
||||||
|
})
|
||||||
|
.then((blockSubId) => {
|
||||||
|
this._blockSubscriptionId = blockSubId;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('::_subscribeToBlock', e, e && e.stack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscribeToPendings = () => {
|
||||||
|
const subscriptions = Object.values(this._subscriptions)
|
||||||
|
.filter((s) => s.options.toBlock && s.options.toBlock === 'pending');
|
||||||
|
|
||||||
|
const timeout = () => setTimeout(() => this._subscribeToPendings(), 1000);
|
||||||
|
|
||||||
|
this._sendSubscriptionChanges(subscriptions)
|
||||||
|
.then(() => {
|
||||||
|
this._pendingsSubscriptionId = timeout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendSubscriptionChanges = (subscriptions) => {
|
||||||
|
return Promise
|
||||||
.all(
|
.all(
|
||||||
subscriptions.map((subscription) => {
|
subscriptions.map((subscription) => {
|
||||||
return this._api.eth.getFilterChanges(subscription.filterId);
|
return this._api.eth.getFilterChanges(subscription.filterId);
|
||||||
@ -315,12 +416,9 @@ export default class Contract {
|
|||||||
console.error('_sendSubscriptionChanges', error);
|
console.error('_sendSubscriptionChanges', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
timeout();
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('_sendSubscriptionChanges', error);
|
console.error('_sendSubscriptionChanges', error);
|
||||||
timeout();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,6 +437,7 @@ describe('api/contract/Contract', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const logs = [{
|
const logs = [{
|
||||||
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
|
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
|
||||||
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
||||||
@ -450,6 +451,7 @@ describe('api/contract/Contract', () => {
|
|||||||
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
||||||
transactionIndex: '0x0'
|
transactionIndex: '0x0'
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const parsed = [{
|
const parsed = [{
|
||||||
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
|
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
|
||||||
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
||||||
@ -466,11 +468,13 @@ describe('api/contract/Contract', () => {
|
|||||||
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
|
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
|
||||||
},
|
},
|
||||||
topics: [
|
topics: [
|
||||||
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0'
|
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
|
||||||
|
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
|
||||||
],
|
],
|
||||||
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
||||||
transactionIndex: new BigNumber(0)
|
transactionIndex: new BigNumber(0)
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let contract;
|
let contract;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -496,18 +500,19 @@ describe('api/contract/Contract', () => {
|
|||||||
scope = mockHttp([
|
scope = mockHttp([
|
||||||
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
||||||
{ method: 'eth_getFilterLogs', reply: { result: logs } },
|
{ method: 'eth_getFilterLogs', reply: { result: logs } },
|
||||||
|
{ method: 'eth_getFilterChanges', reply: { result: logs } },
|
||||||
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
||||||
{ method: 'eth_getFilterLogs', reply: { result: logs } }
|
{ method: 'eth_getFilterLogs', reply: { result: logs } }
|
||||||
]);
|
]);
|
||||||
cbb = sinon.stub();
|
cbb = sinon.stub();
|
||||||
cbe = sinon.stub();
|
cbe = sinon.stub();
|
||||||
|
|
||||||
return contract.subscribe('Message', {}, cbb);
|
return contract.subscribe('Message', { toBlock: 'pending' }, cbb);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets the subscriptionId returned', () => {
|
it('sets the subscriptionId returned', () => {
|
||||||
return contract
|
return contract
|
||||||
.subscribe('Message', {}, cbe)
|
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||||
.then((subscriptionId) => {
|
.then((subscriptionId) => {
|
||||||
expect(subscriptionId).to.equal(1);
|
expect(subscriptionId).to.equal(1);
|
||||||
});
|
});
|
||||||
@ -515,7 +520,7 @@ describe('api/contract/Contract', () => {
|
|||||||
|
|
||||||
it('creates a new filter and retrieves the logs on it', () => {
|
it('creates a new filter and retrieves the logs on it', () => {
|
||||||
return contract
|
return contract
|
||||||
.subscribe('Message', {}, cbe)
|
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||||
.then((subscriptionId) => {
|
.then((subscriptionId) => {
|
||||||
expect(scope.isDone()).to.be.true;
|
expect(scope.isDone()).to.be.true;
|
||||||
});
|
});
|
||||||
@ -523,7 +528,7 @@ describe('api/contract/Contract', () => {
|
|||||||
|
|
||||||
it('returns the logs to the callback', () => {
|
it('returns the logs to the callback', () => {
|
||||||
return contract
|
return contract
|
||||||
.subscribe('Message', {}, cbe)
|
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||||
.then((subscriptionId) => {
|
.then((subscriptionId) => {
|
||||||
expect(cbe).to.have.been.calledWith(null, parsed);
|
expect(cbe).to.have.been.calledWith(null, parsed);
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
import { range } from 'lodash';
|
||||||
|
|
||||||
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
|
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
|
||||||
|
|
||||||
@ -50,14 +51,19 @@ export function inHash (hash) {
|
|||||||
return inHex(hash);
|
return inHex(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pad (input, length) {
|
||||||
|
const value = inHex(input).substr(2, length * 2);
|
||||||
|
return '0x' + value + range(length * 2 - value.length).map(() => '0').join('');
|
||||||
|
}
|
||||||
|
|
||||||
export function inTopics (_topics) {
|
export function inTopics (_topics) {
|
||||||
let topics = (_topics || [])
|
let topics = (_topics || [])
|
||||||
.filter((topic) => topic)
|
.filter((topic) => topic === null || topic)
|
||||||
.map(inHex);
|
.map((topic) => topic === null ? null : pad(topic, 32));
|
||||||
|
|
||||||
while (topics.length < 4) {
|
// while (topics.length < 4) {
|
||||||
topics.push(null);
|
// topics.push(null);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return topics;
|
return topics;
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,7 @@ export function hex2Ascii (_hex) {
|
|||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function asciiToHex (string) {
|
||||||
|
return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join('');
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
import { isAddress as isAddressValid, toChecksumAddress } from '../../abi/util/address';
|
||||||
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
import { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
||||||
import { bytesToHex, hex2Ascii } from './format';
|
import { bytesToHex, hex2Ascii, asciiToHex } from './format';
|
||||||
import { fromWei, toWei } from './wei';
|
import { fromWei, toWei } from './wei';
|
||||||
import { sha3 } from './sha3';
|
import { sha3 } from './sha3';
|
||||||
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
||||||
@ -31,6 +31,7 @@ export default {
|
|||||||
isString,
|
isString,
|
||||||
bytesToHex,
|
bytesToHex,
|
||||||
hex2Ascii,
|
hex2Ascii,
|
||||||
|
asciiToHex,
|
||||||
createIdentityImg,
|
createIdentityImg,
|
||||||
decodeCallData,
|
decodeCallData,
|
||||||
decodeMethodInput,
|
decodeMethodInput,
|
||||||
|
@ -19,17 +19,34 @@ export const checkIfVerified = (contract, account) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const checkIfRequested = (contract, account) => {
|
export const checkIfRequested = (contract, account) => {
|
||||||
|
let subId = null;
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
contract.subscribe('Requested', {
|
contract
|
||||||
fromBlock: 0, toBlock: 'pending'
|
.subscribe('Requested', {
|
||||||
}, (err, logs) => {
|
fromBlock: 0, toBlock: 'pending'
|
||||||
if (err) {
|
}, (err, logs) => {
|
||||||
return reject(err);
|
if (err) {
|
||||||
}
|
return reject(err);
|
||||||
const e = logs.find((l) => {
|
}
|
||||||
return l.type === 'mined' && l.params.who && l.params.who.value === account;
|
const e = logs.find((l) => {
|
||||||
|
return l.type === 'mined' && l.params.who && l.params.who.value === account;
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(e ? e.transactionHash : false);
|
||||||
|
resolved = true;
|
||||||
|
|
||||||
|
if (subId) {
|
||||||
|
contract.unsubscribe(subId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((_subId) => {
|
||||||
|
subId = _subId;
|
||||||
|
|
||||||
|
if (resolved) {
|
||||||
|
contract.unsubscribe(subId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
resolve(e ? e.transactionHash : false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
17
js/src/dapps/dappreg.html
Normal file
17
js/src/dapps/dappreg.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
||||||
|
<title>Dapp Registry</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container"></div>
|
||||||
|
<script src="vendor.js"></script>
|
||||||
|
<script src="commons.js"></script>
|
||||||
|
<script src="/parity-utils/parity.js"></script>
|
||||||
|
<script src="dappreg.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
js/src/dapps/dappreg.js
Normal file
35
js/src/dapps/dappreg.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
|
import { useStrict } from 'mobx';
|
||||||
|
|
||||||
|
injectTapEventPlugin();
|
||||||
|
useStrict(true);
|
||||||
|
|
||||||
|
import Application from './dappreg/Application';
|
||||||
|
|
||||||
|
import '../../assets/fonts/Roboto/font.css';
|
||||||
|
import '../../assets/fonts/RobotoMono/font.css';
|
||||||
|
import './style.css';
|
||||||
|
import './dappreg.html';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Application />,
|
||||||
|
document.querySelector('#container')
|
||||||
|
);
|
58
js/src/dapps/dappreg/Application/application.css
Normal file
58
js/src/dapps/dappreg/Application/application.css
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.body {
|
||||||
|
color: #333;
|
||||||
|
background: #eee;
|
||||||
|
padding: 4.5em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apps {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 980px;
|
||||||
|
padding: 1.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 0.75em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: #44e;
|
||||||
|
border-radius: 0 0 0.25em 0.25em;
|
||||||
|
color: #fff;
|
||||||
|
left: 0;
|
||||||
|
padding: 1em;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 5em;
|
||||||
|
font-size: 2em;
|
||||||
|
color: #999;
|
||||||
|
}
|
64
js/src/dapps/dappreg/Application/application.js
Normal file
64
js/src/dapps/dappreg/Application/application.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
|
import ButtonBar from '../ButtonBar';
|
||||||
|
import Dapp from '../Dapp';
|
||||||
|
import ModalDelete from '../ModalDelete';
|
||||||
|
import ModalRegister from '../ModalRegister';
|
||||||
|
import ModalUpdate from '../ModalUpdate';
|
||||||
|
import SelectDapp from '../SelectDapp';
|
||||||
|
import Warning from '../Warning';
|
||||||
|
import styles from './application.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Application extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.dappsStore.isLoading) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.loading }>
|
||||||
|
Loading application
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.body }>
|
||||||
|
<div className={ styles.header }>
|
||||||
|
DAPP REGISTRY, a global view of distributed applications available on the network. Putting the puzzle together.
|
||||||
|
</div>
|
||||||
|
<div className={ styles.apps }>
|
||||||
|
<SelectDapp />
|
||||||
|
<ButtonBar />
|
||||||
|
<Dapp />
|
||||||
|
</div>
|
||||||
|
<div className={ styles.footer }>
|
||||||
|
{ this.dappsStore.count } applications registered, { this.dappsStore.ownedCount } owned by user
|
||||||
|
</div>
|
||||||
|
<Warning />
|
||||||
|
<ModalDelete />
|
||||||
|
<ModalRegister />
|
||||||
|
<ModalUpdate />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Application/index.js
Normal file
17
js/src/dapps/dappreg/Application/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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';
|
38
js/src/dapps/dappreg/Button/button.css
Normal file
38
js/src/dapps/dappreg/Button/button.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background: #44e;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 1em 0.375em;
|
||||||
|
opacity: 0.85;
|
||||||
|
padding: 0.75em 2em;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
background: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-warning="true"] {
|
||||||
|
background: #e44;
|
||||||
|
}
|
||||||
|
}
|
52
js/src/dapps/dappreg/Button/button.js
Normal file
52
js/src/dapps/dappreg/Button/button.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import styles from './button.css';
|
||||||
|
|
||||||
|
export default class Button extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
warning: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { className, disabled, label, warning } = this.props;
|
||||||
|
const classes = `${styles.button} ${className}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={ classes }
|
||||||
|
data-warning={ warning }
|
||||||
|
disabled={ disabled }
|
||||||
|
onClick={ this.onClick }>
|
||||||
|
{ label }
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (event) => {
|
||||||
|
if (this.props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onClick(event);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Button/index.js
Normal file
17
js/src/dapps/dappreg/Button/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './button';
|
21
js/src/dapps/dappreg/ButtonBar/buttonBar.css
Normal file
21
js/src/dapps/dappreg/ButtonBar/buttonBar.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.buttonbar {
|
||||||
|
text-align: center;
|
||||||
|
margin: 1em 0 0 0;
|
||||||
|
}
|
101
js/src/dapps/dappreg/ButtonBar/buttonBar.js
Normal file
101
js/src/dapps/dappreg/ButtonBar/buttonBar.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import ModalStore from '../modalStore';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import styles from './buttonBar.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class ButtonBar extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
modalStore = ModalStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let buttons = [];
|
||||||
|
|
||||||
|
if (this.dappsStore.isEditing || this.dappsStore.isNew) {
|
||||||
|
buttons = [
|
||||||
|
<Button
|
||||||
|
key='cancel'
|
||||||
|
label='Cancel'
|
||||||
|
warning
|
||||||
|
onClick={ this.onCancelClick } />,
|
||||||
|
<Button
|
||||||
|
key='save'
|
||||||
|
label={ this.dappsStore.isNew ? 'Register' : 'Update' }
|
||||||
|
disabled={ !this.dappsStore.canSave }
|
||||||
|
onClick={ this.onSaveClick } />
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
buttons = [
|
||||||
|
<Button
|
||||||
|
key='delete'
|
||||||
|
label='Delete'
|
||||||
|
warning
|
||||||
|
disabled={ !this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner }
|
||||||
|
onClick={ this.onDeleteClick } />,
|
||||||
|
<Button
|
||||||
|
key='edit'
|
||||||
|
label='Edit'
|
||||||
|
disabled={ !this.dappsStore.currentApp.isOwner }
|
||||||
|
onClick={ this.onEditClick } />,
|
||||||
|
<Button
|
||||||
|
key='new'
|
||||||
|
label='New'
|
||||||
|
onClick={ this.onNewClick } />
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.buttonbar }>
|
||||||
|
{ buttons }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelClick = () => {
|
||||||
|
if (this.dappsStore.isEditing) {
|
||||||
|
this.dappsStore.setEditing(false);
|
||||||
|
} else {
|
||||||
|
this.dappsStore.setNew(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteClick = () => {
|
||||||
|
this.modalStore.showDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditClick = () => {
|
||||||
|
this.dappsStore.setEditing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewClick = () => {
|
||||||
|
this.dappsStore.setNew(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSaveClick = () => {
|
||||||
|
if (this.dappsStore.isEditing) {
|
||||||
|
this.modalStore.showUpdate();
|
||||||
|
} else {
|
||||||
|
this.modalStore.showRegister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/ButtonBar/index.js
Normal file
17
js/src/dapps/dappreg/ButtonBar/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './buttonBar';
|
19
js/src/dapps/dappreg/Dapp/dapp.css
Normal file
19
js/src/dapps/dappreg/Dapp/dapp.css
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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 {
|
||||||
|
}
|
162
js/src/dapps/dappreg/Dapp/dapp.js
Normal file
162
js/src/dapps/dappreg/Dapp/dapp.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
|
import Input from '../Input';
|
||||||
|
import SelectAccount from '../SelectAccount';
|
||||||
|
import styles from './dapp.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Dapp extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const app = this.dappsStore.isNew || this.dappsStore.isEditing
|
||||||
|
? this.dappsStore.wipApp
|
||||||
|
: this.dappsStore.currentApp;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.app }>
|
||||||
|
{ this.dappsStore.isNew ? this.renderOwnerSelect(app) : this.renderOwnerStatic(app) }
|
||||||
|
{ this.renderInputs(app) }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInputs (app) {
|
||||||
|
if (this.dappsStore.isNew) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
this.renderHashInput(app, 'image', 'Image hash, as generated by Githubhint', true),
|
||||||
|
this.renderHashInput(app, 'manifest', 'Manifest hash, as generated by Githubhint'),
|
||||||
|
this.renderHashInput(app, 'content', 'Content hash, as generated by Githubhint')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOwnerSelect (app) {
|
||||||
|
const overlayImage = (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 4) } />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
hint={ this.dappsStore.currentAccount.address }
|
||||||
|
label='Owner, select the application owner and editor'
|
||||||
|
overlay={ overlayImage }>
|
||||||
|
<SelectAccount />
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOwnerStatic (app) {
|
||||||
|
const overlayImage = (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ api.util.createIdentityImg(app.owner, 4) } />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
hint={ app.owner }
|
||||||
|
label='Owner, the application owner and editor'
|
||||||
|
overlay={ overlayImage }>
|
||||||
|
<input value={ app.ownerName } readOnly />
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHashInput (app, type, label, withImage = false) {
|
||||||
|
const onChange = (event) => this.onChangeHash(event, type);
|
||||||
|
const hash = app[`${type}Hash`];
|
||||||
|
|
||||||
|
let overlayImage = null;
|
||||||
|
if (withImage && hash) {
|
||||||
|
overlayImage = (
|
||||||
|
<img
|
||||||
|
className={ styles.overlayImage }
|
||||||
|
src={ `/api/content/${hash.substr(2)}` } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
hint={ app[`${type}Error`] || app[`${type}Url`] || '...' }
|
||||||
|
label={ label }
|
||||||
|
key={ `${type}Edit` }
|
||||||
|
overlay={ overlayImage }>
|
||||||
|
<input
|
||||||
|
value={ app[`${type}Hash`] || '' }
|
||||||
|
data-dirty={ app[`${type}Changed`] }
|
||||||
|
data-error={ !!app[`${type}Error`] }
|
||||||
|
readOnly={ !this.dappsStore.isEditing && !this.dappsStore.isNew }
|
||||||
|
onChange={ onChange } />
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeHash (event, type) {
|
||||||
|
if (!this.dappsStore.isNew && !this.dappsStore.isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = event.target.value;
|
||||||
|
let changed = false;
|
||||||
|
let url = null;
|
||||||
|
|
||||||
|
if (this.dappsStore.isNew) {
|
||||||
|
if (hash && hash.length) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.dappsStore.currentApp[`${type}Hash`] !== hash) {
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
url = this.dappsStore.currentApp[`${type}Url`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dappsStore.editWip({
|
||||||
|
[`${type}Changed`]: changed,
|
||||||
|
[`${type}Error`]: null,
|
||||||
|
[`${type}Hash`]: hash,
|
||||||
|
[`${type}Url`]: changed ? 'Resolving url from hash' : url
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
if (hash.length) {
|
||||||
|
this.dappsStore
|
||||||
|
.lookupHash(hash)
|
||||||
|
.then((url) => {
|
||||||
|
this.dappsStore.editWip({
|
||||||
|
[`${type}Error`]: url ? null : 'Unable to resolve url',
|
||||||
|
[`${type}Url`]: url
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.dappsStore.editWip({ [`${type}Url`]: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Dapp/index.js
Normal file
17
js/src/dapps/dappreg/Dapp/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './dapp';
|
17
js/src/dapps/dappreg/Input/index.js
Normal file
17
js/src/dapps/dappreg/Input/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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';
|
92
js/src/dapps/dappreg/Input/input.css
Normal file
92
js/src/dapps/dappreg/Input/input.css
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.input {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
border: 4px solid rgba(223, 223, 223, 0.85);
|
||||||
|
border-radius: 0.25em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 0.25em 0 0.25em 0;
|
||||||
|
padding: 0.5em 0.5em 1.5em 0.5em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding-bottom: 1.5em;
|
||||||
|
|
||||||
|
&[data-dirty="true"] {
|
||||||
|
background: rgba(255, 255, 203, 0.85);
|
||||||
|
border-color: rgba(203, 203, 151, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-error="true"] {
|
||||||
|
background: rgba(255, 223, 223, 0.85) !important;
|
||||||
|
border-color: rgba(223, 191, 191, 0.85) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[readonly] {
|
||||||
|
background: rgba(239, 239, 239, 0.85);
|
||||||
|
border-color: rgba(223, 223, 223, 0.85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: #888;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75em;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 58px;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
background: rgba(239, 239, 239, 0.85);
|
||||||
|
border-color: rgba(223, 223, 223, 0.85);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
color: #888;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75em;
|
||||||
|
position: absolute;
|
||||||
|
right: 52px;
|
||||||
|
text-align: right;
|
||||||
|
top: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
right: 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
js/src/dapps/dappreg/Input/input.js
Normal file
47
js/src/dapps/dappreg/Input/input.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import styles from './input.css';
|
||||||
|
|
||||||
|
export default class Input extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
hint: PropTypes.string,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
overlay: PropTypes.node
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, hint, label, overlay } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.input }>
|
||||||
|
<label>
|
||||||
|
{ label }
|
||||||
|
</label>
|
||||||
|
{ children }
|
||||||
|
<div className={ styles.hint }>
|
||||||
|
{ hint }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.overlay }>
|
||||||
|
{ overlay }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Modal/index.js
Normal file
17
js/src/dapps/dappreg/Modal/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './modal';
|
116
js/src/dapps/dappreg/Modal/modal.css
Normal file
116
js/src/dapps/dappreg/Modal/modal.css
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
.body {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 50;
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 0 0 0.25em 0.25em;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 840px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
line-height: 1.5em;
|
||||||
|
padding: 2em;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
color: #f44;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section+.section {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 0.5em 1.625em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: #44e;
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.85;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
background: #e44;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
background: rgba(204, 204, 204, 0.7);
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 49;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
display: block !important;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.75em;
|
||||||
|
margin-top: -0.5em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
color: #888;
|
||||||
|
}
|
66
js/src/dapps/dappreg/Modal/modal.js
Normal file
66
js/src/dapps/dappreg/Modal/modal.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import styles from './modal.css';
|
||||||
|
|
||||||
|
export default class Modal extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
buttons: PropTypes.node,
|
||||||
|
children: PropTypes.node,
|
||||||
|
error: PropTypes.object,
|
||||||
|
header: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children, buttons, error, header } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.modal }>
|
||||||
|
<div className={ styles.overlay } />
|
||||||
|
<div className={ styles.body }>
|
||||||
|
<div className={ styles.dialog }>
|
||||||
|
<div className={ `${styles.header} ${error ? styles.error : ''}` }>
|
||||||
|
{ header }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.content }>
|
||||||
|
{ error ? this.renderError() : children }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.footer }>
|
||||||
|
{ buttons }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError () {
|
||||||
|
const { error } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
Your operation failed to complete sucessfully. The following error was returned:
|
||||||
|
</div>
|
||||||
|
<div className={ `${styles.section} ${styles.error}` }>
|
||||||
|
{ error.toString() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/ModalDelete/index.js
Normal file
17
js/src/dapps/dappreg/ModalDelete/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './modalDelete';
|
159
js/src/dapps/dappreg/ModalDelete/modalDelete.js
Normal file
159
js/src/dapps/dappreg/ModalDelete/modalDelete.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import ModalStore from '../modalStore';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
|
||||||
|
import styles from '../Modal/modal.css';
|
||||||
|
|
||||||
|
const HEADERS = [
|
||||||
|
'Error During Deletion',
|
||||||
|
'Confirm Application Deletion',
|
||||||
|
'Waiting for Signer Confirmation',
|
||||||
|
'Waiting for Transaction Receipt',
|
||||||
|
'Deletion Completed'
|
||||||
|
];
|
||||||
|
const STEP_ERROR = 0;
|
||||||
|
const STEP_CONFIRM = 1;
|
||||||
|
const STEP_SIGNER = 2;
|
||||||
|
const STEP_TXRECEIPT = 3;
|
||||||
|
const STEP_DONE = 4;
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class ModalDelete extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
modalStore = ModalStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (!this.modalStore.showingDelete) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
buttons={ this.renderButtons() }
|
||||||
|
error={ this.modalStore.errorDelete }
|
||||||
|
header={ HEADERS[this.modalStore.stepDelete] }>
|
||||||
|
{ this.renderStep() }
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
switch (this.modalStore.stepDelete) {
|
||||||
|
case STEP_ERROR:
|
||||||
|
case STEP_DONE:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='close'
|
||||||
|
label='Close'
|
||||||
|
onClick={ this.onClickClose } />
|
||||||
|
];
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='cancel'
|
||||||
|
label='No, Cancel'
|
||||||
|
onClick={ this.onClickClose } />,
|
||||||
|
<Button
|
||||||
|
key='delete'
|
||||||
|
label='Yes, Delete'
|
||||||
|
warning
|
||||||
|
onClick={ this.onClickYes } />
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStep () {
|
||||||
|
switch (this.modalStore.stepDelete) {
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return this.renderStepConfirm();
|
||||||
|
case STEP_SIGNER:
|
||||||
|
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
|
||||||
|
case STEP_TXRECEIPT:
|
||||||
|
return this.renderStepWait('Waiting for the transaction receipt from the network');
|
||||||
|
case STEP_DONE:
|
||||||
|
return this.renderStepCompleted();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepCompleted () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
Your application has been removed from the registry.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepConfirm () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
You are about to remove a distributed application from the registry, the details of this application is given below. Removal does not return any fees, however the application will not be available to users anymore.
|
||||||
|
</div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Owner account
|
||||||
|
</div>
|
||||||
|
<div className={ styles.account }>
|
||||||
|
<img src={ api.util.createIdentityImg(this.dappsStore.currentApp.owner, 3) } />
|
||||||
|
<div>{ this.dappsStore.currentApp.ownerName }</div>
|
||||||
|
<div className={ styles.address }>{ this.dappsStore.currentApp.owner }</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Application identifier
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ this.dappsStore.currentApp.id }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepWait (waitingFor) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
{ waitingFor }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickClose = () => {
|
||||||
|
this.modalStore.hideDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickYes = () => {
|
||||||
|
this.modalStore.doDelete();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/ModalRegister/index.js
Normal file
17
js/src/dapps/dappreg/ModalRegister/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './modalRegister';
|
159
js/src/dapps/dappreg/ModalRegister/modalRegister.js
Normal file
159
js/src/dapps/dappreg/ModalRegister/modalRegister.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import ModalStore from '../modalStore';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
|
||||||
|
import styles from '../Modal/modal.css';
|
||||||
|
|
||||||
|
const HEADERS = [
|
||||||
|
'Error During Registration',
|
||||||
|
'Confirm Application Registration',
|
||||||
|
'Waiting for Signer Confirmation',
|
||||||
|
'Waiting for Transaction Receipt',
|
||||||
|
'Registration Completed'
|
||||||
|
];
|
||||||
|
const STEP_ERROR = 0;
|
||||||
|
const STEP_CONFIRM = 1;
|
||||||
|
const STEP_SIGNER = 2;
|
||||||
|
const STEP_TXRECEIPT = 3;
|
||||||
|
const STEP_DONE = 4;
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class ModalRegister extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
modalStore = ModalStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (!this.modalStore.showingRegister) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
buttons={ this.renderButtons() }
|
||||||
|
error={ this.modalStore.errorRegister }
|
||||||
|
header={ HEADERS[this.modalStore.stepRegister] }>
|
||||||
|
{ this.renderStep() }
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
switch (this.modalStore.stepRegister) {
|
||||||
|
case STEP_ERROR:
|
||||||
|
case STEP_DONE:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='close'
|
||||||
|
label='Close'
|
||||||
|
onClick={ this.onClickClose } />
|
||||||
|
];
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='cancel'
|
||||||
|
label='No, Cancel'
|
||||||
|
onClick={ this.onClickClose } />,
|
||||||
|
<Button
|
||||||
|
key='register'
|
||||||
|
label='Yes, Register'
|
||||||
|
warning
|
||||||
|
onClick={ this.onClickConfirmYes } />
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStep () {
|
||||||
|
switch (this.modalStore.stepRegister) {
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return this.renderStepConfirm();
|
||||||
|
case STEP_SIGNER:
|
||||||
|
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
|
||||||
|
case STEP_TXRECEIPT:
|
||||||
|
return this.renderStepWait('Waiting for the transaction receipt from the network');
|
||||||
|
case STEP_DONE:
|
||||||
|
return this.renderStepCompleted();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepCompleted () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
Your application has been registered in the registry.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepConfirm () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
You are about to register a new distributed application on the network, the details of this application is given below. This will require a non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small>.
|
||||||
|
</div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Selected owner account
|
||||||
|
</div>
|
||||||
|
<div className={ styles.account }>
|
||||||
|
<img src={ api.util.createIdentityImg(this.dappsStore.currentAccount.address, 3) } />
|
||||||
|
<div>{ this.dappsStore.currentAccount.name }</div>
|
||||||
|
<div className={ styles.hint }>{ this.dappsStore.currentAccount.address }</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Unique assigned application identifier
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ this.dappsStore.wipApp.id }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepWait (waitingFor) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
{ waitingFor }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickClose = () => {
|
||||||
|
this.modalStore.hideRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickConfirmYes = () => {
|
||||||
|
this.modalStore.doRegister();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/ModalUpdate/index.js
Normal file
17
js/src/dapps/dappreg/ModalUpdate/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './modalUpdate';
|
169
js/src/dapps/dappreg/ModalUpdate/modalUpdate.js
Normal file
169
js/src/dapps/dappreg/ModalUpdate/modalUpdate.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import ModalStore from '../modalStore';
|
||||||
|
|
||||||
|
import Button from '../Button';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
|
||||||
|
import styles from '../Modal/modal.css';
|
||||||
|
|
||||||
|
const HEADERS = [
|
||||||
|
'Error During Update',
|
||||||
|
'Confirm Application Update',
|
||||||
|
'Waiting for Signer Confirmation',
|
||||||
|
'Waiting for Transaction Receipt',
|
||||||
|
'Update Completed'
|
||||||
|
];
|
||||||
|
const STEP_ERROR = 0;
|
||||||
|
const STEP_CONFIRM = 1;
|
||||||
|
const STEP_SIGNER = 2;
|
||||||
|
const STEP_TXRECEIPT = 3;
|
||||||
|
const STEP_DONE = 4;
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class ModalUpdate extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
modalStore = ModalStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (!this.modalStore.showingUpdate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
buttons={ this.renderButtons() }
|
||||||
|
error={ this.modalStore.errorUpdate }
|
||||||
|
header={ HEADERS[this.modalStore.stepUpdate] }>
|
||||||
|
{ this.renderStep() }
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
switch (this.modalStore.stepUpdate) {
|
||||||
|
case STEP_ERROR:
|
||||||
|
case STEP_DONE:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='close'
|
||||||
|
label='Close'
|
||||||
|
onClick={ this.onClickClose } />
|
||||||
|
];
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return [
|
||||||
|
<Button
|
||||||
|
key='cancel'
|
||||||
|
label='No, Cancel'
|
||||||
|
onClick={ this.onClickClose } />,
|
||||||
|
<Button
|
||||||
|
key='delete'
|
||||||
|
label='Yes, Update'
|
||||||
|
warning
|
||||||
|
onClick={ this.onClickYes } />
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStep () {
|
||||||
|
switch (this.modalStore.stepUpdate) {
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
return this.renderStepConfirm();
|
||||||
|
case STEP_SIGNER:
|
||||||
|
return this.renderStepWait('Waiting for transaction confirmation in the Parity secure signer');
|
||||||
|
case STEP_TXRECEIPT:
|
||||||
|
return this.renderStepWait('Waiting for the transaction receipt from the network');
|
||||||
|
case STEP_DONE:
|
||||||
|
return this.renderStepCompleted();
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepCompleted () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
Your application metadata has been updated in the registry.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepConfirm () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
You are about to update the application details in the registry, the details of these updates are given below. Please note that each update will generate a seperate transaction.
|
||||||
|
</div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Application identifier
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{ this.dappsStore.wipApp.id }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ this.renderChanges() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChanges () {
|
||||||
|
return ['content', 'image', 'manifest']
|
||||||
|
.filter((type) => this.dappsStore.wipApp[`${type}Changed`])
|
||||||
|
.map((type) => {
|
||||||
|
return (
|
||||||
|
<div className={ styles.section } key={ `${type}Update` }>
|
||||||
|
<div className={ styles.heading }>
|
||||||
|
Updates to { type } hash
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>{ this.dappsStore.wipApp[`${type}Hash`] || '(removed)' }</div>
|
||||||
|
<div className={ styles.hint }>
|
||||||
|
{ this.dappsStore.wipApp[`${type}Url`] || 'current url to be removed from registry' }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStepWait (waitingFor) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={ styles.section }>
|
||||||
|
{ waitingFor }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickClose = () => {
|
||||||
|
this.modalStore.hideUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickYes = () => {
|
||||||
|
this.modalStore.doUpdate();
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/SelectAccount/index.js
Normal file
17
js/src/dapps/dappreg/SelectAccount/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './selectAccount';
|
49
js/src/dapps/dappreg/SelectAccount/selectAccount.js
Normal file
49
js/src/dapps/dappreg/SelectAccount/selectAccount.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class SelectAccount extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={ this.dappsStore.currentAccount.address }
|
||||||
|
onChange={ this.onSelect }>
|
||||||
|
{ this.renderOptions() }
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOptions () {
|
||||||
|
return this.dappsStore.accounts.map((account) => {
|
||||||
|
return (
|
||||||
|
<option value={ account.address } key={ account.address }>
|
||||||
|
{ account.name }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (event) => {
|
||||||
|
this.dappsStore.setCurrentAccount(event.target.value);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/SelectDapp/index.js
Normal file
17
js/src/dapps/dappreg/SelectDapp/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './selectDapp';
|
76
js/src/dapps/dappreg/SelectDapp/selectDapp.js
Normal file
76
js/src/dapps/dappreg/SelectDapp/selectDapp.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
|
||||||
|
import Input from '../Input';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class SelectDapp extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.dappsStore.isNew) {
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
hint='...'
|
||||||
|
label='Application Id, the unique assigned identifier'>
|
||||||
|
<input value={ this.dappsStore.wipApp.id } readOnly />
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let overlayImg = null;
|
||||||
|
if (this.dappsStore.currentApp.imageHash) {
|
||||||
|
overlayImg = (
|
||||||
|
<img src={ `/api/content/${this.dappsStore.currentApp.imageHash.substr(2)}` } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
hint={ this.dappsStore.currentApp.id }
|
||||||
|
label='Application, the actual application details to show below'
|
||||||
|
overlay={ overlayImg }>
|
||||||
|
<select
|
||||||
|
disabled={ this.dappsStore.isEditing }
|
||||||
|
value={ this.dappsStore.currentApp.id }
|
||||||
|
onChange={ this.onSelect }>
|
||||||
|
{ this.renderOptions() }
|
||||||
|
</select>
|
||||||
|
</Input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOptions () {
|
||||||
|
return this.dappsStore.apps.map((app) => {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
value={ app.id }
|
||||||
|
key={ app.id }>
|
||||||
|
{ app.name }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (event) => {
|
||||||
|
this.dappsStore.setCurrentApp(event.target.value);
|
||||||
|
}
|
||||||
|
}
|
17
js/src/dapps/dappreg/Warning/index.js
Normal file
17
js/src/dapps/dappreg/Warning/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 './warning';
|
36
js/src/dapps/dappreg/Warning/warning.css
Normal file
36
js/src/dapps/dappreg/Warning/warning.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
background: #f44;
|
||||||
|
border-top-right-radius: 0.25em;
|
||||||
|
bottom: 0;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.75em;
|
||||||
|
left: 0;
|
||||||
|
line-height: 1.5em;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 1.5em;
|
||||||
|
position: fixed;
|
||||||
|
max-width: 540px;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
div+div {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
51
js/src/dapps/dappreg/Warning/warning.js
Normal file
51
js/src/dapps/dappreg/Warning/warning.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { api } from '../parity';
|
||||||
|
import DappsStore from '../dappsStore';
|
||||||
|
import ModalStore from '../modalStore';
|
||||||
|
|
||||||
|
import styles from './warning.css';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export default class Warning extends Component {
|
||||||
|
dappsStore = DappsStore.instance();
|
||||||
|
modalStore = ModalStore.instance();
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (!this.modalStore.showingWarning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.warning } onClick={ this.onClose }>
|
||||||
|
<div>
|
||||||
|
WARNING: Registering a dapp is for developers only. Please ensure you understand the steps needed to develop and deploy applications, should you wish to use this dapp for anything apart from queries.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
A non-refundable fee of { api.util.fromWei(this.dappsStore.fee).toFormat(3) }<small>ETH</small> is required for any registration.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.modalStore.hideWarning();
|
||||||
|
}
|
||||||
|
}
|
482
js/src/dapps/dappreg/dappsStore.js
Normal file
482
js/src/dapps/dappreg/dappsStore.js
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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 BigNumber from 'bignumber.js';
|
||||||
|
import { action, computed, observable, transaction } from 'mobx';
|
||||||
|
|
||||||
|
import * as abis from '../../contracts/abi';
|
||||||
|
import builtins from '../../views/Dapps/builtin.json';
|
||||||
|
|
||||||
|
import { api } from './parity';
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
export default class DappsStore {
|
||||||
|
@observable accounts = [];
|
||||||
|
@observable addresses = [];
|
||||||
|
@observable apps = [];
|
||||||
|
@observable contractOwner = null;
|
||||||
|
@observable currentAccount = null;
|
||||||
|
@observable currentApp = null;
|
||||||
|
@observable count = 0;
|
||||||
|
@observable fee = new BigNumber(0);
|
||||||
|
@observable isContractOwner = false;
|
||||||
|
@observable isEditing = false;
|
||||||
|
@observable isLoading = true;
|
||||||
|
@observable isNew = false;
|
||||||
|
@observable wipApp = null;
|
||||||
|
|
||||||
|
_startTime = Date.now();
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this._loadDapps();
|
||||||
|
}
|
||||||
|
|
||||||
|
static instance () {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new DappsStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get canSave () {
|
||||||
|
const app = this.wipApp;
|
||||||
|
|
||||||
|
const hasError = app.contentError || app.imageError || app.manifestError;
|
||||||
|
const isDirty = this.isNew || app.contentChanged || app.imageChanged || app.manifestChanged;
|
||||||
|
const isEditMode = this.isEditing || this.isNew;
|
||||||
|
|
||||||
|
return isEditMode && isDirty && !hasError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get isCurrentEditable () {
|
||||||
|
return !!this.accounts.find((account) => account.address === this.currentApp.owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get ownedCount () {
|
||||||
|
return (this.apps.filter((app) => app.isOwner) || []).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action copyToWip = () => {
|
||||||
|
let wipApp;
|
||||||
|
|
||||||
|
if (this.isNew) {
|
||||||
|
wipApp = {
|
||||||
|
id: api.util.sha3(`${this._startTime}_${Date.now()}`),
|
||||||
|
contentHash: null,
|
||||||
|
contentUrl: null,
|
||||||
|
imageHash: null,
|
||||||
|
imageUrl: null,
|
||||||
|
manifestHash: null,
|
||||||
|
manifestUrl: null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const app = this.currentApp;
|
||||||
|
|
||||||
|
wipApp = {
|
||||||
|
id: app.id,
|
||||||
|
contentHash: app.contentHash,
|
||||||
|
contentUrl: app.contentUrl,
|
||||||
|
imageHash: app.imageHash,
|
||||||
|
imageUrl: app.imageUrl,
|
||||||
|
manifestHash: app.manifestHash,
|
||||||
|
manifestUrl: app.manifestUrl,
|
||||||
|
owner: app.owner,
|
||||||
|
ownerName: app.ownerName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wipApp = Object.assign(wipApp, {
|
||||||
|
contentChanged: false,
|
||||||
|
contentError: null,
|
||||||
|
imageChanged: false,
|
||||||
|
imageError: null,
|
||||||
|
manifestChanged: false,
|
||||||
|
manifestError: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.wipApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action editWip = (details) => {
|
||||||
|
if (this.isNew || this.isEditing) {
|
||||||
|
transaction(() => {
|
||||||
|
Object
|
||||||
|
.keys(details)
|
||||||
|
.forEach((key) => {
|
||||||
|
this.wipApp[key] = details[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wipApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action sortApps = (apps = this.apps) => {
|
||||||
|
transaction(() => {
|
||||||
|
const ownApps = apps
|
||||||
|
.filter((app) => app.isOwner)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
const otherApps = apps
|
||||||
|
.filter((app) => !app.isOwner)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
this.apps = ownApps.concat(otherApps);
|
||||||
|
this.currentApp = this.apps[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setApps = (apps) => {
|
||||||
|
this.sortApps(apps.filter((app) => {
|
||||||
|
const bnid = new BigNumber(app.id);
|
||||||
|
return bnid.gt(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return this.apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action _addApp = (app) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.setApps(this.apps.concat([app]));
|
||||||
|
this.setCurrentApp(app.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action addApp = (appId, account) => {
|
||||||
|
this
|
||||||
|
._loadDapp({
|
||||||
|
id: appId,
|
||||||
|
isOwner: true,
|
||||||
|
name: `- ${appId}`,
|
||||||
|
owner: account.address,
|
||||||
|
ownerName: account.name
|
||||||
|
})
|
||||||
|
.then(this._addApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action refreshApp = (appId) => {
|
||||||
|
this._loadDapp(this.apps.find((app) => app.id === appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action removeApp = (appId) => {
|
||||||
|
this.setApps(this.apps.filter((app) => app.id !== appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAppInfo = (app, info) => {
|
||||||
|
transaction(() => {
|
||||||
|
Object.keys(info).forEach((key) => {
|
||||||
|
app[key] = info[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setAccounts = (accountsInfo) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.addresses = Object
|
||||||
|
.keys(accountsInfo)
|
||||||
|
.map((address) => {
|
||||||
|
const account = accountsInfo[address];
|
||||||
|
account.address = address;
|
||||||
|
return account;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.accounts = this.addresses.filter((account) => account.uuid);
|
||||||
|
this.currentAccount = this.accounts[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setContractOwner = (contractOwner) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.contractOwner = contractOwner;
|
||||||
|
this.isContractOwner = !!this.accounts.find((account) => account.address === contractOwner);
|
||||||
|
});
|
||||||
|
return contractOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCurrentApp = (id) => {
|
||||||
|
this.currentApp = this.apps.find((app) => app.id === id);
|
||||||
|
return this.currentApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCurrentAccount = (address) => {
|
||||||
|
this.currentAccount = this.accounts.find((account) => account.address === address);
|
||||||
|
return this.currentAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setCount = (count) => {
|
||||||
|
this.count = count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setEditing = (mode) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.isEditing = mode;
|
||||||
|
this.copyToWip();
|
||||||
|
});
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setFee = (fee) => {
|
||||||
|
this.fee = fee;
|
||||||
|
return fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setLoading = (loading) => {
|
||||||
|
this.isLoading = loading;
|
||||||
|
return loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setNew = (mode) => {
|
||||||
|
transaction(() => {
|
||||||
|
this.isNew = mode;
|
||||||
|
this.copyToWip();
|
||||||
|
});
|
||||||
|
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
lookupHash (hash) {
|
||||||
|
return this._retrieveUrl(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getCount () {
|
||||||
|
return this._instanceReg
|
||||||
|
.count.call()
|
||||||
|
.then((count) => {
|
||||||
|
this.setCount(count.toNumber());
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:getCount', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFee () {
|
||||||
|
return this._instanceReg
|
||||||
|
.fee.call()
|
||||||
|
.then(this.setFee)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:getFee', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOwner () {
|
||||||
|
return this._instanceReg
|
||||||
|
.owner.call()
|
||||||
|
.then(this.setContractOwner)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:getOwner', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadDapps () {
|
||||||
|
return this._loadRegistry()
|
||||||
|
.then(() => Promise.all([
|
||||||
|
this._attachContracts(),
|
||||||
|
this._loadAccounts()
|
||||||
|
]))
|
||||||
|
.then(() => Promise.all([
|
||||||
|
this._getCount(),
|
||||||
|
this._getFee(),
|
||||||
|
this._getOwner()
|
||||||
|
]))
|
||||||
|
.then(() => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < this.count; index++) {
|
||||||
|
promises.push(this._instanceReg.at.call({}, [index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.then((appsInfo) => {
|
||||||
|
return Promise.all(
|
||||||
|
this
|
||||||
|
.setApps(appsInfo.map(([appId, owner]) => {
|
||||||
|
const isOwner = !!this.accounts.find((account) => account.address === owner);
|
||||||
|
const account = this.addresses.find((account) => account.address === owner);
|
||||||
|
const id = api.util.bytesToHex(appId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
ownerName: account ? account.name : owner,
|
||||||
|
isOwner,
|
||||||
|
name: `- ${id}`
|
||||||
|
};
|
||||||
|
}))
|
||||||
|
.map(this._loadDapp)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.sortApps();
|
||||||
|
this.setLoading(this.count === 0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadDapps', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadDapp = (app) => {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._loadMeta(app.id, 'CONTENT'),
|
||||||
|
this._loadMeta(app.id, 'IMG'),
|
||||||
|
this._loadMeta(app.id, 'MANIFEST')
|
||||||
|
])
|
||||||
|
.then(([contentHash, imageHash, manifestHash]) => {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._retrieveUrl(contentHash),
|
||||||
|
this._retrieveUrl(imageHash),
|
||||||
|
this._retrieveUrl(manifestHash)
|
||||||
|
])
|
||||||
|
.then(([contentUrl, imageUrl, manifestUrl]) => {
|
||||||
|
return this
|
||||||
|
._loadManifest(app.id, manifestHash)
|
||||||
|
.then((manifest) => {
|
||||||
|
this.setAppInfo(app, {
|
||||||
|
manifest,
|
||||||
|
manifestHash,
|
||||||
|
manifestUrl,
|
||||||
|
contentHash,
|
||||||
|
contentUrl,
|
||||||
|
imageHash,
|
||||||
|
imageUrl,
|
||||||
|
name: (manifest && manifest.name) || `- ${app.id}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadDapp', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadMeta (appId, key) {
|
||||||
|
return this._instanceReg
|
||||||
|
.meta.call({}, [appId, key])
|
||||||
|
.then((meta) => {
|
||||||
|
const hash = api.util.bytesToHex(meta);
|
||||||
|
const bnhash = new BigNumber(hash);
|
||||||
|
|
||||||
|
return bnhash.gt(0)
|
||||||
|
? hash
|
||||||
|
: null;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadMeta', error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadManifest (appId, manifestHash) {
|
||||||
|
const builtin = builtins.find((app) => app.id === appId);
|
||||||
|
|
||||||
|
if (builtin) {
|
||||||
|
return Promise.resolve(builtin);
|
||||||
|
} else if (!manifestHash) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(`/api/content/${manifestHash.substr(2)}/`, { redirect: 'follow', mode: 'cors' })
|
||||||
|
.then((response) => {
|
||||||
|
return response.ok
|
||||||
|
? response.json()
|
||||||
|
: null;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadManifest', error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_retrieveUrl (urlHash) {
|
||||||
|
if (!urlHash) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._instanceGhh
|
||||||
|
.entries.call({}, [urlHash])
|
||||||
|
.then(([repo, _commit, owner]) => {
|
||||||
|
const bnowner = new BigNumber(owner);
|
||||||
|
|
||||||
|
if (bnowner.eq(0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commit = api.util.bytesToHex(_commit);
|
||||||
|
const bncommit = new BigNumber(commit);
|
||||||
|
|
||||||
|
if (bncommit.eq(0)) {
|
||||||
|
return repo;
|
||||||
|
} else {
|
||||||
|
return `https://codeload.github.com/${repo}/zip/${commit.substr(2)}`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:retriveUrl', error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadAccounts () {
|
||||||
|
return api.parity
|
||||||
|
.accounts()
|
||||||
|
.then(this.setAccounts)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadAccounts', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadRegistry () {
|
||||||
|
return api.parity
|
||||||
|
.registryAddress()
|
||||||
|
.then((registryAddress) => {
|
||||||
|
console.log(`the registry was found at ${registryAddress}`);
|
||||||
|
this._registry = api.newContract(abis.registry, registryAddress).instance;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:loadRegistry', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachContracts () {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._registry.getAddress.call({}, [api.util.sha3('dappreg'), 'A']),
|
||||||
|
this._registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A'])
|
||||||
|
])
|
||||||
|
.then(([dappregAddress, ghhAddress]) => {
|
||||||
|
console.log(`dappreg was found at ${dappregAddress}`);
|
||||||
|
this._contractReg = api.newContract(abis.dappreg, dappregAddress);
|
||||||
|
this._instanceReg = this._contractReg.instance;
|
||||||
|
console.log(`githubhint was found at ${ghhAddress}`);
|
||||||
|
this._contractGhh = api.newContract(abis.githubhint, ghhAddress);
|
||||||
|
this._instanceGhh = this._contractGhh.instance;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Store:attachContract', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
266
js/src/dapps/dappreg/modalStore.js
Normal file
266
js/src/dapps/dappreg/modalStore.js
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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, transaction } from 'mobx';
|
||||||
|
|
||||||
|
import { trackRequest } from './parity';
|
||||||
|
import DappsStore from './dappsStore';
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
export default class ModalStore {
|
||||||
|
@observable errorDelete = null;
|
||||||
|
@observable errorRegister = null;
|
||||||
|
@observable errorUpdate = null;
|
||||||
|
@observable stepDelete = 0;
|
||||||
|
@observable stepRegister = 0;
|
||||||
|
@observable stepUpdate = 0;
|
||||||
|
@observable showingDelete = false;
|
||||||
|
@observable showingRegister = false;
|
||||||
|
@observable showingUpdate = false;
|
||||||
|
@observable showingWarning = true;
|
||||||
|
|
||||||
|
_dappsStore = DappsStore.instance();
|
||||||
|
|
||||||
|
static instance () {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new ModalStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDeleteError (error) {
|
||||||
|
transaction(() => {
|
||||||
|
this.setDeleteStep(0);
|
||||||
|
this.errorDelete = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setDeleteStep (step) {
|
||||||
|
this.stepDelete = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action showDelete () {
|
||||||
|
transaction(() => {
|
||||||
|
this.setDeleteStep(1);
|
||||||
|
this.errorDelete = null;
|
||||||
|
this.showingDelete = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hideDelete () {
|
||||||
|
this.showingDelete = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setRegisterError (error) {
|
||||||
|
transaction(() => {
|
||||||
|
this.setRegisterStep(0);
|
||||||
|
this.errorRegister = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setRegisterStep (step) {
|
||||||
|
this.stepRegister = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action showRegister () {
|
||||||
|
transaction(() => {
|
||||||
|
this.setRegisterStep(1);
|
||||||
|
this.errorRegister = null;
|
||||||
|
this.showingRegister = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hideRegister () {
|
||||||
|
transaction(() => {
|
||||||
|
this._dappsStore.setEditing(false);
|
||||||
|
this._dappsStore.setNew(false);
|
||||||
|
this.showingRegister = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setUpdateError (error) {
|
||||||
|
transaction(() => {
|
||||||
|
this.setUpdateStep(0);
|
||||||
|
this.errorUpdate = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action setUpdateStep (step) {
|
||||||
|
this.stepUpdate = step;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action showUpdate () {
|
||||||
|
transaction(() => {
|
||||||
|
this.setUpdateStep(1);
|
||||||
|
this.errorUpdate = null;
|
||||||
|
this.showingUpdate = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hideUpdate () {
|
||||||
|
transaction(() => {
|
||||||
|
this._dappsStore.setEditing(false);
|
||||||
|
this._dappsStore.setNew(false);
|
||||||
|
this.showingUpdate = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action hideWarning () {
|
||||||
|
this.showingWarning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
doDelete () {
|
||||||
|
this.setDeleteStep(2);
|
||||||
|
|
||||||
|
const appId = this._dappsStore.currentApp.id;
|
||||||
|
const values = [appId];
|
||||||
|
const options = {
|
||||||
|
from: this._dappsStore.currentApp.isOwner ? this._dappsStore.currentApp.owner : this._dappsStore.contractOwner
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('ModalStore:doDelete', `performing deletion for ${appId} from ${options.from}`);
|
||||||
|
|
||||||
|
this._dappsStore._instanceReg
|
||||||
|
.unregister.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
const newGas = gas.mul(1.2);
|
||||||
|
|
||||||
|
console.log('ModalStore:doDelete', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
|
||||||
|
|
||||||
|
options.gas = newGas.toFixed(0);
|
||||||
|
|
||||||
|
const request = this._dappsStore._instanceReg.unregister.postTransaction(options, values);
|
||||||
|
const statusCallback = (error, status) => {
|
||||||
|
if (error) {
|
||||||
|
} else if (status.signerRequestId) {
|
||||||
|
} else if (status.transactionHash) {
|
||||||
|
this.setDeleteStep(3);
|
||||||
|
} else if (status.transactionReceipt) {
|
||||||
|
this.setDeleteStep(4);
|
||||||
|
this._dappsStore.removeApp(appId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return trackRequest(request, statusCallback);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('ModalStore:doDelete', error);
|
||||||
|
this.setDeleteError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doRegister () {
|
||||||
|
this.setRegisterStep(2);
|
||||||
|
|
||||||
|
const appId = this._dappsStore.wipApp.id;
|
||||||
|
const values = [appId];
|
||||||
|
const options = {
|
||||||
|
from: this._dappsStore.currentAccount.address,
|
||||||
|
value: this._dappsStore.fee
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('ModalStore:doRegister', `performing registration for ${appId} from ${this._dappsStore.currentAccount.address}`);
|
||||||
|
|
||||||
|
this._dappsStore._instanceReg
|
||||||
|
.register.estimateGas(options, values)
|
||||||
|
.then((gas) => {
|
||||||
|
const newGas = gas.mul(1.2);
|
||||||
|
|
||||||
|
console.log('ModalStore:doRegister', `gas estimated as ${gas.toFormat(0)}, setting to ${newGas.toFormat(0)}`);
|
||||||
|
|
||||||
|
options.gas = newGas.toFixed(0);
|
||||||
|
|
||||||
|
const request = this._dappsStore._instanceReg.register.postTransaction(options, values);
|
||||||
|
const statusCallback = (error, status) => {
|
||||||
|
if (error) {
|
||||||
|
} else if (status.signerRequestId) {
|
||||||
|
} else if (status.transactionHash) {
|
||||||
|
this.setRegisterStep(3);
|
||||||
|
} else if (status.transactionReceipt) {
|
||||||
|
this.setRegisterStep(4);
|
||||||
|
this._dappsStore.addApp(appId, this._dappsStore.currentAccount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return trackRequest(request, statusCallback);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('ModalStore:doRegister', error);
|
||||||
|
this.setRegisterError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doUpdate () {
|
||||||
|
this.setUpdateStep(2);
|
||||||
|
|
||||||
|
const appId = this._dappsStore.wipApp.id;
|
||||||
|
const options = {
|
||||||
|
from: this._dappsStore.wipApp.owner
|
||||||
|
};
|
||||||
|
const types = {
|
||||||
|
'content': 'CONTENT',
|
||||||
|
'image': 'IMG',
|
||||||
|
'manifest': 'MANIFEST'
|
||||||
|
};
|
||||||
|
const values = Object
|
||||||
|
.keys(types)
|
||||||
|
.filter((type) => this._dappsStore.wipApp[`${type}Changed`])
|
||||||
|
.map((type) => {
|
||||||
|
return [appId, types[type], this._dappsStore.wipApp[`${type}Hash`] || '0x0'];
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('ModalStore:doUpdate', `performing updates for ${appId} from ${options.from}`);
|
||||||
|
|
||||||
|
Promise
|
||||||
|
.all(values.map((value) => this._dappsStore._instanceReg.setMeta.estimateGas(options, value)))
|
||||||
|
.then((gas) => {
|
||||||
|
const newGas = gas.map((gas) => gas.mul(1.2));
|
||||||
|
|
||||||
|
gas.forEach((gas, index) => {
|
||||||
|
console.log('ModalStore:doUpdate', `${values[index][1]} gas estimated as ${gas.toFormat(0)}, setting to ${newGas[index].toFormat(0)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusCallback = (error, status) => {
|
||||||
|
if (error) {
|
||||||
|
} else if (status.signerRequestId) {
|
||||||
|
} else if (status.transactionHash) {
|
||||||
|
this.setUpdateStep(3);
|
||||||
|
} else if (status.transactionReceipt) {
|
||||||
|
this.setUpdateStep(4);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
newGas.map((gas, index) => {
|
||||||
|
return trackRequest(
|
||||||
|
this._dappsStore._instanceReg.setMeta.postTransaction(
|
||||||
|
Object.assign(options, { gas: gas.toFixed(0) }), values[index]
|
||||||
|
), statusCallback
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this._dappsStore.refreshApp(appId);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('ModalStore:doUpdate', error);
|
||||||
|
this.setUpdateError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
53
js/src/dapps/dappreg/parity.js
Normal file
53
js/src/dapps/dappreg/parity.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
const { api } = window.parity;
|
||||||
|
|
||||||
|
function trackRequest (requestPromise, statusCallback) {
|
||||||
|
return requestPromise
|
||||||
|
.then((signerRequestId) => {
|
||||||
|
console.log('trackRequest', `posted to signer with requestId ${signerRequestId.toString()}`);
|
||||||
|
statusCallback(null, { signerRequestId });
|
||||||
|
|
||||||
|
return api.pollMethod('parity_checkRequest', signerRequestId);
|
||||||
|
})
|
||||||
|
.then((transactionHash) => {
|
||||||
|
console.log('trackRequest', `received transaction hash ${transactionHash}`);
|
||||||
|
statusCallback(null, { transactionHash });
|
||||||
|
|
||||||
|
return api.pollMethod('eth_getTransactionReceipt', transactionHash, (receipt) => {
|
||||||
|
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((transactionReceipt) => {
|
||||||
|
console.log('trackRequest', 'received transaction receipt', transactionReceipt);
|
||||||
|
statusCallback(null, { transactionReceipt });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('trackRequest', error);
|
||||||
|
statusCallback(error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
api,
|
||||||
|
trackRequest
|
||||||
|
};
|
@ -46,6 +46,12 @@ import './index.html';
|
|||||||
|
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// Expose the React Performance Tools on the`window` object
|
||||||
|
const Perf = require('react-addons-perf');
|
||||||
|
window.Perf = Perf;
|
||||||
|
}
|
||||||
|
|
||||||
const AUTH_HASH = '#/auth?';
|
const AUTH_HASH = '#/auth?';
|
||||||
const parityUrl = process.env.PARITY_URL ||
|
const parityUrl = process.env.PARITY_URL ||
|
||||||
(
|
(
|
||||||
|
@ -36,6 +36,7 @@ export default class DetailsStep extends Component {
|
|||||||
onFuncChange: PropTypes.func,
|
onFuncChange: PropTypes.func,
|
||||||
values: PropTypes.array.isRequired,
|
values: PropTypes.array.isRequired,
|
||||||
valuesError: PropTypes.array.isRequired,
|
valuesError: PropTypes.array.isRequired,
|
||||||
|
warning: PropTypes.string,
|
||||||
onValueChange: PropTypes.func.isRequired
|
onValueChange: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ export default class DetailsStep extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
|
{ this.renderWarning() }
|
||||||
<AddressSelect
|
<AddressSelect
|
||||||
label='from account'
|
label='from account'
|
||||||
hint='the account to transact with'
|
hint='the account to transact with'
|
||||||
@ -178,6 +180,20 @@ export default class DetailsStep extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderWarning () {
|
||||||
|
const { warning } = this.props;
|
||||||
|
|
||||||
|
if (!warning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.warning }>
|
||||||
|
{ warning }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onFuncChange = (event, index, signature) => {
|
onFuncChange = (event, index, signature) => {
|
||||||
const { contract, onFuncChange } = this.props;
|
const { contract, onFuncChange } = this.props;
|
||||||
|
|
||||||
|
@ -33,3 +33,12 @@
|
|||||||
.txhash {
|
.txhash {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background: #f80;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 0.75em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -15,17 +15,21 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
|
|
||||||
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
|
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
|
||||||
|
import { MAX_GAS_ESTIMATION } from '../../util/constants';
|
||||||
import { validateAddress, validateUint } from '../../util/validation';
|
import { validateAddress, validateUint } from '../../util/validation';
|
||||||
|
|
||||||
import DetailsStep from './DetailsStep';
|
import DetailsStep from './DetailsStep';
|
||||||
|
|
||||||
|
import ERRORS from '../Transfer/errors';
|
||||||
import { ERROR_CODES } from '../../api/transport/error';
|
import { ERROR_CODES } from '../../api/transport/error';
|
||||||
|
|
||||||
export default class ExecuteContract extends Component {
|
class ExecuteContract extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
@ -36,6 +40,7 @@ export default class ExecuteContract extends Component {
|
|||||||
fromAddress: PropTypes.string,
|
fromAddress: PropTypes.string,
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
contract: PropTypes.object,
|
contract: PropTypes.object,
|
||||||
|
gasLimit: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onFromAddressChange: PropTypes.func.isRequired
|
onFromAddressChange: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
@ -46,6 +51,8 @@ export default class ExecuteContract extends Component {
|
|||||||
fromAddressError: null,
|
fromAddressError: null,
|
||||||
func: null,
|
func: null,
|
||||||
funcError: null,
|
funcError: null,
|
||||||
|
gas: null,
|
||||||
|
gasLimitError: null,
|
||||||
values: [],
|
values: [],
|
||||||
valuesError: [],
|
valuesError: [],
|
||||||
step: 0,
|
step: 0,
|
||||||
@ -64,6 +71,12 @@ export default class ExecuteContract extends Component {
|
|||||||
this.onFuncChange(null, functions[0]);
|
this.onFuncChange(null, functions[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (newProps) {
|
||||||
|
if (newProps.fromAddress !== this.props.fromAddress) {
|
||||||
|
this.estimateGas(newProps.fromAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { sending } = this.state;
|
const { sending } = this.state;
|
||||||
|
|
||||||
@ -119,7 +132,7 @@ export default class ExecuteContract extends Component {
|
|||||||
|
|
||||||
renderStep () {
|
renderStep () {
|
||||||
const { onFromAddressChange } = this.props;
|
const { onFromAddressChange } = this.props;
|
||||||
const { step, busyState, txhash, rejected } = this.state;
|
const { step, busyState, gasLimitError, txhash, rejected } = this.state;
|
||||||
|
|
||||||
if (rejected) {
|
if (rejected) {
|
||||||
return (
|
return (
|
||||||
@ -135,6 +148,7 @@ export default class ExecuteContract extends Component {
|
|||||||
<DetailsStep
|
<DetailsStep
|
||||||
{ ...this.props }
|
{ ...this.props }
|
||||||
{ ...this.state }
|
{ ...this.state }
|
||||||
|
warning={ gasLimitError }
|
||||||
onAmountChange={ this.onAmountChange }
|
onAmountChange={ this.onAmountChange }
|
||||||
onFromAddressChange={ onFromAddressChange }
|
onFromAddressChange={ onFromAddressChange }
|
||||||
onFuncChange={ this.onFuncChange }
|
onFuncChange={ this.onFuncChange }
|
||||||
@ -156,7 +170,7 @@ export default class ExecuteContract extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAmountChange = (amount) => {
|
onAmountChange = (amount) => {
|
||||||
this.setState({ amount });
|
this.setState({ amount }, this.estimateGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFuncChange = (event, func) => {
|
onFuncChange = (event, func) => {
|
||||||
@ -182,7 +196,7 @@ export default class ExecuteContract extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
func,
|
func,
|
||||||
values
|
values
|
||||||
});
|
}, this.estimateGas);
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChange = (event, index, _value) => {
|
onValueChange = (event, index, _value) => {
|
||||||
@ -211,14 +225,55 @@ export default class ExecuteContract extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
values: [].concat(values),
|
values: [].concat(values),
|
||||||
valuesError: [].concat(valuesError)
|
valuesError: [].concat(valuesError)
|
||||||
|
}, () => {
|
||||||
|
if (!valueError) {
|
||||||
|
this.estimateGas();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
estimateGas = (_fromAddress) => {
|
||||||
|
const { api } = this.context;
|
||||||
|
const { fromAddress, gasLimit } = this.props;
|
||||||
|
const { amount, func, values } = this.state;
|
||||||
|
const options = {
|
||||||
|
gas: MAX_GAS_ESTIMATION,
|
||||||
|
from: _fromAddress || fromAddress,
|
||||||
|
value: api.util.toWei(amount || 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!func) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
func
|
||||||
|
.estimateGas(options, values)
|
||||||
|
.then((gasEst) => {
|
||||||
|
const gas = gasEst.mul(1.2);
|
||||||
|
let gasLimitError = null;
|
||||||
|
|
||||||
|
if (gas.gte(MAX_GAS_ESTIMATION)) {
|
||||||
|
gasLimitError = ERRORS.gasException;
|
||||||
|
} else if (gas.gt(gasLimit)) {
|
||||||
|
gasLimitError = ERRORS.gasBlockLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
gas,
|
||||||
|
gasLimitError
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('estimateGas', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
postTransaction = () => {
|
postTransaction = () => {
|
||||||
const { api, store } = this.context;
|
const { api, store } = this.context;
|
||||||
const { fromAddress } = this.props;
|
const { fromAddress } = this.props;
|
||||||
const { amount, func, values } = this.state;
|
const { amount, func, values } = this.state;
|
||||||
const options = {
|
const options = {
|
||||||
|
gas: MAX_GAS_ESTIMATION,
|
||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
value: api.util.toWei(amount || 0)
|
value: api.util.toWei(amount || 0)
|
||||||
};
|
};
|
||||||
@ -237,13 +292,13 @@ export default class ExecuteContract extends Component {
|
|||||||
|
|
||||||
return api
|
return api
|
||||||
.pollMethod('parity_checkRequest', requestId)
|
.pollMethod('parity_checkRequest', requestId)
|
||||||
.catch((e) => {
|
.catch((error) => {
|
||||||
if (e.code === ERROR_CODES.REQUEST_REJECTED) {
|
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
|
||||||
this.setState({ rejected: true });
|
this.setState({ rejected: true });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw error;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((txhash) => {
|
.then((txhash) => {
|
||||||
@ -255,3 +310,18 @@ export default class ExecuteContract extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
|
return { gasLimit };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ExecuteContract);
|
||||||
|
@ -63,6 +63,16 @@ export default class Shapeshift extends Component {
|
|||||||
this.retrieveCoins();
|
this.retrieveCoins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe () {
|
||||||
|
// Unsubscribe from Shapeshit
|
||||||
|
const { depositAddress } = this.state;
|
||||||
|
shapeshift.unsubscribe(depositAddress);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { error, stage } = this.state;
|
const { error, stage } = this.state;
|
||||||
|
|
||||||
@ -205,6 +215,10 @@ export default class Shapeshift extends Component {
|
|||||||
console.log('onShift', result);
|
console.log('onShift', result);
|
||||||
const depositAddress = result.deposit;
|
const depositAddress = result.deposit;
|
||||||
|
|
||||||
|
if (this.state.depositAddress) {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
shapeshift.subscribe(depositAddress, this.onExchangeInfo);
|
shapeshift.subscribe(depositAddress, this.onExchangeInfo);
|
||||||
this.setState({ depositAddress });
|
this.setState({ depositAddress });
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,9 @@ const ERRORS = {
|
|||||||
invalidAddress: 'the supplied address is an invalid network address',
|
invalidAddress: 'the supplied address is an invalid network address',
|
||||||
invalidAmount: 'the supplied amount should be a valid positive number',
|
invalidAmount: 'the supplied amount should be a valid positive number',
|
||||||
invalidDecimals: 'the supplied amount exceeds the allowed decimals',
|
invalidDecimals: 'the supplied amount exceeds the allowed decimals',
|
||||||
largeAmount: 'the transaction total is higher than the available balance'
|
largeAmount: 'the transaction total is higher than the available balance',
|
||||||
|
gasException: 'the transaction will throw an exception with the current values',
|
||||||
|
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ERRORS;
|
export default ERRORS;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
/* You should have received a copy of the GNU General Public License
|
/* You should have received a copy of the GNU General Public License
|
||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
line-height: 1.618em;
|
line-height: 1.618em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -151,3 +152,13 @@
|
|||||||
.gasPriceDesc {
|
.gasPriceDesc {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
background: #f80;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 0.75em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||||
|
|
||||||
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
|
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
|
||||||
|
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants';
|
||||||
|
|
||||||
import Details from './Details';
|
import Details from './Details';
|
||||||
import Extras from './Extras';
|
import Extras from './Extras';
|
||||||
@ -30,8 +33,6 @@ import styles from './transfer.css';
|
|||||||
|
|
||||||
import { ERROR_CODES } from '../../api/transport/error';
|
import { ERROR_CODES } from '../../api/transport/error';
|
||||||
|
|
||||||
const DEFAULT_GAS = '21000';
|
|
||||||
const DEFAULT_GASPRICE = '20000000000';
|
|
||||||
const TITLES = {
|
const TITLES = {
|
||||||
transfer: 'transfer details',
|
transfer: 'transfer details',
|
||||||
sending: 'sending',
|
sending: 'sending',
|
||||||
@ -42,7 +43,7 @@ const TITLES = {
|
|||||||
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||||
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
|
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete];
|
||||||
|
|
||||||
export default class Transfer extends Component {
|
class Transfer extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired,
|
api: PropTypes.object.isRequired,
|
||||||
store: PropTypes.object.isRequired
|
store: PropTypes.object.isRequired
|
||||||
@ -52,6 +53,7 @@ export default class Transfer extends Component {
|
|||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
balances: PropTypes.object,
|
balances: PropTypes.object,
|
||||||
|
gasLimit: PropTypes.object.isRequired,
|
||||||
images: PropTypes.object.isRequired,
|
images: PropTypes.object.isRequired,
|
||||||
onClose: PropTypes.func
|
onClose: PropTypes.func
|
||||||
}
|
}
|
||||||
@ -64,6 +66,7 @@ export default class Transfer extends Component {
|
|||||||
gas: DEFAULT_GAS,
|
gas: DEFAULT_GAS,
|
||||||
gasEst: '0',
|
gasEst: '0',
|
||||||
gasError: null,
|
gasError: null,
|
||||||
|
gasLimitError: null,
|
||||||
gasPrice: DEFAULT_GASPRICE,
|
gasPrice: DEFAULT_GASPRICE,
|
||||||
gasPriceHistogram: {},
|
gasPriceHistogram: {},
|
||||||
gasPriceError: null,
|
gasPriceError: null,
|
||||||
@ -103,6 +106,7 @@ export default class Transfer extends Component {
|
|||||||
visible
|
visible
|
||||||
scroll
|
scroll
|
||||||
>
|
>
|
||||||
|
{ this.renderWarning() }
|
||||||
{ this.renderPage() }
|
{ this.renderPage() }
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
@ -264,6 +268,20 @@ export default class Transfer extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderWarning () {
|
||||||
|
const { gasLimitError } = this.state;
|
||||||
|
|
||||||
|
if (!gasLimitError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.warning }>
|
||||||
|
{ gasLimitError }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
isValid () {
|
isValid () {
|
||||||
const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError;
|
const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError;
|
||||||
const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError;
|
const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError;
|
||||||
@ -519,6 +537,7 @@ export default class Transfer extends Component {
|
|||||||
|
|
||||||
return token.contract.instance.transfer
|
return token.contract.instance.transfer
|
||||||
.estimateGas({
|
.estimateGas({
|
||||||
|
gas: MAX_GAS_ESTIMATION,
|
||||||
from: account.address,
|
from: account.address,
|
||||||
to: token.address
|
to: token.address
|
||||||
}, [
|
}, [
|
||||||
@ -532,6 +551,7 @@ export default class Transfer extends Component {
|
|||||||
const { account } = this.props;
|
const { account } = this.props;
|
||||||
const { data, recipient, value } = this.state;
|
const { data, recipient, value } = this.state;
|
||||||
const options = {
|
const options = {
|
||||||
|
gas: MAX_GAS_ESTIMATION,
|
||||||
from: account.address,
|
from: account.address,
|
||||||
to: recipient,
|
to: recipient,
|
||||||
value: api.util.toWei(value || 0)
|
value: api.util.toWei(value || 0)
|
||||||
@ -552,19 +572,29 @@ export default class Transfer extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { gasLimit } = this.props;
|
||||||
|
|
||||||
(this.state.isEth
|
(this.state.isEth
|
||||||
? this._estimateGasEth()
|
? this._estimateGasEth()
|
||||||
: this._estimateGasToken()
|
: this._estimateGasToken()
|
||||||
).then((_value) => {
|
).then((gasEst) => {
|
||||||
let gas = _value;
|
let gas = gasEst;
|
||||||
|
let gasLimitError = null;
|
||||||
|
|
||||||
if (gas.gt(DEFAULT_GAS)) {
|
if (gas.gt(DEFAULT_GAS)) {
|
||||||
gas = gas.mul(1.2);
|
gas = gas.mul(1.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gas.gte(MAX_GAS_ESTIMATION)) {
|
||||||
|
gasLimitError = ERRORS.gasException;
|
||||||
|
} else if (gas.gt(gasLimit)) {
|
||||||
|
gasLimitError = ERRORS.gasBlockLimit;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
gas: gas.toFixed(0),
|
gas: gas.toFixed(0),
|
||||||
gasEst: _value.toFormat()
|
gasEst: gasEst.toFormat(),
|
||||||
|
gasLimitError
|
||||||
}, this.recalculate);
|
}, this.recalculate);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -649,3 +679,18 @@ export default class Transfer extends Component {
|
|||||||
store.dispatch({ type: 'newError', error });
|
store.dispatch({ type: 'newError', error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
const { gasLimit } = state.nodeStatus;
|
||||||
|
|
||||||
|
return { gasLimit };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return bindActionCreators({}, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Transfer);
|
||||||
|
@ -31,13 +31,23 @@ export default class Balances {
|
|||||||
constructor (store, api) {
|
constructor (store, api) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._store = store;
|
this._store = store;
|
||||||
|
|
||||||
|
this._tokens = {};
|
||||||
|
this._images = {};
|
||||||
|
|
||||||
this._accountsInfo = null;
|
this._accountsInfo = null;
|
||||||
this._tokens = [];
|
this._tokenreg = null;
|
||||||
|
this._fetchingTokens = false;
|
||||||
|
this._fetchedTokens = false;
|
||||||
|
|
||||||
|
this._tokenregSubId = null;
|
||||||
|
this._tokenregMetaSubId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
start () {
|
||||||
this._subscribeBlockNumber();
|
this._subscribeBlockNumber();
|
||||||
this._subscribeAccountsInfo();
|
this._subscribeAccountsInfo();
|
||||||
|
this._retrieveTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribeAccountsInfo () {
|
_subscribeAccountsInfo () {
|
||||||
@ -48,10 +58,7 @@ export default class Balances {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._accountsInfo = accountsInfo;
|
this._accountsInfo = accountsInfo;
|
||||||
this._retrieveBalances();
|
this._retrieveTokens();
|
||||||
})
|
|
||||||
.then((subscriptionId) => {
|
|
||||||
console.log('_subscribeAccountsInfo', 'subscriptionId', subscriptionId);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('_subscribeAccountsInfo', error);
|
console.warn('_subscribeAccountsInfo', error);
|
||||||
@ -62,21 +69,22 @@ export default class Balances {
|
|||||||
this._api
|
this._api
|
||||||
.subscribe('eth_blockNumber', (error) => {
|
.subscribe('eth_blockNumber', (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return;
|
return console.warn('_subscribeBlockNumber', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._retrieveTokens();
|
this._retrieveTokens();
|
||||||
})
|
})
|
||||||
.then((subscriptionId) => {
|
|
||||||
console.log('_subscribeBlockNumber', 'subscriptionId', subscriptionId);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('_subscribeBlockNumber', error);
|
console.warn('_subscribeBlockNumber', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_retrieveTokens () {
|
getTokenRegistry () {
|
||||||
this._api.parity
|
if (this._tokenreg) {
|
||||||
|
return Promise.resolve(this._tokenreg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._api.parity
|
||||||
.registryAddress()
|
.registryAddress()
|
||||||
.then((registryAddress) => {
|
.then((registryAddress) => {
|
||||||
const registry = this._api.newContract(abis.registry, registryAddress);
|
const registry = this._api.newContract(abis.registry, registryAddress);
|
||||||
@ -85,60 +93,49 @@ export default class Balances {
|
|||||||
})
|
})
|
||||||
.then((tokenregAddress) => {
|
.then((tokenregAddress) => {
|
||||||
const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress);
|
const tokenreg = this._api.newContract(abis.tokenreg, tokenregAddress);
|
||||||
|
this._tokenreg = tokenreg;
|
||||||
|
this.attachToTokens();
|
||||||
|
|
||||||
|
return tokenreg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_retrieveTokens () {
|
||||||
|
if (this._fetchingTokens) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._fetchedTokens) {
|
||||||
|
return this._retrieveBalances();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._fetchingTokens = true;
|
||||||
|
this._fetchedTokens = false;
|
||||||
|
|
||||||
|
this
|
||||||
|
.getTokenRegistry()
|
||||||
|
.then((tokenreg) => {
|
||||||
return tokenreg.instance.tokenCount
|
return tokenreg.instance.tokenCount
|
||||||
.call()
|
.call()
|
||||||
.then((numTokens) => {
|
.then((numTokens) => {
|
||||||
const promisesTokens = [];
|
const promises = [];
|
||||||
const promisesImages = [];
|
|
||||||
|
|
||||||
while (promisesTokens.length < numTokens.toNumber()) {
|
for (let i = 0; i < numTokens.toNumber(); i++) {
|
||||||
const index = promisesTokens.length;
|
promises.push(this.fetchTokenInfo(tokenreg, i));
|
||||||
|
|
||||||
promisesTokens.push(tokenreg.instance.token.call({}, [index]));
|
|
||||||
promisesImages.push(tokenreg.instance.meta.call({}, [index, 'IMG']));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all(promises);
|
||||||
Promise.all(promisesTokens),
|
|
||||||
Promise.all(promisesImages)
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(([_tokens, images]) => {
|
.then(() => {
|
||||||
const tokens = {};
|
this._fetchingTokens = false;
|
||||||
this._tokens = _tokens
|
this._fetchedTokens = true;
|
||||||
.map((_token, index) => {
|
|
||||||
const [address, tag, format, name] = _token;
|
|
||||||
|
|
||||||
const token = {
|
this._store.dispatch(getTokens(this._tokens));
|
||||||
address,
|
|
||||||
name,
|
|
||||||
tag,
|
|
||||||
format: format.toString(),
|
|
||||||
contract: this._api.newContract(abis.eip20, address)
|
|
||||||
};
|
|
||||||
tokens[address] = token;
|
|
||||||
this._store.dispatch(setAddressImage(address, images[index]));
|
|
||||||
|
|
||||||
return token;
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
if (a.tag < b.tag) {
|
|
||||||
return -1;
|
|
||||||
} else if (a.tag > b.tag) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._store.dispatch(getTokens(tokens));
|
|
||||||
this._retrieveBalances();
|
this._retrieveBalances();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('_retrieveTokens', error);
|
console.warn('balances::_retrieveTokens', error);
|
||||||
this._retrieveBalances();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,48 +144,20 @@ export default class Balances {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addresses = Object.keys(this._accountsInfo);
|
const addresses = Object
|
||||||
|
.keys(this._accountsInfo)
|
||||||
|
.filter((address) => {
|
||||||
|
const account = this._accountsInfo[address];
|
||||||
|
return !account.meta || !account.meta.deleted;
|
||||||
|
});
|
||||||
|
|
||||||
this._balances = {};
|
this._balances = {};
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all(
|
.all(addresses.map((a) => this.fetchAccountBalance(a)))
|
||||||
addresses.map((address) => Promise.all([
|
.then((balances) => {
|
||||||
this._api.eth.getBalance(address),
|
addresses.forEach((a, idx) => {
|
||||||
this._api.eth.getTransactionCount(address)
|
this._balances[a] = balances[idx];
|
||||||
]))
|
|
||||||
)
|
|
||||||
.then((balanceTxCount) => {
|
|
||||||
return Promise.all(
|
|
||||||
balanceTxCount.map(([value, txCount], idx) => {
|
|
||||||
const address = addresses[idx];
|
|
||||||
|
|
||||||
this._balances[address] = {
|
|
||||||
txCount,
|
|
||||||
tokens: [{
|
|
||||||
token: ETH,
|
|
||||||
value
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
this._tokens.map((token) => {
|
|
||||||
return token.contract.instance.balanceOf.call({}, [address]);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then((tokenBalances) => {
|
|
||||||
addresses.forEach((address, idx) => {
|
|
||||||
const balanceOf = tokenBalances[idx];
|
|
||||||
const balance = this._balances[address];
|
|
||||||
|
|
||||||
this._tokens.forEach((token, tidx) => {
|
|
||||||
balance.tokens.push({
|
|
||||||
token,
|
|
||||||
value: balanceOf[tidx]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._store.dispatch(getBalances(this._balances));
|
this._store.dispatch(getBalances(this._balances));
|
||||||
@ -197,4 +166,161 @@ export default class Balances {
|
|||||||
console.warn('_retrieveBalances', error);
|
console.warn('_retrieveBalances', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachToTokens () {
|
||||||
|
this.attachToTokenMetaChange();
|
||||||
|
this.attachToNewToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachToNewToken () {
|
||||||
|
if (this._tokenregSubId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tokenreg
|
||||||
|
.instance
|
||||||
|
.Registered
|
||||||
|
.subscribe({
|
||||||
|
fromBlock: 0,
|
||||||
|
toBlock: 'latest',
|
||||||
|
skipInitFetch: true
|
||||||
|
}, (error, logs) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = logs.map((log) => {
|
||||||
|
const id = log.params.id.value.toNumber();
|
||||||
|
return this.fetchTokenInfo(this._tokenreg, id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.then((tokenregSubId) => {
|
||||||
|
this._tokenregSubId = tokenregSubId;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('balances::attachToNewToken', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
attachToTokenMetaChange () {
|
||||||
|
if (this._tokenregMetaSubId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tokenreg
|
||||||
|
.instance
|
||||||
|
.MetaChanged
|
||||||
|
.subscribe({
|
||||||
|
fromBlock: 0,
|
||||||
|
toBlock: 'latest',
|
||||||
|
topics: [ null, this._api.util.asciiToHex('IMG') ],
|
||||||
|
skipInitFetch: true
|
||||||
|
}, (error, logs) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error('balances::attachToTokenMetaChange', 'failed to attach to tokenreg MetaChanged', error.toString(), error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case multiple logs for same token
|
||||||
|
// in one block. Take the last value.
|
||||||
|
const tokens = logs
|
||||||
|
.filter((log) => log.type === 'mined')
|
||||||
|
.reduce((_tokens, log) => {
|
||||||
|
const id = log.params.id.value.toNumber();
|
||||||
|
const image = log.params.value.value;
|
||||||
|
|
||||||
|
const token = Object.values(this._tokens).find((c) => c.id === id);
|
||||||
|
const { address } = token;
|
||||||
|
|
||||||
|
_tokens[address] = { address, id, image };
|
||||||
|
return _tokens;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
Object
|
||||||
|
.values(tokens)
|
||||||
|
.forEach((token) => {
|
||||||
|
const { address, image } = token;
|
||||||
|
|
||||||
|
if (this._images[address] !== image.toString()) {
|
||||||
|
this._store.dispatch(setAddressImage(address, image));
|
||||||
|
this._images[address] = image.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((tokenregMetaSubId) => {
|
||||||
|
this._tokenregMetaSubId = tokenregMetaSubId;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('balances::attachToTokenMetaChange', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTokenInfo (tokenreg, tokenId) {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
tokenreg.instance.token.call({}, [tokenId]),
|
||||||
|
tokenreg.instance.meta.call({}, [tokenId, 'IMG'])
|
||||||
|
])
|
||||||
|
.then(([ tokenData, image ]) => {
|
||||||
|
const [ address, tag, format, name ] = tokenData;
|
||||||
|
const contract = this._api.newContract(abis.eip20, address);
|
||||||
|
|
||||||
|
if (this._images[address] !== image.toString()) {
|
||||||
|
this._store.dispatch(setAddressImage(address, image));
|
||||||
|
this._images[address] = image.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = {
|
||||||
|
format: format.toString(),
|
||||||
|
id: tokenId,
|
||||||
|
|
||||||
|
address,
|
||||||
|
tag,
|
||||||
|
name,
|
||||||
|
contract
|
||||||
|
};
|
||||||
|
|
||||||
|
this._tokens[address] = token;
|
||||||
|
|
||||||
|
return token;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('balances::fetchTokenInfo', `couldn't fetch token #${tokenId}`, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO?: txCount is only shown on an address page, so we
|
||||||
|
* might not need to fetch it for each address for each block,
|
||||||
|
* but only for one address when the user is on the account
|
||||||
|
* view.
|
||||||
|
*/
|
||||||
|
fetchAccountBalance (address) {
|
||||||
|
const _tokens = Object.values(this._tokens);
|
||||||
|
const tokensPromises = _tokens
|
||||||
|
.map((token) => {
|
||||||
|
return token.contract.instance.balanceOf.call({}, [ address ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._api.eth.getTransactionCount(address),
|
||||||
|
this._api.eth.getBalance(address)
|
||||||
|
].concat(tokensPromises))
|
||||||
|
.then(([ txCount, ethBalance, ...tokensBalance ]) => {
|
||||||
|
const tokens = []
|
||||||
|
.concat(
|
||||||
|
{ token: ETH, value: ethBalance },
|
||||||
|
_tokens
|
||||||
|
.map((token, index) => ({
|
||||||
|
token,
|
||||||
|
value: tokensBalance[index]
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const balance = { txCount, tokens };
|
||||||
|
return balance;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,26 +16,17 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
|
|
||||||
class ParityBackground extends Component {
|
class ParityBackground extends Component {
|
||||||
static contextTypes = {
|
|
||||||
muiTheme: PropTypes.object.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
style: PropTypes.object.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
gradient: PropTypes.string,
|
|
||||||
seed: PropTypes.any,
|
|
||||||
settings: PropTypes.object.isRequired,
|
|
||||||
onClick: PropTypes.func
|
onClick: PropTypes.func
|
||||||
}
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { muiTheme } = this.context;
|
const { children, className, style, onClick } = this.props;
|
||||||
const { children, className, gradient, seed, settings, onClick } = this.props;
|
|
||||||
const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -48,17 +39,29 @@ class ParityBackground extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (_, initProps) {
|
||||||
const { settings } = state;
|
const { gradient, seed, muiTheme } = initProps;
|
||||||
|
|
||||||
return { settings };
|
let _seed = seed;
|
||||||
}
|
let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) };
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
return (state, props) => {
|
||||||
return bindActionCreators({}, dispatch);
|
const { backgroundSeed } = state.settings;
|
||||||
|
const { seed } = props;
|
||||||
|
|
||||||
|
const newSeed = seed || backgroundSeed;
|
||||||
|
|
||||||
|
if (newSeed === _seed) {
|
||||||
|
return _props;
|
||||||
|
}
|
||||||
|
|
||||||
|
_seed = newSeed;
|
||||||
|
_props = { style: muiTheme.parity.getBackgroundStyle(gradient, newSeed) };
|
||||||
|
|
||||||
|
return _props;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps
|
||||||
mapDispatchToProps
|
|
||||||
)(ParityBackground);
|
)(ParityBackground);
|
||||||
|
26
js/src/util/constants.js
Normal file
26
js/src/util/constants.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (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/>.
|
||||||
|
|
||||||
|
const DEFAULT_GAS = '21000';
|
||||||
|
const DEFAULT_GASPRICE = '20000000000';
|
||||||
|
|
||||||
|
const MAX_GAS_ESTIMATION = '50000000';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DEFAULT_GAS,
|
||||||
|
DEFAULT_GASPRICE,
|
||||||
|
MAX_GAS_ESTIMATION
|
||||||
|
};
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
|
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
|
||||||
|
|
||||||
@ -30,7 +31,6 @@ export default class Summary extends Component {
|
|||||||
link: PropTypes.string,
|
link: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
noLink: PropTypes.bool,
|
noLink: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
|
||||||
handleAddSearchToken: PropTypes.func
|
handleAddSearchToken: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,8 +42,42 @@ export default class Summary extends Component {
|
|||||||
name: 'Unnamed'
|
name: 'Unnamed'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate (nextProps) {
|
||||||
|
const prev = {
|
||||||
|
link: this.props.link, name: this.props.name,
|
||||||
|
noLink: this.props.noLink,
|
||||||
|
meta: this.props.account.meta, address: this.props.account.address
|
||||||
|
};
|
||||||
|
|
||||||
|
const next = {
|
||||||
|
link: nextProps.link, name: nextProps.name,
|
||||||
|
noLink: nextProps.noLink,
|
||||||
|
meta: nextProps.account.meta, address: nextProps.account.address
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isEqual(next, prev)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevTokens = this.props.balance.tokens || [];
|
||||||
|
const nextTokens = nextProps.balance.tokens || [];
|
||||||
|
|
||||||
|
if (prevTokens.length !== nextTokens.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevValues = prevTokens.map((t) => t.value.toNumber());
|
||||||
|
const nextValues = nextTokens.map((t) => t.value.toNumber());
|
||||||
|
|
||||||
|
if (!isEqual(prevValues, nextValues)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, children, handleAddSearchToken } = this.props;
|
const { account, handleAddSearchToken } = this.props;
|
||||||
const { tags } = account.meta;
|
const { tags } = account.meta;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@ -71,7 +105,6 @@ export default class Summary extends Component {
|
|||||||
byline={ addressComponent } />
|
byline={ addressComponent } />
|
||||||
|
|
||||||
{ this.renderBalance() }
|
{ this.renderBalance() }
|
||||||
{ children }
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -30,3 +30,25 @@
|
|||||||
right: 1em;
|
right: 1em;
|
||||||
top: 4em;
|
top: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadings {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
flex: 0 1 50%;
|
||||||
|
width: 50%;
|
||||||
|
height: 150px;
|
||||||
|
display: flex;
|
||||||
|
padding: 0.25em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,33 +42,63 @@ class Accounts extends Component {
|
|||||||
newDialog: false,
|
newDialog: false,
|
||||||
sortOrder: '',
|
sortOrder: '',
|
||||||
searchValues: [],
|
searchValues: [],
|
||||||
searchTokens: []
|
searchTokens: [],
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.setState({ show: true });
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, hasAccounts, balances } = this.props;
|
|
||||||
const { searchValues, sortOrder } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.accounts }>
|
<div className={ styles.accounts }>
|
||||||
{ this.renderNewDialog() }
|
{ this.renderNewDialog() }
|
||||||
{ this.renderActionbar() }
|
{ this.renderActionbar() }
|
||||||
<Page>
|
|
||||||
<List
|
{ this.state.show ? this.renderAccounts() : this.renderLoading() }
|
||||||
search={ searchValues }
|
|
||||||
accounts={ accounts }
|
|
||||||
balances={ balances }
|
|
||||||
empty={ !hasAccounts }
|
|
||||||
order={ sortOrder }
|
|
||||||
handleAddSearchToken={ this.onAddSearchToken } />
|
|
||||||
<Tooltip
|
|
||||||
className={ styles.accountTooltip }
|
|
||||||
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
|
|
||||||
</Page>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderLoading () {
|
||||||
|
const { accounts } = this.props;
|
||||||
|
|
||||||
|
const loadings = ((accounts && Object.keys(accounts)) || []).map((_, idx) => (
|
||||||
|
<div key={ idx } className={ styles.loading }>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.loadings }>
|
||||||
|
{ loadings }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccounts () {
|
||||||
|
const { accounts, hasAccounts, balances } = this.props;
|
||||||
|
const { searchValues, sortOrder } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<List
|
||||||
|
search={ searchValues }
|
||||||
|
accounts={ accounts }
|
||||||
|
balances={ balances }
|
||||||
|
empty={ !hasAccounts }
|
||||||
|
order={ sortOrder }
|
||||||
|
handleAddSearchToken={ this.onAddSearchToken } />
|
||||||
|
<Tooltip
|
||||||
|
className={ styles.accountTooltip }
|
||||||
|
text='your accounts are visible for easy access, allowing you to edit the meta information, make transfers, view transactions and fund the account' />
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderSearchButton () {
|
renderSearchButton () {
|
||||||
const onChange = (searchTokens, searchValues) => {
|
const onChange = (searchTokens, searchValues) => {
|
||||||
this.setState({ searchTokens, searchValues });
|
this.setState({ searchTokens, searchValues });
|
||||||
|
@ -22,6 +22,10 @@ import { Errors, ParityBackground, Tooltips } from '../../../ui';
|
|||||||
import styles from '../application.css';
|
import styles from '../application.css';
|
||||||
|
|
||||||
export default class Container extends Component {
|
export default class Container extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
muiTheme: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
showFirstRun: PropTypes.bool,
|
showFirstRun: PropTypes.bool,
|
||||||
@ -30,9 +34,10 @@ export default class Container extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, showFirstRun, onCloseFirstRun } = this.props;
|
const { children, showFirstRun, onCloseFirstRun } = this.props;
|
||||||
|
const { muiTheme } = this.context;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParityBackground className={ styles.container }>
|
<ParityBackground className={ styles.container } muiTheme={ muiTheme }>
|
||||||
<FirstRun
|
<FirstRun
|
||||||
visible={ showFirstRun }
|
visible={ showFirstRun }
|
||||||
onClose={ onCloseFirstRun } />
|
onClose={ onCloseFirstRun } />
|
||||||
|
@ -23,6 +23,11 @@
|
|||||||
.tabs {
|
.tabs {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs button,
|
.tabs button,
|
||||||
@ -38,6 +43,7 @@
|
|||||||
|
|
||||||
button.tabactive,
|
button.tabactive,
|
||||||
button.tabactive:hover {
|
button.tabactive:hover {
|
||||||
|
color: white !important;
|
||||||
background: rgba(0, 0, 0, 0.25) !important;
|
background: rgba(0, 0, 0, 0.25) !important;
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||||
import { Tabs, Tab } from 'material-ui/Tabs';
|
import { Tab as MUITab } from 'material-ui/Tabs';
|
||||||
|
|
||||||
import { Badge, Tooltip } from '../../../ui';
|
import { Badge, Tooltip } from '../../../ui';
|
||||||
|
|
||||||
@ -33,20 +33,138 @@ const TABMAP = {
|
|||||||
deploy: 'contract'
|
deploy: 'contract'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Tab extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
active: PropTypes.bool,
|
||||||
|
view: PropTypes.object,
|
||||||
|
children: PropTypes.node,
|
||||||
|
pendings: PropTypes.number,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate (nextProps) {
|
||||||
|
return nextProps.active !== this.props.active ||
|
||||||
|
(nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { active, view, children } = this.props;
|
||||||
|
|
||||||
|
const label = this.getLabel(view);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MUITab
|
||||||
|
className={ active ? styles.tabactive : '' }
|
||||||
|
selected={ active }
|
||||||
|
icon={ view.icon }
|
||||||
|
label={ label }
|
||||||
|
onClick={ this.handleClick }
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</MUITab>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLabel (view) {
|
||||||
|
const { label } = view;
|
||||||
|
|
||||||
|
if (view.id === 'signer') {
|
||||||
|
return this.renderSignerLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view.id === 'status') {
|
||||||
|
return this.renderStatusLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLabel (name, bubble) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.label }>
|
||||||
|
{ name }
|
||||||
|
{ bubble }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSignerLabel (label) {
|
||||||
|
const { pendings } = this.props;
|
||||||
|
|
||||||
|
if (pendings) {
|
||||||
|
const bubble = (
|
||||||
|
<Badge
|
||||||
|
color='red'
|
||||||
|
className={ styles.labelBubble }
|
||||||
|
value={ pendings } />
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.renderLabel(label, bubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStatusLabel (label) {
|
||||||
|
// const { isTest, netChain } = this.props;
|
||||||
|
// const bubble = (
|
||||||
|
// <Badge
|
||||||
|
// color={ isTest ? 'red' : 'default' }
|
||||||
|
// className={ styles.labelBubble }
|
||||||
|
// value={ isTest ? 'TEST' : netChain } />
|
||||||
|
// );
|
||||||
|
|
||||||
|
return this.renderLabel(label, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
const { onChange, view } = this.props;
|
||||||
|
onChange(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TabBar extends Component {
|
class TabBar extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object.isRequired
|
router: PropTypes.object.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
views: PropTypes.array.isRequired,
|
||||||
|
hash: PropTypes.string.isRequired,
|
||||||
pending: PropTypes.array,
|
pending: PropTypes.array,
|
||||||
isTest: PropTypes.bool,
|
isTest: PropTypes.bool,
|
||||||
netChain: PropTypes.string,
|
netChain: PropTypes.string
|
||||||
settings: PropTypes.object.isRequired
|
};
|
||||||
}
|
|
||||||
|
static defaultProps = {
|
||||||
|
pending: []
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
activeRoute: '/accounts'
|
activeViewId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
setActiveView (props = this.props) {
|
||||||
|
const { hash, views } = props;
|
||||||
|
const view = views.find((view) => view.value === hash);
|
||||||
|
|
||||||
|
this.setState({ activeViewId: view.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.setActiveView();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.hash !== this.props.hash) {
|
||||||
|
this.setActiveView(nextProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate (nextProps, nextState) {
|
||||||
|
return (nextProps.hash !== this.props.hash) ||
|
||||||
|
(nextProps.pending.length !== this.props.pending.length) ||
|
||||||
|
(nextState.activeViewId !== this.state.activeViewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -81,100 +199,64 @@ class TabBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTabs () {
|
renderTabs () {
|
||||||
const { settings } = this.props;
|
const { views, pending } = this.props;
|
||||||
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
|
const { activeViewId } = this.state;
|
||||||
const hash = TABMAP[windowHash] || windowHash;
|
|
||||||
|
|
||||||
const items = Object.keys(settings.views)
|
const items = views
|
||||||
.filter((id) => settings.views[id].fixed || settings.views[id].active)
|
.map((view, index) => {
|
||||||
.map((id) => {
|
const body = (view.id === 'accounts')
|
||||||
const view = settings.views[id];
|
? (
|
||||||
let label = this.renderLabel(view.label);
|
<Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
|
||||||
let body = null;
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (id === 'accounts') {
|
const active = activeViewId === view.id;
|
||||||
body = (
|
|
||||||
<Tooltip className={ styles.tabbarTooltip } text='navigate between the different parts and views of the application, switching between an account view, token view and distributed application view' />
|
|
||||||
);
|
|
||||||
} else if (id === 'signer') {
|
|
||||||
label = this.renderSignerLabel(label);
|
|
||||||
} else if (id === 'status') {
|
|
||||||
label = this.renderStatusLabel(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
className={ hash === view.value ? styles.tabactive : '' }
|
active={ active }
|
||||||
value={ view.value }
|
view={ view }
|
||||||
icon={ view.icon }
|
onChange={ this.onChange }
|
||||||
key={ id }
|
key={ index }
|
||||||
label={ label }
|
pendings={ pending.length }
|
||||||
onActive={ this.onActivate(view.route) }>
|
>
|
||||||
{ body }
|
{ body }
|
||||||
</Tab>
|
</Tab>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<div
|
||||||
className={ styles.tabs }
|
className={ styles.tabs }
|
||||||
value={ hash }>
|
onChange={ this.onChange }>
|
||||||
{ items }
|
{ items }
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderLabel = (name, bubble) => {
|
|
||||||
return (
|
|
||||||
<div className={ styles.label }>
|
|
||||||
{ name }
|
|
||||||
{ bubble }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSignerLabel = (label) => {
|
onChange = (view) => {
|
||||||
const { pending } = this.props;
|
|
||||||
let bubble = null;
|
|
||||||
|
|
||||||
if (pending && pending.length) {
|
|
||||||
bubble = (
|
|
||||||
<Badge
|
|
||||||
color='red'
|
|
||||||
className={ styles.labelBubble }
|
|
||||||
value={ pending.length } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.renderLabel(label, bubble);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderStatusLabel = (label) => {
|
|
||||||
// const { isTest, netChain } = this.props;
|
|
||||||
// const bubble = (
|
|
||||||
// <Badge
|
|
||||||
// color={ isTest ? 'red' : 'default' }
|
|
||||||
// className={ styles.labelBubble }
|
|
||||||
// value={ isTest ? 'TEST' : netChain } />
|
|
||||||
// );
|
|
||||||
|
|
||||||
return this.renderLabel(label, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
onActivate = (activeRoute) => {
|
|
||||||
const { router } = this.context;
|
const { router } = this.context;
|
||||||
|
|
||||||
return (event) => {
|
router.push(view.route);
|
||||||
router.push(activeRoute);
|
this.setState({ activeViewId: view.id });
|
||||||
this.setState({ activeRoute });
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { settings } = state;
|
const { views } = state.settings;
|
||||||
|
|
||||||
return { settings };
|
const filteredViews = Object
|
||||||
|
.keys(views)
|
||||||
|
.filter((id) => views[id].fixed || views[id].active)
|
||||||
|
.map((id) => ({
|
||||||
|
...views[id],
|
||||||
|
id
|
||||||
|
}));
|
||||||
|
|
||||||
|
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
|
||||||
|
const hash = TABMAP[windowHash] || windowHash;
|
||||||
|
|
||||||
|
return { views: filteredViews, hash };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
"description": "Have a peak on internals of transaction queue of your node.",
|
"description": "Have a peak on internals of transaction queue of your node.",
|
||||||
"author": "Parity Team <admin@ethcore.io>",
|
"author": "Parity Team <admin@ethcore.io>",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"visible": true,
|
||||||
"secure": true
|
"secure": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -28,6 +28,10 @@ import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text
|
|||||||
import styles from './parityBar.css';
|
import styles from './parityBar.css';
|
||||||
|
|
||||||
class ParityBar extends Component {
|
class ParityBar extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
muiTheme: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
pending: PropTypes.array,
|
pending: PropTypes.array,
|
||||||
dapp: PropTypes.bool
|
dapp: PropTypes.bool
|
||||||
@ -62,6 +66,7 @@ class ParityBar extends Component {
|
|||||||
|
|
||||||
renderBar () {
|
renderBar () {
|
||||||
const { dapp } = this.props;
|
const { dapp } = this.props;
|
||||||
|
const { muiTheme } = this.context;
|
||||||
|
|
||||||
if (!dapp) {
|
if (!dapp) {
|
||||||
return null;
|
return null;
|
||||||
@ -75,7 +80,7 @@ class ParityBar extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.bar }>
|
<div className={ styles.bar }>
|
||||||
<ParityBackground className={ styles.corner }>
|
<ParityBackground className={ styles.corner } muiTheme={ muiTheme }>
|
||||||
<div className={ styles.cornercolor }>
|
<div className={ styles.cornercolor }>
|
||||||
<Link to='/apps'>
|
<Link to='/apps'>
|
||||||
<Button
|
<Button
|
||||||
@ -95,9 +100,11 @@ class ParityBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExpanded () {
|
renderExpanded () {
|
||||||
|
const { muiTheme } = this.context;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.overlay }>
|
<div className={ styles.overlay }>
|
||||||
<ParityBackground className={ styles.expanded }>
|
<ParityBackground className={ styles.expanded } muiTheme={ muiTheme }>
|
||||||
<div className={ styles.header }>
|
<div className={ styles.header }>
|
||||||
<div className={ styles.title }>
|
<div className={ styles.title }>
|
||||||
<ContainerTitle title='Parity Signer: Pending' />
|
<ContainerTitle title='Parity Signer: Pending' />
|
||||||
|
@ -81,6 +81,7 @@ class Background extends Component {
|
|||||||
renderBackgrounds () {
|
renderBackgrounds () {
|
||||||
const { settings } = this.props;
|
const { settings } = this.props;
|
||||||
const { seeds } = this.state;
|
const { seeds } = this.state;
|
||||||
|
const { muiTheme } = this.context;
|
||||||
|
|
||||||
return seeds.map((seed, index) => {
|
return seeds.map((seed, index) => {
|
||||||
return (
|
return (
|
||||||
@ -89,7 +90,9 @@ class Background extends Component {
|
|||||||
<ParityBackground
|
<ParityBackground
|
||||||
className={ settings.backgroundSeed === seed ? styles.seedactive : styles.seed }
|
className={ settings.backgroundSeed === seed ? styles.seedactive : styles.seed }
|
||||||
seed={ seed }
|
seed={ seed }
|
||||||
onClick={ this.onSelect(seed) } />
|
onClick={ this.onSelect(seed) }
|
||||||
|
muiTheme={ muiTheme }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -23,9 +23,10 @@ export const TEST_HTTP_URL = 'http://localhost:6688';
|
|||||||
export const TEST_WS_URL = 'ws://localhost:8866';
|
export const TEST_WS_URL = 'ws://localhost:8866';
|
||||||
|
|
||||||
export function mockHttp (requests) {
|
export function mockHttp (requests) {
|
||||||
|
nock.cleanAll();
|
||||||
let scope = nock(TEST_HTTP_URL);
|
let scope = nock(TEST_HTTP_URL);
|
||||||
|
|
||||||
requests.forEach((request) => {
|
requests.forEach((request, index) => {
|
||||||
scope = scope
|
scope = scope
|
||||||
.post('/')
|
.post('/')
|
||||||
.reply(request.code || 200, (uri, body) => {
|
.reply(request.code || 200, (uri, body) => {
|
||||||
|
@ -37,6 +37,7 @@ module.exports = {
|
|||||||
entry: {
|
entry: {
|
||||||
// dapps
|
// dapps
|
||||||
'basiccoin': ['./dapps/basiccoin.js'],
|
'basiccoin': ['./dapps/basiccoin.js'],
|
||||||
|
'dappreg': ['./dapps/dappreg.js'],
|
||||||
'githubhint': ['./dapps/githubhint.js'],
|
'githubhint': ['./dapps/githubhint.js'],
|
||||||
'registry': ['./dapps/registry.js'],
|
'registry': ['./dapps/registry.js'],
|
||||||
'signaturereg': ['./dapps/signaturereg.js'],
|
'signaturereg': ['./dapps/signaturereg.js'],
|
||||||
|
Loading…
Reference in New Issue
Block a user