From c9d1b074180e9d30a1a73d0a7d808b09366e2b45 Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Wed, 2 Aug 2017 19:13:38 +0200
Subject: [PATCH] [enh] ES settings: allow to define a google API key, for
 address resolution in maps

---
 www/index.html                                |  1 +
 www/js/config.js                              |  2 +-
 www/plugins/es/js/services/geo-services.js    | 26 ++++++--
 .../es/js/services/settings-services.js       |  5 +-
 .../templates/settings/plugin_settings.html   |  5 ++
 www/plugins/map/i18n/locale-fr-FR.json        |  9 +++
 .../js/controllers/settings-controllers.js    | 22 +++++++
 .../map/js/controllers/wot-controllers.js     | 62 ++++++++++---------
 www/plugins/map/js/plugin.js                  |  3 +-
 www/plugins/map/js/services/wot-services.js   | 28 +++++----
 .../map/templates/network/view_map.html       |  1 +
 .../settings/es_settings_extend.html          | 35 +++++++++++
 .../map/templates/wot/popup_marker.html       |  2 +-
 www/plugins/map/templates/wot/view_map.html   |  3 +
 14 files changed, 155 insertions(+), 49 deletions(-)
 create mode 100644 www/plugins/map/js/controllers/settings-controllers.js
 create mode 100644 www/plugins/map/templates/settings/es_settings_extend.html

diff --git a/www/index.html b/www/index.html
index 99346e3f..8774b6e5 100644
--- a/www/index.html
+++ b/www/index.html
@@ -198,6 +198,7 @@
   <script src="dist/dist_js/plugins/map/js/controllers/wot-controllers.js"></script>
   <script src="dist/dist_js/plugins/map/js/controllers/network-controllers.js"></script>
   <script src="dist/dist_js/plugins/map/js/controllers/user-controllers.js"></script>
+  <script src="dist/dist_js/plugins/map/js/controllers/settings-controllers.js"></script>
 
   <!-- RML9 plugin -->
   <!--<script src="dist/dist_js/plugins/rml9/plugin-01-add_button.js"></script>-->
diff --git a/www/js/config.js b/www/js/config.js
index 663467b0..8dd35f81 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -64,4 +64,4 @@ angular.module("cesium.config", [])
 	"newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug"
 })
 
-;
\ No newline at end of file
+;
diff --git a/www/plugins/es/js/services/geo-services.js b/www/plugins/es/js/services/geo-services.js
index c2e77408..e634e82c 100644
--- a/www/plugins/es/js/services/geo-services.js
+++ b/www/plugins/es/js/services/geo-services.js
@@ -10,7 +10,7 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
 
   })
 
-  .factory('esGeo', function($q, csConfig, csHttp) {
+  .factory('esGeo', function($rootScope, $q, csConfig, csSettings, csHttp) {
     'ngInject';
 
     var
@@ -22,7 +22,7 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
         searchByQuery: csHttp.get('nominatim.openstreetmap.org', 443, '/search.php?format=json')
       },
       google: {
-        apiKey: csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.googleApiKey,
+        apiKey: undefined,
         search: csHttp.get('maps.google.com', 443, '/maps/api/geocode/json')
       },
       searchByIP: csHttp.get('freegeoip.net', 80, '/json/:ip')
@@ -126,6 +126,24 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
         });
     }
 
+    // If no google api key in config: get it from settings
+    that.raw.google.apiKey = csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.googleApiKey;
+    if (!that.raw.google.apiKey) {
+      csSettings.ready()
+        .then(function() {
+
+          // Listen settings changed
+          function onSettingsChanged(data){
+            that.raw.google.enable = data.plugins && data.plugins.es && data.plugins.es.enableGoogleApi;
+            that.raw.google.apiKey = that.raw.google.enable && data.plugins.es.googleApiKey;
+          }
+          csSettings.api.data.on.changed($rootScope, onSettingsChanged, this);
+
+          onSettingsChanged(csSettings.data);
+        })
+    }
+
+
     return {
       point: {
         current: getCurrentPosition,
@@ -133,8 +151,8 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser
         searchByIP: searchPositionByIP
       },
       google: {
-        hasApiKey : function() {
-          return !!that.raw.google.apiKey;
+        isEnable: function() {
+          return that.raw.google.enable && that.raw.google.apiKey;
         },
         searchByAddress: googleSearchPositionByString
       }
diff --git a/www/plugins/es/js/services/settings-services.js b/www/plugins/es/js/services/settings-services.js
index aaeef5d7..1570d704 100644
--- a/www/plugins/es/js/services/settings-services.js
+++ b/www/plugins/es/js/services/settings-services.js
@@ -40,7 +40,10 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
             },
             invitations: {
               readTime: true
-            }
+            },
+            defaultCountry: undefined,
+            enableGoogleApi: false,
+            googleApiKey: undefined
           }
         }
     }, {plugins: {es: csConfig.plugins && csConfig.plugins.es || {}}}),
diff --git a/www/plugins/es/templates/settings/plugin_settings.html b/www/plugins/es/templates/settings/plugin_settings.html
index 20208d92..52c0cecc 100644
--- a/www/plugins/es/templates/settings/plugin_settings.html
+++ b/www/plugins/es/templates/settings/plugin_settings.html
@@ -64,5 +64,10 @@
       </div>
     </label>
 
+    <!-- Allow extension here -->
+    <cs-extension-point name="common"></cs-extension-point>
+
+
+
   </ion-content>
 </ion-view>
diff --git a/www/plugins/map/i18n/locale-fr-FR.json b/www/plugins/map/i18n/locale-fr-FR.json
index 7433568d..8070a7d0 100644
--- a/www/plugins/map/i18n/locale-fr-FR.json
+++ b/www/plugins/map/i18n/locale-fr-FR.json
@@ -36,6 +36,15 @@
     },
     "ERROR": {
       "LOCALIZE_ME_FAILED": "Impossible de récupérer votre position actuelle"
+    },
+    "SETTINGS": {
+      "MAP_DIVIDER": "Cartes",
+      "ENABLE_GOOGLE_API": "Activer les services Google API ?",
+      "ENABLE_GOOGLE_API_HELP": "Permet l'affichage dans la <b>carte des membres</b> des comptes ayant une adresse mais aucun positionnement GPS.",
+      "GOOGLE_API_KEY": "Clé d'API Google",
+      "BTN_GOOGLE_API": "Obtenir une clé",
+      "BTN_GOOGLE_API_WARNING": "nécessite d'avoir un compte Google",
+      "GOOGLE_API_KEY_PLACEHOLDER": "Exemple: AIzaqyAgszvWm0tM81x1sMK_ipDHBI7EowLqR7I"
     }
   }
 }
diff --git a/www/plugins/map/js/controllers/settings-controllers.js b/www/plugins/map/js/controllers/settings-controllers.js
new file mode 100644
index 00000000..a8489331
--- /dev/null
+++ b/www/plugins/map/js/controllers/settings-controllers.js
@@ -0,0 +1,22 @@
+angular.module('cesium.map.settings.controllers', ['cesium.services'])
+
+  // Configure menu items
+  .config(function(PluginServiceProvider, csConfig) {
+    'ngInject';
+
+    var enable = csConfig.plugins && csConfig.plugins.es;
+    if (enable) {
+      // Extend settings via extension points
+      PluginServiceProvider.extendState('app.es_settings', {
+        points: {
+          'common': {
+            templateUrl: "plugins/map/templates/settings/es_settings_extend.html"
+          }
+        }
+      });
+    }
+  })
+
+;
+
+
diff --git a/www/plugins/map/js/controllers/wot-controllers.js b/www/plugins/map/js/controllers/wot-controllers.js
index 391cf86d..ecd7d0c8 100644
--- a/www/plugins/map/js/controllers/wot-controllers.js
+++ b/www/plugins/map/js/controllers/wot-controllers.js
@@ -102,19 +102,8 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
       if ($scope.loading) {
 
         // Load the map (and init if need)
-        $scope.loadMap().then(function(map) {
-
-          // Load indicator
-          map.fire('dataloading');
-
-          // Load data
-          return $scope.load()
-
-            // Hide loading indicator
-            .then(function() {
-              map.fire('dataload');
-            });
-        });
+        $scope.loadMap()
+          .then($scope.load);
       }
       else {
         // Make sur to have previous center coordinate defined in the location URL
@@ -127,16 +116,24 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
       return leafletData.getMap($scope.mapId).then(function(map) {
         if (!$scope.map.loading) return map; // already loaded
 
+        // Add a refresh button
+        if (!UIUtils.screen.isSmall()) {
+          L.easyButton('icon ion-refresh', function(btn, map){
+              return $scope.load(map);
+            },
+            {position: 'topright'}
+          ).addTo(map);
+        }
+
         // Add loading control
-        var loadingControl = L.Control.loading({
+        L.Control.loading({
           position: 'topright',
           separate: true
-        });
-        loadingControl.addTo(map);
+        }).addTo(map);
 
         // Add localize me control
-        var localizeMe = MapUtils.control.localizeMe();
-        localizeMe.addTo(map);
+        MapUtils.control.localizeMe()
+          .addTo(map);
 
         // Add search control
         var searchTip = $interpolate($templateCache.get('plugins/map/templates/wot/item_search_tooltip.html'));
@@ -155,25 +152,28 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
     };
 
     // Load markers data
-    $scope.load = function() {
+    $scope.load = function(map) {
+      if (!map) {
+        return leafletData.getMap($scope.mapId).then(function(map) {
+          return $scope.load(map); // loop with the map object
+        });
+      }
+
       $scope.loading = true;
+      // Show loading indicator
+      map.fire('dataloading');
 
       // Load wot data
       return mapWot.load()
 
         .then(function(res) {
+          var markers = {};
+          var existingMarkerIds = _.keys($scope.map.markers);
           if (res && res.length) {
 
             var formatPubkey = $filter('formatPubkey');
             var markerTemplate = $templateCache.get('plugins/map/templates/wot/popup_marker.html');
 
-            // Sort with member first
-            /*res = _.sortBy(res, function(hit) {
-             var score = 0;
-             score += (!hit.uid) ? 100 : 0;
-             return -score;
-             });*/
-
             _.forEach(res, function (hit) {
               var type = hit.pending ? 'pending' : (hit.uid ? 'member' : 'wallet');
               var shortPubkey = formatPubkey(hit.pubkey);
@@ -193,11 +193,10 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
                 message: markerTemplate
               };
               var id = (hit.uid ? (hit.uid + ':' + hit.pubkey) : hit.pubkey).replace(/-/g, '_');
-              var wasExisting = !!$scope.map.markers[id];
-              $scope.map.markers[id] = marker;
+              markers[id] = marker;
 
               // Create a search marker (will be hide)
-              if (!wasExisting) {
+              if (!existingMarkerIds[id]) {
                 var searchText = hit.name + ((hit.uid && hit.uid != hit.name) ? (' | ' + hit.uid) : '') + ' | ' + shortPubkey;
                 var searchMarker = angular.merge({
                   type: type,
@@ -215,8 +214,11 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
               }
             });
           }
-
+          $scope.map.markers = markers;
           $scope.loading = false;
+
+          // hide loading indicator
+          map.fire('dataload');
         });
     };
 
diff --git a/www/plugins/map/js/plugin.js b/www/plugins/map/js/plugin.js
index d5bef332..ab125fb9 100644
--- a/www/plugins/map/js/plugin.js
+++ b/www/plugins/map/js/plugin.js
@@ -6,7 +6,8 @@ angular.module('cesium.map.plugin', [
     // Controllers
     'cesium.map.wot.controllers',
     'cesium.map.network.controllers',
-    'cesium.map.user.controllers'
+    'cesium.map.user.controllers',
+    'cesium.map.settings.controllers'
   ])
 
   // Configure plugin
diff --git a/www/plugins/map/js/services/wot-services.js b/www/plugins/map/js/services/wot-services.js
index a4f71a2c..de359673 100644
--- a/www/plugins/map/js/services/wot-services.js
+++ b/www/plugins/map/js/services/wot-services.js
@@ -26,7 +26,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
     };
 
     // Limit to profile with geo point
-    if (esGeo.google.hasApiKey()) {
+    if (options.searchAddress) {
       query.bool.should = [
         {exists: {field: "geoPoint"}},
         {exists: {field: "address"}},
@@ -63,6 +63,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
     options = options || {};
     options.from = options.from || 0;
     options.size = options.size || constants.DEFAULT_LOAD_SIZE;
+    options.searchAddress = esGeo.google.isEnable() && (angular.isDefined(options.searchAddress) ? options.searchAddress : true);
 
     var request = {
       query: createFilterQuery(options),
@@ -107,7 +108,7 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
 
         // Transform profile hits
         var commaRegexp = new RegExp('[,]');
-        var noPositionItems = [];
+        var searchAddressItems = [];
         var items = res.hits.hits.reduce(function(res, hit) {
           var pubkey =  hit._id;
 
@@ -117,13 +118,13 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
 
           // City & address
           item.city = hit._source.city;
-          item.fullAddress = item.city && ((hit._source.address ? hit._source.address+ ', ' : '') + item.city);
 
           // Set geo point
           item.geoPoint = hit._source.geoPoint;
           if (!item.geoPoint || !item.geoPoint.lat || !item.geoPoint.lon) {
-            if (!item.fullAddress) return res; // no city: exclude this item
-            noPositionItems.push(item);
+            if (!options.searchAddress || !item.city) return res; // no city: exclude this item
+            item.searchAddress = item.city && ((hit._source.address ? hit._source.address+ ', ' : '') + item.city);
+            searchAddressItems.push(item);
           }
           else {
             // Convert lat/lon to float (if need)
@@ -152,24 +153,29 @@ angular.module('cesium.map.wot.services', ['cesium.services'])
         }, []);
 
         // Resolve missing positions by addresses (only if google API enable)
-        if (noPositionItems.length && esGeo.google.hasApiKey()) {
+        if (searchAddressItems.length) {
           var now = new Date().getTime();
-          console.log('[map] [wot] Search positions of {0} addresses...'.format(noPositionItems.length));
+          console.log('[map] [wot] Search positions of {0} addresses...'.format(searchAddressItems.length));
           var counter = 0;
 
-          return $q.all(noPositionItems.reduce(function(res, item) {
-            return res.concat(esGeo.google.searchByAddress(item.fullAddress)
+          return $q.all(searchAddressItems.reduce(function(res, item) {
+            return !item.searchAddress ? res : res.concat(esGeo.google.searchByAddress(item.searchAddress)
               .then(function(res) {
                 if (!res || !res.length) return;
                 item.geoPoint = res[0];
-                delete item.fullAddress;
+                // If search on city, add a randomized delta to avoid superposition
+                if (item.city == item.searchAddress) {
+                  item.geoPoint.lon += Math.random() / 1000;
+                  item.geoPoint.lat += Math.random() / 1000;
+                }
+                delete item.searchAddress; // not need anymore
                 items.push(item);
                 counter++;
               })
               .catch(function() {/*silent*/}));
             }, []))
               .then(function(){
-                console.log('[map] [wot] Resolved {0}/{1} addresses in {2}ms'.format(counter, noPositionItems.length, new Date().getTime()-now));
+                console.log('[map] [wot] Resolved {0}/{1} addresses in {2}ms'.format(counter, searchAddressItems.length, new Date().getTime()-now));
                 return items;
               });
         }
diff --git a/www/plugins/map/templates/network/view_map.html b/www/plugins/map/templates/network/view_map.html
index ff906349..316a454c 100644
--- a/www/plugins/map/templates/network/view_map.html
+++ b/www/plugins/map/templates/network/view_map.html
@@ -1,5 +1,6 @@
 <ion-view left-buttons="leftButtons" class="view-map-network">
   <ion-nav-title>
+    <span class="hidden-xs" translate>MAP.NETWORK.VIEW.TITLE</span>
   </ion-nav-title>
 
   <ion-nav-buttons side="secondary">
diff --git a/www/plugins/map/templates/settings/es_settings_extend.html b/www/plugins/map/templates/settings/es_settings_extend.html
new file mode 100644
index 00000000..70f41cc5
--- /dev/null
+++ b/www/plugins/map/templates/settings/es_settings_extend.html
@@ -0,0 +1,35 @@
+
+<span class="item item-divider" translate>MAP.SETTINGS.MAP_DIVIDER</span>
+
+<label class="item item-toggle dark item-text-wrap">
+  <span translate>MAP.SETTINGS.ENABLE_GOOGLE_API</span>
+  <h4 class="gray" translate>MAP.SETTINGS.ENABLE_GOOGLE_API_HELP</h4>
+  <div class="toggle toggle-royal">
+    <input type="checkbox" ng-model="formData.enableGoogleApi">
+    <div class="track">
+      <div class="handle"></div>
+    </div>
+  </div>
+</label>
+
+<ion-item class="item-input"
+          ng-if="!$root.config.plugins.es.googleApiKey"
+          ng-class="{'item-input-error': formData.enableGoogleApi && !formData.googleApiKey}">
+  <div class="input-label col-33">
+    <span class="" ng-class="{'gray': !formData.enableGoogleApi}"translate>MAP.SETTINGS.GOOGLE_API_KEY</span>
+    <h4>
+      <a href="https://console.developers.google.com/apis/credentials/key" target="_system" translate>MAP.SETTINGS.BTN_GOOGLE_API</a>
+      <span class="gray"> ({{'MAP.SETTINGS.BTN_GOOGLE_API_WARNING'|translate}})</span>
+    </h4>
+  </div>
+  <input type="text"
+         placeholder="{{'MAP.SETTINGS.GOOGLE_API_KEY_PLACEHOLDER' | translate}}"
+         ng-model="formData.googleApiKey"
+         ng-if="formData.enableGoogleApi">
+</ion-item>
+<div class="form-errors"
+     ng-show="formData.enableGoogleApi && !formData.googleApiKey">
+  <div class="form-error">
+    <span translate="ERROR.FIELD_REQUIRED"></span>
+  </div>
+</div>
diff --git a/www/plugins/map/templates/wot/popup_marker.html b/www/plugins/map/templates/wot/popup_marker.html
index ec551fee..03564575 100644
--- a/www/plugins/map/templates/wot/popup_marker.html
+++ b/www/plugins/map/templates/wot/popup_marker.html
@@ -18,6 +18,6 @@
     </h4>
   </div>
 </div>
-<div class="item no-border no-padding item-text-wrap" ng-if="::hit.description">
+<div class="item no-border no-padding item-text-wrap hidden-xs" ng-if="::hit.description">
   <small ng-bind-html="::hit.description"></small>
 </div>
diff --git a/www/plugins/map/templates/wot/view_map.html b/www/plugins/map/templates/wot/view_map.html
index 15fa1403..939ec7cc 100644
--- a/www/plugins/map/templates/wot/view_map.html
+++ b/www/plugins/map/templates/wot/view_map.html
@@ -1,8 +1,11 @@
 <ion-view left-buttons="leftButtons" class="view-map-wot">
   <ion-nav-title>
+    <span class="hidden-xs" translate>MAP.WOT.VIEW.TITLE</span>
   </ion-nav-title>
 
   <ion-nav-buttons side="secondary">
+    <button class="button button-icon button-clear icon ion-loop visible-xs visible-sm" ng-click="load()">
+    </button>
   </ion-nav-buttons>
 
   <ion-content data-tap-disabled="true">
-- 
GitLab