From 7fb527b0009a29605c32e663ab9e6a812a8cc5a8 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 5 Oct 2016 17:04:57 +0200 Subject: refactor confirm-contract rendering --- pages/confirm-contract.tsx | 187 +++++++++++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 67 deletions(-) (limited to 'pages/confirm-contract.tsx') diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx index f162dca85..8e553b05b 100644 --- a/pages/confirm-contract.tsx +++ b/pages/confirm-contract.tsx @@ -22,113 +22,166 @@ */ -/// -import MithrilComponent = _mithril.MithrilComponent; +/// import {substituteFulfillmentUrl} from "../lib/wallet/helpers"; import m from "mithril"; import {Contract, AmountJson} from "../lib/wallet/types"; import {renderContract, prettyAmount} from "../lib/wallet/renderHtml"; "use strict"; -const Details = { - controller() { - return {collapsed: m.prop(true)}; - }, - view(ctrl: any, contract: Contract) { - if (ctrl.collapsed()) { - return m("div", [ - m("button.linky", { - onclick: () => { - ctrl.collapsed(false); - } - }, "show more details") - ]); + +interface DetailState { + collapsed: boolean; +} + +interface DetailProps { + contract: Contract; +} + +let h = preact.h; + + +class Details extends preact.Component { + constructor() { + super(); + this.state = { + collapsed: true + }; + } + + render(props: DetailProps, state: DetailState) { + if (state.collapsed) { + return h("div", {}, + h("button", { + className: "linky", + onClick: () => { + this.setState({collapsed: false}); + } + }, "show more details")); } else { - return m("div", [ - m("button.linky", { - onclick: () => { - ctrl.collapsed(true); - } - }, "show less details"), - m("div", [ - "Accepted exchanges:", - m("ul", - contract.exchanges.map(e => m("li", `${e.url}: ${e.master_pub}`))) - ]) - ]); + return h("div", {}, + h("button", { + className: "linky", + onClick: () => { + this.setState({collapsed: true}); + } + }, "show less details"), + h("div", {}, + "Accepted exchanges:", + h("ul", {}, + ...props.contract.exchanges.map( + e => h("li", {}, `${e.url}: ${e.master_pub}`))))); } } -}; +} -export function main() { - let url = URI(document.location.href); - let query: any = URI.parseQuery(url.query()); - let offer = JSON.parse(query.offer); - console.dir(offer); - let contract = offer.contract; - let error: string|null = null; - let payDisabled = true; - - var Contract = { - view(ctrl: any) { - return [ - renderContract(contract), - m("button.accept", - {onclick: doPayment, disabled: payDisabled}, - i18n`Confirm Payment`), - (error ? m("p.errorbox", error) : []), - m(Details, contract) - ]; +interface ContractPromptProps { + offer: any; +} + +interface ContractPromptState { + error: string|null; + payDisabled: boolean; +} + +class ContractPrompt extends preact.Component { + constructor() { + super(); + this.state = { + error: null, + payDisabled: true, } - }; + } - m.mount(document.getElementById("contract")!, Contract); + componentWillMount() { + this.checkPayment(); + } + + componentWillUnmount() { + // FIXME: abort running ops + } - function checkPayment() { - chrome.runtime.sendMessage({type: 'check-pay', detail: {offer}}, (resp) => { + checkPayment() { + let msg = { + type: 'check-pay', + detail: { + offer: this.props.offer + } + }; + chrome.runtime.sendMessage(msg, (resp) => { if (resp.error) { console.log("check-pay error", JSON.stringify(resp)); switch (resp.error) { case "coins-insufficient": - error = i18n`You have insufficient funds of the requested currency in your wallet.`; + this.state.error = i18n`You have insufficient funds of the requested currency in your wallet.`; break; default: - error = `Error: ${resp.error}`; + this.state.error = `Error: ${resp.error}`; break; } - payDisabled = true; + this.state.payDisabled = true; } else { - payDisabled = false; - error = null; + this.state.payDisabled = false; + this.state.error = null; } - m.redraw(); - window.setTimeout(checkPayment, 300); + this.forceUpdate(); + window.setTimeout(() => this.checkPayment(), 300); }); } - checkPayment(); - - - function doPayment() { - let d = {offer}; + doPayment() { + let d = {offer: this.props.offer}; chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => { if (resp.error) { console.log("confirm-pay error", JSON.stringify(resp)); switch (resp.error) { case "coins-insufficient": - error = "You do not have enough coins of the requested currency."; + this.state.error = "You do not have enough coins of the" + + " requested currency."; break; default: - error = `Error: ${resp.error}`; + this.state.error = `Error: ${resp.error}`; break; } - m.redraw(); + preact.rerender(); return; } let c = d.offer.contract; console.log("contract", c); document.location.href = substituteFulfillmentUrl(c.fulfillment_url, - offer); + this.props.offer); }); } + + + render(props: ContractPromptProps, state: ContractPromptState) { + let c = props.offer.contract; + return h("div", {}, + renderContract(c), + h("button", + { + onClick: () => this.doPayment(), + disabled: state.payDisabled, + "className": "accept" + }, + i18n`Confirm Payment`), + (state.error ? h("p", + {className: "errorbox"}, + state.error) : h("p", "")), + h(Details, {contract: c}) + ); + } +} + + +export function main() { + let url = URI(document.location.href); + let query: any = URI.parseQuery(url.query()); + let offer = JSON.parse(query.offer); + console.dir(offer); + let contract = offer.contract; + + + let prompt = h(ContractPrompt, {offer}); + preact.render(prompt, document.getElementById("contract")!); } -- cgit v1.2.3 From 0c8a6e21f0324e25d1ec59bcdddf8c5c49b01421 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 5 Oct 2016 17:38:02 +0200 Subject: prettier syntax (JSX) for rendering --- gulpfile.js | 1 + lib/decl/preact.d.ts | 4 ++++ lib/vendor/preact.js | 1 + lib/wallet/renderHtml.ts | 49 --------------------------------------------- lib/wallet/renderHtml.tsx | 50 ++++++++++++++++++++++++++++++++++++++++++++++ pages/confirm-contract.tsx | 17 ++++++++-------- tsconfig.json | 3 ++- 7 files changed, 67 insertions(+), 58 deletions(-) delete mode 100644 lib/wallet/renderHtml.ts create mode 100644 lib/wallet/renderHtml.tsx (limited to 'pages/confirm-contract.tsx') diff --git a/gulpfile.js b/gulpfile.js index 40461b27a..fabfcbdeb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -121,6 +121,7 @@ const paths = { const tsBaseArgs = { target: "es6", jsx: "react", + reactNamespace: "preact", experimentalDecorators: true, module: "system", sourceMap: true, diff --git a/lib/decl/preact.d.ts b/lib/decl/preact.d.ts index fb8a0b764..dfa094e0e 100644 --- a/lib/decl/preact.d.ts +++ b/lib/decl/preact.d.ts @@ -56,12 +56,16 @@ declare namespace preact { function h(node:ComponentConstructor, params:PropsType, ...children:(JSX.Element|string)[]):JSX.Element; function h(node:string, params:JSX.HTMLAttributes|JSX.SVGAttributes, ...children:(JSX.Element|string)[]):JSX.Element; + function createElement(node:ComponentConstructor, params:PropsType, ...children:(JSX.Element|string)[]):JSX.Element; + function createElement(node:string, params:JSX.HTMLAttributes|JSX.SVGAttributes, ...children:(JSX.Element|string)[]):JSX.Element; + function render(node:JSX.Element, parent:Element, merge?:boolean):Element; function rerender():void; function cloneElement(element:JSX.Element, props:any):JSX.Element; + var options:{ syncComponentUpdates?:boolean; debounceRendering?:(render:() => void) => void; diff --git a/lib/vendor/preact.js b/lib/vendor/preact.js index 3b06bb6af..e0239355e 100644 --- a/lib/vendor/preact.js +++ b/lib/vendor/preact.js @@ -460,6 +460,7 @@ render: function() {} }); exports.h = h; + exports.createElement = h; exports.cloneElement = cloneElement; exports.Component = Component; exports.render = render; diff --git a/lib/wallet/renderHtml.ts b/lib/wallet/renderHtml.ts deleted file mode 100644 index 022bce113..000000000 --- a/lib/wallet/renderHtml.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of TALER - (C) 2016 INRIA - - 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 - */ - -/** - * Helpers functions to render Taler-related data structures to HTML. - * - * @author Florian Dold - */ - - -import {AmountJson, Contract} from "./types"; - - -let h = preact.h; - -export function prettyAmount(amount: AmountJson) { - let v = amount.value + amount.fraction / 1e6; - return `${v.toFixed(2)} ${amount.currency}`; -} - -export function renderContract(contract: Contract): JSX.Element { - let merchantName = m("strong", contract.merchant.name); - let amount = m("strong", prettyAmount(contract.amount)); - - return h("div", {}, - h("p", {}, - i18n.parts`${merchantName} - wants to enter a contract over ${amount} - with you.`), - h("p", {}, - i18n`You are about to purchase:`), - h('ul', {}, - ...contract.products.map( - (p: any) => h("li", {}, - `${p.description}: ${prettyAmount(p.price)}`)))); -} \ No newline at end of file diff --git a/lib/wallet/renderHtml.tsx b/lib/wallet/renderHtml.tsx new file mode 100644 index 000000000..f3059f940 --- /dev/null +++ b/lib/wallet/renderHtml.tsx @@ -0,0 +1,50 @@ +/* + This file is part of TALER + (C) 2016 INRIA + + 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 + */ + +/** + * Helpers functions to render Taler-related data structures to HTML. + * + * @author Florian Dold + */ + + +import {AmountJson, Contract} from "./types"; + +export function prettyAmount(amount: AmountJson) { + let v = amount.value + amount.fraction / 1e6; + return `${v.toFixed(2)} ${amount.currency}`; +} + +export function renderContract(contract: Contract): JSX.Element { + let merchantName = m("strong", contract.merchant.name); + let amount = m("strong", prettyAmount(contract.amount)); + + return ( +
+

{ + i18n.parts`${merchantName} + wants to enter a contract over ${amount} + with you.`} +

+

{i18n`You are about to purchase:`}

+
    + {contract.products.map( + (p: any) => (
  • {`${p.description}: ${prettyAmount(p.price)}`}
  • )) + } +
+
+ ); +} \ No newline at end of file diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx index 8e553b05b..9c1752568 100644 --- a/pages/confirm-contract.tsx +++ b/pages/confirm-contract.tsx @@ -51,13 +51,14 @@ class Details extends preact.Component { render(props: DetailProps, state: DetailState) { if (state.collapsed) { - return h("div", {}, - h("button", { - className: "linky", - onClick: () => { - this.setState({collapsed: false}); - } - }, "show more details")); + return ( +
+ +
+ ); } else { return h("div", {}, h("button", { @@ -167,7 +168,7 @@ class ContractPrompt extends preact.Component