<commit_before>import "./vendor"
import sound from "./sound"
import Fire from "./fire"
import DB from "./db"
import $ from "jquery"
import _ from "ramda"
import Notifications from "./notifications"
import cryptoRandomString from "crypto-random-string"
import autosize from "autosize"
import localforage from "localforage"
import MutationSummary from "mutation-summary"
import {Kefir} from "kefir"

// noinspection NpmUsedModulesInstalled
import {Main} from "elm/Main.elm"

//noinspection JSUnresolvedVariable
const isDevelopmentMode = process.env["NODE_ENV"] === "development"
const env = process.env
const npmPackageVersion = env["npm_package_version"]

//test

/*global.xhot = module.hot

 if(module.hot){
 module.hot.addStatusHandler(status => {
 console.warn("hot status", status);
 })
 }*/

const observer = new MutationSummary({
    callback: summaries => {
        // console.log(summaries)

        const autoFocusSummary = summaries[0]
        $(autoFocusSummary.added).first().focus()
        console.log(autoFocusSummary.added)
    },
    queries: [{element: ".auto-focus"}],
})

window.appBoot = async function appBoot() {
    const deviceId = getOrCreateDeviceId()
    const isFirstVisit = getOrCreateFirstVisit()
    const $elm = $("#elm-container")
    // $elm.trap();

    $elm.on("keydown", `.entity-list`, e => {
        // console.log(e.keyCode, e.key, e.target, e);

        if (e.target.tagName !== "PAPER-INPUT") {
            // prevent document scrolling
            if (e.key === " "/*space: 32*/ && e.target.tagName !== "PAPER-INPUT") {
                e.preventDefault()
            }
            else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
                e.preventDefault()
            }
        }

    })

    /*Kefir.merge(
        [Kefir.fromEvents($elm.get(0), "focusin"),
         Kefir.fromEvents($elm.get(0), "focusout"),
        ],
         )
         .debounce(1)
         .filter(_.propEq("type", "focusout"))
         .log()
         .observe({value(){
             requestAnimationFrame(()=>{
                 $(".entity-list .focusable-list-item[tabindex=0]").first().focus()
             })
         }})*/

    $elm.get(0).addEventListener("keydown", e => {
        const $closest = $(e.target).closest("[data-prevent-default-keys]")
        if ($closest.length === 0) return
        const preventDefaultKeys =
            $closest.data("prevent-default-keys").split(",")
        // console.log(e.keyCode, e.key, e, preventDefaultKey);

        if (_.contains(e.key)(preventDefaultKeys)) {
            e.preventDefault()
        }
    }, true)

    const db = await DB()

    const store = localforage.createInstance({
        name: "SimpleGTD.com offline store",
    });

    const localPref = await store.getItem("local-pref")

    const flags = _.merge({
        now: Date.now(),
        // encodedTodoList: await allDocsMap["todo-db"],
        // encodedProjectList: await allDocsMap["project-db"],
        // encodedContextList: await allDocsMap["context-db"],
        // encodedTodoList: [],
        // encodedProjectList: [],
        // encodedContextList: [],
        pouchDBRemoteSyncURI: localStorage.getItem("pouchdb.remote-sync-uri") || "",
        developmentMode: isDevelopmentMode,
        appVersion: npmPackageVersion,
        deviceId,
        config: {isFirstVisit, deviceId, npmPackageVersion, isDevelopmentMode},
        localPref: localPref,
    }, db.allDocsMap)

    const app = Main
        .embed(document.getElementById("elm-container"), flags)

    const fire = Fire.setup(app, _.values(db.list), deviceId)

    db.setupApp(app)

    app.ports["persistLocalPref"].subscribe(async (localPref) => {
        store.setItem("local-pref", localPref)
             .catch(console.error)
    });

    Notifications.setup(fire, app).catch(console.error)

    app.ports["focusSelector"].subscribe((selector) => {
        // setTimeout(() => {
        requestAnimationFrame(() => {
            // note - we blur here so that view scrolls to element if it already had focus
            $(selector).blur().focus()
        })
        // }, 0)
    })

    app.ports["focusSelectorIfNoFocus"].subscribe((selector) => {
        const $focus = $(":focus, [focused]")
        // console.log($focus, $focus.length)
        if ($focus.length === 0) {
            $(selector).focus()
        }
    })

    app.ports["positionPopupMenu"].subscribe((ofSelector) => {
        requestAnimationFrame(() => {
            const $popup = $("#popup-menu")
            $popup.position({
                my: "right top",
                at: "right top",
                of: $(ofSelector),
                within: ".overlay",
                collision: "fit",
            })
            $popup.find(".auto-focus").first().focus()

            const focusableSelector = ":focusable, input:focusable"
            $popup.on("keydown", focusableSelector, function (e) {
                if (e.key !== "Tab") return
                const $focusable = $popup.find(focusableSelector)

                const first = $focusable.first().get(0)
                const last = $focusable.last().get(0)
                const dateTimeInputSelector = `input[type="date"], input[type="time"]`

                if (e.shiftKey) {

                    if (this === first) {
                        e.preventDefault()
                        e.stopPropagation()
                        last.focus()
                    } else if ($(this).is(dateTimeInputSelector)) {
                        e.preventDefault()
                        e.stopPropagation()
                        $focusable.get($.inArray(this, $focusable) - 1).focus()
                    }
                }
                else {

                    if (this === last) {
                        e.preventDefault()
                        e.stopPropagation()
                        first.focus()
                    } else if ($(this).is(dateTimeInputSelector)) {
                        e.preventDefault()
                        e.stopPropagation()
                        $focusable.get($.inArray(this, $focusable) + 1).focus()
                    }
                }
            })
        })
    })

    app.ports["startAlarm"].subscribe(() => {
        sound.start()
    })

    app.ports["stopAlarm"].subscribe(() => {
        sound.stop()
    })
}

function getOrCreateDeviceId() {
    let deviceId = localStorage.getItem("device-id")
    if (!deviceId) {
        deviceId = cryptoRandomString(64)
        localStorage.setItem("device-id", deviceId)
    }
    return deviceId
}

function getOrCreateFirstVisit() {
    let firstVisit = localStorage.getItem("first-visit")
    if (!firstVisit) {
        localStorage.setItem("first-visit", "false")
    }
    return !!firstVisit
}
<commit_msg>* always enabling edit on click and mouse pointer for todo text.<commit_after>import "./vendor"
import sound from "./sound"
import Fire from "./fire"
import DB from "./db"
import $ from "jquery"
import _ from "ramda"
import Notifications from "./notifications"
import cryptoRandomString from "crypto-random-string"
// import autosize from "autosize"
import localforage from "localforage"
import MutationSummary from "mutation-summary"
import {Kefir} from "kefir"

// noinspection NpmUsedModulesInstalled
import {Main} from "elm/Main.elm"

//noinspection JSUnresolvedVariable
const isDevelopmentMode = process.env["NODE_ENV"] === "development"
const env = process.env
const npmPackageVersion = env["npm_package_version"]

//test

/*global.xhot = module.hot

 if(module.hot){
 module.hot.addStatusHandler(status => {
 console.warn("hot status", status);
 })
 }*/

const observer = new MutationSummary({
    callback: summaries => {
        // console.log(summaries)

        const autoFocusSummary = summaries[0]
        $(autoFocusSummary.added).first().focus()
        console.log(autoFocusSummary.added)
    },
    queries: [{element: ".auto-focus"}],
})

window.appBoot = async function appBoot() {
    const deviceId = getOrCreateDeviceId()
    const isFirstVisit = getOrCreateFirstVisit()
    const $elm = $("#elm-container")
    // $elm.trap();

    $elm.on("keydown", `.entity-list`, e => {
        // console.log(e.keyCode, e.key, e.target, e);

        if (e.target.tagName !== "PAPER-INPUT") {
            // prevent document scrolling
            if (e.key === " "/*space: 32*/ && e.target.tagName !== "PAPER-INPUT") {
                e.preventDefault()
            }
            else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
                e.preventDefault()
            }
        }

    })

    /*Kefir.merge(
        [Kefir.fromEvents($elm.get(0), "focusin"),
         Kefir.fromEvents($elm.get(0), "focusout"),
        ],
         )
         .debounce(1)
         .filter(_.propEq("type", "focusout"))
         .log()
         .observe({value(){
             requestAnimationFrame(()=>{
                 $(".entity-list .focusable-list-item[tabindex=0]").first().focus()
             })
         }})*/

    $elm.get(0).addEventListener("keydown", e => {
        const $closest = $(e.target).closest("[data-prevent-default-keys]")
        if ($closest.length === 0) return
        const preventDefaultKeys =
            $closest.data("prevent-default-keys").split(",")
        // console.log(e.keyCode, e.key, e, preventDefaultKey);

        if (_.contains(e.key)(preventDefaultKeys)) {
            e.preventDefault()
        }
    }, true)

    const db = await DB()

    const store = localforage.createInstance({
        name: "SimpleGTD.com offline store",
    });

    const localPref = await store.getItem("local-pref")

    const flags = _.merge({
        now: Date.now(),
        // encodedTodoList: await allDocsMap["todo-db"],
        // encodedProjectList: await allDocsMap["project-db"],
        // encodedContextList: await allDocsMap["context-db"],
        // encodedTodoList: [],
        // encodedProjectList: [],
        // encodedContextList: [],
        pouchDBRemoteSyncURI: localStorage.getItem("pouchdb.remote-sync-uri") || "",
        developmentMode: isDevelopmentMode,
        appVersion: npmPackageVersion,
        deviceId,
        config: {isFirstVisit, deviceId, npmPackageVersion, isDevelopmentMode},
        localPref: localPref,
    }, db.allDocsMap)

    const app = Main
        .embed(document.getElementById("elm-container"), flags)

    const fire = Fire.setup(app, _.values(db.list), deviceId)

    db.setupApp(app)

    app.ports["persistLocalPref"].subscribe(async (localPref) => {
        store.setItem("local-pref", localPref)
             .catch(console.error)
    });

    Notifications.setup(fire, app).catch(console.error)

    app.ports["focusSelector"].subscribe((selector) => {
        // setTimeout(() => {
        requestAnimationFrame(() => {
            // note - we blur here so that view scrolls to element if it already had focus
            $(selector).blur().focus()
        })
        // }, 0)
    })

    app.ports["focusSelectorIfNoFocus"].subscribe((selector) => {
        const $focus = $(":focus, [focused]")
        // console.log($focus, $focus.length)
        if ($focus.length === 0) {
            $(selector).focus()
        }
    })

    app.ports["positionPopupMenu"].subscribe((ofSelector) => {
        requestAnimationFrame(() => {
            const $popup = $("#popup-menu")
            $popup.position({
                my: "right top",
                at: "right top",
                of: $(ofSelector),
                within: ".overlay",
                collision: "fit",
            })
            $popup.find(".auto-focus").first().focus()

            const focusableSelector = ":focusable, input:focusable"
            $popup.on("keydown", focusableSelector, function (e) {
                if (e.key !== "Tab") return
                const $focusable = $popup.find(focusableSelector)

                const first = $focusable.first().get(0)
                const last = $focusable.last().get(0)
                const dateTimeInputSelector = `input[type="date"], input[type="time"]`

                if (e.shiftKey) {

                    if (this === first) {
                        e.preventDefault()
                        e.stopPropagation()
                        last.focus()
                    } else if ($(this).is(dateTimeInputSelector)) {
                        e.preventDefault()
                        e.stopPropagation()
                        $focusable.get($.inArray(this, $focusable) - 1).focus()
                    }
                }
                else {

                    if (this === last) {
                        e.preventDefault()
                        e.stopPropagation()
                        first.focus()
                    } else if ($(this).is(dateTimeInputSelector)) {
                        e.preventDefault()
                        e.stopPropagation()
                        $focusable.get($.inArray(this, $focusable) + 1).focus()
                    }
                }
            })
        })
    })

    app.ports["startAlarm"].subscribe(() => {
        sound.start()
    })

    app.ports["stopAlarm"].subscribe(() => {
        sound.stop()
    })
}

function getOrCreateDeviceId() {
    let deviceId = localStorage.getItem("device-id")
    if (!deviceId) {
        deviceId = cryptoRandomString(64)
        localStorage.setItem("device-id", deviceId)
    }
    return deviceId
}

function getOrCreateFirstVisit() {
    let firstVisit = localStorage.getItem("first-visit")
    if (!firstVisit) {
        localStorage.setItem("first-visit", "false")
    }
    return !!firstVisit
}
<|endoftext|>


<commit_before>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var EXPECTED_PARTS_COUNT = 2,
        NAMESPACE_PREFIX = 0,
        NAME = 1,
        parts = name.split(":");
    if (parts.length === EXPECTED_PARTS_COUNT) {
        return {
            namespace: _gpfWebGetNamespace(parts[NAMESPACE_PREFIX]),
            name: parts[NAME]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (name.includes(":")) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, [].slice.call(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<commit_msg>no-magic-numbers (#280)<commit_after>/**
 * @file Tag generation helper
 * @since 0.2.1
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfArraySlice*/ // [].slice.call
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfIsArray*/ // Return true if the parameter is an array
/*global _gpfIsLiteralObject*/ // Check if the parameter is a literal object
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*global _gpfStringEscapeFor*/ // Make the string content compatible with lang
/*global _gpfSyncReadSourceJSON*/ // Reads a source json file (only in source mode)
/*#endif*/

/**
 * Tag instance allocated by {@link gpf.typedef.tagFunc}
 *
 * @class gpf.typedef.Tag
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * String conversion
 *
 * @method gpf.typedef.Tag.prototype.toString
 * @return {String} HTML result
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * DOM insertion
 *
 * @method gpf.typedef.Tag.prototype.appendTo
 * @param {Object} node DOM parent to append the node to
 * @return {Object} Created DOM Node
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function child specification.  When string, a text node is created.
 *
 * @typedef gpf.typedef.tagChild
 * @property {String|gpf.typedef.Tag}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function children specification
 *
 * @typedef gpf.typedef.tagChildren
 * @property {gpf.typedef.tagChild|gpf.typedef.tagChild[]}
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

/**
 * Tag generation function, it accepts up to two parameters:
 * - When no parameter is passed, an empty node is created
 * - If the first parameter is a literal object (see {@link gpf.isLiteralObject}), its properties are used to
 *   define the node attributes
 * - Otherwise, the second (or first if not a literal object) is used to define children that will be appended to this
 *   tag
 *
 * @callback gpf.typedef.tagFunc
 *
 * @param {Object|gpf.typedef.tagChildren} [attributes=undefined] When a literal object is passed, it is interpreted
 * as an attribute dictionary. The attribute name may contain the xlink namespace prefix.
 * @param {gpf.typedef.tagChildren} [children=undefined] List of children
 * @return {gpf.typedef.Tag} Tag object
 *
 * @see gpf.web.createTagFunction
 * @since 0.2.1
 */

_gpfErrorDeclare("web/tag", {
    /**
     * ### Summary
     *
     * Missing node name
     *
     * ### Description
     *
     * A tag can't be created if the node name is missing
     * @since 0.2.1
     */
    missingNodeName: "Missing node name",

    /**
     * ### Summary
     *
     * Unknown namespace prefix
     *
     * ### Description
     *
     * A prefix has been used prior to be associated with a namespace
     * @since 0.2.2
     */
    unknownNamespacePrefix: "Unknown namespace prefix",

    /**
     * ### Summary
     *
     * Unable to use namespace in string
     *
     * ### Description
     *
     * A prefix associated to a namespace has been used and can't be converted to string
     * @since 0.2.2
     */
    unableToUseNamespaceInString: "Unable to use namespace in string"
});

/**
 * Mapping of attribute name aliases
 * @type {Object}
 * @since 0.2.1
 */
var _gpfWebTagAttributeAliases = _gpfSyncReadSourceJSON("web/attributes.json");

/**
 * Mapping of prefixes for namespaces
 * @type {Object}
 * @since 0.2.2
 */
var _gpfWebNamespacePrefix = _gpfSyncReadSourceJSON("web/namespaces.json");

/**
 * Retrieves namespace associated to the prefix or fail
 *
 * @param {String} prefix Namespace prefix
 * @return {String} Namespace URI
 * @throws {gpf.Error.UnknownNamespacePrefix}
 * @since 0.2.2
 */
function _gpfWebGetNamespace (prefix) {
    var namespace = _gpfWebNamespacePrefix[prefix];
    if (undefined === namespace) {
        gpf.Error.unknownNamespacePrefix();
    }
    return namespace;
}

/**
 * Resolves prefixed name to namespace and name
 *
 * @param {String} name Attribute or node name
 * @return {{namespace, name}|undefined} Namespace and name in a structure if prefixed, undefined otherwise
 * @since 0.2.2
 */
function _gpfWebGetNamespaceAndName (name) {
    var EXPECTED_PARTS_COUNT = 2,
        NAMESPACE_PREFIX = 0,
        NAME = 1,
        parts = name.split(":");
    if (parts.length === EXPECTED_PARTS_COUNT) {
        return {
            namespace: _gpfWebGetNamespace(parts[NAMESPACE_PREFIX]),
            name: parts[NAME]
        };
    }
}

/**
 * Fails if the name includes namespace prefix
 *
 * @param {String} name Attribute or node name
 * @throws {gpf.Error.UnableToUseNamespaceInString}
 * @since 0.2.2
 */
function _gpfWebCheckNamespaceSafe (name) {
    if (name.includes(":")) {
        gpf.Error.unableToUseNamespaceInString();
    }
}

/**
 * Resolve attribute name
 *
 * @param {String} name Attribute name used in the tag function
 * @return {String} Attribute to set on the node ele,ment
 * @since 0.2.1
 */
function _gpfWebTagAttributeAlias (name) {
    return _gpfWebTagAttributeAliases[name] || name;
}

/**
 * Apply the callback to each array item,
 * process recursively if the array item is an array
 *
 * @param {Array} array array of items
 * @param {Function} callback Function to apply on each array item
 * @since 0.2.1
 */
function _gpfWebTagFlattenChildren (array, callback) {
    array.forEach(function (item) {
        if (_gpfIsArray(item)) {
            _gpfWebTagFlattenChildren(item, callback);
        } else {
            callback(item);
        }
    });
}

var _GpfWebTag = _gpfDefine({
    $class: "gpf.web.Tag",

    /**
     * Tag object
     *
     * @param {String} nodeName Node name
     * @param {Object} [attributes] Dictionary of attributes to set
     * @param {Array} [children] Children
     *
     * @constructor gpf.web.Tag
     * @private
     * @since 0.2.1
     */
    constructor: function (nodeName, attributes, children) {
        this._nodeName = nodeName;
        this._attributes = attributes || {};
        this._children = children;
    },

    /**
     * Node name
     * @since 0.2.1
     */
    _nodeName: "",

    /**
     * Node attributes
     * @since 0.2.1
     */
    _attributes: {},

    /**
     * Node children
     * @since 0.2.1
     */
    _children: [],

    //region toString implementation

    _getAttributesAsString: function () {
        return Object.keys(this._attributes).map(function (name) {
            _gpfWebCheckNamespaceSafe(name);
            return " " + _gpfWebTagAttributeAlias(name)
                + "=\"" + _gpfStringEscapeFor(this._attributes[name], "html") + "\"";
        }, this).join("");
    },

    _getChildrenAsString: function () {
        var result = [];
        _gpfWebTagFlattenChildren(this._children, function (child) {
            result.push(child.toString());
        });
        return result.join("");
    },

    _getClosingString: function () {
        if (this._children.length) {
            return ">" + this._getChildrenAsString() + "</" + this._nodeName + ">";
        }
        return "/>";
    },

    /**
     * Convert the current tag into HTML
     *
     * @return {String} HTML representation of the tag
     * @since 0.2.1
     */
    toString: function () {
        _gpfWebCheckNamespaceSafe(this._nodeName);
        return "<" + this._nodeName + this._getAttributesAsString() + this._getClosingString();
    },

    //endregion

    //region appendTo implementation

    _createElement: function (node) {
        var ownerDocument = node.ownerDocument,
            qualified = _gpfWebGetNamespaceAndName(this._nodeName);
        if (qualified) {
            return ownerDocument.createElementNS(qualified.namespace, qualified.name);
        }
        return ownerDocument.createElement(this._nodeName);
    },

    _setAttributesTo: function (node) {
        _gpfObjectForEach(this._attributes, function (value, name) {
            var qualified = _gpfWebGetNamespaceAndName(name);
            if (qualified) {
                node.setAttributeNS(qualified.namespace, _gpfWebTagAttributeAlias(qualified.name), value);
            } else {
                node.setAttribute(_gpfWebTagAttributeAlias(name), value);
            }
        });
    },

    _appendChildrenTo: function (node) {
        var ownerDocument = node.ownerDocument;
        _gpfWebTagFlattenChildren(this._children, function (child) {
            if (child instanceof _GpfWebTag) {
                child.appendTo(node);
            } else {
                node.appendChild(ownerDocument.createTextNode(child.toString()));
            }
        });
    },

    /**
     * Appends the tag to the provided node
     *
     * @param {Object} node Expected to be a DOM node
     * @return {Object} Created node
     * @since 0.2.1
     */
    appendTo: function (node) {
        var element = this._createElement(node);
        this._setAttributesTo(element);
        this._appendChildrenTo(element);
        return node.appendChild(element);
    }

    //endregion

});

/**
 * Create a tag generation function
 *
 * @param {String} nodeName tag name.
 * May include the namespace prefix svg for [SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG)
 * @return {gpf.typedef.tagFunc} The tag generation function
 * @gpf:closure
 * @since 0.2.1
 */
function _gpfWebTagCreateFunction (nodeName) {
    if (!nodeName) {
        gpf.Error.missingNodeName();
    }
    return function (firstParam) {
        var sliceFrom,
            attributes;
        if (_gpfIsLiteralObject(firstParam)) {
            attributes = firstParam;
            sliceFrom = 1;
        } else {
            sliceFrom = 0;
        }
        return new _GpfWebTag(nodeName, attributes, _gpfArraySlice(arguments, sliceFrom));
    };
}

/**
 * @gpf:sameas _gpfWebTagCreateFunction
 * @since 0.2.1
 *
 * @example
Tree building to string

 * var div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 * tree = div({className: "test1"}, "Hello ", span({className: "test2"}, "World!"));
 * // tree.toString() gives
Hello World!

 *
 * @example
Tree building to DOM

 * var mockNode = mockDocument.createElement("any"),
 *     div = gpf.web.createTagFunction("div"),
 *     span = gpf.web.createTagFunction("span"),
 *     tree = div({className: "test"}, "Hello ", span("World!")),
 *     result = tree.appendTo(mockNode);
 *
 * @example
SVG building

 * var mockNode = mockDocument.createElement("any"),
 *     svgImage = gpf.web.createTagFunction("svg:image"),
 *     tree = svgImage({x: 0, y: 0, "xlink:href": "test.png"}),
 *     result = tree.appendTo(mockNode);
 *
 */
gpf.web.createTagFunction = _gpfWebTagCreateFunction;
<|endoftext|>


<commit_before>/**
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
"use strict";
var jsdomfw = require("../jsdomfw");
var chai = require("chai");
var sinon = require("sinon");

var assert = chai.assert;

var substringMatcher = function substringMatcher(strs) {
  return function findMatches(q, cb) {
    var re = new RegExp(q, "i");

    var matches = [];
    for (var i = 0; i < strs.length; ++i) {
      var str = strs[i];
      if (re.test(str)) {
        matches.push({ value: str });
      }
    }

    cb(matches);
  };
};

var test_data = [];
for (var i = 0; i < 100; ++i) {
  test_data.push("Test " + i);
}

describe("TypeaheadPopup", function TypeaheadPopup() {
  var fw;
  var window;
  var typeahead_popup;

  var ta;
  var cb;

  this.timeout(0);
  before(function before(done) {
    fw = new jsdomfw.FW();
    fw.create(function created() {
      window = fw.window;
      window.require(["wed/gui/typeahead_popup"],
                     function loaded(_typeahead_popup) {
                       try {
                         assert.isUndefined(window.document.errors);
                         typeahead_popup = _typeahead_popup;
                         done();
                       }
                       catch (e) {
                         done(e);
                         throw e;
                       }
                     }, done);
    });
  });

  beforeEach(function beforeEach() {
    cb = sinon.spy();
    ta = new typeahead_popup.TypeaheadPopup(
      window.document, 0, 0, 300, "Placeholder",
      {
        options: {
          autoselect: true,
          hint: true,
          highlight: true,
          minLength: 1,
        },
        datasets: [{
          source: substringMatcher(test_data),
        }],
      },
      cb
    );
  });

  afterEach(function afterEach() {
    ta.dismiss();
  });

  describe("setValue", function setValue() {
    it("sets the value", function test() {
      var tt_input = window.document.getElementsByClassName("tt-input")[0];
      assert.notEqual(tt_input.value, "foo");

      ta.setValue("foo");
      assert.equal(tt_input.value, "foo");
    });
  });

  describe("hideSpinner", function hideSpinner() {
    it("hides the spinner", function test() {
      var spinner = window.document.querySelector(
        ".wed-typeahead-popup .spinner");
      assert.notEqual(spinner.style.display, "none");

      ta.hideSpinner();
      assert.equal(spinner.style.display, "none");
    });
  });

  describe("dismiss", function dismiss() {
    it("calls the callback without a value if no value is given",
       function test() {
         ta.dismiss();
         assert.isTrue(cb.calledWith(undefined));
       });

    it("calls the callback with the value passed", function test() {
      ta.dismiss(test_data[0]);
      assert.isTrue(cb.calledWith(test_data[0]));
    });

    it("calls the callback with the value passed", function test() {
      ta.dismiss(test_data[0]);
      assert.isTrue(cb.calledWith(test_data[0]));
    });

    it("calls the callback only once", function test() {
      ta.dismiss();
      ta.dismiss();
      assert.isTrue(cb.calledOnce);
    });
  });
});
<commit_msg>More robust cleanup.<commit_after>/**
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
"use strict";
var jsdomfw = require("../jsdomfw");
var chai = require("chai");
var sinon = require("sinon");

var assert = chai.assert;

var substringMatcher = function substringMatcher(strs) {
  return function findMatches(q, cb) {
    var re = new RegExp(q, "i");

    var matches = [];
    for (var i = 0; i < strs.length; ++i) {
      var str = strs[i];
      if (re.test(str)) {
        matches.push({ value: str });
      }
    }

    cb(matches);
  };
};

var test_data = [];
for (var i = 0; i < 100; ++i) {
  test_data.push("Test " + i);
}

describe("TypeaheadPopup", function TypeaheadPopup() {
  var fw;
  var window;
  var typeahead_popup;

  var ta;
  var cb;

  this.timeout(0);
  before(function before(done) {
    fw = new jsdomfw.FW();
    fw.create(function created() {
      window = fw.window;
      window.require(["wed/gui/typeahead_popup"],
                     function loaded(_typeahead_popup) {
                       try {
                         assert.isUndefined(window.document.errors);
                         typeahead_popup = _typeahead_popup;
                         done();
                       }
                       catch (e) {
                         done(e);
                         throw e;
                       }
                     }, done);
    });
  });

  beforeEach(function beforeEach() {
    cb = sinon.spy();
    ta = new typeahead_popup.TypeaheadPopup(
      window.document, 0, 0, 300, "Placeholder",
      {
        options: {
          autoselect: true,
          hint: true,
          highlight: true,
          minLength: 1,
        },
        datasets: [{
          source: substringMatcher(test_data),
        }],
      },
      cb
    );
  });

  afterEach(function afterEach() {
    if (ta) {
      ta.dismiss();
    }
  });

  describe("setValue", function setValue() {
    it("sets the value", function test() {
      var tt_input = window.document.getElementsByClassName("tt-input")[0];
      assert.notEqual(tt_input.value, "foo");

      ta.setValue("foo");
      assert.equal(tt_input.value, "foo");
    });
  });

  describe("hideSpinner", function hideSpinner() {
    it("hides the spinner", function test() {
      var spinner = window.document.querySelector(
        ".wed-typeahead-popup .spinner");
      assert.notEqual(spinner.style.display, "none");

      ta.hideSpinner();
      assert.equal(spinner.style.display, "none");
    });
  });

  describe("dismiss", function dismiss() {
    it("calls the callback without a value if no value is given",
       function test() {
         ta.dismiss();
         assert.isTrue(cb.calledWith(undefined));
       });

    it("calls the callback with the value passed", function test() {
      ta.dismiss(test_data[0]);
      assert.isTrue(cb.calledWith(test_data[0]));
    });

    it("calls the callback with the value passed", function test() {
      ta.dismiss(test_data[0]);
      assert.isTrue(cb.calledWith(test_data[0]));
    });

    it("calls the callback only once", function test() {
      ta.dismiss();
      ta.dismiss();
      assert.isTrue(cb.calledOnce);
    });
  });
});
<|endoftext|>


<commit_before><commit_msg>Added models/ as fallback for assets/models/<commit_after><|endoftext|>


<commit_before>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class Wechaty
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
const EventEmitter  = require('events')
const co            = require('co')

const log           = require('./npmlog-env')

class Wechaty extends EventEmitter {

  constructor({
    type        = process.env.WECHATY_PUPPET   || 'web'
    , head      = process.env.WECHATY_HEAD     || false
    , port      = process.env.WECHATY_PORT     || 8788  // W(87) X(88), ascii char code ;-]
    , endpoint  = process.env.WECHATY_ENDPOINT          // wechaty.io api endpoint
    , token     = process.env.WECHATY_TOKEN             // token for wechaty.io auth
    , profile   = process.env.WECHATY_PROFILE           // no profile, no session save/restore
  } = {}) {
    super()
    this.type     = type
    this.head     = head
    this.port     = port
    this.token    = token
    this.endpoint = endpoint

    this.profile  = /\.wechaty\.json$/i.test(profile)
                    ? profile
                    : profile + '.wechaty.json'

    this.npmVersion = require('../package.json').version

    this.inited = false
  }

  toString() { return 'Class Wechaty(' + this.type + ')'}

  version()  { return this.npmVersion }

  init() {
    log.info('Wechaty', 'v%s initializing...', this.npmVersion)
    log.verbose('Wechaty', 'puppet: %s' , this.type)
    log.verbose('Wechaty', 'head: %s'   , this.head)
    log.verbose('Wechaty', 'profile: %s', this.profile)

    if (this.inited) {
      log.error('Wechaty', 'init() already inited. return and do nothing.')
      return Promise.resolve(this)
    }

    return co.call(this, function* () {
      const okPort = yield this.getPort(this.port)

      if (okPort != this.port) {
        log.info('Wechaty', 'port: %d not available, changed to %d', this.port, okPort)
        this.port = okPort
      } else {
        log.verbose('Wechaty', 'port: %d', this.port)
      }

      yield this.initIo()
      .catch(e => { // fail safe
        log.error('WechatyIo', 'initIo failed: %s', e.message)
        this.emit('error', e)
      })

      yield this.initPuppet()
      yield this.initEventHook()
      yield this.puppet.init()

      this.inited = true
      return this // for chaining
    })
    .catch(e => {
      log.error('Wechaty', 'init() exception: %s', e.message)
      throw e
    })
  }

  initIo() {
    if (!this.token) {
      log.verbose('Wechaty', 'initIo() skiped for no token set')
      return Promise.resolve('no token')
    } else {
      log.verbose('Wechaty', 'initIo(%s)', this.token)
    }

    const WechatyIo = require('./wechaty-io')
    const io = this.io = new WechatyIo({
      wechaty: this
      , token: this.token
      , endpoint: this.endpoint
    })

    return io.init()
    .catch(e => {
      log.verbose('Wechaty', 'Wechaty.IO init fail: %s', e.message)
      throw e
    })
  }

  initPuppet() {
    switch (this.type) {
      case 'web':
        this.puppet = new Puppet.Web({
          head:       this.head
          , port:     this.port
          , profile:  this.profile
        })
        break
      default:
        throw new Error('Puppet unsupport(yet): ' + this.type)
    }
    return Promise.resolve(this.puppet)
  }

  initEventHook() {
    ;[
      'scan'
      , 'message'
      , 'login'
      , 'logout'
      , 'error'
      , 'heartbeat'
    ].map(e => {
      this.puppet.on(e, data => {
        this.emit(e, data)
      })
    })
    // this.puppet.on('scan', data => {
    //   this.emit('scan', data)    // Scan QRCode
    // })
    // this.puppet.on('message', data => {
    //   this.emit('message', data) // Receive Message
    // })
    // this.puppet.on('login', data => {
    //   this.emit('login', data)
    // })
    // this.puppet.on('logout', data => {
    //   this.emit('logout', data)
    // })
    // this.puppet.on('error', data => {
    //   this.emit('error', data)
    // })

    /**
     * TODO: support more events:
     * 2. send
     * 3. reply
     * 4. quit
     * 5. ...
     */

    return Promise.resolve()
  }

  quit() {
    const puppetBeforeDie = this.puppet
    this.puppet = null
    this.inited = false

    return puppetBeforeDie.quit()
    .catch(e => {
      log.error('Wechaty', 'quit() exception: %s', e.message)
      throw e
    })
  }

  logout()  {
    return this.puppet.logout()
    .catch(e => {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      throw e
    })
  }

  self(message) {
    return this.puppet.self(message)
  }

  send(message) {
    return this.puppet.send(message)
    .catch(e => {
      log.error('Wechaty', 'send() exception: %s', e.message)
      throw e
    })
  }

  reply(message, reply) {
    return this.puppet.reply(message, reply)
    .catch(e => {
      log.error('Wechaty', 'reply() exception: %s', e.message)
      throw e
    })
  }

  ding(data) {
    if (!this.puppet) {
      return Promise.reject(new Error('wechaty cant ding coz no puppet'))
    }

    return this.puppet.ding(data)
    .catch(e => {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      throw e
    })
  }

  getPort(port) {
    return new Promise((resolve, reject) => {
      port = port || 8788
      // https://gist.github.com/mikeal/1840641
      function getPort(cb) {
        var tryPort = port
        port += 1
        var server = require('net').createServer()
        server.on('error', function(err) {
          if (err) {}
          getPort(cb)
        })
        server.listen(tryPort, function(err) {
          if (err) {}
          server.once('close', function() {
            cb(tryPort)
          })
          server.close()
        })
      }
      getPort(okPort => resolve(okPort))
    })
  }
}

const Message = require('./message')
const Contact = require('./contact')
const Room    = require('./room')

const Puppet  = require('./puppet')
Puppet.Web    = require('./puppet-web')

Object.assign(Wechaty, {
  Puppet

  , Message
  , Contact
  , Room

  , log // for convenionce use npmlog with environment variable LEVEL
})

Wechaty.version = require('../package.json').version
/**
 * Expose `Wechaty`.
 */
module.exports = Wechaty.default = Wechaty.Wechaty = Wechaty
<commit_msg>hide useless Class export<commit_after>/**
 *
 * wechaty: Wechat for Bot. and for human who talk to bot/robot
 *
 * Class Wechaty
 *
 * Licenst: ISC
 * https://github.com/zixia/wechaty
 *
 */
const EventEmitter  = require('events')
const co            = require('co')

const log           = require('./npmlog-env')

class Wechaty extends EventEmitter {

  constructor({
    type        = process.env.WECHATY_PUPPET   || 'web'
    , head      = process.env.WECHATY_HEAD     || false
    , port      = process.env.WECHATY_PORT     || 8788  // W(87) X(88), ascii char code ;-]
    , endpoint  = process.env.WECHATY_ENDPOINT          // wechaty.io api endpoint
    , token     = process.env.WECHATY_TOKEN             // token for wechaty.io auth
    , profile   = process.env.WECHATY_PROFILE           // no profile, no session save/restore
  } = {}) {
    super()
    this.type     = type
    this.head     = head
    this.port     = port
    this.token    = token
    this.endpoint = endpoint

    this.profile  = /\.wechaty\.json$/i.test(profile)
                    ? profile
                    : profile + '.wechaty.json'

    this.npmVersion = require('../package.json').version

    this.inited = false
  }

  toString() { return 'Class Wechaty(' + this.type + ')'}

  version()  { return this.npmVersion }

  init() {
    log.info('Wechaty', 'v%s initializing...', this.npmVersion)
    log.verbose('Wechaty', 'puppet: %s' , this.type)
    log.verbose('Wechaty', 'head: %s'   , this.head)
    log.verbose('Wechaty', 'profile: %s', this.profile)

    if (this.inited) {
      log.error('Wechaty', 'init() already inited. return and do nothing.')
      return Promise.resolve(this)
    }

    return co.call(this, function* () {
      const okPort = yield this.getPort(this.port)

      if (okPort != this.port) {
        log.info('Wechaty', 'port: %d not available, changed to %d', this.port, okPort)
        this.port = okPort
      } else {
        log.verbose('Wechaty', 'port: %d', this.port)
      }

      yield this.initIo()
      .catch(e => { // fail safe
        log.error('WechatyIo', 'initIo failed: %s', e.message)
        this.emit('error', e)
      })

      yield this.initPuppet()
      yield this.initEventHook()
      yield this.puppet.init()

      this.inited = true
      return this // for chaining
    })
    .catch(e => {
      log.error('Wechaty', 'init() exception: %s', e.message)
      throw e
    })
  }

  initIo() {
    if (!this.token) {
      log.verbose('Wechaty', 'initIo() skiped for no token set')
      return Promise.resolve('no token')
    } else {
      log.verbose('Wechaty', 'initIo(%s)', this.token)
    }

    const WechatyIo = require('./wechaty-io')
    const io = this.io = new WechatyIo({
      wechaty: this
      , token: this.token
      , endpoint: this.endpoint
    })

    return io.init()
    .catch(e => {
      log.verbose('Wechaty', 'Wechaty.IO init fail: %s', e.message)
      throw e
    })
  }

  initPuppet() {
    switch (this.type) {
      case 'web':
        this.puppet = new PuppetWeb( {
          head:       this.head
          , port:     this.port
          , profile:  this.profile
        })
        break
      default:
        throw new Error('Puppet unsupport(yet): ' + this.type)
    }
    return Promise.resolve(this.puppet)
  }

  initEventHook() {
    ;[
      'scan'
      , 'message'
      , 'login'
      , 'logout'
      , 'error'
      , 'heartbeat'
    ].map(e => {
      this.puppet.on(e, data => {
        this.emit(e, data)
      })
    })
    // this.puppet.on('scan', data => {
    //   this.emit('scan', data)    // Scan QRCode
    // })
    // this.puppet.on('message', data => {
    //   this.emit('message', data) // Receive Message
    // })
    // this.puppet.on('login', data => {
    //   this.emit('login', data)
    // })
    // this.puppet.on('logout', data => {
    //   this.emit('logout', data)
    // })
    // this.puppet.on('error', data => {
    //   this.emit('error', data)
    // })

    /**
     * TODO: support more events:
     * 2. send
     * 3. reply
     * 4. quit
     * 5. ...
     */

    return Promise.resolve()
  }

  quit() {
    const puppetBeforeDie = this.puppet
    this.puppet = null
    this.inited = false

    return puppetBeforeDie.quit()
    .catch(e => {
      log.error('Wechaty', 'quit() exception: %s', e.message)
      throw e
    })
  }

  logout()  {
    return this.puppet.logout()
    .catch(e => {
      log.error('Wechaty', 'logout() exception: %s', e.message)
      throw e
    })
  }

  self(message) {
    return this.puppet.self(message)
  }

  send(message) {
    return this.puppet.send(message)
    .catch(e => {
      log.error('Wechaty', 'send() exception: %s', e.message)
      throw e
    })
  }

  reply(message, reply) {
    return this.puppet.reply(message, reply)
    .catch(e => {
      log.error('Wechaty', 'reply() exception: %s', e.message)
      throw e
    })
  }

  ding(data) {
    if (!this.puppet) {
      return Promise.reject(new Error('wechaty cant ding coz no puppet'))
    }

    return this.puppet.ding(data)
    .catch(e => {
      log.error('Wechaty', 'ding() exception: %s', e.message)
      throw e
    })
  }

  getPort(port) {
    return new Promise((resolve, reject) => {
      port = port || 8788
      // https://gist.github.com/mikeal/1840641
      function getPort(cb) {
        var tryPort = port
        port += 1
        var server = require('net').createServer()
        server.on('error', function(err) {
          if (err) {}
          getPort(cb)
        })
        server.listen(tryPort, function(err) {
          if (err) {}
          server.once('close', function() {
            cb(tryPort)
          })
          server.close()
        })
      }
      getPort(okPort => resolve(okPort))
    })
  }
}

const Message = require('./message')
const Contact = require('./contact')
const Room    = require('./room')

// const Puppet  = require('./puppet')
const PuppetWeb     = require('./puppet-web')

Object.assign(Wechaty, {
  Message
  , Contact
  , Room

  , log // for convenionce use npmlog with environment variable LEVEL
  // Puppet
})

Wechaty.version = require('../package.json').version
/**
 * Expose `Wechaty`.
 */
module.exports = Wechaty.default = Wechaty.Wechaty = Wechaty
<|endoftext|>


<commit_before>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox} from './utils';
import {
  TOOL_NONE,
  TOOL_PAN,
  TOOL_ZOOM,
  MODE_IDLE,
  MODE_PANNING,
  MODE_ZOOMING,
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_IDLE) return;

    let point = ViewerHelper.getSVGPoint(value, x, y);
    if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_ZOOMING) return;

    let selectionMode = abs(startX - endX) > 2 && abs(startY - endY) > 2;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let scaleFactor = specialKeyEnabled ? 0.9 : 1.1;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleClick(event) {
    let {value, tool, onClick, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onClick) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onClick(viewerEvent);
  }

  handleMouseUp(event) {
    let {value, tool, onMouseUp, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseUp) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseUp(viewerEvent);
  }

  handleMouseDown(event) {
    let {value, tool, onMouseDown, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseDown) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseDown(viewerEvent);
  }

  handleMouseMove(event) {
    let {value, tool, onMouseMove, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseMove) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseMove(viewerEvent);
  }

  handleSpecialKeyChange(event) {
    let {value, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if ([18].indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  render() {
    let originalSVG = this.props.children;
    let tool = this.props.tool;
    let {matrix, mode, specialKeyEnabled} = this.props.value;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if (tool === TOOL_ZOOM) gStyle.cursor = cursor(specialKeyEnabled ? 'zoom-out' : 'zoom-in');

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={this.props.width}
        height={this.props.height}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {this.handleMouseDown(event); this.handleStartPan(event);  this.handleStartZoom(event)} }
        onMouseMove={ event => {this.handleMouseMove(event); this.handleUpdatePan(event); this.handleUpdateZoom(event)} }
        onMouseUp={ event =>   {this.handleMouseUp(event);   this.handleStopPan(event);   this.handleStopZoom(event)} }
        onClick={event => this.handleClick(event)}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g ref="originalSvg" transform={matrixStr} style={gStyle}>
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>
        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE
};
<commit_msg>fix zoom cursor view<commit_after>import React from 'react';
import ViewerHelper from './viewer-helper';
import ViewerEvent from './viewer-event';
import cursor from './cursor';
import {calculateBox} from './utils';
import {
  TOOL_NONE,
  TOOL_PAN,
  TOOL_ZOOM,
  MODE_IDLE,
  MODE_PANNING,
  MODE_ZOOMING,
} from './constants';

export default class Viewer extends React.Component {

  constructor(props) {
    super(props);
    this.handleSpecialKeyChange = this.handleSpecialKeyChange.bind(this);
  }

  handleStartPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_IDLE) return;

    let nextValue = ViewerHelper.startPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdatePan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    //the mouse exited and reentered into svg
    let forceExit = (value.mode === MODE_PANNING && event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopPan(value) :
      ViewerHelper.updatePan(value, x, y, 20, SVGWidth, SVGHeight, width, height);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopPan(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange} = this.props;

    if (tool !== TOOL_PAN) return;
    if (value.mode !== MODE_PANNING) return;

    let nextValue = ViewerHelper.stopPan(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStartZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_IDLE) return;

    let point = ViewerHelper.getSVGPoint(value, x, y);
    if(!ViewerHelper.isPointInsideSVG(point.x, point.y, SVGWidth, SVGHeight)) return;

    let nextValue = ViewerHelper.startZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleUpdateZoom(event) {
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_ZOOMING) return;

    //the mouse exited and reentered into svg
    let forceExit = (event.buttons === 0);

    let nextValue = forceExit ?
      ViewerHelper.stopZoomSelection(value, width, height)
      : ViewerHelper.updateZoomSelection(value, x, y);

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleStopZoom(event) {
    let abs = Math.abs;
    let x = event.nativeEvent.offsetX, y = event.nativeEvent.offsetY;
    let {value, tool, onChange, width, height} = this.props;
    let {startX, endX, startY, endY, specialKeyEnabled} = value;

    if (tool !== TOOL_ZOOM) return;
    if (value.mode !== MODE_ZOOMING) return;

    let selectionMode = abs(startX - endX) > 2 && abs(startY - endY) > 2;

    let nextValue;

    if (selectionMode) {
      nextValue = ViewerHelper.stopZoomSelection(value, width, height);
    } else {
      let scaleFactor = specialKeyEnabled ? 0.9 : 1.1;
      nextValue = ViewerHelper.zoom(value, scaleFactor, x, y);
    }

    event.preventDefault();
    onChange(new ViewerEvent(event, nextValue));
  }

  handleClick(event) {
    let {value, tool, onClick, children} = this.props;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onClick) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onClick(viewerEvent);
  }

  handleMouseUp(event) {
    let {value, tool, onMouseUp, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseUp) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseUp(viewerEvent);
  }

  handleMouseDown(event) {
    let {value, tool, onMouseDown, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseDown) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseDown(viewerEvent);
  }

  handleMouseMove(event) {
    let {value, tool, onMouseMove, children} = this.props;
    let x = event.offsetX, y = event.offsetY;
    let SVGWidth = children.props.width, SVGHeight = children.props.height;

    if (tool !== TOOL_NONE) return;
    if (!onMouseMove) return;

    let viewerEvent = new ViewerEvent(event, value);
    if(!ViewerHelper.isPointInsideSVG(viewerEvent.x, viewerEvent.y, SVGWidth, SVGHeight)) return;

    onMouseMove(viewerEvent);
  }

  handleSpecialKeyChange(event) {
    let {value, onChange} = this.props;
    let key = event.which;
    let active = event.type === "keydown";

    if ([18].indexOf(key) === -1) return;

    let nextValue = active ? ViewerHelper.enableSpecialKey(value) : ViewerHelper.disableSpecialKey(value);

    onChange(new ViewerEvent(event, nextValue));
  }

  componentWillMount(event) {
    window.addEventListener("keydown", this.handleSpecialKeyChange, false);
    window.addEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  componentWillUnmount(event) {
    window.removeEventListener("keydown", this.handleSpecialKeyChange, false);
    window.removeEventListener("keyup", this.handleSpecialKeyChange, false);
  }

  render() {
    let originalSVG = this.props.children;
    let tool = this.props.tool;
    let {matrix, mode, specialKeyEnabled} = this.props.value;
    let matrixStr = `matrix(${matrix.a}, ${matrix.b}, ${matrix.c}, ${matrix.d}, ${matrix.e}, ${matrix.f})`;

    let style = {};
    let gStyle = {pointerEvents: "none"};
    if (tool === TOOL_PAN) style.cursor = cursor(mode === MODE_PANNING ? 'grabbing' : 'grab');
    if (tool === TOOL_ZOOM) style.cursor = cursor(specialKeyEnabled ? 'zoom-out' : 'zoom-in');

    let zoomSelectionRect;
    if (mode === MODE_ZOOMING) {
      let {startX, startY, endX, endY} = this.props.value;
      let box = calculateBox({x: startX, y: startY}, {x: endX, y: endY});

      zoomSelectionRect =
        <rect
          stroke="#969FFF"
          strokeOpacity={0.7}
          fill="#F3F4FF"
          fillOpacity={0.7}
          x={box.x}
          y={box.y}
          width={box.width}
          height={box.height}/>
    }

    return (
      <svg
        ref="svg"
        width={this.props.width}
        height={this.props.height}
        style={Object.assign(style, this.props.style)}
        onMouseDown={ event => {this.handleMouseDown(event); this.handleStartPan(event);  this.handleStartZoom(event)} }
        onMouseMove={ event => {this.handleMouseMove(event); this.handleUpdatePan(event); this.handleUpdateZoom(event)} }
        onMouseUp={ event =>   {this.handleMouseUp(event);   this.handleStopPan(event);   this.handleStopZoom(event)} }
        onClick={event => this.handleClick(event)}
      >

        <rect
          fill={this.props.background}
          x={0}
          y={0}
          width={this.props.width}
          height={this.props.height}
          style={{pointerEvents: "none"}}
        />

        <g ref="originalSvg" transform={matrixStr} style={gStyle}>
          <rect
            fill={this.props.SVGBackground}
            x={0}
            y={0}
            width={originalSVG.props.width}
            height={originalSVG.props.height}/>
          <g ref="content">
            {originalSVG.props.children}
          </g>
        </g>
        {zoomSelectionRect}
      </svg>
    );
  }
}

Viewer.propTypes = {
  //width of the viewer displayed on screen
  width: React.PropTypes.number.isRequired,

  //height of the viewer displayed on screen
  height: React.PropTypes.number.isRequired,

  //background of the viewer
  background: React.PropTypes.string,

  //background of the svg
  SVGBackground: React.PropTypes.string,

  //value of the viewer (current point of view)
  value: React.PropTypes.object.isRequired,

  //CSS style of the SVG tag
  style: React.PropTypes.object,

  //handler something changed
  onChange: React.PropTypes.func.isRequired,

  //handler click
  onClick: React.PropTypes.func,

  //handler mouseup
  onMouseUp: React.PropTypes.func,

  //handler mousemove
  onMouseMove: React.PropTypes.func,

  //handler mousedown
  onMouseDown: React.PropTypes.func,

  //current active tool (TOOL_NONE, TOOL_PAN, TOOL_ZOOM)
  tool: React.PropTypes.oneOf([TOOL_NONE, TOOL_PAN, TOOL_ZOOM]),

  //accept only one node SVG
  children: function (props, propName, componentName) {
    // Only accept a single child, of the appropriate type
    //credits: http://www.mattzabriskie.com/blog/react-validating-children
    var prop = props[propName];
    var types = ['svg'];
    if (React.Children.count(prop) !== 1 ||
      types.indexOf(prop.type) === -1) {
      return new Error(
        '`' + componentName + '` ' +
        'should have a single child of the following types: ' +
        ' `' + types.join('`, `') + '`.'
      );
    }
    if (!prop.props.hasOwnProperty('width') || !prop.props.hasOwnProperty('height')) {
      return new Error('SVG should have props `width` and `height`');
    }

  }
};

Viewer.defaultProps = {
  style: {},
  background: "#616264",
  SVGBackground: "#fff",
  tool: TOOL_NONE
};
<|endoftext|>


<commit_before>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

describe('BlinkIDLFileDAO integration', function() {
  var IDLFileContents;
  var StoreAndForwardDAO;
  var BlinkIDLFileDAO;
  var localRepositoryPath;
  var includePaths;

  beforeAll(function() {
    localRepositoryPath =
        require('path').resolve(__dirname, '../data/blink/git');
  });

  beforeEach(function() {
    IDLFileContents = foam.lookup('org.chromium.webidl.IDLFileContents');
    StoreAndForwardDAO = foam.lookup('org.chromium.webidl.StoreAndForwardDAO');
    BlinkIDLFileDAO = foam.lookup('org.chromium.webidl.BlinkIDLFileDAO');
  });

  it('should yield mock included files from select() when composed with store-and-forward DAO', function(done) {
    StoreAndForwardDAO.create({
      of: IDLFileContents,
      delegate: BlinkIDLFileDAO.create({
        localRepositoryPath: localRepositoryPath,
      }),
    }).orderBy(IDLFileContents.ID).select().then(
        function(sink) {
          var actualFileContents = sink.a;
          var expectedPaths = global.testGitRepoData.blink.includePaths;
          expect(actualFileContents.length).toBe(expectedPaths.length);
          for (var i = 0; i < actualFileContents.length; i++) {
            var actualPath = actualFileContents[i].metadata.path;
            var expectedPath = expectedPaths[i];
            expect(actualPath).toBe(expectedPath);
          }
          done();
        }, done.fail);
  });
});
<commit_msg>BlinkIDLFileDAO integration tests: Ensure .git is created before running tests<commit_after>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';

describe('BlinkIDLFileDAO integration', function() {
  var IDLFileContents;
  var StoreAndForwardDAO;
  var BlinkIDLFileDAO;
  var localRepositoryPath;
  var includePaths;

  beforeAll(function() {
    localRepositoryPath =
        require('path').resolve(__dirname, '../data/blink/git');
    require('child_process').execSync(
        `/usr/bin/git init "${localRepositoryPath}"`);
  });

  beforeEach(function() {
    IDLFileContents = foam.lookup('org.chromium.webidl.IDLFileContents');
    StoreAndForwardDAO = foam.lookup('org.chromium.webidl.StoreAndForwardDAO');
    BlinkIDLFileDAO = foam.lookup('org.chromium.webidl.BlinkIDLFileDAO');
  });

  it('should yield mock included files from select() when composed with store-and-forward DAO', function(done) {
    StoreAndForwardDAO.create({
      of: IDLFileContents,
      delegate: BlinkIDLFileDAO.create({
        localRepositoryPath: localRepositoryPath,
      }),
    }).orderBy(IDLFileContents.ID).select().then(
        function(sink) {
          var actualFileContents = sink.a;
          var expectedPaths = global.testGitRepoData.blink.includePaths;
          expect(actualFileContents.length).toBe(expectedPaths.length);
          for (var i = 0; i < actualFileContents.length; i++) {
            var actualPath = actualFileContents[i].metadata.path;
            var expectedPath = expectedPaths[i];
            expect(actualPath).toBe(expectedPath);
          }
          done();
        }, done.fail);
  });
});
<|endoftext|>


<commit_before>let _ = require( 'wTools' );
require( 'wConsequence' );
let Dns = require( 'dns' );

let uri = 'google.com';

/* with callback */

Dns.resolve4( uri, ( err, addresses ) =>
{
  if( err ) console.log( err );
  console.log( `Ips of ${uri} are ${JSON.stringify( addresses )}` );
});

/* with consequence */

let conseuqence = new _.Consequence();
Dns.resolve4( uri, conseuqence );
conseuqence.thenGive( ( addresses ) => console.log( `Ips of ${uri} are ${JSON.stringify( addresses )}` ) );

/*
Consequence `conseuqence` is replacement of callback. It is temporary container for `addresses`.
Consequence `conseuqence` will get error if `Dons.resolve4` will fail.
*/

/* aaa Artem : done. use it for tutorial */
<commit_msg>conflict resolved<commit_after><|endoftext|>


<commit_before>'use strict'

var NLR = require("../..").NLR;

describe('Non-linear regression', function() {

    describe('Should give the correct parameters ', function() {

        it('Potential regression', function () {
            var x = [0.2, 0.4, 0.6, 0.8, 1.0];
            var y = [0.1960, 0.7850, 1.7665, 3.1405, 4.9075];
            var result = new NLR.PotentialRegression(x, y, 2, {computeCoefficient:true});
            result.A.should.be.approximately(4.9073, 10e-5);
            result.M.should.equal(2);
        });

        it('Exponential regresion', function () {
            var x = [0, 1, 2, 3, 4];
            var y = [1.5, 2.5, 3.5, 5.0, 7.5];
            var result = new NLR.ExpRegression(x, y, {computeCoefficient:true});
            result.A.should.be.approximately(0.3912023, 10e-7);
            result.C.should.be.approximately(1.579910, 10e-7);
        });

        it('Polynomial regression', function () {
            var x = [-3, 0, 2, 4];
            var y = [3, 1, 1, 3];
            var result = new NLR.PolynomialRegression(x, y, 2,{computeCoefficient:true});
            var expected = [0.850519, -0.192495, 0.178462];

            for(var i = 0; i < expected.length; ++i) {
                result.coefficients[i].should.be.approximately(expected[i], 10e-6);
                result.powers[i].should.equal(i);
            }
        });

        it('Power regression', function () {
            var x = [17.6, 26, 31.9, 38.9,45.8,51.2,58.1, 64.7, 66.7, 80.8, 82.9];
            var y = [159.9,206.9,236.8,269.9,300.6,323.6,351.7, 377.6, 384.1, 437.2, 444.7];
            var result = new NLR.PowerRegression(x, y, {computeCoefficient:true});

            var expected = {A:24.12989312,
                            B:0.65949782
                            };
            result.A.should.approximately(expected.A,10e-4);
            result.B.should.approximately(expected.B,10e-4);

            var x2 = [20,30];
            var y2 = result.predict(x2);

            y2[0].should.approximately(expected.A*Math.pow(x2[0],expected.B),10e-4);
            y2[1].should.approximately(expected.A*Math.pow(x2[1],expected.B),10e-4);

        });

    });
});<commit_msg>Add currently failing test<commit_after>'use strict'

var NLR = require("../..").NLR;

describe('Non-linear regression', function() {

    describe('Should give the correct parameters ', function() {

        it('Potential regression', function () {
            var x = [0.2, 0.4, 0.6, 0.8, 1.0];
            var y = [0.1960, 0.7850, 1.7665, 3.1405, 4.9075];
            var result = new NLR.PotentialRegression(x, y, 2, {computeCoefficient:true});
            result.A.should.be.approximately(4.9073, 10e-5);
            result.M.should.equal(2);
            result.toString(4).should.equal('XXX');
        });

        it('Exponential regresion', function () {
            var x = [0, 1, 2, 3, 4];
            var y = [1.5, 2.5, 3.5, 5.0, 7.5];
            var result = new NLR.ExpRegression(x, y, {computeCoefficient:true});
            result.A.should.be.approximately(0.3912023, 10e-7);
            result.C.should.be.approximately(1.579910, 10e-7);
            result.toString(4).should.equal('1.580*exp(0.3912*x)');

        });

        it('Polynomial regression', function () {
            var x = [-3, 0, 2, 4];
            var y = [3, 1, 1, 3];
            var result = new NLR.PolynomialRegression(x, y, 2,{computeCoefficient:true});
            var expected = [0.850519, -0.192495, 0.178462];

            for(var i = 0; i < expected.length; ++i) {
                result.coefficients[i].should.be.approximately(expected[i], 10e-6);
                result.powers[i].should.equal(i);
            }
            result.toString(4).should.equal('XXX');
        });

        it('Power regression', function () {
            var x = [17.6, 26, 31.9, 38.9,45.8,51.2,58.1, 64.7, 66.7, 80.8, 82.9];
            var y = [159.9,206.9,236.8,269.9,300.6,323.6,351.7, 377.6, 384.1, 437.2, 444.7];
            var result = new NLR.PowerRegression(x, y, {computeCoefficient:true});

            var expected = {A:24.12989312,
                            B:0.65949782
                            };
            result.A.should.approximately(expected.A,10e-4);
            result.B.should.approximately(expected.B,10e-4);

            var x2 = [20,30];
            var y2 = result.predict(x2);

            y2[0].should.approximately(expected.A*Math.pow(x2[0],expected.B),10e-4);
            y2[1].should.approximately(expected.A*Math.pow(x2[1],expected.B),10e-4);

            result.toString(4).should.equal('24.13*x^0.6595');

        });

    });
});<|endoftext|>


<commit_before>const CONFIG = require('./config');

let BrowserWindow;

module.exports = function (electron) {
  BrowserWindow = electron.BrowserWindow;

  return {
    openMainWindow,
    openLogInWindow,
    closeLogInWindow,
    activateWithNoOpenWindows,
  };
};

const MAIN_WINDOW = 'main';
const AUTH_WINDOW = 'auth';

let windows = {};

function openMainWindow() {
  if (!windows[MAIN_WINDOW])
    windows[MAIN_WINDOW] = createMainWindow();
}

function openLogInWindow(url) {
  windows[AUTH_WINDOW] = createLogInWindow(url);
}

function closeLogInWindow() {
  windows[AUTH_WINDOW].close();
}

function createWindow(windowName, url, width, height, isDevToolsOpen) {
  const win = new BrowserWindow({ width, height });

  win.loadURL(url);
  win.on('closed', onClosed.bind(null, windowName));
  win.setMinimumSize(780, 270);

  win.setAutoHideMenuBar(true);

  if (isDevToolsOpen) win.openDevTools();

  return win;
}

function createMainWindow() {
  const url = 'file://' + __dirname + '/client/index.html';

  return createWindow(
    MAIN_WINDOW,
    url,
    CONFIG.MAIN_WINDOW.WIDTH,
    CONFIG.MAIN_WINDOW.HEIGHT,
    CONFIG.MAIN_WINDOW.IS_DEV_TOOLS_OPEN
  );
}

function createLogInWindow(url) {
  return createWindow(
    AUTH_WINDOW,
    url,
    CONFIG.AUTH_WINDOW.WIDTH,
    CONFIG.AUTH_WINDOW.HEIGHT,
    CONFIG.AUTH_WINDOW.IS_DEV_TOOLS_OPEN
  );
}

function activateWithNoOpenWindows() {
  if (!windows[MAIN_WINDOW]) {
    windows[MAIN_WINDOW] = createMainWindow();
  }
}

function onClosed(windowName) {
  // dereference the window
  windows[windowName] = null;
}
<commit_msg>Add the icon<commit_after>const CONFIG = require('./config');

let BrowserWindow;

module.exports = function (electron) {
  BrowserWindow = electron.BrowserWindow;

  return {
    openMainWindow,
    openLogInWindow,
    closeLogInWindow,
    activateWithNoOpenWindows,
  };
};

const MAIN_WINDOW = 'main';
const AUTH_WINDOW = 'auth';

const ICON = __dirname + '/../YouWatch.png';

let windows = {};

function openMainWindow() {
  if (!windows[MAIN_WINDOW])
    windows[MAIN_WINDOW] = createMainWindow();
}

function openLogInWindow(url) {
  windows[AUTH_WINDOW] = createLogInWindow(url);
}

function closeLogInWindow() {
  windows[AUTH_WINDOW].close();
}

function createWindow(windowName, url, width, height, icon, isDevToolsOpen) {
  const win = new BrowserWindow({ width, height, icon });

  win.loadURL(url);
  win.on('closed', onClosed.bind(null, windowName));
  win.setMinimumSize(780, 270);

  win.setAutoHideMenuBar(true);

  if (isDevToolsOpen) win.openDevTools();

  return win;
}

function createMainWindow() {
  const url = 'file://' + __dirname + '/client/index.html';

  return createWindow(
    MAIN_WINDOW,
    url,
    CONFIG.MAIN_WINDOW.WIDTH,
    CONFIG.MAIN_WINDOW.HEIGHT,
    ICON,
    CONFIG.MAIN_WINDOW.IS_DEV_TOOLS_OPEN
  );
}

function createLogInWindow(url) {
  return createWindow(
    AUTH_WINDOW,
    url,
    CONFIG.AUTH_WINDOW.WIDTH,
    CONFIG.AUTH_WINDOW.HEIGHT,
    ICON,
    CONFIG.AUTH_WINDOW.IS_DEV_TOOLS_OPEN
  );
}

function activateWithNoOpenWindows() {
  if (!windows[MAIN_WINDOW]) {
    windows[MAIN_WINDOW] = createMainWindow();
  }
}

function onClosed(windowName) {
  // dereference the window
  windows[windowName] = null;
}
<|endoftext|>


<commit_before>define(function (require, exports, module) {

/**
 * 组件基类
 *
 * @module Widget
 */

'use strict';

var $ = require('$'),
  Base = require('base');

var DELEGATE_SPLITTER = /^(\S+)\s*(.*)$/,
  DELEGATE_NAMESPACE = '.delegate-widget-';

var cachedInstances = {};

// TODO: 检查是否有内存泄漏发生

/**
 * 组件基类
 *
 * @class Widget
 * @constructor
 * @extends Base
 *
 * @example
 * ```
 * // 创建子类
 * var PersonWidget = Widget.extend({
 *   setup: function () {
 *     // 通知事件 `setup`
 *     this.fire('setup', this.option('name'), this.option('age'));
 *   }
 * });
 * // 创建子类实例
 * var tom = new PersonWidget({
 *   name: 'Tom',
 *   age: 21,
 *   // 订阅事件
 *   events: {
 *     // `setup`
 *     setup: function (e, name, age) {
 *       // e === 'setup'
 *       // name === 'Tom'
 *       // age === 21
 *       // this === tom
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法前执行
 *       // 此处返回 `false` 将阻止 `setup` 方法执行
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法后执行
 *       // 如果 `setup` 方法被阻止，就不会执行到这里
 *     }
 *   },
 *   // 代理事件
 *   delegates: {
 *     'click': function (e) {
 *       // e.target === this.element[0]
 *       // this === tom
 *     },
 *     'mouseover .avatar': function (e) {
 *       // 鼠标悬停在 element 里的 `.avatar` 元素
 *     }
 *   }
 * });
 * ```
 */
var Widget = Base.extend({

  /**
   * 初始化函数，将自动执行；实现事件自动订阅与初始化组件参数
   *
   * @method initialize
   * @param {Object} options 组件参数
   */
  initialize: function (options) {
    Widget.superclass.initialize.apply(this, arguments);

    // this.UI = {
    //   'default': {}
    // };

    // 容器与元素
    this.container = $(this.option('container'));
    this.element = $(this.option('element'))
        .addClass(this.option('classPrefix'));

    // 初始化事件代理
    this.initDelegates();

    // 实例唯一ID
    this.uniqueId = uniqueId();

    this.setup();

    cachedInstances[this.uniqueId] = this;
  },

  /**
   * 默认参数，子类自动继承并覆盖
   *
   * @property {Object} defaults
   * @type {Object}
   */
  defaults: {
    // TODO: ue-component 改成 pandora 之类的，以与旧版组件做区别？
    element: '

',
    // 默认插入到的容器，设置为 `null` 则不执行插入
    container: 'body'
  },

  /**
   * 寻找 element 后代，参数为空时，返回 element
   *
   * @method $
   * @param {Mixed} [selector] 选择符
   * @return {Object} jQuery 包装的 DOM 节点
   */
  $: function (selector) {
    return selector ? this.element.find(selector) : this.element;
  },

  /**
   * 自动执行的设置函数，预留用于子类覆盖
   *
   * @method setup
   */
  setup: function () {
  },

  /**
   * 事件代理，绑定在 element 上
   *
   * @method initDelegates
   * @param {Object|Function} [delegates] 代理事件列表
   * @return {Object} 当前实例
   */
  initDelegates: function (delegates) {
    delegates || (delegates = this.option('delegates'));

    if (!delegates) {
      return this;
    }

    if (typeof delegates === 'function') {
      delegates = delegates.call(this);
    }

    $.each(delegates, $.proxy(function (key, callback) {
      var match = key.match(DELEGATE_SPLITTER),
        event = match[1] + DELEGATE_NAMESPACE + this.uniqueId;

      if (typeof callback === 'string') {
        callback = this[callback];
      }

      if (match[2]) {
        this.element.on(event, match[2], $.proxy(callback, this));
      } else {
        this.element.on(event, $.proxy(callback, this));
      }
    }, this));

    return this;
  },

  /**
   * 插入elemnt到container
   * @method render
   */
  render: function () {
    var content,
      template = this.option('template');

    if (!this.rendered) {

      // 插入到容器中
      this.container.length && this.container.append(this.element);

      /**
       * `element` 所在的 `document` 对象
       *
       * @property {Object} document
       */
      this.document = this.element.prop('ownerDocument');

      /**
       * `element` 所在的 `window` 对象
       *
       * @property {Object} viewport
       */
      this.viewport = (function (doc) {
        return doc.defaultView || doc.parentWindow;
      })(this.document);

      this.rendered = true;
    }

    if (typeof template === 'function') {
      content = template(this.data());
    } else {
      content = this.option('content');
    }

    if (typeof content !== 'undefined') {
      this.element.html(content);
    }
  },

  /**
   * 销毁当前组件对象
   * @method destroy
   */
  destroy: function () {
    // 移除 element 事件代理
    this.element.off();

    // 从DOM中移除element
    this.element.remove();

    Widget.superclass.destroy.apply(this);
  }

});

var uniqueId = (function () {
  var ids = {};
  return function () {
    var id = Math.random().toString(36).substr(2);
    if (ids[id]) {
      return uniqueId();
    }
    ids[id] = true;
    return id;
  };
})();

// For memory leak, from aralejs
$(window).unload(function() {
  var cid;
  for(cid in cachedInstances) {
    cachedInstances[cid].destroy();
  }
});

module.exports = Widget;

});
<commit_msg>解决BUG：uniqueId定义顺序问题<commit_after>define(function (require, exports, module) {

/**
 * 组件基类
 *
 * @module Widget
 */

'use strict';

var $ = require('$'),
  Base = require('base');

var DELEGATE_SPLITTER = /^(\S+)\s*(.*)$/,
  DELEGATE_NAMESPACE = '.delegate-widget-';

var cachedInstances = {};

// TODO: 检查是否有内存泄漏发生

/**
 * 组件基类
 *
 * @class Widget
 * @constructor
 * @extends Base
 *
 * @example
 * ```
 * // 创建子类
 * var PersonWidget = Widget.extend({
 *   setup: function () {
 *     // 通知事件 `setup`
 *     this.fire('setup', this.option('name'), this.option('age'));
 *   }
 * });
 * // 创建子类实例
 * var tom = new PersonWidget({
 *   name: 'Tom',
 *   age: 21,
 *   // 订阅事件
 *   events: {
 *     // `setup`
 *     setup: function (e, name, age) {
 *       // e === 'setup'
 *       // name === 'Tom'
 *       // age === 21
 *       // this === tom
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法前执行
 *       // 此处返回 `false` 将阻止 `setup` 方法执行
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法后执行
 *       // 如果 `setup` 方法被阻止，就不会执行到这里
 *     }
 *   },
 *   // 代理事件
 *   delegates: {
 *     'click': function (e) {
 *       // e.target === this.element[0]
 *       // this === tom
 *     },
 *     'mouseover .avatar': function (e) {
 *       // 鼠标悬停在 element 里的 `.avatar` 元素
 *     }
 *   }
 * });
 * ```
 */
var Widget = Base.extend({

  /**
   * 初始化函数，将自动执行；实现事件自动订阅与初始化组件参数
   *
   * @method initialize
   * @param {Object} options 组件参数
   */
  initialize: function (options) {
    Widget.superclass.initialize.apply(this, arguments);

    // this.UI = {
    //   'default': {}
    // };

    // 实例唯一ID
    this.uniqueId = uniqueId();

    // 储存实例
    cachedInstances[this.uniqueId] = this;

    // 容器与元素
    this.container = $(this.option('container'));
    this.element = $(this.option('element'))
        .addClass(this.option('classPrefix'));

    // 初始化事件代理
    this.initDelegates();

    this.setup();
  },

  /**
   * 默认参数，子类自动继承并覆盖
   *
   * @property {Object} defaults
   * @type {Object}
   */
  defaults: {
    // TODO: ue-component 改成 pandora 之类的，以与旧版组件做区别？
    element: '

',
    // 默认插入到的容器，设置为 `null` 则不执行插入
    container: 'body'
  },

  /**
   * 寻找 element 后代，参数为空时，返回 element
   *
   * @method $
   * @param {Mixed} [selector] 选择符
   * @return {Object} jQuery 包装的 DOM 节点
   */
  $: function (selector) {
    return selector ? this.element.find(selector) : this.element;
  },

  /**
   * 自动执行的设置函数，预留用于子类覆盖
   *
   * @method setup
   */
  setup: function () {
  },

  /**
   * 事件代理，绑定在 element 上
   *
   * @method initDelegates
   * @param {Object|Function} [delegates] 代理事件列表
   * @return {Object} 当前实例
   */
  initDelegates: function (delegates) {
    delegates || (delegates = this.option('delegates'));

    if (!delegates) {
      return this;
    }

    if (typeof delegates === 'function') {
      delegates = delegates.call(this);
    }

    $.each(delegates, $.proxy(function (key, callback) {
      var match = key.match(DELEGATE_SPLITTER),
        event = match[1] + DELEGATE_NAMESPACE + this.uniqueId;

      if (typeof callback === 'string') {
        callback = this[callback];
      }

      if (match[2]) {
        this.element.on(event, match[2], $.proxy(callback, this));
      } else {
        this.element.on(event, $.proxy(callback, this));
      }
    }, this));

    return this;
  },

  /**
   * 插入elemnt到container
   * @method render
   */
  render: function () {
    var content,
      template = this.option('template');

    if (!this.rendered) {

      // 插入到容器中
      this.container.length && this.container.append(this.element);

      /**
       * `element` 所在的 `document` 对象
       *
       * @property {Object} document
       */
      this.document = this.element.prop('ownerDocument');

      /**
       * `element` 所在的 `window` 对象
       *
       * @property {Object} viewport
       */
      this.viewport = (function (doc) {
        return doc.defaultView || doc.parentWindow;
      })(this.document);

      this.rendered = true;
    }

    if (typeof template === 'function') {
      content = template(this.data());
    } else {
      content = this.option('content');
    }

    if (typeof content !== 'undefined') {
      this.element.html(content);
    }
  },

  /**
   * 销毁当前组件对象
   * @method destroy
   */
  destroy: function () {
    // 移除 element 事件代理
    this.element.off();

    // 从DOM中移除element
    this.element.remove();

    Widget.superclass.destroy.apply(this);
  }

});

var uniqueId = (function () {
  var ids = {};
  return function () {
    var id = Math.random().toString(36).substr(2);
    if (ids[id]) {
      return uniqueId();
    }
    ids[id] = true;
    return id;
  };
})();

// For memory leak, from aralejs
$(window).unload(function() {
  var cid;
  for(cid in cachedInstances) {
    cachedInstances[cid].destroy();
  }
});

module.exports = Widget;

});
<|endoftext|>