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]]
|
||||
name = "parity-ui-precompiled"
|
||||
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 = [
|
||||
"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 best_block_header: Header = ::rlp::decode(&chain.best_block_header());
|
||||
transactions.into_iter()
|
||||
.filter(|tx| match self.engine.verify_transaction_basic(tx, &best_block_header) {
|
||||
Ok(()) => true,
|
||||
.map(|tx| {
|
||||
match self.engine.verify_transaction_basic(&tx, &best_block_header) {
|
||||
Err(e) => {
|
||||
debug!(target: "miner", "Rejected tx {:?} with invalid signature: {:?}", tx.hash(), e);
|
||||
false
|
||||
}
|
||||
}
|
||||
)
|
||||
.map(|tx| {
|
||||
let origin = accounts.as_ref().and_then(|accounts| {
|
||||
tx.sender().ok().and_then(|sender| match accounts.contains(&sender) {
|
||||
true => Some(TransactionOrigin::Local),
|
||||
false => None,
|
||||
})
|
||||
}).unwrap_or(default_origin);
|
||||
|
||||
match origin {
|
||||
TransactionOrigin::Local | TransactionOrigin::RetractedBlock => {
|
||||
transaction_queue.add(tx, origin, &fetch_account, &gas_required)
|
||||
Err(e)
|
||||
},
|
||||
Ok(()) => {
|
||||
let origin = accounts.as_ref().and_then(|accounts| {
|
||||
tx.sender().ok().and_then(|sender| match accounts.contains(&sender) {
|
||||
true => Some(TransactionOrigin::Local),
|
||||
false => None,
|
||||
})
|
||||
}).unwrap_or(default_origin);
|
||||
|
||||
match origin {
|
||||
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()
|
||||
|
@ -35,6 +35,9 @@ pub mod kind;
|
||||
const MIN_MEM_LIMIT: usize = 16384;
|
||||
const MIN_QUEUE_LIMIT: usize = 512;
|
||||
|
||||
// maximum possible number of verification threads.
|
||||
const MAX_VERIFIERS: usize = 8;
|
||||
|
||||
/// Type alias for block queue convenience.
|
||||
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.
|
||||
pub struct Verifying<K: Kind> {
|
||||
hash: H256,
|
||||
@ -97,11 +131,12 @@ pub struct VerificationQueue<K: Kind> {
|
||||
engine: Arc<Engine>,
|
||||
more_to_verify: Arc<SCondvar>,
|
||||
verification: Arc<Verification<K>>,
|
||||
verifiers: Vec<JoinHandle<()>>,
|
||||
verifiers: Mutex<(Vec<VerifierHandle>, usize)>,
|
||||
deleting: Arc<AtomicBool>,
|
||||
ready_signal: Arc<QueueSignal>,
|
||||
empty: Arc<SCondvar>,
|
||||
processing: RwLock<HashSet<H256>>,
|
||||
ticks_since_adjustment: AtomicUsize,
|
||||
max_queue_size: usize,
|
||||
max_mem_use: usize,
|
||||
}
|
||||
@ -157,6 +192,7 @@ struct Verification<K: Kind> {
|
||||
more_to_verify: SMutex<()>,
|
||||
empty: SMutex<()>,
|
||||
sizes: Sizes,
|
||||
check_seal: bool,
|
||||
}
|
||||
|
||||
impl<K: Kind> VerificationQueue<K> {
|
||||
@ -173,7 +209,8 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
unverified: AtomicUsize::new(0),
|
||||
verifying: AtomicUsize::new(0),
|
||||
verified: AtomicUsize::new(0),
|
||||
}
|
||||
},
|
||||
check_seal: check_seal,
|
||||
});
|
||||
let more_to_verify = Arc::new(SCondvar::new());
|
||||
let deleting = Arc::new(AtomicBool::new(false));
|
||||
@ -185,44 +222,82 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
let empty = Arc::new(SCondvar::new());
|
||||
let panic_handler = PanicHandler::new_in_arc();
|
||||
|
||||
let mut verifiers: Vec<JoinHandle<()>> = Vec::new();
|
||||
let thread_count = max(::num_cpus::get(), 3) - 2;
|
||||
for i in 0..thread_count {
|
||||
let verification = verification.clone();
|
||||
let engine = engine.clone();
|
||||
let more_to_verify = more_to_verify.clone();
|
||||
let ready_signal = ready_signal.clone();
|
||||
let empty = empty.clone();
|
||||
let max_verifiers = min(::num_cpus::get(), MAX_VERIFIERS);
|
||||
let default_amount = max(::num_cpus::get(), 3) - 2;
|
||||
let mut verifiers = Vec::with_capacity(max_verifiers);
|
||||
|
||||
debug!(target: "verification", "Allocating {} verifiers, {} initially active", max_verifiers, default_amount);
|
||||
|
||||
for i in 0..max_verifiers {
|
||||
debug!(target: "verification", "Adding verification thread #{}", i);
|
||||
|
||||
let deleting = deleting.clone();
|
||||
let panic_handler = panic_handler.clone();
|
||||
verifiers.push(
|
||||
thread::Builder::new()
|
||||
.name(format!("Verifier #{}", i))
|
||||
.spawn(move || {
|
||||
panic_handler.catch_panic(move || {
|
||||
VerificationQueue::verify(verification, engine, more_to_verify, ready_signal, deleting, empty, check_seal)
|
||||
}).unwrap()
|
||||
})
|
||||
.expect("Error starting block verification thread")
|
||||
);
|
||||
let verification = verification.clone();
|
||||
let engine = engine.clone();
|
||||
let wait = more_to_verify.clone();
|
||||
let ready = ready_signal.clone();
|
||||
let empty = empty.clone();
|
||||
|
||||
// enable only the first few verifiers.
|
||||
let sleep = if i < default_amount {
|
||||
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 {
|
||||
engine: engine,
|
||||
panic_handler: panic_handler,
|
||||
ready_signal: ready_signal.clone(),
|
||||
more_to_verify: more_to_verify.clone(),
|
||||
verification: verification.clone(),
|
||||
verifiers: verifiers,
|
||||
deleting: deleting.clone(),
|
||||
ready_signal: ready_signal,
|
||||
more_to_verify: more_to_verify,
|
||||
verification: verification,
|
||||
verifiers: Mutex::new((verifiers, default_amount)),
|
||||
deleting: deleting,
|
||||
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_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 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();
|
||||
|
||||
@ -255,7 +330,7 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
};
|
||||
|
||||
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) => {
|
||||
let mut verifying = verification.verifying.lock();
|
||||
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 inserted_size = 0;
|
||||
|
||||
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
|
||||
assert!(verifying.pop_front().is_some());
|
||||
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) {
|
||||
{
|
||||
self.verification.unverified.lock().shrink_to_fit();
|
||||
// number of ticks to average queue stats over
|
||||
// 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.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();
|
||||
|
||||
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...");
|
||||
self.clear();
|
||||
self.deleting.store(true, AtomicOrdering::Release);
|
||||
self.more_to_verify.notify_all();
|
||||
for t in self.verifiers.drain(..) {
|
||||
t.join().unwrap();
|
||||
|
||||
let mut verifiers = self.verifiers.get_mut();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@ -611,4 +776,56 @@ mod tests {
|
||||
}
|
||||
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",
|
||||
"version": "0.2.61",
|
||||
"version": "0.2.67",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@ -102,6 +102,7 @@
|
||||
"postcss-nested": "^1.0.0",
|
||||
"postcss-simple-vars": "^3.0.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-addons-perf": "~15.3.2",
|
||||
"react-addons-test-utils": "~15.3.2",
|
||||
"react-copy-to-clipboard": "^4.2.3",
|
||||
"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/>.
|
||||
|
||||
export default function (rpc) {
|
||||
const subscriptions = [];
|
||||
let subscriptions = [];
|
||||
let pollStatusIntervalId = null;
|
||||
|
||||
function getCoins () {
|
||||
return rpc.get('getcoins');
|
||||
@ -45,6 +46,24 @@ export default function (rpc) {
|
||||
callback,
|
||||
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) {
|
||||
@ -81,13 +100,12 @@ export default function (rpc) {
|
||||
subscriptions.forEach(_getSubscriptionStatus);
|
||||
}
|
||||
|
||||
setInterval(_pollStatus, 2000);
|
||||
|
||||
return {
|
||||
getCoins,
|
||||
getMarketInfo,
|
||||
getStatus,
|
||||
shift,
|
||||
subscribe
|
||||
subscribe,
|
||||
unsubscribe
|
||||
};
|
||||
}
|
||||
|
@ -48,7 +48,11 @@ export default class Contract {
|
||||
this._instance[fn.signature] = fn;
|
||||
});
|
||||
|
||||
this._sendSubscriptionChanges();
|
||||
this._subscribedToPendings = false;
|
||||
this._pendingsSubscriptionId = null;
|
||||
|
||||
this._subscribedToBlock = false;
|
||||
this._blockSubscriptionId = null;
|
||||
}
|
||||
|
||||
get address () {
|
||||
@ -239,44 +243,71 @@ export default class Contract {
|
||||
return event;
|
||||
}
|
||||
|
||||
subscribe (eventName = null, options = {}, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let event = null;
|
||||
_findEvent (eventName = null) {
|
||||
const event = eventName
|
||||
? this._events.find((evt) => evt.name === eventName)
|
||||
: null;
|
||||
|
||||
if (eventName) {
|
||||
event = this._events.find((evt) => evt.name === eventName);
|
||||
if (eventName && !event) {
|
||||
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) {
|
||||
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 event;
|
||||
}
|
||||
|
||||
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) {
|
||||
const subscriptionId = nextSubscriptionId++;
|
||||
const options = Object.assign({}, _options, {
|
||||
address: this._address,
|
||||
topics: [event ? event.signature : null]
|
||||
});
|
||||
const { skipInitFetch } = _options;
|
||||
delete _options['skipInitFetch'];
|
||||
|
||||
return this._api.eth
|
||||
.newFilter(options)
|
||||
return this
|
||||
._createEthFilter(event, _options)
|
||||
.then((filterId) => {
|
||||
this._subscriptions[subscriptionId] = {
|
||||
options: _options,
|
||||
callback,
|
||||
filterId
|
||||
};
|
||||
|
||||
if (skipInitFetch) {
|
||||
this._subscribeToChanges();
|
||||
return subscriptionId;
|
||||
}
|
||||
|
||||
return this._api.eth
|
||||
.getFilterLogs(filterId)
|
||||
.then((logs) => {
|
||||
callback(null, this.parseEventLogs(logs));
|
||||
this._subscriptions[subscriptionId] = {
|
||||
options,
|
||||
callback,
|
||||
filterId
|
||||
};
|
||||
|
||||
this._subscribeToChanges();
|
||||
return subscriptionId;
|
||||
});
|
||||
});
|
||||
@ -285,19 +316,89 @@ export default class Contract {
|
||||
unsubscribe (subscriptionId) {
|
||||
return this._api.eth
|
||||
.uninstallFilter(this._subscriptions[subscriptionId].filterId)
|
||||
.then(() => {
|
||||
delete this._subscriptions[subscriptionId];
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('unsubscribe', error);
|
||||
})
|
||||
.then(() => {
|
||||
delete this._subscriptions[subscriptionId];
|
||||
this._unsubscribeFromChanges();
|
||||
});
|
||||
}
|
||||
|
||||
_sendSubscriptionChanges = () => {
|
||||
_subscribeToChanges = () => {
|
||||
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(
|
||||
subscriptions.map((subscription) => {
|
||||
return this._api.eth.getFilterChanges(subscription.filterId);
|
||||
@ -315,12 +416,9 @@ export default class Contract {
|
||||
console.error('_sendSubscriptionChanges', error);
|
||||
}
|
||||
});
|
||||
|
||||
timeout();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('_sendSubscriptionChanges', error);
|
||||
timeout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -437,6 +437,7 @@ describe('api/contract/Contract', () => {
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const logs = [{
|
||||
address: '0x22bff18ec62281850546a664bb63a5c06ac5f76c',
|
||||
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
||||
@ -450,6 +451,7 @@ describe('api/contract/Contract', () => {
|
||||
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
||||
transactionIndex: '0x0'
|
||||
}];
|
||||
|
||||
const parsed = [{
|
||||
address: '0x22bfF18ec62281850546a664bb63a5C06AC5F76C',
|
||||
blockHash: '0xa9280530a3b47bee2fc80f2862fd56502ae075350571d724d6442ea4c597347b',
|
||||
@ -466,11 +468,13 @@ describe('api/contract/Contract', () => {
|
||||
sender: { type: 'address', value: '0x63Cf90D3f0410092FC0fca41846f596223979195' }
|
||||
},
|
||||
topics: [
|
||||
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', '0x0000000000000000000000000000000000000000000000000001000000004fe0'
|
||||
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
|
||||
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
|
||||
],
|
||||
transactionHash: '0xca16f537d761d13e4e80953b754e2b15541f267d6cad9381f750af1bae1e4917',
|
||||
transactionIndex: new BigNumber(0)
|
||||
}];
|
||||
|
||||
let contract;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -496,18 +500,19 @@ describe('api/contract/Contract', () => {
|
||||
scope = mockHttp([
|
||||
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
||||
{ method: 'eth_getFilterLogs', reply: { result: logs } },
|
||||
{ method: 'eth_getFilterChanges', reply: { result: logs } },
|
||||
{ method: 'eth_newFilter', reply: { result: '0x123' } },
|
||||
{ method: 'eth_getFilterLogs', reply: { result: logs } }
|
||||
]);
|
||||
cbb = sinon.stub();
|
||||
cbe = sinon.stub();
|
||||
|
||||
return contract.subscribe('Message', {}, cbb);
|
||||
return contract.subscribe('Message', { toBlock: 'pending' }, cbb);
|
||||
});
|
||||
|
||||
it('sets the subscriptionId returned', () => {
|
||||
return contract
|
||||
.subscribe('Message', {}, cbe)
|
||||
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||
.then((subscriptionId) => {
|
||||
expect(subscriptionId).to.equal(1);
|
||||
});
|
||||
@ -515,7 +520,7 @@ describe('api/contract/Contract', () => {
|
||||
|
||||
it('creates a new filter and retrieves the logs on it', () => {
|
||||
return contract
|
||||
.subscribe('Message', {}, cbe)
|
||||
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||
.then((subscriptionId) => {
|
||||
expect(scope.isDone()).to.be.true;
|
||||
});
|
||||
@ -523,7 +528,7 @@ describe('api/contract/Contract', () => {
|
||||
|
||||
it('returns the logs to the callback', () => {
|
||||
return contract
|
||||
.subscribe('Message', {}, cbe)
|
||||
.subscribe('Message', { toBlock: 'pending' }, cbe)
|
||||
.then((subscriptionId) => {
|
||||
expect(cbe).to.have.been.calledWith(null, parsed);
|
||||
});
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { range } from 'lodash';
|
||||
|
||||
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
|
||||
|
||||
@ -50,14 +51,19 @@ export function inHash (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) {
|
||||
let topics = (_topics || [])
|
||||
.filter((topic) => topic)
|
||||
.map(inHex);
|
||||
.filter((topic) => topic === null || topic)
|
||||
.map((topic) => topic === null ? null : pad(topic, 32));
|
||||
|
||||
while (topics.length < 4) {
|
||||
topics.push(null);
|
||||
}
|
||||
// while (topics.length < 4) {
|
||||
// topics.push(null);
|
||||
// }
|
||||
|
||||
return topics;
|
||||
}
|
||||
|
@ -29,3 +29,7 @@ export function hex2Ascii (_hex) {
|
||||
|
||||
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 { decodeCallData, decodeMethodInput, methodToAbi } from './decode';
|
||||
import { bytesToHex, hex2Ascii } from './format';
|
||||
import { bytesToHex, hex2Ascii, asciiToHex } from './format';
|
||||
import { fromWei, toWei } from './wei';
|
||||
import { sha3 } from './sha3';
|
||||
import { isArray, isFunction, isHex, isInstanceOf, isString } from './types';
|
||||
@ -31,6 +31,7 @@ export default {
|
||||
isString,
|
||||
bytesToHex,
|
||||
hex2Ascii,
|
||||
asciiToHex,
|
||||
createIdentityImg,
|
||||
decodeCallData,
|
||||
decodeMethodInput,
|
||||
|
@ -19,17 +19,34 @@ export const checkIfVerified = (contract, account) => {
|
||||
};
|
||||
|
||||
export const checkIfRequested = (contract, account) => {
|
||||
let subId = null;
|
||||
let resolved = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
contract.subscribe('Requested', {
|
||||
fromBlock: 0, toBlock: 'pending'
|
||||
}, (err, logs) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const e = logs.find((l) => {
|
||||
return l.type === 'mined' && l.params.who && l.params.who.value === account;
|
||||
contract
|
||||
.subscribe('Requested', {
|
||||
fromBlock: 0, toBlock: 'pending'
|
||||
}, (err, logs) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
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();
|
||||
|
||||
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 parityUrl = process.env.PARITY_URL ||
|
||||
(
|
||||
|
@ -36,6 +36,7 @@ export default class DetailsStep extends Component {
|
||||
onFuncChange: PropTypes.func,
|
||||
values: PropTypes.array.isRequired,
|
||||
valuesError: PropTypes.array.isRequired,
|
||||
warning: PropTypes.string,
|
||||
onValueChange: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
@ -44,6 +45,7 @@ export default class DetailsStep extends Component {
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ this.renderWarning() }
|
||||
<AddressSelect
|
||||
label='from account'
|
||||
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) => {
|
||||
const { contract, onFuncChange } = this.props;
|
||||
|
||||
|
@ -33,3 +33,12 @@
|
||||
.txhash {
|
||||
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/>.
|
||||
|
||||
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 ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
|
||||
import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui';
|
||||
import { MAX_GAS_ESTIMATION } from '../../util/constants';
|
||||
import { validateAddress, validateUint } from '../../util/validation';
|
||||
|
||||
import DetailsStep from './DetailsStep';
|
||||
|
||||
import ERRORS from '../Transfer/errors';
|
||||
import { ERROR_CODES } from '../../api/transport/error';
|
||||
|
||||
export default class ExecuteContract extends Component {
|
||||
class ExecuteContract extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
@ -36,6 +40,7 @@ export default class ExecuteContract extends Component {
|
||||
fromAddress: PropTypes.string,
|
||||
accounts: PropTypes.object,
|
||||
contract: PropTypes.object,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onFromAddressChange: PropTypes.func.isRequired
|
||||
}
|
||||
@ -46,6 +51,8 @@ export default class ExecuteContract extends Component {
|
||||
fromAddressError: null,
|
||||
func: null,
|
||||
funcError: null,
|
||||
gas: null,
|
||||
gasLimitError: null,
|
||||
values: [],
|
||||
valuesError: [],
|
||||
step: 0,
|
||||
@ -64,6 +71,12 @@ export default class ExecuteContract extends Component {
|
||||
this.onFuncChange(null, functions[0]);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
if (newProps.fromAddress !== this.props.fromAddress) {
|
||||
this.estimateGas(newProps.fromAddress);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sending } = this.state;
|
||||
|
||||
@ -119,7 +132,7 @@ export default class ExecuteContract extends Component {
|
||||
|
||||
renderStep () {
|
||||
const { onFromAddressChange } = this.props;
|
||||
const { step, busyState, txhash, rejected } = this.state;
|
||||
const { step, busyState, gasLimitError, txhash, rejected } = this.state;
|
||||
|
||||
if (rejected) {
|
||||
return (
|
||||
@ -135,6 +148,7 @@ export default class ExecuteContract extends Component {
|
||||
<DetailsStep
|
||||
{ ...this.props }
|
||||
{ ...this.state }
|
||||
warning={ gasLimitError }
|
||||
onAmountChange={ this.onAmountChange }
|
||||
onFromAddressChange={ onFromAddressChange }
|
||||
onFuncChange={ this.onFuncChange }
|
||||
@ -156,7 +170,7 @@ export default class ExecuteContract extends Component {
|
||||
}
|
||||
|
||||
onAmountChange = (amount) => {
|
||||
this.setState({ amount });
|
||||
this.setState({ amount }, this.estimateGas);
|
||||
}
|
||||
|
||||
onFuncChange = (event, func) => {
|
||||
@ -182,7 +196,7 @@ export default class ExecuteContract extends Component {
|
||||
this.setState({
|
||||
func,
|
||||
values
|
||||
});
|
||||
}, this.estimateGas);
|
||||
}
|
||||
|
||||
onValueChange = (event, index, _value) => {
|
||||
@ -211,14 +225,55 @@ export default class ExecuteContract extends Component {
|
||||
this.setState({
|
||||
values: [].concat(values),
|
||||
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 = () => {
|
||||
const { api, store } = this.context;
|
||||
const { fromAddress } = this.props;
|
||||
const { amount, func, values } = this.state;
|
||||
const options = {
|
||||
gas: MAX_GAS_ESTIMATION,
|
||||
from: fromAddress,
|
||||
value: api.util.toWei(amount || 0)
|
||||
};
|
||||
@ -237,13 +292,13 @@ export default class ExecuteContract extends Component {
|
||||
|
||||
return api
|
||||
.pollMethod('parity_checkRequest', requestId)
|
||||
.catch((e) => {
|
||||
if (e.code === ERROR_CODES.REQUEST_REJECTED) {
|
||||
.catch((error) => {
|
||||
if (error.code === ERROR_CODES.REQUEST_REJECTED) {
|
||||
this.setState({ rejected: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
throw e;
|
||||
throw error;
|
||||
});
|
||||
})
|
||||
.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();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
unsubscribe () {
|
||||
// Unsubscribe from Shapeshit
|
||||
const { depositAddress } = this.state;
|
||||
shapeshift.unsubscribe(depositAddress);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error, stage } = this.state;
|
||||
|
||||
@ -205,6 +215,10 @@ export default class Shapeshift extends Component {
|
||||
console.log('onShift', result);
|
||||
const depositAddress = result.deposit;
|
||||
|
||||
if (this.state.depositAddress) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
shapeshift.subscribe(depositAddress, this.onExchangeInfo);
|
||||
this.setState({ depositAddress });
|
||||
})
|
||||
|
@ -19,7 +19,9 @@ const ERRORS = {
|
||||
invalidAddress: 'the supplied address is an invalid network address',
|
||||
invalidAmount: 'the supplied amount should be a valid positive number',
|
||||
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;
|
||||
|
@ -14,6 +14,7 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.info {
|
||||
line-height: 1.618em;
|
||||
width: 100%;
|
||||
@ -151,3 +152,13 @@
|
||||
.gasPriceDesc {
|
||||
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 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 ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
|
||||
|
||||
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 Extras from './Extras';
|
||||
@ -30,8 +33,6 @@ import styles from './transfer.css';
|
||||
|
||||
import { ERROR_CODES } from '../../api/transport/error';
|
||||
|
||||
const DEFAULT_GAS = '21000';
|
||||
const DEFAULT_GASPRICE = '20000000000';
|
||||
const TITLES = {
|
||||
transfer: 'transfer details',
|
||||
sending: 'sending',
|
||||
@ -42,7 +43,7 @@ const TITLES = {
|
||||
const STAGES_BASIC = [TITLES.transfer, 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 = {
|
||||
api: PropTypes.object.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
@ -52,6 +53,7 @@ export default class Transfer extends Component {
|
||||
account: PropTypes.object,
|
||||
balance: PropTypes.object,
|
||||
balances: PropTypes.object,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
images: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func
|
||||
}
|
||||
@ -64,6 +66,7 @@ export default class Transfer extends Component {
|
||||
gas: DEFAULT_GAS,
|
||||
gasEst: '0',
|
||||
gasError: null,
|
||||
gasLimitError: null,
|
||||
gasPrice: DEFAULT_GASPRICE,
|
||||
gasPriceHistogram: {},
|
||||
gasPriceError: null,
|
||||
@ -103,6 +106,7 @@ export default class Transfer extends Component {
|
||||
visible
|
||||
scroll
|
||||
>
|
||||
{ this.renderWarning() }
|
||||
{ this.renderPage() }
|
||||
</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 () {
|
||||
const detailsValid = !this.state.recipientError && !this.state.valueError && !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
|
||||
.estimateGas({
|
||||
gas: MAX_GAS_ESTIMATION,
|
||||
from: account.address,
|
||||
to: token.address
|
||||
}, [
|
||||
@ -532,6 +551,7 @@ export default class Transfer extends Component {
|
||||
const { account } = this.props;
|
||||
const { data, recipient, value } = this.state;
|
||||
const options = {
|
||||
gas: MAX_GAS_ESTIMATION,
|
||||
from: account.address,
|
||||
to: recipient,
|
||||
value: api.util.toWei(value || 0)
|
||||
@ -552,19 +572,29 @@ export default class Transfer extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const { gasLimit } = this.props;
|
||||
|
||||
(this.state.isEth
|
||||
? this._estimateGasEth()
|
||||
: this._estimateGasToken()
|
||||
).then((_value) => {
|
||||
let gas = _value;
|
||||
).then((gasEst) => {
|
||||
let gas = gasEst;
|
||||
let gasLimitError = null;
|
||||
|
||||
if (gas.gt(DEFAULT_GAS)) {
|
||||
gas = gas.mul(1.2);
|
||||
}
|
||||
|
||||
if (gas.gte(MAX_GAS_ESTIMATION)) {
|
||||
gasLimitError = ERRORS.gasException;
|
||||
} else if (gas.gt(gasLimit)) {
|
||||
gasLimitError = ERRORS.gasBlockLimit;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
gas: gas.toFixed(0),
|
||||
gasEst: _value.toFormat()
|
||||
gasEst: gasEst.toFormat(),
|
||||
gasLimitError
|
||||
}, this.recalculate);
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -649,3 +679,18 @@ export default class Transfer extends Component {
|
||||
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) {
|
||||
this._api = api;
|
||||
this._store = store;
|
||||
|
||||
this._tokens = {};
|
||||
this._images = {};
|
||||
|
||||
this._accountsInfo = null;
|
||||
this._tokens = [];
|
||||
this._tokenreg = null;
|
||||
this._fetchingTokens = false;
|
||||
this._fetchedTokens = false;
|
||||
|
||||
this._tokenregSubId = null;
|
||||
this._tokenregMetaSubId = null;
|
||||
}
|
||||
|
||||
start () {
|
||||
this._subscribeBlockNumber();
|
||||
this._subscribeAccountsInfo();
|
||||
this._retrieveTokens();
|
||||
}
|
||||
|
||||
_subscribeAccountsInfo () {
|
||||
@ -48,10 +58,7 @@ export default class Balances {
|
||||
}
|
||||
|
||||
this._accountsInfo = accountsInfo;
|
||||
this._retrieveBalances();
|
||||
})
|
||||
.then((subscriptionId) => {
|
||||
console.log('_subscribeAccountsInfo', 'subscriptionId', subscriptionId);
|
||||
this._retrieveTokens();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeAccountsInfo', error);
|
||||
@ -62,21 +69,22 @@ export default class Balances {
|
||||
this._api
|
||||
.subscribe('eth_blockNumber', (error) => {
|
||||
if (error) {
|
||||
return;
|
||||
return console.warn('_subscribeBlockNumber', error);
|
||||
}
|
||||
|
||||
this._retrieveTokens();
|
||||
})
|
||||
.then((subscriptionId) => {
|
||||
console.log('_subscribeBlockNumber', 'subscriptionId', subscriptionId);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_subscribeBlockNumber', error);
|
||||
});
|
||||
}
|
||||
|
||||
_retrieveTokens () {
|
||||
this._api.parity
|
||||
getTokenRegistry () {
|
||||
if (this._tokenreg) {
|
||||
return Promise.resolve(this._tokenreg);
|
||||
}
|
||||
|
||||
return this._api.parity
|
||||
.registryAddress()
|
||||
.then((registryAddress) => {
|
||||
const registry = this._api.newContract(abis.registry, registryAddress);
|
||||
@ -85,60 +93,49 @@ export default class Balances {
|
||||
})
|
||||
.then((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
|
||||
.call()
|
||||
.then((numTokens) => {
|
||||
const promisesTokens = [];
|
||||
const promisesImages = [];
|
||||
const promises = [];
|
||||
|
||||
while (promisesTokens.length < numTokens.toNumber()) {
|
||||
const index = promisesTokens.length;
|
||||
|
||||
promisesTokens.push(tokenreg.instance.token.call({}, [index]));
|
||||
promisesImages.push(tokenreg.instance.meta.call({}, [index, 'IMG']));
|
||||
for (let i = 0; i < numTokens.toNumber(); i++) {
|
||||
promises.push(this.fetchTokenInfo(tokenreg, i));
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
Promise.all(promisesTokens),
|
||||
Promise.all(promisesImages)
|
||||
]);
|
||||
return Promise.all(promises);
|
||||
});
|
||||
})
|
||||
.then(([_tokens, images]) => {
|
||||
const tokens = {};
|
||||
this._tokens = _tokens
|
||||
.map((_token, index) => {
|
||||
const [address, tag, format, name] = _token;
|
||||
.then(() => {
|
||||
this._fetchingTokens = false;
|
||||
this._fetchedTokens = true;
|
||||
|
||||
const token = {
|
||||
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._store.dispatch(getTokens(this._tokens));
|
||||
this._retrieveBalances();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('_retrieveTokens', error);
|
||||
this._retrieveBalances();
|
||||
console.warn('balances::_retrieveTokens', error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -147,48 +144,20 @@ export default class Balances {
|
||||
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 = {};
|
||||
|
||||
Promise
|
||||
.all(
|
||||
addresses.map((address) => Promise.all([
|
||||
this._api.eth.getBalance(address),
|
||||
this._api.eth.getTransactionCount(address)
|
||||
]))
|
||||
)
|
||||
.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]
|
||||
});
|
||||
});
|
||||
.all(addresses.map((a) => this.fetchAccountBalance(a)))
|
||||
.then((balances) => {
|
||||
addresses.forEach((a, idx) => {
|
||||
this._balances[a] = balances[idx];
|
||||
});
|
||||
|
||||
this._store.dispatch(getBalances(this._balances));
|
||||
@ -197,4 +166,161 @@ export default class Balances {
|
||||
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 { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
class ParityBackground extends Component {
|
||||
static contextTypes = {
|
||||
muiTheme: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
style: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
gradient: PropTypes.string,
|
||||
seed: PropTypes.any,
|
||||
settings: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
const { muiTheme } = this.context;
|
||||
const { children, className, gradient, seed, settings, onClick } = this.props;
|
||||
const style = muiTheme.parity.getBackgroundStyle(gradient, seed || settings.backgroundSeed);
|
||||
const { children, className, style, onClick } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -48,17 +39,29 @@ class ParityBackground extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { settings } = state;
|
||||
function mapStateToProps (_, initProps) {
|
||||
const { gradient, seed, muiTheme } = initProps;
|
||||
|
||||
return { settings };
|
||||
}
|
||||
let _seed = seed;
|
||||
let _props = { style: muiTheme.parity.getBackgroundStyle(gradient, seed) };
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
return (state, props) => {
|
||||
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(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps
|
||||
)(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 { Link } from 'react-router';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '../../../ui';
|
||||
|
||||
@ -30,7 +31,6 @@ export default class Summary extends Component {
|
||||
link: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
noLink: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
handleAddSearchToken: PropTypes.func
|
||||
};
|
||||
|
||||
@ -42,8 +42,42 @@ export default class Summary extends Component {
|
||||
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 () {
|
||||
const { account, children, handleAddSearchToken } = this.props;
|
||||
const { account, handleAddSearchToken } = this.props;
|
||||
const { tags } = account.meta;
|
||||
|
||||
if (!account) {
|
||||
@ -71,7 +105,6 @@ export default class Summary extends Component {
|
||||
byline={ addressComponent } />
|
||||
|
||||
{ this.renderBalance() }
|
||||
{ children }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -30,3 +30,25 @@
|
||||
right: 1em;
|
||||
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,
|
||||
sortOrder: '',
|
||||
searchValues: [],
|
||||
searchTokens: []
|
||||
searchTokens: [],
|
||||
show: false
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
window.setTimeout(() => {
|
||||
this.setState({ show: true });
|
||||
}, 100);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, hasAccounts, balances } = this.props;
|
||||
const { searchValues, sortOrder } = this.state;
|
||||
|
||||
return (
|
||||
<div className={ styles.accounts }>
|
||||
{ this.renderNewDialog() }
|
||||
{ this.renderActionbar() }
|
||||
<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>
|
||||
|
||||
{ this.state.show ? this.renderAccounts() : this.renderLoading() }
|
||||
</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 () {
|
||||
const onChange = (searchTokens, searchValues) => {
|
||||
this.setState({ searchTokens, searchValues });
|
||||
|
@ -22,6 +22,10 @@ import { Errors, ParityBackground, Tooltips } from '../../../ui';
|
||||
import styles from '../application.css';
|
||||
|
||||
export default class Container extends Component {
|
||||
static contextTypes = {
|
||||
muiTheme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
showFirstRun: PropTypes.bool,
|
||||
@ -30,9 +34,10 @@ export default class Container extends Component {
|
||||
|
||||
render () {
|
||||
const { children, showFirstRun, onCloseFirstRun } = this.props;
|
||||
const { muiTheme } = this.context;
|
||||
|
||||
return (
|
||||
<ParityBackground className={ styles.container }>
|
||||
<ParityBackground className={ styles.container } muiTheme={ muiTheme }>
|
||||
<FirstRun
|
||||
visible={ showFirstRun }
|
||||
onClose={ onCloseFirstRun } />
|
||||
|
@ -23,6 +23,11 @@
|
||||
.tabs {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
& > * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs button,
|
||||
@ -38,6 +43,7 @@
|
||||
|
||||
button.tabactive,
|
||||
button.tabactive:hover {
|
||||
color: white !important;
|
||||
background: rgba(0, 0, 0, 0.25) !important;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
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';
|
||||
|
||||
@ -33,20 +33,138 @@ const TABMAP = {
|
||||
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 {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
views: PropTypes.array.isRequired,
|
||||
hash: PropTypes.string.isRequired,
|
||||
pending: PropTypes.array,
|
||||
isTest: PropTypes.bool,
|
||||
netChain: PropTypes.string,
|
||||
settings: PropTypes.object.isRequired
|
||||
}
|
||||
netChain: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
pending: []
|
||||
};
|
||||
|
||||
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 () {
|
||||
@ -81,100 +199,64 @@ class TabBar extends Component {
|
||||
}
|
||||
|
||||
renderTabs () {
|
||||
const { settings } = this.props;
|
||||
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
|
||||
const hash = TABMAP[windowHash] || windowHash;
|
||||
const { views, pending } = this.props;
|
||||
const { activeViewId } = this.state;
|
||||
|
||||
const items = Object.keys(settings.views)
|
||||
.filter((id) => settings.views[id].fixed || settings.views[id].active)
|
||||
.map((id) => {
|
||||
const view = settings.views[id];
|
||||
let label = this.renderLabel(view.label);
|
||||
let body = null;
|
||||
const items = views
|
||||
.map((view, index) => {
|
||||
const body = (view.id === 'accounts')
|
||||
? (
|
||||
<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' />
|
||||
)
|
||||
: null;
|
||||
|
||||
if (id === 'accounts') {
|
||||
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);
|
||||
}
|
||||
const active = activeViewId === view.id;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
className={ hash === view.value ? styles.tabactive : '' }
|
||||
value={ view.value }
|
||||
icon={ view.icon }
|
||||
key={ id }
|
||||
label={ label }
|
||||
onActive={ this.onActivate(view.route) }>
|
||||
active={ active }
|
||||
view={ view }
|
||||
onChange={ this.onChange }
|
||||
key={ index }
|
||||
pendings={ pending.length }
|
||||
>
|
||||
{ body }
|
||||
</Tab>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
<div
|
||||
className={ styles.tabs }
|
||||
value={ hash }>
|
||||
onChange={ this.onChange }>
|
||||
{ items }
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
renderLabel = (name, bubble) => {
|
||||
return (
|
||||
<div className={ styles.label }>
|
||||
{ name }
|
||||
{ bubble }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignerLabel = (label) => {
|
||||
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) => {
|
||||
onChange = (view) => {
|
||||
const { router } = this.context;
|
||||
|
||||
return (event) => {
|
||||
router.push(activeRoute);
|
||||
this.setState({ activeRoute });
|
||||
};
|
||||
router.push(view.route);
|
||||
this.setState({ activeViewId: view.id });
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -52,6 +52,7 @@
|
||||
"description": "Have a peak on internals of transaction queue of your node.",
|
||||
"author": "Parity Team <admin@ethcore.io>",
|
||||
"version": "1.0.0",
|
||||
"visible": true,
|
||||
"secure": true
|
||||
}
|
||||
]
|
||||
|
@ -28,6 +28,10 @@ import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text
|
||||
import styles from './parityBar.css';
|
||||
|
||||
class ParityBar extends Component {
|
||||
static contextTypes = {
|
||||
muiTheme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
pending: PropTypes.array,
|
||||
dapp: PropTypes.bool
|
||||
@ -62,6 +66,7 @@ class ParityBar extends Component {
|
||||
|
||||
renderBar () {
|
||||
const { dapp } = this.props;
|
||||
const { muiTheme } = this.context;
|
||||
|
||||
if (!dapp) {
|
||||
return null;
|
||||
@ -75,7 +80,7 @@ class ParityBar extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.bar }>
|
||||
<ParityBackground className={ styles.corner }>
|
||||
<ParityBackground className={ styles.corner } muiTheme={ muiTheme }>
|
||||
<div className={ styles.cornercolor }>
|
||||
<Link to='/apps'>
|
||||
<Button
|
||||
@ -95,9 +100,11 @@ class ParityBar extends Component {
|
||||
}
|
||||
|
||||
renderExpanded () {
|
||||
const { muiTheme } = this.context;
|
||||
|
||||
return (
|
||||
<div className={ styles.overlay }>
|
||||
<ParityBackground className={ styles.expanded }>
|
||||
<ParityBackground className={ styles.expanded } muiTheme={ muiTheme }>
|
||||
<div className={ styles.header }>
|
||||
<div className={ styles.title }>
|
||||
<ContainerTitle title='Parity Signer: Pending' />
|
||||
|
@ -81,6 +81,7 @@ class Background extends Component {
|
||||
renderBackgrounds () {
|
||||
const { settings } = this.props;
|
||||
const { seeds } = this.state;
|
||||
const { muiTheme } = this.context;
|
||||
|
||||
return seeds.map((seed, index) => {
|
||||
return (
|
||||
@ -89,7 +90,9 @@ class Background extends Component {
|
||||
<ParityBackground
|
||||
className={ settings.backgroundSeed === seed ? styles.seedactive : styles.seed }
|
||||
seed={ seed }
|
||||
onClick={ this.onSelect(seed) } />
|
||||
onClick={ this.onSelect(seed) }
|
||||
muiTheme={ muiTheme }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -23,9 +23,10 @@ export const TEST_HTTP_URL = 'http://localhost:6688';
|
||||
export const TEST_WS_URL = 'ws://localhost:8866';
|
||||
|
||||
export function mockHttp (requests) {
|
||||
nock.cleanAll();
|
||||
let scope = nock(TEST_HTTP_URL);
|
||||
|
||||
requests.forEach((request) => {
|
||||
requests.forEach((request, index) => {
|
||||
scope = scope
|
||||
.post('/')
|
||||
.reply(request.code || 200, (uri, body) => {
|
||||
|
@ -37,6 +37,7 @@ module.exports = {
|
||||
entry: {
|
||||
// dapps
|
||||
'basiccoin': ['./dapps/basiccoin.js'],
|
||||
'dappreg': ['./dapps/dappreg.js'],
|
||||
'githubhint': ['./dapps/githubhint.js'],
|
||||
'registry': ['./dapps/registry.js'],
|
||||
'signaturereg': ['./dapps/signaturereg.js'],
|
||||
|
Loading…
Reference in New Issue
Block a user