From 7fb527b0009a29605c32e663ab9e6a812a8cc5a8 Mon Sep 17 00:00:00 2001
From: Florian Dold <florian.dold@gmail.com>
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 @@
  */
 
 
-/// <reference path="../lib/decl/handlebars/handlebars.d.ts" />
-import MithrilComponent = _mithril.MithrilComponent;
+/// <reference path="../lib/decl/preact.d.ts" />
 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<DetailProps, DetailState> {
+  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<ContractPromptProps, ContractPromptState> {
+  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 <florian.dold@gmail.com>
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<PropsType>(node:ComponentConstructor<PropsType, any>, 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<PropsType>(node:ComponentConstructor<PropsType, any>, 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * 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 (
+    <div>
+      <p>{
+        i18n.parts`${merchantName}
+               wants to enter a contract over ${amount}
+               with you.`}
+      </p>
+      <p>{i18n`You are about to purchase:`}</p>
+      <ul>
+        {contract.products.map(
+          (p: any) => (<li>{`${p.description}: ${prettyAmount(p.price)}`}</li>))
+        }
+      </ul>
+    </div>
+  );
+}
\ 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<DetailProps, DetailState> {
 
   render(props: DetailProps, state: DetailState) {
     if (state.collapsed) {
-      return h("div", {},
-               h("button", {
-                 className: "linky",
-                 onClick: () => {
-                   this.setState({collapsed: false});
-                 }
-               }, "show more details"));
+      return (
+        <div>
+          <button className="linky"
+                  onClick={() => { this.setState({collapsed: false})}}>
+            show more details
+          </button>
+        </div>
+      );
     } else {
       return h("div", {},
                h("button", {
@@ -167,7 +168,7 @@ class ContractPrompt extends preact.Component<ContractPromptProps, ContractPromp
                i18n`Confirm Payment`),
              (state.error ? h("p",
                               {className: "errorbox"},
-                              state.error) : h("p",  "")),
+                              state.error) : h("p", "")),
              h(Details, {contract: c})
     );
   }
diff --git a/tsconfig.json b/tsconfig.json
index ced83818b..7c964ff94 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,7 @@
   "compilerOptions": {
     "target": "es6",
     "jsx": "react",
+    "reactNamespace": "preact",
     "experimentalDecorators": true,
     "module": "system",
     "sourceMap": true,
@@ -26,11 +27,11 @@
     "lib/wallet/helpers.ts",
     "lib/wallet/http.ts",
     "lib/wallet/query.ts",
-    "lib/wallet/renderHtml.ts",
     "lib/wallet/types.ts",
     "lib/wallet/wallet.ts",
     "lib/wallet/wxApi.ts",
     "lib/wallet/wxMessaging.ts",
+    "lib/wallet/renderHtml.tsx",
     "background/main.ts",
     "content_scripts/notify.ts",
     "popup/popup.tsx",
-- 
cgit v1.2.3

