<commit_before>(function(root, factory) {
    if (typeof exports === 'object') { // Node.js
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) { // Require.JS
        define(factory);
    } else { // Browser globals
        root.jef = root.jef || {};
        root.jef.domdiff = factory();
    }
})(this, function() {
    'use strict';

    /**
     * Test if given nodes are the same
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeSame(a, b) {
        return a.nodeName === b.nodeName;
    }

    /**
     * Test if given ndoes are leafs;
     * Don't have children
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeLeaf(a, b) {
        return nodeLength(a) === 0
        && nodeLength(b) === 0;
    }

    /**
     * Find common attributes in elements 'a' & 'b'
     * and return array of common attribites names
     *
     * @param {NamedNodeMap} a
     * @param {NamedNodeMap} b
     * @return {Array}
     */
    function attrIntersection(aAttr, bAttr) {
        var name, result = [];
        Array.prototype.forEach.call(aAttr, function(value) {
            name = value.nodeName;
            if (name && bAttr[name]) {
                result.push(name);
            }
        });
        return result;
    }

    /**
     * Find attributes in 'a' that do not exists in 'b'
     * and return array of different attribitutes names
     *
     * @param {NamedNodeMap} a
     * @param {Array} b
     * @return {Array}
     */
    function attrDifference(aAttr, bArray) {
        var result = [];
        Array.prototype.forEach.call(aAttr, function(value) {
            if (-1 === bArray.indexOf(value.nodeName)) {
                result.push(value.nodeName);
            }
        });
        return result;
    }

    /**
     * Helper function, testing if attribute is the same in two given elements.
     *
     * @param {Element} a
     * @param {Element} b
     * @param {String} name
     * @return {Boolean}
     */
    function nodeAttrValueEqual(a, b, name) {
        return a.attributes[name].value === b.attributes[name].value;
    }

    /**
     * Prepare action to remove attribute {name} from given {node}
     *
     * @param {NamespaceString} namespace
     * @param {String} name
     * @return {String}
     */
    function nodeAttrRemove(namespace, name) {
        return namespace.namespace + '.removeAttribute("'+ name +'");\n';
    }

    /**
     * Prepare action to replace attribute value;
     *
     * @param {NamespaceString} fromNamespace
     * @param {NamespaceString} toNamespace
     * @param {String} name
     * @return {String}
     */
    function nodeAttrReplace(fromNamespace, toNamespace, name) {
        return toNamespace.toString() + '.setAttribute("'+ name +'", '+ fromNamespace.toString() +'.getAttribute("'+ name +'"));\n';
    }

    /**
     * Check if given nodes are exacly the same
     * (textContent and nodeName are equal)
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeExactly(a, b) {
        return a.textContent === b.textContent
        && nodeSame(a, b);
    }

    /**
     * Helper function to generate node actions
     *
     * @param {String} namespace
     * @param {String} action
     * @param {String} nodePath
     * @return {String}
     */
    function nodeAction(namespace, action, nodePath) {
        return namespace + '.'+ action +'(' + nodePath + ');\n';
    }
    function nodeRemove(nodePath, namespace) {
        return nodeAction(namespace, 'removeChild', nodePath);
    }
    function nodeReplace(newPath, oldPath, namespace) {
        return namespace + '.replaceChild('+ newPath +', '+ oldPath +');\n';
    }
    function nodeAppend(nodePath, namespace) {
        return nodeAction(namespace, 'appendChild', nodePath);
    }

    /**
     * Retrive number of children for given node
     *
     * @param {Element} node
     * @return {Integer}
     */
    function nodeLength(node) {
        return node.children.length;
    }

    /**
     * Retrive child node at {index} for given element;
     *
     * @param {Element} node
     * @param {Integer} index
     * @return {Element}
     */
    function nodeRetrieve(node, index) {
        return node.children[index];
    }

    /**
     * Retrive node position in parent element child nodes;
     *
     * @param {Element} element
     * @return {Integer}
     */
    function nodePosition(element) {
        if (!element.parentNode) {
            return 0;
        }
        return Array.prototype.indexOf.call(
            element.parentNode.children,
            element
        );
    }

    /**
     * Namespace object
     *
     * @constructor
     */
    function NamespaceString(namespace, index) {
        this.namespace = namespace;
        this.index = index || 0;
    };
    NamespaceString.prototype.parent = function() {
        return this;
    };
    NamespaceString.prototype.toString = function() {
        return this.namespace;
    };
    NamespaceString.prototype.pop = function() {
        --this.index;
        return this;
    };
    NamespaceString.prototype.push = function() {
        ++this.index;
        return this;
    };
    NamespaceString.prototype.next = function(index) {
        return new NamespaceNext(this, index);
    };

    /**
     * Generalisation of namespace
     *
     * @constructor
     */
    function NamespaceNext(namespace, index) {
        NamespaceString.call(this, namespace, index)
    };
    NamespaceNext.prototype = Object.create(NamespaceString.prototype);
    NamespaceNext.prototype.parent = function() {
        return this.namespace;
    };
    NamespaceNext.prototype.toString = function() {
        return this.namespace.toString() + '.children[' + this.index + ']';
    };

    /**
     * Calculate diff actions to make 'a' attributes the exactly the same as in 'b'
     *
     * @param {Element} a
     * @param {Element} b
     * @param {NamespaceString} namespaceA
     * @param {NamespaceString} namespaceB
     * @return {String}
     */
    function diffattributes(a, b, namespaceA, namespaceB) {
        var common, remove, create, result = '';

        // Calculate changes in attributes
        common = attrIntersection(a.attributes, b.attributes);
        remove = attrDifference(a.attributes, common);
        create = attrDifference(b.attributes, common);

        // Generate actions
        common.forEach(function(name) {
            if (!nodeAttrValueEqual(a, b, name)) {
                result += nodeAttrReplace(namespaceB, namespaceA, name);
            }
        });
        remove.forEach(function(name) {
            result += nodeAttrRemove(namespaceA, name);
        });
        create.forEach(function(name) {
            result += nodeAttrReplace(namespaceB, namespaceA, name);
        });

        return result;
    }

    /**
     * Create DOM API diff from given elements
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Stirng}
     */
    function diff(rootA, rootB) {
        /**
         * Helper function
         *
         * @param {Element} a
         * @param {Element} b
         * @param {NamespaceString} namespaceA
         * @param {NamespaceString} namespaceB
         * @return {Stirng}
         */
        function diffrecursive(a, b, namespaceA, namespaceB) {
            var i, length, delta, isLeaf, isSame,
                result = '';

            isLeaf = nodeLeaf(a, b);
            isSame = nodeSame(a, b);

            // Node are the same so compare difference in attributes
            if (isSame) {
                result += diffattributes(a, b, namespaceA, namespaceB);
            }

            // Nodes are the same, compare children
            if (!isLeaf && isSame) {
                length = nodeLength(a);
                delta = length - nodeLength(b);

                // Create namespace for children
                namespaceA = namespaceA.next();
                namespaceB = namespaceB.next();

                if (delta > 0) {
                    // 'b' has leser length so we need to reduce
                    // 'a' loop length to 'b' length; if we haven't do this
                    // then we would have null elements in nodeB var
                    length -= delta;
                }

                for (i = 0; i < length; i++) {
                    result += diffrecursive(
                        nodeRetrieve(a, i),
                        nodeRetrieve(b, i),
                        namespaceA,
                        namespaceB
                    );

                    // Push namespace child further
                    namespaceA.push();
                    namespaceB.push();
                }

                if (delta > 0) {
                    // remove unused elements form 'a' node
                    do {
                        // We use the same 'path' for removed elements because
                        // When removing elementa at index 1, element at index 2 changes its possition
                        // and became element at position 1
                        result += nodeRemove(
                            namespaceA.toString(),
                            namespaceA.parent().toString()
                        );
                    } while(--delta > 0);
                } else if (delta < 0) {
                    // the 'a' node have less children than the 'b' node
                    // then since we compare all common 'a' and 'b' nodes
                    // then we need add remaining 'b' nodes
                    do {
                        result += nodeAppend(
                            namespaceB.toString(),
                            namespaceA.parent().toString()
                        );
                    } while(++delta < 0);
                }
            }
            // No relation, use b remove a
            else if (!nodeExactly(a, b)){
                result += nodeReplace(
                    namespaceB.toString(),
                    namespaceA.toString(),
                    namespaceA.parent().toString()
                );

                // When we replace element A with B then B's children number decremented
                namespaceB.pop();
            }

            return result;
        }

        // Perform two node comparision
        return diffrecursive(
            rootA,
            rootB,
            new NamespaceString('aElement'),
            new NamespaceString('bElement')
        );
    }

    function applyDiff(a, b, diff) {
        return new Function('aElement', 'bElement', diff)(a, b);
    }

    var exports = {};

    exports.diff = diff;
    exports.applyDiff = applyDiff;
    exports.attrDifference = attrDifference;
    exports.attrIntersection = attrIntersection;
    exports.nodeSame = nodeSame;
    exports.nodeLeaf = nodeLeaf;
    exports.nodeExactly = nodeExactly;
    exports.nodePosition = nodePosition;
    exports.NamespaceString = NamespaceString;
    exports.NamespaceNext = NamespaceNext;

    return exports;
});
<commit_msg>remove explicit to string<commit_after>(function(root, factory) {
    if (typeof exports === 'object') { // Node.js
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) { // Require.JS
        define(factory);
    } else { // Browser globals
        root.jef = root.jef || {};
        root.jef.domdiff = factory();
    }
})(this, function() {
    'use strict';

    /**
     * Test if given nodes are the same
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeSame(a, b) {
        return a.nodeName === b.nodeName;
    }

    /**
     * Test if given ndoes are leafs;
     * Don't have children
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeLeaf(a, b) {
        return nodeLength(a) === 0
        && nodeLength(b) === 0;
    }

    /**
     * Find common attributes in elements 'a' & 'b'
     * and return array of common attribites names
     *
     * @param {NamedNodeMap} a
     * @param {NamedNodeMap} b
     * @return {Array}
     */
    function attrIntersection(aAttr, bAttr) {
        var name, result = [];
        Array.prototype.forEach.call(aAttr, function(value) {
            name = value.nodeName;
            if (name && bAttr[name]) {
                result.push(name);
            }
        });
        return result;
    }

    /**
     * Find attributes in 'a' that do not exists in 'b'
     * and return array of different attribitutes names
     *
     * @param {NamedNodeMap} a
     * @param {Array} b
     * @return {Array}
     */
    function attrDifference(aAttr, bArray) {
        var result = [];
        Array.prototype.forEach.call(aAttr, function(value) {
            if (-1 === bArray.indexOf(value.nodeName)) {
                result.push(value.nodeName);
            }
        });
        return result;
    }

    /**
     * Helper function, testing if attribute is the same in two given elements.
     *
     * @param {Element} a
     * @param {Element} b
     * @param {String} name
     * @return {Boolean}
     */
    function nodeAttrValueEqual(a, b, name) {
        return a.attributes[name].value === b.attributes[name].value;
    }

    /**
     * Prepare action to remove attribute {name} from given {node}
     *
     * @param {NamespaceString} namespace
     * @param {String} name
     * @return {String}
     */
    function nodeAttrRemove(namespace, name) {
        return namespace + '.removeAttribute("'+ name +'");\n';
    }

    /**
     * Prepare action to replace attribute value;
     *
     * @param {NamespaceString} fromNamespace
     * @param {NamespaceString} toNamespace
     * @param {String} name
     * @return {String}
     */
    function nodeAttrReplace(fromNamespace, toNamespace, name) {
        return toNamespace + '.setAttribute("'+ name +'", '+ fromNamespace +'.getAttribute("'+ name +'"));\n';
    }

    /**
     * Check if given nodes are exacly the same
     * (textContent and nodeName are equal)
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Boolean}
     */
    function nodeExactly(a, b) {
        return a.textContent === b.textContent
        && nodeSame(a, b);
    }

    /**
     * Helper function to generate node actions
     *
     * @param {String} namespace
     * @param {String} action
     * @param {String} nodePath
     * @return {String}
     */
    function nodeAction(namespace, action, nodePath) {
        return namespace + '.'+ action +'(' + nodePath + ');\n';
    }
    function nodeRemove(nodePath, namespace) {
        return nodeAction(namespace, 'removeChild', nodePath);
    }
    function nodeReplace(newPath, oldPath, namespace) {
        return namespace + '.replaceChild('+ newPath +', '+ oldPath +');\n';
    }
    function nodeAppend(nodePath, namespace) {
        return nodeAction(namespace, 'appendChild', nodePath);
    }

    /**
     * Retrive number of children for given node
     *
     * @param {Element} node
     * @return {Integer}
     */
    function nodeLength(node) {
        return node.children.length;
    }

    /**
     * Retrive child node at {index} for given element;
     *
     * @param {Element} node
     * @param {Integer} index
     * @return {Element}
     */
    function nodeRetrieve(node, index) {
        return node.children[index];
    }

    /**
     * Retrive node position in parent element child nodes;
     *
     * @param {Element} element
     * @return {Integer}
     */
    function nodePosition(element) {
        if (!element.parentNode) {
            return 0;
        }
        return Array.prototype.indexOf.call(
            element.parentNode.children,
            element
        );
    }

    /**
     * Namespace object
     *
     * @constructor
     */
    function NamespaceString(namespace, index) {
        this.namespace = namespace;
        this.index = index || 0;
    };
    NamespaceString.prototype.parent = function() {
        return this;
    };
    NamespaceString.prototype.toString = function() {
        return this.namespace;
    };
    NamespaceString.prototype.pop = function() {
        --this.index;
        return this;
    };
    NamespaceString.prototype.push = function() {
        ++this.index;
        return this;
    };
    NamespaceString.prototype.next = function(index) {
        return new NamespaceNext(this, index);
    };

    /**
     * Generalisation of namespace
     *
     * @constructor
     */
    function NamespaceNext(namespace, index) {
        NamespaceString.call(this, namespace, index)
    };
    NamespaceNext.prototype = Object.create(NamespaceString.prototype);
    NamespaceNext.prototype.parent = function() {
        return this.namespace;
    };
    NamespaceNext.prototype.toString = function() {
        return this.namespace + '.children[' + this.index + ']';
    };

    /**
     * Calculate diff actions to make 'a' attributes the exactly the same as in 'b'
     *
     * @param {Element} a
     * @param {Element} b
     * @param {NamespaceString} namespaceA
     * @param {NamespaceString} namespaceB
     * @return {String}
     */
    function diffattributes(a, b, namespaceA, namespaceB) {
        var common, remove, create, result = '';

        // Calculate changes in attributes
        common = attrIntersection(a.attributes, b.attributes);
        remove = attrDifference(a.attributes, common);
        create = attrDifference(b.attributes, common);

        // Generate actions
        common.forEach(function(name) {
            if (!nodeAttrValueEqual(a, b, name)) {
                result += nodeAttrReplace(namespaceB, namespaceA, name);
            }
        });
        remove.forEach(function(name) {
            result += nodeAttrRemove(namespaceA, name);
        });
        create.forEach(function(name) {
            result += nodeAttrReplace(namespaceB, namespaceA, name);
        });

        return result;
    }

    /**
     * Create DOM API diff from given elements
     *
     * @param {Element} a
     * @param {Element} b
     * @return {Stirng}
     */
    function diff(rootA, rootB) {
        /**
         * Helper function
         *
         * @param {Element} a
         * @param {Element} b
         * @param {NamespaceString} namespaceA
         * @param {NamespaceString} namespaceB
         * @return {Stirng}
         */
        function diffrecursive(a, b, namespaceA, namespaceB) {
            var i, length, delta, isLeaf, isSame,
                result = '';

            isLeaf = nodeLeaf(a, b);
            isSame = nodeSame(a, b);

            // Node are the same so compare difference in attributes
            if (isSame) {
                result += diffattributes(a, b, namespaceA, namespaceB);
            }

            // Nodes are the same, compare children
            if (!isLeaf && isSame) {
                length = nodeLength(a);
                delta = length - nodeLength(b);

                // Create namespace for children
                namespaceA = namespaceA.next();
                namespaceB = namespaceB.next();

                if (delta > 0) {
                    // 'b' has leser length so we need to reduce
                    // 'a' loop length to 'b' length; if we haven't do this
                    // then we would have null elements in nodeB var
                    length -= delta;
                }

                for (i = 0; i < length; i++) {
                    result += diffrecursive(
                        nodeRetrieve(a, i),
                        nodeRetrieve(b, i),
                        namespaceA,
                        namespaceB
                    );

                    // Push namespace child further
                    namespaceA.push();
                    namespaceB.push();
                }

                if (delta > 0) {
                    // remove unused elements form 'a' node
                    do {
                        // We use the same 'path' for removed elements because
                        // When removing elementa at index 1, element at index 2 changes its possition
                        // and became element at position 1
                        result += nodeRemove(
                            namespaceA,
                            namespaceA.parent()
                        );
                    } while(--delta > 0);
                } else if (delta < 0) {
                    // the 'a' node have less children than the 'b' node
                    // then since we compare all common 'a' and 'b' nodes
                    // then we need add remaining 'b' nodes
                    do {
                        result += nodeAppend(
                            namespaceB,
                            namespaceA.parent()
                        );
                    } while(++delta < 0);
                }
            }
            // No relation, use b remove a
            else if (!nodeExactly(a, b)){
                result += nodeReplace(
                    namespaceB,
                    namespaceA,
                    namespaceA.parent()
                );

                // When we replace element A with B then B's children number decremented
                namespaceB.pop();
            }

            return result;
        }

        // Perform two node comparision
        return diffrecursive(
            rootA,
            rootB,
            new NamespaceString('aElement'),
            new NamespaceString('bElement')
        );
    }

    function applyDiff(a, b, diff) {
        return new Function('aElement', 'bElement', diff)(a, b);
    }

    var exports = {};

    exports.diff = diff;
    exports.applyDiff = applyDiff;
    exports.attrDifference = attrDifference;
    exports.attrIntersection = attrIntersection;
    exports.nodeSame = nodeSame;
    exports.nodeLeaf = nodeLeaf;
    exports.nodeExactly = nodeExactly;
    exports.nodePosition = nodePosition;
    exports.NamespaceString = NamespaceString;
    exports.NamespaceNext = NamespaceNext;

    return exports;
});
<|endoftext|>


<commit_before>const sendData = require('../helpers/sendData');
const delayForDuration = require('../helpers/delayForDuration');
const BroadlinkRMAccessory = require('./accessory');

class WindowCoveringAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config);

    // Override any user defaults
    // config.resendHexAfterReload = true;
  }

  async setTargetPosition (hexData, previousValue) {
    const { config, data, log, name, state } = this;
    const { stop } = data;
    let { initialDelay } = config;

    if (!initialDelay) initialDelay = 1;

    if (state.targetPosition === previousValue) return;

    if (stop && state.operationID ) {
      log(`${name} setTargetPosition: cancel last operation`);
      this.stop();
    }

    if (this.initialDelayTimeout) clearTimeout(this.initialDelayTimeout);

    this.initialDelayTimeout = setTimeout(() => {
      this.performSetTargetPosition(hexData, previousValue);
    }, initialDelay * 1000);
  }

  async performSetTargetPosition (hexData, previousValue) {
    const { config, data, log, name, state } = this;
    const { open, close, stop } = data;

    if (stop && state.operationID ) {
      log(`${name} setTargetPosition: cancel last operation`);
      this.stop();
    }

    state.operationID = Date.now();
    const currentOperationID = state.operationID;

    if (!state.currentPosition) state.currentPosition = 0;

    let { percentageChangePerSend } = config;
    if (!percentageChangePerSend) percentageChangePerSend = 1;

    let difference = state.targetPosition - state.currentPosition;

    state.opening = (difference > 0);
    if (!state.opening) difference = -1 * difference;

    // If the target position is not a multiple of the percentageChangePerSend
    // value then make it so
    if (state.targetPosition % percentageChangePerSend !== 0) {
      let roundedTargetPosition;

      if (state.opening) roundedTargetPosition = Math.ceil(state.targetPosition / percentageChangePerSend) * percentageChangePerSend;
      if (!state.opening) roundedTargetPosition = Math.floor(state.targetPosition / percentageChangePerSend) * percentageChangePerSend;

      state.targetPosition = roundedTargetPosition;

      log(`${name} setTargetPosition: (rounding to multiple of percentageChangePerSend; ${roundedTargetPosition}) ${currentOperationID}`);

      setTimeout(() => {
        if (currentOperationID !== state.operationID) return;

        this.windowCoveringService.setCharacteristic(Characteristic.TargetPosition, roundedTargetPosition);
      }, 200);
    }

    const increments = Math.ceil(difference / percentageChangePerSend);

    hexData = state.opening ? open : close

    this.openOrClose({ hexData, increments, previousValue, currentOperationID }) // Perform this asynchronously i.e. without await
  }

  async openOrClose ({ hexData, increments, previousValue, currentOperationID }) {
    let { config, data, host, name, log, state } = this;
    let { totalDurationOpen, totalDurationClose } = config;
    const { stop } = data;

    if (state.opening) {
      setTimeout(() => {
        this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.INCREASING);
      }, 200);
    } else {
      setTimeout(() => {
        this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.DECREASING);
      }, 200);
    }

    log(`${name} setTargetPosition: currently ${state.currentPosition}%, moving to ${state.targetPosition}%`);

    let difference = state.targetPosition - state.currentPosition
    if (!state.opening) difference = -1 * difference;

    const fullOpenCloseTime = state.opening ? totalDurationOpen : totalDurationClose;
    const durationPerPercentage = fullOpenCloseTime / 100;
    const totalTime = durationPerPercentage * difference;

    log(`${name} setTargetPosition: ${totalTime}s (${fullOpenCloseTime} / 100 * ${difference}) until auto-stop ${currentOperationID}`);

    sendData({ host, hexData, log, name });

    this.updateCurrentPositionAtIntervals(currentOperationID)

    this.autoStopTimeout = setTimeout(() => {
      this.stop();

      this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, state.targetPosition);
    }, totalTime * 1000)
  }

  stop () {
    const { data, host, log, name, state } = this;
    const { stop } = data;

    if (this.autoStopTimeout) clearTimeout(this.autoStopTimeout)
    if (this.updateCurrentPositionTimeout) clearTimeout(this.updateCurrentPositionTimeout)

    state.operationID = undefined;
    state.opening = undefined;

    this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.STOPPED);

    log(`${name} setTargetPosition: stop`);
    if (stop) sendData({ host, hexData: stop, log, name });
  }

  updateCurrentPositionAtIntervals (currentOperationID) {
    const { config, state } = this;
    let { totalDurationOpen, totalDurationClose } = config;

    let fullOpenCloseTime = state.opening ? totalDurationOpen : totalDurationClose;
    let durationPerPercentage = fullOpenCloseTime / 100;

    this.updateCurrentPositionTimeout = setTimeout(() => {
      if (currentOperationID !== state.operationID) return;

      let currentValue = state.currentPosition || 0;
      if (state.opening) currentValue++;
      if (!state.opening) currentValue--;

      this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, currentValue);
      this.updateCurrentPositionAtIntervals(currentOperationID);
    }, durationPerPercentage * 1000)

  }

  getServices () {
    const services = super.getServices();

    const { data, name } = this;

    const service = new Service.WindowCovering(name);
    this.addNameService(service);

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.CurrentPosition,
      propertyName: 'currentPosition',
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.PositionState,
      propertyName: 'positionState',
      defaultValue: Characteristic.PositionState.STOPPED
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.TargetPosition,
      propertyName: 'targetPosition',
      setValuePromise: this.setTargetPosition.bind(this)
    });

    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.CurrentHorizontalTiltAngle,
    //   propertyName: 'currentHorizontalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.CurrentVerticalTiltAngle,
    //   propertyName: 'currentVerticalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.TargetHorizontalTiltAngle,
    //   propertyName: 'targetHorizontalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.TargetVerticalTiltAngle,
    //   propertyName: 'targetVerticalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.HoldPosition,
    //   propertyName: 'holdPosition',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.ObstructionDetected,
    //   propertyName: 'obstructionDetected',
    //   defaultValue: false
    // });

    this.windowCoveringService = service;

    services.push(service);

    return services;
  }
}

module.exports = WindowCoveringAccessory;
<commit_msg>Fix for #101<commit_after>const sendData = require('../helpers/sendData');
const delayForDuration = require('../helpers/delayForDuration');
const BroadlinkRMAccessory = require('./accessory');

class WindowCoveringAccessory extends BroadlinkRMAccessory {

  constructor (log, config) {
    super(log, config);

    // Override any user defaults
    // config.resendHexAfterReload = true;
  }

  async setTargetPosition (hexData, previousValue) {
    const { config, data, log, name, state } = this;
    const { stop } = data;
    let { initialDelay } = config;

    if (!initialDelay) initialDelay = 1;

    if (state.targetPosition === previousValue) return;

    if (stop && state.operationID ) {
      log(`${name} setTargetPosition: cancel last operation`);
      this.stop(true);
    }

    if (this.initialDelayTimeout) clearTimeout(this.initialDelayTimeout);

    this.initialDelayTimeout = setTimeout(() => {
      this.performSetTargetPosition(hexData, previousValue);
    }, initialDelay * 1000);
  }

  async performSetTargetPosition (hexData, previousValue) {
    const { config, data, log, name, state } = this;
    const { open, close, stop } = data;

    if (stop && state.operationID ) {
      log(`${name} setTargetPosition: cancel last operation`);
      this.stop(true);
    }

    state.operationID = Date.now();
    const currentOperationID = state.operationID;

    if (!state.currentPosition) state.currentPosition = 0;

    let { percentageChangePerSend } = config;
    if (!percentageChangePerSend) percentageChangePerSend = 1;

    let difference = state.targetPosition - state.currentPosition;

    state.opening = (difference > 0);
    if (!state.opening) difference = -1 * difference;

    // If the target position is not a multiple of the percentageChangePerSend
    // value then make it so
    if (state.targetPosition % percentageChangePerSend !== 0) {
      let roundedTargetPosition;

      if (state.opening) roundedTargetPosition = Math.ceil(state.targetPosition / percentageChangePerSend) * percentageChangePerSend;
      if (!state.opening) roundedTargetPosition = Math.floor(state.targetPosition / percentageChangePerSend) * percentageChangePerSend;

      state.targetPosition = roundedTargetPosition;

      log(`${name} setTargetPosition: (rounding to multiple of percentageChangePerSend; ${roundedTargetPosition}) ${currentOperationID}`);

      setTimeout(() => {
        if (currentOperationID !== state.operationID) return;

        this.windowCoveringService.setCharacteristic(Characteristic.TargetPosition, roundedTargetPosition);
      }, 200);
    }

    const increments = Math.ceil(difference / percentageChangePerSend);

    hexData = state.opening ? open : close

    this.openOrClose({ hexData, increments, previousValue, currentOperationID }) // Perform this asynchronously i.e. without await
  }

  async openOrClose ({ hexData, increments, previousValue, currentOperationID }) {
    let { config, data, host, name, log, state } = this;
    let { totalDurationOpen, totalDurationClose } = config;
    const { stop } = data;

    if (state.opening) {
      setTimeout(() => {
        this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.INCREASING);
      }, 200);
    } else {
      setTimeout(() => {
        this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.DECREASING);
      }, 200);
    }

    log(`${name} setTargetPosition: currently ${state.currentPosition}%, moving to ${state.targetPosition}%`);

    let difference = state.targetPosition - state.currentPosition
    if (!state.opening) difference = -1 * difference;

    const fullOpenCloseTime = state.opening ? totalDurationOpen : totalDurationClose;
    const durationPerPercentage = fullOpenCloseTime / 100;
    const totalTime = durationPerPercentage * difference;

    log(`${name} setTargetPosition: ${totalTime}s (${fullOpenCloseTime} / 100 * ${difference}) until auto-stop ${currentOperationID}`);

    sendData({ host, hexData, log, name });

    this.updateCurrentPositionAtIntervals(currentOperationID)

    this.autoStopTimeout = setTimeout(() => {
      const sendStopHex = (state.targetPosition !== 100 && state.targetPosition !== 0);
      this.stop(sendStopHex);

      this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, state.targetPosition);
    }, totalTime * 1000)
  }

  stop (sendHex) {
    const { data, host, log, name, state } = this;
    const { stop } = data;

    if (this.autoStopTimeout) clearTimeout(this.autoStopTimeout)
    if (this.updateCurrentPositionTimeout) clearTimeout(this.updateCurrentPositionTimeout)

    state.operationID = undefined;
    state.opening = undefined;

    this.windowCoveringService.setCharacteristic(Characteristic.PositionState, Characteristic.PositionState.STOPPED);

    log(`${name} setTargetPosition: stop`);
    if (sendHex && stop) sendData({ host, hexData: stop, log, name });
  }

  updateCurrentPositionAtIntervals (currentOperationID) {
    const { config, state } = this;
    let { totalDurationOpen, totalDurationClose } = config;

    let fullOpenCloseTime = state.opening ? totalDurationOpen : totalDurationClose;
    let durationPerPercentage = fullOpenCloseTime / 100;

    this.updateCurrentPositionTimeout = setTimeout(() => {
      if (currentOperationID !== state.operationID) return;

      let currentValue = state.currentPosition || 0;
      if (state.opening) currentValue++;
      if (!state.opening) currentValue--;

      this.windowCoveringService.setCharacteristic(Characteristic.CurrentPosition, currentValue);
      this.updateCurrentPositionAtIntervals(currentOperationID);
    }, durationPerPercentage * 1000)

  }

  getServices () {
    const services = super.getServices();

    const { data, name } = this;

    const service = new Service.WindowCovering(name);
    this.addNameService(service);

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.CurrentPosition,
      propertyName: 'currentPosition',
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.PositionState,
      propertyName: 'positionState',
      defaultValue: Characteristic.PositionState.STOPPED
    });

    this.createToggleCharacteristic({
      service,
      characteristicType: Characteristic.TargetPosition,
      propertyName: 'targetPosition',
      setValuePromise: this.setTargetPosition.bind(this)
    });

    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.CurrentHorizontalTiltAngle,
    //   propertyName: 'currentHorizontalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.CurrentVerticalTiltAngle,
    //   propertyName: 'currentVerticalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.TargetHorizontalTiltAngle,
    //   propertyName: 'targetHorizontalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.TargetVerticalTiltAngle,
    //   propertyName: 'targetVerticalTiltAngle',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.HoldPosition,
    //   propertyName: 'holdPosition',
    // });
    //
    // this.createToggleCharacteristic({
    //   service,
    //   characteristicType: Characteristic.ObstructionDetected,
    //   propertyName: 'obstructionDetected',
    //   defaultValue: false
    // });

    this.windowCoveringService = service;

    services.push(service);

    return services;
  }
}

module.exports = WindowCoveringAccessory;
<|endoftext|>


<commit_before>module.exports = function(robot) {
    'use strict'

    var slackmsg = require("./slackMsgs.js");
    var request = require('request');
    var rp = require('request-promise');
    var Trello = require('node-trello');
    var Promise = require('bluebird');

    var TrelloP = require('node-trello');
    Promise.promisifyAll(TrelloP);
// var throwAwayInstance = ParanoidLib.createInstance();
// Promise.promisifyAll(Object.getPrototypeOf(throwAwayInstance));

    // auth
    var key = process.env.HUBOT_TRELLO_KEY;
    var token = process.env.HUBOT_TRELLO_TOKEN;
    var trello = new Trello(key, token);
    var trelloP = new TrelloP(key, token);

    function sendMessageToSlackResponseURL(responseURL, JSONmessage){
      var postOptions = {
          uri: responseURL,
          method: 'POST',
          headers: {
              'Content-type': 'application/json'
          },
          json: JSONmessage
      };
      request(postOptions, (error, response, body) => {
          if (error){
              // handle errors as you see fit
          };
      })
    }

    // Associate a board with a specific Channel
    robot.hear(/trello hooks/, function(res_r) {
        let boardId = 'BE7seI7e';
        let cb_url = 'https://andreasbot.herokuapp.com/hubot/trello-webhooks';
        let args = {description:"my test webhook", callbackURL:cb_url, idModel:'59245663c76f54b975558854'};

        TrelloP.post('/1/webhooks', args).then(function(data){
            robot.logger.info(' NO error')
        }).catch(function(err){
            robot.logger.error('error')
            robot.logger.error(err);
        })

        // trello.post('/1/webhooks', args, function(err, data){
        //     if (err){
        //         robot.logger.error('error')
        //         //robot.logger.error(err);
        //         return 0;
        //     }
        //     robot.logger.info('NO error');
        // })
    })

    robot.hear(/trello h2/, function(res_r) {
        var options = {
            method: 'POST',
            uri: `https://api.trello.com/1/tokens/${token}/webhooks/?key=${key}`,
            body: {
                description: "My first webhook",
                callbackURL: "https://andreasbot.herokuapp.com/hubot/trello-webhooks",
                idModel: "59245663c76f54b975558854",
            },
            json: true // Automatically stringifies the body to JSON
        };

        rp(options)
            .then(function (parsedBody) {
                robot.logger.info(parsedBody);
            })
            .catch(function (err) {
                robot.logger.error(err);
            });

    })

	robot.on('trello-webhook-event', function(data, res){
        var room = "random";
        robot.messageRoom(room, `trello-webhook-event`);
    })

    // trello board
    robot.hear(/trello board/i, function(res_r){
        // TODO: fetch the board id from other source (env, redis or mongodb)
        let boardId = 'BE7seI7e';
        let args = {fields: "name,url,prefs"};

        trello.get("/1/board/"+boardId, args, function(err, data){
            if (err){
                res.send('Error: ' + err);
                robot.logger.error(err);
                return 0;
            }
            // else !err
            let msg = slackmsg.buttons();
            msg.attachments[0].title = `<${data.url}|${data.name}>`;
            msg.attachments[0].title_url = 'www.google.com'
            msg.attachments[0].author_name = 'Board'
            msg.attachments[0].callback_id = `trello_board`;
            msg.attachments[0].color = `${data.prefs.backgroundColor}`;

            // attach the board lists to buttons
            let joinBtn = {"name": "join", "text": "Join","type":"button", "value": "join"};
            let subBtn  = {"name": "sub", "text": "Subscribe","type":"button", "value": "sub"};
            let starBtn = {"name": "star", "text": "Star","type":"button", "value": "star"};
            let listsBtn= {"name": "lists", "text": "Lists","type":"button", "value": "lists"};
            let doneBtn = {"name": "done", "text": "Done","type":"button", "value": "done","style": "danger"};
            msg.attachments[0].actions.push(joinBtn);
            msg.attachments[0].actions.push(subBtn);
            msg.attachments[0].actions.push(starBtn);
            msg.attachments[0].actions.push(listsBtn);
            msg.attachments[0].actions.push(doneBtn);

            res_r.send(msg);
        })
    })

    /*******************************************************************/
    /*                        robot.on listeners                       */
    /*******************************************************************/

    var slackCB = 'slack:msg_action:';

    // responding to 'trello_board' interactive message
    robot.on(slackCB + 'trello_board', function(data, res){
        robot.logger.info(`robot.on: ${slackCB}trello_board`);
        let btnId = data.actions[0].value;
        let btnName = data.actions[0].name;
        let response_url = data.response_url;
        let msg;

        switch (btnId) {
          case 'join':
            break;
          case 'sub':
            break;
          case 'star':
                break;
          case 'lists':
            res.status(200).end(); // best practice to respond with 200 status
            // get board info to fetch lists
            let boardId = 'BE7seI7e';
            let args = {lists:"all"};
            trello.get("1/board/"+boardId, args, function(err, data){
                if (err){
                    res.send(err);
                    robot.logger.error(err);
                    return;
                }
                // else if (!err)
                // create buttons msg
                let msg = slackmsg.buttons();
                msg.text = `*${data.name}* board`;

                msg.attachments[0].text = `Available lists`;
                msg.attachments[0].callback_id = `trello_list`;
                let listsNum = Object.keys(data.lists).length;
                for (var i=0; i<listsNum; i++){
                    // TODO change value to some id or something similar
                    let name = data.lists[i].name;
                    let id = data.lists[i].id;
                    let list = {"name": name, "text": name, "type":"button", "value": id};
                    msg.attachments[0].actions.push(list);
                }
                sendMessageToSlackResponseURL(response_url, msg);
            })
            break;
          case 'done':
            //res.status(200).end() // best practice to respond with 200 status
            msg = slackmsg.plainText();
            res.send(msg);
            //sendMessageToSlackResponseURL(response_url, msg);
            // res.send(msg);
            break;
          default:
            //Statements executed when none of the values match the value of the expression
            break;
        }
    })

    // responding to 'trello_list' interactive message
    robot.on(slackCB + 'trello_list', function(data_board, res){
        let response_url = data_board.response_url;

        res.status(200).end() // best practice to respond with 200 status
        robot.logger.info(`robot.on: ${slackCB}trello_list`);
        let listId = data_board.actions[0].value;
        let listName = data_board.actions[0].name;

        // call function to fetch list - provide list id
        let args = {cards: "all"};
        trello.get("/1/lists/"+listId, args, function(err, data){
            if (err){
                robot.logger.error(err);
                res.send(`Error: ${err}`)
                return 0;
            }
            // else !err
            // create buttons msg
            let msg = slackmsg.buttons();
            msg.text = `*${listName}* list`;
            msg.attachments[0].text = `Available Cards`;
            msg.attachments[0].callback_id = `trello_list`;

            let cardsNum = Object.keys(data.cards).length;
            robot.logger.info(`total cards: ${cardsNum}`);
            for (var i=0; i<cardsNum; i++){
                let card    = data.cards[i].name;
                let cardId  = data.cards[i].id;
                let item    = {"name": card, "text": card,"type":"button", "value": cardId};
                msg.attachments[0].actions.push(item);
            }

            // respond with information for that list
            sendMessageToSlackResponseURL(response_url, msg);
        })
    })

    /*  TODO: add more functionality */

}

    /* developer's personal notes */

    /* template */

    // robot.respond(/trello /i, function(res_r) {
    //     t.post("/1/", function(err, data){
    //         if (err){
    //             res_r.send('Error Encountered: '+ err['responseBody']);
    //         }
    //     })
    // })

/* An example of using slack's response_url. ~For future use */
/* Add this snippet inside robot.on that u want to trigger   */

    // var response_url = data.response_url;
    // var slackMsg = require('./slackMsgs');
    // var response = slackMsg.ephemeralMsg();

    // sendMessageToSlackResponseURL(response_url, response);

<commit_msg>bluebird for node-trello<commit_after>module.exports = function(robot) {
    'use strict'

    var slackmsg = require("./slackMsgs.js");
    var request = require('request');
    var rp = require('request-promise');
    var Trello = require('node-trello');

    const Promise = require("bluebird");
    const TrelloP = Promise.promisifyAll(require("node-trello"));
// this automatically adds `Async` postfixed methods to `fs`.

    // auth
    var key = process.env.HUBOT_TRELLO_KEY;
    var token = process.env.HUBOT_TRELLO_TOKEN;
    var trello = new Trello(key, token);
    var trelloP = new TrelloP(key, token);

    function sendMessageToSlackResponseURL(responseURL, JSONmessage){
      var postOptions = {
          uri: responseURL,
          method: 'POST',
          headers: {
              'Content-type': 'application/json'
          },
          json: JSONmessage
      };
      request(postOptions, (error, response, body) => {
          if (error){
              // handle errors as you see fit
          };
      })
    }

    // Associate a board with a specific Channel
    robot.hear(/trello hooks/, function(res_r) {
        let boardId = 'BE7seI7e';
        let cb_url = 'https://andreasbot.herokuapp.com/hubot/trello-webhooks';
        let args = {description:"my test webhook", callbackURL:cb_url, idModel:'59245663c76f54b975558854'};

        trelloP.postAsync('/1/webhooks', args).then(function(data){
            robot.logger.info(' NO error')
        }).catch(function(err){
            robot.logger.error('error')
            robot.logger.error(err);
        })

        // trello.post('/1/webhooks', args, function(err, data){
        //     if (err){
        //         robot.logger.error('error')
        //         //robot.logger.error(err);
        //         return 0;
        //     }
        //     robot.logger.info('NO error');
        // })
    })

    robot.hear(/trello h2/, function(res_r) {
        var options = {
            method: 'POST',
            uri: `https://api.trello.com/1/tokens/${token}/webhooks/?key=${key}`,
            body: {
                description: "My first webhook",
                callbackURL: "https://andreasbot.herokuapp.com/hubot/trello-webhooks",
                idModel: "59245663c76f54b975558854",
            },
            json: true // Automatically stringifies the body to JSON
        };

        rp(options)
            .then(function (parsedBody) {
                robot.logger.info(parsedBody);
            })
            .catch(function (err) {
                robot.logger.error(err);
            });

    })

	robot.on('trello-webhook-event', function(data, res){
        var room = "random";
        robot.messageRoom(room, `trello-webhook-event`);
    })

    // trello board
    robot.hear(/trello board/i, function(res_r){
        // TODO: fetch the board id from other source (env, redis or mongodb)
        let boardId = 'BE7seI7e';
        let args = {fields: "name,url,prefs"};

        trello.get("/1/board/"+boardId, args, function(err, data){
            if (err){
                res.send('Error: ' + err);
                robot.logger.error(err);
                return 0;
            }
            // else !err
            let msg = slackmsg.buttons();
            msg.attachments[0].title = `<${data.url}|${data.name}>`;
            msg.attachments[0].title_url = 'www.google.com'
            msg.attachments[0].author_name = 'Board'
            msg.attachments[0].callback_id = `trello_board`;
            msg.attachments[0].color = `${data.prefs.backgroundColor}`;

            // attach the board lists to buttons
            let joinBtn = {"name": "join", "text": "Join","type":"button", "value": "join"};
            let subBtn  = {"name": "sub", "text": "Subscribe","type":"button", "value": "sub"};
            let starBtn = {"name": "star", "text": "Star","type":"button", "value": "star"};
            let listsBtn= {"name": "lists", "text": "Lists","type":"button", "value": "lists"};
            let doneBtn = {"name": "done", "text": "Done","type":"button", "value": "done","style": "danger"};
            msg.attachments[0].actions.push(joinBtn);
            msg.attachments[0].actions.push(subBtn);
            msg.attachments[0].actions.push(starBtn);
            msg.attachments[0].actions.push(listsBtn);
            msg.attachments[0].actions.push(doneBtn);

            res_r.send(msg);
        })
    })

    /*******************************************************************/
    /*                        robot.on listeners                       */
    /*******************************************************************/

    var slackCB = 'slack:msg_action:';

    // responding to 'trello_board' interactive message
    robot.on(slackCB + 'trello_board', function(data, res){
        robot.logger.info(`robot.on: ${slackCB}trello_board`);
        let btnId = data.actions[0].value;
        let btnName = data.actions[0].name;
        let response_url = data.response_url;
        let msg;

        switch (btnId) {
          case 'join':
            break;
          case 'sub':
            break;
          case 'star':
                break;
          case 'lists':
            res.status(200).end(); // best practice to respond with 200 status
            // get board info to fetch lists
            let boardId = 'BE7seI7e';
            let args = {lists:"all"};
            trello.get("1/board/"+boardId, args, function(err, data){
                if (err){
                    res.send(err);
                    robot.logger.error(err);
                    return;
                }
                // else if (!err)
                // create buttons msg
                let msg = slackmsg.buttons();
                msg.text = `*${data.name}* board`;

                msg.attachments[0].text = `Available lists`;
                msg.attachments[0].callback_id = `trello_list`;
                let listsNum = Object.keys(data.lists).length;
                for (var i=0; i<listsNum; i++){
                    // TODO change value to some id or something similar
                    let name = data.lists[i].name;
                    let id = data.lists[i].id;
                    let list = {"name": name, "text": name, "type":"button", "value": id};
                    msg.attachments[0].actions.push(list);
                }
                sendMessageToSlackResponseURL(response_url, msg);
            })
            break;
          case 'done':
            //res.status(200).end() // best practice to respond with 200 status
            msg = slackmsg.plainText();
            res.send(msg);
            //sendMessageToSlackResponseURL(response_url, msg);
            // res.send(msg);
            break;
          default:
            //Statements executed when none of the values match the value of the expression
            break;
        }
    })

    // responding to 'trello_list' interactive message
    robot.on(slackCB + 'trello_list', function(data_board, res){
        let response_url = data_board.response_url;

        res.status(200).end() // best practice to respond with 200 status
        robot.logger.info(`robot.on: ${slackCB}trello_list`);
        let listId = data_board.actions[0].value;
        let listName = data_board.actions[0].name;

        // call function to fetch list - provide list id
        let args = {cards: "all"};
        trello.get("/1/lists/"+listId, args, function(err, data){
            if (err){
                robot.logger.error(err);
                res.send(`Error: ${err}`)
                return 0;
            }
            // else !err
            // create buttons msg
            let msg = slackmsg.buttons();
            msg.text = `*${listName}* list`;
            msg.attachments[0].text = `Available Cards`;
            msg.attachments[0].callback_id = `trello_list`;

            let cardsNum = Object.keys(data.cards).length;
            robot.logger.info(`total cards: ${cardsNum}`);
            for (var i=0; i<cardsNum; i++){
                let card    = data.cards[i].name;
                let cardId  = data.cards[i].id;
                let item    = {"name": card, "text": card,"type":"button", "value": cardId};
                msg.attachments[0].actions.push(item);
            }

            // respond with information for that list
            sendMessageToSlackResponseURL(response_url, msg);
        })
    })

    /*  TODO: add more functionality */

}

    /* developer's personal notes */

    /* template */

    // robot.respond(/trello /i, function(res_r) {
    //     t.post("/1/", function(err, data){
    //         if (err){
    //             res_r.send('Error Encountered: '+ err['responseBody']);
    //         }
    //     })
    // })

/* An example of using slack's response_url. ~For future use */
/* Add this snippet inside robot.on that u want to trigger   */

    // var response_url = data.response_url;
    // var slackMsg = require('./slackMsgs');
    // var response = slackMsg.ephemeralMsg();

    // sendMessageToSlackResponseURL(response_url, response);

<|endoftext|>


<commit_before>(function (root, factory) {
  var NAME = 'domscii';
  if (typeof define === 'function' && define.amd) {
    define(['html2canvas'], function (html2canvas) {
      return (root[NAME] = factory(html2canvas));
    });
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('html2canvas'));
  } else {
    root[NAME] = factory(root.html2canvas);
  }
}(this, function (html2canvas) {
  domscii.DEFAULTS = {
    template: '${char}',
    //template: '${char}',
    chars: '@#$=*!;:~-,.', // can be array of strings
    newline: '\n',
    scaleX: 7.875,
    scaleY: 15
  };
  function domscii (opts) {
    if (opts instanceof Node) opts = {el: opts};
    opts = extend(domscii.DEFAULTS, opts);

    var factor = (opts.chars.length-1)/255;

    return html2canvas(opts.el).then(function (canvas) {
      var data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;

      var lines = [];
      for (var y = 0; y < canvas.height; y += opts.scaleY) {
        var line = '';
        for (var x = 0; x < canvas.width; x += opts.scaleX) {
          var i = Math.floor(y*canvas.width + x)*4;
          var rgba = data.slice(i, i+4);
          var char = opts.chars[Math.round(factor*rms(rgba.slice(0, 3)))];
          line += opts.template
            .replace('${char}', char)
            .replace('${color}', 'rgba('+rgba.join(',')+')');
        }
        lines.push(line);
      }
      return lines.join(opts.newline);
    });
  }

  function rms (vector) {
    return Math.sqrt(vector.reduce(function (total, val) {
      return total + (val*val);
    }, 0) / vector.length);
  }

  function extend (obj, props) {
    obj = Object.create(obj);
    for (var prop in props) {
      if (props.hasOwnProperty(prop)) {
        obj[prop] = props[prop];
      }
    }
    return obj;
  }

  return domscii;
}));
<commit_msg>fix(firefox): fix Firefox support<commit_after>(function (root, factory) {
  var NAME = 'domscii';
  if (typeof define === 'function' && define.amd) {
    define(['html2canvas'], function (html2canvas) {
      return (root[NAME] = factory(html2canvas));
    });
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('html2canvas'));
  } else {
    root[NAME] = factory(root.html2canvas);
  }
}(this, function (html2canvas) {
  domscii.DEFAULTS = {
    template: '${char}',
    //template: '${char}',
    chars: '@#$=*!;:~-,.', // can be array of strings
    newline: '\n',
    scaleX: 7.875,
    scaleY: 15
  };
  function domscii (opts) {
    if (opts instanceof Node) opts = {el: opts};
    opts = extend(domscii.DEFAULTS, opts);

    var factor = (opts.chars.length-1)/255;

    return html2canvas(opts.el).then(function (canvas) {
      var data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;
      var _slice = [].slice.bind(data);

      var lines = [];
      for (var y = 0; y < canvas.height; y += opts.scaleY) {
        var line = '';
        for (var x = 0; x < canvas.width; x += opts.scaleX) {
          var i = Math.floor(y*canvas.width + x)*4;
          var rgba = _slice(i, i+4);
          var char = opts.chars[Math.round(factor*rms(rgba.slice(0, 3)))];
          line += opts.template
            .replace('${char}', char)
            .replace('${color}', 'rgba('+rgba.join(',')+')');
        }
        lines.push(line);
      }
      return lines.join(opts.newline);
    });
  }

  function rms (vector) {
    return Math.sqrt(vector.reduce(function (total, val) {
      return total + (val*val);
    }, 0) / vector.length);
  }

  function extend (obj, props) {
    obj = Object.create(obj);
    for (var prop in props) {
      if (props.hasOwnProperty(prop)) {
        obj[prop] = props[prop];
      }
    }
    return obj;
  }

  return domscii;
}));
<|endoftext|>


<commit_before>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/dialog',
], function(IPython, $, utils, dialog) {
    var ContentManager = function(options) {
        // Constructor
        //
        // A contentmanager handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
<<<<<<< HEAD
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          events: $(Events) instance
        //          base_url: string
        this.version = 0.1;
        this.events = options.events;
        this.base_url = options.base_url;
    };

    /**
     * Notebook Functions
     */

    /**
     * Load a notebook.
     *
     * Calls success_callback with notebook JSON object (as string), or
     * error_callback with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Function} success_callback
     * @param {Function} error_callback
     */
    ContentManager.prototype.load_notebook = function (path, name, success_callback,
        error_callback) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : success_callback,
            error : error_callback,
        };
        this.events.trigger('notebook_loading.Notebook');
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    /**
     * Creates a new notebook file at the specified path, and
     * opens that notebook in a new window.
     *
     * @method scroll_to_cell
     * @param {String} path The path to create the new notebook at
     */
    ContentManager.prototype.new_notebook = function(path) {
        var base_url = this.base_url;
        var settings = {
            processData : false,
            cache : false,
            type : "POST",
            dataType : "json",
            async : false,
            success : function (data, status, xhr){
                var notebook_name = data.name;
                window.open(
                    utils.url_join_encode(
                        base_url,
                        'contents',
                        path,
                        notebook_name
                    ),
                    '_blank'
                );
            },
            error : function(xhr, status, error) {
                utils.log_ajax_error(xhr, status, error);
                var msg;
                if (xhr.responseJSON && xhr.responseJSON.message) {
                    msg = xhr.responseJSON.message;
                } else {
                    msg = xhr.statusText;
                }
                dialog.modal({
                    title : 'Creating Notebook Failed',
                    body : "The error was: " + msg,
                    buttons : {'OK' : {'class' : 'btn-primary'}}
                });
            }
        };
        var url = utils.url_join_encode(
            base_url,
            'api/contents',
            path
        );
        $.ajax(url,settings);
    };

    ContentManager.prototype.delete_notebook = function(name, path) {
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : $.proxy(this.events.trigger, this.events,
                'notebook_deleted.ContentManager',
                {
                    name: name,
                    path: path
                }),
            error : utils.log_ajax_error
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    ContentManager.prototype.rename_notebook = function(path, name, new_name) {
        var that = this;
        var data = {name: new_name};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            contentType: 'application/json',
            success :  function (json, status, xhr) {
                that.events.trigger('notebook_rename_success.ContentManager',
                    json);
            },
            error : function (xhr, status, error) {
                that.events.trigger('notebook_rename_error.ContentManager',
                    [xhr, status, error]);
            }
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    ContentManager.prototype.save_notebook = function(path, name, content,
        extra_settings) {
        var that = content;
        // Create a JSON model to be sent to the server.
        var model = {
            name : name,
            path : path,
            type : "notebook",
            content : content
        };
        // time the ajax call for autosave tuning purposes.
        var start =  new Date().getTime();
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "PUT",
            data : JSON.stringify(model),
            contentType: 'application/json',
            success : $.proxy(this.events.trigger, this.events,
                'notebook_save_success.ContentManager',
                $.extend(model, { start : start })),
            error : function (xhr, status, error) {
                that.events.trigger('notebook_save_error.ContentManager',
                    [xhr, status, error, model]);
            }
        };
        if (extra_settings) {
            for (var key in extra_settings) {
                settings[key] = extra_settings[key];
            }
        }
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    /**
     * Checkpointing Functions
     */

    ContentManager.prototype.save_checkpoint = function() {
        // This is not necessary - integrated into save
    };

    ContentManager.prototype.restore_checkpoint = function(notebook, id) {
        that = notebook;
        this.events.trigger('notebook_restoring.Notebook', checkpoint);
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            this.notebook_path,
            this.notebook_name,
            'checkpoints',
            checkpoint
        );
        $.post(url).done(
            $.proxy(that.restore_checkpoint_success, that)
        ).fail(
            $.proxy(that.restore_checkpoint_error, that)
        );
    };

    ContentManager.prototype.list_checkpoints = function(notebook) {
        that = notebook;
        var url = utils.url_join_encode(
            that.base_url,
            'api/contents',
            that.notebook_path,
            that.notebook_name,
            'checkpoints'
        );
        $.get(url).done(
            $.proxy(that.list_checkpoints_success, that)
        ).fail(
            $.proxy(that.list_checkpoints_error, that)
        );
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Function} load_callback called with list of notebooks on success
     * @param {Function} error_callback called with ajax results on error
     */
    ContentManager.prototype.list_contents = function(path, load_callback,
        error_callback) {
        var that = this;
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : load_callback,
            error : error_callback
        };

        var url = utils.url_join_encode(this.base_url, 'api', 'contents',
            path);
        $.ajax(url, settings);
    }

    IPython.ContentManager = ContentManager;

    return {'ContentManager': ContentManager};
});
<commit_msg>ContentManager function signatures updated<commit_after>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/dialog',
], function(IPython, $, utils, dialog) {
    var ContentManager = function(options) {
        // Constructor
        //
        // A contentmanager handles passing file operations
        // to the back-end.  This includes checkpointing
        // with the normal file operations.
        //
        // Parameters:
        //  options: dictionary
        //      Dictionary of keyword arguments.
        //          events: $(Events) instance
        //          base_url: string
        this.version = 0.1;
        this.events = options.events;
        this.base_url = options.base_url;
    };

    /**
     * Notebook Functions
     */

    /**
     * Load a notebook.
     *
     * Calls success_callback with notebook JSON object (as string), or
     * error_callback with error.
     *
     * @method load_notebook
     * @param {String} path
     * @param {String} name
     * @param {Function} success_callback
     * @param {Function} error_callback
     */
    ContentManager.prototype.load_notebook = function (path, name, success_callback,
        error_callback) {
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : success_callback,
            error : error_callback,
        };
        this.events.trigger('notebook_loading.Notebook');
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    /**
     * Creates a new notebook file at the specified path, and
     * opens that notebook in a new window.
     *
     * @method scroll_to_cell
     * @param {String} path The path to create the new notebook at
     */
    ContentManager.prototype.new_notebook = function(path) {
        var base_url = this.base_url;
        var settings = {
            processData : false,
            cache : false,
            type : "POST",
            dataType : "json",
            async : false,
            success : function (data, status, xhr){
                var notebook_name = data.name;
                window.open(
                    utils.url_join_encode(
                        base_url,
                        'contents',
                        path,
                        notebook_name
                    ),
                    '_blank'
                );
            },
            error : function(xhr, status, error) {
                utils.log_ajax_error(xhr, status, error);
                var msg;
                if (xhr.responseJSON && xhr.responseJSON.message) {
                    msg = xhr.responseJSON.message;
                } else {
                    msg = xhr.statusText;
                }
                dialog.modal({
                    title : 'Creating Notebook Failed',
                    body : "The error was: " + msg,
                    buttons : {'OK' : {'class' : 'btn-primary'}}
                });
            }
        };
        var url = utils.url_join_encode(
            base_url,
            'api/contents',
            path
        );
        $.ajax(url,settings);
    };

    ContentManager.prototype.delete_notebook = function(name, path) {
        var settings = {
            processData : false,
            cache : false,
            type : "DELETE",
            dataType : "json",
            success : $.proxy(this.events.trigger, this.events,
                'notebook_deleted.ContentManager',
                {
                    name: name,
                    path: path
                }),
            error : utils.log_ajax_error
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    ContentManager.prototype.rename_notebook = function(path, name, new_name) {
        var that = this;
        var data = {name: new_name};
        var settings = {
            processData : false,
            cache : false,
            type : "PATCH",
            data : JSON.stringify(data),
            dataType: "json",
            contentType: 'application/json',
            success :  function (json, status, xhr) {
                that.events.trigger('notebook_rename_success.ContentManager',
                    json);
            },
            error : function (xhr, status, error) {
                that.events.trigger('notebook_rename_error.ContentManager',
                    [xhr, status, error]);
            }
        };
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    ContentManager.prototype.save_notebook = function(path, name, content,
        extra_settings) {
        var that = content;
        // Create a JSON model to be sent to the server.
        var model = {
            name : name,
            path : path,
            type : "notebook",
            content : content
        };
        // time the ajax call for autosave tuning purposes.
        var start =  new Date().getTime();
        // We do the call with settings so we can set cache to false.
        var settings = {
            processData : false,
            cache : false,
            type : "PUT",
            data : JSON.stringify(model),
            contentType: 'application/json',
            success : $.proxy(this.events.trigger, this.events,
                'notebook_save_success.ContentManager',
                $.extend(model, { start : start })),
            error : function (xhr, status, error) {
                that.events.trigger('notebook_save_error.ContentManager',
                    [xhr, status, error, model]);
            }
        };
        if (extra_settings) {
            for (var key in extra_settings) {
                settings[key] = extra_settings[key];
            }
        }
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            path,
            name
        );
        $.ajax(url, settings);
    };

    /**
     * Checkpointing Functions
     */

    ContentManager.prototype.save_checkpoint = function() {
        // This is not necessary - integrated into save
    };

    ContentManager.prototype.restore_checkpoint = function(notebook, id) {
        that = notebook;
        this.events.trigger('notebook_restoring.Notebook', checkpoint);
        var url = utils.url_join_encode(
            this.base_url,
            'api/contents',
            this.notebook_path,
            this.notebook_name,
            'checkpoints',
            checkpoint
        );
        $.post(url).done(
            $.proxy(that.restore_checkpoint_success, that)
        ).fail(
            $.proxy(that.restore_checkpoint_error, that)
        );
    };

    ContentManager.prototype.list_checkpoints = function(notebook) {
        that = notebook;
        var url = utils.url_join_encode(
            that.base_url,
            'api/contents',
            that.notebook_path,
            that.notebook_name,
            'checkpoints'
        );
        $.get(url).done(
            $.proxy(that.list_checkpoints_success, that)
        ).fail(
            $.proxy(that.list_checkpoints_error, that)
        );
    };

    /**
     * File management functions
     */

    /**
     * List notebooks and directories at a given path
     *
     * On success, load_callback is called with an array of dictionaries
     * representing individual files or directories.  Each dictionary has
     * the keys:
     *     type: "notebook" or "directory"
     *     name: the name of the file or directory
     *     created: created date
     *     last_modified: last modified dat
     *     path: the path
     * @method list_notebooks
     * @param {String} path The path to list notebooks in
     * @param {Function} load_callback called with list of notebooks on success
     * @param {Function} error_callback called with ajax results on error
     */
    ContentManager.prototype.list_contents = function(path, load_callback,
        error_callback) {
        var that = this;
        var settings = {
            processData : false,
            cache : false,
            type : "GET",
            dataType : "json",
            success : load_callback,
            error : error_callback
        };

        var url = utils.url_join_encode(this.base_url, 'api', 'contents',
            path);
        $.ajax(url, settings);
    }

    IPython.ContentManager = ContentManager;

    return {'ContentManager': ContentManager};
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

'use strict';

const React = require('react');
const createReactClass = require('create-react-class');
const ReactNative = require('react-native');
const RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter');
const Subscribable = require('Subscribable');
const {View} = ReactNative;

const {TestModule} = ReactNative.NativeModules;

const reactViewWidth = 111;
const reactViewHeight = 222;

let finalState = false;

const SizeFlexibilityUpdateTest = createReactClass({
  displayName: 'SizeFlexibilityUpdateTest',
  mixins: [Subscribable.Mixin],

  UNSAFE_componentWillMount: function() {
    this.addListenerOn(
      RCTNativeAppEventEmitter,
      'rootViewDidChangeIntrinsicSize',
      this.rootViewDidChangeIntrinsicSize,
    );
  },

  markPassed: function() {
    TestModule.markTestPassed(true);
    finalState = true;
  },

  rootViewDidChangeIntrinsicSize: function(intrinsicSize) {
    if (finalState) {
      // If a test reaches its final state, it is not expected to do anything more
      TestModule.markTestPassed(false);
      return;
    }

    if (this.props.both) {
      if (
        intrinsicSize.width === reactViewWidth &&
        intrinsicSize.height === reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.height) {
      if (
        intrinsicSize.width !== reactViewWidth &&
        intrinsicSize.height === reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.width) {
      if (
        intrinsicSize.width === reactViewWidth &&
        intrinsicSize.height !== reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.none) {
      if (
        intrinsicSize.width !== reactViewWidth &&
        intrinsicSize.height !== reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
  },

  render() {
    return <View style={{height: reactViewHeight, width: reactViewWidth}} />;
  },
});

SizeFlexibilityUpdateTest.displayName = 'SizeFlexibilityUpdateTest';

module.exports = SizeFlexibilityUpdateTest;
<commit_msg>Remove SubscribableMixin from SizeFlexibilityUpdateTest (#21580)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const React = require('react');
const createReactClass = require('create-react-class');
const ReactNative = require('react-native');
const RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter');
const {View} = ReactNative;

const {TestModule} = ReactNative.NativeModules;
import type EmitterSubscription from 'EmitterSubscription';

const reactViewWidth = 111;
const reactViewHeight = 222;

let finalState = false;

const SizeFlexibilityUpdateTest = createReactClass({
  displayName: 'SizeFlexibilityUpdateTest',
  _subscription: (null: ?EmitterSubscription),

  UNSAFE_componentWillMount: function() {
    this._subscription = RCTNativeAppEventEmitter.addListener(
      'rootViewDidChangeIntrinsicSize',
      this.rootViewDidChangeIntrinsicSize,
    );
  },

  componentWillUnmount: function() {
    if (this._subscription != null) {
      this._subscription.remove();
    }
  },

  markPassed: function() {
    TestModule.markTestPassed(true);
    finalState = true;
  },

  rootViewDidChangeIntrinsicSize: function(intrinsicSize) {
    if (finalState) {
      // If a test reaches its final state, it is not expected to do anything more
      TestModule.markTestPassed(false);
      return;
    }

    if (this.props.both) {
      if (
        intrinsicSize.width === reactViewWidth &&
        intrinsicSize.height === reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.height) {
      if (
        intrinsicSize.width !== reactViewWidth &&
        intrinsicSize.height === reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.width) {
      if (
        intrinsicSize.width === reactViewWidth &&
        intrinsicSize.height !== reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
    if (this.props.none) {
      if (
        intrinsicSize.width !== reactViewWidth &&
        intrinsicSize.height !== reactViewHeight
      ) {
        this.markPassed();
        return;
      }
    }
  },

  render() {
    return <View style={{height: reactViewHeight, width: reactViewWidth}} />;
  },
});

SizeFlexibilityUpdateTest.displayName = 'SizeFlexibilityUpdateTest';

module.exports = SizeFlexibilityUpdateTest;
<|endoftext|>


<commit_before><commit_msg>Fix a minor doc issue in fps.js<commit_after><|endoftext|>


<commit_before>var _ = require('underscore');
var swig = require('swig');
var util = require('util');
var async = require('async');
var util = require('util');
var db = require('./../../db/dbConnector').db;
var logger = require('./../../utils/logger');
var notification = require('./../../utils/notification');
var config = require('../../../config');
var mandrill = require('node-mandrill');

var subject = 'Best collections of Startups, Design, JavaScript and more';
var utm = '?utm_source=coll-weekly&utm_medium=email&utm_campaign=coll-promo-w30';

var collections = [
	{
		"id" : "53a03c550648690f00000001",
		"color" : "#56c7aa",
		"public" : true,
		"thumbnail" : "http://i.vimeocdn.com/video/473268404_640.jpg",
		"title" : "motion insprtn",
		"description": "Nice looking motion designs",
		"url": "https://app.likeastore.com/u/Nikitaita/53a03c550648690f00000001",
		"user" : "nikitoss@gmail.com",
		"name" : "Nikitaita",
		"authorUrl": "https://app.likeastore.com/u/Nikitaita"
	},
	{
		"id" : "5355252fedce3c0c00000001",
		"color" : "#56c7aa",
		"public" : true,
		"thumbnail" : "https://i1.ytimg.com/vi/gQ0IlRvT_Mo/hqdefault.jpg",
		"title" : "Music",
		"description" : "We gonna rock you",
		"url": "https://app.likeastore.com/u/summa1910/5355252fedce3c0c00000001",
		"user" : "jonesitis7@gmail.com",
		"name" : "summa1910",
		"authorUrl": "https://app.likeastore.com/u/summa1910"
	},
	{
		"id" : "531f863949e12d0d0000000b",
		"color" : "#e74c3c",
		"public" : true,
		"thumbnail" : "https://i1.ytimg.com/vi/NPISkbzY-bk/hqdefault.jpg",
		"title" : "Movies & TV Shows",
		"description": "If you are boared, check this collection",
		"url": "https://app.likeastore.com/u/yl_ksa/531f863949e12d0d0000000b",
		"user" : "yl.ksaa@gmail.com",
		"name" : "yl_ksa",
		"authorUrl": "https://app.likeastore.com/u/yl_ksa"
	},
	{
		"id" : "535123356c39511000000001",
		"color" : "#56c7aa",
		"public" : true,
		"title" : "Think Javascript",
		"description": "The greatest collection for Javascript developers",
		"url": "https://app.likeastore.com/u/voronianski/535123356c39511000000001",
		"user" : "dmitri.voronianski@gmail.com",
		"name" : "voronianski",
		"authorUrl": "https://app.likeastore.com/u/voronianski"
	},
	{
		"id" : "535e0b4ebc7cb00e0000000d",
		"color" : "#c8c8c8",
		"public" : true,
		"title" : "Great Open Source",
		"description": "Best picks from OS world",
		"url": "https://app.likeastore.com/u/TjRus/535e0b4ebc7cb00e0000000d",
		"user" : "i@tjrus.com",
		"name" : "TjRus",
		"authorUrl": "https://app.likeastore.com/u/TjRus"
	}
];

var template = swig.compileFile(__dirname + '../../../../templates/swig/input/collections-promo/index.html');

function sendCollectionPromo(callback) {
	if (!config.mandrill.token) {
		return callback('no mandrill token. ok for development mode, fail for production mode');
	}

	var emails = {};
	var mandrillApi = mandrill(config.mandrill.token);

	return readAllUsersEmails();

	function readAllUsersEmails() {
		logger.info('reading users emails...');

		db.users.find({unsubscribed: {$exists: false}, firstTimeUser: {$exists: false}}).forEach(function (err, user) {
			if (err) {
				return callback(err);
			}

			if (!user) {
				return sendNofications(null);
			}

			emails[user.email] = user._id.toString();
		});
	}

	function sendNofications() {
		var allEmails = Object.keys(emails);

		var collectionsWithUtm = collections.map(function (collection) {
			var clone = _.clone(collection);
			clone.url += utm;
			clone.authorUrl += utm;

			return clone;
		});

		logger.info('prepared ' + allEmails.length + ' to send...');

		var sendTasks = allEmails.map(function (email) {
			return function(callback) {
				logger.info('sending notification to ' + email);

				var data = _.extend({userId: emails[email]}, {collections: collectionsWithUtm});
				var html = template(data);

				mandrillApi('/messages/send', {
					message: {
						html: html,
						subject: subject,
						from_email: 'no-reply@likeastore.com',
						from_name: 'Likeastore Team',
						track_clicks: false,
						to: [{email: email}],
						preserve_recipients: false,
					},
				}, callback);
			};
		});

		async.parallelLimit(sendTasks, 256, function (err) {
			if (err) {
				logger.error(err);
			}

			callback (err);
		});
	}
}

module.exports = sendCollectionPromo;<commit_msg>title updated<commit_after>var _ = require('underscore');
var swig = require('swig');
var util = require('util');
var async = require('async');
var util = require('util');
var db = require('./../../db/dbConnector').db;
var logger = require('./../../utils/logger');
var notification = require('./../../utils/notification');
var config = require('../../../config');
var mandrill = require('node-mandrill');

var subject = 'Best collections of Motion Design, Music, Movies and more';
var utm = '?utm_source=coll-weekly&utm_medium=email&utm_campaign=coll-promo-w30';

var collections = [
	{
		"id" : "53a03c550648690f00000001",
		"color" : "#56c7aa",
		"public" : true,
		"thumbnail" : "http://i.vimeocdn.com/video/473268404_640.jpg",
		"title" : "motion insprtn",
		"description": "Nice looking motion designs",
		"url": "https://app.likeastore.com/u/Nikitaita/53a03c550648690f00000001",
		"user" : "nikitoss@gmail.com",
		"name" : "Nikitaita",
		"authorUrl": "https://app.likeastore.com/u/Nikitaita"
	},
	{
		"id" : "5355252fedce3c0c00000001",
		"color" : "#56c7aa",
		"public" : true,
		"thumbnail" : "https://i1.ytimg.com/vi/gQ0IlRvT_Mo/hqdefault.jpg",
		"title" : "Music",
		"description" : "We gonna rock you",
		"url": "https://app.likeastore.com/u/summa1910/5355252fedce3c0c00000001",
		"user" : "jonesitis7@gmail.com",
		"name" : "summa1910",
		"authorUrl": "https://app.likeastore.com/u/summa1910"
	},
	{
		"id" : "531f863949e12d0d0000000b",
		"color" : "#e74c3c",
		"public" : true,
		"thumbnail" : "https://i1.ytimg.com/vi/NPISkbzY-bk/hqdefault.jpg",
		"title" : "Movies & TV Shows",
		"description": "If you are boared, check this collection",
		"url": "https://app.likeastore.com/u/yl_ksa/531f863949e12d0d0000000b",
		"user" : "yl.ksaa@gmail.com",
		"name" : "yl_ksa",
		"authorUrl": "https://app.likeastore.com/u/yl_ksa"
	},
	{
		"id" : "535123356c39511000000001",
		"color" : "#56c7aa",
		"public" : true,
		"title" : "Think Javascript",
		"description": "The greatest collection for Javascript developers",
		"url": "https://app.likeastore.com/u/voronianski/535123356c39511000000001",
		"user" : "dmitri.voronianski@gmail.com",
		"name" : "voronianski",
		"authorUrl": "https://app.likeastore.com/u/voronianski"
	},
	{
		"id" : "535e0b4ebc7cb00e0000000d",
		"color" : "#c8c8c8",
		"public" : true,
		"title" : "Great Open Source",
		"description": "Best picks from OS world",
		"url": "https://app.likeastore.com/u/TjRus/535e0b4ebc7cb00e0000000d",
		"user" : "i@tjrus.com",
		"name" : "TjRus",
		"authorUrl": "https://app.likeastore.com/u/TjRus"
	}
];

var template = swig.compileFile(__dirname + '../../../../templates/swig/input/collections-promo/index.html');

function sendCollectionPromo(callback) {
	if (!config.mandrill.token) {
		return callback('no mandrill token. ok for development mode, fail for production mode');
	}

	var emails = {};
	var mandrillApi = mandrill(config.mandrill.token);

	return readAllUsersEmails();

	function readAllUsersEmails() {
		logger.info('reading users emails...');

		db.users.find({unsubscribed: {$exists: false}, firstTimeUser: {$exists: false}}).forEach(function (err, user) {
			if (err) {
				return callback(err);
			}

			if (!user) {
				return sendNofications(null);
			}

			emails[user.email] = user._id.toString();
		});
	}

	function sendNofications() {
		var allEmails = Object.keys(emails);

		var collectionsWithUtm = collections.map(function (collection) {
			var clone = _.clone(collection);
			clone.url += utm;
			clone.authorUrl += utm;

			return clone;
		});

		logger.info('prepared ' + allEmails.length + ' to send...');

		var sendTasks = allEmails.map(function (email) {
			return function(callback) {
				logger.info('sending notification to ' + email);

				var data = _.extend({userId: emails[email]}, {collections: collectionsWithUtm});
				var html = template(data);

				mandrillApi('/messages/send', {
					message: {
						html: html,
						subject: subject,
						from_email: 'no-reply@likeastore.com',
						from_name: 'Likeastore Team',
						track_clicks: false,
						to: [{email: email}],
						preserve_recipients: false,
					},
				}, callback);
			};
		});

		async.parallelLimit(sendTasks, 256, function (err) {
			if (err) {
				logger.error(err);
			}

			callback (err);
		});
	}
}

module.exports = sendCollectionPromo;<|endoftext|>


<commit_before>var Promise_ = require("es6-promise").Promise;
var { Observable, config } = require("rx/dist/rx.lite.js");
var { fromEvent, merge } = Observable;
var { debounce, identity, isArray, map, noop } = require("./misc");

config.useNativeEvents = true;
config.Promise = Promise_;

var observableProto = Observable.prototype;

if (__DEV__) {
  observableProto.log = function(ns, fn) {
    if (!ns) ns = "";
    return this.do(
      x  => console.log(ns, "next",  (fn || identity)(x)),
      e  => console.log(ns, "error", e),
      () => console.log(ns, "completed")
    );
  };
} else {
  observableProto.log = function() { return this; };
}

observableProto.each = function(onNext) {
  return this.subscribe(onNext, noop);
};

var simpleEquals = (a, b) => a === b;
observableProto.changes = function(keySelector) {
  return this.distinctUntilChanged(keySelector, simpleEquals);
};

observableProto.customDebounce = function(time, debounceOptions) {
  var source = this;
  return Observable.create(observer => {
    var debounced = debounce(val => observer.onNext(val), time, debounceOptions);
    var subscribe = source.subscribe(
      debounced,
      e  => observer.onError(e),
      () => observer.onCompleted()
    );
    return () => {
      debounced.dispose();
      subscribe.dispose();
    };
  });
};

module.exports = {
  on(elt, evts) {
    if (isArray(evts)) {
      return merge(map(evts, evt => fromEvent(elt, evt)));
    } else {
      return fromEvent(elt, evts);
    }
  },
  first(obs) {
    return obs.take(1);
  },
  only(x) {
    return Observable.never().startWith(x);
  },
};
<commit_msg>fix import<commit_after>var Promise_ = require("es6-promise").Promise;
var { Observable, config } = require("rx/dist/rx.lite.js");
var { fromEvent, merge } = Observable;
var { identity, isArray, map, noop } = require("./misc");
var debounce = require("./debounce");

config.useNativeEvents = true;
config.Promise = Promise_;

var observableProto = Observable.prototype;

if (__DEV__) {
  observableProto.log = function(ns, fn) {
    if (!ns) ns = "";
    return this.do(
      x  => console.log(ns, "next",  (fn || identity)(x)),
      e  => console.log(ns, "error", e),
      () => console.log(ns, "completed")
    );
  };
} else {
  observableProto.log = function() { return this; };
}

observableProto.each = function(onNext) {
  return this.subscribe(onNext, noop);
};

var simpleEquals = (a, b) => a === b;
observableProto.changes = function(keySelector) {
  return this.distinctUntilChanged(keySelector, simpleEquals);
};

observableProto.customDebounce = function(time, debounceOptions) {
  var source = this;
  return Observable.create(observer => {
    var debounced = debounce(val => observer.onNext(val), time, debounceOptions);
    var subscribe = source.subscribe(
      debounced,
      e  => observer.onError(e),
      () => observer.onCompleted()
    );
    return () => {
      debounced.dispose();
      subscribe.dispose();
    };
  });
};

module.exports = {
  on(elt, evts) {
    if (isArray(evts)) {
      return merge(map(evts, evt => fromEvent(elt, evt)));
    } else {
      return fromEvent(elt, evts);
    }
  },
  first(obs) {
    return obs.take(1);
  },
  only(x) {
    return Observable.never().startWith(x);
  },
};
<|endoftext|>


<commit_before>var ltkb;

$(function() {
	var cont
		, i,
	requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
	window.requestAnimationFrame = requestAnimationFrame;

	litroAudio = new LitroSound();
	litroAudio.init(SAMPLE_RATE, CHANNELS, BUFFER_FRAMES);

	ltkb = new LitroKeyboard();
	ltkb.init(litroAudio);

	requestAnimationFrame(main);

});

function main() {
	litroSoundMain();
	litroKeyboardMain();
	keyStateCheck();

	requestAnimationFrame(main);
};

window.addEventListener("beforeunload", function() {
	return "LitroKeyboardを中断します";
}, false);
<commit_msg>ページ移動確認ダイアログ<commit_after>var ltkb;

$(function() {
	var cont
		, i,
	requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
	window.requestAnimationFrame = requestAnimationFrame;

	litroAudio = new LitroSound();
	litroAudio.init(SAMPLE_RATE, CHANNELS, BUFFER_FRAMES);

	ltkb = new LitroKeyboard();
	ltkb.init(litroAudio);

	requestAnimationFrame(main);

});

function main() {
	litroSoundMain();
	litroKeyboardMain();
	keyStateCheck();

	requestAnimationFrame(main);
};

window.onbeforeunload = function(event){
	event = event || window.event;
	return event.returnValue = 'LitroKeyboardを中断します';
};
<|endoftext|>


<commit_before>var knex = require('knex')({client:'sqlite3',connection:{filename: 'data.sqlite3'}});
var bookshelf = require('bookshelf')(knex);
var Language = bookshelf.Model.extend({tableName: 'languages', idAttribute: 'languageid'});
var Post = bookshelf.Model.extend({tableName: 'posts', idAttribute: 'postid', Language: function(){return this.belongsTo(Language,'languageid');}});

Post.where({postid: 3}).fetch({withRelated:['Language']}).then(function(p) {console.log(p.toJSON());});
// Outputs:
// { postid: 3,
//   languageid: 1,
//   text: 'something else written',
//   Language: { languageid: 1, name: 'english' } }

Post.where({postid: 3}).fetch({withRelated:['Language']}).then(function(p) {console.log(p.related('Language').toJSON());});
// Outputs:
// { languageid: 1, name: 'english' }
<commit_msg>add debug to sample<commit_after>var knex = require('knex')({client:'sqlite3',connection:{filename: 'data.sqlite3'}, debug: true});
var bookshelf = require('bookshelf')(knex);
var Language = bookshelf.Model.extend({tableName: 'languages', idAttribute: 'languageid'});
var Post = bookshelf.Model.extend({tableName: 'posts', idAttribute: 'postid', Language: function(){return this.belongsTo(Language,'languageid');}});

Post.where({postid: 3}).fetch({withRelated:['Language']}).then(function(p) {console.log(p.toJSON());});
// Outputs:
// { postid: 3,
//   languageid: 1,
//   text: 'something else written',
//   Language: { languageid: 1, name: 'english' } }

Post.where({postid: 3}).fetch({withRelated:['Language']}).then(function(p) {console.log(p.related('Language').toJSON());});
// Outputs:
// { languageid: 1, name: 'english' }
<|endoftext|>


<commit_before>"use strict";

describe('View treebank document', function(){
  beforeEach(function() {
    browser.get('/app/#/e2e?doc=caesar2&s=21');
  });

  iit('displays the sentence and a tree', function() {
    var sentenceElement = by.css("p span span");
    var treeNode = by.css("div.node span[token] span");

    // word in the sentence
    expect(element(sentenceElement).getText()).toMatch("In");

    // word in the tree
    expect(element(treeNode).getInnerHtml()).toMatch("In");
  });
});

<commit_msg>Readd first tree test<commit_after>"use strict";

describe('View treebank document', function(){
  beforeEach(function() {
    browser.get('/app/#/e2e?doc=caesar2&s=21');
  });

  it('displays the sentence and a tree', function() {
    var sentenceElement = by.css("p span span");
    var treeNode = by.css("div.node span[token] span");

    // word in the sentence
    expect(element(sentenceElement).getText()).toMatch("In");

    // word in the tree
    expect(element(treeNode).getInnerHtml()).toMatch("In");
  });
});

<|endoftext|>