diff options
Diffstat (limited to 'src/pages')
-rw-r--r-- | src/pages/add-auditor.html | 68 | ||||
-rw-r--r-- | src/pages/add-auditor.tsx | 28 | ||||
-rw-r--r-- | src/pages/auditors.html | 51 | ||||
-rw-r--r-- | src/pages/auditors.tsx | 22 | ||||
-rw-r--r-- | src/pages/confirm-contract.html | 9 | ||||
-rw-r--r-- | src/pages/confirm-contract.tsx | 42 | ||||
-rw-r--r-- | src/pages/confirm-create-reserve.html | 14 | ||||
-rw-r--r-- | src/pages/confirm-create-reserve.tsx | 27 | ||||
-rw-r--r-- | src/pages/debug.html | 13 | ||||
-rw-r--r-- | src/pages/error.html | 23 | ||||
-rw-r--r-- | src/pages/error.tsx | 8 | ||||
-rw-r--r-- | src/pages/logs.html | 39 | ||||
-rw-r--r-- | src/pages/logs.tsx | 4 | ||||
-rw-r--r-- | src/pages/popup.css | 84 | ||||
-rw-r--r-- | src/pages/popup.html | 18 | ||||
-rw-r--r-- | src/pages/popup.tsx | 543 | ||||
-rw-r--r-- | src/pages/show-db.html | 6 | ||||
-rw-r--r-- | src/pages/tree.html | 39 | ||||
-rw-r--r-- | src/pages/tree.tsx | 23 |
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); |