Commit b40d5362 authored by Steffen van Bergerem's avatar Steffen van Bergerem

Merge pull request #6256 from TeamDeltaQuadrant/5813-show-geolocation-on-osm

5813 show geolocation on osm
parents 26011865 1cdcc50c
......@@ -71,6 +71,7 @@ With the port to Bootstrap 3, app/views/terms/default.haml has a new structure.
* Add support for relay based public post federation [#6207](https://github.com/diaspora/diaspora/pull/6207)
* Bigger mobile publisher [#6261](https://github.com/diaspora/diaspora/pull/6261)
* Backend information panel & health checks for known pods [#6290](https://github.com/diaspora/diaspora/pull/6290)
* Allow users to view a posts locations on an OpenStreetMap [#6256](https://github.com/diaspora/diaspora/pull/6256)
# 0.5.4.0
......
......@@ -128,6 +128,9 @@ gem "rails-i18n", "4.0.5"
gem "markerb", "1.1.0"
gem "messagebus_ruby_api", "1.0.3"
# Map
gem "leaflet-rails", "0.7.4"
# Parsing
gem "nokogiri", "1.6.6.2"
......
......@@ -418,6 +418,7 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.10.0)
leaflet-rails (0.7.4)
listen (3.0.3)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
......@@ -841,6 +842,7 @@ DEPENDENCIES
jshintrb (= 0.3.0)
json (= 1.8.3)
json-schema (= 2.5.1)
leaflet-rails (= 0.7.4)
logging-rails (= 0.5.0)
markerb (= 1.1.0)
messagebus_ruby_api (= 1.0.3)
......
......@@ -29,7 +29,6 @@ app.views.Content = app.views.Base.extend({
return photos;
},
expandPost: function(evt) {
var el = $(this.el).find('.collapsible');
el.removeClass('collapsed').addClass('opened');
......@@ -40,8 +39,8 @@ app.views.Content = app.views.Base.extend({
},
location: function(){
var address = this.model.get('address')? this.model.get('address') : '';
return address;
var location = this.model.get("location")? this.model.get("location") : "";
return location;
},
collapseOversized : function() {
......@@ -155,4 +154,5 @@ app.views.SPVOpenGraph = app.views.OpenGraph.extend({
// override with nothing
}
});
// @license-end
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.LocationStream = app.views.Content.extend({
templateName: "status-message-location"
events: {
"click .near-from": "toggleMap"
},
templateName: "status-message-location",
toggleMap: function () {
var mapContainer = this.$el.find(".mapContainer");
if (mapContainer.hasClass("empty")) {
var location = this.model.get("location");
mapContainer.css("height", "150px");
if (location.lat) {
// If map function is enabled the maptiles from the Heidelberg University are used by default.
var map = L.map(mapContainer[0]).setView([location.lat, location.lng], 14);
var tiles = L.tileLayer("http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {
attribution: "Map data &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"rendering <a href='http://giscience.uni-hd.de/'>" +
"GIScience Research Group @ Heidelberg University</a>",
maxZoom: 18,
});
// If the mapbox option is enabled in the diaspora.yml, the mapbox tiles with the podmin's credentials are used.
if (gon.appConfig.map.mapbox.enabled) {
tiles = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
id: gon.appConfig.map.mapbox.id,
/* jshint camelcase: false */
accessToken: gon.appConfig.map.mapbox.access_token,
/* jshint camelcase: true */
attribution: "Map data &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"<a href='http://creativecommons.org/licenses/by-sa/2.0/''>CC-BY-SA</a>, " +
"Imagery © <a href='https://www.mapbox.com'>Mapbox</a>",
maxZoom: 18,
});
}
tiles.addTo(map);
L.marker(location).addTo(map);
mapContainer.removeClass("empty");
return map;
}
} else {
mapContainer.toggle();
}
}
});
// @license-end
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.SinglePostContent = app.views.Base.extend({
events: {
"click .near-from": "toggleMap"
},
templateName: "single-post-viewer/single-post-content",
tooltipSelector: "time, .post_scope",
......@@ -10,8 +14,7 @@ app.views.SinglePostContent = app.views.Base.extend({
"#real-post-content" : "postContentView",
".oembed" : "oEmbedView",
".opengraph" : "openGraphView",
".status-message-location" : "postLocationStreamView",
".poll": "pollView"
".poll": "pollView",
},
initialize : function() {
......@@ -23,8 +26,59 @@ app.views.SinglePostContent = app.views.Base.extend({
this.pollView = new app.views.Poll({ model: this.model });
},
postLocationStreamView : function(){
return new app.views.LocationStream({ model : this.model});
map : function(){
if (this.$el.find(".mapContainer")){
// find and set height of mapContainer to max size of the container
// which is necessary to have all necessary tiles prerendered
var mapContainer = this.$el.find(".mapContainer");
mapContainer.css("height", "200px");
// get location data and render map
var location = this.model.get("location");
// If map function is enabled the maptiles from the Heidelberg University are used by default.
var map = L.map(mapContainer[0]).setView([location.lat, location.lng], 14);
var tiles = L.tileLayer("http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}", {
attribution: "Map data &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"rendering <a href='http://giscience.uni-hd.de/'>" +
"GIScience Research Group @ Heidelberg University</a>",
maxZoom: 18,
});
// If the mapbox option is enabled in the diaspora.yml, the mapbox tiles with the podmin's credentials are used.
if (gon.appConfig.map.mapbox.enabled) {
tiles = L.tileLayer("https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}", {
id: gon.appConfig.map.mapbox.id,
/* jshint camelcase: false */
accessToken: gon.appConfig.map.mapbox.access_token,
/* jshint camelcase: true */
attribution: "Map data &copy; <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors, " +
"<a href='http://creativecommons.org/licenses/by-sa/2.0/''>CC-BY-SA</a>, " +
"Imagery © <a href='https://www.mapbox.com'>Mapbox</a>",
maxZoom: 18,
});
}
tiles.addTo(map);
// set mapContainer size to a smaller preview size
mapContainer.css("height", "75px");
map.invalidateSize();
// put marker on map
L.marker(location).addTo(map);
return map;
}
},
toggleMap: function () {
$(".mapContainer").height($(".small-map")[0] ? 200 : 50);
$(".leaflet-control-zoom").css("display", $(".small-map")[0] ? "block" : "none");
$(".mapContainer").toggleClass("small-map");
},
presenter : function() {
......@@ -37,6 +91,10 @@ app.views.SinglePostContent = app.views.Base.extend({
showPost : function() {
return (app.currentUser.get("showNsfw")) || !this.model.get("nsfw");
},
postRenderTemplate : function(){
_.defer(_.bind(this.map, this));
}
});
// @license-end
......@@ -45,3 +45,4 @@
//= require osmlocator
//= require bootstrap-switch
//= require blueimp-gallery
//= require leaflet
......@@ -42,6 +42,10 @@
@import 'single-post-view';
@import 'new_styles/poll';
/* map*/
@import 'leaflet';
@import 'map';
/* conversations */
@import 'conversations';
......
.mapContainer {
position: relative;
overflow: hidden;
}
.near-from:hover {
cursor: pointer;
text-decoration: underline;
}
.leaflet-control-zoom {
display: none;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 0;
}
.leaflet-right .leaflet-control {
margin-right: 0;
}
......@@ -23,6 +23,14 @@
padding-left: 10px;
}
}
.near-from {
color: $text-grey;
font-size: 12px;
margin: 10px 20px 0px 15px;
}
.mapContainer {
margin: 10px 20px 0px 15px;
}
.row.reshare {
border-top: 1px solid lighten($border-grey,5%);
padding-top: 10px;
......
......@@ -83,10 +83,13 @@
float: left;
margin-top: 6px;
}
.status-message-location .near-from {
.status-message-location {
font-size: $font-size-small;
color: $text-grey;
}
.leaflet-control-zoom {
display: block;
}
.grey { color: $text-grey; }
.post-content p:last-of-type { margin-bottom: 0; }
.nsfw-shield {
......
......@@ -57,7 +57,6 @@
{{t "stream.via" provider=provider_display_name}}
{{/if}}
{{/if}}
<div class="status-message-location" />
</div>
{{#unless root}}
<div id="single-post-moderation" />
......@@ -68,6 +67,14 @@
<div id="single-post-actions" class="col-md-4" />
{{/unless}}
</div>
{{#if location.lat}}
<div class="row">
<div class='near-from'>
{{t "publisher.near_from" location=location.address}}
</div>
<div class="mapContainer small-map"></div>
</div>
{{/if}}
{{#if root}}
<div class="row reshare">
<div class="col-md-8" id="reshare-info">
......
{{#if location}}
{{#if location.address}}
<div class='near-from'>
{{ t "publisher.near_from" location=location}}
{{t "publisher.near_from" location=location.address}}
</div>
<div>
<div class="mapContainer empty"></div>
</div>
{{/if}}
......@@ -145,7 +145,8 @@ class ApplicationController < ActionController::Base
def gon_set_appconfig
gon.push(appConfig: {
chat: {enabled: AppConfig.chat.enabled?},
settings: {podname: AppConfig.settings.pod_name}
settings: {podname: AppConfig.settings.pod_name},
map: {mapbox: AppConfig.map.mapbox}
})
end
......
......@@ -3,7 +3,6 @@
# the COPYRIGHT file.
class Reshare < Post
belongs_to :root, :class_name => 'Post', :foreign_key => :root_guid, :primary_key => :guid
validate :root_must_be_public
validates_presence_of :root, :on => :create
......@@ -45,8 +44,12 @@ class Reshare < Post
absolute_root.try(:photos) || super
end
def address
absolute_root.try(:location).try(:address)
def post_location
{
address: absolute_root.try(:location).try(:address),
lat: absolute_root.try(:location).try(:lat),
lng: absolute_root.try(:location).try(:lng)
}
end
def poll
......
......@@ -158,8 +158,12 @@ class StatusMessage < Post
self.open_graph_url = self.message.urls[0]
end
def address
location.try(:address)
def post_location
{
address: location.try(:address),
lat: location.try(:lat),
lng: location.try(:lng)
}
end
protected
......
......@@ -31,7 +31,7 @@ class PostPresenter < BasePresenter
photos: build_photos_json,
root: root,
title: title,
address: @post.address,
location: @post.post_location,
poll: @post.poll,
already_participated_in_poll: already_participated_in_poll,
participation: participate?,
......
......@@ -46,6 +46,7 @@
"HandlebarsTemplates",
"ImagePaths",
"jsxc",
"L",
"MBP",
"Routes",
"OSM",
......
......@@ -76,6 +76,11 @@ defaults:
log:
file: 'log/vines.log'
level: 'info'
map:
mapbox:
enabled: false
id:
access_token:
privacy:
jquery_cdn: false
google_analytics_key:
......
......@@ -327,6 +327,20 @@ configuration: ## Section
## The debug level logs all XML sent and received by the server.
#level: 'info'
## Displays the location of a post in a map. Per default we are using the map
## tiles of the Heidelberg University (http://giscience.uni-hd.de).
## You also have the possibility to use the map tiles of https://www.mapbox.com
## which is probably more reliable. There you have to create an account to get
## an ID and an access token which is limited. If you want to get an unlimited
## account you can write an email to team@diasporafoundation.org.
## Please enable mapbox and fill out your id and access_token.
map: ##Section
mapbox:
# enabled: false
# id: 'your.id'
# access_token: 'youraccesstoken'
## Settings potentially affecting the privacy of your users.
privacy: ## Section
......
......@@ -140,9 +140,9 @@ FactoryGirl.define do
end
factory(:location) do
address "unicorn city"
lat 1
lng 2
address "Fernsehturm Berlin, Berlin, Germany"
lat 52.520645
lng 13.409779
end
factory(:poll) do
......
......@@ -24,5 +24,10 @@ describe("app.views.Content", function(){
this.post.set({post_type : "Reshare"});
expect(this.view.presenter().isReshare).toBeTruthy();
});
it("provides location", function(){
this.post.set({location : factory.location()});
expect(this.view.presenter().location).toEqual(factory.location());
});
});
});
describe("app.views.LocationStream", function() {
beforeEach(function(){
this.post = factory.post();
this.view = new app.views.LocationStream({model : this.post});
/* jshint camelcase: false */
gon.appConfig = {map: { mapbox: {enabled: true, id: "yourID", access_token: "yourAccessToken" }}};
/* jshint camelcase: true */
});
describe("toggleMap", function() {
context("with location provided", function() {
beforeEach(function(){
this.post.set({location : factory.location()}); // set location
spec.content().html(this.view.render().el); // loads html element to the page
});
it("should contain a map container", function() {
expect(spec.content()).toContainElement(".mapContainer");
});
it("should initialize map", function() {
expect($(".mapContainer")).toHaveClass("empty");
this.view.toggleMap();
expect($(".mapContainer")).not.toHaveClass("empty");
});
it("should change display status on every click", function() {
this.view.toggleMap();
expect($(".mapContainer")).toHaveCss({display: "block"});
this.view.toggleMap();
expect($(".mapContainer")).toHaveCss({display: "none"});
});
});
context("without location provided", function() {
beforeEach(function(){
spec.content().html(this.view.render().el);
});
it("should not initialize the map", function() {
expect(spec.content()).not.toContainElement(".mapContainer");
});
});
});
});
describe("app.views.Location", function(){
beforeEach(function(){
OSM = {};
OSM.Locator = function(){return { getAddress:function(){}}};
this.view = new app.views.Location();
......
describe("app.views.SinglePostContent", function() {
beforeEach(function(){
this.post = factory.post();
this.view = new app.views.SinglePostContent({model : this.post});
gon.appConfig = { map: {mapbox: {enabled: true, id: "yourID", accessToken: "yourAccessToken" }}};
});
describe("toggleMap", function() {
context("with location provided", function() {
beforeEach(function(){
this.post.set({location : factory.location()}); // set location
spec.content().html(this.view.render().el); // loads html element to the page
});
it("should contain a map container", function() {
expect(spec.content()).toContainElement(".mapContainer");
});
it("should provide a small map", function() {
expect($(".mapContainer")).toHaveClass("small-map");
expect($(".mapContainer").height() < 100).toBeTruthy();
expect($(".mapContainer")).toBeVisible();
});
it("should toggle class small-map on every click", function(){
this.view.toggleMap();
expect($(".mapContainer")).not.toHaveClass("small-map");
this.view.toggleMap();
expect($(".mapContainer")).toHaveClass("small-map");
});
it("should change height on every click", function() {
this.view.toggleMap();
expect($(".mapContainer").height() > 100).toBeTruthy();
this.view.toggleMap();
expect($(".mapContainer").height() < 100).toBeTruthy();
});
});
context("without location provided", function() {
beforeEach(function(){
spec.content().html(this.view.render().el);
});
it("should not initialize the map", function() {
expect(spec.content()).not.toContainElement(".mapContainer");
});
});
});
});
......@@ -148,6 +148,14 @@ var factory = {
}, overrides);
},
location : function() {
return {
address: "Starco Mart, Mission Street, San Francisco, Kalifornien, 94103, Vereinigte Staaten von Amerika",
lat: 37.78,
lng: -122.41
};
},
post : function(overrides) {
var defaultAttrs = _.extend(factory.postAttrs(), {"author" : this.author()});
return new app.models.Post(_.extend(defaultAttrs, overrides));
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment