From 9ef85e42ba4a3b10157b2aa0711e68acaf9e3f9e Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Wed, 5 Aug 2015 19:49:07 +0100 Subject: [PATCH 01/13] Import leaflet.contextmenu source files From https://github.com/aratcliffe/Leaflet.contextmenu latest commit 52a542da4f --- app/assets/javascripts/leaflet.contextmenu.js | 7 +++ .../stylesheets/leaflet.contextmenu.css | 54 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 app/assets/javascripts/leaflet.contextmenu.js create mode 100644 app/assets/stylesheets/leaflet.contextmenu.css diff --git a/app/assets/javascripts/leaflet.contextmenu.js b/app/assets/javascripts/leaflet.contextmenu.js new file mode 100644 index 000000000..dff20b412 --- /dev/null +++ b/app/assets/javascripts/leaflet.contextmenu.js @@ -0,0 +1,7 @@ +/* + Leaflet.contextmenu, a context menu for Leaflet. + (c) 2014, Adam Ratcliffe, GeoSmart Maps Limited + + @preserve +*/ +(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module!=="undefined"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(t){t.Map.mergeOptions({contextmenuItems:[]});t.Map.ContextMenu=t.Handler.extend({_touchstart:t.Browser.msPointer?"MSPointerDown":t.Browser.pointer?"pointerdown":"touchstart",statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(e){t.Handler.prototype.initialize.call(this,e);this._items=[];this._visible=false;var n=this._container=t.DomUtil.create("div",t.Map.ContextMenu.BASE_CLS,e._container);n.style.zIndex=1e4;n.style.position="absolute";if(e.options.contextmenuWidth){n.style.width=e.options.contextmenuWidth+"px"}this._createItems();t.DomEvent.on(n,"click",t.DomEvent.stop).on(n,"mousedown",t.DomEvent.stop).on(n,"dblclick",t.DomEvent.stop).on(n,"contextmenu",t.DomEvent.stop)},addHooks:function(){t.DomEvent.on(document,t.Browser.touch?this._touchstart:"mousedown",this._onMouseDown,this).on(document,"keydown",this._onKeyDown,this);this._map.on({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},removeHooks:function(){t.DomEvent.off(document,t.Browser.touch?this._touchstart:"mousedown",this._onMouseDown,this).off(document,"keydown",this._onKeyDown,this);this._map.off({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},showAt:function(e,n){if(e instanceof t.LatLng){e=this._map.latLngToContainerPoint(e)}this._showAtPoint(e,n)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(e){var n=this._container;if(!isNaN(e)){e=n.children[e]}if(e){this._removeItem(t.Util.stamp(e));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:e})}},removeAllItems:function(){var e;while(this._container.children.length){e=this._container.children[0];this._removeItem(t.Util.stamp(e))}},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;e'}else if(n.iconCls){m=''}h.innerHTML=m+n.text;h.href="#";t.DomEvent.on(h,"mouseover",this._onItemMouseOver,this).on(h,"mouseout",this._onItemMouseOut,this).on(h,"mousedown",t.DomEvent.stopPropagation).on(h,"click",a);return{id:t.Util.stamp(h),el:h,callback:a}},_removeItem:function(e){var n,i,o,s;for(o=0,s=this._items.length;on.x){i.style.left="auto";i.style.right=Math.max(n.x-e.x,0)+"px"}else{i.style.left=Math.max(e.x,0)+"px";i.style.right="auto"}if(e.y+o.y>n.y){i.style.top="auto";i.style.bottom=Math.max(n.y-e.y,0)+"px"}else{i.style.top=Math.max(e.y,0)+"px";i.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onMouseDown:function(t){this._hide()},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(e){t.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){t.DomUtil.removeClass(e.target||e.srcElement,"over")}});t.Map.addInitHook("addHandler","contextmenu",t.Map.ContextMenu);t.Mixin.ContextMenu={bindContextMenu:function(e){t.setOptions(this,e);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},_initContextMenu:function(){this._items=[];this.on("contextmenu",this._showContextMenu,this)},_showContextMenu:function(t){var e,n,i,o;if(this._map.contextmenu){n=this._map.mouseEventToContainerPoint(t.originalEvent);if(!this.options.contextmenuInheritItems){this._map.contextmenu.hideAllItems()}for(i=0,o=this.options.contextmenuItems.length;i Date: Wed, 5 Aug 2015 22:30:41 +0100 Subject: [PATCH 02/13] Implement context menu with: directions, add note, describe, centre. --- app/assets/javascripts/index.js | 54 ++++++++++++++++++++++++- app/assets/stylesheets/leaflet-all.scss | 1 + 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 085b615c7..8964cef1d 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -7,6 +7,7 @@ //= require leaflet.share //= require leaflet.polyline //= require leaflet.query +//= require leaflet.contextmenu //= require index/search //= require index/browse //= require index/export @@ -75,9 +76,60 @@ $(document).ready(function () { var params = OSM.mapParams(); + // TODO consider using a separate js file for the context menu additions + var context_describe = function(e){ + var precision = OSM.zoomPrecision(map.getZoom()); + OSM.router.route("/search?query=" + encodeURIComponent( + e.latlng.lat.toFixed(precision) + "," + e.latlng.lng.toFixed(precision) + )); + }; + + var context_directionsfrom = function(e){ + var precision = OSM.zoomPrecision(map.getZoom()); + OSM.router.route("/directions?" + querystring.stringify({ + route: e.latlng.lat.toFixed(precision) + ',' + e.latlng.lng.toFixed(precision) + ';' + $('#route_to').val() + })); + } + + var context_directionsto = function(e){ + var precision = OSM.zoomPrecision(map.getZoom()); + OSM.router.route("/directions?" + querystring.stringify({ + route: $('#route_from').val() + ';' + e.latlng.lat.toFixed(precision) + ',' + e.latlng.lng.toFixed(precision) + })); + } + + var context_addnote = function(e){ + // TODO this currently doesn't work correctly - I think the "route" needs to be chained to ensure it comes once the pan has finished. + map.panTo(e.latlng, {animate: false}); + OSM.router.route('/note/new'); + } + + var context_centrehere = function(e){ + map.panTo(e.latlng); + } + + // TODO internationalisation of the context menu strings var map = new L.OSM.Map("map", { zoomControl: false, - layerControl: false + layerControl: false, + contextmenu: true, + contextmenuWidth: 140, + contextmenuItems: [{ + text: 'Directions from here', + callback: context_directionsfrom + }, { + text: 'Directions to here', + callback: context_directionsto + }, '-', { + text: 'Add a note here', + callback: context_addnote + }, { + text: 'Show address', + callback: context_describe + }, { + text: 'Centre map here', + callback: context_centrehere + }] }); map.attributionControl.setPrefix(''); diff --git a/app/assets/stylesheets/leaflet-all.scss b/app/assets/stylesheets/leaflet-all.scss index 10ad2607a..82312e5c2 100644 --- a/app/assets/stylesheets/leaflet-all.scss +++ b/app/assets/stylesheets/leaflet-all.scss @@ -1,6 +1,7 @@ /* *= require leaflet *= require leaflet.locationfilter + *= require leaflet.contextmenu */ /* Override to serve images through the asset pipeline. */ From 6e12650a453461d8c98c17c82f70d1a0f2df3940 Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Fri, 7 Aug 2015 21:15:05 +0100 Subject: [PATCH 03/13] Add "Query features" to context menu --- app/assets/javascripts/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 8964cef1d..dbc4b8c33 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -108,6 +108,15 @@ $(document).ready(function () { map.panTo(e.latlng); } + var context_queryhere = function(e) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/query?lat=" + lat + "&lon=" + lng); + } + // TODO internationalisation of the context menu strings var map = new L.OSM.Map("map", { zoomControl: false, @@ -126,6 +135,9 @@ $(document).ready(function () { }, { text: 'Show address', callback: context_describe + }, { + text: 'Query features', + callback: context_queryhere }, { text: 'Centre map here', callback: context_centrehere From 8d0472f2cade9739f3b60cda17afe8289322e2f4 Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Sun, 9 Aug 2015 12:11:07 +0100 Subject: [PATCH 04/13] Allow users to get default contextmenu on shift-click --- app/assets/javascripts/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index dbc4b8c33..69e223b22 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -98,6 +98,7 @@ $(document).ready(function () { })); } + // TODO only allow this if zoomed in enough var context_addnote = function(e){ // TODO this currently doesn't work correctly - I think the "route" needs to be chained to ensure it comes once the pan has finished. map.panTo(e.latlng, {animate: false}); @@ -144,6 +145,14 @@ $(document).ready(function () { }] }); + $(document).on('mousedown', function(e){ + if(e.shiftKey){ + map.contextmenu.disable(); // on firefox, shift disables our contextmenu. we explicitly do this for all browsers. + }else{ + map.contextmenu.enable(); + } + }); + map.attributionControl.setPrefix(''); map.updateLayers(params.layers); From a2cf9b5b9f568c614ad8bbf8a477326b01cefb57 Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Sun, 9 Aug 2015 19:11:36 +0100 Subject: [PATCH 05/13] Enable/disable some context-menu options conditional on zoom --- app/assets/javascripts/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 69e223b22..6f081c617 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -98,9 +98,8 @@ $(document).ready(function () { })); } - // TODO only allow this if zoomed in enough var context_addnote = function(e){ - // TODO this currently doesn't work correctly - I think the "route" needs to be chained to ensure it comes once the pan has finished. + // I'd like this, instead of panning, to pass a query parameter about where to place the marker map.panTo(e.latlng, {animate: false}); OSM.router.route('/note/new'); } @@ -150,6 +149,9 @@ $(document).ready(function () { map.contextmenu.disable(); // on firefox, shift disables our contextmenu. we explicitly do this for all browsers. }else{ map.contextmenu.enable(); + // we also decide whether to disable some options that only like high zoom + map.contextmenu.setDisabled(3, map.getZoom() < 12); + map.contextmenu.setDisabled(5, map.getZoom() < 14); } }); From e5b5faad7228253147bc489402ef7640b071eaaf Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Sun, 9 Aug 2015 19:29:37 +0100 Subject: [PATCH 06/13] tidier code --- app/assets/javascripts/index.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 6f081c617..496ab29b9 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -76,31 +76,38 @@ $(document).ready(function () { var params = OSM.mapParams(); - // TODO consider using a separate js file for the context menu additions + // a separate js file would be nice for the context menu additions; however not clear if context menu can be added outside of context of map obj constructor var context_describe = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()); - OSM.router.route("/search?query=" + encodeURIComponent( - e.latlng.lat.toFixed(precision) + "," + e.latlng.lng.toFixed(precision) - )); + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng)); }; var context_directionsfrom = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()); + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); OSM.router.route("/directions?" + querystring.stringify({ - route: e.latlng.lat.toFixed(precision) + ',' + e.latlng.lng.toFixed(precision) + ';' + $('#route_to').val() + route: lat + ',' + lng + ';' + $('#route_to').val() })); } var context_directionsto = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()); + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); OSM.router.route("/directions?" + querystring.stringify({ - route: $('#route_from').val() + ';' + e.latlng.lat.toFixed(precision) + ',' + e.latlng.lng.toFixed(precision) + route: $('#route_from').val() + ';' + lat + ',' + lng })); } var context_addnote = function(e){ // I'd like this, instead of panning, to pass a query parameter about where to place the marker - map.panTo(e.latlng, {animate: false}); + map.panTo(e.latlng.wrap(), {animate: false}); OSM.router.route('/note/new'); } @@ -113,7 +120,6 @@ $(document).ready(function () { latlng = e.latlng.wrap(), lat = latlng.lat.toFixed(precision), lng = latlng.lng.toFixed(precision); - OSM.router.route("/query?lat=" + lat + "&lon=" + lng); } From 0fa051fb40d30978325679bcfec2d128cc15e9e5 Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Mon, 17 Aug 2015 08:55:48 +0100 Subject: [PATCH 07/13] Move leaflet.contextmenu 3rd party code to "vendor" folder --- Vendorfile | 5 +++++ .../assets/leaflet}/leaflet.contextmenu.css | 0 .../assets/leaflet}/leaflet.contextmenu.js | 0 3 files changed, 5 insertions(+) rename {app/assets/stylesheets => vendor/assets/leaflet}/leaflet.contextmenu.css (100%) rename {app/assets/javascripts => vendor/assets/leaflet}/leaflet.contextmenu.js (100%) diff --git a/Vendorfile b/Vendorfile index 097f557aa..6826e8e16 100644 --- a/Vendorfile +++ b/Vendorfile @@ -20,6 +20,11 @@ folder 'vendor/assets' do file "images/#{image}", "http://cdn.leafletjs.com/leaflet-0.7.3/images/#{image}" end + from 'git://github.com/aratcliffe/Leaflet.contextmenu.git' do + file 'leaflet.contextmenu.js', 'dist/leaflet.contextmenu.js' + file 'leaflet.contextmenu.css', 'dist/leaflet.contextmenu.css' + end + from 'git://github.com/kajic/leaflet-locationfilter.git' do file 'leaflet.locationfilter.css', 'src/locationfilter.css' file 'leaflet.locationfilter.js', 'src/locationfilter.js' diff --git a/app/assets/stylesheets/leaflet.contextmenu.css b/vendor/assets/leaflet/leaflet.contextmenu.css similarity index 100% rename from app/assets/stylesheets/leaflet.contextmenu.css rename to vendor/assets/leaflet/leaflet.contextmenu.css diff --git a/app/assets/javascripts/leaflet.contextmenu.js b/vendor/assets/leaflet/leaflet.contextmenu.js similarity index 100% rename from app/assets/javascripts/leaflet.contextmenu.js rename to vendor/assets/leaflet/leaflet.contextmenu.js From 35fbcf28154b6f15f697018016fbabc952e018a5 Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Wed, 19 Aug 2015 22:58:20 +0100 Subject: [PATCH 08/13] Move contextmenu callbacks to separate file --- app/assets/javascripts/index.js | 60 +++------------------ app/assets/javascripts/index/contextmenu.js | 47 ++++++++++++++++ 2 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 app/assets/javascripts/index/contextmenu.js diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 496ab29b9..01682d5f8 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -8,6 +8,7 @@ //= require leaflet.polyline //= require leaflet.query //= require leaflet.contextmenu +//= require index/contextmenu //= require index/search //= require index/browse //= require index/export @@ -76,53 +77,6 @@ $(document).ready(function () { var params = OSM.mapParams(); - // a separate js file would be nice for the context menu additions; however not clear if context menu can be added outside of context of map obj constructor - var context_describe = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng)); - }; - - var context_directionsfrom = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/directions?" + querystring.stringify({ - route: lat + ',' + lng + ';' + $('#route_to').val() - })); - } - - var context_directionsto = function(e){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/directions?" + querystring.stringify({ - route: $('#route_from').val() + ';' + lat + ',' + lng - })); - } - - var context_addnote = function(e){ - // I'd like this, instead of panning, to pass a query parameter about where to place the marker - map.panTo(e.latlng.wrap(), {animate: false}); - OSM.router.route('/note/new'); - } - - var context_centrehere = function(e){ - map.panTo(e.latlng); - } - - var context_queryhere = function(e) { - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/query?lat=" + lat + "&lon=" + lng); - } - // TODO internationalisation of the context menu strings var map = new L.OSM.Map("map", { zoomControl: false, @@ -131,22 +85,22 @@ $(document).ready(function () { contextmenuWidth: 140, contextmenuItems: [{ text: 'Directions from here', - callback: context_directionsfrom + callback: function(e){ context_directionsfrom(e, map) } }, { text: 'Directions to here', - callback: context_directionsto + callback: function(e){ context_directionsto(e, map) } }, '-', { text: 'Add a note here', - callback: context_addnote + callback: function(e){ context_addnote(e, map) } }, { text: 'Show address', - callback: context_describe + callback: function(e){ context_describe(e, map) } }, { text: 'Query features', - callback: context_queryhere + callback: function(e){ context_queryhere(e, map) } }, { text: 'Centre map here', - callback: context_centrehere + callback: function(e){ context_centrehere(e, map) } }] }); diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js new file mode 100644 index 000000000..bdc57567a --- /dev/null +++ b/app/assets/javascripts/index/contextmenu.js @@ -0,0 +1,47 @@ + var context_describe = function(e, map){ + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng)); + }; + + var context_directionsfrom = function(e, map){ + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + OSM.router.route("/directions?" + querystring.stringify({ + route: lat + ',' + lng + ';' + $('#route_to').val() + })); + } + + var context_directionsto = function(e, map){ + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + OSM.router.route("/directions?" + querystring.stringify({ + route: $('#route_from').val() + ';' + lat + ',' + lng + })); + } + + var context_addnote = function(e, map){ + // I'd like this, instead of panning, to pass a query parameter about where to place the marker + map.panTo(e.latlng.wrap(), {animate: false}); + OSM.router.route('/note/new'); + } + + var context_centrehere = function(e, map){ + map.panTo(e.latlng); + } + + var context_queryhere = function(e, map) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + OSM.router.route("/query?lat=" + lat + "&lon=" + lng); + } + + From 6b0451124c0fe1cb7275ce973a069a125386558e Mon Sep 17 00:00:00 2001 From: Dan Stowell Date: Mon, 12 Oct 2015 21:43:00 +0100 Subject: [PATCH 09/13] Fix missing semicolon warnings --- app/assets/javascripts/index.js | 12 ++++++------ app/assets/javascripts/index/contextmenu.js | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 01682d5f8..684685d51 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -85,22 +85,22 @@ $(document).ready(function () { contextmenuWidth: 140, contextmenuItems: [{ text: 'Directions from here', - callback: function(e){ context_directionsfrom(e, map) } + callback: function(e){ context_directionsfrom(e, map); } }, { text: 'Directions to here', - callback: function(e){ context_directionsto(e, map) } + callback: function(e){ context_directionsto(e, map); } }, '-', { text: 'Add a note here', - callback: function(e){ context_addnote(e, map) } + callback: function(e){ context_addnote(e, map); } }, { text: 'Show address', - callback: function(e){ context_describe(e, map) } + callback: function(e){ context_describe(e, map); } }, { text: 'Query features', - callback: function(e){ context_queryhere(e, map) } + callback: function(e){ context_queryhere(e, map); } }, { text: 'Centre map here', - callback: function(e){ context_centrehere(e, map) } + callback: function(e){ context_centrehere(e, map); } }] }); diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index bdc57567a..4a8f3cc1e 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -14,7 +14,7 @@ OSM.router.route("/directions?" + querystring.stringify({ route: lat + ',' + lng + ';' + $('#route_to').val() })); - } + }; var context_directionsto = function(e, map){ var precision = OSM.zoomPrecision(map.getZoom()), @@ -24,17 +24,17 @@ OSM.router.route("/directions?" + querystring.stringify({ route: $('#route_from').val() + ';' + lat + ',' + lng })); - } + }; var context_addnote = function(e, map){ // I'd like this, instead of panning, to pass a query parameter about where to place the marker map.panTo(e.latlng.wrap(), {animate: false}); OSM.router.route('/note/new'); - } + }; var context_centrehere = function(e, map){ map.panTo(e.latlng); - } + }; var context_queryhere = function(e, map) { var precision = OSM.zoomPrecision(map.getZoom()), @@ -42,6 +42,5 @@ lat = latlng.lat.toFixed(precision), lng = latlng.lng.toFixed(precision); OSM.router.route("/query?lat=" + lat + "&lon=" + lng); - } - + }; From 430978fab7c842d87992ff7d984b16943e66a6fe Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 12 Feb 2017 14:59:28 +0000 Subject: [PATCH 10/13] Improve context menu initialisation to avoid namespace pollution --- app/assets/javascripts/index.js | 35 +----- app/assets/javascripts/index/contextmenu.js | 125 +++++++++++++------- 2 files changed, 84 insertions(+), 76 deletions(-) diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 096290806..1ba2fbbde 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -77,42 +77,11 @@ $(document).ready(function () { var params = OSM.mapParams(); - // TODO internationalisation of the context menu strings var map = new L.OSM.Map("map", { zoomControl: false, layerControl: false, contextmenu: true, - contextmenuWidth: 140, - contextmenuItems: [{ - text: 'Directions from here', - callback: function(e){ context_directionsfrom(e, map); } - }, { - text: 'Directions to here', - callback: function(e){ context_directionsto(e, map); } - }, '-', { - text: 'Add a note here', - callback: function(e){ context_addnote(e, map); } - }, { - text: 'Show address', - callback: function(e){ context_describe(e, map); } - }, { - text: 'Query features', - callback: function(e){ context_queryhere(e, map); } - }, { - text: 'Centre map here', - callback: function(e){ context_centrehere(e, map); } - }] - }); - - $(document).on('mousedown', function(e){ - if(e.shiftKey){ - map.contextmenu.disable(); // on firefox, shift disables our contextmenu. we explicitly do this for all browsers. - }else{ - map.contextmenu.enable(); - // we also decide whether to disable some options that only like high zoom - map.contextmenu.setDisabled(3, map.getZoom() < 12); - map.contextmenu.setDisabled(5, map.getZoom() < 14); - } + contextmenuWidth: 140 }); map.attributionControl.setPrefix(''); @@ -182,6 +151,8 @@ $(document).ready(function () { L.control.scale() .addTo(map); + OSM.initializeContextMenu(map); + if (OSM.STATUS !== 'api_offline' && OSM.STATUS !== 'database_offline') { OSM.initializeNotes(map); if (params.layers.indexOf(map.noteLayer.options.code) >= 0) { diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index 4a8f3cc1e..f9f49db15 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -1,46 +1,83 @@ - var context_describe = function(e, map){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng)); - }; - - var context_directionsfrom = function(e, map){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/directions?" + querystring.stringify({ - route: lat + ',' + lng + ';' + $('#route_to').val() - })); - }; - - var context_directionsto = function(e, map){ - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/directions?" + querystring.stringify({ - route: $('#route_from').val() + ';' + lat + ',' + lng - })); - }; - - var context_addnote = function(e, map){ - // I'd like this, instead of panning, to pass a query parameter about where to place the marker - map.panTo(e.latlng.wrap(), {animate: false}); - OSM.router.route('/note/new'); - }; - - var context_centrehere = function(e, map){ - map.panTo(e.latlng); - }; - - var context_queryhere = function(e, map) { - var precision = OSM.zoomPrecision(map.getZoom()), - latlng = e.latlng.wrap(), - lat = latlng.lat.toFixed(precision), - lng = latlng.lng.toFixed(precision); - OSM.router.route("/query?lat=" + lat + "&lon=" + lng); +OSM.initializeContextMenu = function (map) { + map.contextmenu.addItem({ + text: "Directions from here", + callback: function directionsFromHere(e) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/directions?" + querystring.stringify({ + route: lat + "," + lng + ";" + $("#route_to").val() + })); + } + }); + + map.contextmenu.addItem({ + text: "Directions to here", + callback: function directionsToHere(e) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/directions?" + querystring.stringify({ + route: $("#route_from").val() + ";" + lat + "," + lng + })); + } + }); + + map.contextmenu.addItem({ + text: "Add a note here", + callback: function addNoteHere(e) { + // I'd like this, instead of panning, to pass a query parameter about where to place the marker + map.panTo(e.latlng.wrap(), {animate: false}); + OSM.router.route("/note/new"); + } + }); + + map.contextmenu.addItem({ + text: "Show address", + callback: function describeLocation(e) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/search?query=" + encodeURIComponent(lat + "," + lng)); + } + }); + + map.contextmenu.addItem({ + text: "Query features", + callback: function queryFeatures(e) { + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/query?lat=" + lat + "&lon=" + lng); + } + }); + + map.contextmenu.addItem({ + text: "Centre map here", + callback: function centreMap(e) { + map.panTo(e.latlng); + } + }); + + map.on("mousedown", function (e) { + if (e.shiftKey) map.contextmenu.disable(); + }).on("mouseup", function () { + map.contextmenu.enable(); + }); + + var updateMenu = function updateMenu () { + map.contextmenu.setDisabled(2, map.getZoom() < 12); + map.contextmenu.setDisabled(4, map.getZoom() < 14); }; + map.on("zoomend", updateMenu); + updateMenu(); +}; From e58a5c69d3e7cdedfcf6a056fdb74aa505c5e285 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 12 Feb 2017 15:03:56 +0000 Subject: [PATCH 11/13] Make context menu entries translateable --- app/assets/javascripts/index/contextmenu.js | 12 ++++++------ config/locales/en.yml | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index f9f49db15..8a4654b97 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -1,6 +1,6 @@ OSM.initializeContextMenu = function (map) { map.contextmenu.addItem({ - text: "Directions from here", + text: I18n.t("javascripts.context.directions_from"), callback: function directionsFromHere(e) { var precision = OSM.zoomPrecision(map.getZoom()), latlng = e.latlng.wrap(), @@ -14,7 +14,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: "Directions to here", + text: I18n.t("javascripts.context.directions_to"), callback: function directionsToHere(e) { var precision = OSM.zoomPrecision(map.getZoom()), latlng = e.latlng.wrap(), @@ -28,7 +28,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: "Add a note here", + text: I18n.t("javascripts.context.add_note"), callback: function addNoteHere(e) { // I'd like this, instead of panning, to pass a query parameter about where to place the marker map.panTo(e.latlng.wrap(), {animate: false}); @@ -37,7 +37,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: "Show address", + text: I18n.t("javascripts.context.show_address"), callback: function describeLocation(e) { var precision = OSM.zoomPrecision(map.getZoom()), latlng = e.latlng.wrap(), @@ -49,7 +49,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: "Query features", + text: I18n.t("javascripts.context.query_features"), callback: function queryFeatures(e) { var precision = OSM.zoomPrecision(map.getZoom()), latlng = e.latlng.wrap(), @@ -61,7 +61,7 @@ OSM.initializeContextMenu = function (map) { }); map.contextmenu.addItem({ - text: "Centre map here", + text: I18n.t("javascripts.context.centre_map"), callback: function centreMap(e) { map.panTo(e.latlng); } diff --git a/config/locales/en.yml b/config/locales/en.yml index 2414fcd1f..58c21db00 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2305,6 +2305,13 @@ en: nothing_found: No features found error: "Error contacting %{server}: %{error}" timeout: "Timeout contacting %{server}" + context: + directions_from: Directions from here + directions_to: Directions to here + add_note: Add a note here + show_address: Show address + query_features: Query features + centre_map: Centre map here redaction: edit: description: "Description" From e4f1588aa1322132e09538d21e6dc24db14ea644 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 12 Feb 2017 15:50:23 +0000 Subject: [PATCH 12/13] Update Leaflet.contextmenu to the 1.2.1 release --- Vendorfile | 2 +- vendor/assets/leaflet/leaflet.contextmenu.css | 14 +- vendor/assets/leaflet/leaflet.contextmenu.js | 581 +++++++++++++++++- 3 files changed, 585 insertions(+), 12 deletions(-) diff --git a/Vendorfile b/Vendorfile index 93ad74c7b..304be9e15 100644 --- a/Vendorfile +++ b/Vendorfile @@ -20,7 +20,7 @@ folder 'vendor/assets' do file "images/#{image}", "https://unpkg.com/leaflet@1.0.3/dist/images/#{image}" end - from 'git://github.com/aratcliffe/Leaflet.contextmenu.git' do + from 'git://github.com/aratcliffe/Leaflet.contextmenu.git', :tag => 'v1.2.1' do file 'leaflet.contextmenu.js', 'dist/leaflet.contextmenu.js' file 'leaflet.contextmenu.css', 'dist/leaflet.contextmenu.css' end diff --git a/vendor/assets/leaflet/leaflet.contextmenu.css b/vendor/assets/leaflet/leaflet.contextmenu.css index 55e405c6d..0b5e2defc 100644 --- a/vendor/assets/leaflet/leaflet.contextmenu.css +++ b/vendor/assets/leaflet/leaflet.contextmenu.css @@ -1,14 +1,14 @@ .leaflet-contextmenu { display: none; - box-shadow: 0 1px 7px rgba(0,0,0,0.4); - -webkit-border-radius: 4px; - border-radius: 4px; + box-shadow: 0 1px 7px rgba(0,0,0,0.4); + -webkit-border-radius: 4px; + border-radius: 4px; padding: 4px 0; background-color: #fff; cursor: default; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } .leaflet-contextmenu a.leaflet-contextmenu-item { @@ -51,4 +51,4 @@ .leaflet-contextmenu-separator { border-bottom: 1px solid #ccc; margin: 5px 0; -} \ No newline at end of file +} diff --git a/vendor/assets/leaflet/leaflet.contextmenu.js b/vendor/assets/leaflet/leaflet.contextmenu.js index dff20b412..a9b011d95 100644 --- a/vendor/assets/leaflet/leaflet.contextmenu.js +++ b/vendor/assets/leaflet/leaflet.contextmenu.js @@ -1,7 +1,580 @@ /* Leaflet.contextmenu, a context menu for Leaflet. - (c) 2014, Adam Ratcliffe, GeoSmart Maps Limited - - @preserve + (c) 2015, Adam Ratcliffe, GeoSmart Maps Limited + + @preserve */ -(function(t){var e;if(typeof define==="function"&&define.amd){define(["leaflet"],t)}else if(typeof module!=="undefined"){e=require("leaflet");module.exports=t(e)}else{if(typeof window.L==="undefined"){throw new Error("Leaflet must be loaded first")}t(window.L)}})(function(t){t.Map.mergeOptions({contextmenuItems:[]});t.Map.ContextMenu=t.Handler.extend({_touchstart:t.Browser.msPointer?"MSPointerDown":t.Browser.pointer?"pointerdown":"touchstart",statics:{BASE_CLS:"leaflet-contextmenu"},initialize:function(e){t.Handler.prototype.initialize.call(this,e);this._items=[];this._visible=false;var n=this._container=t.DomUtil.create("div",t.Map.ContextMenu.BASE_CLS,e._container);n.style.zIndex=1e4;n.style.position="absolute";if(e.options.contextmenuWidth){n.style.width=e.options.contextmenuWidth+"px"}this._createItems();t.DomEvent.on(n,"click",t.DomEvent.stop).on(n,"mousedown",t.DomEvent.stop).on(n,"dblclick",t.DomEvent.stop).on(n,"contextmenu",t.DomEvent.stop)},addHooks:function(){t.DomEvent.on(document,t.Browser.touch?this._touchstart:"mousedown",this._onMouseDown,this).on(document,"keydown",this._onKeyDown,this);this._map.on({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},removeHooks:function(){t.DomEvent.off(document,t.Browser.touch?this._touchstart:"mousedown",this._onMouseDown,this).off(document,"keydown",this._onKeyDown,this);this._map.off({contextmenu:this._show,mouseout:this._hide,mousedown:this._hide,movestart:this._hide,zoomstart:this._hide},this)},showAt:function(e,n){if(e instanceof t.LatLng){e=this._map.latLngToContainerPoint(e)}this._showAtPoint(e,n)},hide:function(){this._hide()},addItem:function(t){return this.insertItem(t)},insertItem:function(t,e){e=e!==undefined?e:this._items.length;var n=this._createItem(this._container,t,e);this._items.push(n);this._sizeChanged=true;this._map.fire("contextmenu.additem",{contextmenu:this,el:n.el,index:e});return n.el},removeItem:function(e){var n=this._container;if(!isNaN(e)){e=n.children[e]}if(e){this._removeItem(t.Util.stamp(e));this._sizeChanged=true;this._map.fire("contextmenu.removeitem",{contextmenu:this,el:e})}},removeAllItems:function(){var e;while(this._container.children.length){e=this._container.children[0];this._removeItem(t.Util.stamp(e))}},hideAllItems:function(){var t,e,n;for(e=0,n=this._items.length;e'}else if(n.iconCls){m=''}h.innerHTML=m+n.text;h.href="#";t.DomEvent.on(h,"mouseover",this._onItemMouseOver,this).on(h,"mouseout",this._onItemMouseOut,this).on(h,"mousedown",t.DomEvent.stopPropagation).on(h,"click",a);return{id:t.Util.stamp(h),el:h,callback:a}},_removeItem:function(e){var n,i,o,s;for(o=0,s=this._items.length;on.x){i.style.left="auto";i.style.right=Math.max(n.x-e.x,0)+"px"}else{i.style.left=Math.max(e.x,0)+"px";i.style.right="auto"}if(e.y+o.y>n.y){i.style.top="auto";i.style.bottom=Math.max(n.y-e.y,0)+"px"}else{i.style.top=Math.max(e.y,0)+"px";i.style.bottom="auto"}},_getElementSize:function(t){var e=this._size,n=t.style.display;if(!e||this._sizeChanged){e={};t.style.left="-999999px";t.style.right="auto";t.style.display="block";e.x=t.offsetWidth;e.y=t.offsetHeight;t.style.left="auto";t.style.display=n;this._sizeChanged=false}return e},_onMouseDown:function(t){this._hide()},_onKeyDown:function(t){var e=t.keyCode;if(e===27){this._hide()}},_onItemMouseOver:function(e){t.DomUtil.addClass(e.target||e.srcElement,"over")},_onItemMouseOut:function(e){t.DomUtil.removeClass(e.target||e.srcElement,"over")}});t.Map.addInitHook("addHandler","contextmenu",t.Map.ContextMenu);t.Mixin.ContextMenu={bindContextMenu:function(e){t.setOptions(this,e);this._initContextMenu();return this},unbindContextMenu:function(){this.off("contextmenu",this._showContextMenu,this);return this},_initContextMenu:function(){this._items=[];this.on("contextmenu",this._showContextMenu,this)},_showContextMenu:function(t){var e,n,i,o;if(this._map.contextmenu){n=this._map.mouseEventToContainerPoint(t.originalEvent);if(!this.options.contextmenuInheritItems){this._map.contextmenu.hideAllItems()}for(i=0,o=this.options.contextmenuItems.length;i'; + } else if (iconCls) { + html = ''; + } + + el.innerHTML = html + options.text; + el.href = '#'; + + L.DomEvent + .on(el, 'mouseover', this._onItemMouseOver, this) + .on(el, 'mouseout', this._onItemMouseOut, this) + .on(el, 'mousedown', L.DomEvent.stopPropagation) + .on(el, 'click', callback); + + if (L.Browser.touch) { + L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation); + } + + // Devices without a mouse fire "mouseover" on tap, but never “mouseout" + if (!L.Browser.pointer) { + L.DomEvent.on(el, 'click', this._onItemMouseOut, this); + } + + return { + id: L.Util.stamp(el), + el: el, + callback: callback + }; + }, + + _removeItem: function (id) { + var item, + el, + i, l, callback; + + for (i = 0, l = this._items.length; i < l; i++) { + item = this._items[i]; + + if (item.id === id) { + el = item.el; + callback = item.callback; + + if (callback) { + L.DomEvent + .off(el, 'mouseover', this._onItemMouseOver, this) + .off(el, 'mouseover', this._onItemMouseOut, this) + .off(el, 'mousedown', L.DomEvent.stopPropagation) + .off(el, 'click', callback); + + if (L.Browser.touch) { + L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation); + } + + if (!L.Browser.pointer) { + L.DomEvent.on(el, 'click', this._onItemMouseOut, this); + } + } + + this._container.removeChild(el); + this._items.splice(i, 1); + + return item; + } + } + return null; + }, + + _createSeparator: function (container, index) { + var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index); + + return { + id: L.Util.stamp(el), + el: el + }; + }, + + _createEventHandler: function (el, func, context, hideOnSelect) { + var me = this, + map = this._map, + disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled', + hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true; + + return function (e) { + if (L.DomUtil.hasClass(el, disabledCls)) { + return; + } + + if (hideOnSelect) { + me._hide(); + } + + if (func) { + func.call(context || map, me._showLocation); + } + + me._map.fire('contextmenu:select', { + contextmenu: me, + el: el + }); + }; + }, + + _insertElementAt: function (tagName, className, container, index) { + var refEl, + el = document.createElement(tagName); + + el.className = className; + + if (index !== undefined) { + refEl = container.children[index]; + } + + if (refEl) { + container.insertBefore(el, refEl); + } else { + container.appendChild(el); + } + + return el; + }, + + _show: function (e) { + this._showAtPoint(e.containerPoint, e); + }, + + _showAtPoint: function (pt, data) { + if (this._items.length) { + var map = this._map, + layerPoint = map.containerPointToLayerPoint(pt), + latlng = map.layerPointToLatLng(layerPoint), + event = L.extend(data || {}, {contextmenu: this}); + + this._showLocation = { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: pt + }; + + if (data && data.relatedTarget){ + this._showLocation.relatedTarget = data.relatedTarget; + } + + this._setPosition(pt); + + if (!this._visible) { + this._container.style.display = 'block'; + this._visible = true; + } + + this._map.fire('contextmenu.show', event); + } + }, + + _hide: function () { + if (this._visible) { + this._visible = false; + this._container.style.display = 'none'; + this._map.fire('contextmenu.hide', {contextmenu: this}); + } + }, + + _getIcon: function (options) { + return L.Browser.retina && options.retinaIcon || options.icon; + }, + + _getIconCls: function (options) { + return L.Browser.retina && options.retinaIconCls || options.iconCls; + }, + + _setPosition: function (pt) { + var mapSize = this._map.getSize(), + container = this._container, + containerSize = this._getElementSize(container), + anchor; + + if (this._map.options.contextmenuAnchor) { + anchor = L.point(this._map.options.contextmenuAnchor); + pt = pt.add(anchor); + } + + container._leaflet_pos = pt; + + if (pt.x + containerSize.x > mapSize.x) { + container.style.left = 'auto'; + container.style.right = Math.min(Math.max(mapSize.x - pt.x, 0), mapSize.x - containerSize.x - 1) + 'px'; + } else { + container.style.left = Math.max(pt.x, 0) + 'px'; + container.style.right = 'auto'; + } + + if (pt.y + containerSize.y > mapSize.y) { + container.style.top = 'auto'; + container.style.bottom = Math.min(Math.max(mapSize.y - pt.y, 0), mapSize.y - containerSize.y - 1) + 'px'; + } else { + container.style.top = Math.max(pt.y, 0) + 'px'; + container.style.bottom = 'auto'; + } + }, + + _getElementSize: function (el) { + var size = this._size, + initialDisplay = el.style.display; + + if (!size || this._sizeChanged) { + size = {}; + + el.style.left = '-999999px'; + el.style.right = 'auto'; + el.style.display = 'block'; + + size.x = el.offsetWidth; + size.y = el.offsetHeight; + + el.style.left = 'auto'; + el.style.display = initialDisplay; + + this._sizeChanged = false; + } + + return size; + }, + + _onKeyDown: function (e) { + var key = e.keyCode; + + // If ESC pressed and context menu is visible hide it + if (key === 27) { + this._hide(); + } + }, + + _onItemMouseOver: function (e) { + L.DomUtil.addClass(e.target || e.srcElement, 'over'); + }, + + _onItemMouseOut: function (e) { + L.DomUtil.removeClass(e.target || e.srcElement, 'over'); + } +}); + +L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu); +L.Mixin.ContextMenu = { + bindContextMenu: function (options) { + L.setOptions(this, options); + this._initContextMenu(); + + return this; + }, + + unbindContextMenu: function (){ + this.off('contextmenu', this._showContextMenu, this); + + return this; + }, + + addContextMenuItem: function (item) { + this.options.contextmenuItems.push(item); + }, + + removeContextMenuItemWithIndex: function (index) { + var items = []; + for (var i = 0; i < this.options.contextmenuItems.length; i++) { + if (this.options.contextmenuItems[i].index == index){ + items.push(i); + } + } + var elem = items.pop(); + while (elem !== undefined) { + this.options.contextmenuItems.splice(elem,1); + elem = items.pop(); + } + }, + + replaceContextMenuItem: function (item) { + this.removeContextMenuItemWithIndex(item.index); + this.addContextMenuItem(item); + }, + + _initContextMenu: function () { + this._items = []; + + this.on('contextmenu', this._showContextMenu, this); + }, + + _showContextMenu: function (e) { + var itemOptions, + data, pt, i, l; + + if (this._map.contextmenu) { + data = L.extend({relatedTarget: this}, e); + + pt = this._map.mouseEventToContainerPoint(e.originalEvent); + + if (!this.options.contextmenuInheritItems) { + this._map.contextmenu.hideAllItems(); + } + + for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) { + itemOptions = this.options.contextmenuItems[i]; + this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index)); + } + + this._map.once('contextmenu.hide', this._hideContextMenu, this); + + this._map.contextmenu.showAt(pt, data); + } + }, + + _hideContextMenu: function () { + var i, l; + + for (i = 0, l = this._items.length; i < l; i++) { + this._map.contextmenu.removeItem(this._items[i]); + } + this._items.length = 0; + + if (!this.options.contextmenuInheritItems) { + this._map.contextmenu.showAllItems(); + } + } +}; + +var classes = [L.Marker, L.Path], + defaultOptions = { + contextmenu: false, + contextmenuItems: [], + contextmenuInheritItems: true + }, + cls, i, l; + +for (i = 0, l = classes.length; i < l; i++) { + cls = classes[i]; + + // L.Class should probably provide an empty options hash, as it does not test + // for it here and add if needed + if (!cls.prototype.options) { + cls.prototype.options = defaultOptions; + } else { + cls.mergeOptions(defaultOptions); + } + + cls.addInitHook(function () { + if (this.options.contextmenu) { + this._initContextMenu(); + } + }); + + cls.include(L.Mixin.ContextMenu); +} +return L.Map.ContextMenu; +}); From 8376a430b6e8fb3de96b66a05c36cb8ac67e9c59 Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Sun, 12 Feb 2017 17:26:17 +0000 Subject: [PATCH 13/13] Allow context menu to add notes without panning the map --- app/assets/javascripts/index/contextmenu.js | 9 ++++-- app/assets/javascripts/index/new_note.js | 36 +++++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index 8a4654b97..1e7251ec6 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -30,9 +30,12 @@ OSM.initializeContextMenu = function (map) { map.contextmenu.addItem({ text: I18n.t("javascripts.context.add_note"), callback: function addNoteHere(e) { - // I'd like this, instead of panning, to pass a query parameter about where to place the marker - map.panTo(e.latlng.wrap(), {animate: false}); - OSM.router.route("/note/new"); + var precision = OSM.zoomPrecision(map.getZoom()), + latlng = e.latlng.wrap(), + lat = latlng.lat.toFixed(precision), + lng = latlng.lng.toFixed(precision); + + OSM.router.route("/note/new?lat=" + lat + "&lon=" + lng); } }); diff --git a/app/assets/javascripts/index/new_note.js b/app/assets/javascripts/index/new_note.js index 397daa637..53697e65b 100644 --- a/app/assets/javascripts/index/new_note.js +++ b/app/assets/javascripts/index/new_note.js @@ -77,7 +77,9 @@ OSM.NewNote = function(map) { } page.pushstate = page.popstate = function (path) { - OSM.loadSidebarContent(path, page.load); + OSM.loadSidebarContent(path, function () { + page.load(path); + }); }; function newHalo(loc, a) { @@ -97,7 +99,7 @@ OSM.NewNote = function(map) { } } - page.load = function () { + page.load = function (path) { if (addNoteButton.hasClass("disabled")) return; if (addNoteButton.hasClass("active")) return; @@ -105,12 +107,34 @@ OSM.NewNote = function(map) { map.addLayer(noteLayer); - var mapSize = map.getSize(); - var markerPosition; + var params = querystring.parse(path.substring(path.indexOf('?') + 1)); + var markerLatlng; - markerPosition = [mapSize.x / 2, mapSize.y / 2]; + if (params.lat && params.lon) { + markerLatlng = L.latLng(params.lat, params.lon); - newNote = L.marker(map.containerPointToLatLng(markerPosition), { + var markerPosition = map.latLngToContainerPoint(markerLatlng), + mapSize = map.getSize(), + panBy = L.point(0, 0); + + if (markerPosition.x < 50) { + panBy.x = markerPosition.x - 50; + } else if (markerPosition.x > mapSize.x - 50) { + panBy.x = 50 - mapSize.x + markerPosition.x; + } + + if (markerPosition.y < 50) { + panBy.y = markerPosition.y - 50; + } else if (markerPosition.y > mapSize.y - 50) { + panBy.y = 50 - mapSize.y + markerPosition.y; + } + + map.panBy(panBy); + } else { + markerLatlng = map.getCenter(); + } + + newNote = L.marker(markerLatlng, { icon: noteIcons["new"], opacity: 0.9, draggable: true