aboutsummaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-04-20 03:09:25 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-04-24 16:14:29 +0200
commit82f2b76e25a4a67e01ec67e5ebe39d14ad771ea8 (patch)
tree965f6eb89b84d65a62b49008fd972c004832ccd1 /src/pages
parente6e0cbc387c2a77b48e4065c229daa65bf1aa0fa (diff)
downloadwallet-core-82f2b76e25a4a67e01ec67e5ebe39d14ad771ea8.tar.xz
Reorganize module loading.
We now use webpack instead of SystemJS, effectively bundling modules into one file (plus commons chunks) for every entry point. This results in a much smaller extension size (almost half). Furthermore we use yarn/npm even for extension run-time dependencies. This relieves us from manually vendoring and building dependencies. It's also easier to understand for new developers familiar with node.
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/add-auditor.html68
-rw-r--r--src/pages/add-auditor.tsx28
-rw-r--r--src/pages/auditors.html51
-rw-r--r--src/pages/auditors.tsx22
-rw-r--r--src/pages/confirm-contract.html9
-rw-r--r--src/pages/confirm-contract.tsx42
-rw-r--r--src/pages/confirm-create-reserve.html14
-rw-r--r--src/pages/confirm-create-reserve.tsx27
-rw-r--r--src/pages/debug.html13
-rw-r--r--src/pages/error.html23
-rw-r--r--src/pages/error.tsx8
-rw-r--r--src/pages/logs.html39
-rw-r--r--src/pages/logs.tsx4
-rw-r--r--src/pages/popup.css84
-rw-r--r--src/pages/popup.html18
-rw-r--r--src/pages/popup.tsx543
-rw-r--r--src/pages/show-db.html6
-rw-r--r--src/pages/tree.html39
-rw-r--r--src/pages/tree.tsx23
19 files changed, 858 insertions, 203 deletions
diff --git a/src/pages/add-auditor.html b/src/pages/add-auditor.html
index 7966e211f..dce391ff4 100644
--- a/src/pages/add-auditor.html
+++ b/src/pages/add-auditor.html
@@ -2,39 +2,37 @@
<html>
<head>
- <title>Taler Wallet: Add Auditor</title>
-
- <link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
-
- <link rel="icon" href="/img/icon.png">
-
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
-
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
-
- <link rel="stylesheet" type="text/css" href="/src/style/pure.css">
- <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
-
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- .button-linky {
- background: none;
- color: black;
- text-decoration: underline;
- border: none;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
+ <meta charset="UTF-8">
+
+ <title>Taler Wallet: Add Auditor</title>
+
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+
+ <link rel="icon" href="/img/icon.png">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/add-auditor-bundle.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="/src/style/pure.css">
+ <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
+
+ <style>
+ .tree-item {
+ margin: 2em;
+ border-radius: 5px;
+ border: 1px solid gray;
+ padding: 1em;
+ }
+ .button-linky {
+ background: none;
+ color: black;
+ text-decoration: underline;
+ border: none;
+ }
+ </style>
+
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/src/pages/add-auditor.tsx b/src/pages/add-auditor.tsx
index 3ec21f509..db283eade 100644
--- a/src/pages/add-auditor.tsx
+++ b/src/pages/add-auditor.tsx
@@ -21,15 +21,27 @@
*/
-import { ExchangeRecord, DenominationRecord } from "src/types";
-import { AuditorRecord, CurrencyRecord, ReserveRecord, CoinRecord, PreCoinRecord, Denomination } from "src/types";
-import { ImplicitStateComponent, StateHolder } from "src/components";
+import {
+ ExchangeRecord,
+ DenominationRecord,
+ AuditorRecord,
+ CurrencyRecord,
+ ReserveRecord,
+ CoinRecord,
+ PreCoinRecord,
+ Denomination
+} from "../types";
+import { ImplicitStateComponent, StateHolder } from "../components";
import {
getCurrencies,
updateCurrency,
-} from "src/wxApi";
-import { prettyAmount } from "src/renderHtml";
-import { getTalerStampDate } from "src/helpers";
+} from "../wxApi";
+import { prettyAmount } from "../renderHtml";
+import { getTalerStampDate } from "../helpers";
+
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import URI = require("urijs");
interface ConfirmAuditorProps {
url: string;
@@ -101,7 +113,7 @@ class ConfirmAuditor extends ImplicitStateComponent<ConfirmAuditorProps> {
}
export function main() {
- const walletPageUrl = URI(document.location.href);
+ const walletPageUrl = new URI(document.location.href);
const query: any = JSON.parse((URI.parseQuery(walletPageUrl.query()) as any)["req"]);
const url = query.url;
const currency: string = query.currency;
@@ -110,3 +122,5 @@ export function main() {
const args = { url, currency, auditorPub, expirationStamp };
ReactDOM.render(<ConfirmAuditor {...args} />, document.getElementById("container")!);
}
+
+document.addEventListener("DOMContentLoaded", main);
diff --git a/src/pages/auditors.html b/src/pages/auditors.html
index 0a9740f03..7e01f4e1f 100644
--- a/src/pages/auditors.html
+++ b/src/pages/auditors.html
@@ -2,36 +2,33 @@
<html>
<head>
- <title>Taler Wallet: Auditors</title>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Auditors</title>
- <link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
- <link rel="icon" href="/img/icon.png">
+ <link rel="icon" href="/img/icon.png">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/auditors-bundle.js"></script>
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
+ <style>
+ .tree-item {
+ margin: 2em;
+ border-radius: 5px;
+ border: 1px solid gray;
+ padding: 1em;
+ }
+ .button-linky {
+ background: none;
+ color: black;
+ text-decoration: underline;
+ border: none;
+ }
+ </style>
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- .button-linky {
- background: none;
- color: black;
- text-decoration: underline;
- border: none;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/src/pages/auditors.tsx b/src/pages/auditors.tsx
index 7cffec403..4aa922128 100644
--- a/src/pages/auditors.tsx
+++ b/src/pages/auditors.tsx
@@ -21,15 +21,25 @@
*/
-import { ExchangeRecord, DenominationRecord } from "src/types";
-import { AuditorRecord, CurrencyRecord, ReserveRecord, CoinRecord, PreCoinRecord, Denomination } from "src/types";
-import { ImplicitStateComponent, StateHolder } from "src/components";
+import {
+ ExchangeRecord,
+ DenominationRecord,
+ AuditorRecord,
+ CurrencyRecord,
+ ReserveRecord,
+ CoinRecord,
+ PreCoinRecord,
+ Denomination
+} from "../types";
+import { ImplicitStateComponent, StateHolder } from "../components";
import {
getCurrencies,
updateCurrency,
-} from "src/wxApi";
-import { prettyAmount } from "src/renderHtml";
-import { getTalerStampDate } from "src/helpers";
+} from "../wxApi";
+import { prettyAmount } from "../renderHtml";
+import { getTalerStampDate } from "../helpers";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
interface CurrencyListState {
currencies?: CurrencyRecord[];
diff --git a/src/pages/confirm-contract.html b/src/pages/confirm-contract.html
index c42479c29..52b68e627 100644
--- a/src/pages/confirm-contract.html
+++ b/src/pages/confirm-contract.html
@@ -2,6 +2,7 @@
<html>
<head>
+ <meta charset="UTF-8">
<title>Taler Wallet: Confirm Reserve Creation</title>
<link rel="stylesheet" type="text/css" href="/src/style/lang.css">
@@ -9,12 +10,8 @@
<link rel="icon" href="/img/icon.png">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
-
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/confirm-contract-bundle.js"></script>
<style>
button.accept {
diff --git a/src/pages/confirm-contract.tsx b/src/pages/confirm-contract.tsx
index 3a0712a8c..d8f72ba01 100644
--- a/src/pages/confirm-contract.tsx
+++ b/src/pages/confirm-contract.tsx
@@ -23,12 +23,15 @@
"use strict";
-import {substituteFulfillmentUrl} from "src/helpers";
-import {Contract, AmountJson, ExchangeRecord} from "src/types";
-import {OfferRecord} from "src/wallet";
-import {renderContract, prettyAmount} from "src/renderHtml";
-import {getExchanges} from "src/wxApi";
-import * as i18n from "src/i18n";
+import {substituteFulfillmentUrl} from "../helpers";
+import {Contract, AmountJson, ExchangeRecord} from "../types";
+import {OfferRecord} from "../wallet";
+import {renderContract, prettyAmount} from "../renderHtml";
+import {getExchanges} from "../wxApi";
+import * as i18n from "../i18n";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import URI = require("urijs");
interface DetailState {
@@ -129,7 +132,7 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
}
getOffer(): Promise<OfferRecord> {
- return new Promise((resolve, reject) => {
+ return new Promise<OfferRecord>((resolve, reject) => {
let msg = {
type: 'get-offer',
detail: {
@@ -160,22 +163,21 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
let acceptedExchangePubs = this.state.offer.contract.exchanges.map((e) => e.master_pub);
let ex = this.state.exchanges.find((e) => acceptedExchangePubs.indexOf(e.masterPublicKey) >= 0);
if (ex) {
- this.state.error = msgInsufficient;
+ this.setState({error: msgInsufficient});
} else {
- this.state.error = msgNoMatch;
+ this.setState({error: msgNoMatch});
}
} else {
- this.state.error = msgInsufficient;
+ this.setState({error: msgInsufficient});
}
break;
default:
- this.state.error = `Error: ${resp.error}`;
+ this.setState({error: `Error: ${resp.error}`});
break;
}
- this.state.payDisabled = true;
+ this.setState({payDisabled: true});
} else {
- this.state.payDisabled = false;
- this.state.error = null;
+ this.setState({payDisabled: false, error: null});
}
this.setState({} as any);
window.setTimeout(() => this.checkPayment(), 500);
@@ -189,14 +191,12 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
console.log("confirm-pay error", JSON.stringify(resp));
switch (resp.error) {
case "coins-insufficient":
- this.state.error = "You do not have enough coins of the" +
- " requested currency.";
+ this.setState({error: "You do not have enough coins of the requested currency."});
break;
default:
- this.state.error = `Error: ${resp.error}`;
+ this.setState({error: `Error: ${resp.error}`});
break;
}
- this.setState({} as any);
return;
}
let c = d.offer!.contract;
@@ -232,11 +232,11 @@ class ContractPrompt extends React.Component<ContractPromptProps, ContractPrompt
}
-export function main() {
- let url = URI(document.location.href);
+document.addEventListener("DOMContentLoaded", () => {
+ let url = new URI(document.location.href);
let query: any = URI.parseQuery(url.query());
let offerId = JSON.parse(query.offerId);
ReactDOM.render(<ContractPrompt offerId={offerId}/>, document.getElementById(
"contract")!);
-}
+});
diff --git a/src/pages/confirm-create-reserve.html b/src/pages/confirm-create-reserve.html
index 77efc7036..c1e4b7ce3 100644
--- a/src/pages/confirm-create-reserve.html
+++ b/src/pages/confirm-create-reserve.html
@@ -2,21 +2,15 @@
<html>
<head>
+ <meta charset="UTF-8">
<title>Taler Wallet: Select Taler Provider</title>
<link rel="icon" href="/img/icon.png">
+ <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
-
- <!-- module loading -->
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/confirm-create-reserve-bundle.js"></script>
- <link rel="icon" href="/img/icon.png">
-
- <link rel="stylesheet" type="text/css" href="/src/style/wallet.css">
</head>
<body>
diff --git a/src/pages/confirm-create-reserve.tsx b/src/pages/confirm-create-reserve.tsx
index 6617ed6c3..a7fd7b0fd 100644
--- a/src/pages/confirm-create-reserve.tsx
+++ b/src/pages/confirm-create-reserve.tsx
@@ -22,17 +22,18 @@
* @author Florian Dold
*/
-import {amountToPretty, canonicalizeBaseUrl} from "src/helpers";
+import {amountToPretty, canonicalizeBaseUrl} from "../helpers";
import {
AmountJson, CreateReserveResponse,
ReserveCreationInfo, Amounts,
Denomination, DenominationRecord,
-} from "src/types";
-import {getReserveCreationInfo} from "src/wxApi";
-import {ImplicitStateComponent, StateHolder} from "src/components";
-import * as i18n from "src/i18n";
-
-"use strict";
+} from "../types";
+import {getReserveCreationInfo} from "../wxApi";
+import {ImplicitStateComponent, StateHolder} from "../components";
+import * as i18n from "../i18n";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import URI = require("urijs");
function delay<T>(delayMs: number, value: T): Promise<T> {
@@ -220,7 +221,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
);
}
if (this.url() && !this.statusString()) {
- let shortName = URI(this.url()!).host();
+ let shortName = new URI(this.url()!).host();
return (
<i18n.Translate wrap="p">
Waiting for a response from
@@ -283,7 +284,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
}
this.statusString(null);
- let parsedUrl = URI(this.url()!);
+ let parsedUrl = new URI(this.url()!);
if (parsedUrl.is("relative")) {
this.statusString(i18n.str`Error: URL may not be relative`);
this.detailCollapsed(false);
@@ -350,7 +351,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
amount_fraction: amount.fraction,
amount_currency: amount.currency,
};
- let url = URI(callback_url).addQuery(q);
+ let url = new URI(callback_url).addQuery(q);
if (!url.is("absolute")) {
throw Error("callback url is not absolute");
}
@@ -393,7 +394,7 @@ class ExchangeSelection extends ImplicitStateComponent<ExchangeSelectionProps> {
export async function main() {
try {
- const url = URI(document.location.href);
+ const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query());
let amount;
try {
@@ -432,3 +433,7 @@ export async function main() {
console.error(`got error "${e.message}"`, e);
}
}
+
+document.addEventListener("DOMContentLoaded", () => {
+ main();
+});
diff --git a/src/pages/debug.html b/src/pages/debug.html
deleted file mode 100644
index b8ddc7ccb..000000000
--- a/src/pages/debug.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html>
- <head>
- <title>Taler Wallet Debugging</title>
- <link rel="icon" href="../img/icon.png">
- </head>
- <body>
- <h1>Debug Pages</h1>
- <a href="show-db.html">Show DB</a> <br>
- <a href="/src/popup/balance-overview.html">Show balance</a>
-
- </body>
-</html>
diff --git a/src/pages/error.html b/src/pages/error.html
index 7e6103c0e..c67f4a5a3 100644
--- a/src/pages/error.html
+++ b/src/pages/error.html
@@ -2,21 +2,18 @@
<html>
<head>
- <title>Taler Wallet: Error Occured</title>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Error Occured</title>
- <link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
- <link rel="icon" href="/img/icon.png">
+ <link rel="icon" href="/img/icon.png">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/error-bundle.js"></script>
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
-
- <body>
- <div id="container"></div>
- </body>
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/src/pages/error.tsx b/src/pages/error.tsx
index 2878dfcf1..f278bd224 100644
--- a/src/pages/error.tsx
+++ b/src/pages/error.tsx
@@ -22,7 +22,11 @@
* @author Florian Dold
*/
-import {ImplicitStateComponent, StateHolder} from "src/components";
+import {ImplicitStateComponent, StateHolder} from "../components";
+
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import URI = require("urijs");
"use strict";
@@ -42,7 +46,7 @@ class ErrorView extends React.Component<ErrorProps, void> {
export async function main() {
try {
- const url = URI(document.location.href);
+ const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query());
const message: string = query.message || "unknown error";
diff --git a/src/pages/logs.html b/src/pages/logs.html
index 866b434f8..432427ebd 100644
--- a/src/pages/logs.html
+++ b/src/pages/logs.html
@@ -2,30 +2,27 @@
<html>
<head>
- <title>Taler Wallet: Logs</title>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Logs</title>
- <link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
- <link rel="icon" href="/img/icon.png">
+ <link rel="icon" href="/img/icon.png">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/logs-bundle.js"></script>
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
+ <style>
+ .tree-item {
+ margin: 2em;
+ border-radius: 5px;
+ border: 1px solid gray;
+ padding: 1em;
+ }
+ </style>
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/src/pages/logs.tsx b/src/pages/logs.tsx
index 15bb3d270..716eebae6 100644
--- a/src/pages/logs.tsx
+++ b/src/pages/logs.tsx
@@ -20,7 +20,9 @@
* @author Florian Dold
*/
-import {LogEntry, getLogs} from "src/logging";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import {LogEntry, getLogs} from "../logging";
interface LogViewProps {
log: LogEntry;
diff --git a/src/pages/popup.css b/src/pages/popup.css
new file mode 100644
index 000000000..675412c11
--- /dev/null
+++ b/src/pages/popup.css
@@ -0,0 +1,84 @@
+
+/**
+ * @author Gabor X. Toth
+ * @author Marcello Stanisci
+ * @author Florian Dold
+ */
+
+body {
+ min-height: 20em;
+ width: 30em;
+ margin: 0;
+ padding: 0;
+ max-height: 800px;
+ overflow: hidden;
+}
+
+.nav {
+ background-color: #ddd;
+ padding: 0.5em 0;
+}
+
+.nav a {
+ color: black;
+ padding: 0.5em;
+ text-decoration: none;
+}
+
+.nav a.active {
+ background-color: white;
+ font-weight: bold;
+}
+
+
+.container {
+ overflow-y: scroll;
+ max-height: 400px;
+}
+
+.abbrev {
+ text-decoration-style: dotted;
+}
+
+#content {
+ padding: 1em;
+}
+
+
+#wallet-table .amount {
+ text-align: right;
+}
+
+.hidden {
+ display: none;
+}
+
+#transactions-table th,
+#transactions-table td {
+ padding: 0.2em 0.5em;
+}
+
+#reserve-create table {
+ width: 100%;
+}
+
+#reserve-create table td.label {
+ width: 5em;
+}
+
+#reserve-create table .input input[type="text"] {
+ width: 100%;
+}
+
+.historyItem {
+ border: 1px solid black;
+ border-radius: 10px;
+ padding-left: 0.5em;
+ margin: 0.5em;
+}
+
+.historyDate {
+ font-size: 90%;
+ margin: 0.3em;
+ color: slategray;
+}
diff --git a/src/pages/popup.html b/src/pages/popup.html
new file mode 100644
index 000000000..7ff5cffaf
--- /dev/null
+++ b/src/pages/popup.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="popup.css">
+
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/popup-bundle.js"></script>
+</head>
+
+<body>
+ <div id="content" style="margin:0;padding:0"></div>
+</body>
+
+</html>
diff --git a/src/pages/popup.tsx b/src/pages/popup.tsx
new file mode 100644
index 000000000..c8d52b45c
--- /dev/null
+++ b/src/pages/popup.tsx
@@ -0,0 +1,543 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER 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, or (at your option) any later version.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+/**
+ * Popup shown to the user when they click
+ * the Taler browser action button.
+ *
+ * @author Florian Dold
+ */
+
+
+"use strict";
+
+import {substituteFulfillmentUrl} from "../helpers";
+import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
+import {HistoryRecord, HistoryLevel} from "../wallet";
+import {
+ AmountJson, WalletBalance, Amounts,
+ WalletBalanceEntry
+} from "../types";
+import {abbrev, prettyAmount} from "../renderHtml";
+import * as i18n from "../i18n";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
+import URI = require("urijs");
+
+function onUpdateNotification(f: () => void): () => void {
+ let port = chrome.runtime.connect({name: "notifications"});
+ let listener = (msg: any, port: any) => {
+ f();
+ };
+ port.onMessage.addListener(listener);
+ return () => {
+ port.onMessage.removeListener(listener);
+ }
+}
+
+
+class Router extends React.Component<any,any> {
+ static setRoute(s: string): void {
+ window.location.hash = s;
+ }
+
+ static getRoute(): string {
+ // Omit the '#' at the beginning
+ return window.location.hash.substring(1);
+ }
+
+ static onRoute(f: any): () => void {
+ Router.routeHandlers.push(f);
+ return () => {
+ let i = Router.routeHandlers.indexOf(f);
+ this.routeHandlers = this.routeHandlers.splice(i, 1);
+ }
+ }
+
+ static routeHandlers: any[] = [];
+
+ componentWillMount() {
+ console.log("router mounted");
+ window.onhashchange = () => {
+ this.setState({});
+ for (let f of Router.routeHandlers) {
+ f();
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ console.log("router unmounted");
+ }
+
+
+ render(): JSX.Element {
+ let route = window.location.hash.substring(1);
+ console.log("rendering route", route);
+ let defaultChild: React.ReactChild|null = null;
+ let foundChild: React.ReactChild|null = null;
+ React.Children.forEach(this.props.children, (child) => {
+ let childProps: any = (child as any).props;
+ if (!childProps) {
+ return;
+ }
+ if (childProps["default"]) {
+ defaultChild = child;
+ }
+ if (childProps["route"] == route) {
+ foundChild = child;
+ }
+ })
+ let child: React.ReactChild | null = foundChild || defaultChild;
+ if (!child) {
+ throw Error("unknown route");
+ }
+ Router.setRoute((child as any).props["route"]);
+ return <div>{child}</div>;
+ }
+}
+
+
+interface TabProps {
+ target: string;
+ children?: React.ReactNode;
+}
+
+function Tab(props: TabProps) {
+ let cssClass = "";
+ if (props.target == Router.getRoute()) {
+ cssClass = "active";
+ }
+ let onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
+ Router.setRoute(props.target);
+ e.preventDefault();
+ };
+ return (
+ <a onClick={onClick} href={props.target} className={cssClass}>
+ {props.children}
+ </a>
+ );
+}
+
+
+class WalletNavBar extends React.Component<any,any> {
+ cancelSubscription: any;
+
+ componentWillMount() {
+ this.cancelSubscription = Router.onRoute(() => {
+ this.setState({});
+ });
+ }
+
+ componentWillUnmount() {
+ if (this.cancelSubscription) {
+ this.cancelSubscription();
+ }
+ }
+
+ render() {
+ console.log("rendering nav bar");
+ return (
+ <div className="nav" id="header">
+ <Tab target="/balance">
+ {i18n.str`Balance`}
+ </Tab>
+ <Tab target="/history">
+ {i18n.str`History`}
+ </Tab>
+ <Tab target="/debug">
+ {i18n.str`Debug`}
+ </Tab>
+ </div>);
+ }
+}
+
+
+function ExtensionLink(props: any) {
+ let onClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
+ chrome.tabs.create({
+ "url": chrome.extension.getURL(props.target)
+ });
+ e.preventDefault();
+ };
+ return (
+ <a onClick={onClick} href={props.target}>
+ {props.children}
+ </a>)
+}
+
+
+export function bigAmount(amount: AmountJson): JSX.Element {
+ let v = amount.value + amount.fraction / Amounts.fractionalBase;
+ return (
+ <span>
+ <span style={{fontSize: "300%"}}>{v}</span>
+ {" "}
+ <span>{amount.currency}</span>
+ </span>
+ );
+}
+
+class WalletBalanceView extends React.Component<any, any> {
+ balance: WalletBalance;
+ gotError = false;
+ canceler: (() => void) | undefined = undefined;
+ unmount = false;
+
+ componentWillMount() {
+ this.canceler = onUpdateNotification(() => this.updateBalance());
+ this.updateBalance();
+ }
+
+ componentWillUnmount() {
+ console.log("component WalletBalanceView will unmount");
+ if (this.canceler) {
+ this.canceler();
+ }
+ this.unmount = true;
+ }
+
+ updateBalance() {
+ chrome.runtime.sendMessage({type: "balances"}, (resp) => {
+ if (this.unmount) {
+ return;
+ }
+ if (resp.error) {
+ this.gotError = true;
+ console.error("could not retrieve balances", resp);
+ this.setState({});
+ return;
+ }
+ this.gotError = false;
+ console.log("got wallet", resp);
+ this.balance = resp;
+ this.setState({});
+ });
+ }
+
+ renderEmpty(): JSX.Element {
+ let helpLink = (
+ <ExtensionLink target="/src/pages/help/empty-wallet.html">
+ {i18n.str`help`}
+ </ExtensionLink>
+ );
+ return (
+ <div>
+ <i18n.Translate>
+ You have no balance to show. Need some
+ {" "}<span>{helpLink}</span>{" "}
+ getting started?
+ </i18n.Translate>
+ </div>
+ );
+ }
+
+ formatPending(entry: WalletBalanceEntry): JSX.Element {
+ let incoming: JSX.Element | undefined;
+ let payment: JSX.Element | undefined;
+
+ console.log("available: ", entry.pendingIncoming ? prettyAmount(entry.available) : null);
+ console.log("incoming: ", entry.pendingIncoming ? prettyAmount(entry.pendingIncoming) : null);
+
+ if (Amounts.isNonZero(entry.pendingIncoming)) {
+ incoming = (
+ <i18n.Translate wrap="span">
+ <span style={{color: "darkgreen"}}>
+ {"+"}
+ {prettyAmount(entry.pendingIncoming)}
+ </span>
+ {" "}
+ incoming
+ </i18n.Translate>
+ );
+ }
+
+ if (Amounts.isNonZero(entry.pendingPayment)) {
+ payment = (
+ <i18n.Translate wrap="span">
+ <span style={{color: "darkblue"}}>
+ {prettyAmount(entry.pendingPayment)}
+ </span>
+ {" "}
+ being spent
+ </i18n.Translate>
+ );
+ }
+
+ let l = [incoming, payment].filter((x) => x !== undefined);
+ if (l.length == 0) {
+ return <span />;
+ }
+
+ if (l.length == 1) {
+ return <span>({l})</span>
+ }
+ return <span>({l[0]}, {l[1]})</span>;
+
+ }
+
+ render(): JSX.Element {
+ let wallet = this.balance;
+ if (this.gotError) {
+ return i18n.str`Error: could not retrieve balance information.`;
+ }
+ if (!wallet) {
+ return <span></span>;
+ }
+ console.log(wallet);
+ let listing = Object.keys(wallet).map((key) => {
+ let entry: WalletBalanceEntry = wallet[key];
+ return (
+ <p>
+ {bigAmount(entry.available)}
+ {" "}
+ {this.formatPending(entry)}
+ </p>
+ );
+ });
+ if (listing.length > 0) {
+ let link = chrome.extension.getURL("/src/pages/auditors.html");
+ let linkElem = <a href={link} target="_blank">auditors</a>;
+ return (
+ <div>
+ {listing}
+ {linkElem}
+ </div>
+ );
+ }
+
+ return this.renderEmpty();
+ }
+}
+
+
+function formatHistoryItem(historyItem: HistoryRecord) {
+ const d = historyItem.detail;
+ const t = historyItem.timestamp;
+ console.log("hist item", historyItem);
+ switch (historyItem.type) {
+ case "create-reserve":
+ return (
+ <i18n.Translate wrap="p">
+ Bank requested reserve (<span>{abbrev(d.reservePub)}</span>) for <span>{prettyAmount(d.requestedAmount)}</span>.
+ </i18n.Translate>
+ );
+ case "confirm-reserve": {
+ // FIXME: eventually remove compat fix
+ let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
+ let pub = abbrev(d.reservePub);
+ return (
+ <i18n.Translate wrap="p">
+ Started to withdraw
+ {" "}{prettyAmount(d.requestedAmount)}{" "}
+ from <span>{exchange}</span> (<span>{pub}</span>).
+ </i18n.Translate>
+ );
+ }
+ case "offer-contract": {
+ let link = chrome.extension.getURL("view-contract.html");
+ let linkElem = <a href={link}>{abbrev(d.contractHash)}</a>;
+ let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
+ return (
+ <i18n.Translate wrap="p">
+ Merchant <em>{abbrev(d.merchantName, 15)}</em> offered contract <a href={link}>{abbrev(d.contractHash)}</a>;
+ </i18n.Translate>
+ );
+ }
+ case "depleted-reserve": {
+ let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??";
+ let amount = prettyAmount(d.requestedAmount);
+ let pub = abbrev(d.reservePub);
+ return (
+ <i18n.Translate wrap="p">
+ Withdrew <span>{amount}</span> from <span>{exchange}</span> (<span>{pub}</span>).
+ </i18n.Translate>
+ );
+ }
+ case "pay": {
+ let url = substituteFulfillmentUrl(d.fulfillmentUrl,
+ {H_contract: d.contractHash});
+ let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
+ let fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>;
+ return (
+ <i18n.Translate wrap="p">
+ Paid <span>{prettyAmount(d.amount)}</span> to merchant <span>{merchantElem}</span>. (<span>{fulfillmentLinkElem}</span>)
+ </i18n.Translate>
+ );
+ }
+ default:
+ return (<p>{i18n.str`Unknown event (${historyItem.type})`}</p>);
+ }
+}
+
+
+class WalletHistory extends React.Component<any, any> {
+ myHistory: any[];
+ gotError = false;
+ unmounted = false;
+
+ componentWillMount() {
+ this.update();
+ onUpdateNotification(() => this.update());
+ }
+
+ componentWillUnmount() {
+ console.log("history component unmounted");
+ this.unmounted = true;
+ }
+
+ update() {
+ chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
+ if (this.unmounted) {
+ return;
+ }
+ console.log("got history response");
+ if (resp.error) {
+ this.gotError = true;
+ console.error("could not retrieve history", resp);
+ this.setState({});
+ return;
+ }
+ this.gotError = false;
+ console.log("got history", resp.history);
+ this.myHistory = resp.history;
+ this.setState({});
+ });
+ }
+
+ render(): JSX.Element {
+ console.log("rendering history");
+ let history: HistoryRecord[] = this.myHistory;
+ if (this.gotError) {
+ return i18n.str`Error: could not retrieve event history`;
+ }
+
+ if (!history) {
+ // We're not ready yet
+ return <span />;
+ }
+
+ let subjectMemo: {[s: string]: boolean} = {};
+ let listing: any[] = [];
+ for (let record of history.reverse()) {
+ if (record.subjectId && subjectMemo[record.subjectId]) {
+ continue;
+ }
+ if (record.level != undefined && record.level < HistoryLevel.User) {
+ continue;
+ }
+ subjectMemo[record.subjectId as string] = true;
+
+ let item = (
+ <div className="historyItem">
+ <div className="historyDate">
+ {(new Date(record.timestamp)).toString()}
+ </div>
+ {formatHistoryItem(record)}
+ </div>
+ );
+
+ listing.push(item);
+ }
+
+ if (listing.length > 0) {
+ return <div className="container">{listing}</div>;
+ }
+ return <p>{i18n.str`Your wallet has no events recorded.`}</p>
+ }
+
+}
+
+
+function reload() {
+ try {
+ chrome.runtime.reload();
+ window.close();
+ } catch (e) {
+ // Functionality missing in firefox, ignore!
+ }
+}
+
+function confirmReset() {
+ if (confirm("Do you want to IRREVOCABLY DESTROY everything inside your" +
+ " wallet and LOSE ALL YOUR COINS?")) {
+ chrome.runtime.sendMessage({type: "reset"});
+ window.close();
+ }
+}
+
+
+function WalletDebug(props: any) {
+ return (<div>
+ <p>Debug tools:</p>
+ <button onClick={openExtensionPage("/src/pages/popup.html")}>
+ wallet tab
+ </button>
+ <button onClick={openExtensionPage("/src/pages/show-db.html")}>
+ show db
+ </button>
+ <button onClick={openExtensionPage("/src/pages/tree.html")}>
+ show tree
+ </button>
+ <button onClick={openExtensionPage("/src/pages/logs.html")}>
+ show logs
+ </button>
+ <br />
+ <button onClick={confirmReset}>
+ reset
+ </button>
+ <button onClick={reload}>
+ reload chrome extension
+ </button>
+ </div>);
+}
+
+
+function openExtensionPage(page: string) {
+ return function() {
+ chrome.tabs.create({
+ "url": chrome.extension.getURL(page)
+ });
+ }
+}
+
+
+function openTab(page: string) {
+ return function() {
+ chrome.tabs.create({
+ "url": page
+ });
+ }
+}
+
+
+let el = (
+ <div>
+ <WalletNavBar />
+ <div style={{margin: "1em"}}>
+ <Router>
+ <WalletBalanceView route="/balance" default/>
+ <WalletHistory route="/history"/>
+ <WalletDebug route="/debug"/>
+ </Router>
+ </div>
+ </div>
+);
+
+document.addEventListener("DOMContentLoaded", () => {
+ ReactDOM.render(el, document.getElementById("content")!);
+})
diff --git a/src/pages/show-db.html b/src/pages/show-db.html
index 1cf11e4f6..215c726d9 100644
--- a/src/pages/show-db.html
+++ b/src/pages/show-db.html
@@ -1,12 +1,12 @@
-
<!doctype html>
-
<html>
<head>
+ <meta charset="UTF-8">
<title>Taler Wallet: Reserve Created</title>
<link rel="stylesheet" type="text/css" href="../style/wallet.css">
<link rel="icon" href="/img/icon.png">
- <script src="show-db.js"></script>
+ <script src="/dist/page-common.js"></script>
+ <script src="/dist/show-db-bundle.js"></script>
</head>
<body>
<h1>DB Dump</h1>
diff --git a/src/pages/tree.html b/src/pages/tree.html
index 7ff4295a0..638c1484a 100644
--- a/src/pages/tree.html
+++ b/src/pages/tree.html
@@ -2,30 +2,27 @@
<html>
<head>
- <title>Taler Wallet: Tree View</title>
+ <meta charset="UTF-8">
+ <title>Taler Wallet: Tree View</title>
- <link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
+ <link rel="stylesheet" type="text/css" href="../style/lang.css">
+ <link rel="stylesheet" type="text/css" href="../style/wallet.css">
- <link rel="icon" href="/img/icon.png">
+ <link rel="icon" href="/img/icon.png">
- <script src="/src/vendor/URI.js"></script>
- <script src="/src/vendor/react.js"></script>
- <script src="/src/vendor/react-dom.js"></script>
+ <script src="/dist/page-common-bundle.js"></script>
+ <script src="/dist/tree-bundle.js"></script>
- <script src="/src/vendor/system-csp-production.src.js"></script>
- <script src="/src/moduleTrampoline.js"></script>
+ <style>
+ .tree-item {
+ margin: 2em;
+ border-radius: 5px;
+ border: 1px solid gray;
+ padding: 1em;
+ }
+ </style>
- <style>
- .tree-item {
- margin: 2em;
- border-radius: 5px;
- border: 1px solid gray;
- padding: 1em;
- }
- </style>
-
- <body>
- <div id="container"></div>
- </body>
+ <body>
+ <div id="container"></div>
+ </body>
</html>
diff --git a/src/pages/tree.tsx b/src/pages/tree.tsx
index 4909c189b..a465cff59 100644
--- a/src/pages/tree.tsx
+++ b/src/pages/tree.tsx
@@ -21,15 +21,24 @@
*/
-import {ExchangeRecord, DenominationRecord, CoinStatus} from "src/types";
-import { ReserveRecord, CoinRecord, PreCoinRecord, Denomination } from "src/types";
-import { ImplicitStateComponent, StateHolder } from "src/components";
+import {
+ ExchangeRecord,
+ DenominationRecord,
+ CoinStatus,
+ ReserveRecord,
+ CoinRecord,
+ PreCoinRecord,
+ Denomination,
+} from "../types";
+import { ImplicitStateComponent, StateHolder } from "../components";
import {
getReserves, getExchanges, getCoins, getPreCoins,
refresh, getDenoms
-} from "src/wxApi";
-import { prettyAmount } from "src/renderHtml";
-import { getTalerStampDate } from "src/helpers";
+} from "../wxApi";
+import { prettyAmount } from "../renderHtml";
+import { getTalerStampDate } from "../helpers";
+import * as React from "react";
+import * as ReactDOM from "react-dom";
interface ReserveViewProps {
reserve: ReserveRecord;
@@ -423,3 +432,5 @@ class ExchangesList extends React.Component<any, ExchangesListState> {
export function main() {
ReactDOM.render(<ExchangesList />, document.getElementById("container")!);
}
+
+document.addEventListener("DOMContentLoaded", main);