From 7f95c83f2f993fc3a64d4f5cad1d2d5fd29b08b3 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Fri, 7 Oct 2016 17:10:22 +0200
Subject: refactor reserve creation dialog
---
pages/confirm-create-reserve.tsx | 325 +++++++++++++++++----------------------
1 file changed, 138 insertions(+), 187 deletions(-)
(limited to 'pages/confirm-create-reserve.tsx')
diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx
index 666f8c68a..d0a08aac3 100644
--- a/pages/confirm-create-reserve.tsx
+++ b/pages/confirm-create-reserve.tsx
@@ -32,35 +32,37 @@ import {getReserveCreationInfo} from "../lib/wallet/wxApi";
let h = preact.h;
-/**
- * Execute something after a delay, with the possibility
- * to reset the delay.
- */
-class DelayTimer {
- ms: number;
- f: () => void;
- timerId: number|undefined = undefined;
-
- constructor(ms: number, f: () => void) {
- this.f = f;
- this.ms = ms;
+function delay(delayMs: number, value: T): Promise {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => resolve(value), delayMs);
+ });
+}
+
+class EventTrigger {
+ triggerResolve: any;
+ triggerPromise: Promise;
+
+ constructor() {
+ this.reset();
}
- bump() {
- this.stop();
- const handler = () => {
- this.f();
- };
- this.timerId = window.setTimeout(handler, this.ms);
+ private reset() {
+ this.triggerPromise = new Promise((resolve, reject) => {
+ this.triggerResolve = resolve;
+ });
}
- stop() {
- if (this.timerId != undefined) {
- window.clearTimeout(this.timerId);
- }
+ trigger() {
+ this.triggerResolve(false);
+ this.reset();
+ }
+
+ async wait(delayMs: number): Promise {
+ return await Promise.race([this.triggerPromise, delay(delayMs, true)]);
}
}
+
interface StateHolder {
(): T;
(newState: T): void;
@@ -85,7 +87,11 @@ abstract class ImplicitStateComponent extends preact.ComponentDetails will be displayed when a valid exchange provider URL is entered.
+ }
+
let denoms = rci.selectedDenoms;
let countByPub: {[s: string]: number} = {};
@@ -153,6 +159,17 @@ function getSuggestedExchange(currency: string): Promise {
return Promise.resolve(exchange);
}
+
+function WithdrawFee(props: {reserveCreationInfo: ReserveCreationInfo|null}): JSX.Element {
+ if (props.reserveCreationInfo) {
+ let {overhead, withdrawFee} = props.reserveCreationInfo;
+ let totalCost = Amounts.add(overhead, withdrawFee).amount;
+ return Withdraw fees: {amountToPretty(totalCost)}
;
+ }
+ return ;
+}
+
+
interface ExchangeSelectionProps {
suggestedExchangeUrl: string;
amount: AmountJson;
@@ -162,84 +179,77 @@ interface ExchangeSelectionProps {
class ExchangeSelection extends ImplicitStateComponent {
- complexViewRequested: StateHolder = this.makeState(false);
statusString: StateHolder = this.makeState(null);
reserveCreationInfo: StateHolder = this.makeState(
null);
url: StateHolder = this.makeState(null);
detailCollapsed: StateHolder = this.makeState(true);
- private timer: DelayTimer;
-
- isValidExchange: boolean;
+ updateEvent = new EventTrigger();
constructor(props: ExchangeSelectionProps) {
super(props);
- this.timer = new DelayTimer(800, () => this.update());
- this.url(props.suggestedExchangeUrl || null);
- this.update();
+ this.onUrlChanged(props.suggestedExchangeUrl || null);
}
- render(props: ExchangeSelectionProps): JSX.Element {
-
- console.log("props", props);
-
- let header = (
-
- {"You are about to withdraw "}
- {amountToPretty(props.amount)}
- {" from your bank account into your wallet."}
-
- );
- if (this.complexViewRequested() || !props.suggestedExchangeUrl) {
+ renderAdvanced(): JSX.Element {
+ if (this.detailCollapsed()) {
return (
-
- {header}
- {this.viewComplex()}
-
);
+
+ );
}
-
return (
- {header}
- {this.viewSimple()}
-
);
+ Provider Selection
+
+ this.onUrlChanged((e.target as HTMLInputElement).value)}/>
+
+ {this.renderStatus()}
+ Detailed Fee Structure
+ {renderReserveCreationDetails(this.reserveCreationInfo())}
+ )
}
+ renderFee() {
+ if (!this.reserveCreationInfo()) {
+ return "??";
+ }
+ let rci = this.reserveCreationInfo()!;
+ let totalCost = Amounts.add(rci.overhead, rci.withdrawFee).amount;
+ return `${amountToPretty(totalCost)}`;
+ }
- viewSimple() {
- let advancedButton = (
-
+ render(props: ExchangeSelectionProps): JSX.Element {
+ return (
+
+
+ {"You are about to withdraw "}
+ {amountToPretty(props.amount)}
+ {" from your bank account into your wallet."}
+
+
+ The exchange provider will charge
+ {" "}
+ {this.renderFee()}
+ {" "}
+ in fees.
+
+
+
+ {this.renderAdvanced()}
+
);
- if (this.statusString()) {
- return (
-
-
Error: {this.statusString()}
- {advancedButton}
-
- );
- }
- else if (this.reserveCreationInfo() != undefined) {
- let {overhead, withdrawFee} = this.reserveCreationInfo()!;
- let totalCost = Amounts.add(overhead, withdrawFee).amount;
- return (
-
-
{`Withdraw fees: ${amountToPretty(totalCost)}`}
-
-
- {advancedButton}
-
- );
- } else {
- return Please wait...
- }
}
@@ -250,53 +260,41 @@ class ExchangeSelection extends ImplicitStateComponent {
this.props.callback_url);
}
+ /**
+ * Do an update of the reserve creation info, without any debouncing.
+ */
+ async forceReserveUpdate() {
+ this.reserveCreationInfo(null);
+ if (!this.url()) {
+ this.statusString(i18n`Error: URL is empty`);
+ return;
+ }
- update() {
- this.timer.stop();
- const doUpdate = () => {
- this.reserveCreationInfo(null);
- if (!this.url()) {
- this.statusString = i18n`Error: URL is empty`;
- m.redraw(true);
- return;
- }
- this.statusString(null);
- let parsedUrl = URI(this.url()!);
- if (parsedUrl.is("relative")) {
- this.statusString = i18n`Error: URL may not be relative`;
- this.forceUpdate();
- return;
- }
-
- this.forceUpdate();
-
- console.log("doing get exchange info");
-
- getReserveCreationInfo(this.url()!, this.props.amount)
- .then((r: ReserveCreationInfo) => {
- console.log("get exchange info resolved");
- this.isValidExchange = true;
- this.reserveCreationInfo(r);
- console.dir(r);
- })
- .catch((e) => {
- console.log("get exchange info rejected");
- if (e.hasOwnProperty("httpStatus")) {
- this.statusString(`Error: request failed with status ${e.httpStatus}`);
- } else if (e.hasOwnProperty("errorResponse")) {
- let resp = e.errorResponse;
- this.statusString(`Error: ${resp.error} (${resp.hint})`);
- }
- });
- };
-
- doUpdate();
+ this.statusString(null);
+ let parsedUrl = URI(this.url()!);
+ if (parsedUrl.is("relative")) {
+ this.statusString(i18n`Error: URL may not be relative`);
+ return;
+ }
- console.log("got update", this.url());
+ try {
+ let r = await getReserveCreationInfo(this.url()!,
+ this.props.amount);
+ console.log("get exchange info resolved");
+ this.reserveCreationInfo(r);
+ console.dir(r);
+ } catch (e) {
+ console.log("get exchange info rejected");
+ if (e.hasOwnProperty("httpStatus")) {
+ this.statusString(`Error: request failed with status ${e.httpStatus}`);
+ } else if (e.hasOwnProperty("errorResponse")) {
+ let resp = e.errorResponse;
+ this.statusString(`Error: ${resp.error} (${resp.hint})`);
+ }
+ }
}
reset() {
- this.isValidExchange = false;
this.statusString(null);
this.reserveCreationInfo(null);
}
@@ -338,75 +336,28 @@ class ExchangeSelection extends ImplicitStateComponent {
chrome.runtime.sendMessage({type: 'create-reserve', detail: d}, cb);
}
- onUrlChanged(url: string) {
+ async onUrlChanged(url: string|null) {
this.reset();
this.url(url);
- this.timer.bump();
+ if (url == undefined) {
+ return;
+ }
+ this.updateEvent.trigger();
+ let waited = await this.updateEvent.wait(200);
+ if (waited) {
+ // Run the actual update if nobody else preempted us.
+ this.forceReserveUpdate();
+ this.forceUpdate();
+ }
}
- viewComplex() {
- function *f(): IterableIterator {
- if (this.reserveCreationInfo()) {
- let {overhead, withdrawFee} = this.reserveCreationInfo()!;
- let totalCost = Amounts.add(overhead, withdrawFee).amount;
- yield Withdraw fees: {amountToPretty(totalCost)}
;
- }
-
- yield (
-
- );
-
- yield ;
-
- yield (
-