move to ES6, clean up views

This commit is contained in:
Florian Dold 2016-09-14 15:20:18 +02:00
parent 8852857347
commit fc6db1824e
5 changed files with 2326 additions and 2253 deletions

View File

@ -117,7 +117,7 @@ const paths = {
const tsBaseArgs = {
target: "es5",
target: "es6",
jsx: "react",
experimentalDecorators: true,
module: "system",

338
lib/vendor/mithril.js vendored
View File

@ -1,28 +1,3 @@
/*
The MIT License (MIT)
Copyright (c) 2014 Leo Horie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
;(function (global, factory) { // eslint-disable-line
"use strict"
/* eslint-disable no-undef */
@ -35,11 +10,11 @@
global.m = m
}
/* eslint-enable no-undef */
})(typeof window !== "undefined" ? window : {}, function (global, undefined) { // eslint-disable-line
})(typeof window !== "undefined" ? window : this, function (global, undefined) { // eslint-disable-line
"use strict"
m.version = function () {
return "v0.2.2-rc.1"
return "v0.2.5"
}
var hasOwn = {}.hasOwnProperty
@ -63,9 +38,24 @@
function noop() {}
/* eslint-disable max-len */
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/
/* eslint-enable max-len */
var voidElements = {
AREA: 1,
BASE: 1,
BR: 1,
COL: 1,
COMMAND: 1,
EMBED: 1,
HR: 1,
IMG: 1,
INPUT: 1,
KEYGEN: 1,
LINK: 1,
META: 1,
PARAM: 1,
SOURCE: 1,
TRACK: 1,
WBR: 1
}
// caching commonly used variables
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame
@ -106,7 +96,7 @@
classes.push(match[2])
} else if (match[3][0] === "[") {
var pair = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3])
cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true)
cell.attrs[pair[1]] = pair[3] || ""
}
}
@ -151,7 +141,9 @@
* or splat (optional)
*/
function m(tag, pairs) {
for (var args = [], i = 1; i < arguments.length; i++) {
var args = []
for (var i = 1, length = arguments.length; i < length; i++) {
args[i - 1] = arguments[i]
}
@ -247,8 +239,10 @@
})
var actions = []
for (var prop in existing) if (hasOwn.call(existing, prop)) {
actions.push(existing[prop])
for (var prop in existing) {
if (hasOwn.call(existing, prop)) {
actions.push(existing[prop])
}
}
var changes = actions.sort(sortChanges)
@ -379,8 +373,10 @@
if (cached.controllers) {
forEach(cached.controllers, function (controller) {
if (controller.onunload) controller.onunload({preventDefault: noop});
});
if (controller.onunload) {
controller.onunload({preventDefault: noop})
}
})
}
}
}
@ -481,7 +477,7 @@
nodes = injectHTML(parentElement, index, data)
} else {
nodes = [$document.createTextNode(data)]
if (!parentElement.nodeName.match(voidElements)) {
if (!(parentElement.nodeName in voidElements)) {
insertNode(parentElement, nodes[0], index)
}
}
@ -761,7 +757,9 @@
var unloaders = []
function updateLists(views, controllers, view, controller) {
if (controller.onunload != null) {
if (controller.onunload != null &&
unloaders.map(function (u) { return u.handler })
.indexOf(controller.onunload) < 0) {
unloaders.push({
controller: controller,
handler: controller.onunload
@ -773,11 +771,32 @@
}
var forcing = false
function checkView(data, view, cached, cachedControllers, controllers, views) {
var controller = getController(cached.views, view, cachedControllers, data.controller)
function checkView(
data,
view,
cached,
cachedControllers,
controllers,
views
) {
var controller = getController(
cached.views,
view,
cachedControllers,
data.controller)
var key = data && data.attrs && data.attrs.key
data = pendingRequests === 0 || forcing || cachedControllers && cachedControllers.indexOf(controller) > -1 ? data.view(controller) : {tag: "placeholder"}
if (data.subtree === "retain") return data;
if (pendingRequests === 0 ||
forcing ||
cachedControllers &&
cachedControllers.indexOf(controller) > -1) {
data = data.view(controller)
} else {
data = {tag: "placeholder"}
}
if (data.subtree === "retain") return data
data.attrs = data.attrs || {}
data.attrs.key = key
updateLists(views, controllers, view, controller)
@ -842,6 +861,9 @@
// set attributes first, then create children
var attrs = constructAttrs(data, node, namespace, hasKeys)
// add the node to its parent before attaching children to it
insertNode(parentElement, node, index)
var children = constructChildren(data, node, cached, editable,
namespace, configs)
@ -865,7 +887,7 @@
controllers)
}
if (isNew || shouldReattach === true && node != null) {
if (!isNew && shouldReattach === true && node != null) {
insertNode(parentElement, node, index)
}
@ -984,24 +1006,28 @@
}
function copyStyleAttrs(node, dataAttr, cachedAttr) {
for (var rule in dataAttr) if (hasOwn.call(dataAttr, rule)) {
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) {
node.style[rule] = dataAttr[rule]
for (var rule in dataAttr) {
if (hasOwn.call(dataAttr, rule)) {
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) {
node.style[rule] = dataAttr[rule]
}
}
}
for (rule in cachedAttr) if (hasOwn.call(cachedAttr, rule)) {
if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
for (rule in cachedAttr) {
if (hasOwn.call(cachedAttr, rule)) {
if (!hasOwn.call(dataAttr, rule)) node.style[rule] = ""
}
}
}
function shouldUseSetAttribute(attrName) {
return attrName !== "list" &&
attrName !== "style" &&
attrName !== "form" &&
attrName !== "type" &&
attrName !== "width" &&
attrName !== "height"
var shouldUseSetAttribute = {
list: 1,
style: 1,
form: 1,
type: 1,
width: 1,
height: 1
}
function setSingleAttr(
@ -1032,7 +1058,7 @@
attrName === "className" ? "class" : attrName,
dataAttr)
}
} else if (attrName in node && shouldUseSetAttribute(attrName)) {
} else if (attrName in node && !shouldUseSetAttribute[attrName]) {
// handle cases that are properties (but ignore cases where we
// should use setAttribute instead)
//
@ -1064,7 +1090,7 @@
tag,
namespace
) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || ($document.activeElement === node)) {
cachedAttrs[attrName] = dataAttr
try {
return setSingleAttr(
@ -1087,16 +1113,18 @@
}
function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
for (var attrName in dataAttrs) if (hasOwn.call(dataAttrs, attrName)) {
if (trySetAttr(
node,
attrName,
dataAttrs[attrName],
cachedAttrs[attrName],
cachedAttrs,
tag,
namespace)) {
continue
for (var attrName in dataAttrs) {
if (hasOwn.call(dataAttrs, attrName)) {
if (trySetAttr(
node,
attrName,
dataAttrs[attrName],
cachedAttrs[attrName],
cachedAttrs,
tag,
namespace)) {
continue
}
}
}
return cachedAttrs
@ -1148,9 +1176,41 @@
$document.createRange().createContextualFragment(data))
} catch (e) {
parentElement.insertAdjacentHTML("beforeend", data)
replaceScriptNodes(parentElement)
}
}
// Replace script tags inside given DOM element with executable ones.
// Will also check children recursively and replace any found script
// tags in same manner.
function replaceScriptNodes(node) {
if (node.tagName === "SCRIPT") {
node.parentNode.replaceChild(buildExecutableNode(node), node)
} else {
var children = node.childNodes
if (children && children.length) {
for (var i = 0; i < children.length; i++) {
replaceScriptNodes(children[i])
}
}
}
return node
}
// Replace script element with one whose contents are executable.
function buildExecutableNode(node){
var scriptEl = document.createElement("script")
var attrs = node.attributes
for (var i = 0; i < attrs.length; i++) {
scriptEl.setAttribute(attrs[i].name, attrs[i].value)
}
scriptEl.text = node.innerHTML
return scriptEl
}
function injectHTML(parentElement, index, data) {
var nextSibling = parentElement.childNodes[index]
if (nextSibling) {
@ -1278,7 +1338,7 @@
}
m.prop = function (store) {
if ((store != null && isObject(store) || isFunction(store)) &&
if ((store != null && (isObject(store) || isFunction(store)) || ((typeof Promise !== "undefined") && (store instanceof Promise))) &&
isFunction(store.then)) {
return propify(store)
}
@ -1323,8 +1383,10 @@
}
m.component = function (component) {
for (var args = [], i = 1; i < arguments.length; i++) {
args.push(arguments[i])
var args = new Array(arguments.length - 1)
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i]
}
return parameterize(component, args)
@ -1417,7 +1479,7 @@
try {
// lastRedrawId is a positive number if a second redraw is requested
// before the next animation frame
// lastRedrawID is null if it's the first redraw and not an event
// lastRedrawId is null if it's the first redraw and not an event
// handler
if (lastRedrawId && !force) {
// when setTimeout: only reschedule redraw if time between now
@ -1476,7 +1538,7 @@
m.withAttr = function (prop, withAttrCallback, callbackThis) {
return function (e) {
e = e || event
e = e || window.event
/* eslint-disable no-invalid-this */
var currentTarget = e.currentTarget || this
var _this = callbackThis || this
@ -1558,8 +1620,10 @@
params = {}
}
for (var i in args) if (hasOwn.call(args, i)) {
params[i] = args[i]
for (var i in args) {
if (hasOwn.call(args, i)) {
params[i] = args[i]
}
}
var querystring = buildQueryString(params)
@ -1585,8 +1649,16 @@
var method = replaceHistory ? "replaceState" : "pushState"
computePreRedrawHook = setScroll
computePostRedrawHook = function () {
global.history[method](null, $document.title,
modes[m.route.mode] + currentRoute)
try {
global.history[method](null, $document.title,
modes[m.route.mode] + currentRoute)
} catch (err) {
// In the event of a pushState or replaceState failure,
// fallback to a standard redirect. This is specifically
// to address a Safari security error when attempting to
// call pushState more than 100 times.
$location[m.route.mode] = currentRoute
}
}
redirect(modes[m.route.mode] + currentRoute)
} else {
@ -1635,29 +1707,31 @@
return true
}
for (var route in router) if (hasOwn.call(router, route)) {
if (route === path) {
m.mount(root, router[route])
return true
}
var matcher = new RegExp("^" + route
.replace(/:[^\/]+?\.{3}/g, "(.*?)")
.replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
if (matcher.test(path)) {
/* eslint-disable no-loop-func */
path.replace(matcher, function () {
var keys = route.match(/:[^\/]+/g) || []
var values = [].slice.call(arguments, 1, -2)
forEach(keys, function (key, i) {
routeParams[key.replace(/:|\./g, "")] =
decodeURIComponent(values[i])
})
for (var route in router) {
if (hasOwn.call(router, route)) {
if (route === path) {
m.mount(root, router[route])
})
/* eslint-enable no-loop-func */
return true
return true
}
var matcher = new RegExp("^" + route
.replace(/:[^\/]+?\.{3}/g, "(.*?)")
.replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
if (matcher.test(path)) {
/* eslint-disable no-loop-func */
path.replace(matcher, function () {
var keys = route.match(/:[^\/]+/g) || []
var values = [].slice.call(arguments, 1, -2)
forEach(keys, function (key, i) {
routeParams[key.replace(/:|\./g, "")] =
decodeURIComponent(values[i])
})
m.mount(root, router[route])
})
/* eslint-enable no-loop-func */
return true
}
}
}
}
@ -1703,32 +1777,35 @@
var duplicates = {}
var str = []
for (var prop in object) if (hasOwn.call(object, prop)) {
var key = prefix ? prefix + "[" + prop + "]" : prop
var value = object[prop]
for (var prop in object) {
if (hasOwn.call(object, prop)) {
var key = prefix ? prefix + "[" + prop + "]" : prop
var value = object[prop]
if (value === null) {
str.push(encodeURIComponent(key))
} else if (isObject(value)) {
str.push(buildQueryString(value, key))
} else if (isArray(value)) {
var keys = []
duplicates[key] = duplicates[key] || {}
/* eslint-disable no-loop-func */
forEach(value, function (item) {
/* eslint-enable no-loop-func */
if (!duplicates[key][item]) {
duplicates[key][item] = true
keys.push(encodeURIComponent(key) + "=" +
encodeURIComponent(item))
}
})
str.push(keys.join("&"))
} else if (value !== undefined) {
str.push(encodeURIComponent(key) + "=" +
encodeURIComponent(value))
if (value === null) {
str.push(encodeURIComponent(key))
} else if (isObject(value)) {
str.push(buildQueryString(value, key))
} else if (isArray(value)) {
var keys = []
duplicates[key] = duplicates[key] || {}
/* eslint-disable no-loop-func */
forEach(value, function (item) {
/* eslint-enable no-loop-func */
if (!duplicates[key][item]) {
duplicates[key][item] = true
keys.push(encodeURIComponent(key) + "=" +
encodeURIComponent(item))
}
})
str.push(keys.join("&"))
} else if (value !== undefined) {
str.push(encodeURIComponent(key) + "=" +
encodeURIComponent(value))
}
}
}
return str.join("&")
}
@ -1927,7 +2004,7 @@
m.deferred.onerror = function (e) {
if (type.call(e) === "[object Error]" &&
!e.constructor.toString().match(/ Error/)) {
!/ Error/.test(e.constructor.toString())) {
pendingRequests = 0
throw e
}
@ -1936,7 +2013,7 @@
m.sync = function (args) {
var deferred = m.deferred()
var outstanding = args.length
var results = new Array(outstanding)
var results = []
var method = "resolve"
function synchronizer(pos, resolved) {
@ -1965,7 +2042,7 @@
function identity(value) { return value }
function handleJsonp(options) {
var callbackKey = "mithril_callback_" +
var callbackKey = options.callbackName || "mithril_callback_" +
new Date().getTime() + "_" +
(Math.round(Math.random() * 1e16)).toString(36)
@ -2073,16 +2150,14 @@
}
function parameterizeUrl(url, data) {
var tokens = url.match(/:[a-z]\w+/gi)
if (tokens && data) {
forEach(tokens, function (token) {
if (data) {
url = url.replace(/:[a-z]\w+/gi, function (token){
var key = token.slice(1)
url = url.replace(token, data[key])
var value = data[key] || token
delete data[key]
return value
})
}
return url
}
@ -2143,6 +2218,7 @@
}
} catch (e) {
deferred.reject(e)
m.deferred.onerror(e)
} finally {
if (options.background !== true) m.endComputation()
}
@ -2154,4 +2230,4 @@
}
return m
})
}); // eslint-disable-line

View File

@ -27,7 +27,6 @@
import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
import m from "mithril";
import {IExchangeInfo} from "../lib/wallet/types";
import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
import MithrilComponent = _mithril.MithrilComponent;
import {Denomination} from "../lib/wallet/types";
@ -190,34 +189,60 @@ class Controller {
}
function view(ctrl: Controller): any {
let controls: any[] = [];
let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
function* f() {
yield m("p",
i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
ctrl.amount))} from your bank account into your wallet.`);
mx("p",
i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
ctrl.amount))} from your bank account into your wallet.`);
if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
return controls.concat(viewComplex(ctrl));
if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
yield viewComplex(ctrl);
return;
}
yield viewSimple(ctrl);
}
return controls.concat(viewSimple(ctrl));
return Array.from(f());
}
function viewSimple(ctrl: Controller) {
let controls: any[] = [];
let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
if (ctrl.statusString) {
mx("p", "Error: ", ctrl.statusString);
mx("button.linky", {
onclick: () => {
ctrl.complexViewRequested = true;
}
}, "advanced options");
function *f() {
if (ctrl.statusString) {
yield m("p", "Error: ", ctrl.statusString);
yield m("button.linky", {
onclick: () => {
ctrl.complexViewRequested = true;
}
}, "advanced options");
}
else if (ctrl.reserveCreationInfo != undefined) {
yield m("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
ctrl.url(),
ctrl.amount,
ctrl.callbackUrl),
disabled: !ctrl.isValidExchange
},
"Accept fees and withdraw");
yield m("span.spacer");
yield m("button.linky", {
onclick: () => {
ctrl.complexViewRequested = true;
}
}, "advanced options");
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
} else {
yield m("p", "Please wait ...");
}
}
else if (ctrl.reserveCreationInfo != undefined) {
mx("button.accept", {
return Array.from(f());
}
function viewComplex(ctrl: Controller) {
function *f() {
yield m("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
ctrl.url(),
ctrl.amount,
@ -225,84 +250,53 @@ function viewSimple(ctrl: Controller) {
disabled: !ctrl.isValidExchange
},
"Accept fees and withdraw");
mx("span.spacer");
mx("button.linky", {
yield m("span.spacer");
yield m("button.linky", {
onclick: () => {
ctrl.complexViewRequested = true;
ctrl.complexViewRequested = false;
}
}, "advanced options");
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
} else {
mx("p", "Please wait ...");
}
}, "back to simple view");
yield m("br");
return controls;
}
yield m("input", {
className: "url",
type: "text",
spellcheck: false,
value: ctrl.url(),
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
yield m("br");
function viewComplex(ctrl: Controller) {
let controls: any[] = [];
let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
mx("button.accept", {
onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
ctrl.url(),
ctrl.amount,
ctrl.callbackUrl),
disabled: !ctrl.isValidExchange
},
"Accept fees and withdraw");
mx("span.spacer");
mx("button.linky", {
onclick: () => {
ctrl.complexViewRequested = false;
if (ctrl.statusString) {
yield m("p", ctrl.statusString);
} else if (!ctrl.reserveCreationInfo) {
yield m("p", "Checking URL, please wait ...");
}
}, "back to simple view");
mx("br");
mx("input",
{
className: "url",
type: "text",
spellcheck: false,
value: ctrl.url(),
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
mx("br");
if (ctrl.statusString) {
mx("p", ctrl.statusString);
} else if (!ctrl.reserveCreationInfo) {
mx("p", "Checking URL, please wait ...");
}
if (ctrl.reserveCreationInfo) {
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
if (ctrl.detailCollapsed()) {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(false);
}
}, "show more details");
} else {
mx("button.linky", {
onclick: () => {
ctrl.detailCollapsed(true);
}
}, "hide details");
mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
if (ctrl.reserveCreationInfo) {
let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
ctrl.reserveCreationInfo.withdrawFee).amount;
yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
if (ctrl.detailCollapsed()) {
yield m("button.linky", {
onclick: () => {
ctrl.detailCollapsed(false);
}
}, "show more details");
} else {
yield m("button.linky", {
onclick: () => {
ctrl.detailCollapsed(true);
}
}, "hide details");
yield m("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
}
}
}
return m("div", controls);
return Array.from(f());
}
@ -368,6 +362,7 @@ function getSuggestedExchange(currency: string): Promise<string> {
}
export function main() {
const url = URI(document.location.href);
const query: any = URI.parseQuery(url.query());
@ -378,7 +373,7 @@ export function main() {
getSuggestedExchange(amount.currency)
.then((suggestedExchangeUrl) => {
const controller = () => new Controller(suggestedExchangeUrl, amount, callback_url, wt_types);
const controller = function () { return new Controller(suggestedExchangeUrl, amount, callback_url, wt_types); };
var ExchangeSelection = {controller, view};
m.mount(document.getElementById("exchange-selection"), ExchangeSelection);
})
@ -386,6 +381,6 @@ export function main() {
// TODO: provide more context information, maybe factor it out into a
// TODO:generic error reporting function or component.
document.body.innerText = `Fatal error: "${e.message}".`;
console.error(`got backend error "${e.message}"`);
console.error(`got error "${e.message}"`, e);
});
}

View File

@ -91,6 +91,8 @@ function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
});
}
namespace WalletBalance {
export function controller() {
return new Controller();

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"jsx": "react",
"experimentalDecorators": true,
"module": "system",