<commit_before>(function(module) {
    'use strict';

    // TODO: refactor (it's getting way too big!), document and add tests!!
    module.controller('MapCtrl', [
        '$scope', '$location', '$timeout', 'leafletData', 'angularPiwik', 'mapDefaults',
        'playerService', 'Location', 'locationService', 'mainMapService', 'backendService',
        function($scope, $location, $timeout, leafletData, angularPiwik, mapDefaults,
            playerService, Location, locationService, mainMapService, backendService) {
            var player;

            /**
             * Leaflet map settings
             * @type {{}}
             */
            $scope.mapDefaults = mapDefaults;

            /**
             * Locations loaded from the backend indexed by id
             * @type {{}}
             */
            var locations = {};

            /**
             * Whether the user is currently adding a new location
             * @type {boolean}
             */
            $scope.isAddingLocation = false;

            // TODO: all this addLocation stuff should be separated to a directive or other controller
            $scope.addLocationStep = 1;

            $scope.nextStep = function() {
                if ($scope.stepIsValid()) {
                    if ($scope.isLastStep()) {
                        $scope.addNewLocation();
                        angularPiwik.track('map.addLocation', 'finish');
                    }
                    else {
                        $scope.addLocationStep += 1;
                        $scope.addLocationComponent.minimised = false;
                        angularPiwik.track('map.addLocation', 'nextStep', $scope.addLocationStep);
                    }
                }
            };

            $scope.previousStep = function() {
                if ($scope.addLocationStep > 1) {
                    $scope.addLocationStep -= 1;
                    $scope.addLocationComponent.minimised = false;
                    angularPiwik.track('map.addLocation', 'previousStep', $scope.addLocationStep);
                }
            };

            $scope.stepIsValid = function() {
                var loc = $scope.newLocation;
                switch ($scope.addLocationStep) {
                case 1:
                    return (angular.isString(loc.type) && loc.type.length > 0);
                case 2:
                    return (angular.isString(loc.name) && loc.name.length > 0);
                case 3:
                    return (angular.isNumber(loc.lat) && angular.isNumber(loc.lng));
                default:
                    return false;
                }
            };

            $scope.isLastStep = function() {
                return ($scope.addLocationStep === 3);
            };

            // Expose the location service
            $scope.location = locationService;

            // Expose map settings and filters from the service
            $scope.mainMap = mainMapService;
            $scope.activeFilters = locationService.activeFilters;
            $scope.POSSIBLE_FILTERS = locationService.POSSIBLE_FILTERS;

            /**
             * Expose the location types
             * @type {{}}
             */
            $scope.locationTypes = Location.TYPES;

            /**
             * Empty events object (needed to get the leaflet map to broadcast events)
             * @type {{}}
             */
            $scope.events = {};

            /**
             * Location that is currently being added
             * @type {Location}
             */
            $scope.newLocation = undefined;

            /**
             * Whether to show the location products
             * @type {boolean}
             */
            $scope.productShown = false;

            /**
             * Whether to show the location filters
             * @type {boolean}
             */
            $scope.filtersShown = false;

            /**
             * whether to show search
             * @type {boolean}
             */
            $scope.searchShown = false;

            /**
             * Whether the add location form is minimised
             * TODO: should probably go elsewhere
             * @type {{minimised: boolean}}
             */
            $scope.addLocationComponent = {
                minimised: false
            };

            /**
             * Whether the search form is minimised
             * TODO: should probably go elsewhere
             * @type {{minimised: boolean}}
             */
            $scope.searchComponent = {
                minimised: false
            };

            /**
             * Sets whether the product list is shown
             * @param {boolean} [show=true]
             */
            $scope.showProductList = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.productShown !== show) {
                    $scope.productShown = show;
                    angularPiwik.track('map.productList', show ? 'open' : 'close');
                }
            };

            /**
             * Sets whether the filters are shown
             * @param {boolean} [show=true]
             */
            $scope.showFilters = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.filtersShown !== show) {
                    $scope.filtersShown = show;
                    angularPiwik.track('map.filter', show ? 'open' : 'close');
                }
            };

            /**
             * Sets whether the search is shown
             * @param {boolean} [show=true]
             */
            $scope.showSearch = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.searchShown !== show) {
                    $scope.searchShown = show;
                    angularPiwik.track('map.search', show ? 'open' : 'close');
                }
            };

            /**
             * Returns the number of currently active filters
             * @returns {number}
             */
            $scope.numActiveFilters = function() {
                var active = 0;
                if ($scope.activeFilters.recent !== 'anytime') {
                    active += 1;
                }
                if ($scope.activeFilters.type !== 'anytype') {
                    active += 1;
                }

                return active;
            };

            /**
             * Whether the newLocation has already been added to the map
             * @type {boolean}
             */
            var newLocationIsAddedToMap = false;

            /**
             * Whether we are currently placing a location on the map
             * @returns {boolean}
             */
            $scope.isPlacingLocation = function() {
                return ($scope.isAddingLocation && $scope.addLocationStep === 3);
            };

            /**
             * Starts adding a new location
             */
            $scope.startAddNewLocation = function() {
                $scope.addLocationStep = 1;
                $scope.isAddingLocation = true;
                $scope.newLocation = new Location({team: player.team});
                $scope.newLocation.setEditing(true);
                locationService.activate($scope.newLocation);

                angularPiwik.track('map.addLocation', 'start');
            };

            /**
             * Aborts adding a new location
             */
            $scope.resetAddNewLocation = function() {
                locationService.activate();
                // Remove from map if it was added
                if (newLocationIsAddedToMap === true) {
                    var markerToRemove = $scope.newLocation.marker;
                    mapPromise.then(function(map) {
                        map.removeLayer(markerToRemove);
                    });
                }
                $scope.isAddingLocation = false;
                $scope.newLocation = undefined;
                newLocationIsAddedToMap = false;
            };

            /**
             * Finalises adding a new location
             */
            $scope.addNewLocation = function() {
                var newLocation = $scope.newLocation;
                newLocation.setEditing(false);
                $scope.isAddingLocation = false;
                $scope.newLocation = undefined;
                newLocationIsAddedToMap = false;

                // Submit the location and add it to the list once done
                locationService.submitLocation(newLocation)
                    .then(function() {
                        locations[newLocation.id] = newLocation;
                    })
                ;
            };

            /**
             * Goes to the location view
             * @param location
             */
            $scope.visitLocation = function(location) {
                $location.path('location/' + location.id);
            };

            /**
             * Sets the given coordinates as the lat/lng of the location
             * that is being added.
             * @param {number} lat
             * @param {number} lng
             * @return {boolean} Whether the coordinates where set on the new location
             */
            $scope.setNewLocationCoordinates = function(lat, lng) {
                if ($scope.isAddingLocation) {
                    // Set the coordinates
                    $scope.newLocation.setLatLng(lat, lng);

                    // Push to map if not already there
                    if (newLocationIsAddedToMap !== true) {
                        mapPromise.then(function(map) {
                            $scope.newLocation.marker.on('click', locationClickHandler);
                            $scope.newLocation.marker.addTo(map);
                        });
                        newLocationIsAddedToMap = true;
                    }

                    // Hide the component
                    $scope.addLocationComponent.minimised = true;

                    // Zoom in all the way to make sure users place it precisely
                    // TODO: duplication with EditLocationCtrl
                    mapPromise.then(function(map) {
                        var maxZoom = map.getMaxZoom();
                        var zoomTo = [lat, lng];
                        if (map.getZoom() < maxZoom || !map.getBounds().contains(zoomTo)) {
                            map.setView(zoomTo, maxZoom);
                        }
                    });

                    return true;
                }
                return false;
            };

            /**
             * Handler for clicks on the map
             * @param event
             * @param args
             */
            var mapClickHandler = function(event, args) {
                if ($scope.isAddingLocation) {
                    if ($scope.addLocationStep === 3) {
                        // When adding a new location, take the click
                        // as the coordinates of this new location
                        $scope.setNewLocationCoordinates(
                            args.leafletEvent.latlng.lat,
                            args.leafletEvent.latlng.lng
                        );

                        angularPiwik.track('map.addLocation', 'mapClick');
                    }
                    // TODO: else what? We are adding a location but clicked one -> should show some info of the clicked place
                }
                else {
                    // When not adding a location, deselect currently active location
                    locationService.activate();

                    // And hide product list, filters and search
                    $scope.showProductList(false);
                    $scope.showFilters(false);
                    $scope.showSearch(false);
                }
            };

            /**
             * Handler for clicks on map markers (Locations)
             * @param event Event coming directly from leaflet
             *      (not angular-leaflet-directive)
             */
            var locationClickHandler = function(event) {
                if (!$scope.isAddingLocation) {
                    var clickedLocation = locations[event.target.locationId];
                    if (clickedLocation) {
                        // Run it through $apply since we are coming directly from Leaflet
                        $scope.$apply(function() {
                            locationService.activate(clickedLocation);

                            // Hide the product list, filters and search
                            $scope.showProductList(false);
                            $scope.showFilters(false);
                            $scope.showSearch(false);
                        });
                    }
                }
            };

            // Get a reference the the leaflet map object
            var mapPromise = leafletData.getMap();

            // Register event handlers
            $scope.$on('leafletDirectiveMap.click', mapClickHandler);

            // Get the locations
            locationService.getLocations().then(function(loadedLocations) {
                locations = loadedLocations;
                mapPromise.then(function(map) {
                    // Go through all the locations and add the marker to the map
                    angular.forEach(locations, function(location) {
                        location.marker.on('click', locationClickHandler);
                        location.marker.addTo(map);
                    });

                    // Apply the current filter value
                    applyFilters($scope.activeFilters);
                });
            });

            // Check if we are logged in
            if (backendService.isLoggedIn()) {
                // Get the player
                playerService.getMe().then(function(me) {
                    player = me;
                });
            }

            // Watch the map center for changes to save it
            $scope.$watch('mainMap.center', function() {
                mainMapService.saveCenter();
            });

            // TODO: move the filter stuff to a separate controller
            /**
             * Map of recent filter values to the period of
             * time for which to show the locations.
             * @type {{month: number, week: number, day: number}}
             */
            var RECENT_FILTER_PERIOD = {
                month: 4 * 7 * 24 * 3600000,
                week: 7 * 24 * 3600000,
                day: 24 * 3600000
            };

            /**
             * Runs the locations through the given recent filter
             * @param recentFilter
             */
            var _applyRecentFilter = function(recentFilter) {
                var showAll = (recentFilter === 'anytime');
                var recentDate;
                if (!showAll) {
                    recentDate = new Date(Date.now() - RECENT_FILTER_PERIOD[recentFilter]);
                }

                // Go through all the locations and filter them
                angular.forEach(locations, function(location) {
                    // Only apply the filter if the location is not already hidden
                    if (!location.isHidden()) {
                        var hideIt = (!showAll && location.updatedAt < recentDate);
                        location.setHidden(hideIt);
                    }
                });
            };

            /**
             * Runs the locations through the given type filter
             * @param typeFilter
             */
            var _applyTypeFilter = function(typeFilter) {
                var showAll = (typeFilter === 'anytype');
                // Go through all the locations and filter them
                angular.forEach(locations, function(location) {
                    // Only apply the filter if the location is not already hidden
                    if (!location.isHidden()) {
                        var hideIt = (!showAll && location.type !== typeFilter);
                        location.setHidden(hideIt);
                    }
                });
            };

            /**
             * Runs the locations through all the filters
             * Add new filters to this function
             * @param typeFilter
             */
            var applyFilters = function(filters, filtersBefore) {
                // First show all the locations
                // TODO: this is inefficient because the marker might update twice (show it, then hide it again)
                angular.forEach(locations, function(location) {
                    location.setHidden(false);
                });

                // Track filter usage
                if (angular.isDefined(filtersBefore)) {
                    if (filters.recent !== filtersBefore.recent) {
                        angularPiwik.track('map.filters', 'applyFilter.recent', filters.recent);
                    }
                    if (filters.type !== filtersBefore.type) {
                        angularPiwik.track('map.filters', 'applyFilter.type', filters.type);
                    }
                }

                // Then run the filters
                _applyRecentFilter(filters.recent);
                _applyTypeFilter(filters.type);
            };

            // Watch the active filters
            $scope.$watch('activeFilters', applyFilters, true);

            // Listen to location changes to update the map center
            // (if the user changes the hash manually)
            $scope.$onRootScope('$locationChangeSuccess', function() {
                mainMapService.setMapCenterFromUrl();
            });

            // When we go away from this page, reset the url
            $scope.$onRootScope('$routeChangeStart', function() {
                // Remove the search and hash params
                $location.url($location.path());
            });
        }
    ]);
})(window.veganaut.mapModule);
<commit_msg>Correcting piwik tracking.<commit_after>(function(module) {
    'use strict';

    // TODO: refactor (it's getting way too big!), document and add tests!!
    module.controller('MapCtrl', [
        '$scope', '$location', '$timeout', 'leafletData', 'angularPiwik', 'mapDefaults',
        'playerService', 'Location', 'locationService', 'mainMapService', 'backendService',
        function($scope, $location, $timeout, leafletData, angularPiwik, mapDefaults,
            playerService, Location, locationService, mainMapService, backendService) {
            var player;

            /**
             * Leaflet map settings
             * @type {{}}
             */
            $scope.mapDefaults = mapDefaults;

            /**
             * Locations loaded from the backend indexed by id
             * @type {{}}
             */
            var locations = {};

            /**
             * Whether the user is currently adding a new location
             * @type {boolean}
             */
            $scope.isAddingLocation = false;

            // TODO: all this addLocation stuff should be separated to a directive or other controller
            $scope.addLocationStep = 1;

            $scope.nextStep = function() {
                if ($scope.stepIsValid()) {
                    if ($scope.isLastStep()) {
                        $scope.addNewLocation();
                        angularPiwik.track('map.addLocation', 'finish');
                    }
                    else {
                        $scope.addLocationStep += 1;
                        $scope.addLocationComponent.minimised = false;
                        angularPiwik.track('map.addLocation', 'nextStep', $scope.addLocationStep);
                    }
                }
            };

            $scope.previousStep = function() {
                if ($scope.addLocationStep > 1) {
                    $scope.addLocationStep -= 1;
                    $scope.addLocationComponent.minimised = false;
                    angularPiwik.track('map.addLocation', 'previousStep', $scope.addLocationStep);
                }
            };

            $scope.stepIsValid = function() {
                var loc = $scope.newLocation;
                switch ($scope.addLocationStep) {
                case 1:
                    return (angular.isString(loc.type) && loc.type.length > 0);
                case 2:
                    return (angular.isString(loc.name) && loc.name.length > 0);
                case 3:
                    return (angular.isNumber(loc.lat) && angular.isNumber(loc.lng));
                default:
                    return false;
                }
            };

            $scope.isLastStep = function() {
                return ($scope.addLocationStep === 3);
            };

            // Expose the location service
            $scope.location = locationService;

            // Expose map settings and filters from the service
            $scope.mainMap = mainMapService;
            $scope.activeFilters = locationService.activeFilters;
            $scope.POSSIBLE_FILTERS = locationService.POSSIBLE_FILTERS;

            /**
             * Expose the location types
             * @type {{}}
             */
            $scope.locationTypes = Location.TYPES;

            /**
             * Empty events object (needed to get the leaflet map to broadcast events)
             * @type {{}}
             */
            $scope.events = {};

            /**
             * Location that is currently being added
             * @type {Location}
             */
            $scope.newLocation = undefined;

            /**
             * Whether to show the location products
             * @type {boolean}
             */
            $scope.productShown = false;

            /**
             * Whether to show the location filters
             * @type {boolean}
             */
            $scope.filtersShown = false;

            /**
             * whether to show search
             * @type {boolean}
             */
            $scope.searchShown = false;

            /**
             * Whether the add location form is minimised
             * TODO: should probably go elsewhere
             * @type {{minimised: boolean}}
             */
            $scope.addLocationComponent = {
                minimised: false
            };

            /**
             * Whether the search form is minimised
             * TODO: should probably go elsewhere
             * @type {{minimised: boolean}}
             */
            $scope.searchComponent = {
                minimised: false
            };

            /**
             * Sets whether the product list is shown
             * @param {boolean} [show=true]
             */
            $scope.showProductList = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.productShown !== show) {
                    $scope.productShown = show;
                    angularPiwik.track('map.productList', show ? 'open' : 'close');
                }
            };

            /**
             * Sets whether the filters are shown
             * @param {boolean} [show=true]
             */
            $scope.showFilters = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.filtersShown !== show) {
                    $scope.filtersShown = show;
                    angularPiwik.track('map.filters', show ? 'open' : 'close');
                }
            };

            /**
             * Sets whether the search is shown
             * @param {boolean} [show=true]
             */
            $scope.showSearch = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if ($scope.searchShown !== show) {
                    $scope.searchShown = show;
                    angularPiwik.track('map.search', show ? 'open' : 'close');
                }
            };

            /**
             * Returns the number of currently active filters
             * @returns {number}
             */
            $scope.numActiveFilters = function() {
                var active = 0;
                if ($scope.activeFilters.recent !== 'anytime') {
                    active += 1;
                }
                if ($scope.activeFilters.type !== 'anytype') {
                    active += 1;
                }

                return active;
            };

            /**
             * Whether the newLocation has already been added to the map
             * @type {boolean}
             */
            var newLocationIsAddedToMap = false;

            /**
             * Whether we are currently placing a location on the map
             * @returns {boolean}
             */
            $scope.isPlacingLocation = function() {
                return ($scope.isAddingLocation && $scope.addLocationStep === 3);
            };

            /**
             * Starts adding a new location
             */
            $scope.startAddNewLocation = function() {
                $scope.addLocationStep = 1;
                $scope.isAddingLocation = true;
                $scope.newLocation = new Location({team: player.team});
                $scope.newLocation.setEditing(true);
                locationService.activate($scope.newLocation);

                angularPiwik.track('map.addLocation', 'start');
            };

            /**
             * Aborts adding a new location
             */
            $scope.resetAddNewLocation = function() {
                locationService.activate();
                // Remove from map if it was added
                if (newLocationIsAddedToMap === true) {
                    var markerToRemove = $scope.newLocation.marker;
                    mapPromise.then(function(map) {
                        map.removeLayer(markerToRemove);
                    });
                }
                $scope.isAddingLocation = false;
                $scope.newLocation = undefined;
                newLocationIsAddedToMap = false;
            };

            /**
             * Finalises adding a new location
             */
            $scope.addNewLocation = function() {
                var newLocation = $scope.newLocation;
                newLocation.setEditing(false);
                $scope.isAddingLocation = false;
                $scope.newLocation = undefined;
                newLocationIsAddedToMap = false;

                // Submit the location and add it to the list once done
                locationService.submitLocation(newLocation)
                    .then(function() {
                        locations[newLocation.id] = newLocation;
                    })
                ;
            };

            /**
             * Goes to the location view
             * @param location
             */
            $scope.visitLocation = function(location) {
                $location.path('location/' + location.id);
            };

            /**
             * Sets the given coordinates as the lat/lng of the location
             * that is being added.
             * @param {number} lat
             * @param {number} lng
             * @return {boolean} Whether the coordinates where set on the new location
             */
            $scope.setNewLocationCoordinates = function(lat, lng) {
                if ($scope.isAddingLocation) {
                    // Set the coordinates
                    $scope.newLocation.setLatLng(lat, lng);

                    // Push to map if not already there
                    if (newLocationIsAddedToMap !== true) {
                        mapPromise.then(function(map) {
                            $scope.newLocation.marker.on('click', locationClickHandler);
                            $scope.newLocation.marker.addTo(map);
                        });
                        newLocationIsAddedToMap = true;
                    }

                    // Hide the component
                    $scope.addLocationComponent.minimised = true;

                    // Zoom in all the way to make sure users place it precisely
                    // TODO: duplication with EditLocationCtrl
                    mapPromise.then(function(map) {
                        var maxZoom = map.getMaxZoom();
                        var zoomTo = [lat, lng];
                        if (map.getZoom() < maxZoom || !map.getBounds().contains(zoomTo)) {
                            map.setView(zoomTo, maxZoom);
                        }
                    });

                    return true;
                }
                return false;
            };

            /**
             * Handler for clicks on the map
             * @param event
             * @param args
             */
            var mapClickHandler = function(event, args) {
                if ($scope.isAddingLocation) {
                    if ($scope.addLocationStep === 3) {
                        // When adding a new location, take the click
                        // as the coordinates of this new location
                        $scope.setNewLocationCoordinates(
                            args.leafletEvent.latlng.lat,
                            args.leafletEvent.latlng.lng
                        );

                        angularPiwik.track('map.addLocation', 'mapClick');
                    }
                    // TODO: else what? We are adding a location but clicked one -> should show some info of the clicked place
                }
                else {
                    // When not adding a location, deselect currently active location
                    locationService.activate();

                    // And hide product list, filters and search
                    $scope.showProductList(false);
                    $scope.showFilters(false);
                    $scope.showSearch(false);
                }
            };

            /**
             * Handler for clicks on map markers (Locations)
             * @param event Event coming directly from leaflet
             *      (not angular-leaflet-directive)
             */
            var locationClickHandler = function(event) {
                if (!$scope.isAddingLocation) {
                    var clickedLocation = locations[event.target.locationId];
                    if (clickedLocation) {
                        // Run it through $apply since we are coming directly from Leaflet
                        $scope.$apply(function() {
                            locationService.activate(clickedLocation);

                            // Hide the product list, filters and search
                            $scope.showProductList(false);
                            $scope.showFilters(false);
                            $scope.showSearch(false);
                        });
                    }
                }
            };

            // Get a reference the the leaflet map object
            var mapPromise = leafletData.getMap();

            // Register event handlers
            $scope.$on('leafletDirectiveMap.click', mapClickHandler);

            // Get the locations
            locationService.getLocations().then(function(loadedLocations) {
                locations = loadedLocations;
                mapPromise.then(function(map) {
                    // Go through all the locations and add the marker to the map
                    angular.forEach(locations, function(location) {
                        location.marker.on('click', locationClickHandler);
                        location.marker.addTo(map);
                    });

                    // Apply the current filter value
                    applyFilters($scope.activeFilters);
                });
            });

            // Check if we are logged in
            if (backendService.isLoggedIn()) {
                // Get the player
                playerService.getMe().then(function(me) {
                    player = me;
                });
            }

            // Watch the map center for changes to save it
            $scope.$watch('mainMap.center', function() {
                mainMapService.saveCenter();
            });

            // TODO: move the filter stuff to a separate controller
            /**
             * Map of recent filter values to the period of
             * time for which to show the locations.
             * @type {{month: number, week: number, day: number}}
             */
            var RECENT_FILTER_PERIOD = {
                month: 4 * 7 * 24 * 3600000,
                week: 7 * 24 * 3600000,
                day: 24 * 3600000
            };

            /**
             * Runs the locations through the given recent filter
             * @param recentFilter
             */
            var _applyRecentFilter = function(recentFilter) {
                var showAll = (recentFilter === 'anytime');
                var recentDate;
                if (!showAll) {
                    recentDate = new Date(Date.now() - RECENT_FILTER_PERIOD[recentFilter]);
                }

                // Go through all the locations and filter them
                angular.forEach(locations, function(location) {
                    // Only apply the filter if the location is not already hidden
                    if (!location.isHidden()) {
                        var hideIt = (!showAll && location.updatedAt < recentDate);
                        location.setHidden(hideIt);
                    }
                });
            };

            /**
             * Runs the locations through the given type filter
             * @param typeFilter
             */
            var _applyTypeFilter = function(typeFilter) {
                var showAll = (typeFilter === 'anytype');
                // Go through all the locations and filter them
                angular.forEach(locations, function(location) {
                    // Only apply the filter if the location is not already hidden
                    if (!location.isHidden()) {
                        var hideIt = (!showAll && location.type !== typeFilter);
                        location.setHidden(hideIt);
                    }
                });
            };

            /**
             * Runs the locations through all the filters
             * Add new filters to this function
             * @param typeFilter
             */
            var applyFilters = function(filters, filtersBefore) {
                // First show all the locations
                // TODO: this is inefficient because the marker might update twice (show it, then hide it again)
                angular.forEach(locations, function(location) {
                    location.setHidden(false);
                });

                // Track filter usage
                if (angular.isDefined(filtersBefore)) {
                    if (filters.recent !== filtersBefore.recent) {
                        angularPiwik.track('map.filters', 'applyFilter.recent', filters.recent);
                    }
                    if (filters.type !== filtersBefore.type) {
                        angularPiwik.track('map.filters', 'applyFilter.type', filters.type);
                    }
                }

                // Then run the filters
                _applyRecentFilter(filters.recent);
                _applyTypeFilter(filters.type);
            };

            // Watch the active filters
            $scope.$watch('activeFilters', applyFilters, true);

            // Listen to location changes to update the map center
            // (if the user changes the hash manually)
            $scope.$onRootScope('$locationChangeSuccess', function() {
                mainMapService.setMapCenterFromUrl();
            });

            // When we go away from this page, reset the url
            $scope.$onRootScope('$routeChangeStart', function() {
                // Remove the search and hash params
                $location.url($location.path());
            });
        }
    ]);
})(window.veganaut.mapModule);
<|endoftext|>


<commit_before>var intervalId = -1;

$(document).ready(function() {
	// From https://gist.github.com/alanhamlett/6316427
	$.ajaxSetup({
		beforeSend: function(xhr, settings) {
			if (settings.type == 'POST' || settings.type == 'PUT' || settings.type == 'DELETE' || settings.type == 'PATCH') {
				xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
			}
		}
	});

	function updateTimers() {
		$.ajax({
			url: '/status/',
			method: 'GET',
			data: {
				'key': webKey,
			},
			contentType: 'application/json',
			success: function(data) {
				data = JSON.parse(data);

				for (i in data) {
					var goal = data[i];
					var goalElement = $(".goal[data-id=" + goal.id + "]");

					if (goalElement.find(".button.stop").length > 0) {
						goalElement.find(".timer .num").html(goal.current_elapsed);
						goalElement.find(".timer .seconds").html(goal.current_elapsed_in_seconds);
						goalElement.find(".info .current").html(goal.current_amount);
						goalElement.find(".percentage .bar").css("width", goal.current_percentage + "%");

						if (goal.current_amount >= parseFloat(goalElement.find(".info .target").html())) {
							goalElement.find(".percentage .bar").addClass("over");
						}
					}
				}
			},
			error: function(data) {
				console.log("error", data);
			},
		});
	}

	function checkTimers() {
		if (intervalId == -1) {
			intervalId = setInterval(updateTimers, 1000);
		}
	}

	if ($(".goal .button.stop").length > 0) {
		checkTimers();

		// Update the favicon
		updateFavicon("timer");
	}

	function updateFavicon(type) {
		var dataHref = "data-" + type + "-href";

		var oldIco = $("#favicon-ico");
		var oldPng = $("#favicon-png");

		var newIco = "<link id='favicon-ico' rel='shortcut icon' href='" + oldIco.attr(dataHref) + "' data-original-href='" + oldIco.attr("data-original-href") + "' data-timer-href='" + oldIco.attr("data-timer-href") + "' />";
		var newPng = "<link id='favicon-png' rel='shortcut icon' href='" + oldPng.attr(dataHref) + "' data-original-href='" + oldPng.attr("data-original-href") + "' data-timer-href='" + oldPng.attr("data-timer-href") + "' />";

		oldIco.remove();
		oldPng.remove();

		$("head").append(newIco);
		$("head").append(newPng);
	}

	function toggleButtonClass(button, data) {
		if (button.hasClass("start")) {
			// Turn into a Stop button
			button.addClass("stop").removeClass("start");
			button.find(".label").html("Stop");

			// Show the timer
			button.find(".timer").removeClass("hidden");

			// Set it running and remove the stale class if it's there
			button.parents(".goal").addClass("running").removeClass("stale");

			// Update the favicon
			updateFavicon("timer");

			checkTimers();
		} else if (button.hasClass("stop")) {
			// Turn into a Start button
			button.addClass("start").removeClass("stop");
			button.find(".label").html("Start");

			// Hide the timer
			button.find(".timer").addClass("hidden");

			// Stop it running
			button.parents(".goal").removeClass("running");

			// Stop pinging the status web service if there aren't any more active timers
			if ($(".goal .button.stop").length == 0) {
				clearInterval(intervalId);
				intervalId = -1;

				// Change the favicon back
				updateFavicon("original");
			}
		}
	}

	// Start/stop buttons
	$(".button.action").on("click touchstart", function() {
		// Get the id of the goal we want
		var goalId = $(this).parents(".goal").attr("data-id");
		var button = $(this);

		if (goalId != '') {
			// Send in the timer request
			// It's GET so we can use it in Launch Center Pro, Drafts, etc.
			$.ajax({
				url: '/' + goalId + '/timer/',
				method: 'GET',
				data: {
					'key': webKey,
				},
				contentType: 'application/json',
				success: function(data) {
					data = JSON.parse(data);
					toggleButtonClass(button, data);
				},
				error: function(data) {
					console.log("error", data);
				},
			});
		}

		return false;
	});

	// Wordcount/times entry
	$(".entry.action .save").on("click touchstart", function() {
		// Get the ID of the goal we want
		var goalId = $(this).parents(".goal").attr("data-id");

		var parentWrapper = $(this).parents(".entry.action");
		var amount = $(this).siblings("input.number-entry").val();

		if (goalId != '' && amount > 0) {
			// Send in the request
			$.ajax({
				url: '/' + goalId + '/save/',
				method: 'GET',
				data: {
					'key': webKey,
					'amount': amount,
				},
				contentType: 'application/json',
				success: function(data) {
					data = JSON.parse(data);

					if (data.status == 'success') {
						// Update count
						var goalElement = parentWrapper.parents(".goal");
						goalElement.find(".info .current").html(data.total_amount);
						goalElement.find(".percentage .bar").css("width", data.percentage + "%");

						if (parseFloat(data.total_amount) >= parseFloat(goalElement.find(".info .target").html())) {
							goalElement.find(".percentage .bar").addClass("over");
						}

						// Clear out the input
						parentWrapper.find("input.number-entry").val('');
					} else {
						parentWrapper.addClass("error");
					}
				},
				error: function(data) {
					console.log("error", data);
				},
			});
		}

		return false;
	});

	// Add event handler for reordering goals
	var goals = $(".goal-list.sortable");
	for (var i=0; i<goals.length; i++) {
		var goalList = goals[i];

		var sortable = new Sortable(goalList, {
			draggable: ".goal",
			handle: ".handle",
			ghostClass: "placeholder",
			onUpdate: function(e) {
				var item = $(e.item);
				var order = [];
				var items = item.parents(".goal-list").find(".goal");

				for (var i=0; i<items.length; i++) {
					var item = $(items[i]);
					order.push(parseInt(item.attr("data-id")));
				}

				var url = '/update-goals/';

				$.ajax({
					url: url,
					method: 'POST',
					contentType: 'application/json',
					data: JSON.stringify({ "order": order }),
					success: function(data) { },
					error: function(data) {
						console.log("Error! :(", data);
					},
				});
			},
		});
	}
});

// From https://gist.github.com/alanhamlett/6316427
function getCookie(name) {
	var cookieValue = null;
	if (document.cookie && document.cookie != '') {
		var cookies = document.cookie.split(';');
		for (var i=0; i<cookies.length; i++) {
			var cookie = jQuery.trim(cookies[i]);
			// Does this cookie string begin with the name we want?
			if (cookie.substring(0, name.length + 1) == (name + '=')) {
				cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
				break;
			}
		}
	}
	return cookieValue;
}
<commit_msg>Hide completed goals<commit_after>var intervalId = -1;

$(document).ready(function() {
	// From https://gist.github.com/alanhamlett/6316427
	$.ajaxSetup({
		beforeSend: function(xhr, settings) {
			if (settings.type == 'POST' || settings.type == 'PUT' || settings.type == 'DELETE' || settings.type == 'PATCH') {
				xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
			}
		}
	});

	function updateTimers() {
		$.ajax({
			url: '/status/',
			method: 'GET',
			data: {
				'key': webKey,
			},
			contentType: 'application/json',
			success: function(data) {
				data = JSON.parse(data);

				for (i in data) {
					var goal = data[i];
					var goalElement = $(".goal[data-id=" + goal.id + "]");

					if (goalElement.find(".button.stop").length > 0) {
						goalElement.find(".timer .num").html(goal.current_elapsed);
						goalElement.find(".timer .seconds").html(goal.current_elapsed_in_seconds);
						goalElement.find(".info .current").html(goal.current_amount);
						goalElement.find(".percentage .bar").css("width", goal.current_percentage + "%");

						if (goal.current_amount >= parseFloat(goalElement.find(".info .target").html())) {
							goalElement.find(".percentage .bar").addClass("over");
						}
					}
				}
			},
			error: function(data) {
				console.log("error", data);
			},
		});
	}

	function checkTimers() {
		if (intervalId == -1) {
			intervalId = setInterval(updateTimers, 1000);
		}
	}

	if ($(".goal .button.stop").length > 0) {
		checkTimers();

		// Update the favicon
		updateFavicon("timer");
	}

	function updateFavicon(type) {
		var dataHref = "data-" + type + "-href";

		var oldIco = $("#favicon-ico");
		var oldPng = $("#favicon-png");

		var newIco = "<link id='favicon-ico' rel='shortcut icon' href='" + oldIco.attr(dataHref) + "' data-original-href='" + oldIco.attr("data-original-href") + "' data-timer-href='" + oldIco.attr("data-timer-href") + "' />";
		var newPng = "<link id='favicon-png' rel='shortcut icon' href='" + oldPng.attr(dataHref) + "' data-original-href='" + oldPng.attr("data-original-href") + "' data-timer-href='" + oldPng.attr("data-timer-href") + "' />";

		oldIco.remove();
		oldPng.remove();

		$("head").append(newIco);
		$("head").append(newPng);
	}

	function toggleButtonClass(button, data) {
		if (button.hasClass("start")) {
			// Turn into a Stop button
			button.addClass("stop").removeClass("start");
			button.find(".label").html("Stop");

			// Show the timer
			button.find(".timer").removeClass("hidden");

			// Set it running and remove the stale class if it's there
			button.parents(".goal").addClass("running").removeClass("stale");

			// Update the favicon
			updateFavicon("timer");

			checkTimers();
		} else if (button.hasClass("stop")) {
			// Turn into a Start button
			button.addClass("start").removeClass("stop");
			button.find(".label").html("Start");

			// Hide the timer
			button.find(".timer").addClass("hidden");

			// Stop it running
			button.parents(".goal").removeClass("running");

			// Stop pinging the status web service if there aren't any more active timers
			if ($(".goal .button.stop").length == 0) {
				clearInterval(intervalId);
				intervalId = -1;

				// Change the favicon back
				updateFavicon("original");
			}

			// If it was complete, hide it
			if (button.parents(".goal").find(".percentage .bar").hasClass("over")) {
				button.parents(".goal").slideUp(150);
			}
		}
	}

	// Start/stop buttons
	$(".button.action").on("click touchstart", function() {
		// Get the id of the goal we want
		var goalId = $(this).parents(".goal").attr("data-id");
		var button = $(this);

		if (goalId != '') {
			// Send in the timer request
			// It's GET so we can use it in Launch Center Pro, Drafts, etc.
			$.ajax({
				url: '/' + goalId + '/timer/',
				method: 'GET',
				data: {
					'key': webKey,
				},
				contentType: 'application/json',
				success: function(data) {
					data = JSON.parse(data);
					toggleButtonClass(button, data);
				},
				error: function(data) {
					console.log("error", data);
				},
			});
		}

		return false;
	});

	// Wordcount/times entry
	$(".entry.action .save").on("click touchstart", function() {
		// Get the ID of the goal we want
		var goalId = $(this).parents(".goal").attr("data-id");

		var parentWrapper = $(this).parents(".entry.action");
		var amount = $(this).siblings("input.number-entry").val();

		if (goalId != '' && amount > 0) {
			// Send in the request
			$.ajax({
				url: '/' + goalId + '/save/',
				method: 'GET',
				data: {
					'key': webKey,
					'amount': amount,
				},
				contentType: 'application/json',
				success: function(data) {
					data = JSON.parse(data);

					if (data.status == 'success') {
						// Update count
						var goalElement = parentWrapper.parents(".goal");
						goalElement.find(".info .current").html(data.total_amount);
						goalElement.find(".percentage .bar").css("width", data.percentage + "%");

						if (parseFloat(data.total_amount) >= parseFloat(goalElement.find(".info .target").html())) {
							goalElement.find(".percentage .bar").addClass("over");
						}

						// Clear out the input
						parentWrapper.find("input.number-entry").val('');
					} else {
						parentWrapper.addClass("error");
					}
				},
				error: function(data) {
					console.log("error", data);
				},
			});
		}

		return false;
	});

	// Add event handler for reordering goals
	var goals = $(".goal-list.sortable");
	for (var i=0; i<goals.length; i++) {
		var goalList = goals[i];

		var sortable = new Sortable(goalList, {
			draggable: ".goal",
			handle: ".handle",
			ghostClass: "placeholder",
			onUpdate: function(e) {
				var item = $(e.item);
				var order = [];
				var items = item.parents(".goal-list").find(".goal");

				for (var i=0; i<items.length; i++) {
					var item = $(items[i]);
					order.push(parseInt(item.attr("data-id")));
				}

				var url = '/update-goals/';

				$.ajax({
					url: url,
					method: 'POST',
					contentType: 'application/json',
					data: JSON.stringify({ "order": order }),
					success: function(data) { },
					error: function(data) {
						console.log("Error! :(", data);
					},
				});
			},
		});
	}
});

// From https://gist.github.com/alanhamlett/6316427
function getCookie(name) {
	var cookieValue = null;
	if (document.cookie && document.cookie != '') {
		var cookies = document.cookie.split(';');
		for (var i=0; i<cookies.length; i++) {
			var cookie = jQuery.trim(cookies[i]);
			// Does this cookie string begin with the name we want?
			if (cookie.substring(0, name.length + 1) == (name + '=')) {
				cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
				break;
			}
		}
	}
	return cookieValue;
}
<|endoftext|>


<commit_before>import _ from "lodash";
import Mousetrap from "mousetrap";

import Header from "./ui/header.js";
import ModeMenu from "./ui/mode_menu.js";

export default class Keys {
  constructor() {
    this.actions = {};
    this.bindings = [];
  }

  register(actions = {}) {
    this.actions = _.merge({}, this.actions, actions);
    return this;
  }

  bind(bindings) {
    _(bindings).each(binding => {
      const keys = _(binding).keys().get(0);
      const action = binding[keys];
      const callback = this.actions[action];

      if (action === undefined || callback === undefined) {
        console.log(`[KEY] FAIL ${keys} -> ${action}`);
        return this;
      }

      Mousetrap.bind(keys, (...args) => {
        console.log(`[KEY] EXEC ${keys} -> ${action} (${callback.name})`);
        return callback(...args);
      });

      console.log(`[KEY] BIND ${keys} -> ${action} (${callback.name})`);
    });

    this.bindings = _.concat(this.bindings, bindings);
    return this;
  }

  initialize() {
    this.register({
      "escape": Keys.escape,

      "goto-page": Keys.gotoPage,
      "goto-last-page": Keys.gotoLastPage,

      "header-open": Header.open,
      "header-close": Header.close,
      "header-toggle": Header.toggle,
      "header-focus-search": Header.focusSearch,
      "header-execute-search-in-new-tab": Header.executeSearchInNewTab,

      "select-all": ModeMenu.selectAll,
      "invert-selection": ModeMenu.invertSelection,
      "apply-tag-script": ModeMenu.applyTagScript,
      "switch-to-tag-script": ModeMenu.switchToTagScript,
      "set-preview-mode": () => ModeMenu.setMode("preview"),
    });

    this.bind([
      { "q": "header-focus-search" },
      // XXX { "mod enter": "header-execute-search-in-new-tab" },
      { "h o": "header-open" },
      { "h c": "header-close" },
      { "h t": "header-toggle" },
      { "h h": "header-focus-search" },

      { "g 0": "goto-last-page" },
      { "g 1": "goto-page" },
      { "g 2": "goto-page" },
      { "g 3": "goto-page" },
      { "g 4": "goto-page" },
      { "g 5": "goto-page" },
      { "g 6": "goto-page" },
      { "g 7": "goto-page" },
      { "g 8": "goto-page" },
      { "g 9": "goto-page" },

      { "1": "switch-to-tag-script" },
      { "2": "switch-to-tag-script" },
      { "3": "switch-to-tag-script" },
      { "4": "switch-to-tag-script" },
      { "5": "switch-to-tag-script" },
      { "6": "switch-to-tag-script" },
      { "7": "switch-to-tag-script" },
      { "8": "switch-to-tag-script" },
      { "9": "switch-to-tag-script" },

      { "shift+a": "apply-tag-script" },
      { "ctrl+a": "select-all" },
      { "ctrl+i": "invert-selection" },
      { "`": "set-preview-mode" },
      { "~": "set-preview-mode" },
    ]);

    // XXX don't hardcode these
    Mousetrap.bindGlobal("esc", Keys.escape);
    Mousetrap.bindGlobal("ctrl+return", Keys.submitForm);

    // XXX figure out how to unbind W/S properly.
    //$(document).unbind("keydown", "w s");
    //Mousetrap.bind("w", Keys.scroll(+1, 50, 0.06));
    //Mousetrap.bind("s", Keys.scroll(-1, 50, 0.06));
    Danbooru.Shortcuts.nav_scroll_down = Keys.scroll(+1, 50, 0.06);
    Danbooru.Shortcuts.nav_scroll_up   = Keys.scroll(-1, 50, 0.06);
  }

  /* Actions */

  static escape(event) {
    const $target = $(event.target);

    if ($target.is("input, textarea")) {
      $target.blur();
    } else {
      $('#close-notice-link').click();
      // XXX only do if no notice and not already in view mode.
      ModeMenu.setMode("view");
    }
  }

  static submitForm(event) {
    const $target = $(event.target);

    if ($target.is("input, textarea")) {
      $target.closest("form").find('input[type="submit"][value="Submit"]').click();
    }

    return false;
  }

  static gotoPage(event) {
    Keys.gotoPageN(Number(event.key));
  }

  static gotoLastPage(event) {
    // a:not(a[rel]) - exclude the Previous/Next links seen in the paginator on /favorites et al.
    const n = $('div.paginator li:nth-last-child(2) a:not(a[rel])').first().text();

    if (n) {
      Keys.gotoPageN(n);
    }
  }

  static gotoPageN(n) {
    if (location.search.match(/page=(\d+)/)) {
      location.search = location.search.replace(/page=(\d+)/, `page=${n}`);
    } else {
      location.search += `&page=${n}`;
    }
  }

  static scroll(direction, duration, distance) {
    return _.throttle(() => {
      const top = $(window).scrollTop() + direction * $(window).height() * distance;
      $('html, body').animate({scrollTop: top}, duration, "linear");
    }, duration);
  }
}
<commit_msg>[fix] Fix escape not closing dialogs.<commit_after>import _ from "lodash";
import Mousetrap from "mousetrap";

import Header from "./ui/header.js";
import ModeMenu from "./ui/mode_menu.js";

export default class Keys {
  constructor() {
    this.actions = {};
    this.bindings = [];
  }

  register(actions = {}) {
    this.actions = _.merge({}, this.actions, actions);
    return this;
  }

  bind(bindings) {
    _(bindings).each(binding => {
      const keys = _(binding).keys().get(0);
      const action = binding[keys];
      const callback = this.actions[action];

      if (action === undefined || callback === undefined) {
        console.log(`[KEY] FAIL ${keys} -> ${action}`);
        return this;
      }

      Mousetrap.bind(keys, (...args) => {
        console.log(`[KEY] EXEC ${keys} -> ${action} (${callback.name})`);
        return callback(...args);
      });

      console.log(`[KEY] BIND ${keys} -> ${action} (${callback.name})`);
    });

    this.bindings = _.concat(this.bindings, bindings);
    return this;
  }

  initialize() {
    this.register({
      "escape": Keys.escape,

      "goto-page": Keys.gotoPage,
      "goto-last-page": Keys.gotoLastPage,

      "header-open": Header.open,
      "header-close": Header.close,
      "header-toggle": Header.toggle,
      "header-focus-search": Header.focusSearch,
      "header-execute-search-in-new-tab": Header.executeSearchInNewTab,

      "select-all": ModeMenu.selectAll,
      "invert-selection": ModeMenu.invertSelection,
      "apply-tag-script": ModeMenu.applyTagScript,
      "switch-to-tag-script": ModeMenu.switchToTagScript,
      "set-preview-mode": () => ModeMenu.setMode("preview"),
    });

    this.bind([
      { "q": "header-focus-search" },
      // XXX { "mod enter": "header-execute-search-in-new-tab" },
      { "h o": "header-open" },
      { "h c": "header-close" },
      { "h t": "header-toggle" },
      { "h h": "header-focus-search" },

      { "g 0": "goto-last-page" },
      { "g 1": "goto-page" },
      { "g 2": "goto-page" },
      { "g 3": "goto-page" },
      { "g 4": "goto-page" },
      { "g 5": "goto-page" },
      { "g 6": "goto-page" },
      { "g 7": "goto-page" },
      { "g 8": "goto-page" },
      { "g 9": "goto-page" },

      { "1": "switch-to-tag-script" },
      { "2": "switch-to-tag-script" },
      { "3": "switch-to-tag-script" },
      { "4": "switch-to-tag-script" },
      { "5": "switch-to-tag-script" },
      { "6": "switch-to-tag-script" },
      { "7": "switch-to-tag-script" },
      { "8": "switch-to-tag-script" },
      { "9": "switch-to-tag-script" },

      { "shift+a": "apply-tag-script" },
      { "ctrl+a": "select-all" },
      { "ctrl+i": "invert-selection" },
      { "`": "set-preview-mode" },
      { "~": "set-preview-mode" },
    ]);

    // XXX don't hardcode these
    Mousetrap.bindGlobal("esc", Keys.escape);
    Mousetrap.bindGlobal("ctrl+return", Keys.submitForm);

    // XXX figure out how to unbind W/S properly.
    //$(document).unbind("keydown", "w s");
    //Mousetrap.bind("w", Keys.scroll(+1, 50, 0.06));
    //Mousetrap.bind("s", Keys.scroll(-1, 50, 0.06));
    Danbooru.Shortcuts.nav_scroll_down = Keys.scroll(+1, 50, 0.06);
    Danbooru.Shortcuts.nav_scroll_up   = Keys.scroll(-1, 50, 0.06);
  }

  /* Actions */

  static escape(event) {
    const $target = $(event.target);

    if ($target.is("input, textarea")) {
      $target.blur();
    } else {
      $('#close-notice-link').click();
      // XXX only do if no notice and not already in view mode.
      ModeMenu.setMode("view");
    }

    // Allow event to bubble up so that escape still closes jquery UI dialogs.
    return true;
  }

  static submitForm(event) {
    const $target = $(event.target);

    if ($target.is("input, textarea")) {
      $target.closest("form").find('input[type="submit"][value="Submit"]').click();
    }

    return false;
  }

  static gotoPage(event) {
    Keys.gotoPageN(Number(event.key));
  }

  static gotoLastPage(event) {
    // a:not(a[rel]) - exclude the Previous/Next links seen in the paginator on /favorites et al.
    const n = $('div.paginator li:nth-last-child(2) a:not(a[rel])').first().text();

    if (n) {
      Keys.gotoPageN(n);
    }
  }

  static gotoPageN(n) {
    if (location.search.match(/page=(\d+)/)) {
      location.search = location.search.replace(/page=(\d+)/, `page=${n}`);
    } else {
      location.search += `&page=${n}`;
    }
  }

  static scroll(direction, duration, distance) {
    return _.throttle(() => {
      const top = $(window).scrollTop() + direction * $(window).height() * distance;
      $('html, body').animate({scrollTop: top}, duration, "linear");
    }, duration);
  }
}
<|endoftext|>


<commit_before>(function() {
    'use strict';

    /**
     * Component for the main map page.
     * Holds the map and all the actions possible on the map.
     * @returns {directive}
     */
    var mainMapDirective = function() {
        return {
            restrict: 'E',
            scope: {},
            controller: 'vgMainMapCtrl',
            controllerAs: 'mainMapVm',
            bindToController: true,
            link: function(scope, el, attrs, vm) {
                // Remove map on destroy. This clears all Leaflet event listeners properly.
                scope.$on('$destroy', function() {
                    vm.map.remove();
                });
            },
            templateUrl: '/veganaut/map/mainMap.tpl.html'
        };
    };

    // TODO: re-group variable and method definition
    // TODO: the main map should just do without angular-leaflet, it's hardly using it anymore
    var mainMapCtrl = [
        '$scope', '$location', '$route', '$uibModal', 'Leaflet', 'angularPiwik', 'mapDefaults', 'constants',
        'playerService', 'locationService', 'locationFilterService', 'mainMapService',
        function($scope, $location, $route, $uibModal, L, angularPiwik, mapDefaults, constants,
            playerService, locationService, locationFilterService, mainMapService)
        {
            var vm = this;

            // Expose the global methods we still need
            // TODO: find a better way to do this
            vm.legacyGlobals = {
                goToView: $scope.$parent.goToView,
                isLoggedIn: $scope.$parent.isLoggedIn,
                isEmbedded: $scope.$parent.isEmbedded
            };

            /**
             * Locations loaded from the backend
             * @type {LocationSet}
             */
            vm.locationSet = locationService.getLocationSet();

            /**
             * Whether to show the location products
             * @type {boolean}
             */
            vm.productShown = false;

            // Parse legacy URL and redirect if anything found
            // TODO: Remove this after a few month of having the new URL scheme
            var hash = $location.hash();
            var legacyParams = {};
            if (hash.length > 0) {
                var hashArgs = hash.split(',');
                _.each(hashArgs, function(arg) {
                    var split = arg.split(':');
                    if (split.length === 2 && ['zoom', 'coords', 'type'].indexOf(split[0]) > -1) {
                        if (split[0] === 'coords') {
                            // Convert coords from being separated by "-" to ","
                            var match = /(-?[0-9\.]+)-(-?[0-9\.]+)/.exec(split[1]);
                            if (match) {
                                split[1] = match[1] + ',' + match[2];
                            }
                        }
                        legacyParams[split[0]] = split[1];
                    }
                });
            }
            if (Object.keys(legacyParams).length > 0) {
                // Replace the URL (no new history entry) and delete hash
                $location
                    .replace()
                    .hash(null)
                ;

                // Set new route params and make sure the route is reloaded
                $route.updateParams(legacyParams);
                $route.reload();

                // Nothing else to be done here, we are redirecting
                return;
            }

            /**
             * Leaflet map object
             * @type {{}}
             */
            vm.map = L.map('mainMap', {
                layers: [
                    L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                        attribution: '© OpenStreetMap contributors'
                    })
                ],
                worldCopyJump: true
            });
            vm.map.zoomControl.setPosition('bottomleft');

            /**
             * Whether to show the 'city' or the 'street' part of the address.
             * Depends on the zoom level.
             * @type {string}
             */
            vm.addressType = undefined;

            /**
             * Method to inform service that the center changed
             */
            var informCenterChanged = function() {
                var newCenter = vm.map.getCenter();
                var zoom = vm.map.getZoom();

                // Check whether to show the city or street part of the address
                vm.addressType = (zoom > constants.ADDRESS_TYPE_BOUNDARY_ZOOM ? 'street' : 'city');

                mainMapService.onCenterChanged({
                    lat: newCenter.lat,
                    lng: newCenter.lng,
                    zoom: zoom,
                    boundingBox: vm.map.getBounds()
                });
            };

            // Get the player
            var playerPromise = playerService.getDeferredMe();

            /**
             * Sets whether the product list is shown
             * @param {boolean} [show=true]
             */
            vm.showProductList = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if (vm.productShown !== show) {
                    vm.productShown = show;
                    angularPiwik.track('map.products', 'map.products.' + (show ? 'show' : 'hide'));
                }
            };

            /**
             * Starts creating a new location
             */
            vm.startCreateLocation = function() {
                playerPromise.then(function(player) {
                    vm.locationSet.startCreateLocation(player, vm.map);
                });
            };

            /**
             * Temporary handler of the what's new test
             */
            vm.openWhatsNew = function() {
                angularPiwik.track('test.whatsNew', 'test.whatsNew.click');
                $uibModal.open({
                    template: '
{{ "missingFeature.whatsNew" | translate }}
' +
                    '
<button class="btn btn-default btn-block" ng-click="modalVm.close()">' +
                    '{{ "missingFeature.button" | translate }}</button>
',
                    controller: ['$modalInstance', function($modalInstance) {
                        var vm = this;
                        vm.close = function() {
                            $modalInstance.close();
                        };
                    }],
                    controllerAs: 'modalVm',
                    bindToController: true
                });
            };

            /**
             * Temporary handler of the rate test
             */
            vm.openRate = function() {
                angularPiwik.track('test.unregisteredRate', 'test.unregisteredRate.click');
                $uibModal.open({
                    template: '
{{ "missingFeature.rate" | translate }}' +
                    '' +
                    '{{ "missingFeature.rateLink" | translate }}.
' +
                    '
<button class="btn btn-default btn-block" ng-click="modalVm.close()">' +
                    '{{ "missingFeature.button" | translate }}</button>
',
                    controller: ['$modalInstance', function($modalInstance) {
                        var vm = this;
                        vm.close = function() {
                            $modalInstance.close();
                        };
                    }],
                    controllerAs: 'modalVm',
                    bindToController: true
                });
            };

            /**
             * Returns the location type to be shown on the product list
             * @returns {string}
             */
            vm.getProductListType = function() {
                // Default is gastronomy
                var type = 'gastronomy';

                // If filter is set to retail, then retail
                if (locationFilterService.activeFilters.type === 'retail') {
                    type = 'retail';
                }

                return type;
            };

            /**
             * Handler for clicks on map markers. This will only ever be called for
             * Locations, because only those are clickable (clusters aren't).
             * @param {Location} locationItem
             */
            vm.onLocationClick = function(locationItem) {
                if (!vm.locationSet.isCreatingLocation() && !locationItem.isDisabled()) {
                    // Run it through $apply since we are coming directly from Leaflet
                    $scope.$apply(function() {
                        vm.locationSet.activate(locationItem);

                        // Track it
                        angularPiwik.track('map.locations', 'map.locations.click');

                        // Hide the product list
                        vm.showProductList(false);
                    });
                }
                // TODO: if not handled, should pass on the click to the map?
            };

            // Register to map changes
            // We do it directly through leaflet, because watching the center
            // provided from leaflet-directive is buggy in some cases.
            vm.map.on('moveend', informCenterChanged);
            vm.map.on('viewreset', informCenterChanged);

            // Listen to clicks on the map
            vm.map.on('click', function() {
                $scope.$apply(function() {
                    if (!vm.locationSet.isCreatingLocation()) {
                        // When not adding a location, deselect currently active location
                        vm.locationSet.activate();

                        // And hide product list
                        vm.showProductList(false);
                    }
                });
            });

            // Listen to explicit area changes
            $scope.$on('veganaut.area.pushToMap', function() {
                mainMapService.showCurrentArea(vm.map);
            });

            // When we go away from this page, abort adding location
            $scope.$on('$routeChangeStart', function(event) {
                if (!event.defaultPrevented) {
                    vm.locationSet.abortCreateLocation();
                }
            });

            // Finally, initialise the map
            mainMapService.initialiseMap(vm.map);
        }
    ];

    // Expose as directive
    angular.module('veganaut.app.map')
        .controller('vgMainMapCtrl', mainMapCtrl)
        .directive('vgMainMap', [mainMapDirective])
    ;
})();
<commit_msg>Removing handling of legacy map URLs with hash parameters instead of search (GET) parameters. Also removing obsolete TODOs<commit_after>(function() {
    'use strict';

    /**
     * Component for the main map page.
     * Holds the map and all the actions possible on the map.
     * @returns {directive}
     */
    var mainMapDirective = function() {
        return {
            restrict: 'E',
            scope: {},
            controller: 'vgMainMapCtrl',
            controllerAs: 'mainMapVm',
            bindToController: true,
            link: function(scope, el, attrs, vm) {
                // Remove map on destroy. This clears all Leaflet event listeners properly.
                scope.$on('$destroy', function() {
                    vm.map.remove();
                });
            },
            templateUrl: '/veganaut/map/mainMap.tpl.html'
        };
    };

    var mainMapCtrl = [
        '$scope', '$uibModal', 'Leaflet', 'angularPiwik', 'mapDefaults', 'constants',
        'playerService', 'locationService', 'locationFilterService', 'mainMapService',
        function($scope, $uibModal, L, angularPiwik, mapDefaults, constants,
            playerService, locationService, locationFilterService, mainMapService)
        {
            var vm = this;

            // Expose the global methods we still need
            // TODO: find a better way to do this
            vm.legacyGlobals = {
                goToView: $scope.$parent.goToView,
                isLoggedIn: $scope.$parent.isLoggedIn,
                isEmbedded: $scope.$parent.isEmbedded
            };

            /**
             * Locations loaded from the backend
             * @type {LocationSet}
             */
            vm.locationSet = locationService.getLocationSet();

            /**
             * Whether to show the location products
             * @type {boolean}
             */
            vm.productShown = false;

            /**
             * Leaflet map object
             * @type {{}}
             */
            vm.map = L.map('mainMap', {
                layers: [
                    L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                        attribution: '© OpenStreetMap contributors'
                    })
                ],
                worldCopyJump: true
            });
            vm.map.zoomControl.setPosition('bottomleft');

            /**
             * Whether to show the 'city' or the 'street' part of the address.
             * Depends on the zoom level.
             * @type {string}
             */
            vm.addressType = undefined;

            /**
             * Method to inform service that the center changed
             */
            var informCenterChanged = function() {
                var newCenter = vm.map.getCenter();
                var zoom = vm.map.getZoom();

                // Check whether to show the city or street part of the address
                vm.addressType = (zoom > constants.ADDRESS_TYPE_BOUNDARY_ZOOM ? 'street' : 'city');

                mainMapService.onCenterChanged({
                    lat: newCenter.lat,
                    lng: newCenter.lng,
                    zoom: zoom,
                    boundingBox: vm.map.getBounds()
                });
            };

            // Get the player
            var playerPromise = playerService.getDeferredMe();

            /**
             * Sets whether the product list is shown
             * @param {boolean} [show=true]
             */
            vm.showProductList = function(show) {
                if (typeof show === 'undefined') {
                    show = true;
                }
                show = !!show;

                // Update and track if it changed
                if (vm.productShown !== show) {
                    vm.productShown = show;
                    angularPiwik.track('map.products', 'map.products.' + (show ? 'show' : 'hide'));
                }
            };

            /**
             * Starts creating a new location
             */
            vm.startCreateLocation = function() {
                playerPromise.then(function(player) {
                    vm.locationSet.startCreateLocation(player, vm.map);
                });
            };

            /**
             * Temporary handler of the what's new test
             */
            vm.openWhatsNew = function() {
                angularPiwik.track('test.whatsNew', 'test.whatsNew.click');
                $uibModal.open({
                    template: '
{{ "missingFeature.whatsNew" | translate }}
' +
                    '
<button class="btn btn-default btn-block" ng-click="modalVm.close()">' +
                    '{{ "missingFeature.button" | translate }}</button>
',
                    controller: ['$modalInstance', function($modalInstance) {
                        var vm = this;
                        vm.close = function() {
                            $modalInstance.close();
                        };
                    }],
                    controllerAs: 'modalVm',
                    bindToController: true
                });
            };

            /**
             * Temporary handler of the rate test
             */
            vm.openRate = function() {
                angularPiwik.track('test.unregisteredRate', 'test.unregisteredRate.click');
                $uibModal.open({
                    template: '
{{ "missingFeature.rate" | translate }}' +
                    '' +
                    '{{ "missingFeature.rateLink" | translate }}.
' +
                    '
<button class="btn btn-default btn-block" ng-click="modalVm.close()">' +
                    '{{ "missingFeature.button" | translate }}</button>
',
                    controller: ['$modalInstance', function($modalInstance) {
                        var vm = this;
                        vm.close = function() {
                            $modalInstance.close();
                        };
                    }],
                    controllerAs: 'modalVm',
                    bindToController: true
                });
            };

            /**
             * Returns the location type to be shown on the product list
             * @returns {string}
             */
            vm.getProductListType = function() {
                // Default is gastronomy
                var type = 'gastronomy';

                // If filter is set to retail, then retail
                if (locationFilterService.activeFilters.type === 'retail') {
                    type = 'retail';
                }

                return type;
            };

            /**
             * Handler for clicks on map markers. This will only ever be called for
             * Locations, because only those are clickable (clusters aren't).
             * @param {Location} locationItem
             */
            vm.onLocationClick = function(locationItem) {
                if (!vm.locationSet.isCreatingLocation() && !locationItem.isDisabled()) {
                    // Run it through $apply since we are coming directly from Leaflet
                    $scope.$apply(function() {
                        vm.locationSet.activate(locationItem);

                        // Track it
                        angularPiwik.track('map.locations', 'map.locations.click');

                        // Hide the product list
                        vm.showProductList(false);
                    });
                }
                // TODO: if not handled, should pass on the click to the map?
            };

            // Register to map changes
            // We do it directly through leaflet, because watching the center
            // provided from leaflet-directive is buggy in some cases.
            vm.map.on('moveend', informCenterChanged);
            vm.map.on('viewreset', informCenterChanged);

            // Listen to clicks on the map
            vm.map.on('click', function() {
                $scope.$apply(function() {
                    if (!vm.locationSet.isCreatingLocation()) {
                        // When not adding a location, deselect currently active location
                        vm.locationSet.activate();

                        // And hide product list
                        vm.showProductList(false);
                    }
                });
            });

            // Listen to explicit area changes
            $scope.$on('veganaut.area.pushToMap', function() {
                mainMapService.showCurrentArea(vm.map);
            });

            // When we go away from this page, abort adding location
            $scope.$on('$routeChangeStart', function(event) {
                if (!event.defaultPrevented) {
                    vm.locationSet.abortCreateLocation();
                }
            });

            // Finally, initialise the map
            mainMapService.initialiseMap(vm.map);
        }
    ];

    // Expose as directive
    angular.module('veganaut.app.map')
        .controller('vgMainMapCtrl', mainMapCtrl)
        .directive('vgMainMap', [mainMapDirective])
    ;
})();
<|endoftext|>


<commit_before>
var sbTreeHandler = {

    get TREE() { return document.getElementById("sbTree"); },

    get resource() {
        if ( this.TREE.view.selection.count < 1 ) {
            return null;
        } else {
            return this.TREE.builderView.getResourceAtIndex(this.TREE.currentIndex);
        }
    },

    autoCollapse: false,

    init: function(isContainer) {
        this.TREE.database.AddDataSource(sbDataSource.data);
        this.autoCollapse = sbCommonUtils.getPref("tree.autoCollapse", false);
        if ( isContainer ) document.getElementById("sbTreeRule").setAttribute("iscontainer", true);
        this.TREE.builder.rebuild();
    },

    exit: function() {
        var dsEnum = this.TREE.database.GetDataSources();
        while ( dsEnum.hasMoreElements() ) {
            var ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
            this.TREE.database.RemoveDataSource(ds);
        }
    },

    // aType: 0 = folderPicker.xul, 1 = manage.xul, 2 = scrapbook.xul
    onClick: function(aEvent, aType) {
        if ( aEvent.button != 0 && aEvent.button != 1 ) return;
        var obj = {};
        this.TREE.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, {}, {}, obj);
        if ( !obj.value || obj.value == "twisty" ) return;

        var curIdx = this.TREE.currentIndex;
        if (aType < 2) {
            // manage window, simple click to toggle container, mid-click to open in new tab
            // forbid ctrl- or shift- click because they are for multiple selection
            if (aEvent.button == 0 && !aEvent.ctrlKey && !aEvent.shiftKey && this.TREE.view.isContainer(curIdx)) {
                this.toggleFolder(curIdx);
            } else if (aEvent.button == 1 && sbDataSource.getProperty(this.resource, "type") != "folder") {
                sbController.open(this.resource, true);
            }
        } else {
            // sidebar, simple click to toggle container or to open data
            // mid-, ctrl-, and shift-click to open in new tab (except for folders)
            if (this.TREE.view.isContainer(curIdx)) {
                if (aEvent.button == 0 && !aEvent.ctrlKey && !aEvent.shiftKey) {
                    this.toggleFolder(curIdx);
                    return;
                }
                if (sbDataSource.getProperty(this.resource, "type") == "folder") {
                    return;
                }
            }
            sbController.open(this.resource, aEvent.button == 1 || aEvent.ctrlKey || aEvent.shiftKey);
        }
    },

    onKeyPress: function(aEvent) {
        switch ( aEvent.keyCode || aEvent.which ) {
            case aEvent.DOM_VK_RETURN:
                if ( this.TREE.view.isContainer(this.TREE.currentIndex) ) return;
                sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
                break;
            case aEvent.DOM_VK_SPACE:
                if ( sbDataSource.getProperty(this.resource, "type") == "folder" ) return;
                sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
                break;
            case aEvent.DOM_VK_DELETE: this.remove(); break;
            case aEvent.DOM_VK_F2: sbController.forward(this.resource, "P"); break;
        }
    },

    // double click (any button) on container: toggle container (natively)
    onDblClick: function(aEvent) {
        if ( aEvent.originalTarget.localName != "treechildren" || aEvent.button != 0 ) return;
        if ( this.TREE.view.isContainer(this.TREE.currentIndex) ) return;
        sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
    },

    send: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            var curRes = this.TREE.builderView.getResourceAtIndex(idxList[i]);
            var parRes = this.getParentResource(idxList[i]);
            if ( parRes.Value == "urn:scrapbook:search" ) {
                parRes = sbDataSource.findParentResource(curRes);
                if (!parRes) { sbCommonUtils.alert(sbCommonUtils.lang("ERR_FAIL_SEND")); return; }
            }
            resList.push(curRes);
            parList.push(parRes);
        }
        sbController.sendInternal(resList, parList);
    },

    copy: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            var curRes = this.TREE.builderView.getResourceAtIndex(idxList[i]);
            var parRes = this.getParentResource(idxList[i]);
            if ( parRes.Value == "urn:scrapbook:search" ) {
                parRes = sbDataSource.findParentResource(curRes);
                if (!parRes) { sbCommonUtils.alert(sbCommonUtils.lang("ERR_FAIL_SEND")); return; }
            }
            resList.push(curRes);
            parList.push(parRes);
        }
        sbController.copyInternal(resList, parList);
    },

    remove: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            resList.push( this.TREE.builderView.getResourceAtIndex(idxList[i]) );
            parList.push( this.getParentResource(idxList[i]) );
        }
        if ( !sbController.confirmRemovingFor(resList) ) return;
        var rmIDs = sbController.removeInternal(resList, parList, false);
        if ( rmIDs ) {
            sbMainService.trace(sbCommonUtils.lang("ITEMS_REMOVED", rmIDs.length));
            if ( "sbNoteService" in window && sbNoteService.resource && sbNoteService.resource.Value.substring(18,32) == rmIDs[0] ) sbNoteService.exit(false);
        }
    },

    locateInternal: function(aRes) {
        var i = 0;
        var resList = [];
        while ( aRes && aRes.Value != this.TREE.ref && ++i < 32 ) {
            resList.unshift(aRes);
            aRes = sbDataSource.findParentResource(aRes);
        }
        for ( i = 0; i < resList.length; i++ ) {
            var idx = this.TREE.builderView.getIndexOfResource(resList[i]);
            if ( idx != -1 && !this.TREE.view.isContainerOpen(idx) ) this.TREE.view.toggleOpenState(idx);
        }
        this.TREE.treeBoxObject.ensureRowIsVisible(idx);
        this.TREE.view.selection.select(idx);
        this.TREE.focus();
    },

    getParentResource: function(aIdx) {
        var parIdx = this.TREE.builderView.getParentIndex(aIdx);
        if ( parIdx == -1 ) {
            return this.TREE.resource;
        } else {
            return this.TREE.builderView.getResourceAtIndex(parIdx);
        }
    },

    getSelection: function(idx2res, rule) {
        var ret = [];
        for ( var rc = 0; rc < this.TREE.view.selection.getRangeCount(); rc++ ) {
            var start = {}, end = {};
            this.TREE.view.selection.getRangeAt(rc, start, end);
            for ( var i = start.value; i <= end.value; i++ ) {
                if ( rule == 1 && !this.TREE.view.isContainer(i) ) continue;
                if ( rule == 2 && this.TREE.view.isContainer(i) ) continue;
                ret.push( idx2res ? this.TREE.builderView.getResourceAtIndex(i) : i );
            }
        }
        return ret;
    },

    toggleFolder: function(aIdx) {
        if ( !aIdx ) aIdx = this.TREE.currentIndex;
        this.TREE.view.toggleOpenState(aIdx);
        if ( this.autoCollapse ) this.collapseFoldersBut(aIdx);
    },

    toggleAllFolders: function(forceClose) {
        var willOpen = true;
        for ( var i = 0; i < this.TREE.view.rowCount; i++ ) {
            if ( !this.TREE.view.isContainer(i) ) continue;
            if ( this.TREE.view.isContainerOpen(i) ) { willOpen = false; break; }
        }
        if ( forceClose ) willOpen = false;
        if ( willOpen ) {
            for ( var i = 0; i < this.TREE.view.rowCount; i++ ) {
                if ( this.TREE.view.isContainer(i) && !this.TREE.view.isContainerOpen(i) ) this.TREE.view.toggleOpenState(i);
            }
        } else {
            for ( var i = this.TREE.view.rowCount - 1; i >= 0; i-- ) {
                if ( this.TREE.view.isContainer(i) && this.TREE.view.isContainerOpen(i) ) this.TREE.view.toggleOpenState(i);
            }
        }
    },

    collapseFoldersBut: function(curIdx) {
        var ascIdxList = {};
        ascIdxList[curIdx] = true;
        while ( curIdx >= 0 ) {
            curIdx = this.TREE.builderView.getParentIndex(curIdx);
            ascIdxList[curIdx] = true;
        }
        for ( var i = this.TREE.view.rowCount - 1; i >= 0; i-- ) {
            if ( !ascIdxList[i] && this.TREE.view.isContainer(i) && this.TREE.view.isContainerOpen(i) ) {
                this.TREE.view.toggleOpenState(i);
            }
        }
    },

};
<commit_msg>manager: allow ctrl- or shift- enter to open in new tab<commit_after>
var sbTreeHandler = {

    get TREE() { return document.getElementById("sbTree"); },

    get resource() {
        if ( this.TREE.view.selection.count < 1 ) {
            return null;
        } else {
            return this.TREE.builderView.getResourceAtIndex(this.TREE.currentIndex);
        }
    },

    autoCollapse: false,

    init: function(isContainer) {
        this.TREE.database.AddDataSource(sbDataSource.data);
        this.autoCollapse = sbCommonUtils.getPref("tree.autoCollapse", false);
        if ( isContainer ) document.getElementById("sbTreeRule").setAttribute("iscontainer", true);
        this.TREE.builder.rebuild();
    },

    exit: function() {
        var dsEnum = this.TREE.database.GetDataSources();
        while ( dsEnum.hasMoreElements() ) {
            var ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
            this.TREE.database.RemoveDataSource(ds);
        }
    },

    // aType: 0 = folderPicker.xul, 1 = manage.xul, 2 = scrapbook.xul
    onClick: function(aEvent, aType) {
        if ( aEvent.button != 0 && aEvent.button != 1 ) return;
        var obj = {};
        this.TREE.treeBoxObject.getCellAt(aEvent.clientX, aEvent.clientY, {}, {}, obj);
        if ( !obj.value || obj.value == "twisty" ) return;

        var curIdx = this.TREE.currentIndex;
        if (aType < 2) {
            // manage window, simple click to toggle container, mid-click to open in new tab
            // forbid ctrl- or shift- click because they are for multiple selection
            if (aEvent.button == 0 && !aEvent.ctrlKey && !aEvent.shiftKey && this.TREE.view.isContainer(curIdx)) {
                this.toggleFolder(curIdx);
            } else if (aEvent.button == 1 && sbDataSource.getProperty(this.resource, "type") != "folder") {
                sbController.open(this.resource, true);
            }
        } else {
            // sidebar, simple click to toggle container or to open data
            // mid-, ctrl-, and shift-click to open in new tab (except for folders)
            if (this.TREE.view.isContainer(curIdx)) {
                if (aEvent.button == 0 && !aEvent.ctrlKey && !aEvent.shiftKey) {
                    this.toggleFolder(curIdx);
                    return;
                }
                if (sbDataSource.getProperty(this.resource, "type") == "folder") {
                    return;
                }
            }
            sbController.open(this.resource, aEvent.button == 1 || aEvent.ctrlKey || aEvent.shiftKey);
        }
    },

    // simple enter on container: toggle container (natively)
    onKeyPress: function(aEvent) {
        switch ( aEvent.keyCode || aEvent.which ) {
            case aEvent.DOM_VK_RETURN:
                if ( this.TREE.view.isContainer(this.TREE.currentIndex) && !aEvent.ctrlKey && !aEvent.shiftKey ) return;
                sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
                break;
            case aEvent.DOM_VK_SPACE:
                if ( sbDataSource.getProperty(this.resource, "type") == "folder" ) return;
                sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
                break;
            case aEvent.DOM_VK_DELETE: this.remove(); break;
            case aEvent.DOM_VK_F2: sbController.forward(this.resource, "P"); break;
        }
    },

    // double click (any button) on container: toggle container (natively)
    onDblClick: function(aEvent) {
        if ( aEvent.originalTarget.localName != "treechildren" || aEvent.button != 0 ) return;
        if ( this.TREE.view.isContainer(this.TREE.currentIndex) ) return;
        sbController.open(this.resource, aEvent.ctrlKey || aEvent.shiftKey);
    },

    send: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            var curRes = this.TREE.builderView.getResourceAtIndex(idxList[i]);
            var parRes = this.getParentResource(idxList[i]);
            if ( parRes.Value == "urn:scrapbook:search" ) {
                parRes = sbDataSource.findParentResource(curRes);
                if (!parRes) { sbCommonUtils.alert(sbCommonUtils.lang("ERR_FAIL_SEND")); return; }
            }
            resList.push(curRes);
            parList.push(parRes);
        }
        sbController.sendInternal(resList, parList);
    },

    copy: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            var curRes = this.TREE.builderView.getResourceAtIndex(idxList[i]);
            var parRes = this.getParentResource(idxList[i]);
            if ( parRes.Value == "urn:scrapbook:search" ) {
                parRes = sbDataSource.findParentResource(curRes);
                if (!parRes) { sbCommonUtils.alert(sbCommonUtils.lang("ERR_FAIL_SEND")); return; }
            }
            resList.push(curRes);
            parList.push(parRes);
        }
        sbController.copyInternal(resList, parList);
    },

    remove: function() {
        if ( this.TREE.view.selection.count == 0 ) return;
        var idxList = this.getSelection(false, 0), resList = [], parList = [];
        for ( var i = 0, I = idxList.length; i < I; i++ ) {
            resList.push( this.TREE.builderView.getResourceAtIndex(idxList[i]) );
            parList.push( this.getParentResource(idxList[i]) );
        }
        if ( !sbController.confirmRemovingFor(resList) ) return;
        var rmIDs = sbController.removeInternal(resList, parList, false);
        if ( rmIDs ) {
            sbMainService.trace(sbCommonUtils.lang("ITEMS_REMOVED", rmIDs.length));
            if ( "sbNoteService" in window && sbNoteService.resource && sbNoteService.resource.Value.substring(18,32) == rmIDs[0] ) sbNoteService.exit(false);
        }
    },

    locateInternal: function(aRes) {
        var i = 0;
        var resList = [];
        while ( aRes && aRes.Value != this.TREE.ref && ++i < 32 ) {
            resList.unshift(aRes);
            aRes = sbDataSource.findParentResource(aRes);
        }
        for ( i = 0; i < resList.length; i++ ) {
            var idx = this.TREE.builderView.getIndexOfResource(resList[i]);
            if ( idx != -1 && !this.TREE.view.isContainerOpen(idx) ) this.TREE.view.toggleOpenState(idx);
        }
        this.TREE.treeBoxObject.ensureRowIsVisible(idx);
        this.TREE.view.selection.select(idx);
        this.TREE.focus();
    },

    getParentResource: function(aIdx) {
        var parIdx = this.TREE.builderView.getParentIndex(aIdx);
        if ( parIdx == -1 ) {
            return this.TREE.resource;
        } else {
            return this.TREE.builderView.getResourceAtIndex(parIdx);
        }
    },

    getSelection: function(idx2res, rule) {
        var ret = [];
        for ( var rc = 0; rc < this.TREE.view.selection.getRangeCount(); rc++ ) {
            var start = {}, end = {};
            this.TREE.view.selection.getRangeAt(rc, start, end);
            for ( var i = start.value; i <= end.value; i++ ) {
                if ( rule == 1 && !this.TREE.view.isContainer(i) ) continue;
                if ( rule == 2 && this.TREE.view.isContainer(i) ) continue;
                ret.push( idx2res ? this.TREE.builderView.getResourceAtIndex(i) : i );
            }
        }
        return ret;
    },

    toggleFolder: function(aIdx) {
        if ( !aIdx ) aIdx = this.TREE.currentIndex;
        this.TREE.view.toggleOpenState(aIdx);
        if ( this.autoCollapse ) this.collapseFoldersBut(aIdx);
    },

    toggleAllFolders: function(forceClose) {
        var willOpen = true;
        for ( var i = 0; i < this.TREE.view.rowCount; i++ ) {
            if ( !this.TREE.view.isContainer(i) ) continue;
            if ( this.TREE.view.isContainerOpen(i) ) { willOpen = false; break; }
        }
        if ( forceClose ) willOpen = false;
        if ( willOpen ) {
            for ( var i = 0; i < this.TREE.view.rowCount; i++ ) {
                if ( this.TREE.view.isContainer(i) && !this.TREE.view.isContainerOpen(i) ) this.TREE.view.toggleOpenState(i);
            }
        } else {
            for ( var i = this.TREE.view.rowCount - 1; i >= 0; i-- ) {
                if ( this.TREE.view.isContainer(i) && this.TREE.view.isContainerOpen(i) ) this.TREE.view.toggleOpenState(i);
            }
        }
    },

    collapseFoldersBut: function(curIdx) {
        var ascIdxList = {};
        ascIdxList[curIdx] = true;
        while ( curIdx >= 0 ) {
            curIdx = this.TREE.builderView.getParentIndex(curIdx);
            ascIdxList[curIdx] = true;
        }
        for ( var i = this.TREE.view.rowCount - 1; i >= 0; i-- ) {
            if ( !ascIdxList[i] && this.TREE.view.isContainer(i) && this.TREE.view.isContainerOpen(i) ) {
                this.TREE.view.toggleOpenState(i);
            }
        }
    },

};
<|endoftext|>