<commit_before>(function (angular) {
    angular
        .module('mediaCenterWidget')
        .controller('WidgetHomeCtrl', ['$scope', '$window', 'DB', 'COLLECTIONS', '$rootScope', 'Buildfire', 'Messaging', 'EVENTS', 'PATHS', 'Location', 'Orders', '$location',
            function ($scope, $window, DB, COLLECTIONS, $rootScope, Buildfire, Messaging, EVENTS, PATHS, Location, Orders, $location) {
                $rootScope.showFeed = true;
                var WidgetHome = this;
                var _infoData = {
                    data: {
                        content: {
                            images: [],
                            descriptionHTML: '

',
                            description: '',
                            sortBy: Orders.ordersMap.Newest,
                            rankOfLastItem: 0
                        },
                        design: {
                            listLayout: "list-1",
                            itemLayout: "item-1",
                            backgroundImage: ""
                        }
                    }
                };
                var view = null;

                /**
                 * Create instance of MediaContent, MediaCenter db collection
                 * @type {DB}
                 */
                var MediaContent = new DB(COLLECTIONS.MediaContent),
                    MediaCenter = new DB(COLLECTIONS.MediaCenter);

                /**
                 * WidgetHome.media contains MediaCenterInfo.
                 * @type {MediaCenterInfo|*}
                 */
                var MediaCenterInfo = null;
                MediaCenter.get().then(function success(result) {
                        if (result && result.data && result.id) {
                            MediaCenterInfo = result;
                        }
                        else {
                            MediaCenterInfo = _infoData;
                        }
                        WidgetHome.media = MediaCenterInfo;
                        $rootScope.backgroundImage = MediaCenterInfo.data.design.backgroundImage;

                    },
                    function fail() {
                        MediaCenterInfo = _infoData;
                        WidgetHome.media = MediaCenterInfo;
                    }
                );
                var _skip = 0,
                    _limit = 10,
                    searchOptions = {
                        filter: {"$json.title": {"$regex": '/*'}},
                        skip: _skip,
                        limit: _limit + 1 // the plus one is to check if there are any more
                    };
                /**
                 * WidgetHome.isBusy is used for infinite scroll.
                 * @type {boolean}
                 */
                WidgetHome.isBusy = false;

                /*declare the device width heights*/
                $rootScope.deviceHeight = WidgetHome.deviceHeight = window.innerHeight;
                $rootScope.deviceWidth = WidgetHome.deviceWidth = window.innerWidth || 320;

                /*initialize the device width heights*/
                var initDeviceSize = function (callback) {
                    WidgetHome.deviceHeight = window.innerHeight;
                    WidgetHome.deviceWidth = window.innerWidth;
                    if (callback) {
                        if (WidgetHome.deviceWidth == 0 || WidgetHome.deviceHeight == 0) {
                            setTimeout(function () {
                                initDeviceSize(callback);
                            }, 500);
                        } else {
                            callback();
                            if (!$scope.$$phase && !$scope.$root.$$phase) {
                                $scope.$apply();
                            }
                        }
                    }
                };
                /**
                 * Messaging.onReceivedMessage is called when any event is fire from Content/design section.
                 * @param event
                 */
                Messaging.onReceivedMessage = function (event) {
                    if (event) {
                        switch (event.name) {
                            case EVENTS.ROUTE_CHANGE:
                                if((event.message && event.message.path == PATHS.MEDIA && $location.$$path.indexOf('/media') == -1) || (event.message && event.message.path != PATHS.MEDIA)) {
                                    var path = event.message.path,
                                        id = event.message.id;
                                    var url = "#/";
                                    switch (path) {
                                        case PATHS.MEDIA:
                                            url = url + "media/";
                                            if (id) {
                                                url = url + id + "/";
                                            }
                                            break;
                                    }
                                    Location.go(url);
                                    if (path == PATHS.MEDIA) {
                                        $rootScope.showFeed = false;
                                    }
                                    else {
                                        $rootScope.showFeed = true;
                                    }
                                    $scope.$apply();
                                }
                                break;
                            case EVENTS.ITEMS_CHANGE:
                                WidgetHome.refreshItems();
                                break;
                        }
                    }
                };

                var onUpdateCallback = function (event) {
                    if (event.tag == "MediaCenter") {
                        if (event.data) {
                            WidgetHome.media.data = event.data;
                            $rootScope.backgroundImage = WidgetHome.media.data.design.backgroundImage;
                            console.log(WidgetHome.media);
                            $scope.$apply();
                            if (view && event.data.content && event.data.content.images) {
                                view.loadItems(event.data.content.images);
                            }
                            WidgetHome.refreshItems();
                        }
                    }
                    else {
                        WidgetHome.refreshItems();
                    }
                };

                /**
                 * Buildfire.datastore.onUpdate method calls when Data is changed.
                 */
                var listener = Buildfire.datastore.onUpdate(onUpdateCallback);

                /**
                 * updateGetOptions method checks whether sort options changed or not.
                 * @returns {boolean}
                 */
                var updateGetOptions = function () {
                    var order = Orders.getOrder(WidgetHome.media.data.content.sortBy || Orders.ordersMap.Default);
                    if (order) {
                        var sort = {};
                        sort[order.key] = order.order;
                        searchOptions.sort = sort;
                        return true;
                    }
                    else {
                        return false;
                    }
                };

                // ShowDescription only when it have content
                WidgetHome.showDescription = function () {
                    if (WidgetHome.media.data.content.descriptionHTML == '

' || WidgetHome.media.data.content.descriptionHTML == '

'|| WidgetHome.media.data.content.descriptionHTML == '')
                        return false;
                    else
                        return true;
                };

                /**
                 * WidgetHome.items holds the array of items.
                 * @type {Array}
                 */
                WidgetHome.items = [];
                /**
                 * WidgetHome.noMore checks for further data
                 * @type {boolean}
                 */
                WidgetHome.noMore = false;

                /**
                 * loadMore method loads the items in list page.
                 */
                WidgetHome.loadMore = function () {

                    if (WidgetHome.isBusy || WidgetHome.noMore) {
                        return;
                    }
                    updateGetOptions();
                    WidgetHome.isBusy = true;

                    MediaContent.find(searchOptions).then(function success(result) {
                        if (WidgetHome.noMore)
                            return;

                        if (result.length <= _limit) {// to indicate there are more

                            WidgetHome.noMore = true;
                        }
                        else {
                            result.pop();
                            searchOptions.skip = searchOptions.skip + _limit;
                            WidgetHome.noMore = false;
                        }
                        WidgetHome.items = WidgetHome.items ? WidgetHome.items.concat(result) : result;
                        WidgetHome.isBusy = false;
                    }, function fail() {
                        WidgetHome.isBusy = false;
                        console.error('error');
                    });
                };

                WidgetHome.refreshItems = function () {
                    searchOptions.skip = 0;
                    WidgetHome.items = [];
                    WidgetHome.noMore = false;
                    WidgetHome.loadMore();
                };

                WidgetHome.goToMedia = function (ind) {
                    $rootScope.showFeed = false;
                    Location.go('#/media/' + WidgetHome.items[ind].id);
                };

                $rootScope.$on("Carousel:LOADED", function () {
                    if (WidgetHome.media.data.content && WidgetHome.media.data.content.images) {
                        view = new Buildfire.components.carousel.view("#carousel", WidgetHome.media.data.content.images);
                        //view.loadItems(WidgetHome.media.data.content.images, false);
                    } else {
                        view.loadItems([]);
                    }
                });

                Messaging.sendMessageToControl({
                    name: EVENTS.ROUTE_CHANGE,
                    message: {
                        path: PATHS.HOME
                    }
                });

                $rootScope.$watch('showFeed', function () {
                    if ($rootScope.showFeed) {
                        listener.clear();
                        listener = Buildfire.datastore.onUpdate(onUpdateCallback);

                        MediaCenter.get().then(function success(result) {
                                WidgetHome.media = result;
                            },
                            function fail() {
                                WidgetHome.media = _infoData;
                            }
                        );
                    }
                });
                /**
                 * Implementation of pull down to refresh
                 */
                var onRefresh=Buildfire.datastore.onRefresh(function(){
                    Location.goToHome();
                });

            }]);
})(window.angular);<commit_msg>133374019: MEDIA CENTER MANUAL: Share button should be called 'More Options'<commit_after>(function (angular) {
    angular
        .module('mediaCenterWidget')
        .controller('WidgetHomeCtrl', ['$scope', '$window', 'DB', 'COLLECTIONS', '$rootScope', 'Buildfire', 'Messaging', 'EVENTS', 'PATHS', 'Location', 'Orders', '$location',
            function ($scope, $window, DB, COLLECTIONS, $rootScope, Buildfire, Messaging, EVENTS, PATHS, Location, Orders, $location) {
                $rootScope.showFeed = true;
                var WidgetHome = this;
                var _infoData = {
                    data: {
                        content: {
                            images: [],
                            descriptionHTML: '

',
                            description: '',
                            sortBy: Orders.ordersMap.Newest,
                            rankOfLastItem: 0
                        },
                        design: {
                            listLayout: "list-1",
                            itemLayout: "item-1",
                            backgroundImage: ""
                        }
                    }
                };
                var view = null;

                /**
                 * Create instance of MediaContent, MediaCenter db collection
                 * @type {DB}
                 */
                var MediaContent = new DB(COLLECTIONS.MediaContent),
                    MediaCenter = new DB(COLLECTIONS.MediaCenter);

                /**
                 * WidgetHome.media contains MediaCenterInfo.
                 * @type {MediaCenterInfo|*}
                 */
                var MediaCenterInfo = null;
                MediaCenter.get().then(function success(result) {
                        if (result && result.data && result.id) {
                            MediaCenterInfo = result;
                        }
                        else {
                            MediaCenterInfo = _infoData;
                        }
                        WidgetHome.media = MediaCenterInfo;
                        $rootScope.backgroundImage = MediaCenterInfo.data.design.backgroundImage;

                    },
                    function fail() {
                        MediaCenterInfo = _infoData;
                        WidgetHome.media = MediaCenterInfo;
                    }
                );
                var _skip = 0,
                    _limit = 10,
                    searchOptions = {
                        filter: {"$json.title": {"$regex": '/*'}},
                        skip: _skip,
                        limit: _limit + 1 // the plus one is to check if there are any more
                    };
                /**
                 * WidgetHome.isBusy is used for infinite scroll.
                 * @type {boolean}
                 */
                WidgetHome.isBusy = false;

                /*declare the device width heights*/
                $rootScope.deviceHeight = WidgetHome.deviceHeight = window.innerHeight;
                $rootScope.deviceWidth = WidgetHome.deviceWidth = window.innerWidth || 320;

                /*initialize the device width heights*/
                var initDeviceSize = function (callback) {
                    WidgetHome.deviceHeight = window.innerHeight;
                    WidgetHome.deviceWidth = window.innerWidth;
                    if (callback) {
                        if (WidgetHome.deviceWidth == 0 || WidgetHome.deviceHeight == 0) {
                            setTimeout(function () {
                                initDeviceSize(callback);
                            }, 500);
                        } else {
                            callback();
                            if (!$scope.$$phase && !$scope.$root.$$phase) {
                                $scope.$apply();
                            }
                        }
                    }
                };
                /**
                 * Messaging.onReceivedMessage is called when any event is fire from Content/design section.
                 * @param event
                 */
                Messaging.onReceivedMessage = function (event) {
                    if (event) {
                        switch (event.name) {
                            case EVENTS.ROUTE_CHANGE:
                                if((event.message && event.message.path == PATHS.MEDIA && $location.$$path.indexOf('/media') == -1) || (event.message && event.message.path != PATHS.MEDIA)) {
                                    var path = event.message.path,
                                        id = event.message.id;
                                    var url = "#/";
                                    switch (path) {
                                        case PATHS.MEDIA:
                                            url = url + "media/";
                                            if (id) {
                                                url = url + id + "/";
                                            }
                                            break;
                                    }
                                    Location.go(url);
                                    if (path == PATHS.MEDIA) {
                                        $rootScope.showFeed = false;
                                    }
                                    else {
                                        $rootScope.showFeed = true;
                                    }
                                    $scope.$apply();
                                }
                                break;
                            case EVENTS.ITEMS_CHANGE:
                                WidgetHome.refreshItems();
                                break;
                        }
                    }
                };

                var onUpdateCallback = function (event) {
                    if (event.tag == "MediaCenter") {
                        if (event.data) {
                            WidgetHome.media.data = event.data;
                            $rootScope.backgroundImage = WidgetHome.media.data.design.backgroundImage;
                            console.log(WidgetHome.media);
                            $scope.$apply();
                            if (view && event.data.content && event.data.content.images) {
                                view.loadItems(event.data.content.images);
                            }
                            WidgetHome.refreshItems();
                        }
                    }
                    else {
                        WidgetHome.refreshItems();
                    }
                };

                /**
                 * Buildfire.datastore.onUpdate method calls when Data is changed.
                 */
                var listener = Buildfire.datastore.onUpdate(onUpdateCallback);

                /**
                 * updateGetOptions method checks whether sort options changed or not.
                 * @returns {boolean}
                 */
                var updateGetOptions = function () {
                    var order = Orders.getOrder(WidgetHome.media.data.content.sortBy || Orders.ordersMap.Default);
                    if (order) {
                        var sort = {};
                        sort[order.key] = order.order;
                        searchOptions.sort = sort;
                        return true;
                    }
                    else {
                        return false;
                    }
                };

                // ShowDescription only when it have content
                WidgetHome.showDescription = function () {
                    if (WidgetHome.media.data.content.descriptionHTML == '

' || WidgetHome.media.data.content.descriptionHTML == '

'|| WidgetHome.media.data.content.descriptionHTML == '')
                        return false;
                    else
                        return true;
                };

                /**
                 * WidgetHome.items holds the array of items.
                 * @type {Array}
                 */
                WidgetHome.items = [];
                /**
                 * WidgetHome.noMore checks for further data
                 * @type {boolean}
                 */
                WidgetHome.noMore = false;

                /**
                 * loadMore method loads the items in list page.
                 */
                WidgetHome.loadMore = function () {

                    if (WidgetHome.isBusy || WidgetHome.noMore) {
                        return;
                    }
                    updateGetOptions();
                    WidgetHome.isBusy = true;

                    MediaContent.find(searchOptions).then(function success(result) {
                        if (WidgetHome.noMore)
                            return;

                        if (result.length <= _limit) {// to indicate there are more

                            WidgetHome.noMore = true;
                        }
                        else {
                            result.pop();
                            searchOptions.skip = searchOptions.skip + _limit;
                            WidgetHome.noMore = false;
                        }
                        WidgetHome.items = WidgetHome.items ? WidgetHome.items.concat(result) : result;
                        WidgetHome.isBusy = false;
                    }, function fail() {
                        WidgetHome.isBusy = false;
                        console.error('error');
                    });
                };

                WidgetHome.openLinks = function (actionItems, $event) {
                    if (actionItems && actionItems.length) {
                        var options = {};
                        var callback = function (error, result) {
                            if (error) {
                                console.error('Error:', error);
                            }
                        };

                        $event.preventDefault();
                        $timeout(function() {
                            Buildfire.actionItems.list(actionItems, options, callback);
                        });
                    }
                };

                WidgetHome.refreshItems = function () {
                    searchOptions.skip = 0;
                    WidgetHome.items = [];
                    WidgetHome.noMore = false;
                    WidgetHome.loadMore();
                };

                WidgetHome.goToMedia = function (ind) {
                    $rootScope.showFeed = false;
                    Location.go('#/media/' + WidgetHome.items[ind].id);
                };

                $rootScope.$on("Carousel:LOADED", function () {
                    if (WidgetHome.media.data.content && WidgetHome.media.data.content.images) {
                        view = new Buildfire.components.carousel.view("#carousel", WidgetHome.media.data.content.images);
                        //view.loadItems(WidgetHome.media.data.content.images, false);
                    } else {
                        view.loadItems([]);
                    }
                });

                Messaging.sendMessageToControl({
                    name: EVENTS.ROUTE_CHANGE,
                    message: {
                        path: PATHS.HOME
                    }
                });

                $rootScope.$watch('showFeed', function () {
                    if ($rootScope.showFeed) {
                        listener.clear();
                        listener = Buildfire.datastore.onUpdate(onUpdateCallback);

                        MediaCenter.get().then(function success(result) {
                                WidgetHome.media = result;
                            },
                            function fail() {
                                WidgetHome.media = _infoData;
                            }
                        );
                    }
                });
                /**
                 * Implementation of pull down to refresh
                 */
                var onRefresh=Buildfire.datastore.onRefresh(function(){
                    Location.goToHome();
                });

            }]);
})(window.angular);<|endoftext|>


<commit_before>/* Pattern s of twenty-ﬁrst century forest loss across a globalnetwork of important sites for biodiversity */

// Google Earth Engine link: https://code.earthengine.google.com/a950bf782e39bd665812e20dd190d6c6

/* INPUT:
IBA_collection - fusion table with a list of links to fusion tables containing
                 Important Bird Areas (IBA) polygons.

We are working with a list of IBA (rather than a single one, which would make code simpler)
since at the moment of witing the script below Google Earth Engine and Fusion Tables were
not able to handle such large polygons at high resolution (scale of order of 30m).
*/
var IBA_collection = ee.FeatureCollection('ft:15rF8XpX24OYIJmPUD25F8ykQfsPEkntf7yuHs8_3');
var IBA_list = IBA_collection.toList(1e6).getInfo(); // make a list of all links

/* Global Forest Change (GFC) map from Hansen et al. (2013)
   Can be replaced with updates version of GFC:
   - UMD/hansen/global_forest_change_2014
   - UMD/hansen/global_forest_change_2015 */
var gfcImage = ee.Image('UMD/hansen/global_forest_change_2013');

// GFC bands are explained here:
// https://sites.google.com/site/earthengineapidocs/tutorials/global-forest-change-tutorial/reference-guide
var forest2000 = gfcImage.select('treecover2000').divide(100); // Forest cover in 2000 as a fraction
var areaImage2000 = forest2000.multiply(ee.Image.pixelArea()); // Forest cover area in 2000 in m^2
var lossImage = gfcImage.select('loss'); // select loss band
var lossYear = gfcImage.select('lossyear');
var gainImage = gfcImage.select('gain');
var totalLoss2012 = lossImage.multiply(ee.Image.pixelArea());

// Get all pixels from 2000 where loss occured.
// If pixel had 70% forest cover in 2000 we assume that 70% was lost in 200
var maskLossArea = forest2000.mask(lossImage).multiply(ee.Image.pixelArea());

// Most pessimistic approach - always assume 100% forest loss.
var lossImageArea = lossImage.multiply(ee.Image.pixelArea());

// Find gain-only pixels. Reasoning is that if both loss and gain occurs it's
// probably not suitable habitat
var gainOnly = gainImage.and(lossImage.not());
var gainOnlyArea = gainOnly.multiply(ee.Image.pixelArea());

/* We create an array in which we will store the following sequence:
- First element - Name, e.g. 'Tree area 2000'.
- Second element - Name of a band as it appears on GFC. Needed to retrieve results.
- Third element - The image itself. */
var areas = [];
areas.push(['Tree area 2000', 'treecover2000', areaImage2000]);
areas.push(['Forest loss 2001 - 2012', 'treecover2000', maskLossArea]);
areas.push(['Forest loss 2001 - 2012 pessimistic', 'loss', lossImageArea]);
areas.push(['Gain forest', 'gain', gainOnlyArea]);

for (var year = 1; year <= 12; year += 1)
{
  areas.push(['Loss area 200' + year.toString(),
              'treecover2000',
              forest2000.mask(lossYear.eq(year)).multiply(ee.Image.pixelArea())]);
}

// 'Selectors' are a way of informing GEE which data we want exported. If none are specified
// complete data set is exported, together with columns which are not relevant.
var selectors = [];
for (var idx = 0; idx < areas.length; idx += 1)
{
  selectors.push(areas[idx][0]);
}
selectors.push('OBJECTID_1', 'IntName', 'NatName', 'SitRecID');

var scale = 120; // Scale at which calculations should be run. GFC is 30m
var maxPixels = 1e12; // 10^12 - above that number of pixels GEE will use bestEffort flag
var bestEffort = false; // Change scale if too many pixels. Unlikely in this case.
var reducer = ee.Reducer.sum(); // This reducer will sum up all pixels

// For every fusion table with IBA ...
IBA_list.map(function f(IBA)
{
  // Get fusion table ID...
  var fusionTableID = 'ft:' + IBA.properties.table_id;
  // Get geometries from it...
  var collection = ee.FeatureCollection(fusionTableID, 'geometry');

  // For every such geometry...
  var areasComputeObject = collection.map(function f(e) {
    // Go through each metric we pushed to the 'areas' array...
    for (var idx = 0; idx < areas.length; idx++)
    {
      e=e.set(areas[idx][0], // Name of the metric (e.g. Tree area 2000)
        ee.Number(areas[idx][2].reduceRegion({ // [2] is the image itself - perform 'reduce' on it
          reducer: reducer, // our reducer will sum all pixels
          scale: scale, // at this scale
          maxPixels: maxPixels,
          geometry : e.geometry(),
          bestEffort: bestEffort
        }).get(areas[idx][1])).divide(1e6)); // [1] is name from GFC. We divide by 1000^2 to get km^2
    }
    return e;
  });

  var filenamePrefix = 'IBA_' + IBA.properties.table_id;

  // We select subset of available columns. "false" indicate that geometry should not be exported.
  areasComputeObject = areasComputeObject.select(selectors, null, false);

  // "Export" will create an export task. No calculations are fired until one presses a button.
  // Since it is a loop a number of export tasks is created.
  var taskParams = { 'driveFolder' : '', 'fileFormat' : 'CSV' };
  Export.table(areasComputeObject, filenamePrefix, taskParams);

});
<commit_msg>Correct typo in a comment<commit_after>/* Pattern s of twenty-ﬁrst century forest loss across a globalnetwork of important sites for biodiversity */

// Google Earth Engine link: https://code.earthengine.google.com/a950bf782e39bd665812e20dd190d6c6

/* INPUT:
IBA_collection - fusion table with a list of links to fusion tables containing
                 Important Bird Areas (IBA) polygons.

We are working with a list of IBA (rather than a single one, which would make code simpler)
since at the moment of witing the script below Google Earth Engine and Fusion Tables were
not able to handle such large polygons at high resolution (scale of order of 30m).
*/
var IBA_collection = ee.FeatureCollection('ft:15rF8XpX24OYIJmPUD25F8ykQfsPEkntf7yuHs8_3');
var IBA_list = IBA_collection.toList(1e6).getInfo(); // make a list of all links

/* Global Forest Change (GFC) map from Hansen et al. (2013)
   Can be replaced with updates version of GFC:
   - UMD/hansen/global_forest_change_2014
   - UMD/hansen/global_forest_change_2015 */
var gfcImage = ee.Image('UMD/hansen/global_forest_change_2013');

// GFC bands are explained here:
// https://sites.google.com/site/earthengineapidocs/tutorials/global-forest-change-tutorial/reference-guide
var forest2000 = gfcImage.select('treecover2000').divide(100); // Forest cover in 2000 as a fraction
var areaImage2000 = forest2000.multiply(ee.Image.pixelArea()); // Forest cover area in 2000 in m^2
var lossImage = gfcImage.select('loss'); // select loss band
var lossYear = gfcImage.select('lossyear');
var gainImage = gfcImage.select('gain');
var totalLoss2012 = lossImage.multiply(ee.Image.pixelArea());

// Get all pixels from 2000 where loss occured.
// If pixel had 70% forest cover in 2000 we assume that 70% was lost in 2012
var maskLossArea = forest2000.mask(lossImage).multiply(ee.Image.pixelArea());

// Most pessimistic approach - always assume 100% forest loss.
var lossImageArea = lossImage.multiply(ee.Image.pixelArea());

// Find gain-only pixels. Reasoning is that if both loss and gain occurs it's
// probably not suitable habitat
var gainOnly = gainImage.and(lossImage.not());
var gainOnlyArea = gainOnly.multiply(ee.Image.pixelArea());

/* We create an array in which we will store the following sequence:
- First element - Name, e.g. 'Tree area 2000'.
- Second element - Name of a band as it appears on GFC. Needed to retrieve results.
- Third element - The image itself. */
var areas = [];
areas.push(['Tree area 2000', 'treecover2000', areaImage2000]);
areas.push(['Forest loss 2001 - 2012', 'treecover2000', maskLossArea]);
areas.push(['Forest loss 2001 - 2012 pessimistic', 'loss', lossImageArea]);
areas.push(['Gain forest', 'gain', gainOnlyArea]);

for (var year = 1; year <= 12; year += 1)
{
  areas.push(['Loss area 200' + year.toString(),
              'treecover2000',
              forest2000.mask(lossYear.eq(year)).multiply(ee.Image.pixelArea())]);
}

// 'Selectors' are a way of informing GEE which data we want exported. If none are specified
// complete data set is exported, together with columns which are not relevant.
var selectors = [];
for (var idx = 0; idx < areas.length; idx += 1)
{
  selectors.push(areas[idx][0]);
}
selectors.push('OBJECTID_1', 'IntName', 'NatName', 'SitRecID');

var scale = 120; // Scale at which calculations should be run. GFC is 30m
var maxPixels = 1e12; // 10^12 - above that number of pixels GEE will use bestEffort flag
var bestEffort = false; // Change scale if too many pixels. Unlikely in this case.
var reducer = ee.Reducer.sum(); // This reducer will sum up all pixels

// For every fusion table with IBA ...
IBA_list.map(function f(IBA)
{
  // Get fusion table ID...
  var fusionTableID = 'ft:' + IBA.properties.table_id;
  // Get geometries from it...
  var collection = ee.FeatureCollection(fusionTableID, 'geometry');

  // For every such geometry...
  var areasComputeObject = collection.map(function f(e) {
    // Go through each metric we pushed to the 'areas' array...
    for (var idx = 0; idx < areas.length; idx++)
    {
      e=e.set(areas[idx][0], // Name of the metric (e.g. Tree area 2000)
        ee.Number(areas[idx][2].reduceRegion({ // [2] is the image itself - perform 'reduce' on it
          reducer: reducer, // our reducer will sum all pixels
          scale: scale, // at this scale
          maxPixels: maxPixels,
          geometry : e.geometry(),
          bestEffort: bestEffort
        }).get(areas[idx][1])).divide(1e6)); // [1] is name from GFC. We divide by 1000^2 to get km^2
    }
    return e;
  });

  var filenamePrefix = 'IBA_' + IBA.properties.table_id;

  // We select subset of available columns. "false" indicate that geometry should not be exported.
  areasComputeObject = areasComputeObject.select(selectors, null, false);

  // "Export" will create an export task. No calculations are fired until one presses a button.
  // Since it is a loop a number of export tasks is created.
  var taskParams = { 'driveFolder' : '', 'fileFormat' : 'CSV' };
  Export.table(areasComputeObject, filenamePrefix, taskParams);

});
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict'

const log = require("../../net2/logger")('diag');

const express = require('express');
const path = require('path');

const port = 8835

const Promise = require('bluebird')

const exec = require('child-process-promise').exec
const fs = require('fs')
Promise.promisifyAll(fs)

const Config = require('../../net2/config.js');
const sysManager = require('../../net2/SysManager.js');

const jsonfile = require('jsonfile');
const writeFileAsync = Promise.promisify(jsonfile.writeFile);

const { wrapIptables } = require('../../net2/Iptables.js')

const sem = require('../../sensor/SensorEventManager.js').getInstance();

const Mode = require('../../net2/Mode.js');

const VIEW_PATH = 'view';
const STATIC_PATH = 'static';

const errorCodes = {
  "firekick": 101,
  "firemain": 102,
  "fireapi": 103,
  "firemon": 104,
  "memory": 201,
  "database": 301,
  "databaseConnectivity": 302,
  "gid": 401,
  "ip": 501
}

class App {
  constructor() {
    this.app = express();

    this.app.engine('mustache', require('mustache-express')());
    this.app.set('view engine', 'mustache');

    this.app.set('views', path.join(__dirname, VIEW_PATH));
    //this.app.disable('view cache'); //for debug only

    this.routes();
  }

  getSystemTime() {
    return new Date() / 1000
  }

  async getSystemServices() {
    const fireKickCmd = "systemctl is-active firekick"
    const fireMainCmd = "systemctl is-active firemain"
    const fireApiCmd = "systemctl is-active fireapi"
    const fireMonCmd = "systemctl is-active firemon"

    try {
      await exec(fireKickCmd)
    } catch (err) {
      log.error("firekick is not alive", err);
      return errorCodes.firekick
    }

    try {
      await exec(fireMainCmd)
    } catch (err) {
      log.error("firemain is not alive", err);
      return errorCodes.firemain
    }

    try {
      await exec(fireApiCmd)
    } catch (err) {
      log.error("fireapi is not alive", err);
      return errorCodes.fireapi
    }

    try {
      await exec(fireMonCmd)
    } catch (err) {
      log.error("firemon is not alive", err);
      return errorCodes.firemon
    }

    return 0
  }

  getCloudConnectivity() {
    return this.connected
  }

  async getSystemMemory() {
    const result = await exec("free -m")
    const stdout = result.stdout
    const lines = stdout.split(/\n/g)

    for (var i = 0; i < lines.length; i++) {
      lines[i] = lines[i].split(/\s+/)
    }

    const allMem = parseInt(lines[1][1])

    if (allMem > 490) {
      return 0
    } else {
      return errorCodes.memory
    }
  }

  getNodeVersion() {
    return process.version
  }

  getUptime() {
    return require('os').uptime()
  }

  async getDatabase() {
    try {
      await exec("systemctl is-active redis-server")
    } catch (err) {
      log.error("Failed to check database", err);
      return errorCodes.database
    }

    return 0
  }

  async getDatabaseConnectivity() {
    try {
      await exec("redis-cli get mode")
    } catch (err) {
      log.error("Failed to check database connection status", err);
      return errorCodes.databaseConnectivity
    }
    return 0
  }

  async getGID() {
    try {
      const gid = await exec("redis-cli hget sys:ept gid")
      return gid && gid.stdout && gid.stdout.substring(0, 8)
    } catch (err) {
      log.error("Failed to get gid", err);
      return null
    }
  }

  async getFullGID() {
    try {
      const gid = await exec("redis-cli hget sys:ept gid")
      return gid && gid.stdout && gid.stdout.replace("\n", "")
    } catch (err) {
      log.error("Failed to get gid", err);
      return null
    }
  }

  async getPrimaryIP() {
    const config = Config.getConfig(true);
    const eths = require('os').networkInterfaces()[config.monitoringInterface];

    if (eths) {
      for (let index = 0; index < eths.length; index++) {
        const eth = eths[index]
        if (eth.family == "IPv4") {
          return eth.address
        }
      }
    }

    return ''
  }

  async getQRImage() {
    if (!this.broadcastInfo) {
      return null;
    }

    try {
      const imagePath = `${__dirname}/static/firewalla_pairing_info.png`;
      const jsonPath = "/tmp/pairing.info.json";

      const pairingInfo = JSON.parse(JSON.stringify(this.broadcastInfo));
      pairingInfo.type = "pairing";
      delete pairingInfo.keyhint;
      delete pairingInfo.service;
      delete pairingInfo.mid;
      delete pairingInfo.verifymode;

      await writeFileAsync(jsonPath, pairingInfo);

      const cmd = `cat ${jsonPath} | qrencode -o ${imagePath}`;

      await exec(cmd);
      return imagePath;
    } catch (err) {
      log.error("Failed to get QRImage", err);
      return null
    }
  }

  routes() {
    this.router = express.Router();

    this.app.use('/' + VIEW_PATH, this.router);
    this.app.use('/' + STATIC_PATH, express.static(path.join(__dirname, STATIC_PATH)));

    this.app.use('/log', (req, res) => {
      const filename = "/home/pi/logs/FireKick.log";
      (async () => {
        const gid = await this.getFullGID()
        await fs.accessAsync(filename, fs.constants.F_OK)
        //tail -n 1000 /home/pi/logs/FireKick.log | sed -r   "s/0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g"
        const result = (await exec(`tail -n 1000 ${filename}`)).stdout
        let lines = result.split("\n")
        lines = lines.map((originLine) => {
          let line = originLine
          line = line.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')
          line = line.replace(new RegExp(gid, "g"), "<****gid****>")
          line = line.replace(/type in this key:.*$/g, "type in this key: <****key****>")
          line = line.replace(/Inviting .{10,40} to group/g, "Inviting <****rid****> to group")
          line = line.replace(/Set SYS:EPT.*/, "Set SYS:EPT<****token****>")
          return line
        })

        res.setHeader('content-type', 'text/plain');
        res.end(lines.join("\n"))
      })().catch((err) => {
        log.error("Failed to fetch log", err);
        res.status(404).send('')
      })
    });

    this.app.use('/pairing', (req, res) => {
      if (this.broadcastInfo) {
        res.json(this.broadcastInfo);
      } else {
        res.status(501).send('');
      }
    });

    this.app.use('/pair/ping', (req, res) => {
      res.json({});
    });

    this.app.use('/pair/ready', async (req, res) => {
      try {
        const values = await this.getPairingStatus();
        if(values.success) {
          res.json({
            ready: true
          });
        } else {
          res.json({
            ready: false,
            content: values
          });
        }
      } catch(err) {
        log.error("Failed to process request", err);
        res.json({
          ready: false
        });
      }
    });

    this.app.use('*', async (req, res) => {
      log.info("Got a request in *")

      try {
        const values = await this.getPairingStatus();
        if(values.error) {
          log.error("Failed to process request", err);
          res.status(500).send({})
        } else {
          res.render('welcome', values)
        }
      } catch(err) {
        log.error("Failed to process request", err);
        res.status(500).send({})
      }
    })
  }

  async getPairingStatus() {
    try {
      const time = this.getSystemTime()
      const ip = await this.getPrimaryIP();
      const gid = await this.getGID()
      const database = await this.getDatabase()
      const uptime = this.getUptime()
      const nodeVersion = this.getNodeVersion()
      const memory = await this.getSystemMemory()
      const connected = this.getCloudConnectivity()
      const systemServices = await this.getSystemServices()
      const expireDate = this.expireDate;
      const qrImagePath = await this.getQRImage()

      let success = true
      let values = {
        now: new Date() / 1000
      }

      if(!this.broadcastInfo) {
        values.err_binding = true
        success = false;
      }

      if(qrImagePath) {
        values.qrImage = true;
      } else {
        success = false;
      }

      if(ip == "") {
        values.err_ip = true
        success = false
      } else {
        values.ip = ip
      }

      if(gid == null) {
        values.err_config = true
        success = false
      }

      if(database != 0) {
        values.err_database = true
        success = false
      }

      if(memory != 0) {
        values.err_memory = true
        success = false
      }

      if(connected != true) {
        values.err_cloud = true
        success = false
      }

      if(systemServices != 0) {
        values.err_service = true
        success = false
      }

      values.success = success

      return values;

    } catch(err) {
      log.error("Failed to get pairing status, err:", err);
      return {
        success: false,
        error: true
      }
    }
  }

  async iptablesRedirection(create = true) {
    let interfaces = sysManager.getLogicInterfaces()
    if (Mode.isRouterModeOn()) {
      interfaces = interfaces.filter(intf => intf.type != 'wan')
    }
    const IPv4List = interfaces.map(intf => intf.ip_address)

    log.info("", IPv4List);

    const action = create ? '-A' : '-D';

    for (const ip of IPv4List) {
      if (!ip) continue;

      log.info(create ? 'creating' : 'removing', `port forwording from 80 to ${port} on ${ip}`);
      const cmd = wrapIptables(`sudo iptables -w -t nat ${action} FW_PREROUTING -p tcp --destination ${ip} --destination-port 80 -j REDIRECT --to-ports ${port}`);
      await exec(cmd);
    }
  }

  start() {
    this.app.listen(port, () => {
      log.info(`Httpd listening on port ${port}!`)

      sem.on("DiagRedirectionRenew", (event) => {
        log.info("Renew port redirection")
        this.iptablesRedirection();
      })

    });
  }
}

module.exports = App
<commit_msg>add missing await<commit_after>/*    Copyright 2016 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict'

const log = require("../../net2/logger")('diag');

const express = require('express');
const path = require('path');

const port = 8835

const Promise = require('bluebird')

const exec = require('child-process-promise').exec
const fs = require('fs')
Promise.promisifyAll(fs)

const Config = require('../../net2/config.js');
const sysManager = require('../../net2/SysManager.js');

const jsonfile = require('jsonfile');
const writeFileAsync = Promise.promisify(jsonfile.writeFile);

const { wrapIptables } = require('../../net2/Iptables.js')

const sem = require('../../sensor/SensorEventManager.js').getInstance();

const Mode = require('../../net2/Mode.js');

const VIEW_PATH = 'view';
const STATIC_PATH = 'static';

const errorCodes = {
  "firekick": 101,
  "firemain": 102,
  "fireapi": 103,
  "firemon": 104,
  "memory": 201,
  "database": 301,
  "databaseConnectivity": 302,
  "gid": 401,
  "ip": 501
}

class App {
  constructor() {
    this.app = express();

    this.app.engine('mustache', require('mustache-express')());
    this.app.set('view engine', 'mustache');

    this.app.set('views', path.join(__dirname, VIEW_PATH));
    //this.app.disable('view cache'); //for debug only

    this.routes();
  }

  getSystemTime() {
    return new Date() / 1000
  }

  async getSystemServices() {
    const fireKickCmd = "systemctl is-active firekick"
    const fireMainCmd = "systemctl is-active firemain"
    const fireApiCmd = "systemctl is-active fireapi"
    const fireMonCmd = "systemctl is-active firemon"

    try {
      await exec(fireKickCmd)
    } catch (err) {
      log.error("firekick is not alive", err);
      return errorCodes.firekick
    }

    try {
      await exec(fireMainCmd)
    } catch (err) {
      log.error("firemain is not alive", err);
      return errorCodes.firemain
    }

    try {
      await exec(fireApiCmd)
    } catch (err) {
      log.error("fireapi is not alive", err);
      return errorCodes.fireapi
    }

    try {
      await exec(fireMonCmd)
    } catch (err) {
      log.error("firemon is not alive", err);
      return errorCodes.firemon
    }

    return 0
  }

  getCloudConnectivity() {
    return this.connected
  }

  async getSystemMemory() {
    const result = await exec("free -m")
    const stdout = result.stdout
    const lines = stdout.split(/\n/g)

    for (var i = 0; i < lines.length; i++) {
      lines[i] = lines[i].split(/\s+/)
    }

    const allMem = parseInt(lines[1][1])

    if (allMem > 490) {
      return 0
    } else {
      return errorCodes.memory
    }
  }

  getNodeVersion() {
    return process.version
  }

  getUptime() {
    return require('os').uptime()
  }

  async getDatabase() {
    try {
      await exec("systemctl is-active redis-server")
    } catch (err) {
      log.error("Failed to check database", err);
      return errorCodes.database
    }

    return 0
  }

  async getDatabaseConnectivity() {
    try {
      await exec("redis-cli get mode")
    } catch (err) {
      log.error("Failed to check database connection status", err);
      return errorCodes.databaseConnectivity
    }
    return 0
  }

  async getGID() {
    try {
      const gid = await exec("redis-cli hget sys:ept gid")
      return gid && gid.stdout && gid.stdout.substring(0, 8)
    } catch (err) {
      log.error("Failed to get gid", err);
      return null
    }
  }

  async getFullGID() {
    try {
      const gid = await exec("redis-cli hget sys:ept gid")
      return gid && gid.stdout && gid.stdout.replace("\n", "")
    } catch (err) {
      log.error("Failed to get gid", err);
      return null
    }
  }

  async getPrimaryIP() {
    const config = Config.getConfig(true);
    const eths = require('os').networkInterfaces()[config.monitoringInterface];

    if (eths) {
      for (let index = 0; index < eths.length; index++) {
        const eth = eths[index]
        if (eth.family == "IPv4") {
          return eth.address
        }
      }
    }

    return ''
  }

  async getQRImage() {
    if (!this.broadcastInfo) {
      return null;
    }

    try {
      const imagePath = `${__dirname}/static/firewalla_pairing_info.png`;
      const jsonPath = "/tmp/pairing.info.json";

      const pairingInfo = JSON.parse(JSON.stringify(this.broadcastInfo));
      pairingInfo.type = "pairing";
      delete pairingInfo.keyhint;
      delete pairingInfo.service;
      delete pairingInfo.mid;
      delete pairingInfo.verifymode;

      await writeFileAsync(jsonPath, pairingInfo);

      const cmd = `cat ${jsonPath} | qrencode -o ${imagePath}`;

      await exec(cmd);
      return imagePath;
    } catch (err) {
      log.error("Failed to get QRImage", err);
      return null
    }
  }

  routes() {
    this.router = express.Router();

    this.app.use('/' + VIEW_PATH, this.router);
    this.app.use('/' + STATIC_PATH, express.static(path.join(__dirname, STATIC_PATH)));

    this.app.use('/log', (req, res) => {
      const filename = "/home/pi/logs/FireKick.log";
      (async () => {
        const gid = await this.getFullGID()
        await fs.accessAsync(filename, fs.constants.F_OK)
        //tail -n 1000 /home/pi/logs/FireKick.log | sed -r   "s/0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g"
        const result = (await exec(`tail -n 1000 ${filename}`)).stdout
        let lines = result.split("\n")
        lines = lines.map((originLine) => {
          let line = originLine
          line = line.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')
          line = line.replace(new RegExp(gid, "g"), "<****gid****>")
          line = line.replace(/type in this key:.*$/g, "type in this key: <****key****>")
          line = line.replace(/Inviting .{10,40} to group/g, "Inviting <****rid****> to group")
          line = line.replace(/Set SYS:EPT.*/, "Set SYS:EPT<****token****>")
          return line
        })

        res.setHeader('content-type', 'text/plain');
        res.end(lines.join("\n"))
      })().catch((err) => {
        log.error("Failed to fetch log", err);
        res.status(404).send('')
      })
    });

    this.app.use('/pairing', (req, res) => {
      if (this.broadcastInfo) {
        res.json(this.broadcastInfo);
      } else {
        res.status(501).send('');
      }
    });

    this.app.use('/pair/ping', (req, res) => {
      res.json({});
    });

    this.app.use('/pair/ready', async (req, res) => {
      try {
        const values = await this.getPairingStatus();
        if(values.success) {
          res.json({
            ready: true
          });
        } else {
          res.json({
            ready: false,
            content: values
          });
        }
      } catch(err) {
        log.error("Failed to process request", err);
        res.json({
          ready: false
        });
      }
    });

    this.app.use('*', async (req, res) => {
      log.info("Got a request in *")

      try {
        const values = await this.getPairingStatus();
        if(values.error) {
          log.error("Failed to process request", err);
          res.status(500).send({})
        } else {
          res.render('welcome', values)
        }
      } catch(err) {
        log.error("Failed to process request", err);
        res.status(500).send({})
      }
    })
  }

  async getPairingStatus() {
    try {
      const time = this.getSystemTime()
      const ip = await this.getPrimaryIP();
      const gid = await this.getGID()
      const database = await this.getDatabase()
      const uptime = this.getUptime()
      const nodeVersion = this.getNodeVersion()
      const memory = await this.getSystemMemory()
      const connected = this.getCloudConnectivity()
      const systemServices = await this.getSystemServices()
      const expireDate = this.expireDate;
      const qrImagePath = await this.getQRImage()

      let success = true
      let values = {
        now: new Date() / 1000
      }

      if(!this.broadcastInfo) {
        values.err_binding = true
        success = false;
      }

      if(qrImagePath) {
        values.qrImage = true;
      } else {
        success = false;
      }

      if(ip == "") {
        values.err_ip = true
        success = false
      } else {
        values.ip = ip
      }

      if(gid == null) {
        values.err_config = true
        success = false
      }

      if(database != 0) {
        values.err_database = true
        success = false
      }

      if(memory != 0) {
        values.err_memory = true
        success = false
      }

      if(connected != true) {
        values.err_cloud = true
        success = false
      }

      if(systemServices != 0) {
        values.err_service = true
        success = false
      }

      values.success = success

      return values;

    } catch(err) {
      log.error("Failed to get pairing status, err:", err);
      return {
        success: false,
        error: true
      }
    }
  }

  async iptablesRedirection(create = true) {
    let interfaces = sysManager.getLogicInterfaces()
    if (await Mode.isRouterModeOn()) {
      interfaces = interfaces.filter(intf => intf.type != 'wan')
    }
    const IPv4List = interfaces.map(intf => intf.ip_address)

    log.info("", IPv4List);

    const action = create ? '-A' : '-D';

    for (const ip of IPv4List) {
      if (!ip) continue;

      log.info(create ? 'creating' : 'removing', `port forwording from 80 to ${port} on ${ip}`);
      const cmd = wrapIptables(`sudo iptables -w -t nat ${action} FW_PREROUTING -p tcp --destination ${ip} --destination-port 80 -j REDIRECT --to-ports ${port}`);
      await exec(cmd);
    }
  }

  start() {
    this.app.listen(port, () => {
      log.info(`Httpd listening on port ${port}!`)

      sem.on("DiagRedirectionRenew", (event) => {
        log.info("Renew port redirection")
        this.iptablesRedirection();
      })

    });
  }
}

module.exports = App
<|endoftext|>


<commit_before>/*
 * This script contains all functions needed to parse an array of User objects,
 * which is a JS object literal with
 * {username: string, realname: string, location: {lat: number, lng: number}}
 */

function showWorld(map) {
  /* Changes map to show world view
   */
  map.setCenter(new google.maps.LatLng(0, 0));
  map.setZoom(2);
}

function geolocateUser(map) {
  /* Centres map to user's geolocation and sets a marker, if the user
   * rejects user location request, then zooms out to world view
   */
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
      var pos = new google.maps.LatLng(position.coords.latitude,
                                   position.coords.longitude);
      map.setCenter(pos);

      // add a marker representing the player location
      var userMarker = new google.maps.Marker({
        title:'You',
        map: map,
        position: pos,
        animation: google.maps.Animation.DROP
      });

    }, function() {
      // user said no
      showWorld(map);
    });
  } else {
    // browser does not support geolocation in this case show world view of map
    alert('This browser does not support geolocation. ' +
          'To provide you better study partners we suggest you update' +
          'your browser');
    showWorld(map);
  }
}

function addMarker(map, user) {
  /* adds a marker for every user with  info window
   * to map.
   */
  setTimeout(function() {
    // add marker for user
    if(user.position == {lat: 0, lng: 0}) {
      return;
    }
    var marker = new google.maps.Marker({
      title: user.realname,
      map: map,
      position: user.position,
      animation: google.maps.Animation.DROP,
      icon: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png'
    });
    // add info window
    var userContent = '
' +
                        '
' + user.realname +
                        ' (' + user.username + ')' + '
' +
                        '
Invite' +
                      '
';
    var infowindow = new google.maps.InfoWindow({
      content: userContent,
      minWidth: 200
    });

    // add click listener
    google.maps.event.addListener(marker, 'click', function() {
      if (currentInfoWindow) {currentInfoWindow.close()};
      if (currentInfoWindow !== infowindow) {
        currentInfoWindow = infowindow;
        infowindow.open(map, marker);
      } else {
        currentInfoWindow = null;
      }
    });
  }, 500);
}

function initialize(userArray) {
  /* Initializes google Map object in DOM with ID 'map-canvas'
   * centred at the users location. For the map to show locally, the
   * user must accept the prompt asking for location services. Otherwise
   * we show a global view.
   *
   * The function iterates over the array of user objects and adds a marker
   * for each on the map
   */
  var mapOptions = {zoom: 13};
  // make the map
  var map = new google.maps.Map(document.getElementById('map-canvas'),
                                mapOptions);

  // use HTML5 geolocation to set the center of the map as user's location
  geolocateUser(map);

  // now iterate over userArray and add each to map as marker
  userArray.forEach(function(user) {addMarker(map, user);});

}

var currentInfoWindow = null;
var users = [];
// To use, add UserObjects to the users array above this line,
// then everything else will work itself

// --- REMOVE BEFORE USING THIS FILE!!!!!!!!!----
var names = ['bill gates', 'steve jobs', 'mark zuckerberg', 'drew houston',
            'satya nadella', 'bob jones'];
var usernames = ['haxx0r', 'noob', 'deathshot', 'captain', 'morganstanley',
'1337', 'thelord', 'urmom'];

for (var i = 0; i < 100; i++) {
  var name = names[Math.floor(Math.random() * names.length)];
  var usrname = usernames[Math.floor(Math.random() * usernames.length)];
  var usrObj = {
    realname: name,
    username: usrname,
    position: {lat: (2 * Math.random() - 1) * 90, lng: (2 * Math.random() - 1) * 180}
  }
  users.push(usrObj);
}
// --- UNTIL HERE !!!!!!!----

google.maps.event.addDomListener(window, 'load', function() {initialize(users);});
<commit_msg>added get request for user info from server<commit_after>/*
 * This script contains all functions needed to parse an array of User objects,
 * which is a JS object literal with
 * {username: string, realname: string, location: {lat: number, lng: number}}
 */

function showWorld(map) {
  /* Changes map to show world view
   */
  map.setCenter(new google.maps.LatLng(0, 0));
  map.setZoom(2);
}

function geolocateUser(map) {
  /* Centres map to user's geolocation and sets a marker, if the user
   * rejects user location request, then zooms out to world view
   */
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
      var pos = new google.maps.LatLng(position.coords.latitude,
                                   position.coords.longitude);
      map.setCenter(pos);

      // add a marker representing the player location
      var userMarker = new google.maps.Marker({
        title:'You',
        map: map,
        position: pos,
        animation: google.maps.Animation.DROP
      });

    }, function() {
      // user said no
      showWorld(map);
    });
  } else {
    // browser does not support geolocation in this case show world view of map
    alert('This browser does not support geolocation. ' +
          'To provide you better study partners we suggest you update' +
          'your browser');
    showWorld(map);
  }
}

function addMarker(map, user) {
  /* adds a marker for every user with  info window
   * to map.
   */
  setTimeout(function() {
    // add marker for user
    if (user.position == {lat: 0, lng: 0}) {
      return;
    }
    var marker = new google.maps.Marker({
      title: user.realname,
      map: map,
      position: user.position,
      animation: google.maps.Animation.DROP,
      icon: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png'
    });
    // add info window
    var userContent = '
' +
                        '
' + user.realname +
                        ' (' + user.username + ')' + '
' +
                        '
Invite' +
                      '
';
    var infowindow = new google.maps.InfoWindow({
      content: userContent,
      minWidth: 200
    });

    // add click listener
    google.maps.event.addListener(marker, 'click', function() {
      if (currentInfoWindow) {currentInfoWindow.close()};
      if (currentInfoWindow !== infowindow) {
        currentInfoWindow = infowindow;
        infowindow.open(map, marker);
      } else {
        currentInfoWindow = null;
      }
    });
  }, 500);
}

function getUsersFromServer(userArray) {
  /* Gets the information for every user from the server and adds their user
   * objects to the array userArray
   */
  $.ajax({
    url: 'localhost:8000/api/v1/user/',
    data: function(data) {
      // parse the data and add it to the userArray
      userArray = JSON.parse(data).objects;
    },
    dataType: 'json'
  });

  // for all user objects we need to update a bit
  userArray.forEach(function(userObj) {
    usrObj.position = {lat: usrObj.latitude, lng: usrObj.lng};
    usrObj.realname = usrObj.first_name + usrObj.last_name;
  });
}

function initialize(userArray) {
  /* Initializes google Map object in DOM with ID 'map-canvas'
   * centred at the users location. For the map to show locally, the
   * user must accept the prompt asking for location services. Otherwise
   * we show a global view.
   *
   * The function iterates over the array of user objects and adds a marker
   * for each on the map
   */
  var mapOptions = {zoom: 13};
  // make the map
  var map = new google.maps.Map(document.getElementById('map-canvas'),
                                mapOptions);

  // use HTML5 geolocation to set the center of the map as user's location
  geolocateUser(map);

  // now iterate over userArray and add each to map as marker
  userArray.forEach(function(user) {addMarker(map, user);});

}

var currentInfoWindow = null;
var users = [];
// To use, add UserObjects to the users array above this line,
// then everything else will work itself

getUsersFromServer(users);
google.maps.event.addDomListener(window, 'load', function() {initialize(users);});
<|endoftext|>


<commit_before>//entry point
var util = require('util')
var restify = require('../node_modules/restify');
var items = require('./data.js').friends;
var env_port = 3000;
var path = '';
var base = '';
var mType = 'application/vnd.collection+json';
var cj = {};

restify.defaultResponseHeaders = false; // disable default headers altogether

var sendInvalidRequestError = function (req, res, next) {
    res.contentType = 'text/plain';
    res.status(401);
    res.send('we only accept content type ' + mType);
}

//[GET, HEAD]
var respondDefault = function (req, res, next) {
    if (req.accepts(mType)) {
        if (req.is(mType)) {
            res.contentType = mType;
            res.status(200);
            res.send('accepted ' + mType);
        } else {
            res.contentType = 'text/plain';
            res.status(407);
            res.send('we only accept content type ' + mType);
        }
    } else {
        sendInvalidRequestError(req, res, next);
    }
}

//[GET]
var respondMovies = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'application/json';
    res.status(200);
    res.send({movie: 'world'});
}

//[GET]
var respondActors = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'application/json';
    res.status(200);
    res.send({actors: 'world'});
}

//* RESPOND NOT FOUND
var respondNotFound = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'text/plain';
    res.status(404);
    res.send('invalid url requested');
}

//declare server
var server = restify.createServer({

    //add formatter
    formatters: {
        'application/vnd.collection+json; q=0.9': function formatCJ(req, res, body) {
            if (body instanceof Error)
                return body.stack;

            if (Buffer.isBuffer(body))
                return body.toString('base64');

            var data = JSON.stringify(body);
            res.setHeader('Content-Length', Buffer.byteLength(data));
            return util.inspect(body);
        }
    }
});

//routes
server.get('/', respondDefault);
server.head('/', respondDefault);
server.get('/movies', respondMovies) ;
server.get('/actors', respondActors);
server.on('NotFound', respondNotFound);

//server properties
server.name = 'node_restify';

//start server
server.listen(env_port, function() {
    console.log('%s listening at %s', server.name, server.url);
});

// sample collection object
/*
 {
 "collection" :
 {
 "version" : "1.0",
 "href" : URI,
 "links" : [ARRAY],
 "items" : [ARRAY],
 "queries" : [ARRAY],
 "template" : {OBJECT},
 "error" : {OBJECT}
 }
 }
 */
<commit_msg>update default return with links<commit_after>//entry point
var util = require('util')
var restify = require('../node_modules/restify');
var items = require('./data.js').friends;
var env_port = 3000;
var path = '';
var base = '';
var mType = 'application/vnd.collection+json';
var cj = {};
var URI = "http://localhost:3000";

restify.defaultResponseHeaders = false; // disable default headers altogether

var sendInvalidRequestError = function (req, res, next) {
    res.contentType = 'text/plain';
    res.status(401);
    res.send('we only accept content type ' + mType);
};

var renderCJ = function () {
    cj = {
        "collection":
        {
            "version":"0.1",
            "href":URI,
            "links":[
                URI + "/movies",
                URI + "/actors"
            ],
            "items":[],
            "queries":[],
            "template":{},
            "error":{}
        }
    };
};

//[GET, HEAD]
var respondDefault = function (req, res, next) {
    if (req.accepts(mType)) {
        renderCJ();
        var body = JSON.stringify(cj, null, '\t');
        res.writeHead(200, {
            'Content-Length': Buffer.byteLength(body),
            'Content-Type': mType
        });
        res.end(body);
    } else {
        sendInvalidRequestError(req, res, next);
    }
};

//[GET]
var respondMovies = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'application/json';
    res.status(200);
    res.send({movie: 'world'});
};

//[GET]
var respondActors = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'application/json';
    res.status(200);
    res.send({actors: 'world'});
};

//* RESPOND NOT FOUND
var respondNotFound = function (req, res, next) {
    req.accepts(mType);
    res.contentType = 'text/plain';
    res.status(404);
    res.send('invalid url requested');
};

//declare server
var server = restify.createServer({

    //add formatter
    formatters: {
        'application/vnd.collection+json; q=0.9': function formatCJ(req, res, body) {
            if (body instanceof Error)
                return body.stack;

            if (Buffer.isBuffer(body))
                return body.toString('base64');

            var data = JSON.stringify(body);
            res.setHeader('Content-Length', Buffer.byteLength(data));
            return util.inspect(body);
        }
    }
});

//routes
server.get('/', respondDefault);
server.head('/', respondDefault);
server.get('/movies', respondMovies) ;
server.get('/actors', respondActors);
server.on('NotFound', respondNotFound);

//server properties
server.name = 'node_restify';

//start server
server.listen(env_port, function() {
    console.log('%s listening at %s', server.name, server.url);
});

// sample collection object
/*
 {
 "collection" :
 {
 "version" : "1.0",
 "href" : URI,
 "links" : [ARRAY],
 "items" : [ARRAY],
 "queries" : [ARRAY],
 "template" : {OBJECT},
 "error" : {OBJECT}
 }
 }
 */
<|endoftext|>


<commit_before>$(document).ready(function () {

	/* Use this js doc for all application specific JS */

	/* TABS --------------------------------- */
	/* Remove if you don't need :) */

	function activateTab($tab) {
	  var $activeTab = $tab.closest('dl').find('a.active'),
	      contentLocation = $tab.attr("href") + 'Tab';

	  //Make Tab Active
	  $activeTab.removeClass('active');
	  $tab.addClass('active');

    //Show Tab Content
		$(contentLocation).closest('.tabs-content').find('li').hide();
		$(contentLocation).show();
	}

	$('dl.tabs').each(function () {
		//Get all tabs
		var tabs = $(this).children('dd').children('a');
		tabs.click(function (e) {
		  activateTab($(this));
		});
	});

	if (window.location.hash) {
    activateTab($('a[href="' + window.location.hash + '"]'));
  }

	/* PLACEHOLDER FOR FORMS ------------- */
	/* Remove this and jquery.placeholder.min.js if you don't need :) */

	$('input, textarea').placeholder();

	/* DROPDOWN NAV ------------- */
	// $('.nav-bar li a').bind('touchend, click', function(e){
	// 	e.preventDefault();
	// 	e.stopPropagation();
	// 	var f = $(this).siblings('.flyout');
	// 	if (!f.is(':visible') && f.length > 0) {
	// 		$('.nav-bar li .flyout').hide();
	// 		f.show();
	// 	} else {
	// 		window.location = $(this).attr('href');
	// 	}
	// });
	// $('.nav-bar, .nav-bar li .flyout').bind('touchend, touchstart, touchmove, touchscroll, hover, mousemove, mouseup, mouseover, mousedown, click', function(e) {
	// 	return false;
	// });
	// $('body').bind('touchend', function(e) {
	// 	$('.nav-bar li .flyout').hide();
	// });

	/* DISABLED BUTTONS ------------- */
	/* Gives elements with a class of 'disabled' a return: false; */

});
<commit_msg>Updated nav bar javascript.<commit_after>$(document).ready(function () {

	/* Use this js doc for all application specific JS */

	/* TABS --------------------------------- */
	/* Remove if you don't need :) */

	function activateTab($tab) {
	  var $activeTab = $tab.closest('dl').find('a.active'),
	      contentLocation = $tab.attr("href") + 'Tab';

	  //Make Tab Active
	  $activeTab.removeClass('active');
	  $tab.addClass('active');

    //Show Tab Content
		$(contentLocation).closest('.tabs-content').find('li').hide();
		$(contentLocation).show();
	}

	$('dl.tabs').each(function () {
		//Get all tabs
		var tabs = $(this).children('dd').children('a');
		tabs.click(function (e) {
		  activateTab($(this));
		});
	});

	if (window.location.hash) {
    activateTab($('a[href="' + window.location.hash + '"]'));
  }

	/* PLACEHOLDER FOR FORMS ------------- */
	/* Remove this and jquery.placeholder.min.js if you don't need :) */

	$('input, textarea').placeholder();

	/* DROPDOWN NAV ------------- */
	$('.nav-bar li a').bind('touchend click', function(e){
		e.preventDefault();
		e.stopPropagation();
		var f = $(this).siblings('.flyout');
		if (!f.is(':visible') && f.length > 0) {
			$('.nav-bar li .flyout').hide();
			f.show();
		} else {
			window.location = $(this).attr('href');
		}
	});
	$('.nav-bar, .nav-bar li .flyout').bind('touchend touchstart touchmove mousemove mouseup mouseover mousedown click', function(e) {
		return false;
	});
	$('body').bind('touchend', function(e) {
		$('.nav-bar li .flyout').hide();
	});

	/* DISABLED BUTTONS ------------- */
	/* Gives elements with a class of 'disabled' a return: false; */

});
<|endoftext|>


<commit_before>class Style {
  constructor(lines) {
    this.lines = {};

    lines.map((line) => {
      this.lines[line.url_name] = line.color;
    });

    this.typeStyles = {
      'sections': {
        'line-width': 7
      },
      'stations': {
        'circle-radius': 7
      }
    }

    this.operationStyles = {
      'hover': {
        'color': '#000',
        'opacity': 0.4
      },
      'opening': {
      },
      'buildstart': {
        'color': '#A4A4A4'
      },
      'inner': {
        'color': '#e6e6e6'
      }
    }
  }

  get(layerName) {
    const parts = layerName.split('_');
    const type = parts[0];
    let operation = parts[1];

    return this.calculate(type, operation);
  }

  calculate(type, operation) {
    const colorCategory = type === 'sections' ? 'line-color' : 'circle-color';

    let style = Object.assign({}, this.typeStyles[type], this.operationStyles[operation]);

    if (style['color']) {
      style[colorCategory] = style["color"];
      delete style["color"];
    }

    if (operation == 'opening'){
      const stops = [];

      Object.entries(this.lines).map((entry) => {
        const line = entry[0];
        const color = entry[1];
          stops.push([line, color]);
      });

      style[colorCategory] = {
        type: "categorical",
        property : "line_url_name",
        stops : stops
      }
    } else if (operation === 'hover') {
      const opacityCategory = type === 'sections' ? 'line-opacity' : 'circle-opacity';
      style[opacityCategory] = style['opacity'];
      delete style['opacity'];
    } else if (operation === 'inner') {
      style["circle-radius"] = style["circle-radius"] - 3;
    }

    return style;
  }

  lineColor(lineUrlName) {
    return this.lines[lineUrlName];
  }

  lineLabelFontColor(lineUrlName){
    const lineColor = this.lineColor(lineUrlName).substr(1);
    return `#${this.contrastingColor(lineColor)}`;
  }

  contrastingColor(color) {
    // Taken from http://stackoverflow.com/questions/635022/calculating-contrasting-colours-in-javascript/6511606#6511606
    return (this.luma(color) >= 165) ? '000' : 'fff';
  }

  luma(color) {
     // color can be a hx string or an array of RGB values 0-255
    const rgb = (typeof color === 'string') ? this.hexToRGBArray(color) : color;
    return (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]); // SMPTE C, Rec. 709 weightings
  }

  hexToRGBArray(color) {
    if (color.length === 3)
      color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
    else if (color.length !== 6)
      throw('Invalid hex color: ' + color);
    const rgb = [];
    for (let i = 0; i <= 2; i++)
      rgb[i] = parseInt(color.substr(i * 2, 2), 16);
    return rgb;
  }
}

export default Style
<commit_msg>Fix var defintion in Style<commit_after>class Style {
  constructor(lines) {
    this.lines = {};

    lines.map((line) => {
      this.lines[line.url_name] = line.color;
    });

    this.typeStyles = {
      'sections': {
        'line-width': 7
      },
      'stations': {
        'circle-radius': 7
      }
    }

    this.operationStyles = {
      'hover': {
        'color': '#000',
        'opacity': 0.4
      },
      'opening': {
      },
      'buildstart': {
        'color': '#A4A4A4'
      },
      'inner': {
        'color': '#e6e6e6'
      }
    }
  }

  get(layerName) {
    const parts = layerName.split('_');
    const type = parts[0];
    const operation = parts[1];

    return this.calculate(type, operation);
  }

  calculate(type, operation) {
    const colorCategory = type === 'sections' ? 'line-color' : 'circle-color';

    let style = Object.assign({}, this.typeStyles[type], this.operationStyles[operation]);

    if (style['color']) {
      style[colorCategory] = style["color"];
      delete style["color"];
    }

    if (operation == 'opening'){
      const stops = [];

      Object.entries(this.lines).map((entry) => {
        const line = entry[0];
        const color = entry[1];
          stops.push([line, color]);
      });

      style[colorCategory] = {
        type: "categorical",
        property : "line_url_name",
        stops : stops
      }
    } else if (operation === 'hover') {
      const opacityCategory = type === 'sections' ? 'line-opacity' : 'circle-opacity';
      style[opacityCategory] = style['opacity'];
      delete style['opacity'];
    } else if (operation === 'inner') {
      style["circle-radius"] = style["circle-radius"] - 3;
    }

    return style;
  }

  lineColor(lineUrlName) {
    return this.lines[lineUrlName];
  }

  lineLabelFontColor(lineUrlName){
    const lineColor = this.lineColor(lineUrlName).substr(1);
    return `#${this.contrastingColor(lineColor)}`;
  }

  contrastingColor(color) {
    // Taken from http://stackoverflow.com/questions/635022/calculating-contrasting-colours-in-javascript/6511606#6511606
    return (this.luma(color) >= 165) ? '000' : 'fff';
  }

  luma(color) {
     // color can be a hx string or an array of RGB values 0-255
    const rgb = (typeof color === 'string') ? this.hexToRGBArray(color) : color;
    return (0.2126 * rgb[0]) + (0.7152 * rgb[1]) + (0.0722 * rgb[2]); // SMPTE C, Rec. 709 weightings
  }

  hexToRGBArray(color) {
    if (color.length === 3)
      color = color.charAt(0) + color.charAt(0) + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2);
    else if (color.length !== 6)
      throw('Invalid hex color: ' + color);
    const rgb = [];
    for (let i = 0; i <= 2; i++)
      rgb[i] = parseInt(color.substr(i * 2, 2), 16);
    return rgb;
  }
}

export default Style
<|endoftext|>


<commit_before>
import test from 'ava'
import {create} from '../lib/apisauce'
import createServer from '../support/server'
import R from 'ramda'
import getFreePort from '../support/getFreePort'

const MOCK = {b: 1}
let port
let server = null

test.before(async t => {
  port = await getFreePort()
  server = createServer(port, MOCK)
})

test.after('cleanup', (t) => {
  server.close()
})

test('attaches an async request transform', (t) => {
  const api = create({ baseURL: `http://localhost:${port}` })
  t.truthy(api.addAsyncRequestTransform)
  t.truthy(api.asyncRequestTransforms)
  t.is(api.asyncRequestTransforms.length, 0)
  api.addAsyncRequestTransform(R.identity)
  t.is(api.asyncRequestTransforms.length, 1)
})

test('alters the request data', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(req => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(count, 0)
        count = 1
        t.is(req.data.b, 1)
        req.data.b = 2
        resolve()
      })
    })
  })
  return x.post('/post', MOCK).then(response => {
    t.is(response.status, 200)
    t.is(count, 1)
    t.is(response.data.got.b, {got: {b: 2}})
  })
})

test('survives empty PUTs', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(() => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        count++
        resolve()
      })
    })
  })
  t.is(count, 0)
  return x.put('/post', {}).then(response => {
    t.is(response.status, 200)
    t.is(count, 1)
  })
})

test('fires for gets', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(({ data, url, method }) => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        count++
        resolve()
      })
    })
  })
  t.is(count, 0)
  return x.get('/number/201').then(response => {
    t.is(response.status, 201)
    t.is(count, 1)
    t.deepEqual(response.data, MOCK)
  })
})

test('url can be changed', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        request.url = R.replace('/201', '/200', request.url)
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 200)
  })
})

test('params can be added, edited, and deleted', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        request.params.x = 2
        request.params.y = 1
        delete request.params.z
        resolve()
      })
    })
  })
  return x.get('/number/200', {x: 1, z: 4}).then(response => {
    t.is(response.status, 200)
    t.is(response.config.params.x, 2)
    t.is(response.config.params.y, 1)
    t.falsy(response.config.params.z)
  })
})

test('headers can be created', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.falsy(request.headers['X-APISAUCE'])
        request.headers['X-APISAUCE'] = 'new'
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.is(response.config.headers['X-APISAUCE'], 'new')
  })
})

test('headers from creation time can be changed', t => {
  const x = create({ baseURL: `http://localhost:${port}`, headers: { 'X-APISAUCE': 'hello' } })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(request.headers['X-APISAUCE'], 'hello')
        request.headers['X-APISAUCE'] = 'change'
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.is(response.config.headers['X-APISAUCE'], 'change')
  })
})

test('headers can be deleted', t => {
  const x = create({ baseURL: `http://localhost:${port}`, headers: { 'X-APISAUCE': 'omg' } })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(request.headers['X-APISAUCE'], 'omg')
        delete request.headers['X-APISAUCE']
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.falsy(response.config.headers['X-APISAUCE'])
  })
})<commit_msg>fix assertion<commit_after>
import test from 'ava'
import {create} from '../lib/apisauce'
import createServer from '../support/server'
import R from 'ramda'
import getFreePort from '../support/getFreePort'

const MOCK = {b: 1}
let port
let server = null

test.before(async t => {
  port = await getFreePort()
  server = createServer(port, MOCK)
})

test.after('cleanup', (t) => {
  server.close()
})

test('attaches an async request transform', (t) => {
  const api = create({ baseURL: `http://localhost:${port}` })
  t.truthy(api.addAsyncRequestTransform)
  t.truthy(api.asyncRequestTransforms)
  t.is(api.asyncRequestTransforms.length, 0)
  api.addAsyncRequestTransform(R.identity)
  t.is(api.asyncRequestTransforms.length, 1)
})

test('alters the request data', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(req => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(count, 0)
        count = 1
        t.is(req.data.b, 1)
        req.data.b = 2
        resolve()
      })
    })
  })
  return x.post('/post', MOCK).then(response => {
    t.is(response.status, 200)
    t.is(count, 1)
    t.is(response.data.got.b, 2)
  })
})

test('survives empty PUTs', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(() => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        count++
        resolve()
      })
    })
  })
  t.is(count, 0)
  return x.put('/post', {}).then(response => {
    t.is(response.status, 200)
    t.is(count, 1)
  })
})

test('fires for gets', (t) => {
  const x = create({ baseURL: `http://localhost:${port}` })
  let count = 0
  x.addAsyncRequestTransform(({ data, url, method }) => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        count++
        resolve()
      })
    })
  })
  t.is(count, 0)
  return x.get('/number/201').then(response => {
    t.is(response.status, 201)
    t.is(count, 1)
    t.deepEqual(response.data, MOCK)
  })
})

test('url can be changed', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        request.url = R.replace('/201', '/200', request.url)
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 200)
  })
})

test('params can be added, edited, and deleted', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        request.params.x = 2
        request.params.y = 1
        delete request.params.z
        resolve()
      })
    })
  })
  return x.get('/number/200', {x: 1, z: 4}).then(response => {
    t.is(response.status, 200)
    t.is(response.config.params.x, 2)
    t.is(response.config.params.y, 1)
    t.falsy(response.config.params.z)
  })
})

test('headers can be created', t => {
  const x = create({ baseURL: `http://localhost:${port}` })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.falsy(request.headers['X-APISAUCE'])
        request.headers['X-APISAUCE'] = 'new'
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.is(response.config.headers['X-APISAUCE'], 'new')
  })
})

test('headers from creation time can be changed', t => {
  const x = create({ baseURL: `http://localhost:${port}`, headers: { 'X-APISAUCE': 'hello' } })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(request.headers['X-APISAUCE'], 'hello')
        request.headers['X-APISAUCE'] = 'change'
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.is(response.config.headers['X-APISAUCE'], 'change')
  })
})

test('headers can be deleted', t => {
  const x = create({ baseURL: `http://localhost:${port}`, headers: { 'X-APISAUCE': 'omg' } })
  x.addAsyncRequestTransform(request => {
    return new Promise((resolve, reject) => {
      setImmediate(_ => {
        t.is(request.headers['X-APISAUCE'], 'omg')
        delete request.headers['X-APISAUCE']
        resolve()
      })
    })
  })
  return x.get('/number/201', {x: 1}).then(response => {
    t.is(response.status, 201)
    t.falsy(response.config.headers['X-APISAUCE'])
  })
})<|endoftext|>