From b6e774585d32017e5f1ceeeb2b2e2a5e350354d3 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sun, 28 May 2017 23:15:41 +0200 Subject: move webex specific things in their own directory --- src/webex/pages/popup.tsx | 548 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 src/webex/pages/popup.tsx (limited to 'src/webex/pages/popup.tsx') diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx new file mode 100644 index 000000000..a806cfef9 --- /dev/null +++ b/src/webex/pages/popup.tsx @@ -0,0 +1,548 @@ +/* + 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 + */ + + +/** + * Popup shown to the user when they click + * the Taler browser action button. + * + * @author Florian Dold + */ + + +"use strict"; + +import { + AmountJson, + Amounts, + WalletBalance, + WalletBalanceEntry +} from "../../types"; +import { HistoryRecord, HistoryLevel } from "../../wallet"; +import { amountToPretty } from "../../helpers"; +import * as i18n from "../../i18n"; + +import { abbrev } from "../renderHtml"; + +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 { + 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
{child}
; + } +} + + +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) => { + Router.setRoute(props.target); + e.preventDefault(); + }; + return ( + + {props.children} + + ); +} + + +class WalletNavBar extends React.Component { + cancelSubscription: any; + + componentWillMount() { + this.cancelSubscription = Router.onRoute(() => { + this.setState({}); + }); + } + + componentWillUnmount() { + if (this.cancelSubscription) { + this.cancelSubscription(); + } + } + + render() { + console.log("rendering nav bar"); + return ( + ); + } +} + + +function ExtensionLink(props: any) { + let onClick = (e: React.MouseEvent) => { + chrome.tabs.create({ + "url": chrome.extension.getURL(props.target) + }); + e.preventDefault(); + }; + return ( + + {props.children} + ) +} + + +export function bigAmount(amount: AmountJson): JSX.Element { + let v = amount.value + amount.fraction / Amounts.fractionalBase; + return ( + + {v} + {" "} + {amount.currency} + + ); +} + +class WalletBalanceView extends React.Component { + 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 = ( + + {i18n.str`help`} + + ); + return ( +
+ + You have no balance to show. Need some + {" "}{helpLink}{" "} + getting started? + +
+ ); + } + + formatPending(entry: WalletBalanceEntry): JSX.Element { + let incoming: JSX.Element | undefined; + let payment: JSX.Element | undefined; + + console.log("available: ", entry.pendingIncoming ? amountToPretty(entry.available) : null); + console.log("incoming: ", entry.pendingIncoming ? amountToPretty(entry.pendingIncoming) : null); + + if (Amounts.isNonZero(entry.pendingIncoming)) { + incoming = ( + + + {"+"} + {amountToPretty(entry.pendingIncoming)} + + {" "} + incoming + + ); + } + + if (Amounts.isNonZero(entry.pendingPayment)) { + payment = ( + + + {amountToPretty(entry.pendingPayment)} + + {" "} + being spent + + ); + } + + let l = [incoming, payment].filter((x) => x !== undefined); + if (l.length == 0) { + return ; + } + + if (l.length == 1) { + return ({l}) + } + return ({l[0]}, {l[1]}); + + } + + render(): JSX.Element { + let wallet = this.balance; + if (this.gotError) { + return i18n.str`Error: could not retrieve balance information.`; + } + if (!wallet) { + return ; + } + console.log(wallet); + let paybackAvailable = false; + let listing = Object.keys(wallet).map((key) => { + let entry: WalletBalanceEntry = wallet[key]; + if (entry.paybackAmount.value != 0 || entry.paybackAmount.fraction != 0) { + paybackAvailable = true; + } + return ( +

+ {bigAmount(entry.available)} + {" "} + {this.formatPending(entry)} +

+ ); + }); + let link = chrome.extension.getURL("/src/pages/auditors.html"); + let linkElem = Trusted Auditors and Exchanges; + let paybackLink = chrome.extension.getURL("/src/pages/payback.html"); + let paybackLinkElem = Trusted Auditors and Exchanges; + return ( +
+ {listing.length > 0 ? listing : this.renderEmpty()} + {paybackAvailable && paybackLinkElem} + {linkElem} +
+ ); + } +} + + +function formatHistoryItem(historyItem: HistoryRecord) { + const d = historyItem.detail; + const t = historyItem.timestamp; + console.log("hist item", historyItem); + switch (historyItem.type) { + case "create-reserve": + return ( + + Bank requested reserve ({abbrev(d.reservePub)}) for {amountToPretty(d.requestedAmount)}. + + ); + case "confirm-reserve": { + // FIXME: eventually remove compat fix + let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??"; + let pub = abbrev(d.reservePub); + return ( + + Started to withdraw + {" "}{amountToPretty(d.requestedAmount)}{" "} + from {exchange} ({pub}). + + ); + } + case "offer-contract": { + let link = chrome.extension.getURL("view-contract.html"); + let linkElem = {abbrev(d.contractHash)}; + let merchantElem = {abbrev(d.merchantName, 15)}; + return ( + + Merchant {abbrev(d.merchantName, 15)} offered contract {abbrev(d.contractHash)}; + + ); + } + case "depleted-reserve": { + let exchange = d.exchangeBaseUrl ? (new URI(d.exchangeBaseUrl)).host() : "??"; + let amount = amountToPretty(d.requestedAmount); + let pub = abbrev(d.reservePub); + return ( + + Withdrew {amount} from {exchange} ({pub}). + + ); + } + case "pay": { + let url = d.fulfillmentUrl; + let merchantElem = {abbrev(d.merchantName, 15)}; + let fulfillmentLinkElem = view product; + return ( + + Paid {amountToPretty(d.amount)} to merchant {merchantElem}. ({fulfillmentLinkElem}) + + ); + } + default: + return (

{i18n.str`Unknown event (${historyItem.type})`}

); + } +} + + +class WalletHistory extends React.Component { + 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 ; + } + + 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 = ( +
+
+ {(new Date(record.timestamp)).toString()} +
+ {formatHistoryItem(record)} +
+ ); + + listing.push(item); + } + + if (listing.length > 0) { + return
{listing}
; + } + return

{i18n.str`Your wallet has no events recorded.`}

+ } + +} + + +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 (
+

Debug tools:

+ + + + +
+ + +
); +} + + +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 = ( +
+ +
+ + + + + +
+
+); + +document.addEventListener("DOMContentLoaded", () => { + ReactDOM.render(el, document.getElementById("content")!); +}) -- cgit v1.2.3