refactor / format

This commit is contained in:
Florian Dold 2019-09-05 16:23:54 +02:00
parent 8144b0f553
commit 51aa6d8146
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 149 additions and 174 deletions

View File

@ -23,14 +23,13 @@
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import { runOnceWhenReady } from "./common";
import { BenchmarkResult } from "../../walletTypes"; import { BenchmarkResult } from "../../walletTypes";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { registerMountPage } from "../renderHtml";
interface BenchmarkRunnerState { interface BenchmarkRunnerState {
@ -103,9 +102,6 @@ class BenchmarkRunner extends React.Component<any, BenchmarkRunnerState> {
} }
} }
registerMountPage(() => {
runOnceWhenReady(() => { return <BenchmarkRunner />;
ReactDOM.render(<BenchmarkRunner />, document.getElementById("container")!);
// Will be used by the backend to detect when the popup gets closed,
// so we can clear notifications
}); });

View File

@ -1,31 +0,0 @@
/*
This file is part of TALER
(C) 2018 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/>
*/
/**
* Common helper functions for all web extension pages.
*/
/**
* Make sure that a function is executed exactly once
* after the DOM has been loaded.
*/
export function runOnceWhenReady(f: () => void): void {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", f);
return;
}
f();
}

View File

@ -72,7 +72,7 @@
<body> <body>
<section id="main"> <section id="main">
<h1>GNU Taler Wallet</h1> <h1>GNU Taler Wallet</h1>
<article id="contract" class="fade"></article> <article id="container" class="fade"></article>
</section> </section>
</body> </body>

View File

@ -24,19 +24,13 @@
*/ */
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import { runOnceWhenReady } from "./common";
import { ExchangeRecord, ProposalDownloadRecord } from "../../dbTypes";
import { ContractTerms } from "../../talerTypes";
import { CheckPayResult, PreparePayResult } from "../../walletTypes"; import { CheckPayResult, PreparePayResult } from "../../walletTypes";
import { renderAmount, ProgressButton } from "../renderHtml"; import { renderAmount, ProgressButton, registerMountPage } from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import * as ReactDOM from "react-dom";
import URI = require("urijs"); import URI = require("urijs");
import { WalletApiError } from "../wxApi";
import * as Amounts from "../../amounts"; import * as Amounts from "../../amounts";
@ -153,22 +147,11 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }) {
); );
} }
runOnceWhenReady(() => { registerMountPage(() => {
try {
const url = new URI(document.location.href); const url = new URI(document.location.href);
const query: any = URI.parseQuery(url.query()); const query: any = URI.parseQuery(url.query());
let talerPayUri = query.talerPayUri; let talerPayUri = query.talerPayUri;
ReactDOM.render( return <TalerPayDialog talerPayUri={talerPayUri} />;
<TalerPayDialog talerPayUri={talerPayUri} />,
document.getElementById("contract")!,
);
} catch (e) {
ReactDOM.render(
<span>Fatal error: {e.message}</span>,
document.getElementById("contract")!,
);
console.error(e);
}
}); });

View File

@ -12,7 +12,7 @@
</head> </head>
<body> <body>
<div id="content" style="margin:0;padding:0"></div> <div id="container" style="margin:0;padding:0"></div>
</body> </body>
</html> </html>

View File

@ -26,8 +26,6 @@
*/ */
import * as i18n from "../../i18n"; import * as i18n from "../../i18n";
import { runOnceWhenReady } from "./common";
import { AmountJson } from "../../amounts"; import { AmountJson } from "../../amounts";
import * as Amounts from "../../amounts"; import * as Amounts from "../../amounts";
@ -37,7 +35,12 @@ import {
WalletBalanceEntry, WalletBalanceEntry,
} from "../../walletTypes"; } from "../../walletTypes";
import { abbrev, renderAmount, PageLink } from "../renderHtml"; import {
abbrev,
renderAmount,
PageLink,
registerMountPage,
} from "../renderHtml";
import * as wxApi from "../wxApi"; import * as wxApi from "../wxApi";
import * as React from "react"; import * as React from "react";
@ -196,8 +199,7 @@ function EmptyBalanceView() {
<div> <div>
<i18n.Translate wrap="p"> <i18n.Translate wrap="p">
You have no balance to show. Need some{" "} You have no balance to show. Need some{" "}
<PageLink pageName="welcome.html">help</PageLink> getting <PageLink pageName="welcome.html">help</PageLink> getting started?
started?
</i18n.Translate> </i18n.Translate>
</div> </div>
); );
@ -303,7 +305,8 @@ class WalletBalanceView extends React.Component<any, any> {
<div> <div>
<p>{i18n.str`Error: could not retrieve balance information.`}</p> <p>{i18n.str`Error: could not retrieve balance information.`}</p>
<p> <p>
Click <PageLink pageName="welcome.html">here</PageLink> for help and diagnostics. Click <PageLink pageName="welcome.html">here</PageLink> for help and
diagnostics.
</p> </p>
</div> </div>
); );
@ -551,7 +554,8 @@ function openTab(page: string) {
}; };
} }
const el = ( function WalletPopup() {
return (
<div> <div>
<WalletNavBar /> <WalletNavBar />
<div style={{ margin: "1em" }}> <div style={{ margin: "1em" }}>
@ -563,10 +567,9 @@ const el = (
</div> </div>
</div> </div>
); );
}
runOnceWhenReady(() => { registerMountPage(() => {
ReactDOM.render(el, document.getElementById("content")!);
// Will be used by the backend to detect when the popup gets closed,
// so we can clear notifications
chrome.runtime.connect({ name: "popup" }); chrome.runtime.connect({ name: "popup" });
return <WalletPopup />
}); });

View File

@ -20,24 +20,18 @@
* @author Florian Dold * @author Florian Dold
*/ */
/** /**
* Imports. * Imports.
*/ */
import { AmountJson } from "../amounts"; import { AmountJson } from "../amounts";
import * as Amounts from "../amounts"; import * as Amounts from "../amounts";
import { import { DenominationRecord } from "../dbTypes";
DenominationRecord, import { ReserveCreationInfo } from "../walletTypes";
} from "../dbTypes";
import {
ReserveCreationInfo,
} from "../walletTypes";
import * as moment from "moment"; import * as moment from "moment";
import * as i18n from "../i18n"; import * as i18n from "../i18n";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
/** /**
* Render amount as HTML, which non-breaking space between * Render amount as HTML, which non-breaking space between
* decimal value and currency. * decimal value and currency.
@ -53,11 +47,15 @@ export function renderAmount(amount: AmountJson | string) {
return <span>(invalid amount)</span>; return <span>(invalid amount)</span>;
} }
const x = a.value + a.fraction / Amounts.fractionalBase; const x = a.value + a.fraction / Amounts.fractionalBase;
return <span>{x}&nbsp;{a.currency}</span>; return (
<span>
{x}&nbsp;{a.currency}
</span>
);
} }
export const AmountView = ({amount}: {amount: AmountJson | string}) => renderAmount(amount); export const AmountView = ({ amount }: { amount: AmountJson | string }) =>
renderAmount(amount);
/** /**
* Abbreviate a string to a given length, and show the full * Abbreviate a string to a given length, and show the full
@ -75,23 +73,23 @@ export function abbrev(s: string, n: number = 5) {
); );
} }
interface CollapsibleState { interface CollapsibleState {
collapsed: boolean; collapsed: boolean;
} }
interface CollapsibleProps { interface CollapsibleProps {
initiallyCollapsed: boolean; initiallyCollapsed: boolean;
title: string; title: string;
} }
/** /**
* Component that shows/hides its children when clicking * Component that shows/hides its children when clicking
* a heading. * a heading.
*/ */
export class Collapsible extends React.Component<CollapsibleProps, CollapsibleState> { export class Collapsible extends React.Component<
CollapsibleProps,
CollapsibleState
> {
constructor(props: CollapsibleProps) { constructor(props: CollapsibleProps) {
super(props); super(props);
this.state = { collapsed: props.initiallyCollapsed }; this.state = { collapsed: props.initiallyCollapsed };
@ -106,19 +104,30 @@ export class Collapsible extends React.Component<CollapsibleProps, CollapsibleSt
e.preventDefault(); e.preventDefault();
}; };
if (this.state.collapsed) { if (this.state.collapsed) {
return <h2><a className="opener opener-collapsed" href="#" onClick={doOpen}>{this.props.title}</a></h2>; return (
<h2>
<a className="opener opener-collapsed" href="#" onClick={doOpen}>
{this.props.title}
</a>
</h2>
);
} }
return ( return (
<div> <div>
<h2><a className="opener opener-open" href="#" onClick={doClose}>{this.props.title}</a></h2> <h2>
<a className="opener opener-open" href="#" onClick={doClose}>
{this.props.title}
</a>
</h2>
{this.props.children} {this.props.children}
</div> </div>
); );
} }
} }
function AuditorDetailsView(props: {
function AuditorDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element { rci: ReserveCreationInfo | null;
}): JSX.Element {
const rci = props.rci; const rci = props.rci;
console.log("rci", rci); console.log("rci", rci);
if (!rci) { if (!rci) {
@ -129,27 +138,33 @@ function AuditorDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element
); );
} }
if (rci.exchangeInfo.auditors.length === 0) { if (rci.exchangeInfo.auditors.length === 0) {
return ( return <p>The exchange is not audited by any auditors.</p>;
<p>
The exchange is not audited by any auditors.
</p>
);
} }
return ( return (
<div> <div>
{rci.exchangeInfo.auditors.map((a) => ( {rci.exchangeInfo.auditors.map(a => (
<div> <div>
<h3>Auditor {a.auditor_url}</h3> <h3>Auditor {a.auditor_url}</h3>
<p>Public key: <ExpanderText text={a.auditor_pub} /></p> <p>
<p>Trusted: {rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}</p> Public key: <ExpanderText text={a.auditor_pub} />
<p>Audits {a.denomination_keys.length} of {rci.numOfferedDenoms} denominations</p> </p>
<p>
Trusted:{" "}
{rci.trustedAuditorPubs.indexOf(a.auditor_pub) >= 0 ? "yes" : "no"}
</p>
<p>
Audits {a.denomination_keys.length} of {rci.numOfferedDenoms}{" "}
denominations
</p>
</div> </div>
))} ))}
</div> </div>
); );
} }
function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element { function FeeDetailsView(props: {
rci: ReserveCreationInfo | null;
}): JSX.Element {
const rci = props.rci; const rci = props.rci;
if (!rci) { if (!rci) {
return ( return (
@ -198,7 +213,7 @@ function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
</tr> </tr>
</thead>, </thead>,
<tbody> <tbody>
{rci!.wireFees.feesForType[s].map((f) => ( {rci!.wireFees.feesForType[s].map(f => (
<tr> <tr>
<td>{moment.unix(f.endStamp).format("llll")}</td> <td>{moment.unix(f.endStamp).format("llll")}</td>
<td>{renderAmount(f.wireFee)}</td> <td>{renderAmount(f.wireFee)}</td>
@ -215,10 +230,18 @@ function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
return ( return (
<div> <div>
<h3>Overview</h3> <h3>Overview</h3>
<p>Public key: <ExpanderText text={rci.exchangeInfo.masterPublicKey} /></p> <p>
<p>{i18n.str`Withdrawal fees:`} {withdrawFee}</p> Public key: <ExpanderText text={rci.exchangeInfo.masterPublicKey} />
<p>{i18n.str`Rounding loss:`} {overhead}</p> </p>
<p>{i18n.str`Earliest expiration (for deposit): ${moment.unix(rci.earliestDepositExpiration).fromNow()}`}</p> <p>
{i18n.str`Withdrawal fees:`} {withdrawFee}
</p>
<p>
{i18n.str`Rounding loss:`} {overhead}
</p>
<p>{i18n.str`Earliest expiration (for deposit): ${moment
.unix(rci.earliestDepositExpiration)
.fromNow()}`}</p>
<h3>Coin Fees</h3> <h3>Coin Fees</h3>
<div style={{ overflow: "auto" }}> <div style={{ overflow: "auto" }}>
<table className="pure-table"> <table className="pure-table">
@ -231,9 +254,7 @@ function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
<th>{i18n.str`Deposit Fee`}</th> <th>{i18n.str`Deposit Fee`}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>{uniq.map(row)}</tbody>
{uniq.map(row)}
</tbody>
</table> </table>
</div> </div>
<h3>Wire Fees</h3> <h3>Wire Fees</h3>
@ -249,7 +270,9 @@ function FeeDetailsView(props: {rci: ReserveCreationInfo|null}): JSX.Element {
/** /**
* Shows details about a withdraw request. * Shows details about a withdraw request.
*/ */
export function WithdrawDetailView(props: {rci: ReserveCreationInfo | null}): JSX.Element { export function WithdrawDetailView(props: {
rci: ReserveCreationInfo | null;
}): JSX.Element {
const rci = props.rci; const rci = props.rci;
return ( return (
<div> <div>
@ -263,12 +286,10 @@ export function WithdrawDetailView(props: {rci: ReserveCreationInfo | null}): JS
); );
} }
interface ExpanderTextProps { interface ExpanderTextProps {
text: string; text: string;
} }
/** /**
* Show a heading with a toggle to show/hide the expandable content. * Show a heading with a toggle to show/hide the expandable content.
*/ */
@ -276,16 +297,13 @@ export function ExpanderText({ text }: ExpanderTextProps) {
return <span>{text}</span>; return <span>{text}</span>;
} }
export interface LoadingButtonProps { export interface LoadingButtonProps {
loading: boolean; loading: boolean;
} }
export function ProgressButton( export function ProgressButton(
props: props: React.PropsWithChildren<LoadingButtonProps> &
& React.PropsWithChildren<LoadingButtonProps> React.DetailedHTMLProps<
& React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>, React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement HTMLButtonElement
>, >,
@ -296,8 +314,14 @@ export function ProgressButton(
type="button" type="button"
{...props} {...props}
> >
{props.loading ? <span><object className="svg-icon svg-baseline" data="/img/spinner-bars.svg" /></span> : null} {props.loading ? (
{" "} <span>
<object
className="svg-icon svg-baseline"
data="/img/spinner-bars.svg"
/>
</span>
) : null}{" "}
{props.children} {props.children}
</button> </button>
); );
@ -311,10 +335,7 @@ export function registerMountPage(mainFn: () => React.ReactElement) {
if (!container) { if (!container) {
throw Error("container not found, can't mount page contents"); throw Error("container not found, can't mount page contents");
} }
ReactDOM.render( ReactDOM.render(mainElement, container);
mainElement,
container,
);
} catch (e) { } catch (e) {
document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`; document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
console.error("got error", e); console.error("got error", e);
@ -331,5 +352,9 @@ export function registerMountPage(mainFn: () => React.ReactElement) {
export function PageLink(props: React.PropsWithChildren<{ pageName: string }>) { export function PageLink(props: React.PropsWithChildren<{ pageName: string }>) {
const url = chrome.extension.getURL(`/src/webex/pages/${props.pageName}`); const url = chrome.extension.getURL(`/src/webex/pages/${props.pageName}`);
return <a className="actionLink" href={url} target="_blank">{props.children}</a>; return (
<a className="actionLink" href={url} target="_blank">
{props.children}
</a>
);
} }

View File

@ -71,7 +71,6 @@
"src/webex/pages/add-auditor.tsx", "src/webex/pages/add-auditor.tsx",
"src/webex/pages/auditors.tsx", "src/webex/pages/auditors.tsx",
"src/webex/pages/benchmark.tsx", "src/webex/pages/benchmark.tsx",
"src/webex/pages/common.ts",
"src/webex/pages/error.tsx", "src/webex/pages/error.tsx",
"src/webex/pages/logs.tsx", "src/webex/pages/logs.tsx",
"src/webex/pages/pay.tsx", "src/webex/pages/pay.tsx",