aboutsummaryrefslogtreecommitdiff
path: root/popup/popup.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'popup/popup.tsx')
-rw-r--r--popup/popup.tsx406
1 files changed, 246 insertions, 160 deletions
diff --git a/popup/popup.tsx b/popup/popup.tsx
index 5b2b1e9f9..5fcc871e2 100644
--- a/popup/popup.tsx
+++ b/popup/popup.tsx
@@ -23,9 +23,6 @@
*/
-/// <reference path="../lib/decl/mithril.d.ts" />
-/// <reference path="../lib/decl/lodash.d.ts" />
-
"use strict";
import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
@@ -33,10 +30,8 @@ import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
import {HistoryRecord, HistoryLevel} from "../lib/wallet/wallet";
import {AmountJson} from "../lib/wallet/types";
-declare var m: any;
declare var i18n: any;
-
function onUpdateNotification(f: () => void) {
let port = chrome.runtime.connect({name: "notifications"});
port.onMessage.addListener((msg, port) => {
@@ -45,116 +40,198 @@ function onUpdateNotification(f: () => void) {
}
+class Router extends preact.Component<any,any> {
+ static setRoute(s: string): void {
+ window.location.hash = s;
+ preact.rerender();
+ }
+
+ static getRoute(): string {
+ // Omit the '#' at the beginning
+ return window.location.hash.substring(1);
+ }
+
+ static onRoute(f: any): () => void {
+ this.routeHandlers.push(f);
+ return () => {
+ let i = this.routeHandlers.indexOf(f);
+ this.routeHandlers = this.routeHandlers.splice(i, 1);
+ }
+ }
+
+ static routeHandlers: any[] = [];
+
+ componentWillMount() {
+ console.log("router mounted");
+ window.onhashchange = () => {
+ this.forceUpdate();
+ for (let f of Router.routeHandlers) {
+ f();
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ console.log("router unmounted");
+ }
+
+
+ render(props: any, state: any): JSX.Element {
+ let route = window.location.hash.substring(1);
+ console.log("rendering route", route);
+ let defaultChild: JSX.Element|null = null;
+ for (let child of props.children) {
+ if (child.attributes["default"]) {
+ defaultChild = child;
+ }
+ if (child.attributes["route"] == route) {
+ return <div>{child}</div>;
+ }
+ }
+ if (defaultChild == null) {
+ throw Error("unknown route");
+ }
+ console.log("rendering default route");
+ Router.setRoute(defaultChild.attributes["route"]);
+ return <div>{defaultChild}</div>;
+ }
+}
+
export function main() {
console.log("popup main");
- m.route.mode = "hash";
- m.route(document.getElementById("content"), "/balance", {
- "/balance": WalletBalance,
- "/history": WalletHistory,
- "/debug": WalletDebug,
- });
- m.mount(document.getElementById("nav"), WalletNavBar);
-}
-console.log("this is popup");
+ let el = (
+ <div>
+ <WalletNavBar />
+ <Router>
+ <WalletBalance route="/balance" default/>
+ <WalletHistory route="/history"/>
+ <WalletDebug route="/debug"/>
+ </Router>
+ </div>
+ );
+
+ preact.render(el, document.getElementById("content")!);
+}
+interface TabProps extends preact.ComponentProps {
+ target: string;
+}
-function makeTab(target: string, name: string) {
+function Tab(props: TabProps) {
let cssClass = "";
- if (target == m.route()) {
+ if (props.target == Router.getRoute()) {
cssClass = "active";
}
- return m("a", {config: m.route, href: target, "class": cssClass}, name);
+ let onClick = (e: Event) => {
+ Router.setRoute(props.target);
+ e.preventDefault();
+ };
+ return (
+ <a onClick={onClick} href={props.target} className={cssClass}>
+ {props.children}
+ </a>
+ );
}
-namespace WalletNavBar {
- export function view() {
- return m("div#header.nav", [
- makeTab("/balance", i18n`Balance`),
- makeTab("/history", i18n`History`),
- makeTab("/debug", i18n`Debug`),
- ]);
+
+class WalletNavBar extends preact.Component<any,any> {
+ cancelSubscription: any;
+
+ componentWillMount() {
+ this.cancelSubscription = Router.onRoute(() => {
+ this.setState({});
+ });
}
- export function controller() {
- // empty
+ componentWillUnmount() {
+ if (this.cancelSubscription) {
+ this.cancelSubscription();
+ }
+ }
+
+ render() {
+ console.log("rendering nav bar");
+ return (
+ <div class="nav" id="header">
+ <Tab target="/balance">
+ Balance
+ </Tab>
+ <Tab target="/history">
+ History
+ </Tab>
+ <Tab target="/debug">
+ Debug
+ </Tab>
+ </div>);
}
}
-function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
- element.addEventListener("click", (e: Event) => {
+function ExtensionLink(props: any) {
+ let onClick = (e: Event) => {
chrome.tabs.create({
- "url": element.href
+ "url": chrome.extension.getURL(props.target)
});
e.preventDefault();
- });
+ };
+ return (
+ <a onClick={onClick}>
+ {props.children}
+ </a>)
}
+class WalletBalance extends preact.Component<any, any> {
+ myWallet: any;
+ gotError = false;
-namespace WalletBalance {
- export function controller() {
- return new Controller();
- }
-
- class Controller {
- myWallet: any;
- gotError = false;
+ componentWillMount() {
+ this.updateBalance();
- constructor() {
- this.updateBalance();
-
- onUpdateNotification(() => this.updateBalance());
- }
+ onUpdateNotification(() => this.updateBalance());
+ }
- updateBalance() {
- m.startComputation();
- chrome.runtime.sendMessage({type: "balances"}, (resp) => {
- if (resp.error) {
- this.gotError = true;
- console.error("could not retrieve balances", resp);
- m.endComputation();
- return;
- }
- this.gotError = false;
- console.log("got wallet", resp);
- this.myWallet = resp.balances;
- m.endComputation();
- });
- }
+ updateBalance() {
+ chrome.runtime.sendMessage({type: "balances"}, (resp) => {
+ if (resp.error) {
+ this.gotError = true;
+ console.error("could not retrieve balances", resp);
+ this.forceUpdate();
+ return;
+ }
+ this.gotError = false;
+ console.log("got wallet", resp);
+ this.myWallet = resp.balances;
+ this.forceUpdate();
+ });
}
- export function view(ctrl: Controller) {
- let wallet = ctrl.myWallet;
- if (ctrl.gotError) {
+ render(): JSX.Element {
+ let wallet = this.myWallet;
+ if (this.gotError) {
return i18n`Error: could not retrieve balance information.`;
}
if (!wallet) {
- throw Error("Could not retrieve wallet");
+ return <div></div>
}
- let listing = _.map(wallet, (x: any) => m("p", formatAmount(x)));
+ console.log(wallet);
+ let listing = Object.keys(wallet).map((key) => {
+ return <p>{formatAmount(wallet[key])}</p>
+ });
if (listing.length > 0) {
- return listing;
+ return <div>{listing}</div>;
}
- let helpLink = m("a",
- {
- config: openInExtension,
- href: chrome.extension.getURL(
- "pages/help/empty-wallet.html")
- },
- i18n`help`);
+ let helpLink = (
+ <ExtensionLink target="pages/help/empty-wallet.html">
+ help
+ </ExtensionLink>
+ );
return i18n.parts`You have no balance to show. Need some ${helpLink} getting started?`;
}
}
-function formatTimestamp(t: number) {
- let x = new Date(t);
- return x.toLocaleString();
-}
-
-
function formatAmount(amount: AmountJson) {
let v = amount.value + amount.fraction / 1e6;
return `${v.toFixed(2)} ${amount.currency}`;
@@ -166,17 +243,11 @@ function abbrev(s: string, n: number = 5) {
if (s.length > n) {
sAbbrev = s.slice(0, n) + "..";
}
- return m("span.abbrev", {title: s}, sAbbrev);
-}
-
-
-function retryPayment(url: string, contractHash: string) {
- return function() {
- chrome.tabs.create({
- "url": substituteFulfillmentUrl(url,
- {H_contract: contractHash})
- });
- }
+ return (
+ <span className="abbrev" title={s}>
+ {sAbbrev}
+ </span>
+ );
}
@@ -186,78 +257,85 @@ function formatHistoryItem(historyItem: HistoryRecord) {
console.log("hist item", historyItem);
switch (historyItem.type) {
case "create-reserve":
- return m("p",
- i18n.parts`Bank requested reserve (${abbrev(d.reservePub)}) for ${formatAmount(
- d.requestedAmount)}.`);
+ return (
+ <p>
+ {i18n.parts`Bank requested reserve (${abbrev(d.reservePub)}) for ${formatAmount(
+ d.requestedAmount)}.`}
+ </p>
+ );
case "confirm-reserve":
- return m("p",
- i18n.parts`Started to withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
- d.requestedAmount)}.`);
+ return (
+ <p>
+ {i18n.parts`Started to withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
+ d.requestedAmount)}.`}
+ </p>
+ );
case "offer-contract": {
let link = chrome.extension.getURL("view-contract.html");
- let linkElem = m("a", {href: link}, abbrev(d.contractHash));
- let merchantElem = m("em", abbrev(d.merchantName, 15));
- return m("p",
- i18n.parts`Merchant ${merchantElem} offered contract ${linkElem}.`);
+ let linkElem = <a href={link}>{abbrev(d.contractHash)}</a>;
+ let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
+ return (
+ <p>
+ {i18n.parts`Merchant ${merchantElem} offered contract ${linkElem}.`}
+ </p>
+ );
}
case "depleted-reserve":
- return m("p",
- i18n.parts`Withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
- d.requestedAmount)} completed.`);
+ return (<p>
+ {i18n.parts`Withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount(
+ d.requestedAmount)} completed.`}
+ </p>);
case "pay": {
let url = substituteFulfillmentUrl(d.fulfillmentUrl,
{H_contract: d.contractHash});
- let merchantElem = m("em", abbrev(d.merchantName, 15));
- let fulfillmentLinkElem = m(`a`,
- {href: url, onclick: openTab(url)},
- "view product");
- return m("p",
- i18n.parts`Confirmed payment of ${formatAmount(d.amount)} to merchant ${merchantElem}. (${fulfillmentLinkElem})`);
+ let merchantElem = <em>{abbrev(d.merchantName, 15)}</em>;
+ let fulfillmentLinkElem = <a href={url} onClick={openTab(url)}>view product</a>;
+ return (
+ <p>
+ {i18n.parts`Confirmed payment of ${formatAmount(d.amount)} to merchant ${merchantElem}. (${fulfillmentLinkElem})`}
+ </p>);
}
default:
- return m("p", i18n`Unknown event (${historyItem.type})`);
+ return (<p>i18n`Unknown event (${historyItem.type})`</p>);
}
}
-namespace WalletHistory {
- export function controller() {
- return new Controller();
- }
-
- class Controller {
- myHistory: any[];
- gotError = false;
+class WalletHistory extends preact.Component<any, any> {
+ myHistory: any[];
+ gotError = false;
- constructor() {
- this.update();
- onUpdateNotification(() => this.update());
- }
+ componentWillMount() {
+ this.update();
+ onUpdateNotification(() => this.update());
+ }
- update() {
- m.startComputation();
- chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
- if (resp.error) {
- this.gotError = true;
- console.error("could not retrieve history", resp);
- m.endComputation();
- return;
- }
- this.gotError = false;
- console.log("got history", resp.history);
- this.myHistory = resp.history;
- m.endComputation();
- });
- }
+ update() {
+ chrome.runtime.sendMessage({type: "get-history"}, (resp) => {
+ console.log("got history response");
+ if (resp.error) {
+ this.gotError = true;
+ console.error("could not retrieve history", resp);
+ this.forceUpdate();
+ return;
+ }
+ this.gotError = false;
+ console.log("got history", resp.history);
+ this.myHistory = resp.history;
+ this.forceUpdate();
+ });
}
- export function view(ctrl: Controller) {
- let history: HistoryRecord[] = ctrl.myHistory;
- if (ctrl.gotError) {
+ render(): JSX.Element {
+ console.log("rendering history");
+ let history: HistoryRecord[] = this.myHistory;
+ if (this.gotError) {
return i18n`Error: could not retrieve event history`;
}
+
if (!history) {
- throw Error("Could not retrieve history");
+ // We're not ready yet
+ return <span />;
}
let subjectMemo: {[s: string]: boolean} = {};
@@ -271,19 +349,24 @@ namespace WalletHistory {
}
subjectMemo[record.subjectId as string] = true;
- let item = m("div.historyItem", {}, [
- m("div.historyDate", {}, (new Date(record.timestamp)).toString()),
- formatHistoryItem(record)
- ]);
+ 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 m("div.container", listing);
+ return <div className="container">{listing}</div>;
}
- return i18n`Your wallet has no events recorded.`;
+ return <p>{i18n`Your wallet has no events recorded.`}</p>
}
+
}
@@ -305,21 +388,24 @@ function confirmReset() {
}
-var WalletDebug = {
- view() {
- return [
- m("button",
- {onclick: openExtensionPage("popup/popup.html")},
- "wallet tab"),
- m("button",
- {onclick: openExtensionPage("pages/show-db.html")},
- "show db"),
- m("br"),
- m("button", {onclick: confirmReset}, "reset"),
- m("button", {onclick: reload}, "reload chrome extension"),
- ]
- }
-};
+function WalletDebug(props: any) {
+ return (<div>
+ <p>Debug tools:</p>
+ <button onClick={openExtensionPage("popup/popup.html")}>
+ wallet tab
+ </button>
+ <button onClick={openExtensionPage("pages/show-db.html")}>
+ show db
+ </button>
+ <br />
+ <button onClick={confirmReset}>
+ reset
+ </button>
+ <button onClick={reload}>
+ reload chrome extension
+ </button>
+ </div>);
+}
function openExtensionPage(page: string) {