<commit_before>import { now, extend } from '../core/util';
import {
    addDomEvent,
    removeDomEvent,
    preventDefault,
    getEventContainerPoint
} from '../core/util/dom';
import Map from './Map';

const events =
    /**
     * mousedown event
     * @event Map#mousedown
     * @type {Object}
     * @property {String} type                    - mousedown
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mousedown ' +
    /**
     * mouseup event
     * @event Map#mouseup
     * @type {Object}
     * @property {String} type                    - mouseup
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseup ' +
    /**
     * mouseover event
     * @event Map#mouseover
     * @type {Object}
     * @property {String} type                    - mouseover
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseover ' +
    /**
     * mouseout event
     * @event Map#mouseout
     * @type {Object}
     * @property {String} type                    - mouseout
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseout ' +
    /**
     * mouseenter event
     * @event Map#mouseenter
     * @type {Object}
     * @property {String} type                    - mouseenter
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseenter ' +
    /**
     * mouseleave event
     * @event Map#mouseleave
     * @type {Object}
     * @property {String} type                    - mouseleave
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseleave ' +
    /**
     * mousemove event
     * @event Map#mousemove
     * @type {Object}
     * @property {String} type                    - mousemove
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mousemove ' +
    /**
     * click event
     * @event Map#click
     * @type {Object}
     * @property {String} type                    - click
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'click ' +
    /**
     * dblclick event
     * @event Map#dblclick
     * @type {Object}
     * @property {String} type                    - dblclick
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'dblclick ' +
    /**
     * contextmenu event
     * @event Map#contextmenu
     * @type {Object}
     * @property {String} type                    - contextmenu
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'contextmenu ' +
    /**
     * keypress event
     * @event Map#keypress
     * @type {Object}
     * @property {String} type                    - keypress
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'keypress ' +
    /**
     * touchstart event
     * @event Map#touchstart
     * @type {Object}
     * @property {String} type                    - touchstart
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchstart ' +
    /**
     * touchmove event
     * @event Map#touchmove
     * @type {Object}
     * @property {String} type                    - touchmove
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchmove ' +
    /**
     * touchend event
     * @event Map#touchend
     * @type {Object}
     * @property {String} type                    - touchend
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchend ';

Map.include(/** @lends Map.prototype */ {
    _registerDomEvents() {
        const dom = this._panels.mapWrapper || this._containerDOM;
        addDomEvent(dom, events, this._handleDOMEvent, this);
    },

    _removeDomEvents() {
        const dom = this._panels.mapWrapper || this._containerDOM;
        removeDomEvent(dom, events, this._handleDOMEvent, this);
    },

    _handleDOMEvent(e) {
        const clickTimeThreshold = this.options['clickTimeThreshold'];
        const type = e.type;
        const isMouseDown = type === 'mousedown' || (type === 'touchstart' && (!e.touches || e.touches.length === 1));
        // prevent default contextmenu
        if (isMouseDown) {
            this._domMouseDownTime = now();
            this._domMouseDownView = this.getView();
        }
        const isRotating = type === 'contextmenu' && isRotatingMap(this);
        if (type === 'contextmenu') {
            // prevent context menu, if duration from mousedown > 300ms
            preventDefault(e);
            const downTime = this._domMouseDownTime;
            const time = now();
            if (time - downTime <= clickTimeThreshold && !isRotating) {
                this._fireDOMEvent(this, e, 'dom:' + e.type);
            }
        } else {
            this._fireDOMEvent(this, e, 'dom:' + e.type);
        }
        if (this._ignoreEvent(e)) {
            return;
        }
        let mimicClick = false;
        // ignore click lasted for more than 300ms.
        // happen.js produce event without touches
        if (isMouseDown) {
            this._mouseDownTime = now();
        } else if ((type === 'click' || type === 'touchend' || type === 'contextmenu')) {
            if (!this._mouseDownTime) {
                //mousedown | touchstart propogation is stopped
                //ignore the click/touchend/contextmenu
                return;
            } else {
                const downTime = this._mouseDownTime;
                delete this._mouseDownTime;
                const time = now();
                if (time - downTime > clickTimeThreshold) {
                    if (type === 'click' || type === 'contextmenu') {
                        return;
                    }
                } else if (type === 'contextmenu') {
                    if (isRotating) {
                        return;
                    }
                } else if (type === 'touchend') {
                    mimicClick = true;
                }
            }
        }
        let mimicEvent;
        if (mimicClick) {
            if (this._clickTime && (now() - this._clickTime <= clickTimeThreshold)) {
                delete this._clickTime;
                mimicEvent = 'dblclick';
                this._fireDOMEvent(this, e, 'dom:dblclick');
            } else {
                this._clickTime = now();
                mimicEvent = 'click';
                this._fireDOMEvent(this, e, 'dom:click');
            }
        }
        if (this._ignoreEvent(e)) {
            return;
        }
        this._fireDOMEvent(this, e, type);
        if (mimicEvent) {
            this._fireDOMEvent(this, e, mimicEvent);
        }
    },

    _ignoreEvent(domEvent) {
        //ignore events originated from control and ui doms.
        if (!domEvent || !this._panels.control) {
            return false;
        }
        if (this._isEventOutMap(domEvent)) {
            return true;
        }
        let target = domEvent.srcElement || domEvent.target;
        let preTarget;
        if (target) {
            while (target && target !== this._containerDOM) {
                if (target.className && target.className.indexOf &&
                    (target.className.indexOf('maptalks-control') >= 0 || (target.className.indexOf('maptalks-ui') >= 0 && preTarget && !preTarget['eventsPropagation']))) {
                    return true;
                }
                preTarget = target;
                target = target.parentNode;
            }

        }
        return false;
    },

    _isEventOutMap(domEvent) {
        if (this.getPitch() > this.options['maxVisualPitch']) {
            const actualEvent = this._getActualEvent(domEvent);
            const eventPos = getEventContainerPoint(actualEvent, this._containerDOM);
            if (!this.getContainerExtent().contains(eventPos)) {
                return true;
            }
        }
        return false;
    },

    _parseEvent(e, type) {
        if (!e) {
            return null;
        }
        let eventParam = {
            'domEvent': e
        };
        if (type !== 'keypress' && e.clientX !== undefined) {
            const actual = this._getActualEvent(e);
            if (actual) {
                const containerPoint = getEventContainerPoint(actual, this._containerDOM);
                eventParam = extend(eventParam, {
                    'containerPoint': containerPoint,
                    'viewPoint': this.containerPointToViewPoint(containerPoint)
                });
                const maxVisualPitch = this.options['maxVisualPitch'];
                // ignore coorindate out of visual extent
                if (this.getPitch() <= maxVisualPitch || containerPoint.y >= (this.height - this._getVisualHeight(maxVisualPitch))) {
                    eventParam = extend(eventParam, {
                        'coordinate': this.containerPointToCoord(containerPoint),
                        'point2d': this._containerPointToPoint(containerPoint)
                    });
                }
            }
        }
        return eventParam;
    },

    _parseEventFromCoord(coord) {
        const containerPoint = this.coordToContainerPoint(coord),
            viewPoint = this.containerPointToViewPoint(containerPoint);
        const e = {
            'coordinate': coord,
            'containerPoint': containerPoint,
            'viewPoint': viewPoint,
            'point2d': this.coordToPoint(coord)
        };
        return e;
    },

    _getActualEvent(e) {
        return e.touches && e.touches.length > 0 ?
            e.touches[0] : e.changedTouches && e.changedTouches.length > 0 ?
                e.changedTouches[0] : e;
    },

    _fireDOMEvent(target, e, type) {
        if (this.isRemoved()) {
            return;
        }
        const eventParam = this._parseEvent(e, type);
        this._fireEvent(type, eventParam);
    }

    // _onKeyPress(e) {
    //     if (!this.isRemoved() && e.keyCode === 48 && e.ctrlKey) {
    //         this.setBearing(0);
    //     }
    // }
});

Map.addOnLoadHook('_registerDomEvents');

function isRotatingMap(map) {
    if (!map._domMouseDownView) {
        return true;
    }
    const view = map.getView(), mouseDownView = map._domMouseDownView;
    return (view.bearing !== mouseDownView.bearing || view.pitch !== mouseDownView.pitch);
}
<commit_msg>fix #1716<commit_after>import { now, extend } from '../core/util';
import {
    addDomEvent,
    removeDomEvent,
    preventDefault,
    getEventContainerPoint
} from '../core/util/dom';
import Map from './Map';

const events =
    /**
     * mousedown event
     * @event Map#mousedown
     * @type {Object}
     * @property {String} type                    - mousedown
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mousedown ' +
    /**
     * mouseup event
     * @event Map#mouseup
     * @type {Object}
     * @property {String} type                    - mouseup
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseup ' +
    /**
     * mouseover event
     * @event Map#mouseover
     * @type {Object}
     * @property {String} type                    - mouseover
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseover ' +
    /**
     * mouseout event
     * @event Map#mouseout
     * @type {Object}
     * @property {String} type                    - mouseout
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseout ' +
    /**
     * mouseenter event
     * @event Map#mouseenter
     * @type {Object}
     * @property {String} type                    - mouseenter
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseenter ' +
    /**
     * mouseleave event
     * @event Map#mouseleave
     * @type {Object}
     * @property {String} type                    - mouseleave
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mouseleave ' +
    /**
     * mousemove event
     * @event Map#mousemove
     * @type {Object}
     * @property {String} type                    - mousemove
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'mousemove ' +
    /**
     * click event
     * @event Map#click
     * @type {Object}
     * @property {String} type                    - click
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'click ' +
    /**
     * dblclick event
     * @event Map#dblclick
     * @type {Object}
     * @property {String} type                    - dblclick
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'dblclick ' +
    /**
     * contextmenu event
     * @event Map#contextmenu
     * @type {Object}
     * @property {String} type                    - contextmenu
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'contextmenu ' +
    /**
     * keypress event
     * @event Map#keypress
     * @type {Object}
     * @property {String} type                    - keypress
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'keypress ' +
    /**
     * touchstart event
     * @event Map#touchstart
     * @type {Object}
     * @property {String} type                    - touchstart
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchstart ' +
    /**
     * touchmove event
     * @event Map#touchmove
     * @type {Object}
     * @property {String} type                    - touchmove
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchmove ' +
    /**
     * touchend event
     * @event Map#touchend
     * @type {Object}
     * @property {String} type                    - touchend
     * @property {Map} target            - the map fires event
     * @property {Coordinate} coordinate - coordinate of the event
     * @property {Point} containerPoint  - container point of the event
     * @property {Point} viewPoint       - view point of the event
     * @property {Event} domEvent                 - dom event
     */
    'touchend ';

Map.include(/** @lends Map.prototype */ {
    _registerDomEvents() {
        const dom = this._panels.mapWrapper || this._containerDOM;
        addDomEvent(dom, events, this._handleDOMEvent, this);
    },

    _removeDomEvents() {
        const dom = this._panels.mapWrapper || this._containerDOM;
        removeDomEvent(dom, events, this._handleDOMEvent, this);
    },

    _handleDOMEvent(e) {
        const clickTimeThreshold = this.options['clickTimeThreshold'];
        const type = e.type;
        const isMouseDown = type === 'mousedown' || (type === 'touchstart' && (!e.touches || e.touches.length === 1));
        // prevent default contextmenu
        if (isMouseDown) {
            this._domMouseDownTime = now();
            this._domMouseDownView = this.getView();
        }
        const isRotating = type === 'contextmenu' && isRotatingMap(this);
        if (type === 'contextmenu') {
            // prevent context menu, if duration from mousedown > 300ms
            preventDefault(e);
            const downTime = this._domMouseDownTime;
            const time = now();
            if (time - downTime <= clickTimeThreshold && !isRotating) {
                this._fireDOMEvent(this, e, 'dom:' + e.type);
            }
        } else {
            this._fireDOMEvent(this, e, 'dom:' + e.type);
        }
        if (this._ignoreEvent(e)) {
            return;
        }
        let mimicClick = false;
        // ignore click lasted for more than 300ms.
        // happen.js produce event without touches
        if (isMouseDown) {
            this._mouseDownTime = now();
        } else if ((type === 'click' || type === 'touchend' || type === 'contextmenu')) {
            if (!this._mouseDownTime) {
                //mousedown | touchstart propogation is stopped
                //ignore the click/touchend/contextmenu
                return;
            } else {
                const downTime = this._mouseDownTime;
                delete this._mouseDownTime;
                const time = now();
                if (time - downTime > clickTimeThreshold) {
                    if (type === 'click' || type === 'contextmenu') {
                        return;
                    }
                } else if (type === 'contextmenu') {
                    if (isRotating) {
                        return;
                    }
                } else if (type === 'touchend') {
                    mimicClick = true;
                }
            }
        }
        let mimicEvent;
        if (mimicClick) {
            if (this._clickTime && (now() - this._clickTime <= clickTimeThreshold)) {
                delete this._clickTime;
                mimicEvent = 'dblclick';
                this._fireDOMEvent(this, e, 'dom:dblclick');
            } else {
                this._clickTime = now();
                mimicEvent = 'click';
                this._fireDOMEvent(this, e, 'dom:click');
            }
        }
        if (this._ignoreEvent(e)) {
            return;
        }
        this._fireDOMEvent(this, e, type);
        if (mimicEvent) {
            this._fireDOMEvent(this, e, mimicEvent);
        }
    },

    _ignoreEvent(domEvent) {
        //ignore events originated from control and ui doms.
        if (!domEvent || !this._panels.control) {
            return false;
        }
        if (this._isEventOutMap(domEvent)) {
            return true;
        }
        let target = domEvent.srcElement || domEvent.target;
        let preTarget;
        if (target) {
            while (target && target !== this._containerDOM) {
                if (target.className && target.className.indexOf &&
                    (target.className.indexOf('maptalks-control') >= 0 || (target.className.indexOf('maptalks-ui') >= 0 && preTarget && !preTarget['eventsPropagation']))) {
                    return true;
                }
                preTarget = target;
                target = target.parentNode;
            }

        }
        return false;
    },

    _isEventOutMap(domEvent) {
        if (this.getPitch() > this.options['maxVisualPitch']) {
            const actualEvent = this._getActualEvent(domEvent);
            const eventPos = getEventContainerPoint(actualEvent, this._containerDOM);
            if (!this.getContainerExtent().contains(eventPos)) {
                return true;
            }
        }
        return false;
    },

    _parseEvent(e, type) {
        if (!e) {
            return null;
        }
        let eventParam = {
            'domEvent': e
        };
        if (type !== 'keypress') {
            const actual = this._getActualEvent(e);
            if (actual && actual.clientX !== undefined) {
                const containerPoint = getEventContainerPoint(actual, this._containerDOM);
                eventParam = extend(eventParam, {
                    'containerPoint': containerPoint,
                    'viewPoint': this.containerPointToViewPoint(containerPoint)
                });
                const maxVisualPitch = this.options['maxVisualPitch'];
                // ignore coorindate out of visual extent
                if (this.getPitch() <= maxVisualPitch || containerPoint.y >= (this.height - this._getVisualHeight(maxVisualPitch))) {
                    eventParam = extend(eventParam, {
                        'coordinate': this.containerPointToCoord(containerPoint),
                        'point2d': this._containerPointToPoint(containerPoint)
                    });
                }
            }
        }
        return eventParam;
    },

    _parseEventFromCoord(coord) {
        const containerPoint = this.coordToContainerPoint(coord),
            viewPoint = this.containerPointToViewPoint(containerPoint);
        const e = {
            'coordinate': coord,
            'containerPoint': containerPoint,
            'viewPoint': viewPoint,
            'point2d': this.coordToPoint(coord)
        };
        return e;
    },

    _getActualEvent(e) {
        return e.touches && e.touches.length > 0 ?
            e.touches[0] : e.changedTouches && e.changedTouches.length > 0 ?
                e.changedTouches[0] : e;
    },

    _fireDOMEvent(target, e, type) {
        if (this.isRemoved()) {
            return;
        }
        const eventParam = this._parseEvent(e, type);
        this._fireEvent(type, eventParam);
    }

    // _onKeyPress(e) {
    //     if (!this.isRemoved() && e.keyCode === 48 && e.ctrlKey) {
    //         this.setBearing(0);
    //     }
    // }
});

Map.addOnLoadHook('_registerDomEvents');

function isRotatingMap(map) {
    if (!map._domMouseDownView) {
        return true;
    }
    const view = map.getView(), mouseDownView = map._domMouseDownView;
    return (view.bearing !== mouseDownView.bearing || view.pitch !== mouseDownView.pitch);
}
<|endoftext|>


<commit_before>(function(root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([], function() {
            return factory;
        });
    }else if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    }
    else {
        root['eo-graph'] = factory;
    }
})(this, function() { // jscs:disable validateIndentation
'use strict';

var graph = {};
var currentNode;
var currentNodeId;
var edges = [];
var version = 2;

var initGraph = function(object, root) {
    var initialNode;
    var error = false;

    graph = object;
    if (typeof graph === 'object' && graph.__version__ === version) {
        if (root !== undefined && graph.hasOwnProperty(root)) {
            initialNode = root;
        }
        else if (graph.hasOwnProperty('__root__')) {
            initialNode = graph.__root__;
        }
        else {
            console.error('Not root node found');
            error = true;
        }

        if (graph.hasOwnProperty(initialNode)) {
            currentNode = graph[initialNode];
            currentNode.id = initialNode;
        }
        else
        {
            console.error('Invalid root node');
            error = true;
        }
    }
    else {
        console.error('Invalid graph version');
        error = true;
    }

    return !error;
};

var matchByType = function(value, ruleName) {
    var validation = {
        msg: '',
        isValid: true,
        value: value
    };

    switch (ruleName) {
        case 'number':
            var validatedValue = Number(value);
            if (isNaN(validatedValue)) {
                validation.valid = false;
                validation.msg = 'The value is not a number';
            }
            else {
                validation.value = validatedValue;
            }
        break;
        default:
            validation.msg = 'Invalid rule name: ' + ruleName;
    }

    return validation;
};

var matchEdgesWithValue = function(value, edges) {
    var valueTested;
    var edge = null;

    for (var i = 0, len = edges.length; i < len; i++) {
        if (typeof edges[i].match === 'object') {
            valueTested = matchByType(value, edges[i].match.type);
            if (valueTested.isValid) {
                edge =  {
                    edgeId: edges[i].next,
                    label: edges[i].label,
                    value: valueTested.value
                };
                break;
            }
            else {
                console.debug(valueTested.msg);
            }
        }
        else if (edges[i].value === value) {
            edge = {
                edgeId: edges[i].next,
                label: edges[i].label,
                value: value
            };
            break;
        }
    }

    return edge;
};

var isTerminalNode = function(node) {
    if (node.edges && node.edges.length > 0) {
        return false;
    }
    return true;
};

var doNext = function(edge) {
    var nextNode;

    nextNode = graph[edge.edgeId];

    if (nextNode !== undefined) {
        edge.nodeId = currentNode.id;
        edges.push(edge);

        // Move to the next node
        currentNode = nextNode;
        currentNode.id = edge.edgeId;
    }
    else {
        console.error('No found nodes with id: ' + edge.edgeId);
    }

    return nextNode;
};

var doCurrent = function() {
    return currentNode;
};

var doPrev = function() {
    var edge;
    var prevNode;

    prevNode = null;
    edge = edges.pop();

    if (edge !== undefined) {
        currentNode = graph[edge.nodeId];
        prevNode = currentNode;
    }

    return currentNode;
};

var nextNode = function(value) {
    var nextNode;
    var edge;

    if (currentNode.edges !== undefined) {
        edge = matchEdgesWithValue(value, currentNode.edges);

        if (edge !== null) {
            nextNode = doNext({
                edgeId: edge.edgeId,
                value: edge.value,
                label: edge.label
            });
        }
        else {
            console.error('No found edges with value id: ' + value);
        }
    }

    return nextNode;
};

var getEdges = function() {
    return edges;
};

var getValues = function() {
    return edges.map(function(edge) {
        return edge.value;
    });
};

var hasNext = function() {
    return !isTerminalNode(currentNode);
};

var hasPrevious = function() {
    return edges.length > 0;
};

return {
    init: initGraph,
    next: nextNode,
    current: doCurrent,
    previous: doPrev,
    edges: getEdges,
    values: getValues,
    hasNext: hasNext,
    hasPrevious: hasPrevious
};

});
<commit_msg>Add nonempty validation in order to accept any text from a textbox<commit_after>(function(root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define([], function() {
            return factory;
        });
    }else if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    }
    else {
        root['eo-graph'] = factory;
    }
})(this, function() { // jscs:disable validateIndentation
'use strict';

var graph = {};
var currentNode;
var currentNodeId;
var edges = [];
var version = 2;

var initGraph = function(object, root) {
    var initialNode;
    var error = false;

    graph = object;
    if (typeof graph === 'object' && graph.__version__ === version) {
        if (root !== undefined && graph.hasOwnProperty(root)) {
            initialNode = root;
        }
        else if (graph.hasOwnProperty('__root__')) {
            initialNode = graph.__root__;
        }
        else {
            console.error('Not root node found');
            error = true;
        }

        if (graph.hasOwnProperty(initialNode)) {
            currentNode = graph[initialNode];
            currentNode.id = initialNode;
        }
        else
        {
            console.error('Invalid root node');
            error = true;
        }
    }
    else {
        console.error('Invalid graph version');
        error = true;
    }

    return !error;
};

var matchByType = function(value, ruleName) {
    var validation = {
        msg: '',
        isValid: true,
        value: value
    };

    switch (ruleName) {
        case 'number':
            var validatedValue = Number(value);
            if (isNaN(validatedValue)) {
                validation.valid = false;
                validation.msg = 'The value is not a number';
            }
            else {
                validation.value = validatedValue;
            }
        break;
        case 'nonempty':
            if (value === '') {
                validation.valid = false;
                validation.msg = 'The value is empty';
            }
            else {
                validation.validation = validatedValue;
            }
        break;
        default:
            validation.msg = 'Invalid rule name: ' + ruleName;
    }

    return validation;
};

var matchEdgesWithValue = function(value, edges) {
    var valueTested;
    var edge = null;

    for (var i = 0, len = edges.length; i < len; i++) {
        if (typeof edges[i].match === 'object') {
            valueTested = matchByType(value, edges[i].match.type);
            if (valueTested.isValid) {
                edge =  {
                    edgeId: edges[i].next,
                    label: edges[i].label,
                    value: valueTested.value
                };
                break;
            }
            else {
                console.debug(valueTested.msg);
            }
        }
        else if (edges[i].value === value) {
            edge = {
                edgeId: edges[i].next,
                label: edges[i].label,
                value: value
            };
            break;
        }
    }

    return edge;
};

var isTerminalNode = function(node) {
    if (node.edges && node.edges.length > 0) {
        return false;
    }
    return true;
};

var doNext = function(edge) {
    var nextNode;

    nextNode = graph[edge.edgeId];

    if (nextNode !== undefined) {
        edge.nodeId = currentNode.id;
        edges.push(edge);

        // Move to the next node
        currentNode = nextNode;
        currentNode.id = edge.edgeId;
    }
    else {
        console.error('No found nodes with id: ' + edge.edgeId);
    }

    return nextNode;
};

var doCurrent = function() {
    return currentNode;
};

var doPrev = function() {
    var edge;
    var prevNode;

    prevNode = null;
    edge = edges.pop();

    if (edge !== undefined) {
        currentNode = graph[edge.nodeId];
        prevNode = currentNode;
    }

    return currentNode;
};

var nextNode = function(value) {
    var nextNode;
    var edge;

    if (currentNode.edges !== undefined) {
        edge = matchEdgesWithValue(value, currentNode.edges);

        if (edge !== null) {
            nextNode = doNext({
                edgeId: edge.edgeId,
                value: edge.value,
                label: edge.label,
                nodeName: currentNode.label
            });
        }
        else {
            console.error('No found edges with value id: ' + value);
        }
    }

    return nextNode;
};

var getEdges = function() {
    return edges;
};

var getValues = function() {
    return edges.map(function(edge) {
        return edge.value;
    });
};

var hasNext = function() {
    return !isTerminalNode(currentNode);
};

var hasPrevious = function() {
    return edges.length > 0;
};

return {
    init: initGraph,
    next: nextNode,
    current: doCurrent,
    previous: doPrev,
    edges: getEdges,
    values: getValues,
    hasNext: hasNext,
    hasPrevious: hasPrevious
};

});
<|endoftext|>


<commit_before>const React = require('react');
const T = require('prop-types');
const Motion = require('./motion');
const Utils = require('./utils');

// Component

module.exports = class MultiMotion extends React.PureComponent {

    static propTypes = {
        animConfigs: T.object.isRequired,
        children: T.any.isRequired,
        getAnimControllers: T.func
    }

    constructor(props) {

        super(props);

        const children = Utils.getElementsFromChildren(props.children);
        const configKeys = Object.keys(props.animConfigs);
        const childrenByKeys = this._getModelByKeys(props.children);

        const {
            delayConfigs,
            immediateAnimConfigs,
            immediateChildren
        } = Object.keys(props.animConfigs)
        .reduce((collector, animKey) => {

            const anim = props.animConfigs[animKey];
            const child = childrenByKeys[animKey];

            if (!!anim.$delay) {

                const $delay = anim.$delay;

                // delete the $delay for the future animConfig
                // or we'd be in an infinite loop right?

                delete anim.$delay;

                collector.delayConfigs.push({
                    delayAnimConfig: { key: animKey, val: anim },
                    delayChild: child,
                    $delay
                });
            }
            else {
                collector.immediateAnimConfigs[animKey] = anim;
                collector.immediateChildren.push(child);
            }

            return collector;
        }, {
            delayConfigs: [],
            immediateAnimConfigs: {},
            immediateChildren: []
        });

        const immediateAnimConfigsWithDefaults = Object.keys(immediateAnimConfigs)
        .reduce((collector, animKey) => {

            // 'Utils.assignDefaultsToAnimConfig' handles
            // $delay set for individual css props

            collector[animKey] = Utils.assignDefaultsToAnimConfig(immediateAnimConfigs[animKey]);

            return collector;
        }, {});

        const self = this;

        this.animControllers = {};

        this.state = Object.assign(
            {},
            this.state,
            {
                model: immediateChildren,
                animConfigs: immediateAnimConfigsWithDefaults
            }
        );

        /*
            delayConfig's schema
            [{
                delayAnimConfig,
                delayChild,
                $delay
            }]
        */

        delayConfigs.forEach((delayConfig) => {

            const { delayAnimConfig, delayChild, $delay } = delayConfig;

            setTimeout(() => {

                const currentAnimConfigs = this.state.animConfigs;
                const currentModel = this.state.model;
                delete delayAnimConfig.$delay;

                // Can process nested $delays set here
                const delayedAnimConfigWithDefaults = Utils.assignDefaultsToAnimConfig(delayAnimConfig);

                const newAnimConfigs = {
                    ...currentAnimConfigs,
                    [delayedAnimConfigWithDefaults.key]: delayedAnimConfigWithDefaults.val
                }

                const newModel = [...currentModel, delayChild];

                this.setState({
                    model: newModel,
                    animConfigs: newAnimConfigs
                })
            }, $delay);
        });

        this.getModelByKeys = this._getModelByKeys.bind(this);
        this.setAnimController = this._setAnimController.bind(this);
    }

    _getModelByKeys(model) {

        return model.reduce((collector, child) => {

            collector[child.key] = child;
            return collector;
        }, {});
    }

    _setAnimController(animName) {

        const { animConfigs } = this.state;
        const { getAnimControllers } = this.props;

        return (animController) => {

            const { animConfigs } = this.state;

            const newAnimControllers = Object.assign({},
                { ...this.animControllers },
                {
                    [animName]: animController
                }
            );

            this.animControllers = newAnimControllers;

            if (typeof getAnimControllers === 'function') {
                getAnimControllers(this.animControllers);
            }
        }
    }

    /*
        Make a strange-motion plugin called `quake` that accepts seismic number
        etc. and makes the thing shake perpendicularly or in a sine-wavelike fasion
        like an earthquake
        Make a strange-motion plugin called `blur` that accepts a blur multiplier number
        The `timeline` plugin can accept an array of plugins to apply to a partial of
        a timeline animation
    */

    render() {

        const { model, animConfigs } = this.state;

        const modelByKeys = this.getModelByKeys(model);

        const controlledAnimConfigs = Object.keys(animConfigs)
        .reduce((collector, animName) => {

            collector[animName] = animConfigs[animName];

            return collector;
        }, {});

        return (

                {Object.keys(controlledAnimConfigs).map((animName) => {
                    return (
                        <Motion
                            animConfig={controlledAnimConfigs[animName]}
                            getAnimController={this.setAnimController(animName)}
                            key={animName}
                        >
                            {modelByKeys[animName]}
                        </Motion>
                    );
                })}

        );
    }
};
<commit_msg>Fix bug where a single item gets passed for model<commit_after>const React = require('react');
const T = require('prop-types');
const Motion = require('./motion');
const Utils = require('./utils');

// Component

module.exports = class MultiMotion extends React.PureComponent {

    static propTypes = {
        animConfigs: T.object.isRequired,
        children: T.any.isRequired,
        getAnimControllers: T.func
    }

    constructor(props) {

        super(props);

        const children = Utils.getElementsFromChildren(props.children);
        const configKeys = Object.keys(props.animConfigs);
        const childrenByKeys = this._getModelByKeys(props.children);

        const {
            delayConfigs,
            immediateAnimConfigs,
            immediateChildren
        } = Object.keys(props.animConfigs)
        .reduce((collector, animKey) => {

            const anim = props.animConfigs[animKey];
            const child = childrenByKeys[animKey];

            if (!!anim.$delay) {

                const $delay = anim.$delay;

                // delete the $delay for the future animConfig
                // or we'd be in an infinite loop right?

                delete anim.$delay;

                collector.delayConfigs.push({
                    delayAnimConfig: { key: animKey, val: anim },
                    delayChild: child,
                    $delay
                });
            }
            else {
                collector.immediateAnimConfigs[animKey] = anim;
                collector.immediateChildren.push(child);
            }

            return collector;
        }, {
            delayConfigs: [],
            immediateAnimConfigs: {},
            immediateChildren: []
        });

        const immediateAnimConfigsWithDefaults = Object.keys(immediateAnimConfigs)
        .reduce((collector, animKey) => {

            // 'Utils.assignDefaultsToAnimConfig' handles
            // $delay set for individual css props

            collector[animKey] = Utils.assignDefaultsToAnimConfig(immediateAnimConfigs[animKey]);

            return collector;
        }, {});

        const self = this;

        this.animControllers = {};

        this.state = Object.assign(
            {},
            this.state,
            {
                model: immediateChildren,
                animConfigs: immediateAnimConfigsWithDefaults
            }
        );

        /*
            delayConfig's schema
            [{
                delayAnimConfig,
                delayChild,
                $delay
            }]
        */

        delayConfigs.forEach((delayConfig) => {

            const { delayAnimConfig, delayChild, $delay } = delayConfig;

            setTimeout(() => {

                const currentAnimConfigs = this.state.animConfigs;
                const currentModel = this.state.model;
                delete delayAnimConfig.$delay;

                // Can process nested $delays set here
                const delayedAnimConfigWithDefaults = Utils.assignDefaultsToAnimConfig(delayAnimConfig);

                const newAnimConfigs = {
                    ...currentAnimConfigs,
                    [delayedAnimConfigWithDefaults.key]: delayedAnimConfigWithDefaults.val
                }

                const newModel = [...currentModel, delayChild];

                this.setState({
                    model: newModel,
                    animConfigs: newAnimConfigs
                })
            }, $delay);
        });

        this.getModelByKeys = this._getModelByKeys.bind(this);
        this.setAnimController = this._setAnimController.bind(this);
    }

    _getModelByKeys(model) {

        let newModel = model;

        if (typeof model === 'object' &&
            !Array.isArray(model)) {

            newModel = [].concat(model);
        }

        return newModel.reduce((collector, child) => {

            collector[child.key] = child;
            return collector;
        }, {});
    }

    _setAnimController(animName) {

        const { animConfigs } = this.state;
        const { getAnimControllers } = this.props;

        return (animController) => {

            const { animConfigs } = this.state;

            const newAnimControllers = Object.assign({},
                { ...this.animControllers },
                {
                    [animName]: animController
                }
            );

            this.animControllers = newAnimControllers;

            if (typeof getAnimControllers === 'function') {
                getAnimControllers(this.animControllers);
            }
        }
    }

    /*
        Make a strange-motion plugin called `quake` that accepts seismic number
        etc. and makes the thing shake perpendicularly or in a sine-wavelike fasion
        like an earthquake
        Make a strange-motion plugin called `blur` that accepts a blur multiplier number
        The `timeline` plugin can accept an array of plugins to apply to a partial of
        a timeline animation
    */

    render() {

        const { model, animConfigs } = this.state;

        const modelByKeys = this.getModelByKeys(model);

        const controlledAnimConfigs = Object.keys(animConfigs)
        .reduce((collector, animName) => {

            collector[animName] = animConfigs[animName];

            return collector;
        }, {});

        return (

                {Object.keys(controlledAnimConfigs).map((animName) => {
                    return (
                        <Motion
                            animConfig={controlledAnimConfigs[animName]}
                            getAnimController={this.setAnimController(animName)}
                            key={animName}
                        >
                            {modelByKeys[animName]}
                        </Motion>
                    );
                })}

        );
    }
};
<|endoftext|>


<commit_before>// @flow
import hashStr from 'glamor/lib/hash'

import type { RuleSet, NameGenerator } from '../types'
import flatten from '../utils/flatten'
import parse from '../vendor/postcss-safe-parser/parse'
import postcssNested from '../vendor/postcss-nested'
import autoprefix from '../utils/autoprefix'
import styleSheet from './StyleSheet'

/*
 ComponentStyle is all the CSS-specific stuff, not
 the React-specific stuff.
 */
export default (nameGenerator: NameGenerator) => {
  const inserted = {}

  class ComponentStyle {
    rules: RuleSet
    insertedRule: Object

    constructor(rules: RuleSet) {
      this.rules = rules
      if (!styleSheet.injected) styleSheet.inject()
    }

    generateName(str: string) {
      return nameGenerator(hashStr(str))
    }

    /*
     * Flattens a rule set into valid CSS
     * Hashes it, wraps the whole chunk in a ._hashName {}
     * Parses that with PostCSS then runs PostCSS-Nested on it
     * Returns the hash to be injected on render()
     * */
    generateAndInjectStyles(executionContext: Object, identifier: string) {
      if (!this.insertedRule) this.insertedRule = styleSheet.insert(`.${identifier} {}`)
      const flatCSS = flatten(this.rules, executionContext).join('')
        .replace(/^\s*\/\/.*$/gm, '') // replace JS comments
      const hash = hashStr(identifier + flatCSS)
      if (!inserted[hash]) {
        const selector = nameGenerator(hash)
        inserted[hash] = selector
        const root = parse(`.${selector} { ${flatCSS} }`)
        postcssNested(root)
        autoprefix(root)
        this.insertedRule.appendRule(root.toResult().css)
      }
      return inserted[hash]
    }
  }

  return ComponentStyle
}
<commit_msg>add a newline before injecting<commit_after>// @flow
import hashStr from 'glamor/lib/hash'

import type { RuleSet, NameGenerator } from '../types'
import flatten from '../utils/flatten'
import parse from '../vendor/postcss-safe-parser/parse'
import postcssNested from '../vendor/postcss-nested'
import autoprefix from '../utils/autoprefix'
import styleSheet from './StyleSheet'

/*
 ComponentStyle is all the CSS-specific stuff, not
 the React-specific stuff.
 */
export default (nameGenerator: NameGenerator) => {
  const inserted = {}

  class ComponentStyle {
    rules: RuleSet
    insertedRule: Object

    constructor(rules: RuleSet) {
      this.rules = rules
      if (!styleSheet.injected) styleSheet.inject()
    }

    generateName(str: string) {
      return nameGenerator(hashStr(str))
    }

    /*
     * Flattens a rule set into valid CSS
     * Hashes it, wraps the whole chunk in a ._hashName {}
     * Parses that with PostCSS then runs PostCSS-Nested on it
     * Returns the hash to be injected on render()
     * */
    generateAndInjectStyles(executionContext: Object, identifier: string) {
      if (!this.insertedRule) this.insertedRule = styleSheet.insert(`.${identifier} {}`)
      const flatCSS = flatten(this.rules, executionContext).join('')
        .replace(/^\s*\/\/.*$/gm, '') // replace JS comments
      const hash = hashStr(identifier + flatCSS)
      if (!inserted[hash]) {
        const selector = nameGenerator(hash)
        inserted[hash] = selector
        const root = parse(`.${selector} { ${flatCSS} }`)
        postcssNested(root)
        autoprefix(root)
        this.insertedRule.appendRule(`\n${root.toResult().css}`)
      }
      return inserted[hash]
    }
  }

  return ComponentStyle
}
<|endoftext|>


<commit_before>export default class ErrorBag {
  constructor() {
    this.errors = [];
  }

    /**
     * Adds an error to the internal array.
     *
     * @param {string} field The field name.
     * @param {string} msg The error message.
     * @param {String} rule The rule that is responsible for the error.
     * @param {String} scope The Scope name, optional.
     */
  add(field, msg, rule, scope = '__global__') {
    this.errors.push({ field, msg, rule, scope });
  }

    /**
     * Gets all error messages from the internal array.
     *
     * @param {String} scope The Scope name, optional.
     * @return {Array} errors Array of all error messages.
     */
  all(scope) {
    if (! scope) {
      return this.errors.map(e => e.msg);
    }

    return this.errors.filter(e => e.scope === scope).map(e => e.msg);
  }

    /**
     * Checks if there are any errors in the internal array.
     * @param {String} scope The Scope name, optional.
     * @return {boolean} result True if there was at least one error, false otherwise.
     */
  any(scope) {
    if (! scope) {
      return !! this.errors.length;
    }

    return !! this.errors.filter(e => e.scope === scope).length;
  }

    /**
     * Removes all items from the internal array.
     *
     * @param {String} scope The Scope name, optional.
     */
  clear(scope) {
    if (! scope) {
      scope = '__global__';
    }

    const removeCondition = e => e.scope === scope;

    for (let i = 0; i < this.errors.length; ++i) {
      if (removeCondition(this.errors[i])) {
        this.errors.splice(i, 1);
        i--;
      }
    }
  }

    /**
     * Collects errors into groups or for a specific field.
     *
     * @param  {string} field The field name.
     * @param  {string} scope The scope name.
     * @param {Boolean} map If it should map the errors to strings instead of objects.
     * @return {Array} errors The errors for the specified field.
     */
  collect(field, scope, map = true) {
    if (! field) {
      const collection = {};
      this.errors.forEach(e => {
        if (! collection[e.field]) {
          collection[e.field] = [];
        }

        collection[e.field].push(map ? e.msg : e);
      });

      return collection;
    }

    if (! scope) {
      return this.errors.filter(e => e.field === field).map(e => (map ? e.msg : e));
    }

    return this.errors.filter(e => e.field === field && e.scope === scope)
                      .map(e => (map ? e.msg : e));
  }
    /**
     * Gets the internal array length.
     *
     * @return {Number} length The internal array length.
     */
  count() {
    return this.errors.length;
  }

    /**
     * Gets the first error message for a specific field.
     *
     * @param  {string} field The field name.
     * @return {string|null} message The error message.
     */
  first(field, scope = '__global__') {
    const selector = this._selector(field);
    const scoped = this._scope(field);

    if (scoped) {
      const result = this.first(scoped.name, scoped.scope);
      // if such result exist, return it. otherwise it could be a field.
      // with dot in its name.
      if (result) {
        return result;
      }
    }

    if (selector) {
      return this.firstByRule(selector.name, selector.rule, scope);
    }

    for (let i = 0; i < this.errors.length; i++) {
      if (this.errors[i].field === field && (this.errors[i].scope === scope)) {
        return this.errors[i].msg;
      }
    }

    return null;
  }

    /**
     * Returns the first error rule for the specified field
     *
     * @param {string} field The specified field.
     * @return {string|null} First error rule on the specified field if one is found, otherwise null
     */
  firstRule(field, scope) {
    const errors = this.collect(field, scope, false);

    return (errors.length && errors[0].rule) || null;
  }

    /**
     * Checks if the internal array has at least one error for the specified field.
     *
     * @param  {string} field The specified field.
     * @return {Boolean} result True if at least one error is found, false otherwise.
     */
  has(field, scope = '__global__') {
    return !! this.first(field, scope);
  }

    /**
     * Gets the first error message for a specific field and a rule.
     * @param {String} name The name of the field.
     * @param {String} rule The name of the rule.
     * @param {String} scope The name of the scope (optional).
     */
  firstByRule(name, rule, scope) {
    const error = this.collect(name, scope, false).filter(e => e.rule === rule)[0];

    return (error && error.msg) || null;
  }

    /**
     * Removes all error messages associated with a specific field.
     *
     * @param  {string} field The field which messages are to be removed.
     * @param {String} scope The Scope name, optional.
     */
  remove(field, scope) {
    const removeCondition = scope ? (e => e.field === field && e.scope === scope) :
                                    (e => e.field === field && e.scope === '__global__');

    for (let i = 0; i < this.errors.length; ++i) {
      if (removeCondition(this.errors[i])) {
        this.errors.splice(i, 1);
        i--;
      }
    }
  }

    /**
     * Get the field attributes if there's a rule selector.
     *
     * @param  {string} field The specified field.
     * @return {Object|null}
     */
  _selector(field) {
    if (field.indexOf(':') > -1) {
      const [name, rule] = field.split(':');

      return { name, rule };
    }

    return null;
  }

    /**
     * Get the field scope if specified using dot notation.
     *
     * @param {string} field the specifie field.
     * @return {Object|null}
     */
  _scope(field) {
    if (field.indexOf('.') > -1) {
      const [scope, name] = field.split('.');

      return { name, scope };
    }

    return null;
  }
}
<commit_msg>Consistent use of arithmetic operators in errorBag<commit_after>export default class ErrorBag {
  constructor() {
    this.errors = [];
  }

    /**
     * Adds an error to the internal array.
     *
     * @param {string} field The field name.
     * @param {string} msg The error message.
     * @param {String} rule The rule that is responsible for the error.
     * @param {String} scope The Scope name, optional.
     */
  add(field, msg, rule, scope = '__global__') {
    this.errors.push({ field, msg, rule, scope });
  }

    /**
     * Gets all error messages from the internal array.
     *
     * @param {String} scope The Scope name, optional.
     * @return {Array} errors Array of all error messages.
     */
  all(scope) {
    if (! scope) {
      return this.errors.map(e => e.msg);
    }

    return this.errors.filter(e => e.scope === scope).map(e => e.msg);
  }

    /**
     * Checks if there are any errors in the internal array.
     * @param {String} scope The Scope name, optional.
     * @return {boolean} result True if there was at least one error, false otherwise.
     */
  any(scope) {
    if (! scope) {
      return !! this.errors.length;
    }

    return !! this.errors.filter(e => e.scope === scope).length;
  }

    /**
     * Removes all items from the internal array.
     *
     * @param {String} scope The Scope name, optional.
     */
  clear(scope) {
    if (! scope) {
      scope = '__global__';
    }

    const removeCondition = e => e.scope === scope;

    for (let i = 0; i < this.errors.length; ++i) {
      if (removeCondition(this.errors[i])) {
        this.errors.splice(i, 1);
        --i;
      }
    }
  }

    /**
     * Collects errors into groups or for a specific field.
     *
     * @param  {string} field The field name.
     * @param  {string} scope The scope name.
     * @param {Boolean} map If it should map the errors to strings instead of objects.
     * @return {Array} errors The errors for the specified field.
     */
  collect(field, scope, map = true) {
    if (! field) {
      const collection = {};
      this.errors.forEach(e => {
        if (! collection[e.field]) {
          collection[e.field] = [];
        }

        collection[e.field].push(map ? e.msg : e);
      });

      return collection;
    }

    if (! scope) {
      return this.errors.filter(e => e.field === field).map(e => (map ? e.msg : e));
    }

    return this.errors.filter(e => e.field === field && e.scope === scope)
                      .map(e => (map ? e.msg : e));
  }
    /**
     * Gets the internal array length.
     *
     * @return {Number} length The internal array length.
     */
  count() {
    return this.errors.length;
  }

    /**
     * Gets the first error message for a specific field.
     *
     * @param  {string} field The field name.
     * @return {string|null} message The error message.
     */
  first(field, scope = '__global__') {
    const selector = this._selector(field);
    const scoped = this._scope(field);

    if (scoped) {
      const result = this.first(scoped.name, scoped.scope);
      // if such result exist, return it. otherwise it could be a field.
      // with dot in its name.
      if (result) {
        return result;
      }
    }

    if (selector) {
      return this.firstByRule(selector.name, selector.rule, scope);
    }

    for (let i = 0; i < this.errors.length; ++i) {
      if (this.errors[i].field === field && (this.errors[i].scope === scope)) {
        return this.errors[i].msg;
      }
    }

    return null;
  }

    /**
     * Returns the first error rule for the specified field
     *
     * @param {string} field The specified field.
     * @return {string|null} First error rule on the specified field if one is found, otherwise null
     */
  firstRule(field, scope) {
    const errors = this.collect(field, scope, false);

    return (errors.length && errors[0].rule) || null;
  }

    /**
     * Checks if the internal array has at least one error for the specified field.
     *
     * @param  {string} field The specified field.
     * @return {Boolean} result True if at least one error is found, false otherwise.
     */
  has(field, scope = '__global__') {
    return !! this.first(field, scope);
  }

    /**
     * Gets the first error message for a specific field and a rule.
     * @param {String} name The name of the field.
     * @param {String} rule The name of the rule.
     * @param {String} scope The name of the scope (optional).
     */
  firstByRule(name, rule, scope) {
    const error = this.collect(name, scope, false).filter(e => e.rule === rule)[0];

    return (error && error.msg) || null;
  }

    /**
     * Removes all error messages associated with a specific field.
     *
     * @param  {string} field The field which messages are to be removed.
     * @param {String} scope The Scope name, optional.
     */
  remove(field, scope) {
    const removeCondition = scope ? (e => e.field === field && e.scope === scope) :
                                    (e => e.field === field && e.scope === '__global__');

    for (let i = 0; i < this.errors.length; ++i) {
      if (removeCondition(this.errors[i])) {
        this.errors.splice(i, 1);
        --i;
      }
    }
  }

    /**
     * Get the field attributes if there's a rule selector.
     *
     * @param  {string} field The specified field.
     * @return {Object|null}
     */
  _selector(field) {
    if (field.indexOf(':') > -1) {
      const [name, rule] = field.split(':');

      return { name, rule };
    }

    return null;
  }

    /**
     * Get the field scope if specified using dot notation.
     *
     * @param {string} field the specifie field.
     * @return {Object|null}
     */
  _scope(field) {
    if (field.indexOf('.') > -1) {
      const [scope, name] = field.split('.');

      return { name, scope };
    }

    return null;
  }
}
<|endoftext|>


<commit_before>const SekshiModule = require('../Module')
const Promise = require('promise')
const request = require('request')

function normalizeAnswer(a) {
  return a.toLowerCase().replace(/\s+/g, ' ')
}

export default class Trivia extends SekshiModule {

  constructor(sekshi, options) {
    this.author = 'WE ♥ KPOP'
    this.version = '0.0.0'
    this.description = 'Trivia!'

    super(sekshi, options)

    this.permissions = {
      trivia: sekshi.USERROLE.MANAGER,
      trivquit: sekshi.USERROLE.MANAGER,
      skipquestion: sekshi.USERROLE.BOUNCER,
      trivpoints: sekshi.USERROLE.BOUNCER
    }

    this.onChat = this.onChat.bind(this)
  }

  init() {
    this._questions = []
    this._currentQuestion = null
  }
  destroy() {
    if (this._running) {
      this.trivquit()
    }
    this._questions = []
  }

  defaultOptions() {
    return {
      data: 'https://spreadsheets.google.com/feeds/list/1agQloBvRb1zS3Kf9NPZVD9iI5hE-sdNw62adcna4HPE/od6/public/full?alt=json',
      roundDuration: 120,
      historySize: 100
    }
  }

  // Chat Commands
  trivia(user, test = false) {
    this._running = true
    this._history = []
    this._points = {}
    this.sekshi.sendChat(`[Trivia] @everyone ${user.username} started Trivia!`)
    this.sekshi.on(this.sekshi.CHAT, this.onChat)

    this._load()
      .then(questions => {
        this.sekshi.sendChat(`Loaded ${questions.length} questions. First question in 5 seconds!`)
        setTimeout(this.nextQuestion.bind(this), 5 * 1000)
      })
      .catch(e => console.error(e))
  }

  trivquit(user) {
    if (this._running) {
      this._running = false
      clearTimeout(this._timeout)
      clearTimeout(this._5stimeout)
      clearTimeout(this._nqtimeout)
      if (user) {
        this.sekshi.sendChat(`[Trivia] ${user.username} stopped Trivia.`)
      }
      this.sekshi.removeListener(this.sekshi.CHAT, this.onChat)
    }
  }

  trivpoints(user) {
    if (this._running) {
      let points = Object.keys(this._points).map(uid => {
        return { uid: uid, points: this._points[uid] }
      })
      points.sort((a, b) => a.points > b.points ? -1 : a.points < b.points ? 1 : 0)

      this.sekshi.sendChat('Current top points: ' + points.slice(0, 5).map((x, i) => {
        const u = this.sekshi.getUserByID(x.uid)
        return `#${i + 1} ${u.username} (${x.points})`
      }).join(', '))
    }
  }

  skipquestion(user) {
    if (this._running && this._currentQuestion) {
    }
  }

  // public api
  nextQuestion() {
    if (this._running) {
      let question
      do {
        question = this._questions[Math.floor(Math.random() * this._questions.length)]
      } while (this._history.indexOf(question) !== -1)

      this.askQuestion(question)
    }
  }

  askQuestion(question) {
    this.sekshi.sendChat(`[Trivia] From the "${question.category}" category: ${question.question}`)

    this._currentQuestion = question
    this._history.push(question)
    if (this._history.length > this.options.historySize) {
      this._history.shift()
    }

    this._timeout = setTimeout(this.notAnswered.bind(this), this.options.roundDuration * 1000)
  }

  showWinner(winner, answer) {
    this.sekshi.sendChat(`[Trivia] "${answer}" is correct! ${winner.username} wins the round. ` +
                         `Next question in 15 seconds!`)
    this.addPoints(winner)

    clearTimeout(this._timeout)
    this._currentQuestion = null

    this._5stimeout = setTimeout(() => { this.sekshi.sendChat(`[Trivia] 5 seconds...`) }, 10 * 1000)
    this._nqtimeout = setTimeout(this.nextQuestion.bind(this), 15 * 1000)
  }

  notAnswered() {
    this.sekshi.sendChat(`[Trivia] Nobody answered correctly. It was "${this._currentQuestion.answers[0]}", so I win this round~ ^o^/`)
    this.addPoints(this.sekshi.getSelf())
    this._currentQuestion = null

    this._nqtimeout = setTimeout(this.nextQuestion.bind(this), 5 * 1000)
  }

  addPoints(user, points = 1) {
    this._points[user.id] = (this._points[user.id] || 0) + points
  }

  onChat(msg) {
    if (this._running && this._currentQuestion) {
      const answer = normalizeAnswer(msg.message)
      if (this._currentQuestion.answers.indexOf(answer) !== -1) {
        const user = this.sekshi.getUserByID(msg.id)
        this.showWinner(user, msg.message)
      }
    }
  }

  addQuestion(question) {
    this._questions.push(question)
  }

  _load() {
    return new Promise((resolve, reject) => {
      request({ uri: this.options.data, json: true }, (e, _, body) => {
        if (e) reject(e)
        else {
          this._loadFromGoogleSheets(body)
          resolve(this._questions)
        }
      })
    })
  }
  _loadFromGoogleSheets(json) {
    const $t = obj => obj && obj.$t && obj.$t.trim()

    json.feed.entry.forEach(entry => {
      this.addQuestion(new Question(
        $t(entry.gsx$category),
        $t(entry.gsx$question),
        $t(entry.gsx$answer),
        $t(entry.gsx$altanswer1),
        $t(entry.gsx$altanswer2),
        $t(entry.gsx$altanswer3)
      ))
    })
  }

  _historySize() {
    return this.options.historySize === 'auto'
      ? 0.75 * this._questions.length
      : this.options.historySize
  }

}

class Question {
  constructor(category, question, ...answers) {
    this.category = category
    this.question = question
    this.answers = answers.filter(a => a).map(normalizeAnswer)
  }
}<commit_msg>wipwip<commit_after>const SekshiModule = require('../Module')
const Promise = require('promise')
const request = require('request')

function normalizeAnswer(a) {
  return a.toLowerCase().replace(/\s+/g, ' ')
}

export default class Trivia extends SekshiModule {

  constructor(sekshi, options) {
    this.author = 'WE ♥ KPOP'
    this.version = '0.0.0'
    this.description = ''

    super(sekshi, options)

    this.permissions = {
      trivia: sekshi.USERROLE.MANAGER,
      trivquit: sekshi.USERROLE.MANAGER,
      skipquestion: sekshi.USERROLE.BOUNCER,
      trivpoints: sekshi.USERROLE.BOUNCER
    }

    this.onChat = this.onChat.bind(this)
  }

  init() {
    this._questions = []
    this._currentQuestion = null
  }
  destroy() {
    if (this._running) {
      this.trivquit()
    }
    this._questions = []
  }

  defaultOptions() {
    return {
      data: 'https://spreadsheets.google.com/feeds/list/1agQloBvRb1zS3Kf9NPZVD9iI5hE-sdNw62adcna4HPE/od6/public/full?alt=json',
      roundDuration: 120,
      historySize: 100
    }
  }

  // Chat Commands
  trivia(user, test = false) {
    this.sekshi.sendChat(`[Trivia] @everyone ${user.username} started Trivia!`)
    this.startTrivia().then(() => {
      this.sekshi.sendChat(`Loaded ${this._questions.length} questions. First question in 5 seconds!`)
      setTimeout(this.nextQuestion.bind(this), 5 * 1000)
    })
  }

  trivquit(user) {
    if (this._running) {
      this._running = false
      clearTimeout(this._timeout)
      clearTimeout(this._5stimeout)
      clearTimeout(this._nqtimeout)
      if (user) {
        this.sekshi.sendChat(`[Trivia] ${user.username} stopped Trivia.`)
      }
      this.sekshi.removeListener(this.sekshi.CHAT, this.onChat)
    }
  }

  trivpoints(user) {
    if (this._running) {
      let points = Object.keys(this._points).map(uid => {
        return { uid: uid, points: this._points[uid] }
      })
      points.sort((a, b) => a.points > b.points ? -1 : a.points < b.points ? 1 : 0)

      this.sekshi.sendChat('Current top points: ' + points.slice(0, 5).map((x, i) => {
        const u = this.sekshi.getUserByID(x.uid)
        return `#${i + 1} ${u.username} (${x.points})`
      }).join(', '))
    }
  }

  skipquestion(user) {
    if (this._running && this._currentQuestion) {
    }
  }

  startTrivia() {
    this._running = true
    this._history = []
    this._points = {}
    this.sekshi.on(this.sekshi.CHAT, this.onChat)

    return this._load()
      .catch(e => console.error(e))
  }

  // public api
  nextQuestion() {
    if (this._running) {
      let question
      do {
        question = this._questions[Math.floor(Math.random() * this._questions.length)]
      } while (this._history.indexOf(question) !== -1)

      return this.askQuestion(question)
    }
    return Promise.reject(new Error('Trivia is not running'))
  }

  askQuestion(question) {
    this.sekshi.sendChat(`[Trivia] From the "${question.category}" category: ${question.question}`)

    this._currentQuestion = question
    this._history.push(question)
    if (this._history.length > this.options.historySize) {
      this._history.shift()
    }

    this._timeout = setTimeout(this.notAnswered.bind(this), this.options.roundDuration * 1000)
  }

  showWinner(winner, answer) {
    this.sekshi.sendChat(`[Trivia] "${answer}" is correct! ${winner.username} wins the round. ` +
                         `Next question in 15 seconds!`)
    this.addPoints(winner)

    clearTimeout(this._timeout)
    this._currentQuestion = null

    this._5stimeout = setTimeout(() => { this.sekshi.sendChat(`[Trivia] 5 seconds...`) }, 10 * 1000)
    this._nqtimeout = setTimeout(this.nextQuestion.bind(this), 15 * 1000)
  }

  notAnswered() {
    this.sekshi.sendChat(`[Trivia] Nobody answered correctly. It was "${this._currentQuestion.answers[0]}", so I win this round~ ^o^/`)
    this.addPoints(this.sekshi.getSelf())
    this._currentQuestion = null

    this._nqtimeout = setTimeout(this.nextQuestion.bind(this), 5 * 1000)
  }

  addPoints(user, points = 1) {
    this._points[user.id] = (this._points[user.id] || 0) + points
  }

  onChat(msg) {
    if (this._running && this._currentQuestion) {
      const answer = normalizeAnswer(msg.message)
      if (this._currentQuestion.answers.indexOf(answer) !== -1) {
        const user = this.sekshi.getUserByID(msg.id)
        this.showWinner(user, msg.message)
      }
    }
  }

  addQuestion(question) {
    this._questions.push(question)
  }

  _load() {
    return new Promise((resolve, reject) => {
      request({ uri: this.options.data, json: true }, (e, _, body) => {
        if (e) reject(e)
        else {
          this._loadFromGoogleSheets(body)
          resolve(this._questions)
        }
      })
    })
  }
  _loadFromGoogleSheets(json) {
    const $t = obj => obj && obj.$t && obj.$t.trim()

    json.feed.entry.forEach(entry => {
      this.addQuestion(new Question(
        $t(entry.gsx$category),
        $t(entry.gsx$question),
        $t(entry.gsx$answer),
        $t(entry.gsx$altanswer1),
        $t(entry.gsx$altanswer2),
        $t(entry.gsx$altanswer3)
      ))
    })
  }

  _historySize() {
    return this.options.historySize === 'auto'
      ? 0.75 * this._questions.length
      : this.options.historySize
  }

}

class Question {
  constructor(category, question, ...answers) {
    this.category = category
    this.question = question
    this.answers = answers.filter(a => a).map(normalizeAnswer)
  }
}<|endoftext|>


<commit_before>/* @flow */

import { bus, cache, config } from '../../core-server';
import winston from 'winston';
import { room as Room, roomrel as RoomRel } from '../../models/models';
import * as place from './place';
import * as constants from '../../lib/Constants';
import uuid from 'uuid';
import * as pg from '../../lib/pg';

/*
// postgres mock, because jest is acting up.

const pg = {
	read: (conn, sql, cb) => {
		setImmediate(() => cb(null, [
			{
				id: '5055f5b6-466e-46bc-a55d-fe020ee9ac42',
				name: 'Bangalore',
				identities: [ 'place:ChIJbU60yXAWrjsR4E9-UejD3_g' ]
			}
		]));
	}
};

// */

function addRooms(change, addable) {
	for (const stub of addable) {
		if (stub.id) { /* already in db */ continue; }

		stub.id = uuid.v4();

		change[stub.id] = new Room({
			id: stub.id,
			name: stub.name,
			tags: [ stub.type ],
			identities: [ stub.identity ]
		});
	}
}

function addRels(change, user, addable) {
	for (const stub of addable) {
		const rel = new RoomRel({
			user: user.id,
			item: stub.id,
			tags: stub.rels,
			roles: [ constants.ROLE_FOLLOWER ],
			createTime: Date.now()
		});

		change[rel.id] = rel;
	}
}

function removeRels(change, removable) {
	for (const rel of removable) {
		change[rel.id] = { roles: [] };
	}
}

function sendInvitations ([ user, relRooms, ...stubsets ]) {
	const stubs = {}, changedRels = {},
		all = [], addable = [], removable = [],
		change = {};

	for (const stubset of stubsets) {
		let rel;

		switch (stubset[0].identity.substr(6)) {
		case user.params.profile.home:
			rel = constants.TAG_REL_HOME;
			break;
		case user.params.profile.work:
			rel = constants.TAG_REL_WORK;
			break;
		case user.params.profile.hometown:
			rel = constants.TAG_REL_HOMETOWN;
			break;
		default:
			continue;
		}

		changedRels[rel] = true;

		for (const stub of stubset) {
			stub.rels = [ rel ];
			if (!stubs[stub.identity]) {
				stubs[stub.identity] = stub;
			} else {
				stubs[stub.identity].rels.push(rel);
			}
		}
	}

	for (const relRoom of relRooms) {
		const identity = relRoom.room.identities.filter(
			ident => ident.substr(0, 6) === 'place:'
		)[0];

		if (stubs[identity]) {
			stubs[identity].exits = true;
		} else {
			const type = relRoom.rel.tags.filter(tag =>
				tag >= constants.TAG_REL_HOME &&
				tag <= constants.TAG_REL_HOMETOWN
			)[0];

			if (changedRels[type]) {
				removable.push(relRoom.rel);
			} else {
				all.push({ identity, type, name: relRoom.room.name });
			}
		}
	}

	for (const identity in stubs) {
		const stub = stubs[identity];

		all.push(stub);
		if (!stub.exists) { addable.push(stub); }
	}

	// TODO: Find multi-criteria rooms.
	// TODO: Filter addable rooms by creatability.
	// TODO: Create addable rooms that don’t exist.

	pg.read(config.connStr, {
		$: 'SELECT * FROM "rooms" WHERE identities && &{idents}',
		idents: addable.map(a => a.iddentity)
	}, (err, rooms) => {
		if (err) { winston.error(err); return; }
		for (let room of rooms) {
			room = new Room(room);

			const stub = stubs[room.identities.filter(
				ident => ident.substr(0, 6) === 'place:'
			)[0]];

			if (stub) stub.id = room.id;
		}

		addRooms(change, addable);
		addRels(change, user, addable);
		removeRels(change, removable);

		bus.emit('change', change);
	});
}

bus.on('change', change => {

	/* While the work of this module is asynchronous, it will allow
	the change to continue immediately and emit a new change when the
	work is complete. */

	if (change.entities) { for (const id in change.entities) {
		const user = change.entities[id],
			promises = [ user ];

		if (
			user.type !== constants.TYPE_USER ||
			!user.params || !user.params.profile
		) { continue; }

		/* Fetch the current rooms of this user. */
		promises.push(new Promise((resolve, reject) => {
			cache.query({
				type: 'rel',
				link: { room: 'room' },
				filter: { user: id, roleGte: constants.ROLE_FOLLOWER },
				order: 'roleTime'
			}, [ -Infinity, Infinity ], (err, results) => {
				if (err) { reject(err); return; }
				resolve(results);
			});
		}));

		if (user.params.profile.home) {
			promises.push(place.getStubset(user.params.profile.home));
		}

		if (user.params.profile.work) {
			promises.push(place.getStubset(user.params.profile.work));
		}

		if (user.params.profile.hometown) {
			promises.push(place.getStubset(user.params.profile.hometown));
		}

		Promise.all(promises)
		.then(sendInvitations)
		.catch(err => winston.error(err));
	} }
});
<commit_msg>Add source:belong to invite changeset<commit_after>/* @flow */

import { bus, cache, config } from '../../core-server';
import winston from 'winston';
import { room as Room, roomrel as RoomRel } from '../../models/models';
import * as place from './place';
import * as constants from '../../lib/Constants';
import uuid from 'uuid';
import * as pg from '../../lib/pg';

/*
// postgres mock, because jest is acting up.

const pg = {
	read: (conn, sql, cb) => {
		setImmediate(() => cb(null, [
			{
				id: '5055f5b6-466e-46bc-a55d-fe020ee9ac42',
				name: 'Bangalore',
				identities: [ 'place:ChIJbU60yXAWrjsR4E9-UejD3_g' ]
			}
		]));
	}
};

// */

function addRooms(change, addable) {
	for (const stub of addable) {
		if (stub.id) { /* already in db */ continue; }

		stub.id = uuid.v4();

		change[stub.id] = new Room({
			id: stub.id,
			name: stub.name,
			tags: [ stub.type ],
			identities: [ stub.identity ]
		});
	}
}

function addRels(change, user, addable) {
	for (const stub of addable) {
		const rel = new RoomRel({
			user: user.id,
			item: stub.id,
			tags: stub.rels,
			roles: [ constants.ROLE_FOLLOWER ],
			createTime: Date.now()
		});

		change[rel.id] = rel;
	}
}

function removeRels(change, removable) {
	for (const rel of removable) {
		change[rel.id] = { roles: [] };
	}
}

function sendInvitations ([ user, relRooms, ...stubsets ]) {
	const stubs = {}, changedRels = {},
		all = [], addable = [], removable = [],
		change = {};

	for (const stubset of stubsets) {
		let rel;

		switch (stubset[0].identity.substr(6)) {
		case user.params.profile.home:
			rel = constants.TAG_REL_HOME;
			break;
		case user.params.profile.work:
			rel = constants.TAG_REL_WORK;
			break;
		case user.params.profile.hometown:
			rel = constants.TAG_REL_HOMETOWN;
			break;
		default:
			continue;
		}

		changedRels[rel] = true;

		for (const stub of stubset) {
			stub.rels = [ rel ];
			if (!stubs[stub.identity]) {
				stubs[stub.identity] = stub;
			} else {
				stubs[stub.identity].rels.push(rel);
			}
		}
	}

	for (const relRoom of relRooms) {
		const identity = relRoom.room.identities.filter(
			ident => ident.substr(0, 6) === 'place:'
		)[0];

		if (stubs[identity]) {
			stubs[identity].exits = true;
		} else {
			const type = relRoom.rel.tags.filter(tag =>
				tag >= constants.TAG_REL_HOME &&
				tag <= constants.TAG_REL_HOMETOWN
			)[0];

			if (changedRels[type]) {
				removable.push(relRoom.rel);
			} else {
				all.push({ identity, type, name: relRoom.room.name });
			}
		}
	}

	for (const identity in stubs) {
		const stub = stubs[identity];

		all.push(stub);
		if (!stub.exists) { addable.push(stub); }
	}

	// TODO: Find multi-criteria rooms.
	// TODO: Filter addable rooms by creatability.
	// TODO: Create addable rooms that don’t exist.

	pg.read(config.connStr, {
		$: 'SELECT * FROM "rooms" WHERE identities && &{idents}',
		idents: addable.map(a => a.iddentity)
	}, (err, rooms) => {
		if (err) { winston.error(err); return; }
		for (let room of rooms) {
			room = new Room(room);

			const stub = stubs[room.identities.filter(
				ident => ident.substr(0, 6) === 'place:'
			)[0]];

			if (stub) stub.id = room.id;
		}

		addRooms(change, addable);
		addRels(change, user, addable);
		removeRels(change, removable);

		bus.emit('change', { entities: change, source: 'belong' });
	});
}

bus.on('change', change => {

	/* While the work of this module is asynchronous, it will allow
	the change to continue immediately and emit a new change when the
	work is complete. */

	if (change.entities) { for (const id in change.entities) {
		const user = change.entities[id],
			promises = [ user ];

		if (
			user.type !== constants.TYPE_USER ||
			!user.params || !user.params.profile
		) { continue; }

		/* Fetch the current rooms of this user. */
		promises.push(new Promise((resolve, reject) => {
			cache.query({
				type: 'rel',
				link: { room: 'room' },
				filter: { user: id, roleGte: constants.ROLE_FOLLOWER },
				order: 'roleTime'
			}, [ -Infinity, Infinity ], (err, results) => {
				if (err) { reject(err); return; }
				resolve(results);
			});
		}));

		if (user.params.profile.home) {
			promises.push(place.getStubset(user.params.profile.home));
		}

		if (user.params.profile.work) {
			promises.push(place.getStubset(user.params.profile.work));
		}

		if (user.params.profile.hometown) {
			promises.push(place.getStubset(user.params.profile.hometown));
		}

		Promise.all(promises)
		.then(sendInvitations)
		.catch(err => winston.error(err));
	} }
});
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import {
  FlatList,
  StyleSheet,
  View,
  Text,
  Dimensions,
  TouchableHighlight
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import axios from 'axios';
import FastImage from 'react-native-fast-image';

import Loading from '../../wdui/loading/Loading';
import Card from '../../wdui/Card';

const deviceWidth = Dimensions.get('window').width;

class HomeView extends Component {
  static displayName = 'HomeView';

  static navigationOptions = {
    title: '首页推荐',
    tabBarIcon: (props) => (
        <Icon name='home' size={24} color={props.tintColor} />
      )
  };

  static propTypes = {
    navigate: PropTypes.func.isRequired
  };

  constructor() {
    super();
    this.state = {
      loading: true,
      nextLink: null,
      noMore: false,
      data: []
    };
  }

  componentWillMount() {
    axios.get('https://www.wandianshenme.com/api/play/')
      .then(response => {
        this.setState({
          data: response.data,
          plays: response.data.results,
          nextLink: response.data.next ? response.data.next : null,
          loading: false
        });
      });
  }

  onEndReached() {
    if (!this.state.nextLink) {
      this.setState({
        noMore: true
      });
      return;
    }
    axios.get(this.state.nextLink)
      .then(response => {
        this.setState({
          data: response.data,
          plays: this.state.plays.concat(response.data.results),
          nextLink: response.data.next ? response.data.next : null,
          loading: false
        });
      });
  }

  keyExtractor = (item, index) => `key${index}`;

  renderList = ({item}) => {
    console.log('https://www.wandianshenme.com/static/media/' + item.featured_image);
    return (
      <TouchableHighlight
        key={this.keyExtractor}>
        <View>
          <Card
            title={ item.title }>
            <View style={{paddingBottom: 15}}>
              { item.featured_image
                ? <FastImage
                  style={{width: deviceWidth - 60, height: (deviceWidth - 60) * 0.4}}
                  resizeMode={FastImage.resizeMode.cover}
                  source={{
                    uri: 'https://www.wandianshenme.com/static/media/' + item.featured_image
                  }}
                />
                : <Text>{ item.featured_image }</Text>
              }
            </View>

            <Text style={{marginBottom: 10}}>
              { item.description }
            </Text>
          </Card>
        </View>
      </TouchableHighlight>
    );
  };

  render() {
    if (this.state.loading) {
      return <Loading text={'数据加载中'} />;
    }

    return (
      <View style={styles.container}>
        <View>
          <FlatList
            keyExtractor={this.keyExtractor}
            data={this.state.plays}
            renderItem={this.renderList}
            onEndReached={this.onEndReached.bind(this)}
          />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 0,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white'
  }
});

export default HomeView;
<commit_msg>[HOME] add pull to refreshing<commit_after>import React, {Component, PropTypes} from 'react';
import {
  FlatList,
  StyleSheet,
  View,
  Text,
  Dimensions,
  TouchableHighlight
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import axios from 'axios';
import FastImage from 'react-native-fast-image';

import Loading from '../../wdui/loading/Loading';
import Card from '../../wdui/Card';

const deviceWidth = Dimensions.get('window').width;

class HomeView extends Component {
  static displayName = 'HomeView';

  static navigationOptions = {
    title: '首页推荐',
    tabBarIcon: (props) => (
        <Icon name='home' size={24} color={props.tintColor} />
      )
  };

  static propTypes = {
    navigate: PropTypes.func.isRequired
  };

  constructor() {
    super();
    this.state = {
      loading: true,
      nextLink: null,
      noMore: false,
      isRefreshing: false,
      data: []
    };
  }

  componentWillMount() {
    this.fetchData();
  }

  onRefresh() {
    this.setState({
      isRefreshing: true
    });
    this.fetchData();
  }

  fetchData() {
    axios.get('https://www.wandianshenme.com/api/play/')
      .then(response => {
        this.setState({
          data: response.data,
          plays: response.data.results,
          nextLink: response.data.next ? response.data.next : null,
          isRefreshing: false,
          loading: false
        });
      });
  }

  onEndReached() {
    if (!this.state.nextLink) {
      this.setState({
        noMore: true
      });
      return;
    }
    axios.get(this.state.nextLink)
      .then(response => {
        this.setState({
          data: response.data,
          plays: this.state.plays.concat(response.data.results),
          nextLink: response.data.next ? response.data.next : null,
          loading: false
        });
      });
  }

  keyExtractor = (item, index) => `key${index}`;

  renderList = ({item}) => {
    return (
      <TouchableHighlight
        key={this.keyExtractor}>
        <View>
          <Card
            title={ item.title }>
            <View style={{paddingBottom: 15}}>
              { item.featured_image
                ? <FastImage
                  style={{width: deviceWidth - 60, height: (deviceWidth - 60) * 0.4}}
                  resizeMode={FastImage.resizeMode.cover}
                  source={{
                    uri: 'https://www.wandianshenme.com/static/media/' + item.featured_image
                  }}
                />
                : <Text>{ item.featured_image }</Text>
              }
            </View>

            <Text style={{marginBottom: 10}}>
              { item.description }
            </Text>
          </Card>
        </View>
      </TouchableHighlight>
    );
  };

  render() {
    if (this.state.loading) {
      return <Loading text={'数据加载中'} />;
    }

    return (
      <View style={styles.container}>
        <View>
          <FlatList
            keyExtractor={this.keyExtractor}
            data={this.state.plays}
            refreshing={this.state.isRefreshing}
            renderItem={this.renderList}
            onRefresh={this.onRefresh.bind(this)}
            onEndReached={this.onEndReached.bind(this)}
          />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 0,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white'
  }
});

export default HomeView;
<|endoftext|>