<commit_before>import { expect } from 'chai'
import { join } from 'path'
import { HotModuleReplacementPlugin, optimize } from 'webpack'

import plugin from './base'

const saguiPath = join(__dirname, '../../../../')
const projectPath = join(saguiPath, 'spec/fixtures/simple-project')

describe('configure webpack base', function () {
  describe('targets', function () {
    it('should have the HotModuleReplacementPlugin enabled while developing', function () {
      const config = plugin.configure({ projectPath, saguiPath, buildTarget: 'development' })

      const commons = config.plugins.filter((plugin) => plugin instanceof HotModuleReplacementPlugin)
      expect(commons.length).equal(1)
    })
  })
})
<commit_msg>Fix lint error<commit_after>import { expect } from 'chai'
import { join } from 'path'
import { HotModuleReplacementPlugin } from 'webpack'

import plugin from './base'

const saguiPath = join(__dirname, '../../../../')
const projectPath = join(saguiPath, 'spec/fixtures/simple-project')

describe('configure webpack base', function () {
  describe('targets', function () {
    it('should have the HotModuleReplacementPlugin enabled while developing', function () {
      const config = plugin.configure({ projectPath, saguiPath, buildTarget: 'development' })

      const commons = config.plugins.filter((plugin) => plugin instanceof HotModuleReplacementPlugin)
      expect(commons.length).equal(1)
    })
  })
})
<|endoftext|>


<commit_before>(function () {
    "use strict";
    var ImageResizerProxy = {
        resize: function (win, fail, args, env) {
            try {
                if (!args[0]) {
                    fail("Missing options");
                }
                var argConfig = args[0];
                resizeImage(win, fail, argConfig);
            } catch (e) {
                fail(e);
            }
        }
    };

    var fileIO = Windows.Storage.FileIO;
    var getAppData = function () {
        return Windows.Storage.ApplicationData.current;
    };

    // Resize method
    function resizeImage(successCallback, errorCallback, config) {
        var tempPhotoFileName =  config.fileName || new Date().getTime().toString() + ".jpg";
        var file;
        var targetWidth = config.width;
        var targetHeight = config.height;
        var uri = new Windows.Foundation.Uri(config.uri);
        var storageFolder = getAppData().localFolder;
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
            .then(function (storageFile) {
                file = f;
                return fileIO.readBufferAsync(storageFile);
            })
            .then(function (buffer) {
                var strBase64 = encodeToBase64String(buffer);
                var imageData = "data:" + file.contentType + ";base64," + strBase64;
                var image = new Image();
                image.src = imageData;
                image.onload = function () {
                    var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
                    var imageWidth = ratio * this.width;
                    var imageHeight = ratio * this.height;

                    var canvas = document.createElement('canvas');
                    var storageFileName;

                    canvas.width = imageWidth;
                    canvas.height = imageHeight;

                    canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);

                    var fileContent = canvas.toDataURL(file.contentType).split(',')[1];

                    var storageFolder = getAppData().localFolder;

                    storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
                        .then(function (storagefile) {
                            var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
                            storageFileName = storagefile.name;
                            return fileIO.writeBufferAsync(storagefile, content);
                        })
                        .done(function () {
                            successCallback("ms-appdata:///local/" + storageFileName);
                        }, errorCallback);
                };
            })
            .done(null, function (err) {
                errorCallback(err);
            }
        );
    }

    require("cordova/exec/proxy").add("ImageResizer", ImageResizerProxy);
})();<commit_msg>fixing behaviour<commit_after>(function () {
    "use strict";
    var ImageResizerProxy = {
        resize: function (win, fail, args, env) {
            try {
                if (!args[0]) {
                    fail("Missing options");
                }
                var argConfig = args[0];
                resizeImage(win, fail, argConfig);
            } catch (e) {
                fail(e);
            }
        }
    };

    var fileIO = Windows.Storage.FileIO;
    var getAppData = function () {
        return Windows.Storage.ApplicationData.current;
    };

    // Resize method
    function resizeImage(successCallback, errorCallback, config) {
        var tempPhotoFileName =  config.fileName || new Date().getTime().toString() + ".jpg";
        var file;
        var targetWidth = config.width;
        var targetHeight = config.height;
        var uri = new Windows.Foundation.Uri(config.uri);
        var storageFolder = getAppData().localFolder;
        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
            .then(function (storageFile) {
                file = storageFile;
                return fileIO.readBufferAsync(storageFile);
            })
            .then(function (buffer) {
                var strBase64 = encodeToBase64String(buffer);
                var imageData = "data:" + file.contentType + ";base64," + strBase64;
                var image = new Image();
                image.src = imageData;
                image.onload = function () {
                    var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
                    var imageWidth = ratio * this.width;
                    var imageHeight = ratio * this.height;

                    var canvas = document.createElement('canvas');
                    var storageFileName;

                    canvas.width = imageWidth;
                    canvas.height = imageHeight;

                    canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);

                    var fileContent = canvas.toDataURL(file.contentType).split(',')[1];

                    var storageFolder = getAppData().localFolder;

                    storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
                        .then(function (storagefile) {
                            var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
                            storageFileName = storagefile.name;
                            return fileIO.writeBufferAsync(storagefile, content);
                        })
                        .done(function () {
                            successCallback("ms-appdata:///local/" + storageFileName);
                        }, errorCallback);
                };
            })
            .done(null, function (err) {
                errorCallback(err);
            }
        );
    }

    require("cordova/exec/proxy").add("ImageResizer", ImageResizerProxy);
})();<|endoftext|>


<commit_before>'use strict';

/* global cordova */

define(function(require) {
    // Import modules
    var records = require('records');
    var map = require('map');
    var file = require('file');
    var download = require('plugins/sync/js/download');
    var cards = require('./cards');
    var actions = require('./actions');

    //Constants
    var PLUGIN_PATH = 'plugins/points-of-interest';

    // Check if the required sync plugin is installed
    if (typeof download !== 'object') {
        console.warn('Required fieldtrip-sync plugin is not installed');
    }

    /**
     * Show a POI layer adding it if necessary
     */
    var showPOILayer = function(layerMetadata) {
        // If the layer is available make it visible
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            layer.display(true);
        }
        else {
            fetchFileURLAsJSON(layerMetadata.options.fileURL)
                .done(addPOILayer)
                .fail(function(err) {
                    console.error(err);
                });
        }

    };

    /**
     * Hide a POI layer
     */
    var hidePOILayer = function(layerMetadata) {
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            layer.display(false);
        }
        else {
            console.warn('Not layer found with id' + layerMetadata.id);
        }
    };

    /**
     * Zoom to the extent of the layer features
     */
    var zoomToExtentPOILayer = function(layerMetadata) {
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            map.zoomToExtent(layer.getDataExtent());
        }
        else {
            console.warn('Not layer found with id' + layerMetadata.id);
        }
    };

    /**
     * Check if the object contains a geojson FeatureCollection
     * @params object {Object} containin a geoJSON
     * @returns {Bool} true/false
     */
    var containsFeatureCollection = function(object) {
        if (object.type && object.type === 'FeatureCollection' &&
            object.features && typeof Array.isArray(object.features)) {
            return true;
        }
        return false;
    };

    /**
     * Open an editor from the parameters of the action
     * @param action {Object}
     *     param.name {String} name of the editor
     *     param.group {String} group of the editor (optional)
     */
    var openEditor = function(action) {
        var params = action.params;

        var group = params.group || records.EDITOR_GROUP.PRIVATE;
        var name = params.name;
        records.annotate(group, name);
    };

    var openCard = function(action) {
        var params = action.params;
        var group = params.group || records.EDITOR_GROUP.PRIVATE;
        var editor = params.editor;

        var card = params.card;
        var cardUrl = 'view-card.html' +
            '?group=' + group + '&editor=' + editor + '&card=' + card;

        $('body').pagecontainer('change', cardUrl);
    };

    /**
     * Open an editor
     * @param action {Object}
     *     - type {String} type of object to open
     */
    var doOpen = function(action) {
        var type = action.params.type;

        switch (type) {
            case 'editor':
                openEditor(action);
                break;
            case 'card':
                openCard(action);
                break;
            default:
                console.warn('Don\'t know how to open: [' + type + ']');
        }
    };

    /**
     * Register the initial actions for the markers
     */
    var registerActions = function() {
        actions.register('open', doOpen);
    };

    /**
     * Register the actios over the features of the POI Layer
     * @param layer {OpenLayers.Layer} reference to the layer
     */
    var registerPOILayerEvents = function(layer) {
        var onFeatureSelect = function(evt) {
            var feature = evt.feature;
            actions.perform(feature.data.action);
        };

        var onFeatureUnselect = function(evt) {
            var feature = evt.feature;
        };

        map.registerFeatureEvents(layer, {
            selected: onFeatureSelect,
            unselected: onFeatureUnselect
        });
    };

    /**
     * Add styles to the POI features
     * @param layer {OpenLayers.Layer} a layer
     */
    var setPOILayerStyles = function(layer) {
        var defaultStyle;
        var selectStyle;

        defaultStyle = {
            graphicWidth: 35,
            graphicHeight: 50,
            externalGraphic: PLUGIN_PATH + '/css/images/plain_marker@2x.png'
        };

        selectStyle = {
            graphicWidth: 35,
            graphicHeight: 50,
            externalGraphic: PLUGIN_PATH + '/css/images/plain_marker@2x.png',
            graphicOpacity: 0.9
        };

        map.setLayerStyle(layer, defaultStyle, 'default');
        map.setLayerStyle(layer, selectStyle, 'select');
    };

    /**
     * Adds a geoJSON as a POI layer to the map
     * @param poiGeoJSON {Object} and object containing the geoJSON with the POI
     */
    var addPOILayer = function(poiGeoJSON) {
        var poiLayer;
        var layerId;
        var layerName;

        if (!containsFeatureCollection(poiGeoJSON)) {
            console.error('Json doesn\'t contains a FeatureCollection');
            return;
        }

        layerId = poiGeoJSON.properties.id;
        layerName = poiGeoJSON.properties.name;

        poiLayer = map.addGeoJSONLayer(layerId, poiGeoJSON);

        setPOILayerStyles(poiLayer);
        registerPOILayerEvents(poiLayer);
        registerActions();

        poiLayer.setVisibility(true);
    };

    /**
     * Wraps downloadItem as a promise
     *
     * @param options {Object}
     *   remoteDir {String} Remote dir
     *   fileName {String} Remote file name
     *   localDir {String} Local directory
     *   targetName {String} Local filename
     * @returns a promise that resolves in a {FileEntry} or is rejected with an Error
     */
    var downloadItem = function(options) {
        var deferred = $.Deferred();
        var json;

        download.downloadItem(
            options,
            function(fileEntry) {
                deferred.resolve(fileEntry);
            },
            function(err) {
                deferred.reject(err);
            }
        );

        return deferred.promise();
    };

    /**
     * Fetch the contents of a file as JSON
     *
     * @param {String} a fileURL
     * @returns a promise that resolvers in an {Object}
     */
    var fetchFileURLAsJSON = function(fileURL) {
        var deferred = $.Deferred();

        // Resolve the fileURL as a {fileEntry}
        file.resolveFileURL(fileURL)
            .done(function(fileEntry) {
                fetchFileEntryAsJSON(fileEntry)
                    .done(deferred.resolve)
                    .fail(deferred.reject);
            })
            .fail(function(err) {
                console.error(err);
            });
        return deferred.promise();
    };

    /**
     * Fetch the contents of a fileEntry as JSON
     *
     * @param fileEntry {FileEntry} a FileEntry
     * @returns a promise that resolve in an {Object}
     */
    var fetchFileEntryAsJSON = function(fileEntry) {
        var deferred = $.Deferred();

        file.readTextFile(fileEntry)
            .done(function(text) {
                var poiGeoJSON;
                try {
                    poiGeoJSON = JSON.parse(text);
                }
                catch (ex) {
                    deferred.reject(ex);
                }

                deferred.resolve(poiGeoJSON);
            })
            .fail(function(err) {
                deferred.reject(err);
            });

        return deferred.promise();
    };

    /**
     * Read the content of a layer and register it in the layers object
     * fileEntry {FileEntry} - A File entry where the layer is stored
     */
    var registerLayer = function(fileEntry) {
        fetchFileEntryAsJSON(fileEntry)
            .done(function(geoJSON) {
                var id = geoJSON.properties.id;
                var name = geoJSON.properties.name;
                var options = {
                    fileURL: fileEntry.toURL()
                };

                map.addLayerToLayersList(id, name, 'poi', options);
            })
            .fail(function(err) {
                console.error(err);
            });
    };

    /**
     * Extract the poi from the editor markup
     * implements the records.processEditor interface
     * @param editorName name of the editor
     * @param html html content of the editor
     * @param group from records.EDITOR_GROUP
     * @param online boolean value if the processing is held online
     */
    var extractPOI = function(editorName, html, group, online) {
        var $form = $(html);
        var geofence;
        var geofences;

        $form
            .find('[data-fieldtrip-type=poi] [data-poi-file]')
            .each(function(i, el) {
                var options = {};
                var poiFile = el.getAttribute('data-poi-file');
                var fullLocalPath;
                options.remoteDir = 'features';
                options.fileName = poiFile;
                options.localDir = records.getEditorsDir(group);
                options.targetName = poiFile;
                fullLocalPath = file.getFilePath(options.localDir) + '/' + options.targetName;

                downloadItem(options)
                    .done(registerLayer);
            });
    };

    /**
     * Perform initialization actions for the the POI plugin
     */
    var initPOIPlugin = function() {
        // Inject the plugin styles
        $('head').prepend('<link rel="stylesheet" href="' + PLUGIN_PATH + '/css/style.css" type="text/css" />');

        // Add map page show handler
        $(document).on('pagebeforeshow', '#map-page', function() {

        });

        // Add the plugin editor process to the pipeline
        records.addProcessEditor(extractPOI);

        // Listen for on/off events in the layer list
        map.suscribeToLayersControl({
            enableLayer: function(event, layerMetaData) {
                // TODO: Filter the POI layers
                showPOILayer(layerMetaData);
            },
            disableLayer: function(event, layerMetadata) {
                hidePOILayer(layerMetadata);
            },
            clickLayer: function(event, layerMetadata) {
                zoomToExtentPOILayer(layerMetadata);
            }
        });
    };

    // Init the plugin
    initPOIPlugin();
});
<commit_msg>Use core call to show/hide layer<commit_after>'use strict';

/* global cordova */

define(function(require) {
    // Import modules
    var records = require('records');
    var map = require('map');
    var file = require('file');
    var download = require('plugins/sync/js/download');
    var cards = require('./cards');
    var actions = require('./actions');

    //Constants
    var PLUGIN_PATH = 'plugins/points-of-interest';

    // Check if the required sync plugin is installed
    if (typeof download !== 'object') {
        console.warn('Required fieldtrip-sync plugin is not installed');
    }

    /**
     * Show a POI layer adding it if necessary
     */
    var showPOILayer = function(layerMetadata) {
        // If the layer is available make it visible
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            map.showLayer(layer);
        }
        else {
            fetchFileURLAsJSON(layerMetadata.options.fileURL)
                .done(addPOILayer)
                .fail(function(err) {
                    console.error(err);
                });
        }

    };

    /**
     * Hide a POI layer
     */
    var hidePOILayer = function(layerMetadata) {
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            map.hideLayer(layer);
        }
        else {
            console.warn('Not layer found with id' + layerMetadata.id);
        }
    };

    /**
     * Zoom to the extent of the layer features
     */
    var zoomToExtentPOILayer = function(layerMetadata) {
        var layer = map.getLayer(layerMetadata.id);

        if (layer) {
            map.zoomToExtent(layer.getDataExtent());
        }
        else {
            console.warn('Not layer found with id' + layerMetadata.id);
        }
    };

    /**
     * Check if the object contains a geojson FeatureCollection
     * @params object {Object} containin a geoJSON
     * @returns {Bool} true/false
     */
    var containsFeatureCollection = function(object) {
        if (object.type && object.type === 'FeatureCollection' &&
            object.features && typeof Array.isArray(object.features)) {
            return true;
        }
        return false;
    };

    /**
     * Open an editor from the parameters of the action
     * @param action {Object}
     *     param.name {String} name of the editor
     *     param.group {String} group of the editor (optional)
     */
    var openEditor = function(action) {
        var params = action.params;

        var group = params.group || records.EDITOR_GROUP.PRIVATE;
        var name = params.name;
        records.annotate(group, name);
    };

    var openCard = function(action) {
        var params = action.params;
        var group = params.group || records.EDITOR_GROUP.PRIVATE;
        var editor = params.editor;

        var card = params.card;
        var cardUrl = 'view-card.html' +
            '?group=' + group + '&editor=' + editor + '&card=' + card;

        $('body').pagecontainer('change', cardUrl);
    };

    /**
     * Open an editor
     * @param action {Object}
     *     - type {String} type of object to open
     */
    var doOpen = function(action) {
        var type = action.params.type;

        switch (type) {
            case 'editor':
                openEditor(action);
                break;
            case 'card':
                openCard(action);
                break;
            default:
                console.warn('Don\'t know how to open: [' + type + ']');
        }
    };

    /**
     * Register the initial actions for the markers
     */
    var registerActions = function() {
        actions.register('open', doOpen);
    };

    /**
     * Register the actios over the features of the POI Layer
     * @param layer {OpenLayers.Layer} reference to the layer
     */
    var registerPOILayerEvents = function(layer) {
        var onFeatureSelect = function(evt) {
            var feature = evt.feature;
            actions.perform(feature.data.action);
        };

        var onFeatureUnselect = function(evt) {
            var feature = evt.feature;
        };

        map.registerFeatureEvents(layer, {
            selected: onFeatureSelect,
            unselected: onFeatureUnselect
        });
    };

    /**
     * Add styles to the POI features
     * @param layer {OpenLayers.Layer} a layer
     */
    var setPOILayerStyles = function(layer) {
        var defaultStyle;
        var selectStyle;

        defaultStyle = {
            graphicWidth: 35,
            graphicHeight: 50,
            externalGraphic: PLUGIN_PATH + '/css/images/plain_marker@2x.png'
        };

        selectStyle = {
            graphicWidth: 35,
            graphicHeight: 50,
            externalGraphic: PLUGIN_PATH + '/css/images/plain_marker@2x.png',
            graphicOpacity: 0.9
        };

        map.setLayerStyle(layer, defaultStyle, 'default');
        map.setLayerStyle(layer, selectStyle, 'select');
    };

    /**
     * Adds a geoJSON as a POI layer to the map
     * @param poiGeoJSON {Object} and object containing the geoJSON with the POI
     */
    var addPOILayer = function(poiGeoJSON) {
        var poiLayer;
        var layerId;
        var layerName;

        if (!containsFeatureCollection(poiGeoJSON)) {
            console.error('Json doesn\'t contains a FeatureCollection');
            return;
        }

        layerId = poiGeoJSON.properties.id;
        layerName = poiGeoJSON.properties.name;

        poiLayer = map.addGeoJSONLayer(layerId, poiGeoJSON);

        setPOILayerStyles(poiLayer);
        registerPOILayerEvents(poiLayer);
        registerActions();

        poiLayer.setVisibility(true);
    };

    /**
     * Wraps downloadItem as a promise
     *
     * @param options {Object}
     *   remoteDir {String} Remote dir
     *   fileName {String} Remote file name
     *   localDir {String} Local directory
     *   targetName {String} Local filename
     * @returns a promise that resolves in a {FileEntry} or is rejected with an Error
     */
    var downloadItem = function(options) {
        var deferred = $.Deferred();
        var json;

        download.downloadItem(
            options,
            function(fileEntry) {
                deferred.resolve(fileEntry);
            },
            function(err) {
                deferred.reject(err);
            }
        );

        return deferred.promise();
    };

    /**
     * Fetch the contents of a file as JSON
     *
     * @param {String} a fileURL
     * @returns a promise that resolvers in an {Object}
     */
    var fetchFileURLAsJSON = function(fileURL) {
        var deferred = $.Deferred();

        // Resolve the fileURL as a {fileEntry}
        file.resolveFileURL(fileURL)
            .done(function(fileEntry) {
                fetchFileEntryAsJSON(fileEntry)
                    .done(deferred.resolve)
                    .fail(deferred.reject);
            })
            .fail(function(err) {
                console.error(err);
            });
        return deferred.promise();
    };

    /**
     * Fetch the contents of a fileEntry as JSON
     *
     * @param fileEntry {FileEntry} a FileEntry
     * @returns a promise that resolve in an {Object}
     */
    var fetchFileEntryAsJSON = function(fileEntry) {
        var deferred = $.Deferred();

        file.readTextFile(fileEntry)
            .done(function(text) {
                var poiGeoJSON;
                try {
                    poiGeoJSON = JSON.parse(text);
                }
                catch (ex) {
                    deferred.reject(ex);
                }

                deferred.resolve(poiGeoJSON);
            })
            .fail(function(err) {
                deferred.reject(err);
            });

        return deferred.promise();
    };

    /**
     * Read the content of a layer and register it in the layers object
     * fileEntry {FileEntry} - A File entry where the layer is stored
     */
    var registerLayer = function(fileEntry) {
        fetchFileEntryAsJSON(fileEntry)
            .done(function(geoJSON) {
                var id = geoJSON.properties.id;
                var name = geoJSON.properties.name;
                var options = {
                    fileURL: fileEntry.toURL()
                };

                map.addLayerToLayersList(id, name, 'poi', options);
            })
            .fail(function(err) {
                console.error(err);
            });
    };

    /**
     * Extract the poi from the editor markup
     * implements the records.processEditor interface
     * @param editorName name of the editor
     * @param html html content of the editor
     * @param group from records.EDITOR_GROUP
     * @param online boolean value if the processing is held online
     */
    var extractPOI = function(editorName, html, group, online) {
        var $form = $(html);
        var geofence;
        var geofences;

        $form
            .find('[data-fieldtrip-type=poi] [data-poi-file]')
            .each(function(i, el) {
                var options = {};
                var poiFile = el.getAttribute('data-poi-file');
                var fullLocalPath;
                options.remoteDir = 'features';
                options.fileName = poiFile;
                options.localDir = records.getEditorsDir(group);
                options.targetName = poiFile;
                fullLocalPath = file.getFilePath(options.localDir) + '/' + options.targetName;

                downloadItem(options)
                    .done(registerLayer);
            });
    };

    /**
     * Perform initialization actions for the the POI plugin
     */
    var initPOIPlugin = function() {
        // Inject the plugin styles
        $('head').prepend('<link rel="stylesheet" href="' + PLUGIN_PATH + '/css/style.css" type="text/css" />');

        // Add map page show handler
        $(document).on('pagebeforeshow', '#map-page', function() {

        });

        // Add the plugin editor process to the pipeline
        records.addProcessEditor(extractPOI);

        // Listen for on/off events in the layer list
        map.suscribeToLayersControl({
            enableLayer: function(event, layerMetaData) {
                // TODO: Filter the POI layers
                showPOILayer(layerMetaData);
            },
            disableLayer: function(event, layerMetadata) {
                hidePOILayer(layerMetadata);
            },
            clickLayer: function(event, layerMetadata) {
                zoomToExtentPOILayer(layerMetadata);
            }
        });
    };

    // Init the plugin
    initPOIPlugin();
});
<|endoftext|>


<commit_before>import {geoBounds as bounds, geoCentroid as centroid, geoInterpolate as interpolate, geoProjection as projection} from "d3-geo";
import {abs, cos, degrees, epsilon, pi, radians, sin} from "../math";
import {default as matrix, multiply, inverse} from "./matrix";

// Creates a polyhedral projection.
//  * root: a spanning tree of polygon faces.  Nodes are automatically
//    augmented with a transform matrix.
//  * face: a function that returns the appropriate node for a given {lambda, phi}
//    point (radians).
//  * r: rotation angle for final polyhedral net.  Defaults to -pi / 6 (for
//    butterflies).
export default function(root, face, r) {

  r = r == null ? -pi / 6 : r; // TODO automate

  recurse(root, {transform: [
    cos(r), sin(r), 0,
    -sin(r), cos(r), 0
  ]});

  function recurse(node, parent) {
    node.edges = faceEdges(node.face);
    if (parent) {
      // Find shared edge.
      if (parent.face) {
        var shared = node.shared = sharedEdge(node.face, parent.face),
            m = matrix(shared.map(parent.project), shared.map(node.project));
        node.transform = parent.transform ? multiply(parent.transform, m) : m;
        // Replace shared edge in parent edges array.
        var edges = parent.edges;
        for (var i = 0, n = edges.length; i < n; ++i) {
          if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = node;
          if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = node;
        }
        edges = node.edges;
        for (i = 0, n = edges.length; i < n; ++i) {
          if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = parent;
          if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = parent;
        }
      } else {
        node.transform = parent.transform;
      }
    }
    if (node.children) {
      node.children.forEach(function(child) {
        recurse(child, node);
      });
    }
    return node;
  }

  function forward(lambda, phi) {
    var node = face(lambda, phi),
        point = node.project([lambda * degrees, phi * degrees]),
        t;
    if (t = node.transform) {
      return [
        t[0] * point[0] + t[1] * point[1] + t[2],
        -(t[3] * point[0] + t[4] * point[1] + t[5])
      ];
    }
    point[1] = -point[1];
    return point;
  }

  // Naive inverse!  A faster solution would use bounding boxes, or even a
  // polygonal quadtree.
  if (hasInverse(root)) forward.invert = function(x, y) {
    var coordinates = faceInvert(root, [x, -y]);
    return coordinates && (coordinates[0] *= radians, coordinates[1] *= radians, coordinates);
  };

  function faceInvert(node, coordinates) {
    var invert = node.project.invert,
        t = node.transform,
        point = coordinates;
    if (t) {
      t = inverse(t);
      point = [
        t[0] * point[0] + t[1] * point[1] + t[2],
        (t[3] * point[0] + t[4] * point[1] + t[5])
      ];
    }
    if (invert && node === faceDegrees(p = invert(point))) return p;
    var p,
        children = node.children;
    for (var i = 0, n = children && children.length; i < n; ++i) {
      if (p = faceInvert(children[i], coordinates)) return p;
    }
  }

  function faceDegrees(coordinates) {
    return face(coordinates[0] * radians, coordinates[1] * radians);
  }

  var proj = projection(forward),
      stream_ = proj.stream;

  proj.stream = function(stream) {
    var rotate = proj.rotate(),
        rotateStream = stream_(stream),
        sphereStream = (proj.rotate([0, 0]), stream_(stream));
    proj.rotate(rotate);
    rotateStream.sphere = function() {
      sphereStream.polygonStart();
      sphereStream.lineStart();
      outline(sphereStream, root);
      sphereStream.lineEnd();
      sphereStream.polygonEnd();
    };
    return rotateStream;
  };

  return proj;
}

function outline(stream, node, parent) {
  var point,
      edges = node.edges,
      n = edges.length,
      edge,
      multiPoint = {type: "MultiPoint", coordinates: node.face},
      notPoles = node.face.filter(function(d) { return abs(d[1]) !== 90; }),
      b = bounds({type: "MultiPoint", coordinates: notPoles}),
      inside = false,
      j = -1,
      dx = b[1][0] - b[0][0];
  // TODO
  var c = dx === 180 || dx === 360
      ? [(b[0][0] + b[1][0]) / 2, (b[0][1] + b[1][1]) / 2]
      : centroid(multiPoint);
  // First find the shared edge…
  if (parent) while (++j < n) {
    if (edges[j] === parent) break;
  }
  ++j;
  for (var i = 0; i < n; ++i) {
    edge = edges[(i + j) % n];
    if (Array.isArray(edge)) {
      if (!inside) {
        stream.point((point = interpolate(edge[0], c)(epsilon))[0], point[1]);
        inside = true;
      }
      stream.point((point = interpolate(edge[1], c)(epsilon))[0], point[1]);
    } else {
      inside = false;
      if (edge !== parent) outline(stream, edge, node);
    }
  }
}

// Tests equality of two spherical points.
function pointEqual(a, b) {
  return a && b && a[0] === b[0] && a[1] === b[1];
}

// Finds a shared edge given two clockwise polygons.
function sharedEdge(a, b) {
  var x, y, n = a.length, found = null;
  for (var i = 0; i < n; ++i) {
    x = a[i];
    for (var j = b.length; --j >= 0;) {
      y = b[j];
      if (x[0] === y[0] && x[1] === y[1]) {
        if (found) return [found, x];
        found = x;
      }
    }
  }
}

// Converts an array of n face vertices to an array of n + 1 edges.
function faceEdges(face) {
  var n = face.length,
      edges = [];
  for (var a = face[n - 1], i = 0; i < n; ++i) edges.push([a, a = face[i]]);
  return edges;
}

function hasInverse(node) {
  return node.project.invert || node.children && node.children.some(hasInverse);
}
<commit_msg>useless if()<commit_after>import {geoBounds as bounds, geoCentroid as centroid, geoInterpolate as interpolate, geoProjection as projection} from "d3-geo";
import {abs, cos, degrees, epsilon, pi, radians, sin} from "../math";
import {default as matrix, multiply, inverse} from "./matrix";

// Creates a polyhedral projection.
//  * root: a spanning tree of polygon faces.  Nodes are automatically
//    augmented with a transform matrix.
//  * face: a function that returns the appropriate node for a given {lambda, phi}
//    point (radians).
//  * r: rotation angle for final polyhedral net.  Defaults to -pi / 6 (for
//    butterflies).
export default function(root, face, r) {

  r = r == null ? -pi / 6 : r; // TODO automate

  recurse(root, {transform: [
    cos(r), sin(r), 0,
    -sin(r), cos(r), 0
  ]});

  function recurse(node, parent) {
    node.edges = faceEdges(node.face);
    // Find shared edge.
    if (parent.face) {
      var shared = node.shared = sharedEdge(node.face, parent.face),
          m = matrix(shared.map(parent.project), shared.map(node.project));
      node.transform = parent.transform ? multiply(parent.transform, m) : m;
      // Replace shared edge in parent edges array.
      var edges = parent.edges;
      for (var i = 0, n = edges.length; i < n; ++i) {
        if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = node;
        if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = node;
      }
      edges = node.edges;
      for (i = 0, n = edges.length; i < n; ++i) {
        if (pointEqual(shared[0], edges[i][0]) && pointEqual(shared[1], edges[i][1])) edges[i] = parent;
        if (pointEqual(shared[0], edges[i][1]) && pointEqual(shared[1], edges[i][0])) edges[i] = parent;
      }
    } else {
      node.transform = parent.transform;
    }
    if (node.children) {
      node.children.forEach(function(child) {
        recurse(child, node);
      });
    }
    return node;
  }

  function forward(lambda, phi) {
    var node = face(lambda, phi),
        point = node.project([lambda * degrees, phi * degrees]),
        t;
    if (t = node.transform) {
      return [
        t[0] * point[0] + t[1] * point[1] + t[2],
        -(t[3] * point[0] + t[4] * point[1] + t[5])
      ];
    }
    point[1] = -point[1];
    return point;
  }

  // Naive inverse!  A faster solution would use bounding boxes, or even a
  // polygonal quadtree.
  if (hasInverse(root)) forward.invert = function(x, y) {
    var coordinates = faceInvert(root, [x, -y]);
    return coordinates && (coordinates[0] *= radians, coordinates[1] *= radians, coordinates);
  };

  function faceInvert(node, coordinates) {
    var invert = node.project.invert,
        t = node.transform,
        point = coordinates;
    if (t) {
      t = inverse(t);
      point = [
        t[0] * point[0] + t[1] * point[1] + t[2],
        (t[3] * point[0] + t[4] * point[1] + t[5])
      ];
    }
    if (invert && node === faceDegrees(p = invert(point))) return p;
    var p,
        children = node.children;
    for (var i = 0, n = children && children.length; i < n; ++i) {
      if (p = faceInvert(children[i], coordinates)) return p;
    }
  }

  function faceDegrees(coordinates) {
    return face(coordinates[0] * radians, coordinates[1] * radians);
  }

  var proj = projection(forward),
      stream_ = proj.stream;

  proj.stream = function(stream) {
    var rotate = proj.rotate(),
        rotateStream = stream_(stream),
        sphereStream = (proj.rotate([0, 0]), stream_(stream));
    proj.rotate(rotate);
    rotateStream.sphere = function() {
      sphereStream.polygonStart();
      sphereStream.lineStart();
      outline(sphereStream, root);
      sphereStream.lineEnd();
      sphereStream.polygonEnd();
    };
    return rotateStream;
  };

  return proj;
}

function outline(stream, node, parent) {
  var point,
      edges = node.edges,
      n = edges.length,
      edge,
      multiPoint = {type: "MultiPoint", coordinates: node.face},
      notPoles = node.face.filter(function(d) { return abs(d[1]) !== 90; }),
      b = bounds({type: "MultiPoint", coordinates: notPoles}),
      inside = false,
      j = -1,
      dx = b[1][0] - b[0][0];
  // TODO
  var c = dx === 180 || dx === 360
      ? [(b[0][0] + b[1][0]) / 2, (b[0][1] + b[1][1]) / 2]
      : centroid(multiPoint);
  // First find the shared edge…
  if (parent) while (++j < n) {
    if (edges[j] === parent) break;
  }
  ++j;
  for (var i = 0; i < n; ++i) {
    edge = edges[(i + j) % n];
    if (Array.isArray(edge)) {
      if (!inside) {
        stream.point((point = interpolate(edge[0], c)(epsilon))[0], point[1]);
        inside = true;
      }
      stream.point((point = interpolate(edge[1], c)(epsilon))[0], point[1]);
    } else {
      inside = false;
      if (edge !== parent) outline(stream, edge, node);
    }
  }
}

// Tests equality of two spherical points.
function pointEqual(a, b) {
  return a && b && a[0] === b[0] && a[1] === b[1];
}

// Finds a shared edge given two clockwise polygons.
function sharedEdge(a, b) {
  var x, y, n = a.length, found = null;
  for (var i = 0; i < n; ++i) {
    x = a[i];
    for (var j = b.length; --j >= 0;) {
      y = b[j];
      if (x[0] === y[0] && x[1] === y[1]) {
        if (found) return [found, x];
        found = x;
      }
    }
  }
}

// Converts an array of n face vertices to an array of n + 1 edges.
function faceEdges(face) {
  var n = face.length,
      edges = [];
  for (var a = face[n - 1], i = 0; i < n; ++i) edges.push([a, a = face[i]]);
  return edges;
}

function hasInverse(node) {
  return node.project.invert || node.children && node.children.some(hasInverse);
}
<|endoftext|>


<commit_before>/*
 * Project Search and List
 */
// FIXME: We're not using this Model right now.
App.ProjectPreviewModel = Em.Object.extend({
});

App.ProjectModel = Em.Object.extend({
});

App.projectSearchController = Em.ArrayController.create({
    init: function() {
        this._super();
        this.populate();
    },

    // The saved queryFilter state.
    queryFilter: {phases: 'fund'}, // FIXME Limit projects to fund phase until projects are cleaned up.

    // Project preview search results and meta data from tastypie:
    content: [],
    contentMeta: {},

    // List of regions and countries returned from the search form filter. These
    // are content bindings for the filter elements (e.g. select) in the Views.
    filteredRegions: [],
    filteredCountries: [],

    // Query filters that react after a button click or enter from the keyboard:
    searchText: "",
    submitTextSearchForm: function(event) {
        var searchText = this.get('searchText');
        this.updateQueryFilter({'text': searchText});
        this.populate();
    },

    // Pagination:
    nextSearchResult: function() {
        var meta  = this.get('contentMeta');
        var next = (RegExp('offset=' + '(.+?)(&|$)').exec(meta.next)||[,null])[1]
        return next;
    }.property('contentMeta'),

    previousSearchResult: function() {
        var meta  = this.get('contentMeta');
        var previous = (RegExp('offset=' + '(.+?)(&|$)').exec(meta.previous)||[,null])[1]
        return previous;
    }.property('contentMeta'),

    // We can't use an observer for this because it will loop continually.
    clickPrevNext: function(event, offset) {
        this.updateQueryFilter({'offset': offset});
        this.populate();
    },

    // Loads the data from the server based on the current value of queryFilter.
    populate: function() {
        var controller = this;
        require(['app/data_source'], function(){
            var query = controller.get('queryFilter');

            // Get the project preview data using the current queryFilter.
            // We're limiting the number of returned items to 4.
            var projectPreviewQuery = $.extend({limit: 4}, query);
            App.dataSource.get('projectpreview', projectPreviewQuery, function(data) {
                controller.set('content', data['objects']);
                controller.set('contentMeta', data['meta']);
            });

            // Get the project search data using the same queryFilter.
            // Note that we're *not* limiting the number of returned items as above.
            App.dataSource.get('projectsearchform', query, function(data) {
                var objects = data['objects'];
                for (var i = 0; i < objects.length; i++) {
                    switch (objects[i].name) {
                        case "countries":
                            controller.set('filteredCountries', objects[i].options);
                            break;
                        case "regions":
                            controller.set('filteredRegions', objects[i].options);
                            break;
                        default:
                            break;
                    }
                }

            });

        });
    },

    // Updates the queryFilter based on the queryParam. Parameters  with a null
    // or empty value are removed from the filter list.
    updateQueryFilter: function(queryParam) {
        var query = this.get('queryFilter');
        var tempQuery = $.extend({}, query);

        // Delete the param if it's already in the queryFilter. This lets us update
        // the param or leave it deleted depending on what's requested.
        var i = 0;
        for (var property in queryParam) {
            if (i > 0) {
                // TODO: Support multiple queryFilter parameter updates.
                break;  // Only use the first property in queryParam.
            }
            if (undefined != tempQuery[property]) {
                delete tempQuery[property];
            }
            i++;
        }

        // The offset parameter needs to be deleted on every filter update.
        if (undefined != tempQuery['offset']) {
            delete tempQuery['offset'];
        }

        // Create an updated queryFilter.
        if (queryParam[property] == null || queryParam[property].length == 0 ) {
            // QueryParam[property] is null or empty string. Ignore it.
            var updatedQuery = tempQuery;
        } else {
            var updatedQuery = $.extend(queryParam, tempQuery);
        }

        // Set the updated queryFilter and populate the data.
        this.set('queryFilter', updatedQuery);
    },

    // Convenience method that combines updateQueryFilter() and populate().
    applyFilter: function(queryParam) {
        this.updateQueryFilter(queryParam);
        this.populate();
    }
});

/* The search form. */
App.ProjectSearchFormView = Em.View.extend({
    tagName : 'form',
    templateName: 'project-search-form',
    classNames: ['search'],

    submit: function(event) {
        // inspired by http://jsfiddle.net/dgeb/RBbpS/
        event.preventDefault();
        App.projectSearchController.submitTextSearchForm(event);
//        We want to do something generic like this:
//
//        this.get('controller').update();
//        this.get('controller').addPerson(this.getPath('textField.value'));
//        this.setPath('textField.value', null);
//
//        This can't be done right now because the tplhandlebars doesn't support
//        anonymous handlebar scripts which means we can't define a controllerBinding
//        in the template:
//
//          {{#view App.ProjectSearchFormView controllerBinding="App.projectSearchController"}}
//
//        We can either (1) add anonymous handlebar scripts into the tplhandlebars django
//        template tag or (2) create the anonymous handlebar scripts in HTML and load
//        them manually. The best option is probably (2) because we want that functionality
//        for other reasons.

    }
});

App.progressBarAnimationMixin = Em.Mixin.create({
    donatedPercentage: function(){
        var project = this.get('content');
        if (project.money_asked) {
            return 100 * project.money_donated / project.money_asked;
        }
        return 0;
    }.property('content'),
    animateBar: function() {
        var p = this.get('donatedPercentage');
        var bar = this.$('.donated-bar');
        var label = this.$('.donated-text');
        // Only animate
        if (p > 0) {
            label.fadeTo(0,0);
            if (p < 40) {
                label.css({marginRight: '-1px', marginLeft: p + '%'});
                label.removeClass('right');
                label.addClass('left');
            } else {
                var pr = 100 - p;
                label.css({marginLeft: '-1px',marginRight : pr + '%'});
                label.removeClass('left');
                label.addClass('right');
            }
            bar.animate({width: p +'%'}, 1000,
                function(){label.fadeTo(200, 1);}
            );
        }
    }.observes('donatedPercentage')

});

/* The search results. */
App.ProjectSearchResultsSectionView  = Em.View.extend({
    tagName: 'div',
    classNames: ['lightgray', 'section', 'results'],
    templateName: 'project-search-results-section'
});

App.ProjectSearchResultsNextView = Em.View.extend({
    tagName: 'div',
    classNames: ['paginator', 'next'],

    nextBinding: 'App.projectSearchController.nextSearchResult',
    //TODO: Do hidden smarter (template?)
    classNameBindings:['disabled'],

    disabled: function () {
        if (this.next == null) return true;
        return false;
    }.property('next'),

    click: function(event) {
        if (this.next) {
            // TODO: Can we make this independent of the controller?
            App.projectSearchController.clickPrevNext(event, this.next);
        }
    }
});

App.ProjectSearchResultsPreviousView = Em.View.extend({
    tagName: 'div',
    classNames: ['paginator', 'previous'],

    previousBinding: 'App.projectSearchController.previousSearchResult',
    // TODO: Do hidden smarter (template?)
    classNameBindings: ['disabled'],

    disabled: function() {
        if (this.previous == null) return true;
        return false;
    }.property('previous'),

    click: function(event) {
        if (this.previous) {
            // TODO: Can we make this independent of the controller?
            App.projectSearchController.clickPrevNext(event, this.previous);
        }
    }
});

App.ProjectSearchResultsView = Em.CollectionView.extend({
    tagName: 'ul',
    templateName: 'project-search-results',
    classNames: ['row'],
    emptyView: Ember.View.extend({
      templateName: 'project-no-results'
    }),
    contentBinding: 'App.projectSearchController.content',
    itemViewClass: 'App.ProjectPreviewView'
});

App.ProjectPreviewView = Em.View.extend(App.progressBarAnimationMixin, {
    tagName: 'li',
    templateName: 'project-preview',
    classNames: ['project-mid', 'grid_1', 'column'],
    // Trigger animateBar manualy because it doesn't get
    // called properly (content trigger on donatedPercentage doesn't work)
    didInsertElement: function() {
        this._super();
        this.animateBar();
    },
});

// Views that change the query filter.
App.FilterSelect = Em.Select.extend({
    change: function(event){
        event.preventDefault();
        var queryParam = {};
        // TODO Add support for multi-selection by looping through selection object.
        queryParam[this.get('name')] = this.get('selection').id;
        this.get('controller').applyFilter(queryParam);
    }
});

App.ProjectRegionSelect = App.FilterSelect.extend({
    controller: App.projectSearchController,
    name: "regions",
    viewName: "ProjectRegionSelect",
    contentBinding: "App.projectSearchController.filteredRegions",
    optionLabelPath: "content.name",
    optionValuePath: "content.id",
    prompt: "Region"
});

App.ProjectCountrySelect = App.FilterSelect.extend({
    controller: App.projectSearchController,
    name: "countries",
    viewName: "ProjectCountrySelect",
    contentBinding: "App.projectSearchController.filteredCountries",
    optionLabelPath: "content.name",
    optionValuePath: "content.id",
    prompt: "Country"
});

App.ProjectStartView = Em.View.extend({
    templateName:'project-start'
});

/*
 * Project Detail
 */
App.projectModel = Em.Object.create({
    url:'projectdetail'
});

App.projectDetailController = Em.ObjectController.create({
    content: null,
    activePane: 'pictures',

    populate: function(slug){
        var controller = this;
        require(['app/data_source'], function(){
            App.dataSource.get('projectdetail/' + slug, {}, function(data) {
                controller.set('content', data);
            });
        })
    }
});

App.ProjectDetailView = Em.View.extend({
    contentBinding: 'App.projectDetailController',
    templateName:'project-detail',
    classNames: ['lightgreen', 'section']
});

App.ProjectStatsView = Em.View.extend({
    contentBinding: 'App.projectDetailController',
    templateName:'project-stats'
});

App.ProjectProgressBarView = Em.View.extend(App.progressBarAnimationMixin, {
    contentBinding: 'App.projectDetailController.content',
    templateName:'project-progress-bar',
});
/*
 * Media Viewer
 */

App.projectMediaViewerController = Em.ObjectController.create({
    contentBinding: 'App.projectDetailController',
    activePane: 'pictures',
});

App.ProjectMediaView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media',
});

App.ProjectMediaPicturesView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-pictures'
});

App.ProjectMediaPlanView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-plan'
});

App.ProjectMediaVideosView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-videos'
});

// MediaViewer: panes with media come after this
App.ProjectMediaMapView = Em.View.extend({
    // Explicit call to App.projectDetailController.content
    // because it doesn't work with App.projectMediaViewerController
    //contentBinding: 'App.projectMediaViewerController',
    contentBinding: 'App.projectDetailController.content',
    templateName:'project-media-map',
    map: {},
    loadMap: function(){
        var view = this;
        // Delayed loading here so we're sure it's rendered
        // Otherwise gmap might cough
        var project = view.get('content');
        this.map = new BlueMap('bigmap', {
            slug: project.slug,
            latitude: project.latitude,
            longitude: project.longitude
        }).showProjects();
    }.observes('content'),
});

// Used as Mediaviewer panes
App.ProjectMediaPaneView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    activePaneBinding: 'App.projectMediaViewerController.activePane',
    classNames: ['pane'],
    classNameBindings: ['active'],
    active: function(){
        if (this.get('activePane') == this.get('value')) {
            return true;
        }
        return false;
    }.property('activePane'),
    tagName: 'div',
});

// Used for Mediaviewer buttons to switch panes
App.ProjectMediaButtonView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    activePaneBinding: 'App.projectMediaViewerController.activePane',
    classNameBindings: ['active'],
    tagName: 'button',
    active: function(){
        if (this.get('activePane') == this.get('value')) {
            return true;
        }
        return false;
    }.property('activePane'),
    classNameBindings: ['active'],
    click: function(){
        this.set('activePane', this.get('value'));
    }
});

<commit_msg>Display the selected region and country in select boxes.<commit_after>/*
 * Project Search and List
 */
// FIXME: We're not using this Model right now.
App.ProjectPreviewModel = Em.Object.extend({
});

App.ProjectModel = Em.Object.extend({
});

App.IndexOfObjPropMixin = Ember.Mixin.create({
    // Inspired by Ember's Array.indexOf():
    indexOfObjectProperty: function(object, property, startAt) {
        var idx, len = this.get('length');

        if (startAt === undefined) startAt = 0;
        if (startAt < 0) startAt += len;

        for(idx=startAt;idx<len;idx++) {
            if (this.objectAt(idx)[property] == object[property]) return idx ;
        }
        return -1;
    }
});

App.SelectFilterController = Em.ArrayController.extend(App.IndexOfObjPropMixin, {
    content: Em.A(),
    sortProperties: ['name'],
    updateContent: function (data) {
        var updatedList = Em.ArrayProxy.create(App.IndexOfObjPropMixin, {content: Em.A(data)});

        // Remove items not in current list.
        var currentListCopy = this.slice(0);
        var numRemovals = 0;
        Em.beginPropertyChanges();
        currentListCopy.forEach(function (item, index, enumerable) {
            if (this.updatedList.indexOfObjectProperty(item, 'id') < 0) {
                // Item not in updated list; remove it.
                this.currentList.removeAt(index - numRemovals);
                numRemovals++;
            }
        }, {updatedList: updatedList, currentList: this, numRemovals: numRemovals});

        // Add items not in current list and update counts for existing items.
        updatedList.forEach(function (item, index, enumerable) {
            var idx = this.indexOfObjectProperty(item, 'id');
            if (idx >= 0) {
                // Update the count.
                this.objectAt(idx)['count'] = item['count'];
            } else {
                // Item not in current list; add it.
                this.pushObject(item)

            }
        }, this);
        Em.endPropertyChanges();

        // Clear out some variables we're done with.
        updatedList = null;
        currentListCopy = null;
        numRemovals = null;
    }
});

App.projectSearchController = Em.ArrayController.create({
    init: function() {
        this._super();
        this.populate();
    },

    // The saved queryFilter state.
    queryFilter: {phases: 'fund'}, // FIXME Limit projects to fund phase until projects are cleaned up.

    // Project preview search results and meta data from tastypie:
    content: [],
    contentMeta: {},

    // List of regions and countries returned from the search form filter. These
    // are content bindings for the filter elements (e.g. select) in the Views.
    filteredRegions: App.SelectFilterController.create(),
    filteredCountries: App.SelectFilterController.create(),

    // Query filters that react after a button click or enter from the keyboard:
    searchText: "",
    submitTextSearchForm: function(event) {
        var searchText = this.get('searchText');
        this.updateQueryFilter({'text': searchText});
        this.populate();
    },

    // Pagination:
    nextSearchResult: function() {
        var meta  = this.get('contentMeta');
        var next = (RegExp('offset=' + '(.+?)(&|$)').exec(meta.next)||[,null])[1]
        return next;
    }.property('contentMeta'),

    previousSearchResult: function() {
        var meta  = this.get('contentMeta');
        var previous = (RegExp('offset=' + '(.+?)(&|$)').exec(meta.previous)||[,null])[1]
        return previous;
    }.property('contentMeta'),

    // We can't use an observer for this because it will loop continually.
    clickPrevNext: function(event, offset) {
        this.updateQueryFilter({'offset': offset});
        this.populate();
    },

    // Loads the data from the server based on the current value of queryFilter.
    populate: function() {
        var controller = this;
        require(['app/data_source'], function(){
            var query = controller.get('queryFilter');

            // Get the project preview data using the current queryFilter.
            // We're limiting the number of returned items to 4.
            var projectPreviewQuery = $.extend({limit: 4}, query);
            App.dataSource.get('projectpreview', projectPreviewQuery, function(data) {
                controller.set('content', data['objects']);
                controller.set('contentMeta', data['meta']);
            });

            // Get the project search data using the same queryFilter.
            // Note that we're *not* limiting the number of returned items as above.
            App.dataSource.get('projectsearchform', query, function(data) {
                var objects = data['objects'];
                for (var i = 0; i < objects.length; i++) {
                    switch (objects[i].name) {
                        case "countries":
                            controller.get('filteredCountries').updateContent(objects[i].options);
                            break;
                        case "regions":
                            controller.get('filteredRegions').updateContent(objects[i].options);
                            break;
                        default:
                            break;
                    }
                }

            });

        });
    },

    // Updates the queryFilter based on the queryParam. Parameters  with a null
    // or empty value are removed from the filter list.
    updateQueryFilter: function(queryParam) {
        var query = this.get('queryFilter');
        var tempQuery = $.extend({}, query);

        // Delete the param if it's already in the queryFilter. This lets us update
        // the param or leave it deleted depending on what's requested.
        var i = 0;
        for (var property in queryParam) {
            if (i > 0) {
                // TODO: Support multiple queryFilter parameter updates.
                break;  // Only use the first property in queryParam.
            }
            if (undefined != tempQuery[property]) {
                delete tempQuery[property];
            }
            i++;
        }

        // The offset parameter needs to be deleted on every filter update.
        if (undefined != tempQuery['offset']) {
            delete tempQuery['offset'];
        }

        // Create an updated queryFilter.
        if (queryParam[property] == null || queryParam[property].length == 0 ) {
            // QueryParam[property] is null or empty string. Ignore it.
            var updatedQuery = tempQuery;
        } else {
            var updatedQuery = $.extend(queryParam, tempQuery);
        }

        // Set the updated queryFilter and populate the data.
        this.set('queryFilter', updatedQuery);
    },

    // Convenience method that combines updateQueryFilter() and populate().
    applyFilter: function(queryParam) {
        this.updateQueryFilter(queryParam);
        this.populate();
    }
});

/* The search form. */
App.ProjectSearchFormView = Em.View.extend({
    tagName : 'form',
    templateName: 'project-search-form',
    classNames: ['search'],

    submit: function(event) {
        // inspired by http://jsfiddle.net/dgeb/RBbpS/
        event.preventDefault();
        App.projectSearchController.submitTextSearchForm(event);
//        We want to do something generic like this:
//
//        this.get('controller').update();
//        this.get('controller').addPerson(this.getPath('textField.value'));
//        this.setPath('textField.value', null);
//
//        This can't be done right now because the tplhandlebars doesn't support
//        anonymous handlebar scripts which means we can't define a controllerBinding
//        in the template:
//
//          {{#view App.ProjectSearchFormView controllerBinding="App.projectSearchController"}}
//
//        We can either (1) add anonymous handlebar scripts into the tplhandlebars django
//        template tag or (2) create the anonymous handlebar scripts in HTML and load
//        them manually. The best option is probably (2) because we want that functionality
//        for other reasons.

    }
});

App.progressBarAnimationMixin = Em.Mixin.create({
    donatedPercentage: function(){
        var project = this.get('content');
        if (project.money_asked) {
            return 100 * project.money_donated / project.money_asked;
        }
        return 0;
    }.property('content'),
    animateBar: function() {
        var p = this.get('donatedPercentage');
        var bar = this.$('.donated-bar');
        var label = this.$('.donated-text');
        // Only animate
        if (p > 0) {
            label.fadeTo(0,0);
            if (p < 40) {
                label.css({marginRight: '-1px', marginLeft: p + '%'});
                label.removeClass('right');
                label.addClass('left');
            } else {
                var pr = 100 - p;
                label.css({marginLeft: '-1px',marginRight : pr + '%'});
                label.removeClass('left');
                label.addClass('right');
            }
            bar.animate({width: p +'%'}, 1000,
                function(){label.fadeTo(200, 1);}
            );
        }
    }.observes('donatedPercentage')

});

/* The search results. */
App.ProjectSearchResultsSectionView  = Em.View.extend({
    tagName: 'div',
    classNames: ['lightgray', 'section', 'results'],
    templateName: 'project-search-results-section'
});

App.ProjectSearchResultsNextView = Em.View.extend({
    tagName: 'div',
    classNames: ['paginator', 'next'],

    nextBinding: 'App.projectSearchController.nextSearchResult',
    //TODO: Do hidden smarter (template?)
    classNameBindings:['disabled'],

    disabled: function () {
        if (this.next == null) return true;
        return false;
    }.property('next'),

    click: function(event) {
        if (this.next) {
            // TODO: Can we make this independent of the controller?
            App.projectSearchController.clickPrevNext(event, this.next);
        }
    }
});

App.ProjectSearchResultsPreviousView = Em.View.extend({
    tagName: 'div',
    classNames: ['paginator', 'previous'],

    previousBinding: 'App.projectSearchController.previousSearchResult',
    // TODO: Do hidden smarter (template?)
    classNameBindings: ['disabled'],

    disabled: function() {
        if (this.previous == null) return true;
        return false;
    }.property('previous'),

    click: function(event) {
        if (this.previous) {
            // TODO: Can we make this independent of the controller?
            App.projectSearchController.clickPrevNext(event, this.previous);
        }
    }
});

App.ProjectSearchResultsView = Em.CollectionView.extend({
    tagName: 'ul',
    templateName: 'project-search-results',
    classNames: ['row'],
    emptyView: Em.View.extend({
      templateName: 'project-no-results'
    }),
    contentBinding: 'App.projectSearchController.content',
    itemViewClass: 'App.ProjectPreviewView'
});

App.ProjectPreviewView = Em.View.extend(App.progressBarAnimationMixin, {
    tagName: 'li',
    templateName: 'project-preview',
    classNames: ['project-mid', 'grid_1', 'column'],
    // Trigger animateBar manualy because it doesn't get
    // called properly (content trigger on donatedPercentage doesn't work)
    didInsertElement: function() {
        this._super();
        this.animateBar();
    }
});

// Views that change the query filter.
App.FilterSelect = Em.Select.extend({
    change: function(event){
        event.preventDefault();
        var queryParam = {};
        // TODO Add support for multi-selection by looping through selection object.
        var selection = this.get('selection')
        queryParam[this.get('name')] = selection != null ? selection.id : null;
        this.get('controller').applyFilter(queryParam);
    }
});

App.ProjectRegionSelect = App.FilterSelect.extend({
    controller: App.projectSearchController,
    name: "regions",
    viewName: "ProjectRegionSelect",
    contentBinding: "App.projectSearchController.filteredRegions",
    optionLabelPath: "content.name",
    optionValuePath: "content.id",
    prompt: "Region"
});

App.ProjectCountrySelect = App.FilterSelect.extend({
    controller: App.projectSearchController,
    name: "countries",
    viewName: "ProjectCountrySelect",
    contentBinding: "App.projectSearchController.filteredCountries",
    optionLabelPath: "content.name",
    optionValuePath: "content.id",
    prompt: "Country"
});

App.ProjectStartView = Em.View.extend({
    templateName:'project-start'
});

/*
 * Project Detail
 */
App.projectModel = Em.Object.create({
    url:'projectdetail'
});

App.projectDetailController = Em.ObjectController.create({
    content: null,
    activePane: 'pictures',

    populate: function(slug){
        var controller = this;
        require(['app/data_source'], function(){
            App.dataSource.get('projectdetail/' + slug, {}, function(data) {
                controller.set('content', data);
            });
        })
    }
});

App.ProjectDetailView = Em.View.extend({
    contentBinding: 'App.projectDetailController',
    templateName:'project-detail',
    classNames: ['lightgreen', 'section']
});

App.ProjectStatsView = Em.View.extend({
    contentBinding: 'App.projectDetailController',
    templateName:'project-stats'
});

App.ProjectProgressBarView = Em.View.extend(App.progressBarAnimationMixin, {
    contentBinding: 'App.projectDetailController.content',
    templateName:'project-progress-bar'
});

/*
 * Media Viewer
 */
App.projectMediaViewerController = Em.ObjectController.create({
    contentBinding: 'App.projectDetailController',
    activePane: 'pictures'
});

App.ProjectMediaView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media'
});

App.ProjectMediaPicturesView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-pictures'
});

App.ProjectMediaPlanView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-plan'
});

App.ProjectMediaVideosView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    templateName:'project-media-videos'
});

// MediaViewer: panes with media come after this
App.ProjectMediaMapView = Em.View.extend({
    // Explicit call to App.projectDetailController.content
    // because it doesn't work with App.projectMediaViewerController
    //contentBinding: 'App.projectMediaViewerController',
    contentBinding: 'App.projectDetailController.content',
    templateName:'project-media-map',
    map: {},
    loadMap: function(){
        var view = this;
        // Delayed loading here so we're sure it's rendered
        // Otherwise gmap might cough
        var project = view.get('content');
        this.map = new BlueMap('bigmap', {
            slug: project.slug,
            latitude: project.latitude,
            longitude: project.longitude
        }).showProjects();
    }.observes('content')
});

// Used as Mediaviewer panes
App.ProjectMediaPaneView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    activePaneBinding: 'App.projectMediaViewerController.activePane',
    classNames: ['pane'],
    classNameBindings: ['active'],
    active: function(){
        if (this.get('activePane') == this.get('value')) {
            return true;
        }
        return false;
    }.property('activePane'),
    tagName: 'div'
});

// Used for Mediaviewer buttons to switch panes
App.ProjectMediaButtonView = Em.View.extend({
    contentBinding: 'App.projectMediaViewerController',
    activePaneBinding: 'App.projectMediaViewerController.activePane',
    classNameBindings: ['active'],
    tagName: 'button',
    active: function(){
        if (this.get('activePane') == this.get('value')) {
            return true;
        }
        return false;
    }.property('activePane'),
    classNameBindings: ['active'],
    click: function(){
        this.set('activePane', this.get('value'));
    }
});

<|endoftext|>


<commit_before>// One thing I should do is to create a special ZMT icon with a ZMT popup that looks cool.

// create base tile layer variable for map
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
	attribution: '© OpenStreetMap',
	minZoom:0,
	maxZoom: 19
}),
stamenToner = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.{ext}', {
	attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
	subdomains: 'abcd',
	minZoom: 0,
	maxZoom: 20,
	ext: 'png'
});

// set up map, view and base layer
var myMap;
myMap = new L.Map('mapid', {
	center: {lat: 0, lng: 8.8460},
	zoom: 2,
	layers: osm
});

// create layer group and add base tile layer to layer group
var baseLayers = {
	"Open Street Maps": osm,
	"Black and White": stamenToner
};
L.control.layers(baseLayers).addTo(myMap);

// create object to add datasets too
datasets = [];

// add values from the html elements to the dataset list
// add "ds" to the numbers to that they can be called as variables
$("input#datasetCheckbox").each(function() {
	value = "ds"+$(this).val();
	datasets.push(value);
});

// add layers to variables stored in dataset list
// maybe I can use this function on the meta data page load.
function datasetToggle( value ) {
	var dsUrl = "/load_dataset/" + value,
	dsValue = "ds" + value,
	ds = datasets[dsValue];

	// if map already has dataset, remove it, otherwise, add it
	if (myMap.hasLayer(ds)) {
		myMap.removeLayer(ds);
	} else {
	// add if/then for kml/geojson/csv ... how do i check?
		//datasets[dsValue] = omnivore.kml(url=dsUrl);
		datasets[dsValue] = omnivore.geojson(url=dsUrl)
		.on('ready', function(){
			datasets[dsValue].eachLayer(function(layer){
				var popupContent = [];
				for (var key in layer.feature.properties) {
					popupContent.push(key + ": " + layer.feature.properties[key]);
				}
				if (layer.feature.geometry.type === "Point") {
					popupContent.push("Latitude: " + layer.feature.geometry.coordinates[1]);
					popupContent.push("Longitude: " + layer.feature.geometry.coordinates[0]);
				}
				layer.bindPopup(popupContent.join("
"));
			});
			myMap.fitBounds(datasets[dsValue].getBounds());
		})
		.addTo(myMap);
	};
};

// call datasetToggle function on list item click
$("input#datasetCheckbox").on("click", function( event ) {
	var value = event.target.value
	datasetToggle( value );
});

// call datasetToggle function on page load for meta data page.
// maybe this should just be added as a separate script to the page itself
//$(document).on(ready, function( pk )
//	datasetToggle( pk );
//});

//function clearAllLayers() {
//	myMap.eachLayer(function (layer) {
//		myMap.removeLayer(layer);
//	});
//}

// This button works to resize things, but the map is messed up when I use it.
// The map needs to be resized, or reinitiated, or something.
L.easyButton('',
	function() {
		$("#sidebar").toggle();
		$("#main_map").toggleClass("col-sm-6 col-md-8 col-lg-9");
	}
).addTo(myMap);
<commit_msg>need to add tests<commit_after>// One thing I should do is to create a special ZMT icon with a ZMT popup that looks cool.

// create base tile layer variable for map
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
	attribution: '© OpenStreetMap',
	minZoom:0,
	maxZoom: 19
}),
stamenToner = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.{ext}', {
	attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
	subdomains: 'abcd',
	minZoom: 0,
	maxZoom: 20,
	ext: 'png'
});

// set up map, view and base layer
var myMap;
myMap = new L.Map('mapid', {
	center: {lat: 0, lng: 8.8460},
	zoom: 2,
	layers: osm
});

// create layer group and add base tile layer to layer group
var baseLayers = {
	"Open Street Maps": osm,
	"Black and White": stamenToner
};
L.control.layers(baseLayers).addTo(myMap);

// create object to add datasets too
datasets = [];

// add values from the html elements to the dataset list
// add "ds" to the numbers to that they can be called as variables
$("input#datasetCheckbox").each(function() {
	value = "ds"+$(this).val();
	datasets.push(value);
});

// add layers to variables stored in dataset list
// maybe I can use this function on the meta data page load.
function datasetToggle( value ) {
	var dsUrl = "/load_dataset/" + value,
	dsValue = "ds" + value,
	ds = datasets[dsValue];

	// if map already has dataset, remove it, otherwise, add it
	if (myMap.hasLayer(ds)) {
		myMap.removeLayer(ds);
	} else {
	// add if/then for kml/geojson/csv ... how do i check?
		//datasets[dsValue] = omnivore.kml(url=dsUrl);
		datasets[dsValue] = omnivore.geojson(url=dsUrl)
		.on('ready', function(){
			datasets[dsValue].eachLayer(function(layer){
				var popupContent = [];
				for (var key in layer.feature.properties) {
					popupContent.push(
					"" + key + "" + ": " + layer.feature.properties[key]
					);
				}
				if (layer.feature.geometry.type === "Point") {
					popupContent.push("Latitude: " + layer.feature.geometry.coordinates[1]);
					popupContent.push("Longitude: " + layer.feature.geometry.coordinates[0]);
				}
				layer.bindPopup(popupContent.join("
"));
			});
			myMap.fitBounds(datasets[dsValue].getBounds());
		})
		.addTo(myMap);
	};
};

// call datasetToggle function on list item click
$("input#datasetCheckbox").on("click", function( event ) {
	var value = event.target.value
	datasetToggle( value );
});

// call datasetToggle function on page load for meta data page.
// maybe this should just be added as a separate script to the page itself
//$(document).on(ready, function( pk )
//	datasetToggle( pk );
//});

//function clearAllLayers() {
//	myMap.eachLayer(function (layer) {
//		myMap.removeLayer(layer);
//	});
//}

// This button works to resize things, but the map is messed up when I use it.
// The map needs to be resized, or reinitiated, or something.
L.easyButton('',
	function(btn, myMap) {
		$("#sidebar").toggle();
		$("#main_map").toggleClass("col-sm-6 col-md-8 col-lg-9");
		myMap._resetView(myMap.getCenter(), myMap.getZoom(), true);
	}
).addTo(myMap);
<|endoftext|>