/* ---- lib/Class.coffee ---- */ (function() { var Class, slice = [].slice; Class = (function() { function Class() {} Class.prototype.trace = true; Class.prototype.log = function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; if (!this.trace) { return; } if (typeof console === 'undefined') { return; } args.unshift("[" + this.constructor.name + "]"); console.log.apply(console, args); return this; }; Class.prototype.logStart = function() { var args, name; name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; if (!this.trace) { return; } this.logtimers || (this.logtimers = {}); this.logtimers[name] = +(new Date); if (args.length > 0) { this.log.apply(this, ["" + name].concat(slice.call(args), ["(started)"])); } return this; }; Class.prototype.logEnd = function() { var args, ms, name; name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; ms = +(new Date) - this.logtimers[name]; this.log.apply(this, ["" + name].concat(slice.call(args), ["(Done in " + ms + "ms)"])); return this; }; return Class; })(); window.Class = Class; }).call(this); /* ---- lib/Promise.coffee ---- */ (function() { var Promise, slice = [].slice; Promise = (function() { Promise.when = function() { var args, fn, i, len, num_uncompleted, promise, task, task_id, tasks; tasks = 1 <= arguments.length ? slice.call(arguments, 0) : []; num_uncompleted = tasks.length; args = new Array(num_uncompleted); promise = new Promise(); fn = function(task_id) { return task.then(function() { args[task_id] = Array.prototype.slice.call(arguments); num_uncompleted--; if (num_uncompleted === 0) { return promise.complete.apply(promise, args); } }); }; for (task_id = i = 0, len = tasks.length; i < len; task_id = ++i) { task = tasks[task_id]; fn(task_id); } return promise; }; function Promise() { this.resolved = false; this.end_promise = null; this.result = null; this.callbacks = []; } Promise.prototype.resolve = function() { var back, callback, i, len, ref; if (this.resolved) { return false; } this.resolved = true; this.data = arguments; if (!arguments.length) { this.data = [true]; } this.result = this.data[0]; ref = this.callbacks; for (i = 0, len = ref.length; i < len; i++) { callback = ref[i]; back = callback.apply(callback, this.data); } if (this.end_promise) { return this.end_promise.resolve(back); } }; Promise.prototype.fail = function() { return this.resolve(false); }; Promise.prototype.then = function(callback) { if (this.resolved === true) { callback.apply(callback, this.data); return; } this.callbacks.push(callback); return this.end_promise = new Promise(); }; return Promise; })(); window.Promise = Promise; /* s = Date.now() log = (text) -> console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ") log "Started" cmd = (query) -> p = new Promise() setTimeout ( -> p.resolve query+" Result" ), 100 return p back = cmd("SELECT * FROM message").then (res) -> log res return "Return from query" .then (res) -> log "Back then", res log "Query started", back */ }).call(this); /* ---- lib/Prototypes.coffee ---- */ (function() { String.prototype.startsWith = function(s) { return this.slice(0, s.length) === s; }; String.prototype.endsWith = function(s) { return s === '' || this.slice(-s.length) === s; }; String.prototype.repeat = function(count) { return new Array(count + 1).join(this); }; window.isEmpty = function(obj) { var key; for (key in obj) { return false; } return true; }; }).call(this); /* ---- lib/maquette.js ---- */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['exports'], factory); } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { // CommonJS factory(exports); } else { // Browser globals factory(root.maquette = {}); } }(this, function (exports) { 'use strict'; ; ; ; ; var NAMESPACE_W3 = 'http://www.w3.org/'; var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg'; var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink'; // Utilities var emptyArray = []; var extend = function (base, overrides) { var result = {}; Object.keys(base).forEach(function (key) { result[key] = base[key]; }); if (overrides) { Object.keys(overrides).forEach(function (key) { result[key] = overrides[key]; }); } return result; }; // Hyperscript helper functions var same = function (vnode1, vnode2) { if (vnode1.vnodeSelector !== vnode2.vnodeSelector) { return false; } if (vnode1.properties && vnode2.properties) { if (vnode1.properties.key !== vnode2.properties.key) { return false; } return vnode1.properties.bind === vnode2.properties.bind; } return !vnode1.properties && !vnode2.properties; }; var toTextVNode = function (data) { return { vnodeSelector: '', properties: undefined, children: undefined, text: data.toString(), domNode: null }; }; var appendChildren = function (parentSelector, insertions, main) { for (var i = 0; i < insertions.length; i++) { var item = insertions[i]; if (Array.isArray(item)) { appendChildren(parentSelector, item, main); } else { if (item !== null && item !== undefined) { if (!item.hasOwnProperty('vnodeSelector')) { item = toTextVNode(item); } main.push(item); } } } }; // Render helper functions var missingTransition = function () { throw new Error('Provide a transitions object to the projectionOptions to do animations'); }; var DEFAULT_PROJECTION_OPTIONS = { namespace: undefined, eventHandlerInterceptor: undefined, styleApplyer: function (domNode, styleName, value) { // Provides a hook to add vendor prefixes for browsers that still need it. domNode.style[styleName] = value; }, transitions: { enter: missingTransition, exit: missingTransition } }; var applyDefaultProjectionOptions = function (projectorOptions) { return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions); }; var checkStyleValue = function (styleValue) { if (typeof styleValue !== 'string') { throw new Error('Style values must be strings'); } }; var setProperties = function (domNode, properties, projectionOptions) { if (!properties) { return; } var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor; var propNames = Object.keys(properties); var propCount = propNames.length; for (var i = 0; i < propCount; i++) { var propName = propNames[i]; /* tslint:disable:no-var-keyword: edge case */ var propValue = properties[propName]; /* tslint:enable:no-var-keyword */ if (propName === 'className') { throw new Error('Property "className" is not supported, use "class".'); } else if (propName === 'class') { if (domNode.className) { // May happen if classes is specified before class domNode.className += ' ' + propValue; } else { domNode.className = propValue; } } else if (propName === 'classes') { // object with string keys and boolean values var classNames = Object.keys(propValue); var classNameCount = classNames.length; for (var j = 0; j < classNameCount; j++) { var className = classNames[j]; if (propValue[className]) { domNode.classList.add(className); } } } else if (propName === 'styles') { // object with string keys and string (!) values var styleNames = Object.keys(propValue); var styleCount = styleNames.length; for (var j = 0; j < styleCount; j++) { var styleName = styleNames[j]; var styleValue = propValue[styleName]; if (styleValue) { checkStyleValue(styleValue); projectionOptions.styleApplyer(domNode, styleName, styleValue); } } } else if (propName === 'key') { continue; } else if (propValue === null || propValue === undefined) { continue; } else { var type = typeof propValue; if (type === 'function') { if (propName.lastIndexOf('on', 0) === 0) { if (eventHandlerInterceptor) { propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers } if (propName === 'oninput') { (function () { // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput var oldPropValue = propValue; propValue = function (evt) { evt.target['oninput-value'] = evt.target.value; // may be HTMLTextAreaElement as well oldPropValue.apply(this, [evt]); }; }()); } domNode[propName] = propValue; } } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') { if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') { domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue); } else { domNode.setAttribute(propName, propValue); } } else { domNode[propName] = propValue; } } } }; var updateProperties = function (domNode, previousProperties, properties, projectionOptions) { if (!properties) { return; } var propertiesUpdated = false; var propNames = Object.keys(properties); var propCount = propNames.length; for (var i = 0; i < propCount; i++) { var propName = propNames[i]; // assuming that properties will be nullified instead of missing is by design var propValue = properties[propName]; var previousValue = previousProperties[propName]; if (propName === 'class') { if (previousValue !== propValue) { throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.'); } } else if (propName === 'classes') { var classList = domNode.classList; var classNames = Object.keys(propValue); var classNameCount = classNames.length; for (var j = 0; j < classNameCount; j++) { var className = classNames[j]; var on = !!propValue[className]; var previousOn = !!previousValue[className]; if (on === previousOn) { continue; } propertiesUpdated = true; if (on) { classList.add(className); } else { classList.remove(className); } } } else if (propName === 'styles') { var styleNames = Object.keys(propValue); var styleCount = styleNames.length; for (var j = 0; j < styleCount; j++) { var styleName = styleNames[j]; var newStyleValue = propValue[styleName]; var oldStyleValue = previousValue[styleName]; if (newStyleValue === oldStyleValue) { continue; } propertiesUpdated = true; if (newStyleValue) { checkStyleValue(newStyleValue); projectionOptions.styleApplyer(domNode, styleName, newStyleValue); } else { projectionOptions.styleApplyer(domNode, styleName, ''); } } } else { if (!propValue && typeof previousValue === 'string') { propValue = ''; } if (propName === 'value') { if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) { domNode[propName] = propValue; // Reset the value, even if the virtual DOM did not change domNode['oninput-value'] = undefined; } // else do not update the domNode, otherwise the cursor position would be changed if (propValue !== previousValue) { propertiesUpdated = true; } } else if (propValue !== previousValue) { var type = typeof propValue; if (type === 'function') { throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.'); } if (type === 'string' && propName !== 'innerHTML') { if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') { domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue); } else { domNode.setAttribute(propName, propValue); } } else { if (domNode[propName] !== propValue) { domNode[propName] = propValue; } } propertiesUpdated = true; } } } return propertiesUpdated; }; var findIndexOfChild = function (children, sameAs, start) { if (sameAs.vnodeSelector !== '') { // Never scan for text-nodes for (var i = start; i < children.length; i++) { if (same(children[i], sameAs)) { return i; } } } return -1; }; var nodeAdded = function (vNode, transitions) { if (vNode.properties) { var enterAnimation = vNode.properties.enterAnimation; if (enterAnimation) { if (typeof enterAnimation === 'function') { enterAnimation(vNode.domNode, vNode.properties); } else { transitions.enter(vNode.domNode, vNode.properties, enterAnimation); } } } }; var nodeToRemove = function (vNode, transitions) { var domNode = vNode.domNode; if (vNode.properties) { var exitAnimation = vNode.properties.exitAnimation; if (exitAnimation) { domNode.style.pointerEvents = 'none'; var removeDomNode = function () { if (domNode.parentNode) { domNode.parentNode.removeChild(domNode); } }; if (typeof exitAnimation === 'function') { exitAnimation(domNode, removeDomNode, vNode.properties); return; } else { transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode); return; } } } if (domNode.parentNode) { domNode.parentNode.removeChild(domNode); } }; var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) { var childNode = childNodes[indexToCheck]; if (childNode.vnodeSelector === '') { return; // Text nodes need not be distinguishable } var properties = childNode.properties; var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined; if (!key) { for (var i = 0; i < childNodes.length; i++) { if (i !== indexToCheck) { var node = childNodes[i]; if (same(node, childNode)) { if (operation === 'added') { throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.'); } else { throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.'); } } } } } }; var createDom; var updateDom; var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) { if (oldChildren === newChildren) { return false; } oldChildren = oldChildren || emptyArray; newChildren = newChildren || emptyArray; var oldChildrenLength = oldChildren.length; var newChildrenLength = newChildren.length; var transitions = projectionOptions.transitions; var oldIndex = 0; var newIndex = 0; var i; var textUpdated = false; while (newIndex < newChildrenLength) { var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined; var newChild = newChildren[newIndex]; if (oldChild !== undefined && same(oldChild, newChild)) { textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated; oldIndex++; } else { var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1); if (findOldIndex >= 0) { // Remove preceding missing children for (i = oldIndex; i < findOldIndex; i++) { nodeToRemove(oldChildren[i], transitions); checkDistinguishable(oldChildren, i, vnode, 'removed'); } textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated; oldIndex = findOldIndex + 1; } else { // New child createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions); nodeAdded(newChild, transitions); checkDistinguishable(newChildren, newIndex, vnode, 'added'); } } newIndex++; } if (oldChildrenLength > oldIndex) { // Remove child fragments for (i = oldIndex; i < oldChildrenLength; i++) { nodeToRemove(oldChildren[i], transitions); checkDistinguishable(oldChildren, i, vnode, 'removed'); } } return textUpdated; }; var addChildren = function (domNode, children, projectionOptions) { if (!children) { return; } for (var i = 0; i < children.length; i++) { createDom(children[i], domNode, undefined, projectionOptions); } }; var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) { addChildren(domNode, vnode.children, projectionOptions); // children before properties, needed for value property of <select>. if (vnode.text) { domNode.textContent = vnode.text; } setProperties(domNode, vnode.properties, projectionOptions); if (vnode.properties && vnode.properties.afterCreate) { vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children); } }; createDom = function (vnode, parentNode, insertBefore, projectionOptions) { var domNode, i, c, start = 0, type, found; var vnodeSelector = vnode.vnodeSelector; if (vnodeSelector === '') { domNode = vnode.domNode = document.createTextNode(vnode.text); if (insertBefore !== undefined) { parentNode.insertBefore(domNode, insertBefore); } else { parentNode.appendChild(domNode); } } else { for (i = 0; i <= vnodeSelector.length; ++i) { c = vnodeSelector.charAt(i); if (i === vnodeSelector.length || c === '.' || c === '#') { type = vnodeSelector.charAt(start - 1); found = vnodeSelector.slice(start, i); if (type === '.') { domNode.classList.add(found); } else if (type === '#') { domNode.id = found; } else { if (found === 'svg') { projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG }); } if (projectionOptions.namespace !== undefined) { domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found); } else { domNode = vnode.domNode = document.createElement(found); } if (insertBefore !== undefined) { parentNode.insertBefore(domNode, insertBefore); } else { parentNode.appendChild(domNode); } } start = i + 1; } } initPropertiesAndChildren(domNode, vnode, projectionOptions); } }; updateDom = function (previous, vnode, projectionOptions) { var domNode = previous.domNode; var textUpdated = false; if (previous === vnode) { return false; // By contract, VNode objects may not be modified anymore after passing them to maquette } var updated = false; if (vnode.vnodeSelector === '') { if (vnode.text !== previous.text) { var newVNode = document.createTextNode(vnode.text); domNode.parentNode.replaceChild(newVNode, domNode); vnode.domNode = newVNode; textUpdated = true; return textUpdated; } } else { if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) { projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG }); } if (previous.text !== vnode.text) { updated = true; if (vnode.text === undefined) { domNode.removeChild(domNode.firstChild); // the only textnode presumably } else { domNode.textContent = vnode.text; } } updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated; updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated; if (vnode.properties && vnode.properties.afterUpdate) { vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children); } } if (updated && vnode.properties && vnode.properties.updateAnimation) { vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties); } vnode.domNode = previous.domNode; return textUpdated; }; var createProjection = function (vnode, projectionOptions) { return { update: function (updatedVnode) { if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) { throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)'); } updateDom(vnode, updatedVnode, projectionOptions); vnode = updatedVnode; }, domNode: vnode.domNode }; }; ; // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'. exports.h = function (selector) { var properties = arguments[1]; if (typeof selector !== 'string') { throw new Error(); } var childIndex = 1; if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') { childIndex = 2; } else { // Optional properties argument was omitted properties = undefined; } var text = undefined; var children = undefined; var argsLength = arguments.length; // Recognize a common special case where there is only a single text node if (argsLength === childIndex + 1) { var onlyChild = arguments[childIndex]; if (typeof onlyChild === 'string') { text = onlyChild; } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') { text = onlyChild[0]; } } if (text === undefined) { children = []; for (; childIndex < arguments.length; childIndex++) { var child = arguments[childIndex]; if (child === null || child === undefined) { continue; } else if (Array.isArray(child)) { appendChildren(selector, child, children); } else if (child.hasOwnProperty('vnodeSelector')) { children.push(child); } else { children.push(toTextVNode(child)); } } } return { vnodeSelector: selector, properties: properties, children: children, text: text === '' ? undefined : text, domNode: null }; }; /** * Contains simple low-level utility functions to manipulate the real DOM. */ exports.dom = { /** * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in * its [[Projection.domNode|domNode]] property. * This is a low-level method. Users wil typically use a [[Projector]] instead. * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] * objects may only be rendered once. * @param projectionOptions - Options to be used to create and update the projection. * @returns The [[Projection]] which also contains the DOM Node that was created. */ create: function (vnode, projectionOptions) { projectionOptions = applyDefaultProjectionOptions(projectionOptions); createDom(vnode, document.createElement('div'), undefined, projectionOptions); return createProjection(vnode, projectionOptions); }, /** * Appends a new childnode to the DOM which is generated from a [[VNode]]. * This is a low-level method. Users wil typically use a [[Projector]] instead. * @param parentNode - The parent node for the new childNode. * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] * objects may only be rendered once. * @param projectionOptions - Options to be used to create and update the [[Projection]]. * @returns The [[Projection]] that was created. */ append: function (parentNode, vnode, projectionOptions) { projectionOptions = applyDefaultProjectionOptions(projectionOptions); createDom(vnode, parentNode, undefined, projectionOptions); return createProjection(vnode, projectionOptions); }, /** * Inserts a new DOM node which is generated from a [[VNode]]. * This is a low-level method. Users wil typically use a [[Projector]] instead. * @param beforeNode - The node that the DOM Node is inserted before. * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. * NOTE: [[VNode]] objects may only be rendered once. * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]]. * @returns The [[Projection]] that was created. */ insertBefore: function (beforeNode, vnode, projectionOptions) { projectionOptions = applyDefaultProjectionOptions(projectionOptions); createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions); return createProjection(vnode, projectionOptions); }, /** * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node. * This means that the virtual DOM and the real DOM will have one overlapping element. * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided. * This is a low-level method. Users wil typically use a [[Projector]] instead. * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved. * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects * may only be rendered once. * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]]. * @returns The [[Projection]] that was created. */ merge: function (element, vnode, projectionOptions) { projectionOptions = applyDefaultProjectionOptions(projectionOptions); vnode.domNode = element; initPropertiesAndChildren(element, vnode, projectionOptions); return createProjection(vnode, projectionOptions); } }; /** * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees. * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem. * For more information, see [[CalculationCache]]. * * @param <Result> The type of the value that is cached. */ exports.createCache = function () { var cachedInputs = undefined; var cachedOutcome = undefined; var result = { invalidate: function () { cachedOutcome = undefined; cachedInputs = undefined; }, result: function (inputs, calculation) { if (cachedInputs) { for (var i = 0; i < inputs.length; i++) { if (cachedInputs[i] !== inputs[i]) { cachedOutcome = undefined; } } } if (!cachedOutcome) { cachedOutcome = calculation(); cachedInputs = inputs; } return cachedOutcome; } }; return result; }; /** * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects. * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}. * * @param <Source> The type of source items. A database-record for instance. * @param <Target> The type of target items. A [[Component]] for instance. * @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number. * @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical * to the `callback` argument in `Array.map(callback)`. * @param updateResult `function(source, target, index)` that updates a result to an updated source. */ exports.createMapping = function (getSourceKey, createResult, updateResult) { var keys = []; var results = []; return { results: results, map: function (newSources) { var newKeys = newSources.map(getSourceKey); var oldTargets = results.slice(); var oldIndex = 0; for (var i = 0; i < newSources.length; i++) { var source = newSources[i]; var sourceKey = newKeys[i]; if (sourceKey === keys[oldIndex]) { results[i] = oldTargets[oldIndex]; updateResult(source, oldTargets[oldIndex], i); oldIndex++; } else { var found = false; for (var j = 1; j < keys.length; j++) { var searchIndex = (oldIndex + j) % keys.length; if (keys[searchIndex] === sourceKey) { results[i] = oldTargets[searchIndex]; updateResult(newSources[i], oldTargets[searchIndex], i); oldIndex = searchIndex + 1; found = true; break; } } if (!found) { results[i] = createResult(source, i); } } } results.length = newSources.length; keys = newKeys; } }; }; /** * Creates a [[Projector]] instance using the provided projectionOptions. * * For more information, see [[Projector]]. * * @param projectionOptions Options that influence how the DOM is rendered and updated. */ exports.createProjector = function (projectorOptions) { var projector; var projectionOptions = applyDefaultProjectionOptions(projectorOptions); projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) { return function () { // intercept function calls (event handlers) to do a render afterwards. projector.scheduleRender(); return eventHandler.apply(properties.bind || this, arguments); }; }; var renderCompleted = true; var scheduled; var stopped = false; var projections = []; var renderFunctions = []; // matches the projections array var doRender = function () { scheduled = undefined; if (!renderCompleted) { return; // The last render threw an error, it should be logged in the browser console. } renderCompleted = false; for (var i = 0; i < projections.length; i++) { var updatedVnode = renderFunctions[i](); projections[i].update(updatedVnode); } renderCompleted = true; }; projector = { scheduleRender: function () { if (!scheduled && !stopped) { scheduled = requestAnimationFrame(doRender); } }, stop: function () { if (scheduled) { cancelAnimationFrame(scheduled); scheduled = undefined; } stopped = true; }, resume: function () { stopped = false; renderCompleted = true; projector.scheduleRender(); }, append: function (parentNode, renderMaquetteFunction) { projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions)); renderFunctions.push(renderMaquetteFunction); }, insertBefore: function (beforeNode, renderMaquetteFunction) { projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions)); renderFunctions.push(renderMaquetteFunction); }, merge: function (domNode, renderMaquetteFunction) { projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions)); renderFunctions.push(renderMaquetteFunction); }, replace: function (domNode, renderMaquetteFunction) { var vnode = renderMaquetteFunction(); createDom(vnode, domNode.parentNode, domNode, projectionOptions); domNode.parentNode.removeChild(domNode); projections.push(createProjection(vnode, projectionOptions)); renderFunctions.push(renderMaquetteFunction); }, detach: function (renderMaquetteFunction) { for (var i = 0; i < renderFunctions.length; i++) { if (renderFunctions[i] === renderMaquetteFunction) { renderFunctions.splice(i, 1); return projections.splice(i, 1)[0]; } } throw new Error('renderMaquetteFunction was not found'); } }; return projector; }; })); /* ---- utils/Animation.coffee ---- */ (function() { var Animation; Animation = (function() { function Animation() {} Animation.prototype.slideDown = function(elem, props) { var cstyle, h, margin_bottom, margin_top, padding_bottom, padding_top, transition; if (elem.offsetTop > 2000) { return; } h = elem.offsetHeight; cstyle = window.getComputedStyle(elem); margin_top = cstyle.marginTop; margin_bottom = cstyle.marginBottom; padding_top = cstyle.paddingTop; padding_bottom = cstyle.paddingBottom; transition = cstyle.transition; elem.style.boxSizing = "border-box"; elem.style.overflow = "hidden"; elem.style.transform = "scale(0.6)"; elem.style.opacity = "0"; elem.style.height = "0px"; elem.style.marginTop = "0px"; elem.style.marginBottom = "0px"; elem.style.paddingTop = "0px"; elem.style.paddingBottom = "0px"; elem.style.transition = "none"; setTimeout((function() { elem.className += " animate-inout"; elem.style.height = h + "px"; elem.style.transform = "scale(1)"; elem.style.opacity = "1"; elem.style.marginTop = margin_top; elem.style.marginBottom = margin_bottom; elem.style.paddingTop = padding_top; return elem.style.paddingBottom = padding_bottom; }), 1); return elem.addEventListener("transitionend", function() { elem.classList.remove("animate-inout"); elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null; elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null; elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null; return elem.removeEventListener("transitionend", arguments.callee, false); }); }; Animation.prototype.slideUp = function(elem, remove_func, props) { if (elem.offsetTop > 1000) { return remove_func(); } elem.className += " animate-back"; elem.style.boxSizing = "border-box"; elem.style.height = elem.offsetHeight + "px"; elem.style.overflow = "hidden"; elem.style.transform = "scale(1)"; elem.style.opacity = "1"; elem.style.pointerEvents = "none"; setTimeout((function() { elem.style.height = "0px"; elem.style.marginTop = "0px"; elem.style.marginBottom = "0px"; elem.style.paddingTop = "0px"; elem.style.paddingBottom = "0px"; elem.style.transform = "scale(0.8)"; elem.style.borderTopWidth = "0px"; elem.style.borderBottomWidth = "0px"; return elem.style.opacity = "0"; }), 1); return elem.addEventListener("transitionend", function(e) { if (e.propertyName === "opacity" || e.elapsedTime >= 0.6) { elem.removeEventListener("transitionend", arguments.callee, false); return remove_func(); } }); }; Animation.prototype.slideUpInout = function(elem, remove_func, props) { elem.className += " animate-inout"; elem.style.boxSizing = "border-box"; elem.style.height = elem.offsetHeight + "px"; elem.style.overflow = "hidden"; elem.style.transform = "scale(1)"; elem.style.opacity = "1"; elem.style.pointerEvents = "none"; setTimeout((function() { elem.style.height = "0px"; elem.style.marginTop = "0px"; elem.style.marginBottom = "0px"; elem.style.paddingTop = "0px"; elem.style.paddingBottom = "0px"; elem.style.transform = "scale(0.8)"; elem.style.borderTopWidth = "0px"; elem.style.borderBottomWidth = "0px"; return elem.style.opacity = "0"; }), 1); return elem.addEventListener("transitionend", function(e) { if (e.propertyName === "opacity" || e.elapsedTime >= 0.6) { elem.removeEventListener("transitionend", arguments.callee, false); return remove_func(); } }); }; Animation.prototype.showRight = function(elem, props) { elem.className += " animate"; elem.style.opacity = 0; elem.style.transform = "TranslateX(-20px) Scale(1.01)"; setTimeout((function() { elem.style.opacity = 1; return elem.style.transform = "TranslateX(0px) Scale(1)"; }), 1); return elem.addEventListener("transitionend", function() { elem.classList.remove("animate"); return elem.style.transform = elem.style.opacity = null; }); }; Animation.prototype.show = function(elem, props) { var delay, ref; delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1; elem.style.opacity = 0; setTimeout((function() { return elem.className += " animate"; }), 1); setTimeout((function() { return elem.style.opacity = 1; }), delay); return elem.addEventListener("transitionend", function() { elem.classList.remove("animate"); elem.style.opacity = null; return elem.removeEventListener("transitionend", arguments.callee, false); }); }; Animation.prototype.hide = function(elem, remove_func, props) { var delay, ref; delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1; elem.className += " animate"; setTimeout((function() { return elem.style.opacity = 0; }), delay); return elem.addEventListener("transitionend", function(e) { if (e.propertyName === "opacity") { return remove_func(); } }); }; Animation.prototype.addVisibleClass = function(elem, props) { return setTimeout(function() { return elem.classList.add("visible"); }); }; return Animation; })(); window.Animation = new Animation(); }).call(this); /* ---- utils/Dollar.coffee ---- */ (function() { window.$ = function(selector) { if (selector.startsWith("#")) { return document.getElementById(selector.replace("#", "")); } }; }).call(this); /* ---- utils/ZeroFrame.coffee ---- */ (function() { var ZeroFrame, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; ZeroFrame = (function(superClass) { extend(ZeroFrame, superClass); function ZeroFrame(url) { this.onCloseWebsocket = bind(this.onCloseWebsocket, this); this.onOpenWebsocket = bind(this.onOpenWebsocket, this); this.onRequest = bind(this.onRequest, this); this.onMessage = bind(this.onMessage, this); this.url = url; this.waiting_cb = {}; this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1"); this.connect(); this.next_message_id = 1; this.history_state = {}; this.init(); } ZeroFrame.prototype.init = function() { return this; }; ZeroFrame.prototype.connect = function() { this.target = window.parent; window.addEventListener("message", this.onMessage, false); this.cmd("innerReady"); window.addEventListener("beforeunload", (function(_this) { return function(e) { _this.log("save scrollTop", window.pageYOffset); _this.history_state["scrollTop"] = window.pageYOffset; return _this.cmd("wrapperReplaceState", [_this.history_state, null]); }; })(this)); return this.cmd("wrapperGetState", [], (function(_this) { return function(state) { if (state != null) { _this.history_state = state; } _this.log("restore scrollTop", state, window.pageYOffset); if (window.pageYOffset === 0 && state) { return window.scroll(window.pageXOffset, state.scrollTop); } }; })(this)); }; ZeroFrame.prototype.onMessage = function(e) { var cmd, message; message = e.data; cmd = message.cmd; if (cmd === "response") { if (this.waiting_cb[message.to] != null) { return this.waiting_cb[message.to](message.result); } else { return this.log("Websocket callback not found:", message); } } else if (cmd === "wrapperReady") { return this.cmd("innerReady"); } else if (cmd === "ping") { return this.response(message.id, "pong"); } else if (cmd === "wrapperOpenedWebsocket") { return this.onOpenWebsocket(); } else if (cmd === "wrapperClosedWebsocket") { return this.onCloseWebsocket(); } else { return this.onRequest(cmd, message.params); } }; ZeroFrame.prototype.onRequest = function(cmd, message) { return this.log("Unknown request", message); }; ZeroFrame.prototype.response = function(to, result) { return this.send({ "cmd": "response", "to": to, "result": result }); }; ZeroFrame.prototype.cmd = function(cmd, params, cb) { if (params == null) { params = {}; } if (cb == null) { cb = null; } return this.send({ "cmd": cmd, "params": params }, cb); }; ZeroFrame.prototype.send = function(message, cb) { if (cb == null) { cb = null; } message.wrapper_nonce = this.wrapper_nonce; message.id = this.next_message_id; this.next_message_id += 1; this.target.postMessage(message, "*"); if (cb) { return this.waiting_cb[message.id] = cb; } }; ZeroFrame.prototype.onOpenWebsocket = function() { return this.log("Websocket open"); }; ZeroFrame.prototype.onCloseWebsocket = function() { return this.log("Websocket close"); }; return ZeroFrame; })(Class); window.ZeroFrame = ZeroFrame; }).call(this); /* ---- ConfigStorage.coffee ---- */ (function() { var ConfigStorage, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; ConfigStorage = (function(superClass) { extend(ConfigStorage, superClass); function ConfigStorage(config) { this.config = config; this.createSection = bind(this.createSection, this); this.items = []; this.createSections(); this.setValues(this.config); } ConfigStorage.prototype.setValues = function(values) { var i, item, len, ref, results, section; ref = this.items; results = []; for (i = 0, len = ref.length; i < len; i++) { section = ref[i]; results.push((function() { var j, len1, ref1, results1; ref1 = section.items; results1 = []; for (j = 0, len1 = ref1.length; j < len1; j++) { item = ref1[j]; if (!values[item.key]) { continue; } item.value = this.formatValue(values[item.key].value); item["default"] = this.formatValue(values[item.key]["default"]); item.pending = values[item.key].pending; results1.push(values[item.key].item = item); } return results1; }).call(this)); } return results; }; ConfigStorage.prototype.formatValue = function(value) { if (!value) { return false; } else if (typeof value === "object") { return value.join("\n"); } else if (typeof value === "number") { return value.toString(); } else { return value; } }; ConfigStorage.prototype.deformatValue = function(value, type) { if (type === "object" && typeof value === "string") { if (!value.length) { return value = null; } else { return value.split("\n"); } } if (type === "boolean" && !value) { return false; } else if (type === "number") { if (typeof value === "number") { return value.toString(); } else if (!value) { return "0"; } else { return value; } } else { return value; } }; ConfigStorage.prototype.createSections = function() { var section; section = this.createSection("Web Interface"); section.items.push({ key: "open_browser", title: "Open web browser on ZeroNet startup", type: "checkbox" }); section = this.createSection("Network"); section.items.push({ key: "offline", title: "Offline mode", type: "checkbox", description: "Disable network communication." }); section.items.push({ key: "fileserver_ip_type", title: "File server network", type: "select", options: [ { title: "IPv4", value: "ipv4" }, { title: "IPv6", value: "ipv6" }, { title: "Dual (IPv4 & IPv6)", value: "dual" } ], description: "Accept incoming peers using IPv4 or IPv6 address. (default: dual)" }); section.items.push({ key: "fileserver_port", title: "File server port", type: "text", valid_pattern: /[0-9]*/, description: "Other peers will use this port to reach your served sites. (default: randomize)" }); section.items.push({ key: "ip_external", title: "File server external ip", type: "textarea", placeholder: "Detect automatically", description: "Your file server is accessible on these ips. (default: detect automatically)" }); section.items.push({ title: "Tor", key: "tor", type: "select", options: [ { title: "Disable", value: "disable" }, { title: "Enable", value: "enable" }, { title: "Always", value: "always" } ], description: ["Disable: Don't connect to peers on Tor network", h("br"), "Enable: Only use Tor for Tor network peers", h("br"), "Always: Use Tor for every connections to hide your IP address (slower)"] }); section.items.push({ title: "Use Tor bridges", key: "tor_use_bridges", type: "checkbox", description: "Use obfuscated bridge relays to avoid network level Tor block (even slower)", isHidden: function() { return !Page.server_info.tor_has_meek_bridges; } }); section.items.push({ title: "Trackers", key: "trackers", type: "textarea", description: "Discover new peers using these adresses" }); section.items.push({ title: "Trackers files", key: "trackers_file", type: "textarea", description: "Load additional list of torrent trackers dynamically, from a file", placeholder: "Eg.: {data_dir}/trackers.json", value_pos: "fullwidth" }); section.items.push({ title: "Proxy for tracker connections", key: "trackers_proxy", type: "select", options: [ { title: "Custom", value: "" }, { title: "Tor", value: "tor" }, { title: "Disable", value: "disable" } ], isHidden: function() { return Page.values["tor"] === "always"; } }); section.items.push({ title: "Custom socks proxy address for trackers", key: "trackers_proxy", type: "text", placeholder: "Eg.: 127.0.0.1:1080", value_pos: "fullwidth", valid_pattern: /.+:[0-9]+/, isHidden: (function(_this) { return function() { var ref; return (ref = Page.values["trackers_proxy"]) === "tor" || ref === "disable"; }; })(this) }); section = this.createSection("Performance"); section.items.push({ key: "log_level", title: "Level of logging to file", type: "select", options: [ { title: "Everything", value: "DEBUG" }, { title: "Only important messages", value: "INFO" }, { title: "Only errors", value: "ERROR" } ] }); section.items.push({ key: "threads_fs_read", title: "Threads for async file system reads", type: "select", options: [ { title: "Sync read", value: 0 }, { title: "1 thread", value: 1 }, { title: "2 threads", value: 2 }, { title: "3 threads", value: 3 }, { title: "4 threads", value: 4 }, { title: "5 threads", value: 5 }, { title: "10 threads", value: 10 } ] }); section.items.push({ key: "threads_fs_write", title: "Threads for async file system writes", type: "select", options: [ { title: "Sync write", value: 0 }, { title: "1 thread", value: 1 }, { title: "2 threads", value: 2 }, { title: "3 threads", value: 3 }, { title: "4 threads", value: 4 }, { title: "5 threads", value: 5 }, { title: "10 threads", value: 10 } ] }); section.items.push({ key: "threads_crypt", title: "Threads for cryptographic functions", type: "select", options: [ { title: "Sync execution", value: 0 }, { title: "1 thread", value: 1 }, { title: "2 threads", value: 2 }, { title: "3 threads", value: 3 }, { title: "4 threads", value: 4 }, { title: "5 threads", value: 5 }, { title: "10 threads", value: 10 } ] }); return section.items.push({ key: "threads_db", title: "Threads for database operations", type: "select", options: [ { title: "Sync execution", value: 0 }, { title: "1 thread", value: 1 }, { title: "2 threads", value: 2 }, { title: "3 threads", value: 3 }, { title: "4 threads", value: 4 }, { title: "5 threads", value: 5 }, { title: "10 threads", value: 10 } ] }); }; ConfigStorage.prototype.createSection = function(title) { var section; section = {}; section.title = title; section.items = []; this.items.push(section); return section; }; return ConfigStorage; })(Class); window.ConfigStorage = ConfigStorage; }).call(this); /* ---- ConfigView.coffee ---- */ (function() { var ConfigView, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; ConfigView = (function(superClass) { extend(ConfigView, superClass); function ConfigView() { this.renderValueSelect = bind(this.renderValueSelect, this); this.renderValueCheckbox = bind(this.renderValueCheckbox, this); this.renderValueTextarea = bind(this.renderValueTextarea, this); this.autosizeTextarea = bind(this.autosizeTextarea, this); this.renderValueText = bind(this.renderValueText, this); this.handleCheckboxChange = bind(this.handleCheckboxChange, this); this.handleInputChange = bind(this.handleInputChange, this); this.renderSectionItem = bind(this.renderSectionItem, this); this.handleResetClick = bind(this.handleResetClick, this); this.renderSection = bind(this.renderSection, this); this; } ConfigView.prototype.render = function() { return this.config_storage.items.map(this.renderSection); }; ConfigView.prototype.renderSection = function(section) { return h("div.section", { key: section.title }, [h("h2", section.title), h("div.config-items", section.items.map(this.renderSectionItem))]); }; ConfigView.prototype.handleResetClick = function(e) { var config_key, default_value, node, ref; node = e.currentTarget; config_key = node.attributes.config_key.value; default_value = (ref = node.attributes.default_value) != null ? ref.value : void 0; return Page.cmd("wrapperConfirm", ["Reset " + config_key + " value?", "Reset to default"], (function(_this) { return function(res) { if (res) { _this.values[config_key] = default_value; } return Page.projector.scheduleRender(); }; })(this)); }; ConfigView.prototype.renderSectionItem = function(item) { var marker_title, ref, value_changed, value_default, value_pos; value_pos = item.value_pos; if (item.type === "textarea") { if (value_pos == null) { value_pos = "fullwidth"; } } else { if (value_pos == null) { value_pos = "right"; } } value_changed = this.config_storage.formatValue(this.values[item.key]) !== item.value; value_default = this.config_storage.formatValue(this.values[item.key]) === item["default"]; if ((ref = item.key) === "open_browser" || ref === "fileserver_port") { value_default = true; } marker_title = "Changed from default value: " + item["default"] + " -> " + this.values[item.key]; if (item.pending) { marker_title += " (change pending until client restart)"; } if (typeof item.isHidden === "function" ? item.isHidden() : void 0) { return null; } return h("div.config-item", { key: item.title, enterAnimation: Animation.slideDown, exitAnimation: Animation.slideUpInout }, [ h("div.title", [h("h3", item.title), h("div.description", item.description)]), h("div.value.value-" + value_pos, item.type === "select" ? this.renderValueSelect(item) : item.type === "checkbox" ? this.renderValueCheckbox(item) : item.type === "textarea" ? this.renderValueTextarea(item) : this.renderValueText(item), h("a.marker", { href: "#Reset", title: marker_title, onclick: this.handleResetClick, config_key: item.key, default_value: item["default"], classes: { "default": value_default, changed: value_changed, visible: !value_default || value_changed || item.pending, pending: item.pending } }, "\u2022")) ]); }; ConfigView.prototype.handleInputChange = function(e) { var config_key, node; node = e.target; config_key = node.attributes.config_key.value; this.values[config_key] = node.value; return Page.projector.scheduleRender(); }; ConfigView.prototype.handleCheckboxChange = function(e) { var config_key, node, value; node = e.currentTarget; config_key = node.attributes.config_key.value; value = !node.classList.contains("checked"); this.values[config_key] = value; return Page.projector.scheduleRender(); }; ConfigView.prototype.renderValueText = function(item) { var value; value = this.values[item.key]; if (!value) { value = ""; } return h("input.input-" + item.type, { type: item.type, config_key: item.key, value: value, placeholder: item.placeholder, oninput: this.handleInputChange }); }; ConfigView.prototype.autosizeTextarea = function(e) { var h, height_before, node, scrollh; if (e.currentTarget) { node = e.currentTarget; } else { node = e; } height_before = node.style.height; if (height_before) { node.style.height = "0px"; } h = node.offsetHeight; scrollh = node.scrollHeight + 20; if (scrollh > h) { return node.style.height = scrollh + "px"; } else { return node.style.height = height_before; } }; ConfigView.prototype.renderValueTextarea = function(item) { var value; value = this.values[item.key]; if (!value) { value = ""; } return h("textarea.input-" + item.type + ".input-text", { type: item.type, config_key: item.key, oninput: this.handleInputChange, afterCreate: this.autosizeTextarea, updateAnimation: this.autosizeTextarea, value: value, placeholder: item.placeholder }); }; ConfigView.prototype.renderValueCheckbox = function(item) { var checked; if (this.values[item.key] && this.values[item.key] !== "False") { checked = true; } else { checked = false; } return h("div.checkbox", { onclick: this.handleCheckboxChange, config_key: item.key, classes: { checked: checked } }, h("div.checkbox-skin")); }; ConfigView.prototype.renderValueSelect = function(item) { return h("select.input-select", { config_key: item.key, oninput: this.handleInputChange }, item.options.map((function(_this) { return function(option) { return h("option", { selected: option.value.toString() === _this.values[item.key], value: option.value }, option.title); }; })(this))); }; return ConfigView; })(Class); window.ConfigView = ConfigView; }).call(this); /* ---- UiConfig.coffee ---- */ (function() { var UiConfig, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; window.h = maquette.h; UiConfig = (function(superClass) { extend(UiConfig, superClass); function UiConfig() { this.renderBottomRestart = bind(this.renderBottomRestart, this); this.handleRestartClick = bind(this.handleRestartClick, this); this.renderBottomSave = bind(this.renderBottomSave, this); this.handleSaveClick = bind(this.handleSaveClick, this); this.render = bind(this.render, this); this.saveValue = bind(this.saveValue, this); this.saveValues = bind(this.saveValues, this); this.getValuesPending = bind(this.getValuesPending, this); this.getValuesChanged = bind(this.getValuesChanged, this); this.createProjector = bind(this.createProjector, this); this.updateConfig = bind(this.updateConfig, this); this.onOpenWebsocket = bind(this.onOpenWebsocket, this); return UiConfig.__super__.constructor.apply(this, arguments); } UiConfig.prototype.init = function() { this.save_visible = true; this.config = null; this.values = null; this.config_view = new ConfigView(); return window.onbeforeunload = (function(_this) { return function() { if (_this.getValuesChanged().length > 0) { return true; } else { return null; } }; })(this); }; UiConfig.prototype.onOpenWebsocket = function() { this.cmd("wrapperSetTitle", "Config - ZeroNet"); this.cmd("serverInfo", {}, (function(_this) { return function(server_info) { return _this.server_info = server_info; }; })(this)); this.restart_loading = false; return this.updateConfig(); }; UiConfig.prototype.updateConfig = function(cb) { return this.cmd("configList", [], (function(_this) { return function(res) { var item, key, value; _this.config = res; _this.values = {}; _this.config_storage = new ConfigStorage(_this.config); _this.config_view.values = _this.values; _this.config_view.config_storage = _this.config_storage; for (key in res) { item = res[key]; value = item.value; _this.values[key] = _this.config_storage.formatValue(value); } _this.projector.scheduleRender(); return typeof cb === "function" ? cb() : void 0; }; })(this)); }; UiConfig.prototype.createProjector = function() { this.projector = maquette.createProjector(); this.projector.replace($("#content"), this.render); this.projector.replace($("#bottom-save"), this.renderBottomSave); return this.projector.replace($("#bottom-restart"), this.renderBottomRestart); }; UiConfig.prototype.getValuesChanged = function() { var key, ref, ref1, value, values_changed; values_changed = []; ref = this.values; for (key in ref) { value = ref[key]; if (this.config_storage.formatValue(value) !== this.config_storage.formatValue((ref1 = this.config[key]) != null ? ref1.value : void 0)) { values_changed.push({ key: key, value: value }); } } return values_changed; }; UiConfig.prototype.getValuesPending = function() { var item, key, ref, values_pending; values_pending = []; ref = this.config; for (key in ref) { item = ref[key]; if (item.pending) { values_pending.push(key); } } return values_pending; }; UiConfig.prototype.saveValues = function(cb) { var base, changed_values, default_value, i, item, j, last, len, match, message, results, value, value_same_as_default; changed_values = this.getValuesChanged(); results = []; for (i = j = 0, len = changed_values.length; j < len; i = ++j) { item = changed_values[i]; last = i === changed_values.length - 1; value = this.config_storage.deformatValue(item.value, typeof this.config[item.key]["default"]); default_value = this.config_storage.deformatValue(this.config[item.key]["default"], typeof this.config[item.key]["default"]); value_same_as_default = JSON.stringify(default_value) === JSON.stringify(value); if (this.config[item.key].item.valid_pattern && !(typeof (base = this.config[item.key].item).isHidden === "function" ? base.isHidden() : void 0)) { match = value.match(this.config[item.key].item.valid_pattern); if (!match || match[0] !== value) { message = "Invalid value of " + this.config[item.key].item.title + ": " + value + " (does not matches " + this.config[item.key].item.valid_pattern + ")"; Page.cmd("wrapperNotification", ["error", message]); cb(false); break; } } if (value_same_as_default) { value = null; } results.push(this.saveValue(item.key, value, last ? cb : null)); } return results; }; UiConfig.prototype.saveValue = function(key, value, cb) { if (key === "open_browser") { if (value) { value = "default_browser"; } else { value = "False"; } } return Page.cmd("configSet", [key, value], (function(_this) { return function(res) { if (res !== "ok") { Page.cmd("wrapperNotification", ["error", res.error]); } return typeof cb === "function" ? cb(true) : void 0; }; })(this)); }; UiConfig.prototype.render = function() { if (!this.config) { return h("div.content"); } return h("div.content", [this.config_view.render()]); }; UiConfig.prototype.handleSaveClick = function() { this.save_loading = true; this.logStart("Save"); this.saveValues((function(_this) { return function(success) { _this.save_loading = false; _this.logEnd("Save"); if (success) { _this.updateConfig(); } return Page.projector.scheduleRender(); }; })(this)); return false; }; UiConfig.prototype.renderBottomSave = function() { var values_changed; values_changed = this.getValuesChanged(); return h("div.bottom.bottom-save", { classes: { visible: values_changed.length } }, h("div.bottom-content", [ h("div.title", values_changed.length + " configuration item value changed"), h("a.button.button-submit.button-save", { href: "#Save", classes: { loading: this.save_loading }, onclick: this.handleSaveClick }, "Save settings") ])); }; UiConfig.prototype.handleRestartClick = function() { this.restart_loading = true; Page.cmd("serverShutdown", { restart: true }); Page.projector.scheduleRender(); return false; }; UiConfig.prototype.renderBottomRestart = function() { var values_changed, values_pending; values_pending = this.getValuesPending(); values_changed = this.getValuesChanged(); return h("div.bottom.bottom-restart", { classes: { visible: values_pending.length && !values_changed.length } }, h("div.bottom-content", [ h("div.title", "Some changed settings requires restart"), h("a.button.button-submit.button-restart", { href: "#Restart", classes: { loading: this.restart_loading }, onclick: this.handleRestartClick }, "Restart ZeroNet client") ])); }; return UiConfig; })(ZeroFrame); window.Page = new UiConfig(); window.Page.createProjector(); }).call(this);