<commit_before>var AtlasTemplate = require("../views/templates/atlas");
var AtlasMapTemplate = require("../views/templates/atlasmap");
var LineItemTemplate = require("../views/templates/line_item");

module.exports = Backbone.View.extend({
    "last-z-index": 10,
    "currentZoomLevel": 1.0,
    "lastPosition": {
        "top": 0, "left": 0
    },

    events: {
        "click a.refresh-loaded": function() {
            _.each(this.$el.find(".atlas-map"), this.loadMapData);
        },
        "click a.zoom-in": function() {
            this.currentZoomLevel = this.currentZoomLevel * 1.15;
            this.zoom(this.currentZoomLevel);
        },
        "click a.zoom-out": function() {
            this.currentZoomLevel = this.currentZoomLevel * 0.85;
            this.zoom(this.currentZoomLevel);
        },
        "click a.zoom-home": function() {
            this.currentZoomLevel = 1.0;
            this.zoom(this.currentZoomLevel);
        },
        "click a.resize-item": function(e) {
            var li = $(e.target).parents("li");
            if (li.hasClass("active")) {
                this.$el.find(".atlas-map").resizable("destroy");
            } else {
                this.$el.find(".atlas-map").resizable({ "ghost":true});
            }
            li.toggleClass("active");
        },
        "click a.scale-item": function(e) {
            var li = $(e.target).parents("li");
            if (li.hasClass("active")) {
                this.$el.find(".atlas-map").resizable("destroy");
            } else {
                this.$el.find(".atlas-map").resizable({
                    "handles":"n, e, s, w, ne, se, sw, nw",
                    "aspectRatio":true,
                    "animateEasing":"linear",
                    "stop":function (stopE, ui) {
                        var scaleLevel = (ui.size.width / ui.originalSize.width) * 90;
                        $(stopE.target).children().effect("scale", {"scale":"both", "percent":scaleLevel}, 1000);
                    }
                });
            }
            li.toggleClass("active");
        },
        "click a.minimize-me": function(e) {
            this.closeMap($(e.target).parents(".atlas-map"));
        },
        "click a.refresh-me": function(e) {
            this.loadMapData($(e.target).parents(".atlas-map"));
        },
        "click a.open-map": function(e) {
            var mapId = $(e.target).data("id");
            _.each(this.model.get("maps"), function(map) {
                if (_.isEqual(map.id, mapId)) {
                    map.isOpen = true;
                    this.appendAtlasMap(map);
                }
            }, this);
        },
        "click div.atlas-map": function(e) {
            var $target = $(e.target);
            if (!$target.hasClass("atlas-map")) {
                $target = $(e.target).parents(".atlas-map");
            }

            $target.css("z-index", this.nextZindex());
        }
    },

    initialize: function(options) {
        _.extend(this, options);
        _.bindAll(this, "initMaps", "appendAtlasMap", "loadMapData", "loadMapContents", "viewsByUri", "closeMap", "zoom");
        _.bindAll(this, "loadCancerList", "initGeneTypeahead", "nextZindex", "nextPosition", "currentState");

        this.$el.html(AtlasTemplate());
        this.$el.find(".atlas-zoom").draggable({ "scroll":true });

        $.ajax({ url:"svc/data/lookups/cancers", type:"GET", dataType:"text", success:this.loadCancerList });
        $.ajax({ url:"svc/data/lookups/genes", type:"GET", dataType:"text", success:this.initGeneTypeahead });

        qed.Sessions.Producers["atlas_maps"] = this;
        this.model.on("load", this.initMaps);
    },

    initMaps: function() {
        var maps = this.model.get("maps");
        _.each(_.sortBy(maps, "label"), function(map) {
            var lit = { "a_class": "open-map", "id": map.id, "label": map.label };
            if (map.disabled) {
                lit = { "li_class": "disabled", "id": map.id, "label": map.label };
            }
            this.$el.find(".maps-selector").append(LineItemTemplate(lit));
        }, this);

        if (qed.Sessions.Active) {
            var mapsFromSession = qed.Sessions.Active.get("atlas_maps");
            if (mapsFromSession) {
                maps = _.compact(_.map(mapsFromSession, function(mapFromSession) {
                    var matchedMap = _.find(maps, function(m) { return _.isEqual(m.id, mapFromSession.id); });
                    if (matchedMap) {
                        return _.extend(_.clone(matchedMap), mapFromSession, {
                            "assignedPosition": mapFromSession.position,
                            "assignedZindex": mapFromSession.zindex
                        });
                    }
                    return null;
                }));
            }
        }
        _.each(maps, function(map) {
            if (map.isOpen) this.appendAtlasMap(map);
        }, this);
    },

    appendAtlasMap: function(map) {
        var uid = Math.round(Math.random() * 10000);
        _.each(map.views, function(view, idx) {
            if (idx == 0) view["li_class"] = "active";
            view["uid"] = uid++;
        });

        if (!_.has(map, "position")) {
            map.assignedPosition = this.nextPosition();
        }

        if (!_.has(map, "zindex")) {
            map.assignedZindex = this.nextZindex();
        }

        this.$el.find(".atlas-zoom").append(AtlasMapTemplate(map));
        var $atlasMap = this.$el.find(".atlas-zoom").children().last();

        $atlasMap.find(".info-me").popover({
            "title": "Description",
            "trigger": "hover",
            "content": map.description
        });

        var _this = this;
        this.$el.find(".maps-btn").effect("transfer", {
            "to": $atlasMap,
            complete: function() {
                _this.loadMapData($atlasMap);
                $atlasMap.draggable({ "scroll":true });
            }
        }, 750);
    },

    loadMapData: function(atlasMap) {
        _.each($(atlasMap).find(".map-contents"), function(mc) {
            _.defer(this.loadMapContents, mc);
        }, this);
    },

    loadMapContents: function(contentContainer, view_options, query_options) {
        var $target = $(contentContainer);
        var source = $target.data("source");
        var view_name = $target.data("view");
        var linked_to = $target.data("linked");
        if (source && view_name) {
            var afn = function(link) {
                return $(link).data("id")
            };

            var cancerList = _.map(this.$el.find(".cancer-selector .active a"), afn);
            var geneList = _.map(this.$el.find(".gene-selector .item-remover"), afn);

            var v_options = _.extend({ "genes": geneList, "cancers": cancerList, "hideSelector": true }, view_options || {});
            var q_options = _.extend({ "gene": geneList, "cancer": cancerList }, query_options || {});

            this.viewsByUri($target, source, view_name, v_options, q_options);
        }
    },

    viewsByUri: function(targetEl, uri, view_name, options, query) {
//        console.log("viewsByUri(" + uri + "," + view_name + "," + JSON.stringify(options) + "," + JSON.stringify(query) + ")");
        var ViewClass = qed.Views[view_name];
        if (ViewClass) {
            var parts = uri.split("/");
            var data_root = parts[0];
            var analysis_id = parts[1];
            var dataset_id = parts[2];
            var model_unit = qed.Datamodel.get(data_root)[analysis_id];
            var catalog = model_unit.catalog;
            var catalog_unit = catalog[dataset_id];
            var modelName = model_unit.model || catalog_unit.model;
            var serviceUri = catalog_unit.service || model_unit.service || "data/" + uri;
            var Model = qed.Models[modelName || "Default"];

            var model_optns = _.extend(options, {
                "data_uri": "svc/" + serviceUri,
                "analysis_id": analysis_id,
                "dataset_id": dataset_id,
                "model_unit": model_unit,
                "catalog_unit": catalog_unit
            });
            // TODO: Determine which views need annotations
            // qed.FetchAnnotations(dataset_id);

            var model = new Model(model_optns);
            _.defer(function() {
                model.fetch({
                    "data": query,
                    "traditional": true,
                    success:function () {
                        model.trigger("load");
                    }
                });
            });

            var view_options = _.extend({"model":model}, (model_unit.view_options || {}), (options || {}));

            console.log("viewsByUri(" + uri + "," + view_name + "):loading view");
            var view = new ViewClass(view_options);
            $(targetEl).html(view.render().el);
        }
    },

    closeMap: function(atlasMap) {
        $(atlasMap).effect("transfer", {
            "to": this.$el.find(".maps-btn"),
            complete: function() {
                $(atlasMap).remove();
            }
        }, 750);
    },

    zoom: function(zoomLevel) {
        this.$el.find(".atlas-zoom").zoomTo({
            "duration":1000,
            "scalemode": "both",
            "easing": "ease",
            "nativeanimation": true,
            "root": this.$el.find(".atlas-canvas"),
            "closeclick": false,
            "targetsize":zoomLevel
        });
    },

    loadCancerList: function(txt) {
        var cancerList = txt.trim().split("\n");
        var UL = this.$el.find(".cancer-selector");
        _.each(cancerList, function(cancer) {
            UL.append(LineItemTemplate({"li_class":"active","a_class":"toggle-active","id":cancer,"label":cancer}));
        });

        UL.find(".toggle-active").click(function(e) {
            $(e.target).parent().toggleClass("active");
        });

        UL.sortable();
    },

    initGeneTypeahead: function(txt) {
        var genelist = txt.trim().split("\n");

        var UL = this.$el.find(".gene-selector");
        this.$el.find(".genes-typeahead").typeahead({
            source:function (q, p) {
                p(_.compact(_.flatten(_.map(q.toLowerCase().split(" "), function (qi) {
                    return _.map(genelist, function (geneitem) {
                        if (geneitem.toLowerCase().indexOf(qi) >= 0) return geneitem;
                    });
                }))));
            },

            updater:function (gene) {
                UL.append(LineItemTemplate({ "label":gene, "id":gene, "a_class":"item-remover", "i_class":"icon-trash" }));
                UL.find(".item-remover").click(function(e) {
                    $(e.target).parent().remove();
                });
                return "";
            }
        });

        UL.find(".item-remover").click(function(e) {
            $(e.target).parent().remove();
        });

        UL.sortable();
    },

    nextZindex: function() {
        var nextZindex = 1 + this["last-z-index"];
        this["last-z-index"] = nextZindex;
        return nextZindex;
    },

    nextPosition: function() {
        var lastPos = {
            "left": this.lastPosition.left + 50,
            "top": this.lastPosition.top + 50
        };
        this.lastPosition = lastPos;
        return lastPos;
    },

    currentState:function () {
        return _.map(this.$el.find(".atlas-map"), function (map) {
            var mapid = $(map).data("mapid");
            var top = map.style["top"].replace("px", "");
            var left = map.style["left"].replace("px", "");
            var zindex = map.style["z-index"];
            return { "id":mapid, "isOpen":true, "position":{ "top":top, "left":left }, "zindex": zindex };
        });
    }
});<commit_msg>added cancers and genes to session<commit_after>var AtlasTemplate = require("../views/templates/atlas");
var AtlasMapTemplate = require("../views/templates/atlasmap");
var LineItemTemplate = require("../views/templates/line_item");

module.exports = Backbone.View.extend({
    "last-z-index": 10,
    "currentZoomLevel": 1.0,
    "lastPosition": {
        "top": 0, "left": 0
    },

    events: {
        "click a.refresh-loaded": function() {
            _.each(this.$el.find(".atlas-map"), this.loadMapData);
        },
        "click a.zoom-in": function() {
            this.currentZoomLevel = this.currentZoomLevel * 1.15;
            this.zoom(this.currentZoomLevel);
        },
        "click a.zoom-out": function() {
            this.currentZoomLevel = this.currentZoomLevel * 0.85;
            this.zoom(this.currentZoomLevel);
        },
        "click a.zoom-home": function() {
            this.currentZoomLevel = 1.0;
            this.zoom(this.currentZoomLevel);
        },
        "click a.resize-item": function(e) {
            var li = $(e.target).parents("li");
            if (li.hasClass("active")) {
                this.$el.find(".atlas-map").resizable("destroy");
            } else {
                this.$el.find(".atlas-map").resizable({ "ghost":true});
            }
            li.toggleClass("active");
        },
        "click a.scale-item": function(e) {
            var li = $(e.target).parents("li");
            if (li.hasClass("active")) {
                this.$el.find(".atlas-map").resizable("destroy");
            } else {
                this.$el.find(".atlas-map").resizable({
                    "handles":"n, e, s, w, ne, se, sw, nw",
                    "aspectRatio":true,
                    "animateEasing":"linear",
                    "stop":function (stopE, ui) {
                        var scaleLevel = (ui.size.width / ui.originalSize.width) * 90;
                        $(stopE.target).children().effect("scale", {"scale":"both", "percent":scaleLevel}, 1000);
                    }
                });
            }
            li.toggleClass("active");
        },
        "click a.minimize-me": function(e) {
            this.closeMap($(e.target).parents(".atlas-map"));
        },
        "click a.refresh-me": function(e) {
            this.loadMapData($(e.target).parents(".atlas-map"));
        },
        "click a.open-map": function(e) {
            var mapId = $(e.target).data("id");
            _.each(this.model.get("maps"), function(map) {
                if (_.isEqual(map.id, mapId)) {
                    map.isOpen = true;
                    this.appendAtlasMap(map);
                }
            }, this);
        },
        "click div.atlas-map": function(e) {
            var $target = $(e.target);
            if (!$target.hasClass("atlas-map")) {
                $target = $(e.target).parents(".atlas-map");
            }

            $target.css("z-index", this.nextZindex());
        }
    },

    initialize: function(options) {
        _.extend(this, options);
        _.bindAll(this, "initMaps", "appendAtlasMap", "loadMapData", "loadMapContents", "viewsByUri", "closeMap", "zoom");
        _.bindAll(this, "loadCancerList", "initGeneTypeahead", "nextZindex", "nextPosition", "currentState");

        this.$el.html(AtlasTemplate());
        this.$el.find(".atlas-zoom").draggable({ "scroll":true });

        $.ajax({ url:"svc/data/lookups/cancers", type:"GET", dataType:"text", success:this.loadCancerList });
        $.ajax({ url:"svc/data/lookups/genes", type:"GET", dataType:"text", success:this.initGeneTypeahead });

        qed.Sessions.Producers["atlas_maps"] = this;
        this.model.on("load", this.initMaps);
    },

    initMaps: function() {
        var maps = this.model.get("maps");
        _.each(_.sortBy(maps, "label"), function(map) {
            var lit = { "a_class": "open-map", "id": map.id, "label": map.label };
            if (map.disabled) {
                lit = { "li_class": "disabled", "id": map.id, "label": map.label };
            }
            this.$el.find(".maps-selector").append(LineItemTemplate(lit));
        }, this);

        if (qed.Sessions.Active) {
            var session_atlas = qed.Sessions.Active.get("atlas_maps");
            if (session_atlas) {
                if (session_atlas.genes) {
                    var UL = this.$el.find(".gene-selector");
                    UL.empty();
                    _.each(session_atlas.genes, function(gene) {
                        UL.append(LineItemTemplate({ "label":gene, "id":gene, "a_class":"item-remover", "i_class":"icon-trash" }));
                        UL.find(".item-remover").click(function(e) {
                            $(e.target).parent().remove();
                        });
                    });
                }

                if (session_atlas.cancers) {
                    _.each(this.$el.find(".cancer-selector li"), function(li) {
                        var cancer = $(li).find("a").data("id");
                        if (session_atlas.cancers.indexOf(cancer) < 0) {
                            $(li).removeClass("active");
                        }
                    })
                }
                if (session_atlas.maps) {
                    maps = _.compact(_.map(session_atlas.maps, function(mapFromSession) {
                        var matchedMap = _.find(maps, function(m) { return _.isEqual(m.id, mapFromSession.id); });
                        if (matchedMap) {
                            return _.extend(_.clone(matchedMap), mapFromSession, {
                                "assignedPosition": mapFromSession.position,
                                "assignedZindex": mapFromSession.zindex
                            });
                        }
                        return null;
                    }));
                }
            }
        }
        _.each(maps, function(map) {
            if (map.isOpen) this.appendAtlasMap(map);
        }, this);
    },

    appendAtlasMap: function(map) {
        var uid = Math.round(Math.random() * 10000);
        _.each(map.views, function(view, idx) {
            if (idx == 0) view["li_class"] = "active";
            view["uid"] = uid++;
        });

        if (!_.has(map, "position")) {
            map.assignedPosition = this.nextPosition();
        }

        if (!_.has(map, "zindex")) {
            map.assignedZindex = this.nextZindex();
        }

        this.$el.find(".atlas-zoom").append(AtlasMapTemplate(map));
        var $atlasMap = this.$el.find(".atlas-zoom").children().last();

        $atlasMap.find(".info-me").popover({
            "title": "Description",
            "trigger": "hover",
            "content": map.description
        });

        var _this = this;
        this.$el.find(".maps-btn").effect("transfer", {
            "to": $atlasMap,
            complete: function() {
                _this.loadMapData($atlasMap);
                $atlasMap.draggable({ "scroll":true });
            }
        }, 750);
    },

    loadMapData: function(atlasMap) {
        _.each($(atlasMap).find(".map-contents"), function(mc) {
            _.defer(this.loadMapContents, mc);
        }, this);
    },

    loadMapContents: function(contentContainer, view_options, query_options) {
        var $target = $(contentContainer);
        var source = $target.data("source");
        var view_name = $target.data("view");
        var linked_to = $target.data("linked");
        if (source && view_name) {
            var afn = function(link) {
                return $(link).data("id")
            };

            var cancerList = _.map(this.$el.find(".cancer-selector .active a"), afn);
            var geneList = _.map(this.$el.find(".gene-selector .item-remover"), afn);

            var v_options = _.extend({ "genes": geneList, "cancers": cancerList, "hideSelector": true }, view_options || {});
            var q_options = _.extend({ "gene": geneList, "cancer": cancerList }, query_options || {});

            this.viewsByUri($target, source, view_name, v_options, q_options);
        }
    },

    viewsByUri: function(targetEl, uri, view_name, options, query) {
//        console.log("viewsByUri(" + uri + "," + view_name + "," + JSON.stringify(options) + "," + JSON.stringify(query) + ")");
        var ViewClass = qed.Views[view_name];
        if (ViewClass) {
            var parts = uri.split("/");
            var data_root = parts[0];
            var analysis_id = parts[1];
            var dataset_id = parts[2];
            var model_unit = qed.Datamodel.get(data_root)[analysis_id];
            var catalog = model_unit.catalog;
            var catalog_unit = catalog[dataset_id];
            var modelName = model_unit.model || catalog_unit.model;
            var serviceUri = catalog_unit.service || model_unit.service || "data/" + uri;
            var Model = qed.Models[modelName || "Default"];

            var model_optns = _.extend(options, {
                "data_uri": "svc/" + serviceUri,
                "analysis_id": analysis_id,
                "dataset_id": dataset_id,
                "model_unit": model_unit,
                "catalog_unit": catalog_unit
            });
            // TODO: Determine which views need annotations
            // qed.FetchAnnotations(dataset_id);

            var model = new Model(model_optns);
            _.defer(function() {
                model.fetch({
                    "data": query,
                    "traditional": true,
                    success:function () {
                        model.trigger("load");
                    }
                });
            });

            var view_options = _.extend({"model":model}, (model_unit.view_options || {}), (options || {}));

            console.log("viewsByUri(" + uri + "," + view_name + "):loading view");
            var view = new ViewClass(view_options);
            $(targetEl).html(view.render().el);
        }
    },

    closeMap: function(atlasMap) {
        $(atlasMap).effect("transfer", {
            "to": this.$el.find(".maps-btn"),
            complete: function() {
                $(atlasMap).remove();
            }
        }, 750);
    },

    zoom: function(zoomLevel) {
        this.$el.find(".atlas-zoom").zoomTo({
            "duration":1000,
            "scalemode": "both",
            "easing": "ease",
            "nativeanimation": true,
            "root": this.$el.find(".atlas-canvas"),
            "closeclick": false,
            "targetsize":zoomLevel
        });
    },

    loadCancerList: function(txt) {
        var cancerList = txt.trim().split("\n");
        var UL = this.$el.find(".cancer-selector");
        _.each(cancerList, function(cancer) {
            UL.append(LineItemTemplate({"li_class":"active","a_class":"toggle-active","id":cancer,"label":cancer}));
        });

        UL.find(".toggle-active").click(function(e) {
            $(e.target).parent().toggleClass("active");
        });

        UL.sortable();
    },

    initGeneTypeahead: function(txt) {
        var genelist = txt.trim().split("\n");

        var UL = this.$el.find(".gene-selector");
        this.$el.find(".genes-typeahead").typeahead({
            source:function (q, p) {
                p(_.compact(_.flatten(_.map(q.toLowerCase().split(" "), function (qi) {
                    return _.map(genelist, function (geneitem) {
                        if (geneitem.toLowerCase().indexOf(qi) >= 0) return geneitem;
                    });
                }))));
            },

            updater:function (gene) {
                UL.append(LineItemTemplate({ "label":gene, "id":gene, "a_class":"item-remover", "i_class":"icon-trash" }));
                UL.find(".item-remover").click(function(e) {
                    $(e.target).parent().remove();
                });
                return "";
            }
        });

        UL.find(".item-remover").click(function(e) {
            $(e.target).parent().remove();
        });

        UL.sortable();
    },

    nextZindex: function() {
        var nextZindex = 1 + this["last-z-index"];
        this["last-z-index"] = nextZindex;
        return nextZindex;
    },

    nextPosition: function() {
        var lastPos = {
            "left": this.lastPosition.left + 50,
            "top": this.lastPosition.top + 50
        };
        this.lastPosition = lastPos;
        return lastPos;
    },

    currentState:function () {
        var openMaps = _.map(this.$el.find(".atlas-map"), function (map) {
            var mapid = $(map).data("mapid");
            var top = map.style["top"].replace("px", "");
            var left = map.style["left"].replace("px", "");
            var zindex = map.style["z-index"];
            return { "id":mapid, "isOpen":true, "position":{ "top":top, "left":left }, "zindex": zindex };
        });

        var afn = function(link) {
            return $(link).data("id")
        };

        return {
            "genes": _.map(this.$el.find(".gene-selector .item-remover"), afn),
            "cancers": _.map(this.$el.find(".cancer-selector .active a"), afn),
            "maps": openMaps
        }
    }
});<|endoftext|>


<commit_before>/**
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var async = require("async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
function getPadPlainText(pad, revNum)
{
  var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
  var textLines = atext.text.slice(0, -1).split('\n');
  var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
  var apool = pad.pool();

  var pieces = [];
  for (var i = 0; i < textLines.length; i++)
  {
    var line = _analyzeLine(textLines[i], attribLines[i], apool);
    if (line.listLevel)
    {
      var numSpaces = line.listLevel * 2 - 1;
      var bullet = '*';
      pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
    }
    else
    {
      pieces.push(line.text, '\n');
    }
  }

  return pieces.join('');
}

function getPadTXT(pad, revNum, callback)
{
  var atext = pad.atext;
  var html;
  async.waterfall([
  // fetch revision atext

  function (callback)
  {
    if (revNum != undefined)
    {
      pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
      {
        if(ERR(err, callback)) return;
        atext = revisionAtext;
        callback();
      });
    }
    else
    {
      callback(null);
    }
  },

  // convert atext to html

  function (callback)
  {
    html = getTXTFromAtext(pad, atext);
    callback(null);
  }],
  // run final callback

  function (err)
  {
    if(ERR(err, callback)) return;
    callback(null, html);
  });
}

exports.getPadTXT = getPadTXT;
exports.getTXTFromAtext = getTXTFromAtext;

function getTXTFromAtext(pad, atext, authorColors)
{
  var apool = pad.apool();
  var textLines = atext.text.slice(0, -1).split('\n');
  var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);

  var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
  var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
  var anumMap = {};
  var css = "";

  props.forEach(function (propName, i)
  {
    var propTrueNum = apool.putAttrib([propName, true], true);
    if (propTrueNum >= 0)
    {
      anumMap[propTrueNum] = i;
    }
  });

  function getLineTXT(text, attribs)
  {
    var propVals = [false, false, false];
    var ENTER = 1;
    var STAY = 2;
    var LEAVE = 0;

    // Use order of tags (b/i/u) as order of nesting, for simplicity
    // and decent nesting.  For example,
    // Just bold Bold and italics Just italics
    // becomes
    // Just bold Bold and italics Just italics
    var taker = Changeset.stringIterator(text);
    var assem = Changeset.stringAssembler();
    var openTags = [];

    var urls = _findURLs(text);

    var idx = 0;

    function processNextChars(numChars)
    {
      if (numChars <= 0)
      {
        return;
      }

      var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
      idx += numChars;

      while (iter.hasNext())
      {
        var o = iter.next();
        var propChanged = false;
        Changeset.eachAttribNumber(o.attribs, function (a)
        {
          if (a in anumMap)
          {
            var i = anumMap[a]; // i = 0 => bold, etc.
            if (!propVals[i])
            {
              propVals[i] = ENTER;
              propChanged = true;
            }
            else
            {
              propVals[i] = STAY;
            }
          }
        });
        for (var i = 0; i < propVals.length; i++)
        {
          if (propVals[i] === true)
          {
            propVals[i] = LEAVE;
            propChanged = true;
          }
          else if (propVals[i] === STAY)
          {
            propVals[i] = true; // set it back
          }
        }
        // now each member of propVal is in {false,LEAVE,ENTER,true}
        // according to what happens at start of span
        if (propChanged)
        {
          // leaving bold (e.g.) also leaves italics, etc.
          var left = false;
          for (var i = 0; i < propVals.length; i++)
          {
            var v = propVals[i];
            if (!left)
            {
              if (v === LEAVE)
              {
                left = true;
              }
            }
            else
            {
              if (v === true)
              {
                propVals[i] = STAY; // tag will be closed and re-opened
              }
            }
          }

          var tags2close = [];

          for (var i = propVals.length - 1; i >= 0; i--)
          {
            if (propVals[i] === LEAVE)
            {
              //emitCloseTag(i);
              tags2close.push(i);
              propVals[i] = false;
            }
            else if (propVals[i] === STAY)
            {
              //emitCloseTag(i);
              tags2close.push(i);
            }
          }

          for (var i = 0; i < propVals.length; i++)
          {
            if (propVals[i] === ENTER || propVals[i] === STAY)
            {
              emitOpenTag(i);
              propVals[i] = true;
            }
          }
          // propVals is now all {true,false} again
        } // end if (propChanged)
        var chars = o.chars;
        if (o.lines)
        {
          chars--; // exclude newline at end of line, if present
        }

        var s = taker.take(chars);

        //removes the characters with the code 12. Don't know where they come
        //from but they break the abiword parser and are completly useless
        s = s.replace(String.fromCharCode(12), "");

        assem.append(_encodeWhitespace(Security.escapeHTML(s)));
      } // end iteration over spans in line

      var tags2close = [];
      for (var i = propVals.length - 1; i >= 0; i--)
      {
        if (propVals[i])
        {
          tags2close.push(i);
          propVals[i] = false;
        }
      }

    } // end processNextChars
    if (urls)
    {
      urls.forEach(function (urlData)
      {
        var startIndex = urlData[0];
        var url = urlData[1];
        var urlLength = url.length;
        processNextChars(startIndex - idx);
        console.warn(url);
        // assem.append('');
        assem.append(url);
        processNextChars(urlLength);
        // assem.append('');
      });
    }
    processNextChars(text.length - idx);

    return _processSpaces(assem.toString());
  } // end getLineHTML
  var pieces = [css];

  // Need to deal with constraints imposed on HTML lists; can
  // only gain one level of nesting at once, can't change type
  // mid-list, etc.
  // People might use weird indenting, e.g. skip a level,
  // so we want to do something reasonable there.  We also
  // want to deal gracefully with blank lines.
  // => keeps track of the parents level of indentation
  var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
  for (var i = 0; i < textLines.length; i++)
  {
    var line = _analyzeLine(textLines[i], attribLines[i], apool);
    var lineContent = getLineTXT(line.text, line.aline);
    if(line.listTypeName == "bullet"){
      lineContent = "* " + lineContent; // add a bullet
    }
    if(line.listLevel > 0){
      for (var j = line.listLevel - 1; j >= 0; j--){
        pieces.push('\t');
      }
      if(line.listTypeName == "number"){
        pieces.push(line.listLevel + ". ");
        // This is bad because it doesn't truly reflect what the user
        // sees because browsers do magic on nested

s
      }
      pieces.push(lineContent, '\n');
    }else{
      console.warn(line);
      pieces.push(lineContent, '\n');
    }

    // I'm not too keen about using teh HTML export filters here, they could cause real pain in the future
    // I'd suggest supporting getLineTXTForExport
    var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
    {
      line: line,
      apool: apool,
      attribLine: attribLines[i],
      text: textLines[i]
    }, " ", " ", "");
    if (lineContentFromHook)
    {
      pieces.push(lineContentFromHook, '');
    }
    else
    {
      // pieces.push(lineContent, '\n');
    }
  }

  return pieces.join('');
}

function _analyzeLine(text, aline, apool)
{
  var line = {};

  // identify list
  var lineMarker = 0;
  line.listLevel = 0;
  if (aline)
  {
    var opIter = Changeset.opIterator(aline);
    if (opIter.hasNext())
    {
      var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
      if (listType)
      {
        lineMarker = 1;
        listType = /([a-z]+)([12345678])/.exec(listType);
        if (listType)
        {
          line.listTypeName = listType[1];
          line.listLevel = Number(listType[2]);
        }
      }
    }
  }
  if (lineMarker)
  {
    line.text = text.substring(1);
    line.aline = Changeset.subattribution(aline, 1);
  }
  else
  {
    line.text = text;
    line.aline = aline;
  }

  return line;
}

exports.getPadTXTDocument = function (padId, revNum, noDocType, callback)
{
  padManager.getPad(padId, function (err, pad)
  {
    if(ERR(err, callback)) return;

    getPadTXT(pad, revNum, function (err, html)
    {
      if(ERR(err, callback)) return;
      callback(null, html);
    });
  });
}

function _encodeWhitespace(s) {
  return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
  {
    return "&#" +c.charCodeAt(0) + ";"
  });
}

// copied from ACE
function _processSpaces(s)
{
  var doesWrap = true;
  if (s.indexOf("<") < 0 && !doesWrap)
  {
    // short-cut
    return s.replace(/ /g, ' ');
  }
  var parts = [];
  s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
  {
    parts.push(m);
  });
  if (doesWrap)
  {
    var endOfLine = true;
    var beforeSpace = false;
    // last space in a run is normal, others are nbsp,
    // end of line is nbsp
    for (var i = parts.length - 1; i >= 0; i--)
    {
      var p = parts[i];
      if (p == " ")
      {
        if (endOfLine || beforeSpace) parts[i] = ' ';
        endOfLine = false;
        beforeSpace = true;
      }
      else if (p.charAt(0) != "<")
      {
        endOfLine = false;
        beforeSpace = false;
      }
    }
    // beginning of line is nbsp
    for (var i = 0; i < parts.length; i++)
    {
      var p = parts[i];
      if (p == " ")
      {
        parts[i] = ' ';
        break;
      }
      else if (p.charAt(0) != "<")
      {
        break;
      }
    }
  }
  else
  {
    for (var i = 0; i < parts.length; i++)
    {
      var p = parts[i];
      if (p == " ")
      {
        parts[i] = ' ';
      }
    }
  }
  return parts.join('');
}

// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_SPACE = /\s/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');

// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]

function _findURLs(text)
{
  _REGEX_URL.lastIndex = 0;
  var urls = null;
  var execResult;
  while ((execResult = _REGEX_URL.exec(text)))
  {
    urls = (urls || []);
    var startIndex = execResult.index;
    var url = execResult[0];
    urls.push([startIndex, url]);
  }

  return urls;
}

<commit_msg>remove console warns<commit_after>/**
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var async = require("async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
function getPadPlainText(pad, revNum)
{
  var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
  var textLines = atext.text.slice(0, -1).split('\n');
  var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
  var apool = pad.pool();

  var pieces = [];
  for (var i = 0; i < textLines.length; i++)
  {
    var line = _analyzeLine(textLines[i], attribLines[i], apool);
    if (line.listLevel)
    {
      var numSpaces = line.listLevel * 2 - 1;
      var bullet = '*';
      pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
    }
    else
    {
      pieces.push(line.text, '\n');
    }
  }

  return pieces.join('');
}

function getPadTXT(pad, revNum, callback)
{
  var atext = pad.atext;
  var html;
  async.waterfall([
  // fetch revision atext

  function (callback)
  {
    if (revNum != undefined)
    {
      pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
      {
        if(ERR(err, callback)) return;
        atext = revisionAtext;
        callback();
      });
    }
    else
    {
      callback(null);
    }
  },

  // convert atext to html

  function (callback)
  {
    html = getTXTFromAtext(pad, atext);
    callback(null);
  }],
  // run final callback

  function (err)
  {
    if(ERR(err, callback)) return;
    callback(null, html);
  });
}

exports.getPadTXT = getPadTXT;
exports.getTXTFromAtext = getTXTFromAtext;

function getTXTFromAtext(pad, atext, authorColors)
{
  var apool = pad.apool();
  var textLines = atext.text.slice(0, -1).split('\n');
  var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);

  var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
  var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
  var anumMap = {};
  var css = "";

  props.forEach(function (propName, i)
  {
    var propTrueNum = apool.putAttrib([propName, true], true);
    if (propTrueNum >= 0)
    {
      anumMap[propTrueNum] = i;
    }
  });

  function getLineTXT(text, attribs)
  {
    var propVals = [false, false, false];
    var ENTER = 1;
    var STAY = 2;
    var LEAVE = 0;

    // Use order of tags (b/i/u) as order of nesting, for simplicity
    // and decent nesting.  For example,
    // Just bold Bold and italics Just italics
    // becomes
    // Just bold Bold and italics Just italics
    var taker = Changeset.stringIterator(text);
    var assem = Changeset.stringAssembler();
    var openTags = [];

    var urls = _findURLs(text);

    var idx = 0;

    function processNextChars(numChars)
    {
      if (numChars <= 0)
      {
        return;
      }

      var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
      idx += numChars;

      while (iter.hasNext())
      {
        var o = iter.next();
        var propChanged = false;
        Changeset.eachAttribNumber(o.attribs, function (a)
        {
          if (a in anumMap)
          {
            var i = anumMap[a]; // i = 0 => bold, etc.
            if (!propVals[i])
            {
              propVals[i] = ENTER;
              propChanged = true;
            }
            else
            {
              propVals[i] = STAY;
            }
          }
        });
        for (var i = 0; i < propVals.length; i++)
        {
          if (propVals[i] === true)
          {
            propVals[i] = LEAVE;
            propChanged = true;
          }
          else if (propVals[i] === STAY)
          {
            propVals[i] = true; // set it back
          }
        }
        // now each member of propVal is in {false,LEAVE,ENTER,true}
        // according to what happens at start of span
        if (propChanged)
        {
          // leaving bold (e.g.) also leaves italics, etc.
          var left = false;
          for (var i = 0; i < propVals.length; i++)
          {
            var v = propVals[i];
            if (!left)
            {
              if (v === LEAVE)
              {
                left = true;
              }
            }
            else
            {
              if (v === true)
              {
                propVals[i] = STAY; // tag will be closed and re-opened
              }
            }
          }

          var tags2close = [];

          for (var i = propVals.length - 1; i >= 0; i--)
          {
            if (propVals[i] === LEAVE)
            {
              //emitCloseTag(i);
              tags2close.push(i);
              propVals[i] = false;
            }
            else if (propVals[i] === STAY)
            {
              //emitCloseTag(i);
              tags2close.push(i);
            }
          }

          for (var i = 0; i < propVals.length; i++)
          {
            if (propVals[i] === ENTER || propVals[i] === STAY)
            {
              emitOpenTag(i);
              propVals[i] = true;
            }
          }
          // propVals is now all {true,false} again
        } // end if (propChanged)
        var chars = o.chars;
        if (o.lines)
        {
          chars--; // exclude newline at end of line, if present
        }

        var s = taker.take(chars);

        //removes the characters with the code 12. Don't know where they come
        //from but they break the abiword parser and are completly useless
        s = s.replace(String.fromCharCode(12), "");

        assem.append(_encodeWhitespace(Security.escapeHTML(s)));
      } // end iteration over spans in line

      var tags2close = [];
      for (var i = propVals.length - 1; i >= 0; i--)
      {
        if (propVals[i])
        {
          tags2close.push(i);
          propVals[i] = false;
        }
      }

    } // end processNextChars
    if (urls)
    {
      urls.forEach(function (urlData)
      {
        var startIndex = urlData[0];
        var url = urlData[1];
        var urlLength = url.length;
        processNextChars(startIndex - idx);
        // assem.append('');
        assem.append(url);
        processNextChars(urlLength);
        // assem.append('');
      });
    }
    processNextChars(text.length - idx);

    return _processSpaces(assem.toString());
  } // end getLineHTML
  var pieces = [css];

  // Need to deal with constraints imposed on HTML lists; can
  // only gain one level of nesting at once, can't change type
  // mid-list, etc.
  // People might use weird indenting, e.g. skip a level,
  // so we want to do something reasonable there.  We also
  // want to deal gracefully with blank lines.
  // => keeps track of the parents level of indentation
  var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
  for (var i = 0; i < textLines.length; i++)
  {
    var line = _analyzeLine(textLines[i], attribLines[i], apool);
    var lineContent = getLineTXT(line.text, line.aline);
    if(line.listTypeName == "bullet"){
      lineContent = "* " + lineContent; // add a bullet
    }
    if(line.listLevel > 0){
      for (var j = line.listLevel - 1; j >= 0; j--){
        pieces.push('\t');
      }
      if(line.listTypeName == "number"){
        pieces.push(line.listLevel + ". ");
        // This is bad because it doesn't truly reflect what the user
        // sees because browsers do magic on nested

s
      }
      pieces.push(lineContent, '\n');
    }else{
      pieces.push(lineContent, '\n');
    }

    // I'm not too keen about using teh HTML export filters here, they could cause real pain in the future
    // I'd suggest supporting getLineTXTForExport
    var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport",
    {
      line: line,
      apool: apool,
      attribLine: attribLines[i],
      text: textLines[i]
    }, " ", " ", "");
    if (lineContentFromHook)
    {
      pieces.push(lineContentFromHook, '');
    }
    else
    {
      // pieces.push(lineContent, '\n');
    }
  }

  return pieces.join('');
}

function _analyzeLine(text, aline, apool)
{
  var line = {};

  // identify list
  var lineMarker = 0;
  line.listLevel = 0;
  if (aline)
  {
    var opIter = Changeset.opIterator(aline);
    if (opIter.hasNext())
    {
      var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
      if (listType)
      {
        lineMarker = 1;
        listType = /([a-z]+)([12345678])/.exec(listType);
        if (listType)
        {
          line.listTypeName = listType[1];
          line.listLevel = Number(listType[2]);
        }
      }
    }
  }
  if (lineMarker)
  {
    line.text = text.substring(1);
    line.aline = Changeset.subattribution(aline, 1);
  }
  else
  {
    line.text = text;
    line.aline = aline;
  }

  return line;
}

exports.getPadTXTDocument = function (padId, revNum, noDocType, callback)
{
  padManager.getPad(padId, function (err, pad)
  {
    if(ERR(err, callback)) return;

    getPadTXT(pad, revNum, function (err, html)
    {
      if(ERR(err, callback)) return;
      callback(null, html);
    });
  });
}

function _encodeWhitespace(s) {
  return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
  {
    return "&#" +c.charCodeAt(0) + ";"
  });
}

// copied from ACE
function _processSpaces(s)
{
  var doesWrap = true;
  if (s.indexOf("<") < 0 && !doesWrap)
  {
    // short-cut
    return s.replace(/ /g, ' ');
  }
  var parts = [];
  s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
  {
    parts.push(m);
  });
  if (doesWrap)
  {
    var endOfLine = true;
    var beforeSpace = false;
    // last space in a run is normal, others are nbsp,
    // end of line is nbsp
    for (var i = parts.length - 1; i >= 0; i--)
    {
      var p = parts[i];
      if (p == " ")
      {
        if (endOfLine || beforeSpace) parts[i] = ' ';
        endOfLine = false;
        beforeSpace = true;
      }
      else if (p.charAt(0) != "<")
      {
        endOfLine = false;
        beforeSpace = false;
      }
    }
    // beginning of line is nbsp
    for (var i = 0; i < parts.length; i++)
    {
      var p = parts[i];
      if (p == " ")
      {
        parts[i] = ' ';
        break;
      }
      else if (p.charAt(0) != "<")
      {
        break;
      }
    }
  }
  else
  {
    for (var i = 0; i < parts.length; i++)
    {
      var p = parts[i];
      if (p == " ")
      {
        parts[i] = ' ';
      }
    }
  }
  return parts.join('');
}

// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_SPACE = /\s/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');

// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]

function _findURLs(text)
{
  _REGEX_URL.lastIndex = 0;
  var urls = null;
  var execResult;
  while ((execResult = _REGEX_URL.exec(text)))
  {
    urls = (urls || []);
    var startIndex = execResult.index;
    var url = execResult[0];
    urls.push([startIndex, url]);
  }

  return urls;
}

<|endoftext|>


<commit_before>'use strict';

import './styles.postcss';
import _ from 'underscore';
import Backbone from 'backbone';
import TileLayer from './TileLayer';
import MarkerLayer from './layers/MarkerLayer';
import TorqueLayer from './TorqueLayer';
import PopUpContentView from './../PopUp/PopUpContentView';
import layersCollection from '../../scripts/collections/layersCollection';
import filtersModel from '../../scripts/models/filtersModel';
import utils from '../../scripts/helpers/utils';

class MapView extends Backbone.View {

  initialize(settings) {
    // First configure mapbox
    L.mapbox.accessToken = config.mapboxToken;

    // Setting default options
    this.options = _.extend({}, this.defaults, settings.options);

    //Checking for device
    this.device = utils.checkDevice();

    // Setting first state
    this.state = new Backbone.Model(settings.state);
    this.state.attributes = _.extend({}, this.options, this.state.attributes);
    this.state.set({ 'filters': filtersModel.toJSON() }, { silent: true });
    this._checkMapInitialSettings();

    this._createMap();
    this._addLayer();
    this._setEvents();
  }

  _checkMapInitialSettings() {
    if (this.device.mobile || this.device.tablet) {
      this.state.attributes.zoom = 2;
    }

    // mobile
    if (this.device.mobile) {
      this.state.attributes.lat = 10;
      this.state.attributes.lng = -100;
    }

    // Ipad landscape
    if ( !this.device.tablet && this.device.device ) {
      this.state.attributes.lat = 40;
      this.state.attributes.lng = -120;
    }
  }

  drawDonationMarker(options) {
    this.donationMarker = new MarkerLayer(options);
    this.donationMarker.addLayer(this.map);

    this.myDonationPopUp = new PopUpContentView({
      currentMode: 'my-donation',
      currentLayer: 'my-donation',
      latLng: options.position,
      map: this.map,
      name: options.name
    })

    this.myDonationPopUp.getPopUp();
  }

  _createMap() {
    const mapOptions = {
      zoom: this.state.attributes.zoom,
      center: [this.state.attributes.lat, this.state.attributes.lng]
    };
    this.map = L.mapbox.map(this.el, this.options.style, mapOptions);

    this._addAttributions();
  }

  _addAttributions() {
    // Add attribution to Mapbox and OpenStreetMap.
    let attribution = L.control.attribution();
    attribution.setPrefix('');
    attribution.addAttribution('© Mapbox  | © OpenStreetMap | © CartoDB');
    attribution.addTo(this.map);
  }

  _setEvents() {
    this.map.on('click', this._infowindowSetUp.bind(this));

    this.state.on('change:zoom', () => {
      this.map.setZoom(this.state.attributes.zoom);
    });

    this.state.on('change:lat', () => {
      const center = this.map.getCenter();
      const latlng = L.latLng(this.state.attributes.lat, center.lng);
      this.map.setView(latlng, this.map.getZoom());
    });

    this.state.on('change:lng', () => {
      const center = this.map.getCenter();
      const latlng = L.latLng(center.lat, this.state.attributes.lng);
      this.map.setView(latlng, this.map.getZoom());
    });

    this.state.on('change:filters', () => this.changeLayer());
    this.state.on('change:timelineDates', () => this.changeLayerTimeline());

    this.state.on('change:mode', _.bind(this.changeLayer, this));
    layersCollection.on('change', _.bind(this.changeLayer, this));
    filtersModel.on('change', _.bind(this._updateFilters, this));

    this.map.on('zoomend', _.bind(this._setStateZoom, this));
    this.map.on('dragend', _.bind(this._setStatePosition, this));
  }

  _setStateZoom(e) {
    this.state.set({zoom: this.map.getZoom()});
  }

  _setStatePosition(e) {
    const position = this.map.getCenter();
    this.state.set({ lat: position.lat, lng: position.lng });
  }

  _updateFilters() {
    this.state.set({'filters': filtersModel.toJSON()});
  }

  _infowindowSetUp(e) {
    this.popUp = new PopUpContentView({
      currentMode: this.state.get('mode'),
      currentLayer: this.state.get('currentLayer'),
      latLng: e.latlng,
      map: this.map,
      zoom: this.state.get('zoom'),
      timelineDates: this.state.get('timelineDates')
    });

    this.popUp.getPopUp();
  }

  _setFilters() {
    if ( !(filtersModel.filtersIsEmpty()) ) {
      this.options.filters = filtersModel.toJSON();
    } else {
      this.options.filters = null;
    }
  }

  _addLayer() {
    // Remove current pop up at beginning
    if (this.popUp) {
      this.popUp.closeCurrentPopup();
    }

    // I will draw only active layers for each category;
    const activeLayers = layersCollection.where({
      active: true,
      category: this.state.get('mode')
    });

    _.each(activeLayers, activeLayer => {
      const layerConfig = activeLayer.attributes;
      // Selecting kind of layer by layer_type attribute
      const layerClass = (layerConfig.layer_type === 'torque') ? TorqueLayer : TileLayer;
      const newLayer = new layerClass(layerConfig, this.state.toJSON());
      newLayer.createLayer().done(() => {
        /* We ensure to always display the latest tiles */
        if(!this.currentLayer ||
          newLayer.timestamp > this.currentLayer.timestamp) {
          this._removeCurrentLayer();
          newLayer.addLayer(this.map);
          this.currentLayer = newLayer;
          this.currentLayerConfig = layerConfig;
        }
      });
      this.state.set('currentLayer', activeLayer.get('slug'));
    });
  }

  _removeCurrentLayer() {
    if (this.currentLayer && this.currentLayer.removeLayer) {
      this.currentLayer.removeLayer(this.map);
      this.currentLayer = null;
    }
  }

  changeLayer() {
    this._addLayer();
  }

};

MapView.prototype.changeLayerTimeline = (function() {

  const addLayer = _.throttle(function() {
    this._addLayer();
  }, 200);

  return function() {
    if (this.currentLayerConfig && this.currentLayerConfig.layer_type &&
      this.currentLayerConfig.layer_type === 'torque') {

      const currentDate = this.state.toJSON().timelineDates.to;
      const step = Math.round(this.currentLayer.layer.timeToStep(currentDate));
      // Doc: https://github.com/CartoDB/torque/blob/master/doc/torque_api.md
      this.currentLayer.layer.setStep(step);

    } else {
      addLayer.call(this);
    }
  };
})();

MapView.prototype.defaults = {
  style: location.hostname === 'localhost' ? 'mapbox.streets' : 'jhanley.a25ffffe',
  lat: 35,
  lng: -80,
  zoom: 3
};

export default MapView;
<commit_msg>avoid infinite repetition of world map<commit_after>'use strict';

import './styles.postcss';
import _ from 'underscore';
import Backbone from 'backbone';
import TileLayer from './TileLayer';
import MarkerLayer from './layers/MarkerLayer';
import TorqueLayer from './TorqueLayer';
import PopUpContentView from './../PopUp/PopUpContentView';
import layersCollection from '../../scripts/collections/layersCollection';
import filtersModel from '../../scripts/models/filtersModel';
import utils from '../../scripts/helpers/utils';

class MapView extends Backbone.View {

  initialize(settings) {
    // First configure mapbox
    L.mapbox.accessToken = config.mapboxToken;

    // Setting default options
    this.options = _.extend({}, this.defaults, settings.options);

    //Checking for device
    this.device = utils.checkDevice();

    // Setting first state
    this.state = new Backbone.Model(settings.state);
    this.state.attributes = _.extend({}, this.options, this.state.attributes);
    this.state.set({ 'filters': filtersModel.toJSON() }, { silent: true });
    this._checkMapInitialSettings();

    this._createMap();
    this._addLayer();
    this._setEvents();
  }

  _checkMapInitialSettings() {
    if (this.device.mobile || this.device.tablet) {
      this.state.attributes.zoom = 2;
    }

    // mobile
    if (this.device.mobile) {
      this.state.attributes.lat = 10;
      this.state.attributes.lng = -100;
    }

    // Ipad landscape
    if ( !this.device.tablet && this.device.device ) {
      this.state.attributes.lat = 40;
      this.state.attributes.lng = -120;
    }
  }

  drawDonationMarker(options) {
    this.donationMarker = new MarkerLayer(options);
    this.donationMarker.addLayer(this.map);

    this.myDonationPopUp = new PopUpContentView({
      currentMode: 'my-donation',
      currentLayer: 'my-donation',
      latLng: options.position,
      map: this.map,
      name: options.name
    })

    this.myDonationPopUp.getPopUp();
  }

  _createMap() {
    const mapOptions = {
      zoom: this.state.attributes.zoom,
      center: [this.state.attributes.lat, this.state.attributes.lng],
      tileLayer: {
        continuousWorld: false,
        noWrap: true
      }
    };
    this.map = L.mapbox.map(this.el, this.options.style, mapOptions);

    this._addAttributions();
  }

  _addAttributions() {
    // Add attribution to Mapbox and OpenStreetMap.
    let attribution = L.control.attribution();
    attribution.setPrefix('');
    attribution.addAttribution('© Mapbox  | © OpenStreetMap | © CartoDB');
    attribution.addTo(this.map);
  }

  _setEvents() {
    this.map.on('click', this._infowindowSetUp.bind(this));

    this.state.on('change:zoom', () => {
      this.map.setZoom(this.state.attributes.zoom);
    });

    this.state.on('change:lat', () => {
      const center = this.map.getCenter();
      const latlng = L.latLng(this.state.attributes.lat, center.lng);
      this.map.setView(latlng, this.map.getZoom());
    });

    this.state.on('change:lng', () => {
      const center = this.map.getCenter();
      const latlng = L.latLng(center.lat, this.state.attributes.lng);
      this.map.setView(latlng, this.map.getZoom());
    });

    this.state.on('change:filters', () => this.changeLayer());
    this.state.on('change:timelineDates', () => this.changeLayerTimeline());

    this.state.on('change:mode', _.bind(this.changeLayer, this));
    layersCollection.on('change', _.bind(this.changeLayer, this));
    filtersModel.on('change', _.bind(this._updateFilters, this));

    this.map.on('zoomend', _.bind(this._setStateZoom, this));
    this.map.on('dragend', _.bind(this._setStatePosition, this));
  }

  _setStateZoom(e) {
    this.state.set({zoom: this.map.getZoom()});
  }

  _setStatePosition(e) {
    const position = this.map.getCenter();
    this.state.set({ lat: position.lat, lng: position.lng });
  }

  _updateFilters() {
    this.state.set({'filters': filtersModel.toJSON()});
  }

  _infowindowSetUp(e) {
    this.popUp = new PopUpContentView({
      currentMode: this.state.get('mode'),
      currentLayer: this.state.get('currentLayer'),
      latLng: e.latlng,
      map: this.map,
      zoom: this.state.get('zoom'),
      timelineDates: this.state.get('timelineDates')
    });

    this.popUp.getPopUp();
  }

  _setFilters() {
    if ( !(filtersModel.filtersIsEmpty()) ) {
      this.options.filters = filtersModel.toJSON();
    } else {
      this.options.filters = null;
    }
  }

  _addLayer() {
    // Remove current pop up at beginning
    if (this.popUp) {
      this.popUp.closeCurrentPopup();
    }

    // I will draw only active layers for each category;
    const activeLayers = layersCollection.where({
      active: true,
      category: this.state.get('mode')
    });

    _.each(activeLayers, activeLayer => {
      const layerConfig = activeLayer.attributes;
      // Selecting kind of layer by layer_type attribute
      const layerClass = (layerConfig.layer_type === 'torque') ? TorqueLayer : TileLayer;
      const newLayer = new layerClass(layerConfig, this.state.toJSON());
      newLayer.createLayer().done(() => {
        /* We ensure to always display the latest tiles */
        if(!this.currentLayer ||
          newLayer.timestamp > this.currentLayer.timestamp) {
          this._removeCurrentLayer();
          newLayer.addLayer(this.map);
          this.currentLayer = newLayer;
          this.currentLayerConfig = layerConfig;
        }
      });
      this.state.set('currentLayer', activeLayer.get('slug'));
    });
  }

  _removeCurrentLayer() {
    if (this.currentLayer && this.currentLayer.removeLayer) {
      this.currentLayer.removeLayer(this.map);
      this.currentLayer = null;
    }
  }

  changeLayer() {
    this._addLayer();
  }

};

MapView.prototype.changeLayerTimeline = (function() {

  const addLayer = _.throttle(function() {
    this._addLayer();
  }, 200);

  return function() {
    if (this.currentLayerConfig && this.currentLayerConfig.layer_type &&
      this.currentLayerConfig.layer_type === 'torque') {

      const currentDate = this.state.toJSON().timelineDates.to;
      const step = Math.round(this.currentLayer.layer.timeToStep(currentDate));
      // Doc: https://github.com/CartoDB/torque/blob/master/doc/torque_api.md
      this.currentLayer.layer.setStep(step);

    } else {
      addLayer.call(this);
    }
  };
})();

MapView.prototype.defaults = {
  style: location.hostname === 'localhost' ? 'mapbox.streets' : 'jhanley.a25ffffe',
  lat: 35,
  lng: -80,
  zoom: 3
};

export default MapView;
<|endoftext|>


<commit_before>import React from 'react'
import MapFilter from 'react-mapfilter'
import { ipcRenderer, remote } from 'electron'
import {
  FIELD_TYPE_STRING
} from 'react-mapfilter/es5/constants'
import xor from 'lodash/xor'
import differenceBy from 'lodash/differenceBy'
import url from 'url'

import MenuItem from '@material-ui/core/MenuItem'
import randomBytes from 'randombytes'

import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'

import api from '../api'
import MenuItems from './MenuItems'
import ConvertDialog from './ConvertDialog'

const theme = createMuiTheme({
  palette: {
    primary: {
      main: '#39527b'
    }
  }
})

const osmServerHost = 'http://' + remote.getGlobal('osmServerHost')

const styleUrl = `${osmServerHost}/styles/mapfilter-style/style.json`

class Home extends React.Component {
  constructor (props) {
    super(props)
    var self = this
    self.state = {
      features: [],
      mapPosition: { center: [0, 0], zoom: 0 },
      showModal: false,
      mapStyle: styleUrl
    }
    self.getFeatures()
    this.handleChangeFeatures = this.handleChangeFeatures.bind(this)
    this.zoomToDataRequest = this.zoomToDataRequest.bind(this)
    this.zoomToDataResponse = this.zoomToDataResponse.bind(this)
    this.zoomToLatLonResponse = this.zoomToLatLonResponse.bind(this)
    this.refresh = this.refresh.bind(this)
    ipcRenderer.on('refresh-window', this.refresh)
    ipcRenderer.on('zoom-to-data-request', this.zoomToDataRequest)
    ipcRenderer.on('zoom-to-data-response', self.zoomToDataResponse)
    ipcRenderer.on('zoom-to-latlon-response', self.zoomToLatLonResponse)
  }

  refresh () {
    window.location.reload()
  }

  componentWillUnmount () {
    ipcRenderer.removeListener('refresh-window', this.refresh)
    ipcRenderer.removeListener('zoom-to-data-request', this.zoomToDataRequest)
    ipcRenderer.removeListener('zoom-to-data-response', this.zoomToDataResponse)
    ipcRenderer.removeListener('zoom-to-latlon-response', this.zoomToLatLonResponse)
  }

  zoomToLatLonResponse (_, lat, lon) {
    this.setState({
      mapPosition: { center: [lat, lon], zoom: 14 }
    })
  }

  zoomToDataResponse (_, loc) {
    this.setState({
      mapPosition: { center: loc, zoom: 14 }
    })
  }

  zoomToDataRequest () {
    ipcRenderer.send('zoom-to-data-get-centroid')
  }

  handleDatasetChange () {
    return (e) => {
      this.setState({ formId: e.target.value })
    }
  }

  handleConvertFeaturesClick () {
    return () => {
      this.setState({ showModal: 'convert' })
    }
  }

  handleChangeFeatures (changedFeatures) {
    const { features } = this.state
    const xorFeatures = xor(changedFeatures, features)
    const deleted = differenceBy(xorFeatures, changedFeatures, 'id')
    const added = differenceBy(xorFeatures, features, 'id')
    const updated = xorFeatures.filter(f => {
      return added.indexOf(f) === -1 &&
        deleted.indexOf(f) === -1 &&
        features.indexOf(f) === -1
    })

    var cb = function (err, resp) {
      if (err) return this.handleError(err)
    }

    deleted.forEach(f => api.del(f, cb))
    added.forEach(f => this.createObservation(f))
    updated.forEach(f => this.updateObservation(f))
    this.setState({ features: changedFeatures })
  }

  updateObservation (f) {
    const obs = this._observationsById[f.id]
    const newObs = Object.assign({}, obs)

    // TODO: media is currently not updated, but it will be in the future
    const WHITELIST = ['fields', 'media']
    Object.keys(f.properties || {}).forEach(function (key) {
      if (WHITELIST.indexOf(key) > -1) return
      newObs.tags[key] = f.properties[key]
    })

    // Mapeo Mobile currently expects field definitions as a property on tags
    ;(obs.tags.fields || []).forEach(function (field, i) {
      if (!f.properties || f.properties[field.id] === undefined) return
      newObs.tags.fields[i].answer = f.properties[field.id]
      newObs.tags.fields[i].answered = true
    })

    api.update(newObs, (err, obs) => {
      if (err) return this.handleError(err)
      // Keep a reference to the updated obs
      this._observationsById[obs.id] = obs
    })
  }

  createObservation (f, cb) {
    const newObs = {
      id: f.id || randomBytes(8).toString('hex'),
      type: 'observation',
      tags: f.properties || {}
    }
    if (f.geometry) {
      newObs.lon = f.geometry.coordinates[0]
      newObs.lat = f.geometry.coordinates[1]
    }
    api.create(newObs, (err, obs) => {
      if (err) return this.handleError(err)
      // Keep a reference to the updated obs
      this._observationsById[obs.id] = obs
    })
  }

  closeModal () {
    return () => { this.setState({ showModal: false }) }
  }

  getFeatures () {
    var self = this
    api.list(function (err, resp) {
      if (err) return self.handleError(err)
      const observations = JSON.parse(resp.body)
      const byId = self._observationsById = observations.reduce(observationIdReducer, {})
      // the byId reducer removes forks, so use that for the features array
      const features = Object.keys(byId)
        .map(key => byId[key])
        .map(observationToFeature)
      self.setState({ features })
    })
  }

  handleError (err) {
    // TODO: Show some kind of error message in the UI
    console.error(err)
  }

  handleChangeMapPosition (mapPosition) {
    this.setState({ mapPosition })
  }

  onMenuItemClick (view) {
    if (view.modal) this.props.openModal(view.name)
    else this.props.changeView(view.name)
  }

  render () {
    const { features, showModal, mapPosition } = this.state

    var appBarMenuItems = []

    MenuItems.forEach((view, i) => {
      var id = `menu-option-${view.name}`
      if (view.name === 'MapFilter') return
      appBarMenuItems.push(
        <MenuItem
          id={id}
          onClick={this.onMenuItemClick.bind(this, view)}>
          {view.label}
        </MenuItem>
      )
    })

    return (

      <MuiThemeProvider theme={theme}>
        <MapFilter
          mapStyle={styleUrl}
          features={features}
          mapPosition={mapPosition}
          onChangeMapPosition={this.handleChangeMapPosition.bind(this)}
          onChangeFeatures={this.handleChangeFeatures}
          fieldTypes={{
            notes: FIELD_TYPE_STRING
          }}
          datasetName='mapeo'
          resizer={resizer}
          appBarMenuItems={appBarMenuItems}
          appBarTitle='Mapeo' />

        <ConvertDialog
          open={showModal === 'convert'}
          onClose={() => { this.setState({ showModal: false }) }}
          features={features} />
      </MuiThemeProvider>

)
  }
}

function observationToFeature (obs, id) {
  var feature = {
    id: obs.id,
    type: 'Feature',
    geometry: null,
    properties: {}
  }

  if (obs.lon && obs.lat) {
    feature.geometry = {
      type: 'Point',
      coordinates: [obs.lon, obs.lat]
    }
  }

  const WHITELIST = ['fields']
  Object.keys(obs.tags || {}).forEach(function (key) {
    if (WHITELIST.indexOf(key) > -1) return
    feature.properties[key] = obs.tags[key]
  })

  feature.properties.media = (obs.attachments || []).map(function (a) {
    var id = a.id || a // the phone doesn't have id property on it's attachments.
    return {
      // type: 'image' -- turns on media filtering on the sidebar.
      value: `${osmServerHost}/media/original/${id}`
    }
  })

  feature.properties.notes = obs.notes || ' '
  return feature
}

function resizer (src, size) {
  const parsedUrl = url.parse(src)
  // Don't resize local images
  if (parsedUrl.hostname === 'localhost' || parsedUrl.hostname === '127.0.0.1') return src
  return 'https://resizer.digital-democracy.org/{width}/{height}/{url}'
    .replace('{width}', size)
    .replace('{height}', size)
    .replace('{url}', src)
}

function observationIdReducer (acc, obs) {
  if (acc[obs.id]) {
    // there is a fork
    if (obs.timestamp > acc[obs.id].timestamp) {
      // use the most recent
      acc[obs.id] = obs
    }
  } else {
    acc[obs.id] = obs
  }
  return acc
}

module.exports = Home
<commit_msg>don't override notes<commit_after>import React from 'react'
import MapFilter from 'react-mapfilter'
import { ipcRenderer, remote } from 'electron'
import {
  FIELD_TYPE_STRING
} from 'react-mapfilter/es5/constants'
import xor from 'lodash/xor'
import differenceBy from 'lodash/differenceBy'
import url from 'url'

import MenuItem from '@material-ui/core/MenuItem'
import randomBytes from 'randombytes'

import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'

import api from '../api'
import MenuItems from './MenuItems'
import ConvertDialog from './ConvertDialog'

const theme = createMuiTheme({
  palette: {
    primary: {
      main: '#39527b'
    }
  }
})

const osmServerHost = 'http://' + remote.getGlobal('osmServerHost')

const styleUrl = `${osmServerHost}/styles/mapfilter-style/style.json`

class Home extends React.Component {
  constructor (props) {
    super(props)
    var self = this
    self.state = {
      features: [],
      mapPosition: { center: [0, 0], zoom: 0 },
      showModal: false,
      mapStyle: styleUrl
    }
    self.getFeatures()
    this.handleChangeFeatures = this.handleChangeFeatures.bind(this)
    this.zoomToDataRequest = this.zoomToDataRequest.bind(this)
    this.zoomToDataResponse = this.zoomToDataResponse.bind(this)
    this.zoomToLatLonResponse = this.zoomToLatLonResponse.bind(this)
    this.refresh = this.refresh.bind(this)
    ipcRenderer.on('refresh-window', this.refresh)
    ipcRenderer.on('zoom-to-data-request', this.zoomToDataRequest)
    ipcRenderer.on('zoom-to-data-response', self.zoomToDataResponse)
    ipcRenderer.on('zoom-to-latlon-response', self.zoomToLatLonResponse)
  }

  refresh () {
    window.location.reload()
  }

  componentWillUnmount () {
    ipcRenderer.removeListener('refresh-window', this.refresh)
    ipcRenderer.removeListener('zoom-to-data-request', this.zoomToDataRequest)
    ipcRenderer.removeListener('zoom-to-data-response', this.zoomToDataResponse)
    ipcRenderer.removeListener('zoom-to-latlon-response', this.zoomToLatLonResponse)
  }

  zoomToLatLonResponse (_, lat, lon) {
    this.setState({
      mapPosition: { center: [lat, lon], zoom: 14 }
    })
  }

  zoomToDataResponse (_, loc) {
    this.setState({
      mapPosition: { center: loc, zoom: 14 }
    })
  }

  zoomToDataRequest () {
    ipcRenderer.send('zoom-to-data-get-centroid')
  }

  handleDatasetChange () {
    return (e) => {
      this.setState({ formId: e.target.value })
    }
  }

  handleConvertFeaturesClick () {
    return () => {
      this.setState({ showModal: 'convert' })
    }
  }

  handleChangeFeatures (changedFeatures) {
    const { features } = this.state
    const xorFeatures = xor(changedFeatures, features)
    const deleted = differenceBy(xorFeatures, changedFeatures, 'id')
    const added = differenceBy(xorFeatures, features, 'id')
    const updated = xorFeatures.filter(f => {
      return added.indexOf(f) === -1 &&
        deleted.indexOf(f) === -1 &&
        features.indexOf(f) === -1
    })

    var cb = function (err, resp) {
      if (err) return this.handleError(err)
    }

    deleted.forEach(f => api.del(f, cb))
    added.forEach(f => this.createObservation(f))
    updated.forEach(f => this.updateObservation(f))
    this.setState({ features: changedFeatures })
  }

  updateObservation (f) {
    const obs = this._observationsById[f.id]
    const newObs = Object.assign({}, obs)

    // TODO: media is currently not updated, but it will be in the future
    const WHITELIST = ['fields', 'media']
    Object.keys(f.properties || {}).forEach(function (key) {
      if (WHITELIST.indexOf(key) > -1) return
      newObs.tags[key] = f.properties[key]
    })

    // Mapeo Mobile currently expects field definitions as a property on tags
    ;(obs.tags.fields || []).forEach(function (field, i) {
      if (!f.properties || f.properties[field.id] === undefined) return
      newObs.tags.fields[i].answer = f.properties[field.id]
      newObs.tags.fields[i].answered = true
    })

    api.update(newObs, (err, obs) => {
      if (err) return this.handleError(err)
      // Keep a reference to the updated obs
      this._observationsById[obs.id] = obs
    })
  }

  createObservation (f, cb) {
    const newObs = {
      id: f.id || randomBytes(8).toString('hex'),
      type: 'observation',
      tags: f.properties || {}
    }
    if (f.geometry) {
      newObs.lon = f.geometry.coordinates[0]
      newObs.lat = f.geometry.coordinates[1]
    }
    api.create(newObs, (err, obs) => {
      if (err) return this.handleError(err)
      // Keep a reference to the updated obs
      this._observationsById[obs.id] = obs
    })
  }

  closeModal () {
    return () => { this.setState({ showModal: false }) }
  }

  getFeatures () {
    var self = this
    api.list(function (err, resp) {
      if (err) return self.handleError(err)
      const observations = JSON.parse(resp.body)
      const byId = self._observationsById = observations.reduce(observationIdReducer, {})
      // the byId reducer removes forks, so use that for the features array
      const features = Object.keys(byId)
        .map(key => byId[key])
        .map(observationToFeature)
      self.setState({ features })
    })
  }

  handleError (err) {
    // TODO: Show some kind of error message in the UI
    console.error(err)
  }

  handleChangeMapPosition (mapPosition) {
    this.setState({ mapPosition })
  }

  onMenuItemClick (view) {
    if (view.modal) this.props.openModal(view.name)
    else this.props.changeView(view.name)
  }

  render () {
    const { features, showModal, mapPosition } = this.state

    var appBarMenuItems = []

    MenuItems.forEach((view, i) => {
      var id = `menu-option-${view.name}`
      if (view.name === 'MapFilter') return
      appBarMenuItems.push(
        <MenuItem
          id={id}
          onClick={this.onMenuItemClick.bind(this, view)}>
          {view.label}
        </MenuItem>
      )
    })

    return (

      <MuiThemeProvider theme={theme}>
        <MapFilter
          mapStyle={styleUrl}
          features={features}
          mapPosition={mapPosition}
          onChangeMapPosition={this.handleChangeMapPosition.bind(this)}
          onChangeFeatures={this.handleChangeFeatures}
          fieldTypes={{
            notes: FIELD_TYPE_STRING
          }}
          datasetName='mapeo'
          resizer={resizer}
          appBarMenuItems={appBarMenuItems}
          appBarTitle='Mapeo' />

        <ConvertDialog
          open={showModal === 'convert'}
          onClose={() => { this.setState({ showModal: false }) }}
          features={features} />
      </MuiThemeProvider>

)
  }
}

function observationToFeature (obs, id) {
  var feature = {
    id: obs.id,
    type: 'Feature',
    geometry: null,
    properties: {}
  }

  if (obs.lon && obs.lat) {
    feature.geometry = {
      type: 'Point',
      coordinates: [obs.lon, obs.lat]
    }
  }

  const WHITELIST = ['fields']
  Object.keys(obs.tags || {}).forEach(function (key) {
    if (WHITELIST.indexOf(key) > -1) return
    feature.properties[key] = obs.tags[key]
  })

  feature.properties.media = (obs.attachments || []).map(function (a) {
    var id = a.id || a // the phone doesn't have id property on it's attachments.
    return {
      // type: 'image' -- turns on media filtering on the sidebar.
      value: `${osmServerHost}/media/original/${id}`
    }
  })

  if (!feature.properties.notes) feature.properties.notes = ' '
  return feature
}

function resizer (src, size) {
  const parsedUrl = url.parse(src)
  // Don't resize local images
  if (parsedUrl.hostname === 'localhost' || parsedUrl.hostname === '127.0.0.1') return src
  return 'https://resizer.digital-democracy.org/{width}/{height}/{url}'
    .replace('{width}', size)
    .replace('{height}', size)
    .replace('{url}', src)
}

function observationIdReducer (acc, obs) {
  if (acc[obs.id]) {
    // there is a fork
    if (obs.timestamp > acc[obs.id].timestamp) {
      // use the most recent
      acc[obs.id] = obs
    }
  } else {
    acc[obs.id] = obs
  }
  return acc
}

module.exports = Home
<|endoftext|>


<commit_before>// Menu

import React from 'react/addons';
import { Link } from 'react-router';
import { Grid, Row, Col } from 'react-bootstrap';
import MenuToggle from '../../components/MenuToggle';
import styles from './Menu.less';
import withViewport from '../../decorators/withViewport';
import withStyles from '../../decorators/withStyles';
import MenuActions from '../../actions/MenuActions';

@withViewport
@withStyles(styles)
export default class Menu extends React.Component {
  static propTypes = {
    isOpen: React.PropTypes.bool,
    menuItems: React.PropTypes.shape({
      categories: React.PropTypes.array,
      projects: React.PropTypes.array
    }).isRequired,
    viewport: React.PropTypes.shape({
      width: React.PropTypes.number.isRequired,
      height: React.PropTypes.number.isRequired
    }).isRequired
  };

  constructor(props) {
    super(props);

    this.defaultProps = {
      isOpen: false
    };
  }

  openMenu() {
    MenuActions.openMenu(true);
  }

  closeMenu() {
    MenuActions.openMenu(false);
  }

  render() {
    const categories = this.props.menuItems.categories;
    // const projects = this.props.menuItems.projects;

    let displayStyle = (this.props.viewport.width <= 991 && this.props.isOpen === true) ?
    {display: 'block'} :
    {display: 'none'};

    return (

            <Grid>

                <Link to='/' className='LogoContainer' onClick={() => { this.closeMenu(); }}>
                  Toni Karttunen
                </Link>

                  <MenuToggle isOpen={this.props.isOpen}/>

            </Grid>

        <Grid className='Menu'>
          {categories.map(item => {
            return (
              <Row key={item.title}>
                <Col md={12}>

                    <Link
                      to={item.url}
                      onClick={() => { this.closeMenu(); }}
                    >
                      {item.title}
                    </Link>

                </Col>
              </Row>
            );
          })}
          {/*
            <Row>
              <Col md={12}>

                  {projects.map(project => {
                    return (

                        <Link
                          to={project.url}
                          onClick={
                            () => {
                              this.closeMenu();
                            }
                          }>
                          {project.title}
                        </Link>

                    );
                  })}

              </Col>
            </Row>
          */}
        </Grid>

    );
  }
}
<commit_msg>Make touch targets larger in mobile menu<commit_after>// Menu

import React from 'react/addons';
import { Link } from 'react-router';
import { Grid, Row, Col } from 'react-bootstrap';
import MenuToggle from '../../components/MenuToggle';
import styles from './Menu.less';
import withViewport from '../../decorators/withViewport';
import withStyles from '../../decorators/withStyles';
import MenuActions from '../../actions/MenuActions';

@withViewport
@withStyles(styles)
export default class Menu extends React.Component {
  static propTypes = {
    isOpen: React.PropTypes.bool,
    menuItems: React.PropTypes.shape({
      categories: React.PropTypes.array,
      projects: React.PropTypes.array
    }).isRequired,
    viewport: React.PropTypes.shape({
      width: React.PropTypes.number.isRequired,
      height: React.PropTypes.number.isRequired
    }).isRequired
  };

  constructor(props) {
    super(props);

    this.defaultProps = {
      isOpen: false
    };
  }

  openMenu() {
    MenuActions.openMenu(true);
  }

  closeMenu() {
    MenuActions.openMenu(false);
  }

  render() {
    const categories = this.props.menuItems.categories;
    // const projects = this.props.menuItems.projects;

    let displayStyle = (this.props.viewport.width <= 991 && this.props.isOpen === true) ?
    {display: 'block'} :
    {display: 'none'};

    return (

            <Grid>

                <Link to='/' className='LogoContainer' onClick={() => { this.closeMenu(); }}>
                  Toni Karttunen
                </Link>

                  <MenuToggle isOpen={this.props.isOpen}/>

            </Grid>

        <Grid className='Menu'>
          {categories.map(item => {
            return (
              <Row key={item.title}>
                <Col md={12}>
                  <Link
                    to={item.url}
                    onClick={() => { this.closeMenu(); }}
                  >

{item.title}

                  </Link>
                </Col>
              </Row>
            );
          })}
        </Grid>

    );
  }
}
<|endoftext|>


<commit_before>import React from "react";
import { withPrefix } from "gatsby-link";
import Img from "gatsby-image";
import styled from "styled-components";

import * as palette from "../../utils/styles";

const Hero = ({ avatar }) => {
  const NavBar = styled.div`
    height: 4rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: ${palette.SECONDARY_COLOR};
  `;
  const NavLeft = styled.div`
    padding: 1rem;
    position: relative;
    opacity: 1;
    align-self: self-start;
    display: flex;
    align-items: center;
    justify-content: center;
a {text-decoration:none;}
    a img {
      height: 2rem;
      margin: 0;
    }
    img {
      height: 100%;
      margin: 0;
      vertical-align: middle;
    }
    h3 {
      margin: 0 0 0 1rem;
    }
    @media (max-width: 991px) {
      h3 {
        display: none;
      }
    }
  `;
  const NavRight = styled.div`
    padding: 1rem;
    position: relative;
    opacity: 1;
    align-self: self-end;
    display: flex;
    align-items: center;
    justify-content: center;
    img {
      height: 1.618rem;
      margin: 0 0 0 0;
    }
    h3 {
      margin: 0 0 0 1rem;
    }
  `;

  const toggleMenu = () => {
    const wrapper = document.getElementById("nav");
    wrapper.classList.toggle("open--menu");
  };

  const closeMenu = () => {
    const wrapper = document.getElementById("nav");
    wrapper.classList.remove("open--menu");
  };

  return (

      <NavBar>
        <NavLeft>

I AM MATTHIAS

        </NavLeft>
        <NavRight>

ABOUT

        </NavRight>
      </NavBar>

  );
};

export default Hero;
<commit_msg>Link styling<commit_after>import React from "react";
import { withPrefix } from "gatsby-link";
import Img from "gatsby-image";
import styled from "styled-components";

import * as palette from "../../utils/styles";

const Hero = ({ avatar }) => {
  const NavBar = styled.div`
    height: 4rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: ${palette.SECONDARY_COLOR};
  `;
  const NavLeft = styled.div`
    padding: 1rem;
    position: relative;
    opacity: 1;
    align-self: self-start;
    display: flex;
    align-items: center;
    justify-content: center;
a {text-decoration:none;}
    a img {
      height: 2rem;
      margin: 0;
    }
    img {
      height: 100%;
      margin: 0;
      vertical-align: middle;
    }
    h3 {
      margin: 0 0 0 1rem;
    }
    @media (max-width: 991px) {
      h3 {
        display: none;
      }
    }
  `;
  const NavRight = styled.div`
a {text-decoration:none;}
    padding: 1rem;
    position: relative;
    opacity: 1;
    align-self: self-end;
    display: flex;
    align-items: center;
    justify-content: center;
    img {
      height: 1.618rem;
      margin: 0 0 0 0;
    }
    h3 {
      margin: 0 0 0 1rem;
    }
  `;

  const toggleMenu = () => {
    const wrapper = document.getElementById("nav");
    wrapper.classList.toggle("open--menu");
  };

  const closeMenu = () => {
    const wrapper = document.getElementById("nav");
    wrapper.classList.remove("open--menu");
  };

  return (

      <NavBar>
        <NavLeft>

I AM MATTHIAS

        </NavLeft>
        <NavRight>

ABOUT

        </NavRight>
      </NavBar>

  );
};

export default Hero;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import Link from 'gatsby-link'
import svgRightArrow from '../images/right-arrow.svg'

const months = [
  'Jan',
  'Feb',
  'Mar',
  'Apr',
  'May',
  'Jun',
  'Jul',
  'Aug',
  'Sep',
  'Oct',
  'Nov',
  'Dec'
]

const currentMonth = months[new Date().getMonth()]

export default class PostsList extends Component {
  state = {
    search: '',
    currentFilter: 'all',
    allPosts: [...this.props.posts],
    general: [],
    fpv: [],
    software: [],
    smarthome: []
  }

  filter(pages) {
    let general = []
    let fpv = []
    let software = []
    let smarthome = []

    // Filter the data
    pages.map(page => {
      switch (page.node.frontmatter.tags) {
        case 'general':
          general.push(page)
          break
        case 'smart home':
          smarthome.push(page)
          break
        case 'software':
          software.push(page)
          break
        case 'fpv':
          fpv.push(page)
          break
      }
    })

    this.setState({
      general,
      fpv,
      software,
      smarthome
    })
  }

  handleFilterClick = e => {
    this.setState({
      currentFilter: e.target.dataset.filter
    })
  }

  handleSearch = e => {
    let { value } = e.target
    let { posts } = this.props

    const searchResults = posts.filter(post => {
      if (
        post.node.frontmatter.title
          .toLowerCase()
          .includes(value.toLowerCase()) ||
        post.node.excerpt.toLowerCase().includes(value.toLowerCase())
      ) {
        return post
      }
    })

    this.setState({
      search: value,
      allPosts: searchResults
    })
  }

  componentWillMount() {
    // Filter pages into categories
    this.filter(this.props.posts)
  }

  componentDidMount() {
    // Setup ESC listener
    document.addEventListener(
      'keydown',
      e => {
        e.code === 'Escape'
          ? this.setState({ search: '' }, () => this.handleSearch(e))
          : null
      },
      false
    )
  }

  render() {
    const { allPosts, currentFilter } = this.state

    return (

        {this.props.showCategories === 'yes' && (

            <button
              className={`category all ${currentFilter === 'all' && 'active'}`}
              data-filter="all"
              onClick={this.handleFilterClick}
            >
              All posts
            </button>
            {this.state.fpv.length > 0 && (
              <button
                className={`category fpv ${currentFilter === 'fpv' &&
                  'active'}`}
                data-filter="fpv"
                onClick={this.handleFilterClick}
              >
                FPV
              </button>
            )}
            {this.state.general.length > 0 && (
              <button
                className={`category general ${currentFilter === 'general' &&
                  'active'}`}
                data-filter="general"
                onClick={this.handleFilterClick}
              >
                General
              </button>
            )}
            {this.state.smarthome.length > 0 && (
              <button
                className={`category smart home ${currentFilter ===
                  'smarthome' && 'active'}`}
                data-filter="smarthome"
                onClick={this.handleFilterClick}
              >
                Smarthome
              </button>
            )}
            {this.state.software.length > 0 && (
              <button
                className={`category software ${currentFilter === 'software' &&
                  'active'}`}
                data-filter="software"
                onClick={this.handleFilterClick}
              >
                Software
              </button>
            )}

        )}
        {this.props.showSearch === 'yes' && (

            <input
              type="text"
              onChange={this.handleSearch}
              value={this.state.search}
              autoFocus
              placeholder="Search..."
              style={{ width: '100%', maxWidth: '300px' }}
            />

        )}

          {currentFilter === 'all' && allPosts
            ? allPosts.map(post => (

                  <Link
                    key={post.node.id}
                    to={post.node.frontmatter.path + '/'}
                  >

                      {currentMonth === post.node.frontmatter.date.split(' ')[0]
                        ? '🆕 '
                        : null}
                      {post.node.frontmatter.title}

                      {this.props.showImage === 'yes' && (

                          {post.node.frontmatter.ogImage !== null ? (

                          ) : (

                          )}

                      )}

                        {post.node.excerpt}

                        {post.node.timeToRead} min read by{' '}
                        {post.node.frontmatter.author} on{' '}
                        {post.node.frontmatter.date} in{' '}

                          {post.node.frontmatter.tags}

                      {this.props.showChevron === 'yes' && (

                      )}

                  </Link>

              ))
            : this.state[currentFilter].map(post => (

                  <Link
                    key={post.node.id}
                    to={post.node.frontmatter.path + '/'}
                  >

                      {currentMonth === post.node.frontmatter.date.split(' ')[0]
                        ? '🆕 '
                        : null}
                      {post.node.frontmatter.title}

                      {this.props.showImage === 'yes' && (

                          {post.node.frontmatter.ogImage !== null ? (

                          ) : (

                          )}

                      )}

                        {post.node.excerpt}

                        {post.node.timeToRead} min read by{' '}
                        {post.node.frontmatter.author} on{' '}
                        {post.node.frontmatter.date} in{' '}

                          {post.node.frontmatter.tags}

                      {this.props.showChevron === 'yes' && (

                      )}

                  </Link>

              ))}

    )
  }
}
<commit_msg>Remove autofocus as it opens up keyboard on mobile<commit_after>import React, { Component } from 'react'
import Link from 'gatsby-link'
import svgRightArrow from '../images/right-arrow.svg'

const months = [
  'Jan',
  'Feb',
  'Mar',
  'Apr',
  'May',
  'Jun',
  'Jul',
  'Aug',
  'Sep',
  'Oct',
  'Nov',
  'Dec'
]

const currentMonth = months[new Date().getMonth()]

export default class PostsList extends Component {
  state = {
    search: '',
    currentFilter: 'all',
    allPosts: [...this.props.posts],
    general: [],
    fpv: [],
    software: [],
    smarthome: []
  }

  filter(pages) {
    let general = []
    let fpv = []
    let software = []
    let smarthome = []

    // Filter the data
    pages.map(page => {
      switch (page.node.frontmatter.tags) {
        case 'general':
          general.push(page)
          break
        case 'smart home':
          smarthome.push(page)
          break
        case 'software':
          software.push(page)
          break
        case 'fpv':
          fpv.push(page)
          break
      }
    })

    this.setState({
      general,
      fpv,
      software,
      smarthome
    })
  }

  handleFilterClick = e => {
    this.setState({
      currentFilter: e.target.dataset.filter
    })
  }

  handleSearch = e => {
    let { value } = e.target
    let { posts } = this.props

    const searchResults = posts.filter(post => {
      if (
        post.node.frontmatter.title
          .toLowerCase()
          .includes(value.toLowerCase()) ||
        post.node.excerpt.toLowerCase().includes(value.toLowerCase())
      ) {
        return post
      }
    })

    this.setState({
      search: value,
      allPosts: searchResults
    })
  }

  componentWillMount() {
    // Filter pages into categories
    this.filter(this.props.posts)
  }

  componentDidMount() {
    // Setup ESC listener
    document.addEventListener(
      'keydown',
      e => {
        e.code === 'Escape'
          ? this.setState({ search: '' }, () => this.handleSearch(e))
          : null
      },
      false
    )
  }

  render() {
    const { allPosts, currentFilter } = this.state

    return (

        {this.props.showCategories === 'yes' && (

            <button
              className={`category all ${currentFilter === 'all' && 'active'}`}
              data-filter="all"
              onClick={this.handleFilterClick}
            >
              All posts
            </button>
            {this.state.fpv.length > 0 && (
              <button
                className={`category fpv ${currentFilter === 'fpv' &&
                  'active'}`}
                data-filter="fpv"
                onClick={this.handleFilterClick}
              >
                FPV
              </button>
            )}
            {this.state.general.length > 0 && (
              <button
                className={`category general ${currentFilter === 'general' &&
                  'active'}`}
                data-filter="general"
                onClick={this.handleFilterClick}
              >
                General
              </button>
            )}
            {this.state.smarthome.length > 0 && (
              <button
                className={`category smart home ${currentFilter ===
                  'smarthome' && 'active'}`}
                data-filter="smarthome"
                onClick={this.handleFilterClick}
              >
                Smarthome
              </button>
            )}
            {this.state.software.length > 0 && (
              <button
                className={`category software ${currentFilter === 'software' &&
                  'active'}`}
                data-filter="software"
                onClick={this.handleFilterClick}
              >
                Software
              </button>
            )}

        )}
        {this.props.showSearch === 'yes' && (

            <input
              type="text"
              onChange={this.handleSearch}
              value={this.state.search}
              placeholder="Search..."
              style={{ width: '100%', maxWidth: '300px' }}
            />

        )}

          {currentFilter === 'all' && allPosts
            ? allPosts.map(post => (

                  <Link
                    key={post.node.id}
                    to={post.node.frontmatter.path + '/'}
                  >

                      {currentMonth === post.node.frontmatter.date.split(' ')[0]
                        ? '🆕 '
                        : null}
                      {post.node.frontmatter.title}

                      {this.props.showImage === 'yes' && (

                          {post.node.frontmatter.ogImage !== null ? (

                          ) : (

                          )}

                      )}

                        {post.node.excerpt}

                        {post.node.timeToRead} min read by{' '}
                        {post.node.frontmatter.author} on{' '}
                        {post.node.frontmatter.date} in{' '}

                          {post.node.frontmatter.tags}

                      {this.props.showChevron === 'yes' && (

                      )}

                  </Link>

              ))
            : this.state[currentFilter].map(post => (

                  <Link
                    key={post.node.id}
                    to={post.node.frontmatter.path + '/'}
                  >

                      {currentMonth === post.node.frontmatter.date.split(' ')[0]
                        ? '🆕 '
                        : null}
                      {post.node.frontmatter.title}

                      {this.props.showImage === 'yes' && (

                          {post.node.frontmatter.ogImage !== null ? (

                          ) : (

                          )}

                      )}

                        {post.node.excerpt}

                        {post.node.timeToRead} min read by{' '}
                        {post.node.frontmatter.author} on{' '}
                        {post.node.frontmatter.date} in{' '}

                          {post.node.frontmatter.tags}

                      {this.props.showChevron === 'yes' && (

                      )}

                  </Link>

              ))}

    )
  }
}
<|endoftext|>