<commit_before>/**
 * @module      com.louisvillemetro.the-city
 * @author      Reydel Leon Machado
 * @copyright   Copyright (c) 2016 Reydel Leon Machado
 * @license     This code is licensed under the MIT license. See the LICENSE file in the project root for license terms.
 */

"use strict";

let defaults = require('../defaults');

module.exports = handleProvideAddressIntent;

/**
 * Handles the ProvideAddressIntent. This intent is used to process an address that the user has voiced.
 * It will verify the address and in case multiple addresses are found to match the address provided by
 * the user, will cycle through them till the user selects one or discard them all.
 *
 * @param event The request body
 * @param response The response object
 * @returns {*}
 */
function handleProvideAddressIntent(event, response) {
    let streetAddress = event.request.intent.slots.StreetAddress;

    if (streetAddress && streetAddress.value) {
        let googleApiUrl = config.googleGeoApi.url + config.googleGeoApi.format + '?address=' + encodeURI(streetAddress.value) + '&key=' + config.googleGeoApi.key;

        return request.get(googleApiUrl)
                .then(function (verifiedAddress) {
                    verifiedAddress = JSON.parse(verifiedAddress);

                    var speechOutput,
                        repromptSpeech;

                    if (verifiedAddress.status && verifiedAddress.status == 'OK') {
                        speechOutput = {
                            speech: `Thanks! Just to make sure: Is your address ${verifiedAddress.results[0].formatted_address}?`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        repromptSpeech = {
                            speech: `Please, confirm if your address is ${verifiedAddress.results[0].formatted_address}`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        // Segment address for Junk Pickup API consumption
                        var verifiedStreetNumber  = '',
                            verifiedStreetAddress = '',
                            verifiedCity          = '';

                        _.forEach(verifiedAddress.results[0].address_components, function (component) {
                            if (component.types.includes('street_number')) {
                                verifiedStreetNumber = component.long_name;
                            }

                            if (component.types.includes('route')) {
                                verifiedStreetAddress = component.short_name;
                            }

                            if (component.types.includes('locality')) {
                                verifiedCity = component.long_name;
                            }
                        });

                        response._session.attributes.VerifiedStreetAddress = verifiedStreetNumber + ' ' + verifiedStreetAddress;
                        response._session.attributes.VerifiedCity = verifiedCity;
                    } else {
                        speechOutput = {
                            speech: `Uhmmm! The address you provided seems to be invalid. Please, repeat your address, number first. Remember to include your city or state.`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        repromptSpeech = {
                            speech: `Please, repeat your address, number first.`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };
                    }

                    return response.ask(speechOutput, repromptSpeech);
                }).catch(function (err) {
                    console.dir(err);

                    let speechOutput = {
                        speech: `I'm having some trouble verifying that address. Please, repeat your address, number first.`,
                        type: defaults.speechOutputType.PLAIN_TEXT
                    };

                    let repromptSpeech = {
                        speech: `Please, repeat your address, number first`,
                        type: defaults.speechOutputType.PLAIN_TEXT
                    };

                    return response.ask(speechOutput, repromptSpeech);
                });
    } else {
        return new Promise(function (resolve) {
            let speechOutput = {
                speech: `I didn't quite get that. Please, repeat your address, number first.`,
                type: defaults.speechOutputType.PLAIN_TEXT
            };

            let repromptSpeech = {
                speech: `Please, repeat your address, number first.`,
                type: defaults.speechOutputType.PLAIN_TEXT
            };

            resolve(response.ask(speechOutput, repromptSpeech));
        });
    }
}
<commit_msg>Fixes a bug in the skill<commit_after>/**
 * @module      com.louisvillemetro.the-city
 * @author      Reydel Leon Machado
 * @copyright   Copyright (c) 2016 Reydel Leon Machado
 * @license     This code is licensed under the MIT license. See the LICENSE file in the project root for license terms.
 */

"use strict";

let defaults = require('../defaults'),
    config   = require('../../../config');

module.exports = handleProvideAddressIntent;

/**
 * Handles the ProvideAddressIntent. This intent is used to process an address that the user has voiced.
 * It will verify the address and in case multiple addresses are found to match the address provided by
 * the user, will cycle through them till the user selects one or discard them all.
 *
 * @param event The request body
 * @param response The response object
 * @returns {*}
 */
function handleProvideAddressIntent(event, response) {
    let streetAddress = event.request.intent.slots.StreetAddress;

    if (streetAddress && streetAddress.value) {
        let googleApiUrl = config.googleGeoApi.url + config.googleGeoApi.format + '?address=' + encodeURI(streetAddress.value) + '&key=' + config.googleGeoApi.key;

        return request.get(googleApiUrl)
                .then(function (verifiedAddress) {
                    verifiedAddress = JSON.parse(verifiedAddress);

                    var speechOutput,
                        repromptSpeech;

                    if (verifiedAddress.status && verifiedAddress.status == 'OK') {
                        speechOutput = {
                            speech: `Thanks! Just to make sure: Is your address ${verifiedAddress.results[0].formatted_address}?`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        repromptSpeech = {
                            speech: `Please, confirm if your address is ${verifiedAddress.results[0].formatted_address}`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        // Segment address for Junk Pickup API consumption
                        var verifiedStreetNumber  = '',
                            verifiedStreetAddress = '',
                            verifiedCity          = '';

                        _.forEach(verifiedAddress.results[0].address_components, function (component) {
                            if (component.types.includes('street_number')) {
                                verifiedStreetNumber = component.long_name;
                            }

                            if (component.types.includes('route')) {
                                verifiedStreetAddress = component.short_name;
                            }

                            if (component.types.includes('locality')) {
                                verifiedCity = component.long_name;
                            }
                        });

                        response._session.attributes.VerifiedStreetAddress = verifiedStreetNumber + ' ' + verifiedStreetAddress;
                        response._session.attributes.VerifiedCity = verifiedCity;
                    } else {
                        speechOutput = {
                            speech: `Uhmmm! The address you provided seems to be invalid. Please, repeat your address, number first. Remember to include your city or state.`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };

                        repromptSpeech = {
                            speech: `Please, repeat your address, number first.`,
                            type: defaults.speechOutputType.PLAIN_TEXT
                        };
                    }

                    return response.ask(speechOutput, repromptSpeech);
                }).catch(function (err) {
                    console.dir(err);

                    let speechOutput = {
                        speech: `I'm having some trouble verifying that address. Please, repeat your address, number first.`,
                        type: defaults.speechOutputType.PLAIN_TEXT
                    };

                    let repromptSpeech = {
                        speech: `Please, repeat your address, number first`,
                        type: defaults.speechOutputType.PLAIN_TEXT
                    };

                    return response.ask(speechOutput, repromptSpeech);
                });
    } else {
        return new Promise(function (resolve) {
            let speechOutput = {
                speech: `I didn't quite get that. Please, repeat your address, number first.`,
                type: defaults.speechOutputType.PLAIN_TEXT
            };

            let repromptSpeech = {
                speech: `Please, repeat your address, number first.`,
                type: defaults.speechOutputType.PLAIN_TEXT
            };

            resolve(response.ask(speechOutput, repromptSpeech));
        });
    }
}
<|endoftext|>


<commit_before>import { TileLayer, Util } from 'leaflet';
import { pointerEvents } from '../Support';
import {
  setEsriAttribution,
  _getAttributionData,
  _updateMapAttribution
} from '../Util';

var tileProtocol = (window.location.protocol !== 'https:') ? 'http:' : 'https:';

export var BasemapLayer = TileLayer.extend({
  statics: {
    TILES: {
      Streets: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Street_Map'
        }
      },
      Topographic: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Topo_Map'
        }
      },
      Oceans: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/Ocean_Basemap'
        }
      },
      OceansLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      NationalGeographic: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'National Geographic, DeLorme, HERE, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, increment P Corp.'
        }
      },
      DarkGray: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'HERE, DeLorme, MapmyIndia, © OpenStreetMap contributors'
        }
      },
      DarkGrayLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''

        }
      },
      Gray: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'HERE, DeLorme, MapmyIndia, © OpenStreetMap contributors'
        }
      },
      GrayLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      Imagery: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'DigitalGlobe, GeoEye, i-cubed, USDA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Imagery'
        }
      },
      ImageryLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      ImageryTransportation: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      ShadedRelief: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          attribution: 'USGS'
        }
      },
      ShadedReliefLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 12,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      Terrain: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA'
        }
      },
      TerrainLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      USATopo: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 15,
          subdomains: ['server', 'services'],
          attribution: 'USGS, National Geographic Society, i-cubed'
        }
      },
      ImageryClarity: {
        urlTemplate: tileProtocol + '//clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
        }
      },
      ImageryFirefly: {
        urlTemplate: tileProtocol + '//fly.maptiles.arcgis.com/arcgis/rest/services/World_Imagery_Firefly/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
        }
      }
    }
  },

  initialize: function (key, options) {
    var config;

    // set the config variable with the appropriate config object
    if (typeof key === 'object' && key.urlTemplate && key.options) {
      config = key;
    } else if (typeof key === 'string' && BasemapLayer.TILES[key]) {
      config = BasemapLayer.TILES[key];
    } else {
      throw new Error('L.esri.BasemapLayer: Invalid parameter. Use one of "Streets", "Topographic", "Oceans", "OceansLabels", "NationalGeographic", "Gray", "GrayLabels", "DarkGray", "DarkGrayLabels", "Imagery", "ImageryLabels", "ImageryTransportation", "ImageryClarity", "ImageryFirefly", "ShadedRelief", "ShadedReliefLabels", "Terrain", "TerrainLabels" or "USATopo"');
    }

    // merge passed options into the config options
    var tileOptions = Util.extend(config.options, options);

    Util.setOptions(this, tileOptions);

    if (this.options.token && config.urlTemplate.indexOf('token=') === -1) {
      config.urlTemplate += ('?token=' + this.options.token);
    }

    // call the initialize method on L.TileLayer to set everything up
    TileLayer.prototype.initialize.call(this, config.urlTemplate, tileOptions);
  },

  onAdd: function (map) {
    // include 'Powered by Esri' in map attribution
    setEsriAttribution(map);

    if (this.options.pane === 'esri-labels') {
      this._initPane();
    }
    // some basemaps can supply dynamic attribution
    if (this.options.attributionUrl) {
      _getAttributionData(this.options.attributionUrl, map);
    }

    map.on('moveend', _updateMapAttribution);

    TileLayer.prototype.onAdd.call(this, map);
  },

  onRemove: function (map) {
    map.off('moveend', _updateMapAttribution);
    TileLayer.prototype.onRemove.call(this, map);
  },

  _initPane: function () {
    if (!this._map.getPane(this.options.pane)) {
      var pane = this._map.createPane(this.options.pane);
      pane.style.pointerEvents = 'none';
      pane.style.zIndex = 500;
    }
  },

  getAttribution: function () {
    if (this.options.attribution) {
      var attribution = '' + this.options.attribution + '';
    }
    return attribution;
  }
});

export function basemapLayer (key, options) {
  return new BasemapLayer(key, options);
}

export default basemapLayer;
<commit_msg>ensure Firefly updates attribution dynamically<commit_after>import { TileLayer, Util } from 'leaflet';
import { pointerEvents } from '../Support';
import {
  setEsriAttribution,
  _getAttributionData,
  _updateMapAttribution
} from '../Util';

var tileProtocol = (window.location.protocol !== 'https:') ? 'http:' : 'https:';

export var BasemapLayer = TileLayer.extend({
  statics: {
    TILES: {
      Streets: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Street_Map'
        }
      },
      Topographic: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Topo_Map'
        }
      },
      Oceans: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA',
          attributionUrl: 'https://static.arcgis.com/attribution/Ocean_Basemap'
        }
      },
      OceansLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      NationalGeographic: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'National Geographic, DeLorme, HERE, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, increment P Corp.'
        }
      },
      DarkGray: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'HERE, DeLorme, MapmyIndia, © OpenStreetMap contributors'
        }
      },
      DarkGrayLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''

        }
      },
      Gray: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          attribution: 'HERE, DeLorme, MapmyIndia, © OpenStreetMap contributors'
        }
      },
      GrayLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 16,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      Imagery: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          attribution: 'DigitalGlobe, GeoEye, i-cubed, USDA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, and the GIS User Community',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Imagery'
        }
      },
      ImageryLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      ImageryTransportation: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      ShadedRelief: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          attribution: 'USGS'
        }
      },
      ShadedReliefLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 12,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      Terrain: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          attribution: 'USGS, NOAA'
        }
      },
      TerrainLabels: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 13,
          subdomains: ['server', 'services'],
          pane: (pointerEvents) ? 'esri-labels' : 'tilePane',
          attribution: ''
        }
      },
      USATopo: {
        urlTemplate: tileProtocol + '//{s}.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 15,
          subdomains: ['server', 'services'],
          attribution: 'USGS, National Geographic Society, i-cubed'
        }
      },
      ImageryClarity: {
        urlTemplate: tileProtocol + '//clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community'
        }
      },
      ImageryFirefly: {
        urlTemplate: tileProtocol + '//fly.maptiles.arcgis.com/arcgis/rest/services/World_Imagery_Firefly/MapServer/tile/{z}/{y}/{x}',
        options: {
          minZoom: 1,
          maxZoom: 19,
          attribution: 'Esri, DigitalGlobe, GeoEye, Earthstar Geographics, CNES/Airbus DS, USDA, USGS, AeroGRID, IGN, and the GIS User Community',
          attributionUrl: 'https://static.arcgis.com/attribution/World_Imagery'
        }
      }
    }
  },

  initialize: function (key, options) {
    var config;

    // set the config variable with the appropriate config object
    if (typeof key === 'object' && key.urlTemplate && key.options) {
      config = key;
    } else if (typeof key === 'string' && BasemapLayer.TILES[key]) {
      config = BasemapLayer.TILES[key];
    } else {
      throw new Error('L.esri.BasemapLayer: Invalid parameter. Use one of "Streets", "Topographic", "Oceans", "OceansLabels", "NationalGeographic", "Gray", "GrayLabels", "DarkGray", "DarkGrayLabels", "Imagery", "ImageryLabels", "ImageryTransportation", "ImageryClarity", "ImageryFirefly", "ShadedRelief", "ShadedReliefLabels", "Terrain", "TerrainLabels" or "USATopo"');
    }

    // merge passed options into the config options
    var tileOptions = Util.extend(config.options, options);

    Util.setOptions(this, tileOptions);

    if (this.options.token && config.urlTemplate.indexOf('token=') === -1) {
      config.urlTemplate += ('?token=' + this.options.token);
    }

    // call the initialize method on L.TileLayer to set everything up
    TileLayer.prototype.initialize.call(this, config.urlTemplate, tileOptions);
  },

  onAdd: function (map) {
    // include 'Powered by Esri' in map attribution
    setEsriAttribution(map);

    if (this.options.pane === 'esri-labels') {
      this._initPane();
    }
    // some basemaps can supply dynamic attribution
    if (this.options.attributionUrl) {
      _getAttributionData(this.options.attributionUrl, map);
    }

    map.on('moveend', _updateMapAttribution);

    TileLayer.prototype.onAdd.call(this, map);
  },

  onRemove: function (map) {
    map.off('moveend', _updateMapAttribution);
    TileLayer.prototype.onRemove.call(this, map);
  },

  _initPane: function () {
    if (!this._map.getPane(this.options.pane)) {
      var pane = this._map.createPane(this.options.pane);
      pane.style.pointerEvents = 'none';
      pane.style.zIndex = 500;
    }
  },

  getAttribution: function () {
    if (this.options.attribution) {
      var attribution = '' + this.options.attribution + '';
    }
    return attribution;
  }
});

export function basemapLayer (key, options) {
  return new BasemapLayer(key, options);
}

export default basemapLayer;
<|endoftext|>


<commit_before>//dependencies
var utils = require('./utils');
var config = require('../config/config');
var mongoose = require('mongoose');
var should = require('should');
var request = require('supertest');
var _ = require('underscore');
var im = require('imagemagick');

// express server
var app = require('../app.js');
var key;

describe('#bw', function() {
  before(function(done) {
    _.each(mongoose.connection.collections, function(item) {
      item.remove(function(err) {
        if (err) {
          throw new Error('</3', err);
        }
      });
    });

    return done();
  });

  before(function(done) {
    request(app)
    .post('/upload?imgUrl=http://maxcdn.thedesigninspiration.com/wp-content/uploads/2009/09/cute-animals/baby01.jpg')
    .expect(201)
    .end(function(err, res) {
      if (err) {
        return done(err);
      }

      key = JSON.parse(res.text).imgId;
      return done();
    });
  });

  it('transforms image to have a gray colorspace', function(done) {
    request(app)
    .get('/' + key + '/bw')
    .expect(200)
    .end(function(err, res) {
      if (err) {
        return done(err);
      }

      im.identify(process.env.LOCAL_FILE_PATH + '/' + JSON.parse(res.text).imgId + '.jpg', function(err, features) {
        if (err) {
          return done(err);
        }

        features.colorspace.should.equal('Gray');

        return done();
      });
    });
  });
});<commit_msg>add blur and charcoal specs<commit_after>//dependencies
var utils = require('./utils');
var config = require('../config/config');
var mongoose = require('mongoose');
var should = require('should');
var request = require('supertest');
var _ = require('underscore');
var im = require('imagemagick');
process.env.NODE_ENV = 'test';

// express server
var app = require('../app.js');
var key;

before(function(done) {
  request(app)
  .put('/upload?src=http://maxcdn.thedesigninspiration.com/wp-content/uploads/2009/09/cute-animals/baby01.jpg')
  .expect(201)
  .end(function(err, res) {
    if (err) {
      return done(err);
    }

    key = JSON.parse(res.text).id;
    return done();
  });
});

describe('#filters', function() {
  describe('blur', function() {
    it('should respond 200 when blur is uploaded', function() {
      request(app)
      .get('/' + key + '/blur')
      .expect(200)
      .end(function(err, res) {
        if (err) {
          return done(err);
        }

        var filter = JSON.parse(res.text).id.filter;
        filter.should.equal('blur');
      });
    });
  });

  describe('charcoal', function() {
    it('should respond 200 when blur is uploaded', function() {
      request(app)
      .get('/' + key + '/charcoal')
      .expect(200)
      .end(function(err, res) {
        if (err) {
          return done(err);
        }

        var filter = JSON.parse(res.text).id.filter;
        filter.should.equal('charcoal');
      });
    });
  });

  describe('bw_grad', function() {
    it('transforms image to have a gray colorspace', function(done) {
      request(app)
      .get('/' + key + '/bw_grad')
      .expect(200)
      .end(function(err, res) {
        if (err) {
          return done(err);
        }

        im.identify(process.env.LOCAL_FILE_PATH + '/' + JSON.parse(res.text).id + '.jpg', function(err, features) {
          if (err) {
            return done(err);
          }

          features.colorspace.should.equal('Gray');

          return done();
        });
      });
    });
  });
});

<|endoftext|>


<commit_before>import { EOL } from 'os';
import { remote, ipcRenderer } from 'electron';
import _ from 'underscore';
import ServerConfig from '../models/ServerConfig';
import Socket from '../utils/Socket';
import $ from 'jquery';
import { Events } from 'backbone';

const events = {
  ...Events,
};

export { events };

const getLocalServer = _.once(() => (remote.getGlobal('localServer')));

let currentConnection = null;
let debugLog = '';

function log(msg) {
  if (typeof msg !== 'string') {
    throw new Error('Please provide a message.');
  }

  if (!msg) return;

  const logMsg = `[SERVER-CONNECT] ${msg}${EOL}`;
  debugLog += logMsg;
  ipcRenderer.send('server-connect-log', logMsg);
}

export function getDebugLog() {
  return debugLog;
}

export function getCurrentConnection() {
  return currentConnection;
}

function socketConnect(socket) {
  // todo: validate args

  const deferred = $.Deferred();

  const onOpen = () => deferred.resolve();
  const onClose = (e) => deferred.reject('failed', e);

  const cancel = () => {
    socket.close();
    socket.off(null, onOpen);
    socket.off(null, onClose);
    deferred.reject('canceled');
  };

  socket.on('open', onOpen);
  socket.on('close', onClose);
  socket.connect();

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

function authenticate(server) {
  // todo: validate args

  const deferred = $.Deferred();

  const fetchConfig = $.get(`${server.httpUrl}/ob/config`)
    .done(() => deferred.resolve())
    .fail((e) => deferred.reject('failed', e));

  const cancel = () => {
    deferred.reject('canceled');
    fetchConfig.abort();
  };

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

export default function connect(server, options = {}) {
  if (!server instanceof ServerConfig) {
    throw new Error('Please provide a server as a ServerConfig instance.');
  }

  const curCon = getCurrentConnection();

  if (curCon && curCon.server.id === server.id &&
    (curCon.status === 'connected' || curCon.status === 'connecting')) {
    throw new Error('You are already connected or connecting to the given server');
  }

  log(`[${server.id.slice(0, 8)}] Will attempt to connect to server "${server.get('name')}"` +
    ` at ${server.get('serverIp')}.`);

  const opts = {
    attempts: 2,
    timeoutBetweenAttempts: 2000,
    maxAttemptTime: 5000,
    // todo: work this one in
    restartDefaultServerOnFirstAttempt: true,
    ...options,
  };

  const deferred = $.Deferred();
  const localServer = getLocalServer();
  let attempt = 1;
  let socket = null;
  let connectAttempt = null;
  let cancel = null;

  const getPromiseData = (data = {}, getPromiseOpts = {}) => {
    const getPromiseDataOpts = {
      includeAttemptData: true,
      ...getPromiseOpts,
    };

    let aggregateData = {
      localServer,
      server,
      socket,
      ...data,
    };

    if (getPromiseDataOpts.includeAttemptData) {
      aggregateData = {
        ...aggregateData,
        connectAttempt: attempt,
        totalConnectAttempts: opts.attempts,
      };
    }

    if (localServer) aggregateData.localServer = localServer;

    return aggregateData;
  };

  const notify = (e) => {
    currentConnection = {
      ...getPromiseData(e),
      cancel,
    };

    events.trigger(e.status, getPromiseData(e));
    deferred.notify(getPromiseData(e));
  };

  const reject = (e) => {
    currentConnection = {
      ...getPromiseData(e),
      status: 'connect-attempt-failed',
    };

    events.trigger('connect-attempt-failed', getPromiseData(e));
    deferred.reject(getPromiseData(e));
  };

  const resolve = (e) => {
    const data = getPromiseData(e);

    currentConnection = {
      ...data,
      status: 'connected',
    };

    data.socket.on('close', (connectionLostE) => {
      const connectionLostEventData = getPromiseData({ socketCloseEvent: connectionLostE },
        { includeAttemptData: false });
      events.trigger('disconnect', connectionLostEventData);
      if (!currentConnection || currentConnection.socket === data.socket) {
        currentConnection = {
          ...data,
          status: 'disconnected',
        };
      }
    });

    events.trigger('connected', getPromiseData(e));
    deferred.resolve(getPromiseData(e));
  };

  cancel = () => {
    if (connectAttempt) connectAttempt.cancel();
  };

  // If we're not connecting to the local bundled server,
  // then let's ensure it's stopped.
  if (!server.get('default') && localServer && localServer.isRunning) {
    deferred.notify({
      status: 'stopping-local-server',
      localServer,
    });

    localServer.stop();
  }

  if (curCon) {
    if (curCon.cancel) curCon.cancel();
    if (curCon.socket) curCon.socket.close();
  }

  socket = new Socket(server.socketUrl);

  const innerConnect = () => {
    const innerConnectDeferred = $.Deferred();
    let socketConnectAttempt = null;
    const connectCancel = (reason) => {
      innerConnectDeferred.reject(reason || 'canceled');
      if (socketConnectAttempt) socketConnectAttempt.cancel();
    };

    if (server.get('default') && !localServer) {
      // This should never happen to normal users. The only way it would is if you are a dev
      // and mucking with localStorage and / or fudging the source for the app to masquerade
      // as a bundled app.
      throw new Error('The default configuration should only be used on the bundled app.');
    }

    if (server.get('default') && !localServer.isRunning && !localServer.isStopping) {
      // If we're connecting to the bundled server and the local server is not
      // running or in the process of stopping, we'll start it.
      innerConnectDeferred.notify('starting-local-server');

      localServer.on('start', () => {
        innerConnectDeferred.notify('connecting');

        socketConnectAttempt = socketConnect(socket)
          .done(() => {
            innerConnectDeferred.resolve('connected');
          }).fail((reason, e) => {
            if (reason === 'canceled') {
              innerConnectDeferred.reject('canceled');
            } else {
              innerConnectDeferred.reject('socket-connect-failed', { socketCloseEvent: e });
            }
          });
      });

      localServer.start();
    } else {
      innerConnectDeferred.notify('connecting');

      socketConnectAttempt = socketConnect(socket)
        .done(() => {
          if (server.needsAuthentication()) {
            innerConnectDeferred.notify('authenticating');
            authenticate(server)
              .done(() => innerConnectDeferred.resolve('connected'))
              .fail((reason, e) => {
                innerConnectDeferred.reject('authentication-failed', { failedAuthEvent: e });
              });
          } else {
            innerConnectDeferred.resolve('connected');
          }
        }).fail((reason, e) => {
          if (reason === 'canceled') {
            innerConnectDeferred.reject('canceled');
          } else {
            innerConnectDeferred.reject('socket-connect-failed', { socketCloseEvent: e });
          }
        });
    }

    const promise = innerConnectDeferred.promise();
    promise.cancel = connectCancel;

    return promise;
  };

  let connectAttemptStartTime = null;
  const attemptConnection = () => {
    let maxTimeTimeout = null;
    let nextAttemptTimeout = null;
    connectAttemptStartTime = Date.now();

    const innerConnectAttempt = innerConnect()
      .progress((status, data = {}) => notify({ status, ...data }))
      .done((status, data = {}) => resolve({ status, ...data }))
      .fail((status, data = {}) => {
        if (attempt === opts.attempts || status === 'authentication-failed' ||
          status === 'outer-connect-attempt-canceled') {
          let reason;

          if (status === 'socket-connect-failed') {
            reason = 'unable-to-reach-server';
          } else if (status === 'outer-connect-attempt-canceled') {
            reason = 'canceled';
          } else {
            reason = status;
          }

          reject({
            reason,
            ...data,
          });
        } else {
          const delay = opts.timeoutBetweenAttempts - (Date.now() - connectAttemptStartTime);

          nextAttemptTimeout = setTimeout(() => {
            attempt += 1;
            connectAttempt = attemptConnection();
          }, delay < 0 ? 0 : delay);
        }
      })
      .always(() => clearTimeout(maxTimeTimeout));

    const attemptConnectionCancel = () => {
      reject({ reason: 'canceled' });
      clearTimeout(maxTimeTimeout);
      clearTimeout(nextAttemptTimeout);
      innerConnectAttempt.cancel('outer-connect-attempt-canceled');
    };

    maxTimeTimeout = setTimeout(() => {
      if (innerConnectAttempt.state() === 'pending') innerConnectAttempt.cancel('timed-out');
    }, opts.maxAttemptTime);

    return { cancel: attemptConnectionCancel };
  };

  connectAttempt = attemptConnection();

  // wire in some logging
  deferred.progress(e => {
    log(`[${server.id.slice(0, 8)}] Status is "${e.status}" for connect attempt` +
      ` ${e.connectAttempt} of ${e.totalConnectAttempts}.`);
  }).done((e) => {
    log(`[${server.id.slice(0, 8)}] Connected to "${e.server.get('name')}"`);

    e.socket.on('close', () => {
      const logMsg = `[${e.server.id.slice(0, 8)}] Disconnected from ` +
        `"${e.server.get('name')}"`;
      log(logMsg);
    });
  }).fail((e) => {
    log(`[${server.id.slice(0, 8)}] Failed to connect to "${e.server.get('name')}"`);
    log(` ====> Reason: ${e.status}`);

    if (e.socketCloseEvent) {
      log(` ====> Code: ${e.socketCloseEvent.code}`);
      if (e.socketCloseEvent.reason) log(`Reason: ${e.socketCloseEvent.reason}`);
    } else if (e.failedAuthEvent) {
      log(` ====> Status: ${e.failedAuthEvent.status}`);
      log(` ====> Status text: ${e.failedAuthEvent.statusText}`);

      if (e.failedAuthEvent.responseText) {
        log(` ====> Response text: ${e.failedAuthEvent.responseText}`);
      }
    }
  });

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

ipcRenderer.send('server-connect-ready');
log('Browser has been started or refreshed.');
<commit_msg>added documentation to the ServerConnect module<commit_after>import { EOL } from 'os';
import { remote, ipcRenderer } from 'electron';
import _ from 'underscore';
import ServerConfig from '../models/ServerConfig';
import Socket from '../utils/Socket';
import $ from 'jquery';
import { Events } from 'backbone';

/*
  The module is used to establish a connection with a server as well as monitor
  the state of any current connection.

  To establish a connection call connect().

  To get a snapshot of a current connection, call getCurrentConnection().

  To be informed of the state of a connection as it changes - If you initiated the
  connection (via a call to connect()),you can use the promise handlers (progress,
  done, fail) of the promise returned via the connect() call. Alternatively,
  particularly if you weren't the one that made the connect() call, you can bind
  to the { events } object exported in this module, which will fire relevant events
  ('connecting', 'connected', etc...).
*/

const events = {
  ...Events,
};

export { events };

const getLocalServer = _.once(() => (remote.getGlobal('localServer')));

let currentConnection = null;
let debugLog = '';

function log(msg) {
  if (typeof msg !== 'string') {
    throw new Error('Please provide a message.');
  }

  if (!msg) return;

  const logMsg = `[SERVER-CONNECT] ${msg}${EOL}`;
  debugLog += logMsg;
  ipcRenderer.send('server-connect-log', logMsg);
}

export function getDebugLog() {
  return debugLog;
}

/**
 * Returns an object with data about the current connection, incuding
 * the status (e.g. 'connecting', 'connected'...) and any relevant data
 * (e.g. the Socket instance, the Server Configuration model...).
 *
 * If you're looking to bind to Socket events, you would call this method
 * and then bind to the Socket instance that's returned. Keep in mind that
 * until the first connection is made, this method will return null, in which
 * case you'd wanted to wait until the 'connected' event to do your binding.
 *
 * But, since the app won't proceed out of Start.js, until a connection is
 * made, it's really only in Start.js that you need to wait for the event
 * and beyond Start.js you should just be able to get the socket via this
 * method.
 */
export function getCurrentConnection() {
  return currentConnection;
}

function socketConnect(socket) {
  // todo: validate args

  const deferred = $.Deferred();

  const onOpen = () => deferred.resolve();
  const onClose = (e) => deferred.reject('failed', e);

  const cancel = () => {
    socket.close();
    socket.off(null, onOpen);
    socket.off(null, onClose);
    deferred.reject('canceled');
  };

  socket.on('open', onOpen);
  socket.on('close', onClose);
  socket.connect();

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

function authenticate(server) {
  // todo: validate args

  const deferred = $.Deferred();

  const fetchConfig = $.get(`${server.httpUrl}/ob/config`)
    .done(() => deferred.resolve())
    .fail((e) => deferred.reject('failed', e));

  const cancel = () => {
    deferred.reject('canceled');
    fetchConfig.abort();
  };

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

/**
 * Called to establish a connection with a server. This involves ensuring the
 * local server is running, if attempting to connect to the Default
 * (i.e. local bundled) connection. It also involves opening a websocket connection
 * with the server. Additionally, if the configuration requires authentication,
 * it will call a server endpoint to make sure authentication was successful.
 *
 * @param {object} server - A ServerConfig instance representing the server you want to
 *   connect to.
 * @param {object} [options={}] - Options determining certain behavior of the connection
 *   process.
 * @param {number} [options.attempts=2] - The number of connection attempts to make. During
 *   the initial connection process (e.g. at startup), it is useful to have multiple attempts
 *   since the server may still be in the process of starting up.
 * @param {number} [options.timeoutBetweenAttempts=2000] - Number of milliseconds to wait
 *   after an unsuccessful connection attempt before starting the next one. If a server is
 *   is down, in most cases the failure will be instant, so it is important to have a timeout
 *   inbetween connection attempts, otherwise they will all happen in a fraction of a second,
 *   never giving a server a chance to startup.
 * @param {number} [options.maxAttemptTime=5000] - The maximum amount of milliseconds before
 *   giving up on a connection attempt (and moving on to the next one if there is one) and
 *   considering it a failure. This is particularly important if you are attempting to connect
 *   to a remote server with a non numeric ServerIp value. Since it's trying to get the name
 *   via DNS, it will take over a minute before it fails. The recommendation is to not make this
 *   value too large during start-up, or you risk making a very long start-up sequence,
 *   particularly if you have multiple attempts. In the Connection Managament modal, the value
 *   could be much higher and the user could always cancel the attempt if they feel it is
 *   taking too long.
 * @return {object} A promise object will be returned that will allow you
 *   to monitor the progress and result of the connection via progress, done
 *   and fail handlers. The object will also include a cancel function, whereby
 *   you can cancel the connection attempt.
 */
export default function connect(server, options = {}) {
  if (!server instanceof ServerConfig) {
    throw new Error('Please provide a server as a ServerConfig instance.');
  }

  const curCon = getCurrentConnection();

  if (curCon && curCon.server.id === server.id &&
    (curCon.status === 'connected' || curCon.status === 'connecting')) {
    throw new Error('You are already connected or connecting to the given server');
  }

  log(`[${server.id.slice(0, 8)}] Will attempt to connect to server "${server.get('name')}"` +
    ` at ${server.get('serverIp')}.`);

  const opts = {
    attempts: 2,
    timeoutBetweenAttempts: 2000,
    maxAttemptTime: 5000,
    // todo: work this one in
    restartDefaultServerOnFirstAttempt: true,
    ...options,
  };

  const deferred = $.Deferred();
  const localServer = getLocalServer();
  let attempt = 1;
  let socket = null;
  let connectAttempt = null;
  let cancel = null;

  const getPromiseData = (data = {}, getPromiseOpts = {}) => {
    const getPromiseDataOpts = {
      includeAttemptData: true,
      ...getPromiseOpts,
    };

    let aggregateData = {
      localServer,
      server,
      socket,
      ...data,
    };

    if (getPromiseDataOpts.includeAttemptData) {
      aggregateData = {
        ...aggregateData,
        connectAttempt: attempt,
        totalConnectAttempts: opts.attempts,
      };
    }

    if (localServer) aggregateData.localServer = localServer;

    return aggregateData;
  };

  const notify = (e) => {
    currentConnection = {
      ...getPromiseData(e),
      cancel,
    };

    events.trigger(e.status, getPromiseData(e));
    deferred.notify(getPromiseData(e));
  };

  const reject = (e) => {
    currentConnection = {
      ...getPromiseData(e),
      status: 'connect-attempt-failed',
    };

    events.trigger('connect-attempt-failed', getPromiseData(e));
    deferred.reject(getPromiseData(e));
  };

  const resolve = (e) => {
    const data = getPromiseData(e);

    currentConnection = {
      ...data,
      status: 'connected',
    };

    data.socket.on('close', (connectionLostE) => {
      const connectionLostEventData = getPromiseData({ socketCloseEvent: connectionLostE },
        { includeAttemptData: false });
      events.trigger('disconnect', connectionLostEventData);
      if (!currentConnection || currentConnection.socket === data.socket) {
        currentConnection = {
          ...data,
          status: 'disconnected',
        };
      }
    });

    events.trigger('connected', getPromiseData(e));
    deferred.resolve(getPromiseData(e));
  };

  cancel = () => {
    if (connectAttempt) connectAttempt.cancel();
  };

  // If we're not connecting to the local bundled server,
  // then let's ensure it's stopped.
  if (!server.get('default') && localServer && localServer.isRunning) {
    deferred.notify({
      status: 'stopping-local-server',
      localServer,
    });

    localServer.stop();
  }

  if (curCon) {
    if (curCon.cancel) curCon.cancel();
    if (curCon.socket) curCon.socket.close();
  }

  socket = new Socket(server.socketUrl);

  const innerConnect = () => {
    const innerConnectDeferred = $.Deferred();
    let socketConnectAttempt = null;
    const connectCancel = (reason) => {
      innerConnectDeferred.reject(reason || 'canceled');
      if (socketConnectAttempt) socketConnectAttempt.cancel();
    };

    if (server.get('default') && !localServer) {
      // This should never happen to normal users. The only way it would is if you are a dev
      // and mucking with localStorage and / or fudging the source for the app to masquerade
      // as a bundled app.
      throw new Error('The default configuration should only be used on the bundled app.');
    }

    if (server.get('default') && !localServer.isRunning && !localServer.isStopping) {
      // If we're connecting to the bundled server and the local server is not
      // running or in the process of stopping, we'll start it.
      innerConnectDeferred.notify('starting-local-server');

      localServer.on('start', () => {
        innerConnectDeferred.notify('connecting');

        socketConnectAttempt = socketConnect(socket)
          .done(() => {
            innerConnectDeferred.resolve('connected');
          }).fail((reason, e) => {
            if (reason === 'canceled') {
              innerConnectDeferred.reject('canceled');
            } else {
              innerConnectDeferred.reject('socket-connect-failed', { socketCloseEvent: e });
            }
          });
      });

      localServer.start();
    } else {
      innerConnectDeferred.notify('connecting');

      socketConnectAttempt = socketConnect(socket)
        .done(() => {
          if (server.needsAuthentication()) {
            innerConnectDeferred.notify('authenticating');
            authenticate(server)
              .done(() => innerConnectDeferred.resolve('connected'))
              .fail((reason, e) => {
                innerConnectDeferred.reject('authentication-failed', { failedAuthEvent: e });
              });
          } else {
            innerConnectDeferred.resolve('connected');
          }
        }).fail((reason, e) => {
          if (reason === 'canceled') {
            innerConnectDeferred.reject('canceled');
          } else {
            innerConnectDeferred.reject('socket-connect-failed', { socketCloseEvent: e });
          }
        });
    }

    const promise = innerConnectDeferred.promise();
    promise.cancel = connectCancel;

    return promise;
  };

  let connectAttemptStartTime = null;
  const attemptConnection = () => {
    let maxTimeTimeout = null;
    let nextAttemptTimeout = null;
    connectAttemptStartTime = Date.now();

    const innerConnectAttempt = innerConnect()
      .progress((status, data = {}) => notify({ status, ...data }))
      .done((status, data = {}) => resolve({ status, ...data }))
      .fail((status, data = {}) => {
        if (attempt === opts.attempts || status === 'authentication-failed' ||
          status === 'outer-connect-attempt-canceled') {
          let reason;

          if (status === 'socket-connect-failed') {
            reason = 'unable-to-reach-server';
          } else if (status === 'outer-connect-attempt-canceled') {
            reason = 'canceled';
          } else {
            reason = status;
          }

          reject({
            reason,
            ...data,
          });
        } else {
          const delay = opts.timeoutBetweenAttempts - (Date.now() - connectAttemptStartTime);

          nextAttemptTimeout = setTimeout(() => {
            attempt += 1;
            connectAttempt = attemptConnection();
          }, delay < 0 ? 0 : delay);
        }
      })
      .always(() => clearTimeout(maxTimeTimeout));

    const attemptConnectionCancel = () => {
      reject({ reason: 'canceled' });
      clearTimeout(maxTimeTimeout);
      clearTimeout(nextAttemptTimeout);
      innerConnectAttempt.cancel('outer-connect-attempt-canceled');
    };

    maxTimeTimeout = setTimeout(() => {
      if (innerConnectAttempt.state() === 'pending') innerConnectAttempt.cancel('timed-out');
    }, opts.maxAttemptTime);

    return { cancel: attemptConnectionCancel };
  };

  connectAttempt = attemptConnection();

  // wire in some logging
  deferred.progress(e => {
    log(`[${server.id.slice(0, 8)}] Status is "${e.status}" for connect attempt` +
      ` ${e.connectAttempt} of ${e.totalConnectAttempts}.`);
  }).done((e) => {
    log(`[${server.id.slice(0, 8)}] Connected to "${e.server.get('name')}"`);

    e.socket.on('close', () => {
      const logMsg = `[${e.server.id.slice(0, 8)}] Disconnected from ` +
        `"${e.server.get('name')}"`;
      log(logMsg);
    });
  }).fail((e) => {
    log(`[${server.id.slice(0, 8)}] Failed to connect to "${e.server.get('name')}"`);
    log(` ====> Reason: ${e.status}`);

    if (e.socketCloseEvent) {
      log(` ====> Code: ${e.socketCloseEvent.code}`);
      if (e.socketCloseEvent.reason) log(`Reason: ${e.socketCloseEvent.reason}`);
    } else if (e.failedAuthEvent) {
      log(` ====> Status: ${e.failedAuthEvent.status}`);
      log(` ====> Status text: ${e.failedAuthEvent.statusText}`);

      if (e.failedAuthEvent.responseText) {
        log(` ====> Response text: ${e.failedAuthEvent.responseText}`);
      }
    }
  });

  const promise = deferred.promise();
  promise.cancel = cancel;

  return promise;
}

ipcRenderer.send('server-connect-ready');
log('Browser has been started or refreshed.');
<|endoftext|>


<commit_before>(function(window) {
    var document = window.document,
        navigator = window.navigator,
        msPointerEnabled = navigator.msPointerEnabled,
        TOUCH_EVENTS = {
            start: msPointerEnabled ? 'MSPointerDown' : 'touchstart',
            move: msPointerEnabled ? 'MSPointerMove' : 'touchmove',
            end: msPointerEnabled ? 'MSPointerUp' : 'touchend'
        },
        dummyStyle = document.createElement('div').style,
        vendor = (function () {
            var vendors = 't,webkitT,MozT,msT,OT'.split(','),
                t,
                i = 0,
                l = vendors.length;

            for (; i < l; i++) {
                t = vendors[i] + 'ransform';
                if (t in dummyStyle) {
                    return vendors[i].substr(0, vendors[i].length - 1);
                }
            }

            return false;
        }()),
        prefixStyle = function(style) {
            if (vendor === '') return style;
            style = style.charAt(0).toUpperCase() + style.substr(1);
            return vendor + style;
        },
        transition = prefixStyle('transition'),
        transitionEndEvent = (function() {
            if (vendor == 'webkit' || vendor === 'O') {
                return vendor.toLowerCase() + 'TransitionEnd';
            }
            return 'transitionend';
        }()),
        listenTransition = function(target, duration, callbackFn) {
            var me = this,
                clear = function() {
                    if (target.transitionTimer) clearTimeout(target.transitionTimer);
                    target.transitionTimer = null;
                    target.removeEventListener(transitionEndEvent, handler, false);
                },
                handler = function() {
                    clear();
                    if (callbackFn) callbackFn.call(me);
                };
            clear();
            target.addEventListener(transitionEndEvent, handler, false);
            target.transitionTimer = setTimeout(handler, duration + 100);
        };

    var DragLoader = function(options) {
        options = options || {};
        this.options = options;
        this.ct = options.ct ? (typeof options.ct === 'string' ? document.querySelector(options.ct) : options.ct) : document.body;
        if (typeof options.threshold === 'undefined') options.threshold = 80;
        if (typeof options.dragDownThreshold === 'undefined') options.dragDownThreshold = options.threshold;
        if (typeof options.dragUpThreshold === 'undefined') options.dragUpThreshold = options.threshold;

        this._events = {};
        this._draggable = true;

        this.ct.addEventListener(TOUCH_EVENTS.start, this, false);
    };

    DragLoader.prototype = {
        STATUS: {
            default: 'default',
            prepare: 'prepare',
            load: 'load'
        },

        _createDragDownRegion: function() {
            this._removeDragDownRegion();
            this.header = document.createElement('div');
            this.header.style.cssText = 'position:relative;top:0;left:0;margin:0;padding:0;overflow:hidden;width:100%;height:0px;';
            this.header.className = this.options.dragDownRegionCls || '';
            this._touchCoords.status = this._processStatus('down', 0, null, true);
            this.ct.insertBefore(this.header, this.ct.children[0]);
            return this.header;
        },

        _removeDragDownRegion: function() {
            if (this.header) {
                this.ct.removeChild(this.header);
                this.header = null;
            }
        },

        _createDragUpRegion: function() {
            this._removeDragUpRegion();
            this.footer = document.createElement('div');
            this.footer.style.cssText = 'position:relative;bottom:0;left:0;margin:0;padding:0;overflow:hidden;width:100%;height:0px;';
            this.footer.className = this.options.dragUpRegionCls || '';
            this._touchCoords.status = this._processStatus('up', 0, null, true);
            this.ct.appendChild(this.footer);
            return this.footer;
        },

        _removeDragUpRegion: function() {
            if (this.footer) {
                this.ct.removeChild(this.footer);
                this.footer = null;
            }
        },

        _processDragDownHelper: function(status) {
            var options = this.options,
                helper = options.dragDownHelper;
            if (!options.preventDragHelper && helper) {
                this.header.innerHTML = helper.call(this, status);
            }
        },

        _processDragUpHelper: function(status) {
            var options = this.options,
                helper = options.dragUpHelper;
            if (!options.preventDragHelper && helper) {
                this.footer.innerHTML = helper.call(this, status);
            }
        },

        /*
         * status:
         *   default 默认状态
         *   prepare 释放加载
         *   load 加载
         */
        _processStatus: function(orient, offsetY, currentStatus, moved) {
            var options = this.options,
                STATUS = this.STATUS,
                overflow, nextStatus = currentStatus,
                upperStr;
            if (orient) {
                upperStr = orient.charAt(0).toUpperCase() + orient.substr(1);
                overflow = offsetY > options['drag' + upperStr + 'Threshold'];
                if (!overflow && currentStatus != STATUS.default) {
                    this['_processDrag' + upperStr + 'Helper'](STATUS.default);
                    this._fireEvent('drag' + upperStr + 'Default');
                    nextStatus = STATUS.default;
                } else if (moved && overflow && currentStatus != STATUS.prepare) {
                    this['_processDrag' + upperStr + 'Helper'](STATUS.prepare);
                    this._fireEvent('drag' + upperStr + 'Prepare');
                    nextStatus = STATUS.prepare;
                } else if (!moved && overflow && currentStatus != STATUS.load) {
                    this['_processDrag' + upperStr + 'Helper'](STATUS.load);
                    this._fireEvent('drag' + upperStr + 'Load');
                    nextStatus = STATUS.load;
                }
            }
            return nextStatus;
        },

        _onTouchStrat: function(e) {
            this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
            this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
            var body = document.body,
                startScrollY = this.ct === body ? window.pageYOffset || window.scrollY || body.scrollTop || document.documentElement.scrollTop : this.ct.scrollTop;
            if (this._draggable && (this.options.disableDragDown !== true || this.options.disableDragUp !== true) && this._fireEvent('beforeDrag') !== false) {
                this._draggable = false;
                this.ct.addEventListener(TOUCH_EVENTS.move, this, false);
                this.ct.addEventListener(TOUCH_EVENTS.end, this, false);
                this._touchCoords = {};
                this._touchCoords.startY = msPointerEnabled ? e.screenY : e.touches[0].screenY;
                this._touchCoords.startScrollY = startScrollY;
            }
        },

        _onTouchMove: function(e) {
            var ct = this.ct, header = this.header, footer = this.footer,
                options = this.options,
                innerHeight = window.innerHeight,
                ctHeight = ct.scrollHeight,
                coords = this._touchCoords,
                startScrollY = coords.startScrollY,
                blockY = coords.blockY,
                startY = coords.startY,
                stopY = msPointerEnabled ? e.screenY : e.touches[0].screenY,
                offsetY, overY;

            if (typeof coords.canDragDown === 'undefined') {
                coords.canDragDown = options.disableDragDown !== true && startY < stopY && startScrollY <= 0;
            }
            if (typeof coords.canDragUp === 'undefined') {
                coords.canDragUp = options.disableDragUp !== true && startY > stopY && startScrollY + innerHeight >= ctHeight;
            }

            if (coords.canDragDown && coords.dragUp !== true && (coords.dragDown || startY - stopY + startScrollY < 0)) {
                e.preventDefault();
                coords.dragDown = true;
                if (!header) {
                    header = this._createDragDownRegion();
                }
                if (typeof blockY === 'undefined') {
                    coords.blockY = blockY = stopY;
                }
                offsetY = stopY - blockY;
                offsetY = offsetY > 0 ? offsetY : 0;
                overY = offsetY - options.dragDownThreshold;
                if (overY > 100) {
                    offsetY = options.dragDownThreshold + 75 + (overY - 100) * 0.25;
                } else if (overY > 50) {
                    offsetY = options.dragDownThreshold + 50 + (overY - 50) * 0.5;
                }
                header.style.height = offsetY + 'px';
                coords.status = this._processStatus('down', offsetY, coords.status, true);
            } else if (coords.canDragUp && coords.dragDown !== true && (coords.dragUp || startY - stopY + startScrollY + innerHeight > ctHeight)) {
                e.preventDefault();
                coords.dragUp = true;
                if (!footer) {
                    footer = this._createDragUpRegion();
                }
                if (typeof blockY === 'undefined') {
                    coords.blockY = blockY = stopY;
                }
                offsetY = blockY - stopY;
                offsetY = offsetY > 0 ? offsetY : 0;
                overY = offsetY - options.dragUpThreshold;
                if (overY > 100) {
                    offsetY = options.dragUpThreshold + 75 + (overY - 100) * 0.2;
                } else if (overY > 50) {
                    offsetY = options.dragUpThreshold + 50 + (overY - 50) * 0.5;
                }
                ct.scrollTop = startScrollY + offsetY;
                footer.style.height = offsetY + 'px';
                coords.status = this._processStatus('up', offsetY, coords.status, true);
            } else {
                coords.blockY = stopY;
            }
        },

        _onTouchEnd: function(e) {
            this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
            this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
            this._translate();
        },

        _translate: function() {
            var me = this,
                options = me.options,
                coords = me._touchCoords, orient,
                target, targetHeight,
                adjustHeight,
                maxDuration = 200, duration,
                upperStr, threshold,
                endFn = function() {
                    coords.status = me._processStatus(orient, targetHeight, coords.status, false);
                    if (!orient || coords.status !== me.STATUS.load) {
                        me._removeDragDownRegion();
                        me._removeDragUpRegion();
                        me._touchCoords = null;
                        me._draggable = true;
                    } else if (orient == 'down') {
                        me._removeDragUpRegion();
                    } else if (orient == 'up') {
                        me._removeDragDownRegion();
                    }
                };

            if (!coords) return;

            orient = coords.dragDown ? 'down' : (coords.dragUp ? 'up' : null);
            if (orient) {
                target = orient == 'down' ? me.header : me.footer;
                targetHeight = target.offsetHeight;
                upperStr = orient.charAt(0).toUpperCase() + orient.substr(1);
                threshold = options['drag' + upperStr + 'Threshold'];
                adjustHeight = (!options.preventDragHelper && targetHeight > threshold) ? threshold : 0;
                duration = Math.ceil((targetHeight - adjustHeight) / threshold * maxDuration);
                duration = duration > maxDuration ? maxDuration : duration;
                listenTransition(target, duration, endFn);
                target.style[transition] = 'height ' + duration + 'ms';
                setTimeout(function() {
                    target.style.height = adjustHeight + 'px';
                }, 0);
            } else {
                endFn();
            }
        },

        reset: function() {
            this._translate();
        },

        on: function(type, fn) {
            if (!this._events[type]) {
                this._events[type] = [];
            }
            this._events[type].push(fn);
        },

        off: function(type, fn) {
            if (this._events[type]) {
                this._events[type].every(function(cb, i) {
                    if (cb === fn) {
                        this._events[type].splice(i, 1);
                        return false;
                    }
                });
            }
        },

        _fireEvent: function(type, args) {
            var ret;
            if (this._events[type]) {
                this._events[type].forEach(function(fn) {
                    ret = fn.apply(this, args || []);
                });
            }
            return ret;
        },

        setDragDownDisabled: function(disabled) {
            this.options.disableDragDown = disabled;
        },

        setDragUpDisabled: function(disabled) {
            this.options.disableDragUp = disabled;
        },

        handleEvent: function(e) {
            switch (e.type) {
                case TOUCH_EVENTS.start:
                    this._onTouchStrat(e);
                    break;
                case TOUCH_EVENTS.move:
                    this._onTouchMove(e);
                    break;
                case TOUCH_EVENTS.end:
                    this._onTouchEnd(e);
                    break;
            }
        },

        destroy: function() {
            if (!this.destroyed) {
                this.destroyed = true;
                this._removeDragDownRegion();
                this._removeDragUpRegion();
                this.ct.removeEventListener(TOUCH_EVENTS.start, this, false);
                this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
                this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
                this.ct = null;
            }
        }
    };

    dummyStyle = null;

    window.DragLoader = DragLoader;

})(window);<commit_msg>修改Event回调函数的Scope<commit_after>(function(window) {
    var document = window.document,
        navigator = window.navigator,
        msPointerEnabled = navigator.msPointerEnabled,
        TOUCH_EVENTS = {
            start: msPointerEnabled ? 'MSPointerDown' : 'touchstart',
            move: msPointerEnabled ? 'MSPointerMove' : 'touchmove',
            end: msPointerEnabled ? 'MSPointerUp' : 'touchend'
        },
        dummyStyle = document.createElement('div').style,
        vendor = (function () {
            var vendors = 't,webkitT,MozT,msT,OT'.split(','),
                t,
                i = 0,
                l = vendors.length;

            for (; i < l; i++) {
                t = vendors[i] + 'ransform';
                if (t in dummyStyle) {
                    return vendors[i].substr(0, vendors[i].length - 1);
                }
            }

            return false;
        }()),
        prefixStyle = function(style) {
            if (vendor === '') return style;
            style = style.charAt(0).toUpperCase() + style.substr(1);
            return vendor + style;
        },
        transition = prefixStyle('transition'),
        transitionEndEvent = (function() {
            if (vendor == 'webkit' || vendor === 'O') {
                return vendor.toLowerCase() + 'TransitionEnd';
            }
            return 'transitionend';
        }()),
        listenTransition = function(target, duration, callbackFn) {
            var me = this,
                clear = function() {
                    if (target.transitionTimer) clearTimeout(target.transitionTimer);
                    target.transitionTimer = null;
                    target.removeEventListener(transitionEndEvent, handler, false);
                },
                handler = function() {
                    clear();
                    if (callbackFn) callbackFn.call(me);
                };
            clear();
            target.addEventListener(transitionEndEvent, handler, false);
            target.transitionTimer = setTimeout(handler, duration + 100);
        };

    var DragLoader = function(options) {
        options = options || {};
        this.options = options;
        this.ct = options.ct ? (typeof options.ct === 'string' ? document.querySelector(options.ct) : options.ct) : document.body;
        if (typeof options.threshold === 'undefined') options.threshold = 80;
        if (typeof options.dragDownThreshold === 'undefined') options.dragDownThreshold = options.threshold;
        if (typeof options.dragUpThreshold === 'undefined') options.dragUpThreshold = options.threshold;

        this._events = {};
        this._draggable = true;

        this.ct.addEventListener(TOUCH_EVENTS.start, this, false);
    };

    DragLoader.prototype = {
        STATUS: {
            default: 'default',
            prepare: 'prepare',
            load: 'load'
        },

        _createDragDownRegion: function() {
            this._removeDragDownRegion();
            this.header = document.createElement('div');
            this.header.style.cssText = 'position:relative;top:0;left:0;margin:0;padding:0;overflow:hidden;width:100%;height:0px;';
            this.header.className = this.options.dragDownRegionCls || '';
            this._touchCoords.status = this._processStatus('down', 0, null, true);
            this.ct.insertBefore(this.header, this.ct.children[0]);
            return this.header;
        },

        _removeDragDownRegion: function() {
            if (this.header) {
                this.ct.removeChild(this.header);
                this.header = null;
            }
        },

        _createDragUpRegion: function() {
            this._removeDragUpRegion();
            this.footer = document.createElement('div');
            this.footer.style.cssText = 'position:relative;bottom:0;left:0;margin:0;padding:0;overflow:hidden;width:100%;height:0px;';
            this.footer.className = this.options.dragUpRegionCls || '';
            this._touchCoords.status = this._processStatus('up', 0, null, true);
            this.ct.appendChild(this.footer);
            return this.footer;
        },

        _removeDragUpRegion: function() {
            if (this.footer) {
                this.ct.removeChild(this.footer);
                this.footer = null;
            }
        },

        _processDragDownHelper: function(status) {
            var options = this.options,
                helper = options.dragDownHelper;
            if (!options.preventDragHelper && helper) {
                this.header.innerHTML = helper.call(this, status);
            }
        },

        _processDragUpHelper: function(status) {
            var options = this.options,
                helper = options.dragUpHelper;
            if (!options.preventDragHelper && helper) {
                this.footer.innerHTML = helper.call(this, status);
            }
        },

        /*
         * status:
         *   default 默认状态
         *   prepare 释放加载
         *   load 加载
         */
        _processStatus: function(orient, offsetY, currentStatus, moved) {
            var options = this.options,
                STATUS = this.STATUS,
                overflow, nextStatus = currentStatus,
                upperStr;
            if (orient) {
                upperStr = orient.charAt(0).toUpperCase() + orient.substr(1);
                overflow = offsetY > options['drag' + upperStr + 'Threshold'];
                if (!overflow && currentStatus != STATUS.default) {
                    this['_processDrag' + upperStr + 'Helper'].call(this, STATUS.default);
                    this._fireEvent('drag' + upperStr + 'Default');
                    nextStatus = STATUS.default;
                } else if (moved && overflow && currentStatus != STATUS.prepare) {
                    this['_processDrag' + upperStr + 'Helper'].call(this, STATUS.prepare);
                    this._fireEvent('drag' + upperStr + 'Prepare');
                    nextStatus = STATUS.prepare;
                } else if (!moved && overflow && currentStatus != STATUS.load) {
                    this['_processDrag' + upperStr + 'Helper'].call(this, STATUS.load);
                    this._fireEvent('drag' + upperStr + 'Load');
                    nextStatus = STATUS.load;
                }
            }
            return nextStatus;
        },

        _onTouchStrat: function(e) {
            this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
            this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
            var body = document.body,
                startScrollY = this.ct === body ? window.pageYOffset || window.scrollY || body.scrollTop || document.documentElement.scrollTop : this.ct.scrollTop;
            if (this._draggable && (this.options.disableDragDown !== true || this.options.disableDragUp !== true) && this._fireEvent('beforeDrag') !== false) {
                this._draggable = false;
                this.ct.addEventListener(TOUCH_EVENTS.move, this, false);
                this.ct.addEventListener(TOUCH_EVENTS.end, this, false);
                this._touchCoords = {};
                this._touchCoords.startY = msPointerEnabled ? e.screenY : e.touches[0].screenY;
                this._touchCoords.startScrollY = startScrollY;
            }
        },

        _onTouchMove: function(e) {
            var ct = this.ct, header = this.header, footer = this.footer,
                options = this.options,
                innerHeight = window.innerHeight,
                ctHeight = ct.scrollHeight,
                coords = this._touchCoords,
                startScrollY = coords.startScrollY,
                blockY = coords.blockY,
                startY = coords.startY,
                stopY = msPointerEnabled ? e.screenY : e.touches[0].screenY,
                offsetY, overY;

            if (typeof coords.canDragDown === 'undefined') {
                coords.canDragDown = options.disableDragDown !== true && startY < stopY && startScrollY <= 0;
            }
            if (typeof coords.canDragUp === 'undefined') {
                coords.canDragUp = options.disableDragUp !== true && startY > stopY && startScrollY + innerHeight >= ctHeight;
            }

            if (coords.canDragDown && coords.dragUp !== true && (coords.dragDown || startY - stopY + startScrollY < 0)) {
                e.preventDefault();
                coords.dragDown = true;
                if (!header) {
                    header = this._createDragDownRegion();
                }
                if (typeof blockY === 'undefined') {
                    coords.blockY = blockY = stopY;
                }
                offsetY = stopY - blockY;
                offsetY = offsetY > 0 ? offsetY : 0;
                overY = offsetY - options.dragDownThreshold;
                if (overY > 100) {
                    offsetY = options.dragDownThreshold + 75 + (overY - 100) * 0.25;
                } else if (overY > 50) {
                    offsetY = options.dragDownThreshold + 50 + (overY - 50) * 0.5;
                }
                header.style.height = offsetY + 'px';
                coords.status = this._processStatus('down', offsetY, coords.status, true);
            } else if (coords.canDragUp && coords.dragDown !== true && (coords.dragUp || startY - stopY + startScrollY + innerHeight > ctHeight)) {
                e.preventDefault();
                coords.dragUp = true;
                if (!footer) {
                    footer = this._createDragUpRegion();
                }
                if (typeof blockY === 'undefined') {
                    coords.blockY = blockY = stopY;
                }
                offsetY = blockY - stopY;
                offsetY = offsetY > 0 ? offsetY : 0;
                overY = offsetY - options.dragUpThreshold;
                if (overY > 100) {
                    offsetY = options.dragUpThreshold + 75 + (overY - 100) * 0.2;
                } else if (overY > 50) {
                    offsetY = options.dragUpThreshold + 50 + (overY - 50) * 0.5;
                }
                ct.scrollTop = startScrollY + offsetY;
                footer.style.height = offsetY + 'px';
                coords.status = this._processStatus('up', offsetY, coords.status, true);
            } else {
                coords.blockY = stopY;
            }
        },

        _onTouchEnd: function(e) {
            this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
            this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
            this._translate();
        },

        _translate: function() {
            var me = this,
                options = me.options,
                coords = me._touchCoords, orient,
                target, targetHeight,
                adjustHeight,
                maxDuration = 200, duration,
                upperStr, threshold,
                endFn = function() {
                    coords.status = me._processStatus(orient, targetHeight, coords.status, false);
                    if (!orient || coords.status !== me.STATUS.load) {
                        me._removeDragDownRegion();
                        me._removeDragUpRegion();
                        me._touchCoords = null;
                        me._draggable = true;
                    } else if (orient == 'down') {
                        me._removeDragUpRegion();
                    } else if (orient == 'up') {
                        me._removeDragDownRegion();
                    }
                };

            if (!coords) return;

            orient = coords.dragDown ? 'down' : (coords.dragUp ? 'up' : null);
            if (orient) {
                target = orient == 'down' ? me.header : me.footer;
                targetHeight = target.offsetHeight;
                upperStr = orient.charAt(0).toUpperCase() + orient.substr(1);
                threshold = options['drag' + upperStr + 'Threshold'];
                adjustHeight = (!options.preventDragHelper && targetHeight > threshold) ? threshold : 0;
                duration = Math.ceil((targetHeight - adjustHeight) / threshold * maxDuration);
                duration = duration > maxDuration ? maxDuration : duration;
                listenTransition(target, duration, endFn);
                target.style[transition] = 'height ' + duration + 'ms';
                setTimeout(function() {
                    target.style.height = adjustHeight + 'px';
                }, 0);
            } else {
                endFn();
            }
        },

        reset: function() {
            this._translate();
        },

        on: function(type, fn) {
            if (!this._events[type]) {
                this._events[type] = [];
            }
            this._events[type].push(fn);
        },

        off: function(type, fn) {
            if (this._events[type]) {
                this._events[type].every(function(cb, i) {
                    if (cb === fn) {
                        this._events[type].splice(i, 1);
                        return false;
                    }
                });
            }
        },

        _fireEvent: function(type, args) {
            var me = this, ret;
            if (me._events[type]) {
                me._events[type].forEach(function(fn) {
                    ret = fn.apply(me, args || []);
                });
            }
            return ret;
        },

        setDragDownDisabled: function(disabled) {
            this.options.disableDragDown = disabled;
        },

        setDragUpDisabled: function(disabled) {
            this.options.disableDragUp = disabled;
        },

        handleEvent: function(e) {
            switch (e.type) {
                case TOUCH_EVENTS.start:
                    this._onTouchStrat(e);
                    break;
                case TOUCH_EVENTS.move:
                    this._onTouchMove(e);
                    break;
                case TOUCH_EVENTS.end:
                    this._onTouchEnd(e);
                    break;
            }
        },

        destroy: function() {
            if (!this.destroyed) {
                this.destroyed = true;
                this._removeDragDownRegion();
                this._removeDragUpRegion();
                this.ct.removeEventListener(TOUCH_EVENTS.start, this, false);
                this.ct.removeEventListener(TOUCH_EVENTS.move, this, false);
                this.ct.removeEventListener(TOUCH_EVENTS.end, this, false);
                this.ct = null;
            }
        }
    };

    dummyStyle = null;

    window.DragLoader = DragLoader;

})(window);<|endoftext|>