<commit_before>"use strict";

var L = require('leaflet'),
    $ = require('jquery'),
    _ = require('underscore'),
    router = require('../router.js').router,
    Marionette = require('../../shim/backbone.marionette'),
    TransitionRegion = require('../../shim/marionette.transition-region'),
    drawUtils = require('../draw/utils'),
    modificationConfigUtils = require('../modeling/modificationConfigUtils'),
    headerTmpl = require('./templates/header.html'),
    modificationPopupTmpl = require('./templates/modificationPopup.html'),
    areaOfInterestTmpl = require('./templates/areaOfInterestHeader.html'),
    modalModels = require('./modals/models'),
    modalViews = require('./modals/views'),
    settings = require('./settings');

require('leaflet.locatecontrol');
require('leaflet-plugins/layer/tile/Google');

var RootView = Marionette.LayoutView.extend({
    el: 'body',
    regions: {
        mainRegion: '#container',
        geocodeSearchRegion: '#geocode-search-region',
        drawToolsRegion: '#draw-tools-region',
        subHeaderRegion: '#sub-header',
        footerRegion: {
            regionClass: TransitionRegion,
            selector: '#footer'
        }
    }
});

var HeaderView = Marionette.ItemView.extend({
    template: headerTmpl,

    ui: {
        login: '.show-login',
        logout: '.user-logout',
        cloneProject: '.clone-project'
    },

    events: {
        'click @ui.login': 'showLogin',
        'click @ui.logout': 'userLogout',
        'click @ui.cloneProject': 'cloneProject'
    },

    modelEvents: {
        'sync'  : 'render',
        'change': 'render'
    },

    showLogin: function() {
        // Defer requiring app until needed as it is not defined when
        // core.views are initialized (they are required in app.js)
        require('../app').getUserOrShowLogin();
    },

    userLogout: function(event) {
        event.preventDefault();

        this.model.logout().done(function() {
            router.navigate('', {trigger: true});
        });
    },

    cloneProject: function() {
        event.preventDefault();

        var view = new modalViews.InputView({
            model: new modalModels.InputModel({
                title: 'Clone Project',
                fieldLabel: 'Project ID'
            })
        });

        view.on('update', function(projectId) {
            var cloneUrlFragment = '/project/' + projectId + '/clone',
                cloneUrl = window.location.origin + cloneUrlFragment,
                testUrl = '/api/modeling/projects/' + projectId;

            $.ajax(testUrl)
                .done(function() {
                    window.location.replace(cloneUrl);
                })
                .fail(function() {
                    window.alert(
                        'There was an error trying to clone that project. ' +
                        'Please ensure that the project ID is valid and ' +
                        'that you have permission to view the project.'
                    );
                });
        });
        view.render();
    }
});

// Init the locate plugin button and add it to the map.
function addLocateMeButton(map, maxZoom, maxAge) {
    var locateOptions = {
        position: 'topright',
        metric: false,
        drawCircle: false,
        showPopup: false,
        follow: false,
        markerClass: L.marker,
        markerStyle: {
            opacity: 0.0,
            clickable: false,
            keyboard: false
        },
        locateOptions: {
            maxZoom: maxZoom,
            // Cache location response, in ms
            maximumAge: maxAge
        },
        strings: {
            title: 'Zoom to your location.'
        }
    };

    L.control.locate(locateOptions).addTo(map);
}

// This view houses a Leaflet instance. The map container element must exist
// in the DOM before initializing.
var MapView = Marionette.ItemView.extend({
    modelEvents: {
        'change': 'updateView',
        'change:areaOfInterest': 'updateAreaOfInterest',
        'change:size': 'toggleMapSize',
        'change:maskLayerApplied': 'toggleMask'
    },

    // L.Map instance.
    _leafletMap: null,

    // Active "area of interest" shape on the map.
    // L.FeatureGroup instance.
    _areaOfInterestLayer: null,

    // Scenario modification shapes drawn on top of area of interest.
    // L.FeatureGroup instance.
    _modificationsLayer: null,

    // Flag used to determine if AOI change should trigger a prompt.
    _areaOfInterestSet: false,
    _didRevert: false,

    // Google Maps API library is loaded asynchronously via an inline script tag
    _googleMaps: (window.google ? window.google.maps : null),

    initialize: function(options) {
        var defaultLayerName = _.findKey(settings.get('base_layers'), function(layerData) {
            return layerData.default;
        });

        _.defaults(options, {
            addZoomControl: true,
            addLocateMeButton: true,
            addLayerSelector: true,
            showLayerAttribution: true,
            initialLayerName: defaultLayerName,
            interactiveMode: true // True if clicking on map does stuff
        });

        var map = new L.Map(this.el, {
                zoomControl: false,
                attributionControl: options.showLayerAttribution
            }),
            areaOfInterestLayer = new L.FeatureGroup(),
            modificationsLayer = new L.FeatureGroup(),
            maxAge = 60000,
            timeout = 30000,
            self = this;

        this.interactiveMode = options.interactiveMode;

        if (!options.interactiveMode) {
            // Disable panning and zooming.
            // http://gis.stackexchange.com/questions/54454/disable-leaflet-interaction-temporary
            map.dragging.disable();
            map.touchZoom.disable();
            map.doubleClickZoom.disable();
            map.scrollWheelZoom.disable();
            map.boxZoom.disable();
            map.keyboard.disable();

            if (map.tap) {
                map.tap.disable();
            }

            document.getElementById('map').style.cursor='default';
        }

        if (options.addZoomControl) {
            map.addControl(new L.Control.Zoom({position: 'topright'}));
        }

        if (options.addLocateMeButton) {
            addLocateMeButton(map, maxAge);
        }

        this.baseLayers = _.mapObject(settings.get('base_layers'), function(layerData) {

            // Check to see if the google api service has been loaded
            // before creating a google layer
            if (self._googleMaps && layerData.type === 'google') {
                return new L.Google(layerData.googleType, {
                    maxZoom: layerData.maxZoom
                });
            } else {
                return new L.TileLayer(layerData.url, {
                    attribution: layerData.attribution || '',
                    maxZoom: layerData.maxZoom
                });
            }
        });

        if (options.addLayerSelector) {
            this.layerControl = L.control.layers(this.baseLayers, {}, {autoZIndex:false}).addTo(map);
        }

        var initialLayer = this.baseLayers[options.initialLayerName] ||
                           this.baseLayers[defaultLayerName];

        if (initialLayer) {
            map.addLayer(initialLayer);
        }

        map.addLayer(areaOfInterestLayer);
        map.addLayer(modificationsLayer);

        // Center the map on the U.S.
        map.fitBounds([
            [24.2, -126.4],
            [49.8, -66.0]
        ]);

        // Keep the map model up-to-date with the position of the map
        this.listenTo(map, 'moveend', this.updateMapModelPosition);
        this.listenTo(map, 'zoomend', this.updateMapModelZoom);
        this.listenTo(this.model, 'change:areaOfInterest', this.aoiChangeWarning);

        // The max available zoom level changes based on the active base layer
        map.on('baselayerchange', this.updateCurrentZoomLevel);

        // Some Google layers have a dynamic max zoom that we need to handle.
        // Check that Google Maps API library is available before implementing
        // this special handling.
        if (this._googleMaps) {
            this._googleMaxZoomService = new this._googleMaps.MaxZoomService();

            // TODO: Because the max zoom level is only read when a layer is selected
            // in the basemap control, updates to the maximum zoom level won't
            // be used until a user reselects a google base map. This can
            // be better implemented in Leaflet 1.0 which has map.setMaxZoom
            map.on('moveend', _.bind(this.updateGoogleMaxZoom, this));

            // Get the maximum zoom level for the initial location
            this.updateGoogleMaxZoom({ target: map });
        }

        this._leafletMap = map;
        this._areaOfInterestLayer = areaOfInterestLayer;
        this._modificationsLayer = modificationsLayer;

        // Geolocation success handler
        function geolocation_success(position) {
            if (self.model.get('geolocationEnabled')) {
                var lng = position.coords.longitude,
                    lat = position.coords.latitude,
                    zoom = 12; // Regional zoom level

                map.setView([lat, lng], zoom);
            }
        }

        // Attempt to Geolocate.  If geolocation fails or is not
        // supported, nothing more needs to be done since the map has
        // already been centered.
        if (navigator.geolocation) {
            var geolocationOptions = {
                maximumAge : maxAge,
                timeout : timeout
            };

            // Wait a bit and then get the location. Suppresses issues on Safari
            // in which immediately requesting the location results in an
            // infinite request loop for geolocation permissions.
            setTimeout(function() {
                navigator.geolocation.getCurrentPosition(
                    geolocation_success,
                    _.noop,
                    geolocationOptions
                );
            }, 500);
        }
    },

    getActiveBaseLayerName: function() {
        var activeBaseLayerName,
            self = this;

        activeBaseLayerName = _.findKey(self.baseLayers, function(layer) {
            return self._leafletMap.hasLayer(layer);
        });

        return activeBaseLayerName;
    },

    onBeforeDestroy: function() {
        this._leafletMap.remove();
    },

    aoiChangeWarning: _.debounce(function() {
        var activityMode = settings.get('activityMode');

        // Fail fast.
        if (this._didRevert || !activityMode) {
            this._didRevert = false;
            return;
        }

        if (this._areaOfInterestSet) {
            var self = this,
                clearProject = new modalViews.ConfirmView({<commit_msg>Clean-up and refactor map initialization<commit_after>"use strict";

var L = require('leaflet'),
    $ = require('jquery'),
    _ = require('underscore'),
    router = require('../router.js').router,
    Marionette = require('../../shim/backbone.marionette'),
    TransitionRegion = require('../../shim/marionette.transition-region'),
    drawUtils = require('../draw/utils'),
    modificationConfigUtils = require('../modeling/modificationConfigUtils'),
    headerTmpl = require('./templates/header.html'),
    modificationPopupTmpl = require('./templates/modificationPopup.html'),
    areaOfInterestTmpl = require('./templates/areaOfInterestHeader.html'),
    modalModels = require('./modals/models'),
    modalViews = require('./modals/views'),
    settings = require('./settings');

require('leaflet.locatecontrol');
require('leaflet-plugins/layer/tile/Google');

var RootView = Marionette.LayoutView.extend({
    el: 'body',
    regions: {
        mainRegion: '#container',
        geocodeSearchRegion: '#geocode-search-region',
        drawToolsRegion: '#draw-tools-region',
        subHeaderRegion: '#sub-header',
        footerRegion: {
            regionClass: TransitionRegion,
            selector: '#footer'
        }
    }
});

var HeaderView = Marionette.ItemView.extend({
    template: headerTmpl,

    ui: {
        login: '.show-login',
        logout: '.user-logout',
        cloneProject: '.clone-project'
    },

    events: {
        'click @ui.login': 'showLogin',
        'click @ui.logout': 'userLogout',
        'click @ui.cloneProject': 'cloneProject'
    },

    modelEvents: {
        'sync'  : 'render',
        'change': 'render'
    },

    showLogin: function() {
        // Defer requiring app until needed as it is not defined when
        // core.views are initialized (they are required in app.js)
        require('../app').getUserOrShowLogin();
    },

    userLogout: function(event) {
        event.preventDefault();

        this.model.logout().done(function() {
            router.navigate('', {trigger: true});
        });
    },

    cloneProject: function() {
        event.preventDefault();

        var view = new modalViews.InputView({
            model: new modalModels.InputModel({
                title: 'Clone Project',
                fieldLabel: 'Project ID'
            })
        });

        view.on('update', function(projectId) {
            var cloneUrlFragment = '/project/' + projectId + '/clone',
                cloneUrl = window.location.origin + cloneUrlFragment,
                testUrl = '/api/modeling/projects/' + projectId;

            $.ajax(testUrl)
                .done(function() {
                    window.location.replace(cloneUrl);
                })
                .fail(function() {
                    window.alert(
                        'There was an error trying to clone that project. ' +
                        'Please ensure that the project ID is valid and ' +
                        'that you have permission to view the project.'
                    );
                });
        });
        view.render();
    }
});

// Init the locate plugin button and add it to the map.
function addLocateMeButton(map, maxZoom, maxAge) {
    var locateOptions = {
        position: 'topright',
        metric: false,
        drawCircle: false,
        showPopup: false,
        follow: false,
        markerClass: L.marker,
        markerStyle: {
            opacity: 0.0,
            clickable: false,
            keyboard: false
        },
        locateOptions: {
            maxZoom: maxZoom,
            // Cache location response, in ms
            maximumAge: maxAge
        },
        strings: {
            title: 'Zoom to your location.'
        }
    };

    L.control.locate(locateOptions).addTo(map);
}

// This view houses a Leaflet instance. The map container element must exist
// in the DOM before initializing.
var MapView = Marionette.ItemView.extend({
    modelEvents: {
        'change': 'updateView',
        'change:areaOfInterest': 'updateAreaOfInterest',
        'change:size': 'toggleMapSize',
        'change:maskLayerApplied': 'toggleMask'
    },

    // L.Map instance.
    _leafletMap: null,

    // Active "area of interest" shape on the map.
    // L.FeatureGroup instance.
    _areaOfInterestLayer: null,

    // Scenario modification shapes drawn on top of area of interest.
    // L.FeatureGroup instance.
    _modificationsLayer: null,

    // Flag used to determine if AOI change should trigger a prompt.
    _areaOfInterestSet: false,
    _didRevert: false,

    // Google Maps API library is loaded asynchronously via an inline script tag
    _googleMaps: (window.google ? window.google.maps : null),

    initialize: function(options) {
        var defaultLayerName = _.findKey(settings.get('base_layers'), function(layerData) {
            return layerData.default;
        });

        _.defaults(options, {
            addZoomControl: true,
            addLocateMeButton: true,
            addLayerSelector: true,
            showLayerAttribution: true,
            initialLayerName: defaultLayerName,
            interactiveMode: true // True if clicking on map does stuff
        });

        var map = new L.Map(this.el, {
                zoomControl: false,
                attributionControl: options.showLayerAttribution
            });

        this._leafletMap = map;
        this._areaOfInterestLayer = new L.FeatureGroup();
        this._modificationsLayer = new L.FeatureGroup();
        this.baseLayers = this.getBaseLayers();

        if (!options.interactiveMode) {
            this.setMapToNonInteractive();
        }

        if (options.addLayerSelector) {
            this.layerControl = L.control.layers(this.baseLayers, {}, {autoZIndex:false}).addTo(map);
        }

        if (options.addZoomControl) {
            map.addControl(new L.Control.Zoom({position: 'topright'}));
        }

        var maxGeolocationAge = 60000;
        if (options.addLocateMeButton) {
            addLocateMeButton(map, maxGeolocationAge);
        }

        this.setMapEvents();
        this.setupGeoLocation(maxGeolocationAge);

        var initialLayer = this.baseLayers[options.initialLayerName] ||
                           this.baseLayers[defaultLayerName];

        if (initialLayer) {
            map.addLayer(initialLayer);
        }

        map.addLayer(this._areaOfInterestLayer);
        map.addLayer(this._modificationsLayer);

        // Center the map on the U.S.
        map.fitBounds([
            [24.2, -126.4],
            [49.8, -66.0]
        ]);
    },

    setupGeoLocation: function(maxAge) {
        var self = this,
            timeout = 30000;

        // Geolocation success handler
        function geolocation_success(position) {
            if (self.model.get('geolocationEnabled')) {
                var lng = position.coords.longitude,
                    lat = position.coords.latitude,
                    zoom = 12; // Regional zoom level

                self._leafletMap.setView([lat, lng], zoom);
            }
        }

        // Attempt to Geolocate. If geolocation fails or is not
        // supported, nothing more needs to be done since the map has
        // already been centered.
        if (navigator.geolocation) {
            var geolocationOptions = {
                maximumAge: maxAge,
                timeout: timeout
            };

            // Wait a bit and then get the location. Suppresses issues on Safari
            // in which immediately requesting the location results in an
            // infinite request loop for geolocation permissions.
            setTimeout(function() {
                navigator.geolocation.getCurrentPosition(
                    geolocation_success,
                    _.noop,
                    geolocationOptions
                );
            }, 500);
        }
    },

    setMapToNonInteractive: function() {
        // Disable panning and zooming.
        // Source: http://gis.stackexchange.com/questions/54454
        this._leafletMap.dragging.disable();
        this._leafletMap.touchZoom.disable();
        this._leafletMap.doubleClickZoom.disable();
        this._leafletMap.scrollWheelZoom.disable();
        this._leafletMap.boxZoom.disable();
        this._leafletMap.keyboard.disable();

        if (this._leafletMap.tap) {
            this._leafletMap.tap.disable();
        }

        this.el.style.cursor = 'default';
    },

    setMapEvents: function() {
        // Keep the map model up-to-date with the position of the map
        this.listenTo(this._leafletMap, 'moveend', this.updateMapModelPosition);
        this.listenTo(this._leafletMap, 'zoomend', this.updateMapModelZoom);
        this.listenTo(this.model, 'change:areaOfInterest', this.aoiChangeWarning);

        // The max available zoom level changes based on the active base layer
        this._leafletMap.on('baselayerchange', this.updateCurrentZoomLevel);

        // Some Google layers have a dynamic max zoom that we need to handle.
        // Check that Google Maps API library is available before implementing
        // this special handling.
        if (this._googleMaps) {
            this._googleMaxZoomService = new this._googleMaps.MaxZoomService();

            // TODO: Because the max zoom level is only read when a layer is selected
            // in the basemap control, updates to the maximum zoom level won't
            // be used until a user reselects a google base map. This can
            // be better implemented in Leaflet 1.0 which has map.setMaxZoom
            this._leafletMap.on('moveend', _.bind(this.updateGoogleMaxZoom, this));

            // Get the maximum zoom level for the initial location
            this.updateGoogleMaxZoom({ target: this._leafletMap });
        }
    },

    getBaseLayers: function() {
        var self = this,
            baseLayers = _.mapObject(settings.get('base_layers'), function(layerData) {
            // Check to see if the google api service has been loaded
            // before creating a google layer
            if (self._googleMaps && layerData.type === 'google') {
                return new L.Google(layerData.googleType, {
                    maxZoom: layerData.maxZoom
                });
            } else {
                return new L.TileLayer(layerData.url, {
                    attribution: layerData.attribution || '',
                    maxZoom: layerData.maxZoom
                });
            }
        });

        return baseLayers;
    },

    getActiveBaseLayerName: function() {
        var activeBaseLayerName,
            self = this;

        activeBaseLayerName = _.findKey(self.baseLayers, function(layer) {
            return self._leafletMap.hasLayer(layer);
        });

        return activeBaseLayerName;
    },

    onBeforeDestroy: function() {
        this._leafletMap.remove();
    },

    aoiChangeWarning: _.debounce(function() {
        var activityMode = settings.get('activityMode');

        // Fail fast.
        if (this._didRevert || !activityMode) {
            this._didRevert = false;
            return;
        }

        if (this._areaOfInterestSet) {
            var self = this,
                clearProject = new modalViews.ConfirmView({
{'fraction_non_alphanumeric': 0.10692248795207855, 'fraction_numerical': 0.0033328829437463407, 'mean_word_length': 1.8892648015614835, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6484664', 'n_tokens_mistral': 6023, 'n_tokens_neox': 5720, 'n_words': 1419}