<commit_before>/*
 ---

 name: Eyes

 description: The main type - to be used by the users of the library to access all functionality.

 provides: [Eyes]
 requires: [eyes.sdk, ElementFinderWrapper, ViewportSize, protractor]

 ---
 */

(function() {
    'use strict';

    var EyesSDK = require('eyes.sdk');
    var EyesBase = EyesSDK.EyesBase;
    var ViewportSize = require('./ViewportSize');
    var promise = require('protractor').promise;
    var EyesUtils = require('eyes.utils');
    var PromiseFactory = EyesUtils.PromiseFactory;
    var BrowserUtils = EyesUtils.BrowserUtils;
    var ElementFinderWrapper = require('./ElementFinderWrappers').ElementFinderWrapper;
    var ElementArrayFinderWrapper = require('./ElementFinderWrappers').ElementArrayFinderWrapper;

    /**
     *
     * @param {String} serverUrl
     * @param {Boolean} isDisabled - set to true to disable Applitools Eyes and use the protractor webdriver directly.
     * @constructor
     **/
    function Eyes(serverUrl, isDisabled) {
        this._forceFullPage = false;
        this._imageRotationDegrees = 0;
        this._automaticRotation = true;
        this._isLandscape = false;
        this._hideScrollbars = false;
        this._stitchMode = Eyes.StitchMode.Scroll;
        this._promiseFactory = new PromiseFactory();
        EyesBase.call(this, this._promiseFactory, serverUrl || EyesBase.DEFAULT_EYES_SERVER, isDisabled);
    }

    Eyes.prototype = new EyesBase();
    Eyes.prototype.constructor = Eyes;

    Eyes.StitchMode = Object.freeze({
        // Uses scrolling to get to the different parts of the page.
        Scroll: 'Scroll',
        // Uses CSS transitions to get to the different parts of the page.
        CSS: 'CSS'
    });

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype._getBaseAgentId = function() {
        return 'eyes-protractor/0.0.45';
    };

    function _init(that, flow, isDisabled) {
        // extend protractor element to return ours
        if (!isDisabled) {
            var originalElementFn = global.element;
            global.element = function(locator) {
                return new ElementFinderWrapper(originalElementFn(locator), that, that._logger);
            };

            global.element.all = function(locator) {
                return new ElementArrayFinderWrapper(originalElementFn.all(locator), that, that._logger);
            };
        }
        // Set PromiseFactory to work with the protractor control flow and promises
        that._promiseFactory.setFactoryMethods(function(asyncAction) {
            return flow.execute(function() {
                var deferred = promise.defer();
                asyncAction(deferred.fulfill, deferred.reject);
                return deferred.promise;
            });
        }, function() {
            return promise.defer();
        });
    }

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.open = function(driver, appName, testName, viewportSize) {
        var that = this;
        var flow = that._flow = driver.controlFlow();
        _init(that, flow, this._isDisabled);

        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }

        that._driver = driver;

        return flow.execute(function() {
            return that._driver.getCapabilities()
                .then(function(capabilities) {
                    var platformName = capabilities.caps_.platformName;
                    var platformVersion = capabilities.caps_.platformVersion;
                    var orientation = capabilities.caps_.orientation || capabilities.caps_.deviceOrientation;

                    var majorVersion;
                    if (!platformVersion || platformVersion.length < 1) {
                        return;
                    }
                    majorVersion = platformVersion.split('.', 2)[0];
                    if (platformName.toUpperCase() === 'ANDROID') {
                        if (!that.getHostOS()) {
                            that.setHostOS('Android ' + majorVersion);
                        }
                    } else if (platformName.toUpperCase() === 'IOS') {
                        if (!that.getHostOS()) {
                            that.setHostOS('iOS ' + majorVersion);
                        }
                    } else {
                        return;
                    }

                    if (orientation && orientation.toUpperCase() === 'LANDSCAPE') {
                        that._isLandscape = true;
                    }
                })
                .then(function() {
                    return EyesBase.prototype.open.call(that, appName, testName, viewportSize);
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.close = function(throwEx) {
        var that = this;

        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        if (throwEx === undefined) {
            throwEx = true;
        }

        return that._flow.execute(function() {
            return EyesBase.prototype.close.call(that, false)
                .then(function(results) {
                    if (results.isPassed || !throwEx) {
                        return results;
                    } else {
                        throw EyesBase.buildTestError(results, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.checkWindow = function(tag, matchTimeout) {
        var that = this;
        if (that._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout)
                .then(function(result) {
                    if (result.asExpected || !that._failureReportOverridden) {
                        return result;
                    } else {
                        throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {Object} region The region to validate (in screenshot coordinates).
     *                          Object is {width: *, height: *, top: *, left: *}
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Object} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegion = function(region, tag, matchTimeout) {
        var that = this;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                .then(function(result) {
                    if (result.asExpected || !that._failureReportOverridden) {
                        return result;
                    } else {
                        throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {Object} element The element defining the region to validate.
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Object} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegionByElement = function(element, tag, matchTimeout) {
        var that = this;
        var size;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return element.getSize()
                .then(function(elementSize) {
                    size = elementSize;
                    return element.getLocation();
                })
                .then(function(point) {
                    var region = {height: size.height, width: size.width, left: point.x, top: point.y, relative: true};
                    return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                        .then(function(result) {
                            if (result.asExpected || !that._failureReportOverridden) {
                                return result;
                            } else {
                                throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                                    that._sessionStartInfo.appIdOrName);
                            }
                        });
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {By} by The WebDriver selector used for finding the region to validate.
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Promise} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegionBy = function(by, tag, matchTimeout) {
        var that = this;
        var element;
        var size;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return that._driver.findElement(by)
                .then(function(elem) {
                    element = elem;
                    return element.getSize();
                })
                .then(function(elementSize) {
                    size = elementSize;
                    return element.getLocation();
                })
                .then(function(point) {
                    var region = {height: size.height, width: size.width, left: point.x, top: point.y, relative: true};
                    return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                        .then(function(result) {
                            if (result.asExpected || !that._failureReportOverridden) {
                                return result;
                            } else {
                                throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                                    that._sessionStartInfo.appIdOrName);
                            }
                        });
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype._waitTimeout = function(ms) {
        return this._flow.timeout(ms);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getScreenShot = function() {
        return BrowserUtils.getScreenshot(this._driver, this._promiseFactory, this._viewportSize, this._forceFullPage,
            this._hideScrollbars, this._stitchMode === Eyes.StitchMode.CSS, this._imageRotationDegrees,
            this._automaticRotation, this._os === 'Android' ? 90 : 270, this._isLandscape);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getTitle = function() {
        return this._driver.getTitle();
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getInferredEnvironment = function() {
        var res = 'useragent:';
        return this._driver.executeScript('return navigator.userAgent')
            .then(function(userAgent) {
                return res + userAgent;
            }, function() {
                return res;
            });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @param mode Use one of the values in EyesBase.FailureReport.
     */
    Eyes.prototype.setFailureReport = function(mode) {
        if (mode === EyesBase.FailureReport.Immediate) {
            this._failureReportOverridden = true;
            mode = EyesBase.FailureReport.OnClose;
        }

        EyesBase.prototype.setFailureReport.call(this, mode);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getViewportSize = function() {
        return ViewportSize.getViewportSize(this._driver, this._promiseFactory);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setViewportSize = function(size) {
        return ViewportSize.setViewportSize(this._driver, size, this._promiseFactory);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setForceFullPageScreenshot = function(force) {
        this._forceFullPage = force;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getForceFullPageScreenshot = function() {
        return this._forceFullPage;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setForcedImageRotation = function(degrees) {
        if (typeof degrees != 'number') {
            throw new TypeError('degrees must be a number! set to 0 to clear');
        }
        this._imageRotationDegrees = degrees;
        this._automaticRotation = false;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getForcedImageRotation = function() {
        return this._imageRotationDegrees || 0;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setHideScrollbars = function(hide) {
        this._hideScrollbars = hide;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getHideScrollbars = function() {
        return this._hideScrollbars;
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @param mode Use one of the values in Eyes.StitchMode.
     */
    Eyes.prototype.setStitchMode = function(mode) {
        switch (mode) {
            case Eyes.StitchMode.Scroll:
                this._stitchMode = Eyes.StitchMode.Scroll;
                break;
            case Eyes.StitchMode.CSS:
                this._stitchMode = Eyes.StitchMode.CSS;
                break;
            default:
                this._stitchMode = Eyes.StitchMode.Scroll;
                break;
        }
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @return {Eyes.StitchMode} The currently set StitchMode.
     */
    Eyes.prototype.getStitchMode = function() {
        return this._stitchMode;
    };

    module.exports = Eyes;
}());
<commit_msg>Inline documentation.<commit_after>/*
 ---

 name: Eyes

 description: The main type - to be used by the users of the library to access all functionality.

 provides: [Eyes]
 requires: [eyes.sdk, ElementFinderWrapper, ViewportSize, protractor]

 ---
 */

(function() {
    'use strict';

    var EyesSDK = require('eyes.sdk');
    var EyesBase = EyesSDK.EyesBase;
    var ViewportSize = require('./ViewportSize');
    var promise = require('protractor').promise;
    var EyesUtils = require('eyes.utils');
    var PromiseFactory = EyesUtils.PromiseFactory;
    var BrowserUtils = EyesUtils.BrowserUtils;
    var ElementFinderWrapper = require('./ElementFinderWrappers').ElementFinderWrapper;
    var ElementArrayFinderWrapper = require('./ElementFinderWrappers').ElementArrayFinderWrapper;

    /**
     *
     * @param {String} serverUrl
     * @param {Boolean} isDisabled - set to true to disable Applitools Eyes and use the protractor webdriver directly.
     * @constructor
     **/
    function Eyes(serverUrl, isDisabled) {
        this._forceFullPage = false;
        this._imageRotationDegrees = 0;
        this._automaticRotation = true;
        this._isLandscape = false;
        this._hideScrollbars = false;
        this._stitchMode = Eyes.StitchMode.Scroll;
        this._promiseFactory = new PromiseFactory();
        EyesBase.call(this, this._promiseFactory, serverUrl || EyesBase.DEFAULT_EYES_SERVER, isDisabled);
    }

    Eyes.prototype = new EyesBase();
    Eyes.prototype.constructor = Eyes;

    Eyes.StitchMode = Object.freeze({
        // Uses scrolling to get to the different parts of the page.
        Scroll: 'Scroll',
        // Uses CSS transitions to get to the different parts of the page.
        CSS: 'CSS'
    });

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype._getBaseAgentId = function() {
        return 'eyes-protractor/0.0.45';
    };

    function _init(that, flow, isDisabled) {
        // extend protractor element to return ours
        if (!isDisabled) {
            var originalElementFn = global.element;
            global.element = function(locator) {
                return new ElementFinderWrapper(originalElementFn(locator), that, that._logger);
            };

            global.element.all = function(locator) {
                return new ElementArrayFinderWrapper(originalElementFn.all(locator), that, that._logger);
            };
        }
        // Set PromiseFactory to work with the protractor control flow and promises
        that._promiseFactory.setFactoryMethods(function(asyncAction) {
            return flow.execute(function() {
                var deferred = promise.defer();
                asyncAction(deferred.fulfill, deferred.reject);
                return deferred.promise;
            });
        }, function() {
            return promise.defer();
        });
    }

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.open = function(driver, appName, testName, viewportSize) {
        var that = this;
        var flow = that._flow = driver.controlFlow();
        _init(that, flow, this._isDisabled);

        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }

        that._driver = driver;

        return flow.execute(function() {
            return that._driver.getCapabilities()
                .then(function(capabilities) {
                    var platformName = capabilities.caps_.platformName;
                    var platformVersion = capabilities.caps_.platformVersion;
                    var orientation = capabilities.caps_.orientation || capabilities.caps_.deviceOrientation;

                    var majorVersion;
                    if (!platformVersion || platformVersion.length < 1) {
                        return;
                    }
                    majorVersion = platformVersion.split('.', 2)[0];
                    if (platformName.toUpperCase() === 'ANDROID') {
                        // We only automatically set the OS, if the user hadn't manually set it previously.
                        if (!that.getHostOS()) {
                            that.setHostOS('Android ' + majorVersion);
                        }
                    } else if (platformName.toUpperCase() === 'IOS') {
                        if (!that.getHostOS()) {
                            that.setHostOS('iOS ' + majorVersion);
                        }
                    } else {
                        return;
                    }

                    if (orientation && orientation.toUpperCase() === 'LANDSCAPE') {
                        that._isLandscape = true;
                    }
                })
                .then(function() {
                    return EyesBase.prototype.open.call(that, appName, testName, viewportSize);
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.close = function(throwEx) {
        var that = this;

        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        if (throwEx === undefined) {
            throwEx = true;
        }

        return that._flow.execute(function() {
            return EyesBase.prototype.close.call(that, false)
                .then(function(results) {
                    if (results.isPassed || !throwEx) {
                        return results;
                    } else {
                        throw EyesBase.buildTestError(results, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.checkWindow = function(tag, matchTimeout) {
        var that = this;
        if (that._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout)
                .then(function(result) {
                    if (result.asExpected || !that._failureReportOverridden) {
                        return result;
                    } else {
                        throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {Object} region The region to validate (in screenshot coordinates).
     *                          Object is {width: *, height: *, top: *, left: *}
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Object} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegion = function(region, tag, matchTimeout) {
        var that = this;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                .then(function(result) {
                    if (result.asExpected || !that._failureReportOverridden) {
                        return result;
                    } else {
                        throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                            that._sessionStartInfo.appIdOrName);
                    }
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {Object} element The element defining the region to validate.
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Object} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegionByElement = function(element, tag, matchTimeout) {
        var that = this;
        var size;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return element.getSize()
                .then(function(elementSize) {
                    size = elementSize;
                    return element.getLocation();
                })
                .then(function(point) {
                    var region = {height: size.height, width: size.width, left: point.x, top: point.y, relative: true};
                    return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                        .then(function(result) {
                            if (result.asExpected || !that._failureReportOverridden) {
                                return result;
                            } else {
                                throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                                    that._sessionStartInfo.appIdOrName);
                            }
                        });
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     * Visually validates a region in the screenshot.
     *
     * @param {By} by The WebDriver selector used for finding the region to validate.
     * @param {string} tag An optional tag to be associated with the screenshot.
     * @param {int} matchTimeout The amount of time to retry matching.
     * @return {Promise} A promise which is resolved when the validation is finished.
     */
    Eyes.prototype.checkRegionBy = function(by, tag, matchTimeout) {
        var that = this;
        var element;
        var size;
        if (this._isDisabled) {
            return that._flow.execute(function() {
            });
        }
        return that._flow.execute(function() {
            return that._driver.findElement(by)
                .then(function(elem) {
                    element = elem;
                    return element.getSize();
                })
                .then(function(elementSize) {
                    size = elementSize;
                    return element.getLocation();
                })
                .then(function(point) {
                    var region = {height: size.height, width: size.width, left: point.x, top: point.y, relative: true};
                    return EyesBase.prototype.checkWindow.call(that, tag, false, matchTimeout, region)
                        .then(function(result) {
                            if (result.asExpected || !that._failureReportOverridden) {
                                return result;
                            } else {
                                throw EyesBase.buildTestError(result, that._sessionStartInfo.scenarioIdOrName,
                                    that._sessionStartInfo.appIdOrName);
                            }
                        });
                });
        });
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype._waitTimeout = function(ms) {
        return this._flow.timeout(ms);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getScreenShot = function() {
        return BrowserUtils.getScreenshot(this._driver, this._promiseFactory, this._viewportSize, this._forceFullPage,
            this._hideScrollbars, this._stitchMode === Eyes.StitchMode.CSS, this._imageRotationDegrees,
            this._automaticRotation, this._os === 'Android' ? 90 : 270, this._isLandscape);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getTitle = function() {
        return this._driver.getTitle();
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getInferredEnvironment = function() {
        var res = 'useragent:';
        return this._driver.executeScript('return navigator.userAgent')
            .then(function(userAgent) {
                return res + userAgent;
            }, function() {
                return res;
            });
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @param mode Use one of the values in EyesBase.FailureReport.
     */
    Eyes.prototype.setFailureReport = function(mode) {
        if (mode === EyesBase.FailureReport.Immediate) {
            this._failureReportOverridden = true;
            mode = EyesBase.FailureReport.OnClose;
        }

        EyesBase.prototype.setFailureReport.call(this, mode);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getViewportSize = function() {
        return ViewportSize.getViewportSize(this._driver, this._promiseFactory);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setViewportSize = function(size) {
        return ViewportSize.setViewportSize(this._driver, size, this._promiseFactory);
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setForceFullPageScreenshot = function(force) {
        this._forceFullPage = force;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getForceFullPageScreenshot = function() {
        return this._forceFullPage;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setForcedImageRotation = function(degrees) {
        if (typeof degrees != 'number') {
            throw new TypeError('degrees must be a number! set to 0 to clear');
        }
        this._imageRotationDegrees = degrees;
        this._automaticRotation = false;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getForcedImageRotation = function() {
        return this._imageRotationDegrees || 0;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.setHideScrollbars = function(hide) {
        this._hideScrollbars = hide;
    };

    //noinspection JSUnusedGlobalSymbols
    Eyes.prototype.getHideScrollbars = function() {
        return this._hideScrollbars;
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @param mode Use one of the values in Eyes.StitchMode.
     */
    Eyes.prototype.setStitchMode = function(mode) {
        switch (mode) {
            case Eyes.StitchMode.Scroll:
                this._stitchMode = Eyes.StitchMode.Scroll;
                break;
            case Eyes.StitchMode.CSS:
                this._stitchMode = Eyes.StitchMode.CSS;
                break;
            default:
                this._stitchMode = Eyes.StitchMode.Scroll;
                break;
        }
    };

    //noinspection JSUnusedGlobalSymbols
    /**
     *
     * @return {Eyes.StitchMode} The currently set StitchMode.
     */
    Eyes.prototype.getStitchMode = function() {
        return this._stitchMode;
    };

    module.exports = Eyes;
}());
<|endoftext|>


<commit_before>goog.provide('ol.interaction.Modify');

goog.require('goog.array');
goog.require('goog.events');
goog.require('goog.object');
goog.require('ol.Feature');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.ViewHint');
goog.require('ol.coordinate');
goog.require('ol.extent');
goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Drag');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorLayerEventType');
goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.structs.RTree');

/**
 * @constructor
 * @extends {ol.interaction.Drag}
 * @param {ol.interaction.ModifyOptions=} opt_options Options.
 */
ol.interaction.Modify = function(opt_options) {
  goog.base(this);

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @type {null|function(ol.layer.Layer):boolean}
   * @private
   */
  this.layerFilter_ = goog.isDef(options.layerFilter) ?
      options.layerFilter : null;

  /**
   * @type {Array.<ol.layer.Layer>}
   * @private
   */
  this.layers_ = null;

  /**
   * @type {boolean}
   * @private
   */
  this.modifiable_ = false;

  /**
   * @type {number}
   * @private
   */
  this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
      options.pixelTolerance : 20;

  /**
   * @type {Array}
   * @private
   */
  this.dragVertices_ = null;

  this.interactingHint = 0;
};
goog.inherits(ol.interaction.Modify, ol.interaction.Drag);

/**
 * @param {ol.layer.Vector} layer The vector layer.
 * @param {Array.<ol.Feature>} features Array of features.
 * @private
 */
ol.interaction.Modify.prototype.addIndex_ = function(layer, features) {
  for (var i = 0, ii = features.length; i < ii; ++i) {
    var feature = features[i];
    var geometry = feature.getGeometry();
    if (geometry instanceof ol.geom.AbstractCollection) {
      for (var j = 0, jj = geometry.components.length; j < jj; ++j) {
        this.addSegments_(layer, feature, geometry.components[j],
            [[geometry.components, j]]);
      }
    } else {
      this.addSegments_(layer, feature, geometry);
    }
  }
};

/**
 * Listen for feature additions.
 * @param {ol.layer.VectorLayerEventObject} evt Event object.
 * @private
 */
ol.interaction.Modify.prototype.handleFeaturesAdded_ = function(evt) {
  this.addIndex_(evt.target, evt.features);
};

/**
 * @param {ol.layer.Layer} layer Layer.
 */
ol.interaction.Modify.prototype.addLayer = function(layer) {
  var selectionData = layer.getSelectionData();
  var selectionLayer = selectionData.layer;
  var editData = selectionLayer.getEditData();
  if (goog.isNull(editData.rTree)) {
    editData.rTree = new ol.structs.RTree();
    var vertexFeature = new ol.Feature();
    vertexFeature.renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
    vertexFeature.setGeometry(new ol.geom.Point([NaN, NaN]));
    selectionLayer.addFeatures([vertexFeature]);
    editData.vertexFeature = vertexFeature;
  }
  this.addIndex_(selectionLayer,
      goog.object.getValues(selectionData.selectedFeaturesByFeatureUid));

  goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.ADD,
      this.handleFeaturesAdded_, false, this);
  goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.REMOVE,
      this.removeIndex, false, this);
};

/**
 * @param {ol.layer.Vector} selectionLayer Selection layer.
 * @param {ol.Feature} feature Feature to add segments for.
 * @param {ol.geom.Geometry} geometry Geometry to add segments for.
 * @param {Array=} opt_parent Parent structure on the feature that the geometry
 *     belongs to. This array has two values:
 *     [0] the parent array;
 *     [1] the index of the geometry in the parent array.
 * @private
 */
ol.interaction.Modify.prototype.addSegments_ =
    function(selectionLayer, feature, geometry, opt_parent) {
  var uid = goog.getUid(feature);
  var rTree = selectionLayer.getEditData().rTree;
  var vertex, segment, segmentData, coordinates;
  if (geometry instanceof ol.geom.Point) {
    vertex = geometry.getCoordinates();
    segmentData = [[vertex, vertex], feature, geometry, NaN];
    if (goog.isDef(opt_parent)) {
      segmentData.push(opt_parent);
    }
    rTree.insert(geometry.getBounds(), segmentData, uid);
  } else if (geometry instanceof ol.geom.LineString ||
      geometry instanceof ol.geom.LinearRing) {
    coordinates = geometry.getCoordinates();
    for (var i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = [segment, feature, geometry, i];
      if (opt_parent) {
        segmentData.push(opt_parent);
      }
      rTree.insert(ol.extent.boundingExtent(segment), segmentData, uid);
    }
  } else if (geometry instanceof ol.geom.Polygon) {
    for (var j = 0, jj = geometry.rings.length; j < jj; ++j) {
      this.addSegments_(selectionLayer, feature, geometry.rings[j],
          [geometry.rings, j]);
    }
  }
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleDragStart = function(evt) {
  this.dragVertices_ = [];
  for (var i = 0, ii = this.layers_.length; i < ii; ++i) {
    var selectionData = this.layers_[i].getSelectionData();
    var selectionLayer = selectionData.layer;
    if (!goog.isNull(selectionLayer)) {
      var editData = selectionLayer.getEditData();
      var vertexFeature = editData.vertexFeature;
      if (!goog.isNull(vertexFeature) && vertexFeature.renderIntent !=
          ol.layer.VectorLayerRenderIntent.HIDDEN) {
        var vertex = vertexFeature.getGeometry().getCoordinates();
        var vertexExtent = ol.extent.boundingExtent([vertex]);
        var segments = editData.rTree.search(vertexExtent);
        for (var j = 0, jj = segments.length; j < jj; ++j) {
          var segmentData = segments[j];
          var segment = segmentData[0];
          if (vertexFeature.renderIntent ==
              ol.layer.VectorLayerRenderIntent.TEMPORARY) {
            if (ol.coordinate.equals(segment[0], vertex)) {
              this.dragVertices_.push([selectionLayer, segmentData, 0]);
            } else {
              this.dragVertices_.push([selectionLayer, segmentData, 1]);
            }
          } else {
            this.insertVertex_(selectionLayer, segmentData, vertex);
          }
        }
      }
    }
  }
  return this.modifiable_;
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleDrag = function(evt) {
  var vertex = evt.getCoordinate();
  for (var i = 0, ii = this.dragVertices_.length; i < ii; ++i) {
    var dragVertex = this.dragVertices_[i];
    var selectionLayer = dragVertex[0];
    var segmentData = dragVertex[1];
    var feature = segmentData[1];
    var geometry = segmentData[2];
    var index = dragVertex[2];
    geometry.set(segmentData[3] + index, 0, vertex[0]);
    geometry.set(segmentData[3] + index, 1, vertex[1]);
    feature.getGeometry().invalidateBounds();

    var editData = selectionLayer.getEditData();
    var vertexFeature = editData.vertexFeature;
    var vertexGeometry = vertexFeature.getGeometry();
    var segment = segmentData[0];
    editData.rTree.remove(ol.extent.boundingExtent(segment), segmentData);
    segment[index] = vertex;
    vertexGeometry.set(0, vertex[0]);
    vertexGeometry.set(1, vertex[1]);
    editData.rTree.insert(ol.extent.boundingExtent(segment), segmentData,
        goog.getUid(feature));

    selectionLayer.updateFeatures([feature, vertexFeature]);
  }
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleMapBrowserEvent =
    function(mapBrowserEvent) {
  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
      !this.getDragging() &&
      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.MOUSEMOVE) {
    this.handleMouseMove_(mapBrowserEvent);
  }
  goog.base(this, 'handleMapBrowserEvent', mapBrowserEvent);
  return !this.modifiable_;
};

/**
 * @param {ol.MapBrowserEvent} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) {
  var map = evt.map;
  var layers = goog.array.filter(map.getLayerGroup().getLayers().getArray(),
      this.ignoreTemporaryLayersFilter_);
  if (!goog.isNull(this.layerFilter_)) {
    layers = goog.array.filter(layers, this.layerFilter_);
  }
  this.layers_ = layers;
  var pixel = evt.getPixel();
  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
  var sortByDistance = function(a, b) {
    return ol.coordinate.closestOnSegment(pixelCoordinate, a[0])[2] -
        ol.coordinate.closestOnSegment(pixelCoordinate, b[0])[2];
  };

  var lowerLeft = map.getCoordinateFromPixel(
      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
  var upperRight = map.getCoordinateFromPixel(
      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
  var vertexFeature;
  this.modifiable_ = false;
  for (var i = layers.length - 1; i >= 0; --i) {
    var layer = layers[i];
    var selectionLayer = layer.getSelectionData().layer;
    if (!goog.isNull(selectionLayer)) {
      var listener = goog.events.getListener(selectionLayer,
          ol.layer.VectorLayerEventType.ADD,
          this.handleFeaturesAdded_, false, this);
      if (goog.isNull(listener)) {
        this.addLayer(layer);
      }
      var editData = selectionLayer.getEditData();
      vertexFeature = editData.vertexFeature;
      var segments = editData.rTree.search(box);
      var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
      if (segments.length > 0) {
        segments.sort(sortByDistance);
        var segment = segments[0][0]; // the closest segment
        var geometry = vertexFeature.getGeometry();
        var vertex = /** @type {ol.Coordinate} */
            (ol.coordinate.closestOnSegment(pixelCoordinate, segment));
        var coordPixel = map.getPixelFromCoordinate(vertex);
        var pixel1 = map.getPixelFromCoordinate(segment[0]);
        var pixel2 = map.getPixelFromCoordinate(segment[1]);
        var squaredDist1 = ol.coordinate.squaredDistance(coordPixel, pixel1);
        var squaredDist2 = ol.coordinate.squaredDistance(coordPixel, pixel2);
        var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
        renderIntent = ol.layer.VectorLayerRenderIntent.FUTURE;
        if (dist <= 10) {
          vertex = squaredDist1 > squaredDist2 ? segment[1] : segment[0];
          renderIntent = ol.layer.VectorLayerRenderIntent.TEMPORARY;
        }
        geometry.set(0, vertex[0]);
        geometry.set(1, vertex[1]);
        selectionLayer.updateFeatures([vertexFeature]);
        this.modifiable_ = true;
      }
      if (vertexFeature.renderIntent != renderIntent) {
        selectionLayer.setRenderIntent(renderIntent, [vertexFeature]);
      }
    }
  }
};

/**
 * @param {ol.layer.Layer} layer Layer.
 * @return {boolean} Whether the layer is no temporary vector layer.
 * @private
 */
ol.interaction.Modify.prototype.ignoreTemporaryLayersFilter_ = function(layer) {
  return !(layer instanceof ol.layer.Vector && layer.getTemporary());
};

/**
 * @param {ol.layer.Vector} selectionLayer Selection layer.
 * @param {Array} segmentData Segment data.
 * @param {ol.Coordinate} vertex Vertex.
 * @private
 */
ol.interaction.Modify.prototype.insertVertex_ =
    function(selectionLayer, segmentData, vertex) {
  var segment = segmentData[0];
  var feature = segmentData[1];
  var geometry = segmentData[2];
  var index = segmentData[3];
  var coordinates = geometry.getCoordinates();
  coordinates.splice(index + 1, 0, vertex);
  var oldGeometry = geometry;
  geometry = new geometry.constructor(coordinates);
  var parent;
  if (segmentData.length > 4) {
    parent = segmentData[4];
    parent[0][parent[1]] = geometry;
    feature.getGeometry().invalidateBounds();
  } else {
    feature.setGeometry(geometry);
  }
  var rTree = selectionLayer.getEditData().rTree;
  rTree.remove(ol.extent.boundingExtent(segment), segmentData);
  var uid = goog.getUid(feature);
  var allSegments = rTree.search(geometry.getBounds(), uid);
  for (var i = 0, ii = allSegments.length; i < ii; ++i) {
    var allSegmentsData = allSegments[i];
    if (allSegmentsData[2] === oldGeometry) {
      allSegmentsData[2] = geometry;
      if (allSegmentsData[3] > index) {
        ++allSegmentsData[3];
      }
    }
  }
  var newSegment = [segment[0], vertex];
  var newSegmentData = [newSegment, feature, geometry, index];
  if (goog.isDef(parent)) {
    newSegmentData.push(parent);
  }
  rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
  this.dragVertices_.push([selectionLayer, newSegmentData, 1]);
  newSegment = [vertex, segment[1]];
  newSegmentData = [newSegment, feature, geometry, index + 1];
  if (goog.isDef(parent)) {
    newSegmentData.push(parent);
  }
  rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
  this.dragVertices_.push([selectionLayer, newSegmentData, 0]);
};

/**
 * @param {ol.layer.VectorLayerEventObject} evt Event object.
 */
ol.interaction.Modify.prototype.removeIndex = function(evt) {
  var layer = evt.target;
  var rTree = layer.getEditData().rTree;
  var features = evt.features;
  for (var i = 0, ii = features.length; i < ii; ++i) {
    var feature = features[i];
    var segments = rTree.search(feature.getGeometry().getBounds(),
        goog.getUid(feature));
    for (var j = segments.length - 1; j >= 0; --j) {
      var segment = segments[j];
      rTree.remove(ol.extent.boundingExtent(segment[0]), segment);
    }
  }
};
<commit_msg>Do not split segments while in the loop<commit_after>goog.provide('ol.interaction.Modify');

goog.require('goog.array');
goog.require('goog.events');
goog.require('goog.object');
goog.require('ol.Feature');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.ViewHint');
goog.require('ol.coordinate');
goog.require('ol.extent');
goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Drag');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorLayerEventType');
goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.structs.RTree');

/**
 * @constructor
 * @extends {ol.interaction.Drag}
 * @param {ol.interaction.ModifyOptions=} opt_options Options.
 */
ol.interaction.Modify = function(opt_options) {
  goog.base(this);

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @type {null|function(ol.layer.Layer):boolean}
   * @private
   */
  this.layerFilter_ = goog.isDef(options.layerFilter) ?
      options.layerFilter : null;

  /**
   * @type {Array.<ol.layer.Layer>}
   * @private
   */
  this.layers_ = null;

  /**
   * @type {boolean}
   * @private
   */
  this.modifiable_ = false;

  /**
   * @type {number}
   * @private
   */
  this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
      options.pixelTolerance : 20;

  /**
   * @type {Array}
   * @private
   */
  this.dragSegments_ = null;

  this.interactingHint = 0;
};
goog.inherits(ol.interaction.Modify, ol.interaction.Drag);

/**
 * @param {ol.layer.Vector} layer The vector layer.
 * @param {Array.<ol.Feature>} features Array of features.
 * @private
 */
ol.interaction.Modify.prototype.addIndex_ = function(layer, features) {
  for (var i = 0, ii = features.length; i < ii; ++i) {
    var feature = features[i];
    var geometry = feature.getGeometry();
    if (geometry instanceof ol.geom.AbstractCollection) {
      for (var j = 0, jj = geometry.components.length; j < jj; ++j) {
        this.addSegments_(layer, feature, geometry.components[j],
            [[geometry.components, j]]);
      }
    } else {
      this.addSegments_(layer, feature, geometry);
    }
  }
};

/**
 * Listen for feature additions.
 * @param {ol.layer.VectorLayerEventObject} evt Event object.
 * @private
 */
ol.interaction.Modify.prototype.handleFeaturesAdded_ = function(evt) {
  this.addIndex_(evt.target, evt.features);
};

/**
 * @param {ol.layer.Layer} layer Layer.
 */
ol.interaction.Modify.prototype.addLayer = function(layer) {
  var selectionData = layer.getSelectionData();
  var selectionLayer = selectionData.layer;
  var editData = selectionLayer.getEditData();
  if (goog.isNull(editData.rTree)) {
    editData.rTree = new ol.structs.RTree();
    var vertexFeature = new ol.Feature();
    vertexFeature.renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
    vertexFeature.setGeometry(new ol.geom.Point([NaN, NaN]));
    selectionLayer.addFeatures([vertexFeature]);
    editData.vertexFeature = vertexFeature;
  }
  this.addIndex_(selectionLayer,
      goog.object.getValues(selectionData.selectedFeaturesByFeatureUid));

  goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.ADD,
      this.handleFeaturesAdded_, false, this);
  goog.events.listen(selectionLayer, ol.layer.VectorLayerEventType.REMOVE,
      this.removeIndex, false, this);
};

/**
 * @param {ol.layer.Vector} selectionLayer Selection layer.
 * @param {ol.Feature} feature Feature to add segments for.
 * @param {ol.geom.Geometry} geometry Geometry to add segments for.
 * @param {Array=} opt_parent Parent structure on the feature that the geometry
 *     belongs to. This array has two values:
 *     [0] the parent array;
 *     [1] the index of the geometry in the parent array.
 * @private
 */
ol.interaction.Modify.prototype.addSegments_ =
    function(selectionLayer, feature, geometry, opt_parent) {
  var uid = goog.getUid(feature);
  var rTree = selectionLayer.getEditData().rTree;
  var vertex, segment, segmentData, coordinates;
  if (geometry instanceof ol.geom.Point) {
    vertex = geometry.getCoordinates();
    segmentData = [[vertex, vertex], feature, geometry, NaN];
    if (goog.isDef(opt_parent)) {
      segmentData.push(opt_parent);
    }
    rTree.insert(geometry.getBounds(), segmentData, uid);
  } else if (geometry instanceof ol.geom.LineString ||
      geometry instanceof ol.geom.LinearRing) {
    coordinates = geometry.getCoordinates();
    for (var i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = [segment, feature, geometry, i];
      if (opt_parent) {
        segmentData.push(opt_parent);
      }
      rTree.insert(ol.extent.boundingExtent(segment), segmentData, uid);
    }
  } else if (geometry instanceof ol.geom.Polygon) {
    for (var j = 0, jj = geometry.rings.length; j < jj; ++j) {
      this.addSegments_(selectionLayer, feature, geometry.rings[j],
          [geometry.rings, j]);
    }
  }
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleDragStart = function(evt) {
  this.dragSegments_ = [];
  for (var i = 0, ii = this.layers_.length; i < ii; ++i) {
    var selectionData = this.layers_[i].getSelectionData();
    var selectionLayer = selectionData.layer;
    if (!goog.isNull(selectionLayer)) {
      var editData = selectionLayer.getEditData();
      var vertexFeature = editData.vertexFeature;
      var insertVertices = [];
      if (!goog.isNull(vertexFeature) && vertexFeature.renderIntent !=
          ol.layer.VectorLayerRenderIntent.HIDDEN) {
        var vertex = vertexFeature.getGeometry().getCoordinates();
        var vertexExtent = ol.extent.boundingExtent([vertex]);
        var segments = editData.rTree.search(vertexExtent);
        for (var j = 0, jj = segments.length; j < jj; ++j) {
          var segmentData = segments[j];
          var segment = segmentData[0];
          if (vertexFeature.renderIntent ==
              ol.layer.VectorLayerRenderIntent.TEMPORARY) {
            if (ol.coordinate.equals(segment[0], vertex)) {
              this.dragSegments_.push([selectionLayer, segmentData, 0]);
            } else {
              this.dragSegments_.push([selectionLayer, segmentData, 1]);
            }
          } else if (
              ol.coordinate.squaredDistanceToSegment(vertex, segment) === 0) {
            insertVertices.push([selectionLayer, segmentData, vertex]);
          }
        }
        for (j = insertVertices.length - 1; j >= 0; --j) {
          this.insertVertex_.apply(this, insertVertices[j]);
        }
      }
    }
  }
  return this.modifiable_;
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleDrag = function(evt) {
  var vertex = evt.getCoordinate();
  for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
    var dragSegment = this.dragSegments_[i];
    var selectionLayer = dragSegment[0];
    var segmentData = dragSegment[1];
    var feature = segmentData[1];
    var geometry = segmentData[2];
    var index = dragSegment[2];
    geometry.set(segmentData[3] + index, 0, vertex[0]);
    geometry.set(segmentData[3] + index, 1, vertex[1]);
    feature.getGeometry().invalidateBounds();

    var editData = selectionLayer.getEditData();
    var vertexFeature = editData.vertexFeature;
    var vertexGeometry = vertexFeature.getGeometry();
    var segment = segmentData[0];
    editData.rTree.remove(ol.extent.boundingExtent(segment), segmentData);
    segment[index] = vertex;
    vertexGeometry.set(0, vertex[0]);
    vertexGeometry.set(1, vertex[1]);
    editData.rTree.insert(ol.extent.boundingExtent(segment), segmentData,
        goog.getUid(feature));

    selectionLayer.updateFeatures([feature, vertexFeature]);
  }
};

/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.handleMapBrowserEvent =
    function(mapBrowserEvent) {
  if (!mapBrowserEvent.map.getView().getHints()[ol.ViewHint.INTERACTING] &&
      !this.getDragging() &&
      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.MOUSEMOVE) {
    this.handleMouseMove_(mapBrowserEvent);
  }
  goog.base(this, 'handleMapBrowserEvent', mapBrowserEvent);
  return !this.modifiable_;
};

/**
 * @param {ol.MapBrowserEvent} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) {
  var map = evt.map;
  var layers = goog.array.filter(map.getLayerGroup().getLayers().getArray(),
      this.ignoreTemporaryLayersFilter_);
  if (!goog.isNull(this.layerFilter_)) {
    layers = goog.array.filter(layers, this.layerFilter_);
  }
  this.layers_ = layers;
  var pixel = evt.getPixel();
  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
  var sortByDistance = function(a, b) {
    return ol.coordinate.closestOnSegment(pixelCoordinate, a[0])[2] -
        ol.coordinate.closestOnSegment(pixelCoordinate, b[0])[2];
  };

  var lowerLeft = map.getCoordinateFromPixel(
      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
  var upperRight = map.getCoordinateFromPixel(
      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
  var vertexFeature;
  this.modifiable_ = false;
  for (var i = layers.length - 1; i >= 0; --i) {
    var layer = layers[i];
    var selectionLayer = layer.getSelectionData().layer;
    if (!goog.isNull(selectionLayer)) {
      var listener = goog.events.getListener(selectionLayer,
          ol.layer.VectorLayerEventType.ADD,
          this.handleFeaturesAdded_, false, this);
      if (goog.isNull(listener)) {
        this.addLayer(layer);
      }
      var editData = selectionLayer.getEditData();
      vertexFeature = editData.vertexFeature;
      var segments = editData.rTree.search(box);
      var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
      if (segments.length > 0) {
        segments.sort(sortByDistance);
        var segment = segments[0][0]; // the closest segment
        var geometry = vertexFeature.getGeometry();
        var vertex = /** @type {ol.Coordinate} */
            (ol.coordinate.closestOnSegment(pixelCoordinate, segment));
        var coordPixel = map.getPixelFromCoordinate(vertex);
        var pixel1 = map.getPixelFromCoordinate(segment[0]);
        var pixel2 = map.getPixelFromCoordinate(segment[1]);
        var squaredDist1 = ol.coordinate.squaredDistance(coordPixel, pixel1);
        var squaredDist2 = ol.coordinate.squaredDistance(coordPixel, pixel2);
        var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
        renderIntent = ol.layer.VectorLayerRenderIntent.FUTURE;
        if (dist <= 10) {
          vertex = squaredDist1 > squaredDist2 ? segment[1] : segment[0];
          renderIntent = ol.layer.VectorLayerRenderIntent.TEMPORARY;
        }
        geometry.set(0, vertex[0]);
        geometry.set(1, vertex[1]);
        selectionLayer.updateFeatures([vertexFeature]);
        this.modifiable_ = true;
      }
      if (vertexFeature.renderIntent != renderIntent) {
        selectionLayer.setRenderIntent(renderIntent, [vertexFeature]);
      }
    }
  }
};

/**
 * @param {ol.layer.Layer} layer Layer.
 * @return {boolean} Whether the layer is no temporary vector layer.
 * @private
 */
ol.interaction.Modify.prototype.ignoreTemporaryLayersFilter_ = function(layer) {
  return !(layer instanceof ol.layer.Vector && layer.getTemporary());
};

/**
 * @param {ol.layer.Vector} selectionLayer Selection layer.
 * @param {Array} segmentData Segment data.
 * @param {ol.Coordinate} vertex Vertex.
 * @private
 */
ol.interaction.Modify.prototype.insertVertex_ =
    function(selectionLayer, segmentData, vertex) {
  var segment = segmentData[0];
  var feature = segmentData[1];
  var geometry = segmentData[2];
  var index = segmentData[3];
  var coordinates = geometry.getCoordinates();
  coordinates.splice(index + 1, 0, vertex);
  var oldGeometry = geometry;
  geometry = new geometry.constructor(coordinates);
  var parent;
  if (segmentData.length > 4) {
    parent = segmentData[4];
    parent[0][parent[1]] = geometry;
    feature.getGeometry().invalidateBounds();
  } else {
    feature.setGeometry(geometry);
  }
  var rTree = selectionLayer.getEditData().rTree;
  rTree.remove(ol.extent.boundingExtent(segment), segmentData);
  var uid = goog.getUid(feature);
  var allSegments = rTree.search(geometry.getBounds(), uid);
  for (var i = 0, ii = allSegments.length; i < ii; ++i) {
    var allSegmentsData = allSegments[i];
    if (allSegmentsData[2] === oldGeometry) {
      allSegmentsData[2] = geometry;
      if (allSegmentsData[3] > index) {
        ++allSegmentsData[3];
      }
    }
  }
  var newSegment = [segment[0], vertex];
  var newSegmentData = [newSegment, feature, geometry, index];
  if (goog.isDef(parent)) {
    newSegmentData.push(parent);
  }
  rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
  this.dragSegments_.push([selectionLayer, newSegmentData, 1]);
  newSegment = [vertex, segment[1]];
  newSegmentData = [newSegment, feature, geometry, index + 1];
  if (goog.isDef(parent)) {
    newSegmentData.push(parent);
  }
  rTree.insert(ol.extent.boundingExtent(newSegment), newSegmentData, uid);
  this.dragSegments_.push([selectionLayer, newSegmentData, 0]);
};

/**
 * @param {ol.layer.VectorLayerEventObject} evt Event object.
 */
ol.interaction.Modify.prototype.removeIndex = function(evt) {
  var layer = evt.target;
  var rTree = layer.getEditData().rTree;
  var features = evt.features;
  for (var i = 0, ii = features.length; i < ii; ++i) {
    var feature = features[i];
    var segments = rTree.search(feature.getGeometry().getBounds(),
        goog.getUid(feature));
    for (var j = segments.length - 1; j >= 0; --j) {
      var segment = segments[j];
      rTree.remove(ol.extent.boundingExtent(segment[0]), segment);
    }
  }
};
<|endoftext|>


<commit_before>import produce from 'immer'

import {
	SET_VIEWS_VALUE,
	SET_VIEWS_FETCHING,
	SET_VIEWS_ERROR,
	RESET_VIEWS
} from '../actions'

const initialState = () => ({
	value: {}
})

const initialSubState = () => ({
	value: [],
	fetching: false,
	error: undefined
})

export default produce((draft, action) => {

	const hasDomainId = () => action.domainId != null
	const hasDomainValue = () => draft.value[action.domainId] != null

	if (hasDomainId() === true && hasDomainValue() === false) draft.value[action.domainId] = initialSubState()

	switch (action.type) {
		case SET_VIEWS_VALUE:
			draft.value[action.domainId].value = action.payload || initialSubState.value
			break
		case SET_VIEWS_FETCHING:
			draft.value[action.domainId].fetching = action.payload || initialSubState.fetching
			break
		case SET_VIEWS_ERROR:
			draft.value[action.domainId].error = action.payload || initialSubState.error
			break
		case RESET_VIEWS:
			return initialState()
	}

}, initialState())<commit_msg>Fix fallback values<commit_after>import produce from 'immer'

import {
	SET_VIEWS_VALUE,
	SET_VIEWS_FETCHING,
	SET_VIEWS_ERROR,
	RESET_VIEWS
} from '../actions'

const initialState = () => ({
	value: {}
})

const initialSubState = () => ({
	value: [],
	fetching: false,
	error: undefined
})

export default produce((draft, action) => {

	const hasDomainId = () => action.domainId != null
	const hasDomainValue = () => draft.value[action.domainId] != null

	if (hasDomainId() === true && hasDomainValue() === false) draft.value[action.domainId] = initialSubState()

	switch (action.type) {
		case SET_VIEWS_VALUE:
			draft.value[action.domainId].value = action.payload || initialSubState().value
			break
		case SET_VIEWS_FETCHING:
			draft.value[action.domainId].fetching = action.payload || initialSubState().fetching
			break
		case SET_VIEWS_ERROR:
			draft.value[action.domainId].error = action.payload || initialSubState().error
			break
		case RESET_VIEWS:
			return initialState()
	}

}, initialState())<|endoftext|>


<commit_before>'use strict';

var toolbar = 'ko-grid-toolbar';

define(['module', 'ko-grid', toolbar], function (module, koGrid) {
    var extensionId = module.id.indexOf('/') < 0 ? module.id : module.id.substring(0, module.id.indexOf('/'));

    var document = window.document,
        html = document.documentElement;

    var Blob = window.Blob,
        URL = window.URL;

    koGrid.defineExtension(extensionId, {
        dependencies: [toolbar],
        initializer: template => {
            template.into('left-toolbar').insert(' <button class="ko-grid-toolbar-button ko-grid-excel-export">Excel Export</button> ');
        },
        Constructor: function ExportExtension(bindingValue, config, grid) {
            grid.rootElement.addEventListener('click', e => {
                if (e.target.classList.contains('ko-grid-excel-export'))
                    supplyExcelExport(grid);
            });
        }
    });

    return koGrid.declareExtensionAlias('export', extensionId);

    function supplyExcelExport(grid) {
        var columns = grid.columns.displayed().filter(c => !!c.property);

        var valueSelector = grid.data.valueSelector;

        grid.data.source.streamValues(q => q.filteredBy(grid.data.predicate).sortedBy(grid.data.comparator))
            .then(values => values
                .map(value=> '
' + columns.map(c => '' + valueSelector(value[c.property]) + '	').join('') + '
')
                .reduce((a, b) => a + b, ''))
            .then(data => {
                var excelDocument = [
                    '',
                    '  ' +
                    '  ',
                    '
',
                    '
',
                    '
' + columns.map(c => '' + c.label() + '	').join('') + '
', // TODO groups
                    '
',
                    '
',
                    data,
                    '
',
                    '
',
                    '  ',
                    ''
                ].join('');

                var saveOrOpen = window.navigator.msSaveOrOpenBlob ? window.navigator.msSaveOrOpenBlob.bind(window.navigator) :
                    function (blob, name) {
                        var url = URL.createObjectURL(blob);
                        var anchor = document.createElement('a');
                        anchor.href = url;
                        anchor.download = name;
                        var event = document.createEvent('MouseEvents');
                        event.initEvent('click', true, true);
                        anchor.dispatchEvent(event);
                        // TODO there needs to be a cleaner way to do this
                        window.setTimeout(() => URL.revokeObjectURL(url));
                    };

                saveOrOpen(
                    new Blob([excelDocument], {type: 'application/vnd.ms-excel;charset=utf-8'}),
                    'excel-export.xls');
            });
    }
});
<commit_msg>Apparently `download` has yet to be added to closures externs.<commit_after>'use strict';

var toolbar = 'ko-grid-toolbar';

define(['module', 'ko-grid', toolbar], function (module, koGrid) {
    var extensionId = module.id.indexOf('/') < 0 ? module.id : module.id.substring(0, module.id.indexOf('/'));

    var document = window.document,
        html = document.documentElement;

    var Blob = window.Blob,
        URL = window.URL;

    koGrid.defineExtension(extensionId, {
        dependencies: [toolbar],
        initializer: template => {
            template.into('left-toolbar').insert(' <button class="ko-grid-toolbar-button ko-grid-excel-export">Excel Export</button> ');
        },
        Constructor: function ExportExtension(bindingValue, config, grid) {
            grid.rootElement.addEventListener('click', e => {
                if (e.target.classList.contains('ko-grid-excel-export'))
                    supplyExcelExport(grid);
            });
        }
    });

    return koGrid.declareExtensionAlias('export', extensionId);

    function supplyExcelExport(grid) {
        var columns = grid.columns.displayed().filter(c => !!c.property);

        var valueSelector = grid.data.valueSelector;

        grid.data.source.streamValues(q => q.filteredBy(grid.data.predicate).sortedBy(grid.data.comparator))
            .then(values => values
                .map(value=> '
' + columns.map(c => '' + valueSelector(value[c.property]) + '	').join('') + '
')
                .reduce((a, b) => a + b, ''))
            .then(data => {
                var excelDocument = [
                    '',
                    '  ' +
                    '  ',
                    '
',
                    '
',
                    '
' + columns.map(c => '' + c.label() + '	').join('') + '
', // TODO groups
                    '
',
                    '
',
                    data,
                    '
',
                    '
',
                    '  ',
                    ''
                ].join('');

                var saveOrOpen = window.navigator.msSaveOrOpenBlob ? window.navigator.msSaveOrOpenBlob.bind(window.navigator) :
                    function (blob, name) {
                        var url = URL.createObjectURL(blob);
                        var anchor = document.createElement('a');
                        anchor.href = url;
                        anchor['download'] = name;
                        var event = document.createEvent('MouseEvents');
                        event.initEvent('click', true, true);
                        anchor.dispatchEvent(event);
                        // TODO there needs to be a cleaner way to do this
                        window.setTimeout(() => URL.revokeObjectURL(url));
                    };

                saveOrOpen(
                    new Blob([excelDocument], {type: 'application/vnd.ms-excel;charset=utf-8'}),
                    'excel-export.xls');
            });
    }
});
<|endoftext|>


<commit_before>/**
 * author: feifeiyu
 * verion: 2.0
 * @param target // list container DOM id， not null
 * @param refresh  // enable or disable page refresh, type: boolean, default: true
 * @param url // request api address, type: url string,
 * @param headers //http request headers, type: object
 * @param param //http request query(parameters), type: object
 * @param height //list container height, type: number, default: window.screen.height
 * @param loadImmedate  // whether send request on page init or not, type: boolean, default: true
 * @param preload  // enable or disable page list preload, default: true
 × @param cb // callback type: function
 * parameters for reset function
 * @param url // reset request api address, type: url string
 * @param param // reset request query, type: object
 * @param cb // callback type: function
 * Yscroll is a page split component for h5
 * Yscroll blog: https://feifeiyum.github.io/2016/09/26/front-yscroll-for-h5/
 */

// const reqwest = require('reqwest')

var Yscroll = (function() {
    var YS = function(opt, cb) {
        if(!opt.target) {
            throw '[error]: Yscroll - target id can\'t be null'
        }
        if(typeof opt.target !== 'string') {
            throw '[error]: Yscroll - type of target id is string'
        }
        if(opt.url !== undefined && typeof opt.url !== 'string') {
            throw '[error]: Yscroll - type of request url is string '
        }
        if(opt.headers !== undefined && typeof opt.headers !== 'object') {
            throw '[error]: Yscroll - type of headers (http headers) is object '
        }
        if(opt.refresh !== undefined && typeof opt.refresh !== 'boolean') {
            throw '[error]: Yscroll - type of refresh is boolean'
        }
        if(opt.loadImmediate !== undefined && typeof opt.loadImmediate !== 'boolean') {
            throw '[error]: Yscroll - type of loadImmediate is boolean'
        }
        if(opt.preload !== undefined && typeof opt.preload !== 'boolean') {
            throw '[error]: Yscroll - type of preload is boolean'
        }
        if(opt.height !== undefined && typeof opt.height !== 'number') {
            throw '[error]: Yscroll - type of height is number'
        }
        if(opt.param !== undefined && typeof opt.param != 'object') {
            throw '[error]: Yscroll - type of param is object'
        }

        var self = this
        self.refresh = opt.refresh !== undefined ? opt.refresh : true //是否使能下拉刷新
        opt.headers && (self.headers = opt.headers) //http 请求头部
        self.height = opt.height || window.screen.height //列表高度
        opt.url && (self.next = opt.url)  //请求地址
        self.preload = opt.preload !== undefined ? opt.preload : true //是否使能预加载
        self.loadEnable = opt.url !== undefined
        self.cb = cb //回调
        //是否实例化后就加载数据
        var loadImmediate = opt.loadImmediate !== undefined ? opt.loadImmediate : true

        if(opt.param) {
            self.next = self.assembleQuery(self.next, opt.param)
        }

        //生成所需要dom
        self.genDom(opt.target)
        //注册事件
        self.touchEvt()
        //加载数据
        loadImmediate && self.fetchData()
    }

    YS.prototype.genDom = function(target) {
        var self = this
        //Yscroll Wrap
        self.ysWrap = document.createElement('div')
        self.ysWrap.id = 'yscroll-wrapper'
        self.ysWrap.style.cssText = 'position:absolute;'
            + 'width:100%;'
            + 'overflow-x:hidden;'
            + 'overflow-y:scroll;'
            + '-webkit-overflow-scrolling:touch;'
            + 'height:' + self.height + 'px;'

        //加载更多图标
        self.loadMoreNode = document.createElement('p')
        self.loadMoreNode.id = 'yscroll-more'
        self.loadMoreNode.style.cssText = 'display:none;'
            + 'margin:0;'
            + 'bottom:-28px;'
            + 'text-align:center;'
            + 'height:28px;'
            + 'line-height:28px;'
            + 'font-size:12px;'
            + 'color:#323232;'
        self.loadMoreNode.innerHTML = '上拉加载更多'

        //下拉刷新图标
        self.refreshNode = document.createElement('p')
        self.refreshNode.id = 'yscroll-more'
        self.refreshNode.style.cssText = 'display:none;'
            + 'margin:0;'
            + 'text-align:center;'
            + 'height:28px;'
            + 'line-height:28px;'
            + 'font-size:12px;'
            + 'color:#323232;'
        self.refreshNode.innerHTML = '下拉刷新'

        var targetNode = document.getElementById(target)
        var tarParent = targetNode.parentNode
        tarParent.style.position = 'relative'

        if(!targetNode) {
            throw '[error]: Yscroll - can not find list target DOM'
        }
        self.ysWrap.appendChild(self.refreshNode)
        self.ysWrap.appendChild(targetNode)
        self.ysWrap.appendChild(self.loadMoreNode)
        tarParent.appendChild(self.ysWrap)
    }

    YS.prototype.fetchData = function() {
        var self = this
        console.log('next', self.next)
        if(self.next === 'refresh') {
            self.loadEnable = true
            self.cb(null, 'refresh')
        } else {
            self.loadMoreNode.innerHTML = '加载中 . . .'
            self.refreshNode.innerHTML = '加载中 . . .'
            reqwest({
                url: self.next,
                method: 'GET',
                headers: self.headers,
                crossOrigin: true
            }).then(function(resp) {
                if(resp.next) {
                    self.next = resp.next
                    self.loadMoreNode.style.display = 'none'
                    self.loadMoreNode.innerHTML = '上拉加载更多'
                } else {
                    self.loadEnable = false
                    self.loadMoreNode.style.display = 'block'
                    self.loadMoreNode.innerHTML = '没有更多了'
                }
                self.refreshNode.style.display = 'none'
                self.refreshNode.innerHTML = '下拉刷新'

                self.cb(null, resp)
            })
        }
    }

    YS.prototype.touchEvt = function() {
        var self = this
        var startY = 0 //滑动起点
        var offsetY = 0 //拉动距离

        //touch事件处理
        var touch = function(e) {
            // e.preventDefault()  //加了 scroll 事件没了
            var evt = e || window.event
            var offsetTop = self.ysWrap.scrollHeight - self.height  //相对与顶部最大偏移距离
            var scrollTop = self.ysWrap.scrollTop
            var touch = e.touches[0]

            switch(evt.type) {
                case 'touchstart':
                    startY = touch.pageY
                    break
                case 'touchmove':
                   if(touch.pageY < 200) { //avoid scroll leakage
                        evt.preventDefault
                    } else {
                        offsetY = touch.pageY - startY
                        if(self.refresh && scrollTop === 0 && offsetY > 0) {
                            //在顶部，下拉
                            evt.preventDefault()
                            self.ysWrap.style.top = offsetY + 'px'
                            self.refreshNode.style.display = 'block'
                            self.loadMoreNode.style.display = 'none'
                        } else if((offsetTop - scrollTop) < 3 && offsetTop > 0 && offsetY < 0) {
                            //在底部， 上拉
                            //页面存在折叠 即 offsetTop > 0
                            evt.preventDefault()
                            self.ysWrap.style.top = offsetY + 'px'
                            self.loadMoreNode.style.display = 'block'
                            self.refreshNode.style.display = 'none'
                        }
                    }
                    break
                case 'touchend':
                    self.ysWrap.style.top = '0px'
                    if(!self.loadEnable && offsetY < 0) {
                        //不加载更多，下拉时，把页面拉到底部
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                            offsetTop && (self.ysWrap.scrollTop = self.ysWrap.scrollHeight)
                        }
                    } else if(self.loadEnable && offsetY < 0) {
                        //下滑
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                        }

                        if(self.preload && (offsetTop - scrollTop) / self.height < 0.3) {
                            //提前加载， 在页面滚到到底部之前加载
                            self.fetchData() //请求接口
                        } else if((offsetTop - scrollTop) < 3 && offsetY < -100) {
                            //下拉加载 在页面滚到到底部加载
                            self.fetchData() //请求接口
                        }
                    } else if(self.refresh && offsetY > 0) {
                        //上滑
                        document.body.scrollTop = 0  //把body移到顶部
                        if(scrollTop == 0) {
                            if (offsetY > 120) {
                                let urlTmp = self.next
                                self.next = 'refresh'
                                self.fetchData() //请求清空
                                self.next = urlTmp.replace(/&?page=\d+/, '')
                                self.fetchData()  //重新加载数据
                            }
                        }
                    }
                    break
                case 'touchcancel':
                    self.ysWrap.style.top = '0px'
                    if(!self.loadEnable && offsetY < 0) {
                        //不加载更多，下拉时，把页面拉到底部
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                            offsetTop && (self.ysWrap.scrollTop = self.ysWrap.scrollHeight)
                        }
                    } else if(self.loadEnable && offsetY < 0) {
                        //下滑
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                        }

                        if(self.preload && (offsetTop - scrollTop) / self.height < 0.3) {
                            //提前加载， 在页面滚到到底部之前加载
                            self.fetchData() //请求接口
                        } else if((offsetTop - scrollTop) < 3 && offsetY < -100) {
                            //下拉加载 在页面滚到到底部加载
                            self.fetchData() //请求接口
                        }
                    } else if(self.refresh && offsetY > 0) {
                        //上滑
                        document.body.scrollTop = 0  //把body移到顶部
                        if(scrollTop == 0) {
                            if (offsetY > 120) {
                                let urlTmp = self.next
                                self.next = 'refresh'
                                self.fetchData() //请求清空
                                self.next = urlTmp.replace(/&?page=\d+/, '')
                                self.fetchData()  //重新加载数据
                            }
                        }
                    }
                    break
                default:
                    break
            }
        }

        self.ysWrap.addEventListener('touchstart', touch, false)
        self.ysWrap.addEventListener('touchmove', touch, false)
        self.ysWrap.addEventListener('touchend', touch, false)
        self.ysWrap.addEventListener('touchcancel', touch, false)
    }

    YS.prototype.reset = function(url, param, cb) {
        console.log('in reset')
        var self = this
        if(url !== undefined && typeof url !== 'string') {
            throw '[error] Yscroll - type of url in Yscroll.reset is string'
        }
        if(param !== undefined && typeof param !== 'object') {
            throw '[error] Yscroll - type of param in Yscroll.reset is object'
        }

        url && (self.next = url)
        if(param) {
            self.next = self.assembleQuery(self.next, param)
        }
        cb && (self.cb = cb)
        self.loadEnable = true
        //请求数据
        self.fetchData()
    }

    //组装参数
    YS.prototype.assembleQuery = function(url, param) {
        var query = ''
        for(var key in param) {
            query += '&' + key + '=' + param[key]
        }
        if(/\?\w+/.test(url)) {
            url += query
        } else {
            url += '?' + query.slice(1)
        }
        return url
    }

    return YS
}())

//如果采用模块化打包引入的时候加上这句
// module.exports = Yscroll<commit_msg>update<commit_after>/**
 * author: feifeiyu
 * verion: 2.1
 * @param target // list container DOM id， not null
 * @param refresh  // enable or disable page refresh, type: boolean, default: true
 * @param url // request api address, type: url string,
 * @param headers //http request headers, type: object
 * @param param //http request query(parameters), type: object
 * @param height //list container height, type: number, default: window.screen.height
 * @param loadImmedate  // whether send request on page init or not, type: boolean, default: true
 * @param preload  // enable or disable page list preload, default: true
 × @param cb // callback type: function
 * parameters for reset function
 * @param url // reset request api address, type: url string
 * @param param // reset request query, type: object
 * @param cb // callback type: function
 * Yscroll is a page split component for h5
 * Yscroll blog: https://feifeiyum.github.io/2016/09/26/front-yscroll-for-h5/
 */

// const reqwest = require('reqwest')

var Yscroll = (function() {
    var YS = function(opt, cb) {
        if(!opt.target) {
            throw '[error]: Yscroll - target id can\'t be null'
        }
        if(typeof opt.target !== 'string') {
            throw '[error]: Yscroll - type of target id is string'
        }
        if(opt.url !== undefined && typeof opt.url !== 'string') {
            throw '[error]: Yscroll - type of request url is string '
        }
        if(opt.headers !== undefined && typeof opt.headers !== 'object') {
            throw '[error]: Yscroll - type of headers (http headers) is object '
        }
        if(opt.refresh !== undefined && typeof opt.refresh !== 'boolean') {
            throw '[error]: Yscroll - type of refresh is boolean'
        }
        if(opt.loadImmediate !== undefined && typeof opt.loadImmediate !== 'boolean') {
            throw '[error]: Yscroll - type of loadImmediate is boolean'
        }
        if(opt.preload !== undefined && typeof opt.preload !== 'boolean') {
            throw '[error]: Yscroll - type of preload is boolean'
        }
        if(opt.height !== undefined && typeof opt.height !== 'number') {
            throw '[error]: Yscroll - type of height is number'
        }
        if(opt.param !== undefined && typeof opt.param != 'object') {
            throw '[error]: Yscroll - type of param is object'
        }

        var self = this
        self.refresh = opt.refresh !== undefined ? opt.refresh : true //是否使能下拉刷新
        opt.headers && (self.headers = opt.headers) //http 请求头部
        self.height = opt.height || window.screen.height //列表高度
        opt.url && (self.next = opt.url)  //请求地址
        self.preload = opt.preload !== undefined ? opt.preload : true //是否使能预加载
        self.loadEnable = opt.url !== undefined
        self.cb = cb //回调
        //是否实例化后就加载数据
        var loadImmediate = opt.loadImmediate !== undefined ? opt.loadImmediate : true

        if(opt.param) {
            self.next = self.assembleQuery(self.next, opt.param)
        }

        //生成所需要dom
        self.genDom(opt.target)
        //注册事件
        self.touchEvt()
        //加载数据
        loadImmediate && self.fetchData()
    }

    YS.prototype.genDom = function(target) {
        var self = this
        //Yscroll Wrap
        self.ysWrap = document.createElement('div')
        self.ysWrap.id = 'yscroll-wrapper'
        self.ysWrap.style.cssText = 'position:absolute;'
            + 'width:100%;'
            + 'overflow-x:hidden;'
            + 'overflow-y:scroll;'
            + '-webkit-overflow-scrolling:touch;'
            + 'height:' + self.height + 'px;'

        //加载更多图标
        self.loadMoreNode = document.createElement('p')
        self.loadMoreNode.id = 'yscroll-more'
        self.loadMoreNode.style.cssText = 'display:none;'
            + 'margin:0;'
            + 'bottom:-28px;'
            + 'text-align:center;'
            + 'height:28px;'
            + 'line-height:28px;'
            + 'font-size:12px;'
            + 'color:#323232;'
        self.loadMoreNode.innerHTML = '上拉加载更多'

        //下拉刷新图标
        self.refreshNode = document.createElement('p')
        self.refreshNode.id = 'yscroll-more'
        self.refreshNode.style.cssText = 'display:none;'
            + 'margin:0;'
            + 'text-align:center;'
            + 'height:28px;'
            + 'line-height:28px;'
            + 'font-size:12px;'
            + 'color:#323232;'
        self.refreshNode.innerHTML = '下拉刷新'

        var targetNode = document.getElementById(target)
        var tarParent = targetNode.parentNode
        tarParent.style.position = 'relative'

        if(!targetNode) {
            throw '[error]: Yscroll - can not find list target DOM'
        }
        self.ysWrap.appendChild(self.refreshNode)
        self.ysWrap.appendChild(targetNode)
        self.ysWrap.appendChild(self.loadMoreNode)
        tarParent.appendChild(self.ysWrap)
    }

    YS.prototype.fetchData = function() {
        var self = this
        console.log('next', self.next)
        if(self.next === 'refresh') {
            self.loadEnable = true
            self.cb(null, 'refresh')
        } else {
            self.loadMoreNode.innerHTML = '加载中 . . .'
            self.refreshNode.innerHTML = '加载中 . . .'
            reqwest({
                url: self.next,
                method: 'GET',
                headers: self.headers,
                crossOrigin: true
            }).then(function(resp) {
                if(resp.next) {
                    self.next = resp.next
                    self.loadMoreNode.style.display = 'none'
                    self.loadMoreNode.innerHTML = '上拉加载更多'
                } else {
                    self.loadEnable = false
                    self.loadMoreNode.style.display = 'block'
                    self.loadMoreNode.innerHTML = '没有更多了'
                }
                self.refreshNode.style.display = 'none'
                self.refreshNode.innerHTML = '下拉刷新'

                self.cb(null, resp)
            })
        }
    }

    YS.prototype.touchEvt = function() {
        var self = this
        var startY = 0 //滑动起点
        var offsetY = 0 //拉动距离

        //touch事件处理
        var touch = function(e) {
            // e.preventDefault()  //加了 scroll 事件没了
            var evt = e || window.event
            var offsetTop = self.ysWrap.scrollHeight - self.height  //相对与顶部最大偏移距离
            var scrollTop = self.ysWrap.scrollTop
            var touch = e.touches[0]

            switch(evt.type) {
                case 'touchstart':
                    startY = touch.pageY
                    break
                case 'touchmove':
                   if(touch.pageY < 200) { //avoid scroll leakage
                        evt.preventDefault
                    } else {
                        offsetY = touch.pageY - startY
                        if(self.refresh && scrollTop === 0 && offsetY > 0) {
                            //在顶部，下拉
                            evt.preventDefault()
                            self.ysWrap.style.top = offsetY + 'px'
                            self.refreshNode.style.display = 'block'
                            self.loadMoreNode.style.display = 'none'
                        } else if((offsetTop - scrollTop) < 3 && offsetTop > 0 && offsetY < 0) {
                            //在底部， 上拉
                            //页面存在折叠 即 offsetTop > 0
                            evt.preventDefault()
                            self.ysWrap.style.top = offsetY + 'px'
                            self.loadMoreNode.style.display = 'block'
                            self.refreshNode.style.display = 'none'
                        }
                    }
                    break
                case 'touchend':
                    self.ysWrap.style.top = '0px'
                    if(!self.loadEnable && offsetY < 0) {
                        //不加载更多，下拉时，把页面拉到底部
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                            offsetTop && (self.ysWrap.scrollTop = self.ysWrap.scrollHeight)
                        }
                    } else if(self.loadEnable && offsetY < 0) {
                        //下滑
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                        }

                        if(self.preload && (offsetTop - scrollTop) / self.height < 0.3) {
                            //提前加载， 在页面滚到到底部之前加载
                            self.fetchData() //请求接口
                        } else if((offsetTop - scrollTop) < 3 && offsetY < -100) {
                            //下拉加载 在页面滚到到底部加载
                            self.fetchData() //请求接口
                        }
                    } else if(self.refresh && offsetY > 0) {
                        //上滑
                        document.body.scrollTop = 0  //把body移到顶部
                        if(scrollTop == 0) {
                            if (offsetY > 120) {
                                let urlTmp = self.next
                                self.next = 'refresh'
                                self.fetchData() //请求清空
                                self.next = urlTmp.replace(/&?page=\d+/, '')
                                self.fetchData()  //重新加载数据
                            }
                        }
                    }
                    offsetY = 0
                    break
                case 'touchcancel':
                    self.ysWrap.style.top = '0px'
                    if(!self.loadEnable && offsetY < 0) {
                        //不加载更多，下拉时，把页面拉到底部
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                            offsetTop && (self.ysWrap.scrollTop = self.ysWrap.scrollHeight)
                        }
                    } else if(self.loadEnable && offsetY < 0) {
                        //下滑
                        if((offsetTop - scrollTop) < 100) {
                            document.body.scrollTop = 10000  //把body移到底部
                        }

                        if(self.preload && (offsetTop - scrollTop) / self.height < 0.3) {
                            //提前加载， 在页面滚到到底部之前加载
                            self.fetchData() //请求接口
                        } else if((offsetTop - scrollTop) < 3 && offsetY < -100) {
                            //下拉加载 在页面滚到到底部加载
                            self.fetchData() //请求接口
                        }
                    } else if(self.refresh && offsetY > 0) {
                        //上滑
                        document.body.scrollTop = 0  //把body移到顶部
                        if(scrollTop == 0) {
                            if (offsetY > 120) {
                                let urlTmp = self.next
                                self.next = 'refresh'
                                self.fetchData() //请求清空
                                self.next = urlTmp.replace(/&?page=\d+/, '')
                                self.fetchData()  //重新加载数据
                            }
                        }
                    }
                    offsetY = 0
                    break
                default:
                    break
            }
        }

        self.ysWrap.addEventListener('touchstart', touch, false)
        self.ysWrap.addEventListener('touchmove', touch, false)
        self.ysWrap.addEventListener('touchend', touch, false)
        self.ysWrap.addEventListener('touchcancel', touch, false)
    }

    YS.prototype.reset = function(url, param, cb) {
        console.log('in reset')
        var self = this
        if(url !== undefined && typeof url !== 'string') {
            throw '[error] Yscroll - type of url in Yscroll.reset is string'
        }
        if(param !== undefined && typeof param !== 'object') {
            throw '[error] Yscroll - type of param in Yscroll.reset is object'
        }

        url && (self.next = url)
        if(param) {
            self.next = self.assembleQuery(self.next, param)
        }
        cb && (self.cb = cb)
        self.loadEnable = true
        //请求数据
        self.fetchData()
    }

    //组装参数
    YS.prototype.assembleQuery = function(url, param) {
        var query = ''
        for(var key in param) {
            query += '&' + key + '=' + param[key]
        }
        if(/\?\w+/.test(url)) {
            url += query
        } else {
            url += '?' + query.slice(1)
        }
        return url
    }

    return YS
}())

//如果采用模块化打包引入的时候加上这句
// module.exports = Yscroll<|endoftext|>