diff --git a/bower.json b/bower.json index 498c7e07368e8d2d6a741b959171c81338922bb6..c0d0fc0a32eecb853d7020ffb56c0fcf943cd408 100644 --- a/bower.json +++ b/bower.json @@ -15,7 +15,10 @@ "angular-bind-notifier": "^1.1.7", "angular-image-crop": "^2.0.0", "ng-idle": "^1.3.2", - "chart.js": "chartjs#^2.6.0" + "chart.js": "chartjs#^2.6.0", + "Leaflet.awesome-markers": "^2.0.2", + "ui-leaflet": "^2.0.0", + "leaflet-search": "^2.7.2" }, "resolutions": { "angular-sanitize": "1.5.3", diff --git a/ionic.project b/ionic.project index d6981951ccdd19d2d442ef460f1685ab85ced8dd..e7acc66003ab5468b51261c8dbb130969cdd44b3 100644 --- a/ionic.project +++ b/ionic.project @@ -26,4 +26,4 @@ "version": "12.41.296.5" } ] -} +} \ No newline at end of file diff --git a/www/index.html b/www/index.html index 73a164150fe260a5d3bae72222446a4f7b637c9e..9ad01f166f913868a68460ac1f84e494db1f89ab 100644 --- a/www/index.html +++ b/www/index.html @@ -18,11 +18,19 @@ <!--endRemoveIf(device)--> <!--removeIf(no-plugin)--> - <link href="dist/dist_css/plugins/es/css/style.css" rel="stylesheet"> - <link href="dist/dist_css/plugins/graph/css/style.css" rel="stylesheet"> - - + <link rel="stylesheet" type="text/css" href="lib/leaflet/dist/leaflet.css"> + <link rel="stylesheet" type="text/css" href="lib/Leaflet.awesome-markers/dist/leaflet.awesome-markers.css"> + <!--removeIf(no-device)--> + <!--<link rel="stylesheet" type="text/css" href="lib/leaflet-search/dist/leaflet-search.mobile.src.css">--> + <!--endRemoveIf(no-device)--> + <!--removeIf(device)--> + <link rel="stylesheet" type="text/css" href="lib/leaflet-search/dist/leaflet-search.src.css"> + <!--endRemoveIf(device)--> + <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/es/css/style.css"> + <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/graph/css/style.css"> + <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/map/css/style.css"> <!--endRemoveIf(no-plugin)--> + <!-- endbuild --> <!-- build:js dist_js/vendor.js --> <!-- vendor js --> @@ -72,6 +80,15 @@ <script src="js/vendor/sha256.min.js" async></script> <script src="js/vendor/ng-cordova.min.js"></script> <!--endRemoveIf(no-device)--> + + <!--removeIf(no-plugin)--> + <script src="lib/leaflet/dist/leaflet.js"></script> + <script src="lib/angular-simple-logger/dist/angular-simple-logger.js"></script> + <script src="lib/ui-leaflet/dist/ui-leaflet.js"></script> + <script src="lib/Leaflet.awesome-markers/dist/leaflet.awesome-markers.js"></script> + <script src="lib/leaflet-search/dist/leaflet-search.src.js"></script> + <!--endRemoveIf(no-plugin)--> + <!-- endbuild --> <!--removeIf(no-device)--> @@ -149,6 +166,7 @@ <script src="dist/dist_js/plugins/es/js/services/invitation-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/subscription-services.js"></script> <script src="dist/dist_js/plugins/es/js/services/wallet-services.js"></script> + <script src="dist/dist_js/plugins/es/js/services/geo-services.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/common-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/app-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/settings-controllers.js"></script> @@ -165,7 +183,6 @@ <script src="dist/dist_js/plugins/es/js/controllers/invitation-controllers.js"></script> <script src="dist/dist_js/plugins/es/js/controllers/subscription-controllers.js"></script> - <!-- Graph plugin --> <!--removeIf(ubuntu)--> <!-- FIXME: issue #463 --> <script src="dist/dist_js/plugins/graph/js/plugin.js"></script> @@ -179,6 +196,9 @@ <script src="dist/dist_js/plugins/graph/js/controllers/account-controllers.js"></script> <!--endRemoveIf(ubuntu)--> + <!-- Map plugin --> + <script src="dist/dist_js/plugins/map/js/plugin.js"></script> + <!-- RML9 plugin --> <!--<script src="dist/dist_js/plugins/rml9/plugin-01-add_button.js"></script>--> <!--<script src="dist/dist_js/plugins/rml9/plugin-02-add_view.js"></script>--> @@ -186,16 +206,9 @@ <!--<script src="dist/dist_js/plugins/rml9/plugin-04-chart.js"></script>--> <!--<script src="dist/dist_js/plugins/rml9/plugin-05-service_api.js"></script>--> <!--<script src="dist/dist_js/plugins/rml9/plugin-06-settings.js"></script>--> + <!--<script src="dist/dist_js/plugins/rml9/plugin-07-add_map.js"></script>--> <!--<script src="dist/dist_js/plugins/rml9/plugin-final.js"></script>--> - <!-- RML9 plugin: add a map - <link rel="stylesheet" type="text/css" href="lib/leaflet/dist/leaflet.css"> - <link rel="stylesheet" type="text/css" href="dist/dist_css/plugins/rml9/css/style.css"> - <script src="lib/leaflet/dist/leaflet.js"></script> - <script src="lib/angular-simple-logger/dist/angular-simple-logger.js"></script> - <script src="lib/ui-leaflet/dist/ui-leaflet.js"></script> - <script src="dist/dist_js/plugins/rml9/plugin-07-add_map.js"></script>--> - <!--endRemoveIf(no-plugin)--> <!-- App --> diff --git a/www/js/app.js b/www/js/app.js index 28447db712818754395a15c8df31c9dc8995446c..6d607a1b09981d5cd92c58f7ec586be6364de05b 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -5,8 +5,7 @@ // the 2nd parameter is an array of 'requires' // 'starter.controllers' is found in controllers.js angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht.translate', - 'ngApi', 'angular-cache', 'angular.screenmatch', 'angular.bind.notifier', 'ImageCropper', - //'ui-leaflet', + 'ngApi', 'angular-cache', 'angular.screenmatch', 'angular.bind.notifier', 'ImageCropper', 'ui-leaflet', // removeIf(no-device) 'ngCordova', // endRemoveIf(no-device) diff --git a/www/js/plugins.js b/www/js/plugins.js index b3bbfa6ae5776eb1de0b5c69c3c14abb6631aab4..9dcbb1a6d1ab4804bc6162436255fa3556f3526b 100644 --- a/www/js/plugins.js +++ b/www/js/plugins.js @@ -17,6 +17,9 @@ angular.module('cesium.plugins', [ //'cesium.rml9.plugin', // ES plugin (Cesium+): - 'cesium.es.plugin' + 'cesium.es.plugin', + + // Map plugin (Cesium+): + 'cesium.map.plugin' ]) ; diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json index 729e02932b205300650dee74148e9592e99f2fca..08e041fb15fa5a5d665c308a18ee26724171c9f7 100644 --- a/www/plugins/es/i18n/locale-fr-FR.json +++ b/www/plugins/es/i18n/locale-fr-FR.json @@ -374,6 +374,8 @@ "NO_PROFILE_DEFINED": "Aucun profil Cesium+", "BTN_ADD": "Saisir mon profil", "BTN_EDIT": "Editer mon profil", + "BTN_GEOLOC_CITY": "Localiser la ville", + "BTN_GEOLOC_ME": "Me localiser", "UID": "Pseudonyme", "TITLE": "Nom, Prénom", "TITLE_HELP": "Nom, Prénom", @@ -387,6 +389,11 @@ "GENERAL_DIVIDER": "Informations générales", "LOCATION_DIVIDER": "Adresse", "SOCIAL_NETWORKS_DIVIDER": "Réseaux sociaux, sites web", + "GEO_POINT_DIVIDER": "Position GPS", + "LATITUDE": "Latitude", + "LATITUDE_HELP": "Latitude", + "LONGITUDE": "Longitude", + "LONGITUDE_HELP": "Longitude", "TECHNICAL_DIVIDER": "Informations techniques", "MODAL_AVATAR": { "TITLE": "Photo de profil", @@ -399,7 +406,8 @@ "LOAD_PROFILE_FAILED": "Erreur de chargement du profil utilisateur.", "SAVE_PROFILE_FAILED": "Erreur lors de la sauvegarde", "INVALID_SOCIAL_NETWORK_FORMAT": "Format non pris en compte : veuillez indiquer une adresse valide.<br/><br/>Exemples :<ul><li>- Une page Facebook (https://www.facebook.com/user)</li><li>- Une page web (http://www.monsite.fr)</li><li>- Une adresse email (joe@dalton.com)</li></ul>", - "IMAGE_RESIZE_FAILED": "Erreur lors du redimensionnement de l'image" + "IMAGE_RESIZE_FAILED": "Erreur lors du redimensionnement de l'image", + "GEO_LOCATION_FAILED": "Echec de la récupération de votre position" }, "INFO": { "PROFILE_SAVED": "Profil sauvegardé" diff --git a/www/plugins/es/js/controllers/profile-controllers.js b/www/plugins/es/js/controllers/profile-controllers.js index 8cd37f2dffdb81d10b627a245aba0ad62c8a5174..5322dd8a50a762f711beaf197b366fc996754319 100644 --- a/www/plugins/es/js/controllers/profile-controllers.js +++ b/www/plugins/es/js/controllers/profile-controllers.js @@ -25,7 +25,7 @@ angular.module('cesium.es.profile.controllers', ['cesium.es.services']) ; function ESViewEditProfileController($scope, $rootScope, $timeout, $state, $focus, $translate, $ionicHistory, - UIUtils, esHttp, esProfile, ModalUtils, Device) { + UIUtils, esHttp, esProfile, esGeo, ModalUtils, Device) { 'ngInject'; $scope.loading = true; @@ -34,7 +34,8 @@ function ESViewEditProfileController($scope, $rootScope, $timeout, $state, $focu $scope.formData = { title: null, description: null, - socials: [] + socials: [], + geoPoint: {} }; $scope.avatar = null; $scope.existing = false; @@ -144,11 +145,11 @@ function ESViewEditProfileController($scope, $rootScope, $timeout, $state, $focu console.debug('[ES] [profile] Saving user profile...'); $scope.saving = true; - // removeIf(device) + // removeIf(no-device) if (!silent) { UIUtils.loading.show(); } - // endRemoveIf(device) + // endRemoveIf(no-device) var onError = function(message) { return function(err) { @@ -174,9 +175,9 @@ function ESViewEditProfileController($scope, $rootScope, $timeout, $state, $focu var showSuccessToast = function() { if (!silent) { - // removeIf(device) + // removeIf(no-device) UIUtils.loading.hide(); - // endRemoveIf(device) + // endRemoveIf(no-device) return $translate('PROFILE.INFO.PROFILE_SAVED') .then(function(message){ @@ -289,6 +290,50 @@ function ESViewEditProfileController($scope, $rootScope, $timeout, $state, $focu $scope.rotating = false; }); }; + + $scope.localizeByCity = function() { + if (!$scope.formData.city) { + return; + } + + var address = angular.copy($scope.formData.city); + + return esGeo.point.searchByAddress(address) + .then(function(res) { + if (address != $scope.formData.city) return; // user changed value again + + if (res && res.length >= 1) { + var position = res[0]; + if (position.lat && position.lon) { + $scope.formData.geoPoint = $scope.formData.geoPoint || {}; + $scope.formData.geoPoint.lat = parseFloat(position.lat); + $scope.formData.geoPoint.lon = parseFloat(position.lon); + $scope.dirty = true; + } + } + }); + }; + + + $scope.localizeMe = function() { + return esGeo.point.current() + .then(function(position) { + if (!position || !position.lat || !position.lon) return; + $scope.formData.geoPoint = $scope.formData.geoPoint || {}; + $scope.formData.geoPoint.lat = parseFloat(position.lat); + $scope.formData.geoPoint.lon = parseFloat(position.lon); + }) + .catch(UIUtils.onError('PROFILE.ERROR.GEO_LOCATION_FAILED')); + }; + + $scope.onCityChanged = function() { + var hasGeoPoint = $scope.formData.geoPoint && $scope.formData.geoPoint.lat && $scope.formData.geoPoint.lon; + if (!hasGeoPoint) { + return $scope.localizeByCity(); + } + }; + $scope.$watch('formData.city', $scope.onCityChanged, true); + } diff --git a/www/plugins/es/js/services.js b/www/plugins/es/js/services.js index 2597433ff17798c952b2a4a1c9056488de705065..61a0824da0a2b220fad5072703d2d7ebcbb5a863 100644 --- a/www/plugins/es/js/services.js +++ b/www/plugins/es/js/services.js @@ -15,6 +15,7 @@ angular.module('cesium.es.services', [ 'cesium.es.group.services', 'cesium.es.wallet.services', 'cesium.es.invitation.services', - 'cesium.es.subscription.services' + 'cesium.es.subscription.services', + 'cesium.es.geo.services' ]) ; diff --git a/www/plugins/es/js/services/geo-services.js b/www/plugins/es/js/services/geo-services.js new file mode 100644 index 0000000000000000000000000000000000000000..87314b32216cf923a01ad177dfadeb71fc223ea2 --- /dev/null +++ b/www/plugins/es/js/services/geo-services.js @@ -0,0 +1,76 @@ +angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.services']) + .config(function(PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.es; + if (enable) { + // Will force to load this service + PluginServiceProvider.registerEagerLoadingService('esGeo'); + } + + }) + + .factory('esGeo', function($q, csHttp) { + 'ngInject'; + + var + that = this; + + that.raw = { + searchByAddress: csHttp.get('nominatim.openstreetmap.org', 80, '/search.php?format=json&q=:query'), + searchByIP: csHttp.get('freegeoip.net', 80, '/json/:ip') + }; + + function searchPositionByAddress(queryString) { + + var now = new Date(); + console.debug('[ES] [geo] Searching address position [{0}]...'.format(queryString)); + + return that.raw.searchByAddress({query: queryString}) + .then(function(res) { + console.debug('[ES] [geo] Found {0} address position(s) in {0}ms'.format(res && res.length || 0, new Date().getTime() - now.getTime())); + return res; + }); + } + + function getCurrentPosition() { + var defer = $q.defer(); + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position) { + if (!position || !position.coords) { + console.error('[ES] [geo] navigator geolocation > Unknown format:', position); + return; + } + defer.resolve({ + lat: position.coords.latitude, + lon: position.coords.longitude + }); + }, function(error) { + defer.reject(error); + },{timeout:5000}); + }else{ + defer.reject(); + } + return defer.promise; + } + + function searchPositionByIP(ip) { + + var now = new Date(); + console.debug('[ES] [geo] Searching IP position [{0}]...'.format(ip)); + + return that.raw.searchByIP({ip: ip}) + .then(function(res) { + console.debug('[ES] [geo] Found IP {0} position in {0}ms'.format(res ? 1 : 0, new Date().getTime() - now.getTime())); + return res ? {lat: res.latitude,lng: res.longitude} : undefined; + }); + } + + return { + point: { + current: getCurrentPosition, + searchByAddress: searchPositionByAddress, + searchByIP: searchPositionByIP + } + }; + }); diff --git a/www/plugins/es/js/services/group-services.js b/www/plugins/es/js/services/group-services.js index aac5eb2cad35eb94e443b1ee01c069b2f9b85493..6fd028275b55e6e743aa12b018ac89095be2d24a 100644 --- a/www/plugins/es/js/services/group-services.js +++ b/www/plugins/es/js/services/group-services.js @@ -14,219 +14,214 @@ angular.module('cesium.es.group.services', ['cesium.platform', 'cesium.es.http.s .factory('esGroup', function($q, $rootScope, csPlatform, csSettings, esHttp, CryptoUtils, csWot, csWallet, esNotification, esComment) { 'ngInject'; - function EsGroup() { - - var - listeners, - defaultLoadSize = 50, - fields = { - list: ["issuer", "title"], - commons: ["issuer", "title", "description", "creationTime", "time", "signature"], - notifications: ["issuer", "time", "hash", "read_signature"] - }, - exports = { - _internal: {} - }; + var + listeners, + defaultLoadSize = 50, + fields = { + list: ["issuer", "title"], + commons: ["issuer", "title", "description", "creationTime", "time", "signature"], + notifications: ["issuer", "time", "hash", "read_signature"] + }, + exports = { + _internal: {} + }; - function onWalletInit(data) { - data.groups = data.groups || {}; - data.groups.unreadCount = null; - } + function onWalletInit(data) { + data.groups = data.groups || {}; + data.groups.unreadCount = null; + } - function onWalletReset(data) { - if (data.groups) { - delete data.groups; - } + function onWalletReset(data) { + if (data.groups) { + delete data.groups; } + } - function onWalletLogin(data, deferred) { - deferred = deferred || $q.defer(); - if (!data || !data.pubkey) { - deferred.resolve(); - return deferred.promise; - } - - // Count unread notifications - esNotification.unreadCount(data.pubkey, {codes: { - includes: ['GROUP_INVITATION'], - excludes: [] - }}) - .then(function(unreadCount){ - data.groups = data.groups || {}; - data.groups.unreadCount = unreadCount; - console.debug('[ES] [group] Detecting ' + unreadCount + ' unread notifications'); - deferred.resolve(data); - }) - .catch(function(err){ - console.error('Error while counting group notifications: ' + (err.message ? err.message : err)); - deferred.resolve(data); - }); + function onWalletLogin(data, deferred) { + deferred = deferred || $q.defer(); + if (!data || !data.pubkey) { + deferred.resolve(); return deferred.promise; } - function readRecordFromHit(hit, html) { - if (!hit) return; - var record = hit._source; - if (html && hit.highlight) { - if (hit.highlight.title) { - record.title = hit.highlight.title[0]; - } - if (hit.highlight.description) { - record.description = hit.highlight.description[0]; - } - if (hit.highlight.location) { - record.location = hit.highlight.location[0]; - } - if (hit.highlight.tags) { - data.tags = hit.highlight.tags.reduce(function(res, tag){ - return res.concat(tag.replace('<em>', '').replace('</em>', '')); - },[]); - } - } + // Count unread notifications + esNotification.unreadCount(data.pubkey, {codes: { + includes: ['GROUP_INVITATION'], + excludes: [] + }}) + .then(function(unreadCount){ + data.groups = data.groups || {}; + data.groups.unreadCount = unreadCount; + console.debug('[ES] [group] Detecting ' + unreadCount + ' unread notifications'); + deferred.resolve(data); + }) + .catch(function(err){ + console.error('Error while counting group notifications: ' + (err.message ? err.message : err)); + deferred.resolve(data); + }); + return deferred.promise; + } - // description - if (html) { - record.description = esHttp.util.trustAsHtml(record.description); + function readRecordFromHit(hit, html) { + if (!hit) return; + var record = hit._source; + if (html && hit.highlight) { + if (hit.highlight.title) { + record.title = hit.highlight.title[0]; } - - // thumbnail - record.thumbnail = esHttp.image.fromHit(hit, 'thumbnail'); - - // pictures - if (hit._source.pictures && hit._source.pictures.reduce) { - record.pictures = hit._source.pictures.reduce(function(res, pic) { - return res.concat(esHttp.image.fromAttachment(pic.file)); - }, []); + if (hit.highlight.description) { + record.description = hit.highlight.description[0]; + } + if (hit.highlight.location) { + record.location = hit.highlight.location[0]; + } + if (hit.highlight.tags) { + data.tags = hit.highlight.tags.reduce(function(res, tag){ + return res.concat(tag.replace('<em>', '').replace('</em>', '')); + },[]); } - - return record; } - exports._internal.search = esHttp.post('/group/record/_search'); + // description + if (html) { + record.description = esHttp.util.trustAsHtml(record.description); + } - function searchGroups(options) { - if (!csWallet.isLogin()) { - return $q.when([]); - } + // thumbnail + record.thumbnail = esHttp.image.fromHit(hit, 'thumbnail'); - options = options || {}; - options.from = options.from || 0; - options.size = options.size || defaultLoadSize; - options._source = options._source || fields.list; - var request = { - sort: { - "time" : "desc" - }, - from: options.from, - size: options.size, - _source: options._source - }; - - return exports._internal.search(request) - .then(function(res) { - if (!res || !res.hits || !res.hits.total) { - return []; - } - var groups = res.hits.hits.reduce(function(res, hit) { - var record = readRecordFromHit(hit, true/*html*/); - record.id = hit._id; - return record ? res.concat(record) : res; - }, []); - - console.debug('[ES] [group] Loading {0} {1} messages'.format(groups.length, options.type)); - - return groups; - }); + // pictures + if (hit._source.pictures && hit._source.pictures.reduce) { + record.pictures = hit._source.pictures.reduce(function(res, pic) { + return res.concat(esHttp.image.fromAttachment(pic.file)); + }, []); } - exports._internal.get = esHttp.get('/group/record/:id'); - exports._internal.getCommons = esHttp.get('/group/record/:id?_source=' + fields.commons.join(',')); - - function loadData(id, options) { - options = options || {}; - options.fecthPictures = angular.isDefined(options.fetchPictures) ? options.fetchPictures : false; - options.html = angular.isDefined(options.html) ? options.html : true; - - // Do get source - var promise = options.fecthPictures ? - exports._internal.get({id: id}) : - exports._internal.getCommons({id: id}); - - return promise - .then(function(hit) { - var record = readRecordFromHit(hit, options.html); - - // Load issuer (avatar, name, uid, etc.) - return csWot.extend({pubkey: record.issuer}) - .then(function(issuer) { - return { - id: hit._id, - issuer: issuer, - record: record - }; - }); - }); - } + return record; + } - function removeListeners() { - _.forEach(listeners, function(remove){ - remove(); - }); - listeners = []; - } + exports._internal.search = esHttp.post('/group/record/_search'); - function addListeners() { - // Extend csWallet.loadData() - listeners = [ - csWallet.api.data.on.login($rootScope, onWalletLogin, this), - csWallet.api.data.on.init($rootScope, onWalletInit, this), - csWallet.api.data.on.reset($rootScope, onWalletReset, this) - ]; + function searchGroups(options) { + if (!csWallet.isLogin()) { + return $q.when([]); } - function refreshState() { - var enable = esHttp.alive; - if (!enable && listeners && listeners.length > 0) { - console.debug("[ES] [group] Disable"); - removeListeners(); - if (csWallet.isLogin()) { - onWalletReset(csWallet.data); - } - } - else if (enable && (!listeners || listeners.length === 0)) { - console.debug("[ES] [group] Enable"); - addListeners(); - if (csWallet.isLogin()) { - onWalletLogin(csWallet.data); + options = options || {}; + options.from = options.from || 0; + options.size = options.size || defaultLoadSize; + options._source = options._source || fields.list; + var request = { + sort: { + "time" : "desc" + }, + from: options.from, + size: options.size, + _source: options._source + }; + + return exports._internal.search(request) + .then(function(res) { + if (!res || !res.hits || !res.hits.total) { + return []; } - } - } + var groups = res.hits.hits.reduce(function(res, hit) { + var record = readRecordFromHit(hit, true/*html*/); + record.id = hit._id; + return record ? res.concat(record) : res; + }, []); + + console.debug('[ES] [group] Loading {0} {1} messages'.format(groups.length, options.type)); + + return groups; + }); + } - // Default actions - csPlatform.ready().then(function() { - esHttp.api.node.on.start($rootScope, refreshState, this); - esHttp.api.node.on.stop($rootScope, refreshState, this); - return refreshState(); + exports._internal.get = esHttp.get('/group/record/:id'); + exports._internal.getCommons = esHttp.get('/group/record/:id?_source=' + fields.commons.join(',')); + + function loadData(id, options) { + options = options || {}; + options.fecthPictures = angular.isDefined(options.fetchPictures) ? options.fetchPictures : false; + options.html = angular.isDefined(options.html) ? options.html : true; + + // Do get source + var promise = options.fecthPictures ? + exports._internal.get({id: id}) : + exports._internal.getCommons({id: id}); + + return promise + .then(function(hit) { + var record = readRecordFromHit(hit, options.html); + + // Load issuer (avatar, name, uid, etc.) + return csWot.extend({pubkey: record.issuer}) + .then(function(issuer) { + return { + id: hit._id, + issuer: issuer, + record: record + }; + }); + }); + } + + function removeListeners() { + _.forEach(listeners, function(remove){ + remove(); }); + listeners = []; + } - return { - record: { - search: searchGroups, - load: loadData, - add: esHttp.record.post('/group/record'), - update: esHttp.record.post('/group/record/:id/_update'), - remove: esHttp.record.remove('group', 'record'), - fields: { - commons: fields.commons - }, - picture: { - all: esHttp.get('/group/record/:id?_source=pictures') - }, - comment: esComment.instance('group') + function addListeners() { + // Extend csWallet.loadData() + listeners = [ + csWallet.api.data.on.login($rootScope, onWalletLogin, this), + csWallet.api.data.on.init($rootScope, onWalletInit, this), + csWallet.api.data.on.reset($rootScope, onWalletReset, this) + ]; + } + + function refreshState() { + var enable = esHttp.alive; + if (!enable && listeners && listeners.length > 0) { + console.debug("[ES] [group] Disable"); + removeListeners(); + if (csWallet.isLogin()) { + onWalletReset(csWallet.data); } - }; + } + else if (enable && (!listeners || listeners.length === 0)) { + console.debug("[ES] [group] Enable"); + addListeners(); + if (csWallet.isLogin()) { + onWalletLogin(csWallet.data); + } + } } - return EsGroup(); + // Default actions + csPlatform.ready().then(function() { + esHttp.api.node.on.start($rootScope, refreshState, this); + esHttp.api.node.on.stop($rootScope, refreshState, this); + return refreshState(); + }); + + return { + record: { + search: searchGroups, + load: loadData, + add: esHttp.record.post('/group/record'), + update: esHttp.record.post('/group/record/:id/_update'), + remove: esHttp.record.remove('group', 'record'), + fields: { + commons: fields.commons + }, + picture: { + all: esHttp.get('/group/record/:id?_source=pictures') + }, + comment: esComment.instance('group') + } + }; }) ; diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html index 46d77aeaa75d4ac71d83887d18c3319f463558d6..72e4cb4c7edd622b7403d313e2b39eda34101d0f 100644 --- a/www/plugins/es/templates/user/edit_profile.html +++ b/www/plugins/es/templates/user/edit_profile.html @@ -112,6 +112,60 @@ ng-model-options="{ debounce: 350 }"> </div> + <!--<div class="item"> + <span class="input-label"> + {{'PROFILE.GEO_POINT_DIVIDER' | translate}} + </span> + </div>--> + + <!-- Position (lat/lon) --> + <div class="row responsive-md responsive-sm no-padding"> + + <!-- lat --> + <div class="col no-padding"> + <label class="item item-input item-floating-label"> + <span class="input-label" translate>PROFILE.LATITUDE</span> + <input class="no-padding-right" type="number" placeholder="{{'PROFILE.LATITUDE_HELP'|translate}}" + ng-model="formData.geoPoint.lat" + ng-model-options="{ debounce: 350 }" + ng-change="onGeoPointChanged()" + required> + </label> + </div> + + <div class="col-10 no-padding hidden-xs"> + + </div> + + <!-- lon --> + <div class="col no-padding"> + <label class="item item-input item-floating-label"> + <span class="input-label" translate>PROFILE.LONGITUDE</span> + <input class="no-padding-right" type="number" placeholder="{{'PROFILE.LONGITUDE_HELP'|translate}}" + ng-model="formData.geoPoint.lon" + ng-model-options="{ debounce: 350 }" + ng-change="onGeoPointChanged()" + required> + </label> + </div> + + <div class="col no-padding"> + <div class="item no-padding text-center"> + <span class="input-label"></span> + + <a class="button button-stable button-small-padding icon ion-refresh" + title="{{'PROFILE.BTN_GEOLOC_CITY'|translate}}" + ng-click="localizeByCity()"> + </a> + + <a class="button button-stable button-small-padding icon ion-pinpoint" + title="{{'PROFILE.BTN_GEOLOC_ME'|translate}}" + ng-click="localizeMe()"> + </a> + </div> + </div> + </div> + <!-- social networks --> <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include> diff --git a/www/plugins/map/css/style.css b/www/plugins/map/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..1c02c1383769f8a5b38e1a0942580e0f5e8e966b --- /dev/null +++ b/www/plugins/map/css/style.css @@ -0,0 +1,60 @@ +.leaflet-top { + top: 44px !important; +} + +.legend { + font: 14px/16px Arial, Helvetica, sans-serif; + background: rgba(255,255,255, 0.9); + box-shadow: 0 0 15px rgba(0,0,0,0.2); + border-radius: 5px; + padding: 6px 8px; + width: 180px; + line-height: 18px; + color: #555; +} + +.legend .outline { + border: 0; +} +.legend i { + width: 16px; + height: 16px; + float: left; + margin-right: 8px; + opacity: 0.7; +} + + +/* Control: Seach - see */ +/*.leaflet-marker-icon {*/ + /*color: #fff;*/ + /*font-size: 16px;*/ + /*line-height: 16px;*/ + /*text-align: center;*/ + /*vertical-align: middle;*/ + /*box-shadow: 2px 1px 4px rgba(0,0,0,0.3);*/ + /*border-radius: 8px;*/ + /*border:1px solid #fff;*/ +/*}*/ +.search-tip { + white-space: nowrap; +} +.search-tip b { + color: #fff; +} +.search-tip.member b { + background: #38aadd; /* blue */ +} +.search-tip.wallet b { + background: #a3a3a3; /* lightgray */ +} +.search-tip.group b { + background: #71ae26; /* lightgray */ +} +.search-tip b { + display: inline-block; + clear: left; + float: right; + padding: 0; + margin-left: 4px; +} diff --git a/www/plugins/map/i18n/locale-fr-FR.json b/www/plugins/map/i18n/locale-fr-FR.json new file mode 100644 index 0000000000000000000000000000000000000000..52c1f83cffe8bb89b7403c92e4523b9ae8c13a92 --- /dev/null +++ b/www/plugins/map/i18n/locale-fr-FR.json @@ -0,0 +1,18 @@ +{ + "MAP": { + "WOT": { + "BTN_SHOW_IDENTITY": "Consulter la fiche", + "LOOKUP": { + "BTN_SHOW_MAP": "Carte" + }, + "VIEW": { + "TITLE": "Carte des membres", + "SEARCH_DOTS": "Rechercher...", + "LEGEND": { + "MEMBER": "Membre", + "WALLET": "Non-membres" + } + } + } + } +} diff --git a/www/plugins/map/js/plugin.js b/www/plugins/map/js/plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..0a9b7e8a055d0687ab9aa216b55a03c2eb3feff5 --- /dev/null +++ b/www/plugins/map/js/plugin.js @@ -0,0 +1,388 @@ + +angular.module('cesium.map.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.es; + if (enable) { + + PluginServiceProvider + + // Extension de la vue d'une identité: ajout d'un bouton + .extendState('app.wot_lookup', { + points: { + 'filter-buttons': { + templateUrl: "plugins/map/templates/wot/lookup_extend.html" + } + } + }); + + // [NEW] Ajout d'une nouvelle page #/app/wot/map + $stateProvider + .state('app.view_wot_map', { + url: "/wot/map?lat&lng&zoom", + cache: false, + views: { + 'menuContent': { + templateUrl: "plugins/map/templates/wot/map.html", + controller: 'MapWotViewCtrl' + } + } + }); + } + + L.AwesomeMarkers.Icon.prototype.options.prefix = 'ion'; + }) + + // [NEW] Manage events from the page #/app/wot/map + .controller('MapWotViewCtrl', function($scope, $q, $translate, $state, $filter, $templateCache, $timeout, $ionicHistory, UIUtils, MapData, leafletData) { + 'ngInject'; + + $scope.loading = true; + + var constants = { + FRANCE: { + lat: 47.35, lng: 5.65, zoom: 6 + } + }; + constants.DEFAULT_CENTER = constants.FRANCE; + + $scope.map = { + center: angular.copy(constants.DEFAULT_CENTER), + defaults: { + scrollWheelZoom: true + }, + layers: { + baselayers: { + openStreetMap: { + name: 'OpenStreetMap', + type: 'xyz', + url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' + } + }, + overlays: { + member: { + type: 'group', + name: '', + visible: true + }, + wallet: { + type: 'group', + name: '', + visible: true + } + } + }, + legend: {} + }; + + var icons = { + member: { + type: 'awesomeMarker', + icon: 'person', + markerColor: 'blue' + }, + wallet: { + type: 'awesomeMarker', + icon: 'key', + markerColor: 'lightgray' + }, + group: { + type: 'awesomeMarker', + icon: 'person-stalker', + markerColor: 'green' + }, + registry: { + type: 'awesomeMarker', + icon: 'person-stalker', // TODO + markerColor: 'green' // TODO + } + }; + + var markersLayer = L.layerGroup({ + visible: false + }); + + //$scope.$on() + //$scope.map.controls.custom.push(searchControl); + + // [NEW] When opening the view + $scope.$on('$ionicView.enter', function(e, state) { + + if ($scope.loading) { + console.log("[map] Opening the view... (first time)"); + + // remember state, to be able to refresh location + $scope.stateName = state && state.stateName; + $scope.stateParams = angular.copy(state && state.stateParams||{}); + + var center = angular.copy(constants.DEFAULT_CENTER); + if (state.stateParams) { + if (state.stateParams && state.stateParams.lat) { + center.lat = parseFloat(state.stateParams.lat); + } + if (state.stateParams && state.stateParams.lng) { + center.lng = parseFloat(state.stateParams.lng); + } + if (state.stateParams && state.stateParams.zoom) { + center.zoom = parseFloat(state.stateParams.zoom); + } + } + + $scope.load(center); + } + else { + console.log("[map] Opening the view... NOT the first time !"); + } + }); + + $scope.load = function(center) { + center = center||constants.DEFAULT_CENTER; + + $scope.loading = true; + + // removeIf(no-device) + UIUtils.loading.show(); + // endRemoveIf(no-device) + + $q.all([ + $translate(['MAP.WOT.VIEW.LEGEND.MEMBER', 'MAP.WOT.VIEW.LEGEND.WALLET', 'MAP.WOT.VIEW.SEARCH_DOTS', 'COMMON.SEARCH_NO_RESULT']), + MapData.load() + ]) + .then(function(res) { + var translations = res[0]; + res = res[1]; + if (!res || !res.length) return; + + var overlaysNames = { + member: translations['MAP.WOT.VIEW.LEGEND.MEMBER'], + wallet: translations['MAP.WOT.VIEW.LEGEND.WALLET'] + }; + + $scope.map.layers.overlays.member.name=overlaysNames.member; + $scope.map.layers.overlays.wallet.name=overlaysNames.wallet; + + 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; + }); + + var markers = res.reduce(function(res, hit) { + var type = hit.uid ? 'member' : 'wallet'; + var shortPubkey = formatPubkey(hit.issuer); + var marker = { + layer: type, + icon: icons[type], + title: hit.title + ' | ' + shortPubkey, + lat: hit.geoPoint.lat, + lng: hit.geoPoint.lon, + getMessageScope: function() { + var scope = $scope.$new(); + scope.hit = hit; + return scope; + }, + focus: false, + message: markerTemplate + }; + res[hit.issuer] = marker; + + // Create a search marker (will be hide) + var searchText = hit.title + ((hit.uid && hit.uid != hit.title) ? (' | ' + hit.uid) : '') + ' | ' + shortPubkey; + var searchMarker = angular.merge({ + type: type, + opacity: 0, + icon: L.divIcon({ + className: type + ' ng-hide', + iconSize: L.point(0, 0) + }) + }, {title: searchText, shortPubkey: shortPubkey, uid: hit.uid, name: hit.title}); + markersLayer.addLayer(new L.Marker({ + lat: hit.geoPoint.lat, + lng: hit.geoPoint.lon + }, + searchMarker)); + return res; + }, {}); + + $scope.map.markers = markers; + angular.merge($scope.map.center, center); + + + $scope.loading = false; + UIUtils.loading.hide(); + leafletData.getMap().then(function(map) { + + // Control: search + L.control.search({ + layer: markersLayer, + initial: false, + marker: false, + propertyName: 'title', + position: 'topleft', + zoom: 13, + buildTip: function(text, val) { + var marker = val.layer.options; + var title = marker.name != marker.uid ? marker.name +' ' : ''; + if (marker.type == 'member') { + return '<a href="#" class="'+marker.type+'">'+title+'<span class="positive"><i class="icon ion-person"></i> '+marker.uid+'</span></a>'; + } + else { + return '<a href="#" class="'+marker.type+'">'+title+'<span class="gray"><i class="icon ion-key"></i> '+marker.shortPubkey+'</span></a>'; + } + + return '<a href="#" class="'+marker.type+'">'+title+'</a>'; + }, + textPlaceholder: translations['MAP.WOT.VIEW.SEARCH_DOTS'], + textErr: translations['COMMON.SEARCH_NO_RESULT'], + markerLocation: true + }).addTo(map); + + L.control.search({ + url: 'search.php?q={s}', + jsonpParam:'callback', + position: 'topright', + hideMarkerOnCollapse: true, + marker: { + icon: new L.Icon({iconUrl:'data/custom-icon.png', iconSize: [20,20]}), + circle: { + radius: 20, + color: '#0a0', + opacity: 1 + } + } + }).addTo(map); + + map.invalidateSize(); + map._resetView(map.getCenter(), map.getZoom(), true); + }); + /* + leafletData.getMap().then(function(map) { + // Show map + $scope.loading = false; + $timeout(function() { + map.invalidateSize(); + UIUtils.loading.hide(); + }, 300); + });*/ + + }); + }; + + $scope.updateLocation = function() { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + disableBack: true, + historyRoot: true + }); + + $scope.stateParams = $scope.stateParams || {}; + $scope.stateParams.lat = ($scope.map.center.lat != constants.DEFAULT_CENTER.lat) ? $scope.map.center.lat : undefined; + $scope.stateParams.lng = ($scope.map.center.lng != constants.DEFAULT_CENTER.lng) ? $scope.map.center.lng : undefined; + $scope.stateParams.zoom = ($scope.map.center.zoom != constants.DEFAULT_CENTER.zoom) ? $scope.map.center.zoom : undefined; + + $state.go($scope.stateName, $scope.stateParams, { + reload: false, + inherit: true, + notify: false} + ); + }; + + $scope.centerMe = function() { + $scope.map.center.autoDiscover = true; + /*esGeo.point.current() + .then(function() { + + })*/ + }; + + $scope.onMapCenterChanged = function() { + if (!$scope.loading) { + $timeout($scope.updateLocation, 500); + } + }; + $scope.$watch('map.center', $scope.onMapCenterChanged, true); + + }) + + // [NEW] Manage events from the page #/app/wot/map + .factory('MapData', function(csHttp, esHttp, csWot) { + 'ngInject'; + + var + that = this, + constants = { + DEFAULT_LOAD_SIZE: 1000 + }, + fields = { + profile: ["issuer", "title", "description", "geoPoint"] + }; + + that.raw = { + profile: { + postSearch: esHttp.post('/user/profile/_search') + } + }; + + function createFilterQuery(options) { + var query = { + bool: { + must: [ + {exists: {field: "geoPoint"}} + ] + } + }; + + return query; + } + + function load(options) { + options = options || {}; + options.from = options.from || 0; + options.size = options.size || constants.DEFAULT_LOAD_SIZE; + + var request = { + query: createFilterQuery(options), + from: options.from, + size: options.size, + _source: fields.profile + }; + + return that.raw.profile.postSearch(request) + .then(function(res) { + if (!res.hits || !res.hits.total) return []; + + var commaRegexp = new RegExp('[,]'); + + res = res.hits.hits.reduce(function(res, hit) { + var item = hit._source; + + if (!item.geoPoint || !item.geoPoint.lat || !item.geoPoint.lon) return res; + + // Convert lat/lon to float (if need) + if (item.geoPoint.lat && typeof item.geoPoint.lat === 'string') { + item.geoPoint.lat = parseFloat(item.geoPoint.lat.replace(commaRegexp, '.')); + } + if (item.geoPoint.lon && typeof item.geoPoint.lon === 'string') { + item.geoPoint.lon = parseFloat(item.geoPoint.lon.replace(commaRegexp, '.')); + } + + return res.concat(item); + }, []); + + return csWot.extendAll(res, 'issuer'); + }); + } + + return { + load: load + }; + + }); + + diff --git a/www/plugins/map/templates/wot/lookup_extend.html b/www/plugins/map/templates/wot/lookup_extend.html new file mode 100644 index 0000000000000000000000000000000000000000..e6dcccc743a53431f6a9e4c957bef5674ce8324a --- /dev/null +++ b/www/plugins/map/templates/wot/lookup_extend.html @@ -0,0 +1,5 @@ +<a class="button button-text button-small ink hidden-sm hidden-xs" + ui-sref="app.view_wot_map"> + <i class="icon ion-ios-location"></i> + {{'MAP.WOT.LOOKUP.BTN_SHOW_MAP' | translate}} +</a> diff --git a/www/plugins/map/templates/wot/map.html b/www/plugins/map/templates/wot/map.html new file mode 100644 index 0000000000000000000000000000000000000000..4c1903d1f3c5393a8be4f40f86a9805c4325db91 --- /dev/null +++ b/www/plugins/map/templates/wot/map.html @@ -0,0 +1,7 @@ + +<leaflet id="map-wot" + center="map.center" + markers="map.markers" + layers="map.layers" + controls="map.controls"> +</leaflet> diff --git a/www/plugins/map/templates/wot/popup_marker.html b/www/plugins/map/templates/wot/popup_marker.html new file mode 100644 index 0000000000000000000000000000000000000000..7e31eecb1861814fa30689a0b2f244ba0867f726 --- /dev/null +++ b/www/plugins/map/templates/wot/popup_marker.html @@ -0,0 +1,18 @@ +<div class="item no-border no-padding" ng-class="::{'item-avatar': hit.avatar}"> + <img ng-if="::hit.avatar" class="avatar" ng-src="{{::hit.avatar.src}}"> + <div class="item-content item-avatar-left-padding padding-top"> + <h2 class="dark"><a ui-sref="app.wot_identity({pubkey: hit.issuer, uid: hit.uid})">{{::hit.title}}</a></h2> + <h4> + <span ng-if="::hit.uid" class="positive"> + <i class="icon ion-person"></i> + {{::hit.uid}} + </span> + <span class="assertive" ng-if="!hit.uid"> + {{::'WOT.NOT_MEMBER_PARENTHESIS'|translate}} + </span> + </h4> + <h4> + <span class="gray" title="{{::hit.issuer}}"><i class="icon ion-key"></i> {{::hit.issuer|formatPubkey}}</span> + </h4> + </div> +</div> diff --git a/www/plugins/rml9/css/style.css b/www/plugins/rml9/css/style.css index 24f86b1eea8bd636d35453a450e889f96ad7d018..6e8d8bc7fb8eeed4d9f263ec95ef7fd6b5bd1f1f 100644 --- a/www/plugins/rml9/css/style.css +++ b/www/plugins/rml9/css/style.css @@ -1,3 +1,3 @@ -.pane.has-header { +.leaflet-top { top: 44px !important; } diff --git a/www/plugins/rml9/templates/07-view.html b/www/plugins/rml9/templates/07-view.html index 3842a3198e3580efdb7eb34e4970d27fce47a959..6143da3579cb924447658a1e1d82479300eb30ff 100644 --- a/www/plugins/rml9/templates/07-view.html +++ b/www/plugins/rml9/templates/07-view.html @@ -1 +1 @@ -<leaflet class="has-header" id="map-geojson" center="map.center" geojson="map.geojson"></leaflet> +<leaflet id="map-geojson" center="map.center" geojson="map.geojson"></leaflet> diff --git a/www/templates/wot/lookup_form.html b/www/templates/wot/lookup_form.html index 2ba711744d4df50fedec3a37b1997ff8e19c4c71..40329d6b131c4a70d3798b9e820154e60a4bb1d6 100644 --- a/www/templates/wot/lookup_form.html +++ b/www/templates/wot/lookup_form.html @@ -64,12 +64,15 @@ </a> <a ng-if="enableFilter" - class="button button-text button-small" + class="button button-text button-small ink" ng-class="{'button-text-positive': search.type=='pending'}" ng-click="doGetPending()" class="badge-balanced"> <i class="icon ion-clock"></i> {{'WOT.LOOKUP.BTN_PENDING' | translate}} </a> + + <!-- Allow extension here --> + <cs-extension-point name="filter-buttons"></cs-extension-point> <button class="button button-small button-stable ink" ng-click="doSearch()">