diff --git a/www/js/directives.js b/www/js/directives.js index 4871350e7f92a2d94fabd285f30e9aa339e795c2..7a78f61bb7d320d16dc22dc094249aa13027f529 100644 --- a/www/js/directives.js +++ b/www/js/directives.js @@ -43,8 +43,8 @@ angular.module('cesium.directives', []) require: '?ngModel', link: function(scope, element, attributes, ngModel) { if (ngModel) { - ngModel.$validators.numberFloat = function(modelValue) { - return ngModel.$isEmpty(modelValue) || NUMBER_REGEXP.test(modelValue); + ngModel.$validators.numberFloat = function(value) { + return ngModel.$isEmpty(value) || NUMBER_REGEXP.test(value); }; } } @@ -79,6 +79,19 @@ angular.module('cesium.directives', []) }; }) + .directive('geoPointRequired', function() { + return { + require: '?ngModel', + link: function(scope, element, attributes, ngModel) { + if (ngModel) { + ngModel.$validators.required = function(value) { + return ngModel.$isEmpty(value) || !value.lat || !value.lon ; + }; + } + } + }; + }) + // Add a copy-on-click directive .directive('copyOnClick', function ($window, $document, Device, UIUtils) { 'ngInject'; diff --git a/www/plugins/es/css/style.css b/www/plugins/es/css/style.css index 1845a1d8602f9d2d580dee206b73e82714f744b5..7991c6df30ed32a01b69d61a5ba4cd537a866179 100644 --- a/www/plugins/es/css/style.css +++ b/www/plugins/es/css/style.css @@ -150,9 +150,9 @@ line-height: 88px; } -/** -* Message screen -*/ +/********** + Message screen +**********/ #composeMessage .list .item { border-bottom: solid 1px #ccc; @@ -177,9 +177,23 @@ padding-bottom: 8px; } -/** -* Add specific Icons -*/ +/********** + Message screen +**********/ + +.modal-search-location .bar-footer { + height: 30px; +} +.modal-search-location .bar-footer .copyright { + font-size: 12px; + line-height: 30px; +} + + +/********** + Add specific Icons +**********/ + /* Association */ .cion-page-association:before { diff --git a/www/plugins/es/i18n/locale-en-GB.json b/www/plugins/es/i18n/locale-en-GB.json index 22c10e74ee6fc57a99ad5413ec385d7cb422a45c..acbf0184e8e21f9895aab8f52b96a2190ab846c9 100644 --- a/www/plugins/es/i18n/locale-en-GB.json +++ b/www/plugins/es/i18n/locale-en-GB.json @@ -17,6 +17,10 @@ "SETTINGS": "Paramètres", "SHOW_ALL": "Show all", "LOAD_NOTIFICATIONS_FAILED": "Could not load notifications" + }, + "ERROR": { + "REQUIRED_FOR_LOCATION": "Required field to appear on the map", + "INVALID_FOR_LOCATION": "Unknown address" } }, "MENU": { @@ -279,9 +283,9 @@ "NO_PROFILE_DEFINED": "No Cesium+ profile", "BTN_ADD": "Create my profile", "BTN_EDIT": "Edit my profile", - "BTN_GEOLOC_ADDRESS": "Update position from address", - "BTN_GEOLOC_ME": "Localize me", - "BTN_REMOVE_GEOLOC": "Remove position", + "BTN_GEOLOC_ADDRESS": "Find my address on the map", + "USE_GEO_POINT": "Appear on the members map?", + "LOADING_LOCATION": "Searching address...", "UID": "Pseudonym", "TITLE": "Lastname, FirstName", "TITLE_HELP": "Name", @@ -290,7 +294,7 @@ "ADDRESS": "Address", "ADDRESS_HELP": "Address (optional)", "CITY": "City", - "CITY_HELP": "City (optional)", + "CITY_HELP": "City, Country", "SOCIAL_HELP": "http://...", "GENERAL_DIVIDER": "General data", "LOCATION_DIVIDER": "Localisation", @@ -307,16 +311,18 @@ "RESIZE_HELP": "<b>Re-crop the image</b> if necessary. A click on the image allows to move it. Click on the area at the bottom left to zoom in.", "RESULT_HELP": "<b>Here is the result</b> as seen on your profile:" }, - "MODAL_LOCATIONS": { - "TITLE": "Validation de l'adresse", - "RESULT_DIVIDER": "Résultat(s) pour <b>{{address}}</b> :" + "MODAL_LOCATION": { + "TITLE": "Search position", + "SEARCH_HELP": "City, Country", + "ALTERNATIVE_RESULT_DIVIDER": "Alternative results for <b>{{address}}</b>:", + "POSITION": "lat/lon : {{lat}} {{lon}}" }, "ERROR": { "LOAD_PROFILE_FAILED": "Could not load user profile.", "SAVE_PROFILE_FAILED": "Saving profile failed", "INVALID_SOCIAL_NETWORK_FORMAT": "Invalid format: please fill a valid Internet address.<br/><br/>Examples :<ul><li>- A Facebook page (https://www.facebook.com/user)</li><li>- A web page (http://www.domain.com)</li><li>- An email address (joe@dalton.com)</li></ul>", "IMAGE_RESIZE_FAILED": "Error while resizing picture", - "GEO_LOCATION_FAILED": "Unable to retrieve your current position", + "GEO_LOCATION_FAILED": "Unable to retrieve your current position. Please use the search button.", "ADDRESS_LOCATION_FAILED": "Unable to retrieve the address position" }, "INFO": { @@ -328,6 +334,7 @@ }, "SUBSCRIPTION": { "SUBSCRIPTION_DIVIDER": "Online services", + "SUBSCRIPTION_DIVIDER_HELP": "Online services offer optional additional services, delegated to a third party.", "BTN_ADD": "Add a service", "BTN_EDIT": "Manage my services", "NO_SUBSCRIPTION": "No service defined", diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json index 22c10e74ee6fc57a99ad5413ec385d7cb422a45c..acbf0184e8e21f9895aab8f52b96a2190ab846c9 100644 --- a/www/plugins/es/i18n/locale-en.json +++ b/www/plugins/es/i18n/locale-en.json @@ -17,6 +17,10 @@ "SETTINGS": "Paramètres", "SHOW_ALL": "Show all", "LOAD_NOTIFICATIONS_FAILED": "Could not load notifications" + }, + "ERROR": { + "REQUIRED_FOR_LOCATION": "Required field to appear on the map", + "INVALID_FOR_LOCATION": "Unknown address" } }, "MENU": { @@ -279,9 +283,9 @@ "NO_PROFILE_DEFINED": "No Cesium+ profile", "BTN_ADD": "Create my profile", "BTN_EDIT": "Edit my profile", - "BTN_GEOLOC_ADDRESS": "Update position from address", - "BTN_GEOLOC_ME": "Localize me", - "BTN_REMOVE_GEOLOC": "Remove position", + "BTN_GEOLOC_ADDRESS": "Find my address on the map", + "USE_GEO_POINT": "Appear on the members map?", + "LOADING_LOCATION": "Searching address...", "UID": "Pseudonym", "TITLE": "Lastname, FirstName", "TITLE_HELP": "Name", @@ -290,7 +294,7 @@ "ADDRESS": "Address", "ADDRESS_HELP": "Address (optional)", "CITY": "City", - "CITY_HELP": "City (optional)", + "CITY_HELP": "City, Country", "SOCIAL_HELP": "http://...", "GENERAL_DIVIDER": "General data", "LOCATION_DIVIDER": "Localisation", @@ -307,16 +311,18 @@ "RESIZE_HELP": "<b>Re-crop the image</b> if necessary. A click on the image allows to move it. Click on the area at the bottom left to zoom in.", "RESULT_HELP": "<b>Here is the result</b> as seen on your profile:" }, - "MODAL_LOCATIONS": { - "TITLE": "Validation de l'adresse", - "RESULT_DIVIDER": "Résultat(s) pour <b>{{address}}</b> :" + "MODAL_LOCATION": { + "TITLE": "Search position", + "SEARCH_HELP": "City, Country", + "ALTERNATIVE_RESULT_DIVIDER": "Alternative results for <b>{{address}}</b>:", + "POSITION": "lat/lon : {{lat}} {{lon}}" }, "ERROR": { "LOAD_PROFILE_FAILED": "Could not load user profile.", "SAVE_PROFILE_FAILED": "Saving profile failed", "INVALID_SOCIAL_NETWORK_FORMAT": "Invalid format: please fill a valid Internet address.<br/><br/>Examples :<ul><li>- A Facebook page (https://www.facebook.com/user)</li><li>- A web page (http://www.domain.com)</li><li>- An email address (joe@dalton.com)</li></ul>", "IMAGE_RESIZE_FAILED": "Error while resizing picture", - "GEO_LOCATION_FAILED": "Unable to retrieve your current position", + "GEO_LOCATION_FAILED": "Unable to retrieve your current position. Please use the search button.", "ADDRESS_LOCATION_FAILED": "Unable to retrieve the address position" }, "INFO": { @@ -328,6 +334,7 @@ }, "SUBSCRIPTION": { "SUBSCRIPTION_DIVIDER": "Online services", + "SUBSCRIPTION_DIVIDER_HELP": "Online services offer optional additional services, delegated to a third party.", "BTN_ADD": "Add a service", "BTN_EDIT": "Manage my services", "NO_SUBSCRIPTION": "No service defined", diff --git a/www/plugins/es/i18n/locale-es-ES.json b/www/plugins/es/i18n/locale-es-ES.json index e681babdc099d25f605102c92dc7c0396bbde002..8dadf4204cda92091b813d5b410b3804b7179edd 100644 --- a/www/plugins/es/i18n/locale-es-ES.json +++ b/www/plugins/es/i18n/locale-es-ES.json @@ -17,6 +17,10 @@ "SETTINGS": "configuraciónes", "SHOW_ALL": "Ver todo", "LOAD_NOTIFICATIONS_FAILED": "Fracaso en la carga de las notificaciónes" + }, + "ERROR": { + "REQUIRED_FOR_LOCATION": "Campo obligatorio para aparecer en el mapa", + "INVALID_FOR_LOCATION": "Dirección desconocida" } }, "MENU": { @@ -237,7 +241,8 @@ }, "VIEW": { "POPOVER_SHARE_TITLE": "{{title}}", - "MENU_TITLE": "Opciones" + "MENU_TITLE": "Opciones", + "REMOVE_CONFIRMATION" : "Seguro que quieres eliminar este grupo?<br/><br/>Esta operación es irreversible." }, "EDIT": { "TITLE": "Grupo", @@ -249,6 +254,10 @@ }, "ERROR": { "SEARCH_GROUPS_FAILED": "Fracaso en la búsqueda de grupos" + "REMOVE_RECORD_FAILED": "Error al eliminar el grupo" + }, + "INFO": { + "RECORD_REMOVED" : "Grupo eliminado" } }, "REGISTRY": { @@ -326,8 +335,8 @@ "BTN_ADD": "Ingresar mi perfil", "BTN_EDIT": "Editar mi perfil", "BTN_GEOLOC_ADDRESS": "Actualizar desde la dirección", - "BTN_GEOLOC_ME": "localizarme", - "BTN_REMOVE_GEOLOC": "Posición de eliminar", + "USE_GEO_POINT": "Aparecer en la tarjeta de membresÃa?", + "LOADING_LOCATION": "Encontrar la dirección ...", "UID": "Seudónimo", "TITLE": "Nombre, Apellido", "TITLE_HELP": "Nombre, Apellido", @@ -336,7 +345,7 @@ "ADDRESS": "Calle", "ADDRESS_HELP": "Calle, complemento de dirección...", "CITY": "Ciudad", - "CITY_HELP": "Ciudad (opcional)", + "CITY_HELP": "Ciudad, PaÃs", "SOCIAL_HELP": "http://...", "GENERAL_DIVIDER": "Informaciónes generales", "LOCATION_DIVIDER": "Dirección", @@ -353,9 +362,11 @@ "RESIZE_HELP": "<b>Encuadra la imagen</b>, si es necesario. Un clic mantenido sobre la imagen permite desplazarla. Hace un clic sobre la zona abajo a la izquierda para hacer zoom.", "RESULT_HELP": "<b>Aquà está el resultado</b> tal como está visible sobre su perfil :" }, - "MODAL_LOCATIONS": { - "TITLE": "Validación de direcciones", - "RESULT_DIVIDER": "Resultado(s) para <b>{{address}}</b>:" + "MODAL_LOCATION": { + "TITLE": "Búsqueda de dirección", + "SEARCH_HELP": "Ciudad, PaÃs", + "ALTERNATIVE_RESULT_DIVIDER": "Resultados alternativos para <b>{{address}}</b> :", + "POSITION": "Latitud/Longitud : {{lat}} {{lon}}" }, "ERROR": { "LOAD_PROFILE_FAILED": "Fracaso en la carga del perfil usuario.", @@ -374,6 +385,7 @@ }, "SUBSCRIPTION": { "SUBSCRIPTION_DIVIDER": "Servicios en lÃnea", + "SUBSCRIPTION_DIVIDER_HELP": "Los servicios en lÃnea ofrecen servicios adicionales opcionales, delegados a un tercero.", "BTN_ADD": "Agregar un servicio", "BTN_EDIT": "Administrar mis servicios", "NO_SUBSCRIPTION": "Ningún servicio definido", @@ -399,7 +411,7 @@ }, "MODAL_EMAIL": { "TITLE" : "Notificación por correo electrónico", - "HELP" : "Rellene este formulario para <b> ser notificado por correo electrónico </ b> de los eventos de su cuenta. <br/> Su dirección de correo electrónico se cifrará únicamente para que sea visible para el proveedor de servicios.", + "HELP" : "Rellene este formulario para <b>ser notificado por correo electrónico</b> de los eventos de su cuenta. <br/> Su dirección de correo electrónico se cifrará únicamente para que sea visible para el proveedor de servicios.", "EMAIL_LABEL" : "Tu correo electrónico :", "EMAIL_HELP": "carlos@dominio.com", "FREQUENCY_LABEL": "Frecuencia de las notificaciones :", diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json index ca737a6b88af804adb0d49d39e49081336ca46e8..639b0dc9afa19cefc7f862494628d47add37fb9a 100644 --- a/www/plugins/es/i18n/locale-fr-FR.json +++ b/www/plugins/es/i18n/locale-fr-FR.json @@ -17,6 +17,10 @@ "SETTINGS": "Paramètres", "SHOW_ALL": "Voir tout", "LOAD_NOTIFICATIONS_FAILED": "Erreur de chargement des notifications" + }, + "ERROR": { + "REQUIRED_FOR_LOCATION": "Champ obligatoire pour apparaître sur la carte", + "INVALID_FOR_LOCATION": "Adresse inconnue" } }, "MENU": { @@ -330,9 +334,9 @@ "NO_PROFILE_DEFINED": "Aucun profil Cesium+", "BTN_ADD": "Saisir mon profil", "BTN_EDIT": "Editer mon profil", - "BTN_GEOLOC_ADDRESS": "Mettre à jour à partir de l'adresse", - "BTN_GEOLOC_ME": "Me localiser", - "BTN_REMOVE_GEOLOC": "Supprimer la position", + "BTN_GEOLOC_ADDRESS": "Trouver mon adresse sur la carte", + "USE_GEO_POINT": "Apparaître sur la carte des membres ?", + "LOADING_LOCATION": "Recherche de l'adresse...", "UID": "Pseudonyme", "TITLE": "Nom, Prénom", "TITLE_HELP": "Nom, Prénom", @@ -358,16 +362,18 @@ "RESIZE_HELP": "<b>Recadrez l'image</b>, si besoin. Un clic maintenu sur l'image permet de la déplacer. Cliquez sur la zone en bas à gauche pour zoomer.", "RESULT_HELP": "<b>Voici le résultat</b> tel que visible sur votre profil :" }, - "MODAL_LOCATIONS": { - "TITLE": "Validation de l'addresse", - "RESULT_DIVIDER": "Résultat(s) pour <b>{{address}}</b> :" + "MODAL_LOCATION": { + "TITLE": "Recherche de l'adresse", + "SEARCH_HELP": "Ville, Pays", + "ALTERNATIVE_RESULT_DIVIDER": "Résultats alternatifs pour <b>{{address}}</b> :", + "POSITION": "Latitude/Longitude : {{lat}} {{lon}}" }, "ERROR": { "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", - "GEO_LOCATION_FAILED": "Impossible de récupérer votre position actuelle", + "GEO_LOCATION_FAILED": "Impossible de récupérer votre position. Veuillez utiliser le bouton de recherche.", "ADDRESS_LOCATION_FAILED": "Impossible de récupérer la position à partir de l'adresse" }, "INFO": { diff --git a/www/plugins/es/js/controllers/common-controllers.js b/www/plugins/es/js/controllers/common-controllers.js index 12d368f71a6d77c5312c34cf070e7687a6f2a3b2..12241291cfc78be29e56f65990c54ccb5d413e17 100644 --- a/www/plugins/es/js/controllers/common-controllers.js +++ b/www/plugins/es/js/controllers/common-controllers.js @@ -16,6 +16,9 @@ angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.service .controller('ESPositionEditCtrl', ESPositionEditController) + .controller('ESSearchPositionModalCtrl', ESSearchPositionModalController) + + ; @@ -136,7 +139,7 @@ function ESCategoryModalController($scope, UIUtils, $timeout, parameters) { -function ESCommentsController($scope, $timeout, $filter, $state, $focus, UIUtils) { +function ESCommentsController($scope, $filter, $state, $focus, UIUtils) { 'ngInject'; $scope.loading = true; @@ -420,139 +423,211 @@ function ESAvatarModalController($scope) { } -function ESPositionEditController($scope, $q, $translate, - csConfig, UIUtils, esGeo, ModalUtils) { +function ESPositionEditController($scope, $timeout, csConfig, esGeo, ModalUtils) { 'ngInject'; // The default country used for address localisation var defaultCountry = csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.defaultCountry; - //$scope.formData = $scope.formData || {}; - //$scope.formData.geoPoint = $scope.formData.geoPoint || {}; - - $scope.localizeByAddress = function() { + var loadingCurrentPosition = false; + $scope.loadingPosition = false; + + $scope.tryToLocalize = function() { + if ($scope.loadingPosition || loadingCurrentPosition) return; + + var searchText = $scope.getAddressToSearch(); + + // No address, so try to localize by device + if (!searchText) { + loadingCurrentPosition = true; + return esGeo.point.current() + .then($scope.updateGeoPoint) + .then(function() { + loadingCurrentPosition = false; + }) + .catch(function(err) { + console.error(err); // Silent + loadingCurrentPosition = false; + $scope.form.geoPoint.$setValidity('required', false); + }); + } - return UIUtils.loading.show() - .then($scope.searchPositions) + $scope.loadingPosition = true; + return esGeo.point.searchByAddress(searchText) .then(function(res) { - UIUtils.loading.hide(); - - if (!res) return; // no result, or city value just changed - if (res.length == 1) { - return res[0]; + if (res && res.length == 1) { + return $scope.updateGeoPoint(res[0]); } - - return ModalUtils.show('plugins/es/templates/common/modal_category.html', 'ESCategoryModalCtrl as ctrl', - { - categories : res, - title: 'PROFILE.MODAL_LOCATIONS.TITLE' - }, - {focusFirstInput: true} - ); + return $scope.openSearchLocationModal({ + text: searchText, + results: res||[], + forceFallback: !res || !res.length // force fallback search first + }); }) - .then(function(res) { - if (res && res.lat && res.lon) { - $scope.formData.geoPoint = $scope.formData.geoPoint || {}; - $scope.formData.geoPoint.lat = parseFloat(res.lat); - $scope.formData.geoPoint.lon = parseFloat(res.lon); - } + .then(function() { + $scope.loadingPosition = false; }) - .catch(UIUtils.onError('PROFILE.ERROR.ADDRESS_LOCATION_FAILED')); + .catch(function(err) { + console.error(err); // Silent + $scope.loadingPosition = false; + }); + }; + + $scope.onCityChanged = function() { + if ($scope.loading) return; + if ($scope.form) { + $scope.form.$valid = undefined; + } + if ($scope.formData.enableGeoPoint) { + return $scope.tryToLocalize(); + } + }; + + $scope.onUseGeopointChanged = function() { + if ($scope.loading) return; + if (!$scope.formData.enableGeoPoint) { + if ($scope.formData.geoPoint) { + $scope.formData.geoPoint.lat = null; + $scope.formData.geoPoint.lon = null; + $scope.form.geoPoint.$setValidity('required', true); + $scope.dirty = true; + } + } + else { + $scope.tryToLocalize(); + } + }; + + $scope.onGeopointChanged = function() { + if ($scope.loading) { + $scope.formData.enableGeoPoint = $scope.formData.geoPoint && !!$scope.formData.geoPoint.lat && !!$scope.formData.geoPoint.lon; + } }; + $scope.$watch('formData.geoPoint', $scope.onGeopointChanged); - $scope.searchPositions = function(query) { + $scope.getAddressToSearch = function() { + return $scope.formData.address && $scope.formData.city ? + [$scope.formData.address.trim(), $scope.formData.city.trim()].join(', ') : + $scope.formData.city || $scope.formData.address; + }; - // Build the query - if (!query) { - if (!$scope.formData.city) { - return $q.when(); // nothing to search + $scope.updateGeoPoint = function(res) { + // user cancel + if (!res || !res.lat || !res.lon) { + // Force use GeoPoint as invalid (if not already a position) + if ($scope.formData.enableGeoPoint && (!$scope.formData.geoPoint || !$scope.formData.geoPoint.lat)) { + $scope.form.geoPoint.$setValidity('required', false); } + return; + } - var cityPart = $scope.formData.city.split(','); - var city = cityPart[0]; + $scope.dirty = true; + $scope.formData.geoPoint = $scope.formData.geoPoint || {}; + $scope.formData.geoPoint.lat = parseFloat(res.lat); + $scope.formData.geoPoint.lon = parseFloat(res.lon); + $scope.form.geoPoint.$setValidity('required', true); - var country = cityPart.length > 1 ? cityPart[1].trim() : defaultCountry; - var street = $scope.formData.address ? angular.copy($scope.formData.address.trim()) : undefined; - if (street) { - // Search with AND without street - return $q.all([ - $scope.searchPositions({ - street: street, - city: city, - country: country - }), - $scope.searchPositions({ - city: city, - country: country - }) - ]) - .then(function(res){ - return res[0].concat(res[1]); - }); + if (res.address && res.address.city) { + var cityParts = [res.address.city]; + if (res.address.postcode) { + cityParts.push(res.address.postcode); } - else { - return $scope.searchPositions({ - city: city, - country: country - }); + if (res.address.country != defaultCountry) { + cityParts.push(res.address.country); } + $scope.formData.city = cityParts.join(', '); } + }; - var queryString = (query.street ? query.street + ', ' : '') + - query.city + - (query.country ? ', ' + query.country : ''); - // Execute the given query - return $q.all([ - $translate('PROFILE.MODAL_LOCATIONS.RESULT_DIVIDER', {address: queryString}), - esGeo.point.searchByAddress(query) - ]) - .then(function(res) { - var dividerText = res[0]; - res = res[1]; - if (!res) return $q.when(); // no result - - // Ask user to choose - var parent = {name: dividerText}; - var hits = res.reduce(function(res, hit){ - if (hit.class == 'waterway') return res; - return res.concat({ - name: hit.display_name, - parent: parent, - lat: hit.lat, - lon: hit.lon - }); - }, [parent]); + /* -- modal -- */ - if (hits.length == 1) return $q.when(); // no result (after filtering) + $scope.openSearchLocationModal = function(options) { - return hits; - }); - }; + options = options || {}; - $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')); + var parameters = { + text: options.text || $scope.getAddressToSearch(), + results: options.results, + fallbackText: options.fallbackText || $scope.formData.city, + forceFallback: angular.isDefined(options.forceFallback) ? options.forceFallback : undefined + }; + + return ModalUtils.show( + 'plugins/es/templates/common/modal_location.html', + 'ESSearchPositionModalCtrl', + parameters, + { + focusFirstInput: true + //,scope: $scope + } + ) + .then($scope.updateGeoPoint); }; +} - $scope.removeLocalisation = function() { - if ($scope.formData.geoPoint) { - $scope.formData.geoPoint.lat = null; - $scope.formData.geoPoint.lon = null; - } +function ESSearchPositionModalController($scope, $q, $translate, esGeo, parameters) { + 'ngInject'; + + $scope.search = { + text: parameters.text || '', + fallbackText: parameters.fallbackText || undefined, + forceFallback: angular.isDefined(parameters.forceFallback) ? parameters.forceFallback : false, + loading: false, + results: parameters.results || undefined }; - $scope.onCityChanged = function() { - if ($scope.loading) return; - var hasGeoPoint = $scope.formData.geoPoint && $scope.formData.geoPoint.lat && $scope.formData.geoPoint.lon; - if (!hasGeoPoint) { - return $scope.localizeByAddress(); + $scope.$on('modal.shown', function() { + // Load search + $scope.doSearch(true/*first search*/); + }); + + $scope.doSearch = function(firstSearch) { + + var text = $scope.search.text && $scope.search.text.trim(); + if (!text) { + return $q.when(); // nothing to search } + + $scope.search.loading = true; + + // Compute alternative query text + var fallbackText = firstSearch && $scope.search.fallbackText && $scope.search.fallbackText.trim(); + fallbackText = fallbackText && fallbackText != text ? fallbackText : undefined; + + // Execute the given query + return ((firstSearch && $scope.search.forceFallback && $scope.search.results) ? + $q.when($scope.search.results) : + esGeo.point.searchByAddress(text) + ) + .then(function(res) { + if (res && res.length || !fallbackText) return res; + + // Fallback search + return $q.all([ + $translate('PROFILE.MODAL_LOCATION.ALTERNATIVE_RESULT_DIVIDER', {address: fallbackText}), + esGeo.point.searchByAddress(fallbackText) + ]) + .then(function (res) { + var dividerText = res[0]; + var res = res[1]; + if (!res || !res.length) return res; + + return [{name: dividerText}].concat(res); + }); + }) + .then(function(res) { + $scope.search.loading = false; + $scope.search.results = res||[]; + + $scope.license = res && res.length && res[0].license; + }) + .catch(function(err) { + $scope.search.loading = false; + $scope.search.results = []; + $scope.license = undefined; + throw err; + }) + ; }; } diff --git a/www/plugins/es/js/controllers/profile-controllers.js b/www/plugins/es/js/controllers/profile-controllers.js index 3ca009adb1bd3938adb9986649b5095e5831fb4c..ed21fbad6bcd101785a787733d59d042114ba6a9 100644 --- a/www/plugins/es/js/controllers/profile-controllers.js +++ b/www/plugins/es/js/controllers/profile-controllers.js @@ -171,6 +171,7 @@ function ESViewEditProfileController($scope, $rootScope, $q, $timeout, $state, $ }, 650); } + $scope.saving = true; console.debug('[ES] [profile] Saving user profile...'); diff --git a/www/plugins/es/js/services/geo-services.js b/www/plugins/es/js/services/geo-services.js index b338f93888622907022ccc9c881bf950d41c8ed6..e7ad142477b4f8accb24e9383933ae76d0bf2160 100644 --- a/www/plugins/es/js/services/geo-services.js +++ b/www/plugins/es/js/services/geo-services.js @@ -18,7 +18,11 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser that.raw = { osm: { - search: csHttp.get('nominatim.openstreetmap.org', 443, '/search.php?format=json') + search: csHttp.get('nominatim.openstreetmap.org', 443, '/search.php?format=json'), + license: { + name: 'OpenStreetMap', + url: 'https://www.openstreetmap.org/copyright' + } }, google: { apiKey: undefined, @@ -27,6 +31,16 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser searchByIP: csHttp.get('freegeoip.net', 443, '/json/:ip') }; + function _normalizeAddressString(text) { + // Remove line break + var searchText = text.trim().replace(/\n/g, ','); + // Remove zip code + searchText = searchText.replace(/(?:^|[\t\n\r\s ])([A−Z09-]+)(?:$|[\t\n\r\s ])/g, ''); + // Remove redundant comma + searchText = searchText.replace(/,[ ,]+/g, ', '); + return searchText; + } + function googleSearchPositionByString(address) { return that.raw.google.search({address: address, key: that.raw.google.apiKey}) @@ -61,13 +75,44 @@ angular.module('cesium.es.geo.services', ['cesium.services', 'cesium.es.http.ser query = {q: query}; } + // Normalize query string + if (query.q) { + query.q = _normalizeAddressString(query.q); + } + + query.addressdetails = 1; // need address field + var now = new Date(); //console.debug('[ES] [geo] Searching position...', query); return that.raw.osm.search(query) .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()), res); - return res; + //console.debug('[ES] [geo] Received {0} results from OSM'.format(res && res.length || 0), res); + if (!res) return; // no result + + // Filter on city/town/village + res = res.reduce(function(res, hit){ + if (hit.class == 'waterway' || !hit.address) return res; + hit.address.city = hit.address.city || hit.address.village || hit.address.town || hit.address.postcode; + hit.address.road = hit.address.road || hit.address.suburb || hit.address.hamlet; + if (hit.address.postcode && hit.address.city == hit.address.postcode) { + delete hit.address.postcode; + } + if (!hit.address.city) return res; + return res.concat({ + id: hit.place_id, + name: hit.display_name, + address: hit.address, + lat: hit.lat, + lon: hit.lon, + class: hit.class, + license: that.raw.osm.license + }); + }, []); + + console.debug('[ES] [geo] Found {0} address position(s)'.format(res && res.length || 0, new Date().getTime() - now.getTime()), res); + + return res.length ? res : undefined; }) // Fallback service diff --git a/www/plugins/es/templates/common/edit_position.html b/www/plugins/es/templates/common/edit_position.html index 04f495c3246d1f94327724a5ae637e825b998221..1d12151d26fc481dd82cc37b5232961badc78f60 100644 --- a/www/plugins/es/templates/common/edit_position.html +++ b/www/plugins/es/templates/common/edit_position.html @@ -11,92 +11,61 @@ </ion-item> <!-- city --> -<div class="item item-input item-floating-label"> +<div class="item item-input item-floating-label" + ng-class="{'item-input-error': form.$submitted && form.geoPoint.$invalid}"> <span class="input-label" translate>PROFILE.CITY</span> <input type="text" placeholder="{{'PROFILE.CITY_HELP'|translate}}" ng-model="formData.city" ng-model-options="{ updateOn: 'blur' }" ng-change="onCityChanged()"> </div> +<input type="hidden" + name="geoPoint" + ng-model="formData.geoPoint" + geo-point-required> +<div class="form-errors" + ng-show="form.$submitted && form.geoPoint.$error" + ng-messages="form.geoPoint.$error"> + <div class="form-error" ng-message="required"> + <span translate="COMMON.ERROR.REQUIRED_FOR_LOCATION" ng-if="!formData.city"></span> + <span translate="COMMON.ERROR.INVALID_FOR_LOCATION" ng-if="formData.city"></span> + </div> +</div> + <!-- Position (lat/lon) --> -<div class="row responsive-md responsive-sm no-padding"> +<div class="item row responsive-md responsive-sm no-padding"> - <!-- lat --> <div class="col no-padding"> - <label class="item item-input item-floating-label" - ng-class="{'item-input-error': form.$submitted && form.latitude.$invalid}"> - <span class="input-label" translate>PROFILE.LATITUDE</span> - <input class="no-padding-right" - name="latitude" - type="number" placeholder="{{'PROFILE.LATITUDE_HELP'|translate}}" - ng-model="formData.geoPoint.lat" - ng-model-options="{ debounce: 350 }" - ng-change="onFormDataChanged()" - min="-90" max="90"> - </label> - <div class="form-errors" - ng-show="form.$submitted && form.latitude.$error" - ng-messages="form.latitude.$error"> - <div class="form-error" ng-message="min"> - <span translate="ERROR.FIELD_MIN" translate-values="{min: -90}"></span> - </div> - <div class="form-error" ng-message="max"> - <span translate="ERROR.FIELD_MAX" translate-values="{max: 90}"></span> - </div> - </div> - </div> - - <div class="col-10 no-padding hidden-xs"> - - </div> - <!-- lon --> - <div class="col no-padding"> - <label class="item item-input item-floating-label" - ng-class="{'item-input-error': form.$submitted && form.longitude.$invalid}"> - <span class="input-label" translate>PROFILE.LONGITUDE</span> - <input class="no-padding-right" - name="longitude" - type="number" placeholder="{{'PROFILE.LONGITUDE_HELP'|translate}}" - ng-model="formData.geoPoint.lon" - ng-model-options="{ debounce: 350 }" - ng-change="onFormDataChanged()" - min="-180" max="180"> - </label> - <div class="form-errors" - ng-show="form.$submitted && form.longitude.$error" - ng-messages="form.longitude.$error"> - <div class="form-error" ng-message="min"> - <span translate="ERROR.FIELD_MIN" translate-values="{min: -180}"></span> + <!-- appear on map ? --> + <ion-checkbox ng-model="formData.enableGeoPoint" + ng-change="onUseGeopointChanged()" + class="item item-border-large done in"> + <div class="item-content"> + <span translate>PROFILE.USE_GEO_POINT</span> + <h4 class="gray" ng-if="loadingPosition"> + <ion-spinner class="icon ion-spinner-small" icon="android"></ion-spinner> + {{'PROFILE.LOADING_LOCATION'|translate}} + </h4> </div> - <div class="form-error" ng-message="max"> - <span translate="ERROR.FIELD_MAX" translate-values="{max: 180}"></span> - </div> - </div> + </ion-checkbox> </div> - <div class="col col-40"> + <div class="col col-10 no-padding"> <div class="row text-center"> - <a class="col button button-stable button-small-padding icon ion-refresh" + <a class="button button-stable button-small-padding" title="{{'PROFILE.BTN_GEOLOC_ADDRESS'|translate}}" - ng-disabled="!formData.city" - ng-click="localizeByAddress()"> + ng-disabled="!formData.enableGeoPoint" + ng-click="openSearchLocationModal()"> + <i class=" icon ion-home" style="left: 15px;"></i> + <b class=" icon-secondary ion-search" style="top: -9px; left:32px; font-size: 18px;"></b> </a> - <a class="col button button-stable button-small-padding icon ion-android-locate" - title="{{'PROFILE.BTN_GEOLOC_ME'|translate}}" - ng-click="localizeMe()"> - </a> - - <a class="col button button-stable button-small-padding icon ion-close" - title="{{'PROFILE.BTN_REMOVE_GEOLOC'|translate}}" - ng-disabled="!formData.geoPoint || (!formData.geoPoint.lat && !formData.geoPoint.lon)" - ng-click="removeLocalisation()"> - </a> </div> </div> </div> + <cs-extension-point name="after-position"></cs-extension-point> diff --git a/www/plugins/es/templates/common/modal_location.html b/www/plugins/es/templates/common/modal_location.html new file mode 100644 index 0000000000000000000000000000000000000000..b8987bb6f8ca3df69c6cf13111cf6953126f7610 --- /dev/null +++ b/www/plugins/es/templates/common/modal_location.html @@ -0,0 +1,90 @@ +<ion-modal-view class="modal-full-height modal-search-location"> + <ion-header-bar class="bar-positive"> + <button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button> + <h1 class="title" translate>PROFILE.MODAL_LOCATION.TITLE</h1> + </ion-header-bar> + + <ion-content class="padding no-padding-xs" scroll="true"> + + + <!-- search text --> + <div class="item item-input"> + <i class="icon ion-search placeholder-icon"></i> + + <input type="text" + class="visible-xs visible-sm" + placeholder="{{'PROFILE.MODAL_LOCATION.SEARCH_HELP'|translate}}" + ng-model="search.text" + ng-model-options="{ debounce: 650 }" + ng-change="doSearch()"> + <input type="text" + class="hidden-xs hidden-sm" + placeholder="{{'PROFILE.MODAL_LOCATION.SEARCH_HELP'|translate}}" + ng-model="search.text" + on-return="doSearch()"> + </div> + + <div class="padding-top padding-xs" style="display: block; height: 60px;"> + <div class="pull-left" ng-if="!search.loading && search.results"> + <h4 translate>COMMON.RESULTS_LIST</h4> + </div> + + <div class="pull-right hidden-xs hidden-sm"> + <button class="button button-small button-stable ink" + ng-click="doSearch()"> + {{'COMMON.BTN_SEARCH' | translate}} + </button> + </div> + + </div> + + <div class="text-center" ng-if="search.loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + + <div ng-if="!search.loading && search.results && (!search.results.length || !search.results[0].address)" + class="assertive padding"> + <span translate>COMMON.SEARCH_NO_RESULT</span> + </div> + + <ion-list ng-if="!search.loading" + class="padding-top {{::motion.ionListClass}}"> + <div ng-repeat="res in search.results" + class="item item-border-large item-text-wrap ink" + ng-class="::{'item-divider': !res.address, 'item-icon-left item-icon-right': res.address}" + ng-click="res.address ? closeModal(res) : false"> + + <!-- if divider --> + <h4 class="text-italic" ng-if="::!res.address" ng-bind-html="res.name"></h4> + + <!-- if divider --> + <ng-if ng-if="::res.address"> + + <i class="icon ion-location"></i> + + <h2 ng-if="res.address.road"> + {{::res.address.road}} + </h2> + <h3> + <span ng-if="res.address.postcode">{{::res.address.postcode}}</span> + {{::res.address.city||res.address.village}} + <span class="gray">| {{::res.address.country}}</span> + </h3> + <h5 class="gray"> + {{'PROFILE.MODAL_LOCATION.POSITION'|translate:res }} + </h5> + + <i class="icon ion-ios-arrow-right"></i> + </ng-if> + + </div> + </ion-list> + </ion-content> + + <ion-footer-bar class="stable-bg padding-left padding-right block" ng-if="license"> + <div class="pull-right copyright"> + <span class="dark">© </span> + <a class="positive" href="{{license.url}}" target="_blank">{{license.name}}</a> + </div> + </ion-footer-bar> +</ion-modal-view> diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html index 5a49ea3ccec6ef13443df5e394ac5af11af7a299..296b9268e2bdfb6acd559de70cbb98abc0398da3 100644 --- a/www/plugins/es/templates/user/edit_profile.html +++ b/www/plugins/es/templates/user/edit_profile.html @@ -93,7 +93,7 @@ </ion-item> <!-- position --> - <ng-include src="'plugins/es/templates/common/edit_position.html'" ng-controller="ESPositionEditCtrl"></ng-include> + <ng-include src="'plugins/es/templates/common/edit_position.html'" ng-controller="ESPositionEditCtrl as ctrl"></ng-include> <!-- social networks --> <ng-include src="'plugins/es/templates/common/edit_socials.html'" ng-controller="ESSocialsEditCtrl"></ng-include> diff --git a/www/plugins/map/i18n/locale-fr-FR.json b/www/plugins/map/i18n/locale-fr-FR.json index 8070a7d00798e80525d4d43a1bfc48d6dc2c9d20..a7df1a09e968a66b36e3124a6b583229eea7e8f4 100644 --- a/www/plugins/map/i18n/locale-fr-FR.json +++ b/www/plugins/map/i18n/locale-fr-FR.json @@ -32,7 +32,7 @@ } }, "PROFILE": { - "MARKER_HELP": "<b>Glissez-déposez</b> ce marqueur pour <b>mettre<br/>à jour votre position</b>, ou utilisez les boutons<br/>au dessus de la carte." + "MARKER_HELP": "<b>Glissez-déposez</b> ce marqueur pour <b>mettre<br/>à jour votre position</b> sur la carte, ou utilisez le bouton<br/>de recherche au dessus de la carte." }, "ERROR": { "LOCALIZE_ME_FAILED": "Impossible de récupérer votre position actuelle" diff --git a/www/plugins/map/js/controllers/user-controllers.js b/www/plugins/map/js/controllers/user-controllers.js index 457941574c44ee86d39e45036a8ceadfcfb24c8d..246a4711fdb56c8c441732496f32ed33ef99bf69 100644 --- a/www/plugins/map/js/controllers/user-controllers.js +++ b/www/plugins/map/js/controllers/user-controllers.js @@ -30,11 +30,6 @@ angular.module('cesium.map.user.controllers', ['cesium.services', 'cesium.map.se markers: {}, center: { zoom: 13 - }, - defaults: { - tileLayerOptions: { - attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' - } } }); $scope.loading = true; diff --git a/www/plugins/map/js/services/utils-services.js b/www/plugins/map/js/services/utils-services.js index 10aad7631c66ed724f5e6f9f4df541a7f82169bf..aad671082571a0d664a830af3fe8f149ccf424a3 100644 --- a/www/plugins/map/js/services/utils-services.js +++ b/www/plugins/map/js/services/utils-services.js @@ -26,7 +26,10 @@ angular.module('cesium.map.utils.services', ['cesium.services', 'ui-leaflet']) center: angular.copy(constants.DEFAULT_CENTER), cache: false, defaults: { - scrollWheelZoom: true + scrollWheelZoom: true, + tileLayerOptions: { + attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' + } }, layers: { baselayers: { diff --git a/www/templates/wot/lookup_form.html b/www/templates/wot/lookup_form.html index cde0230d1a8be0fe62d214ff086fe18e3551db3a..2f0f12786bd8fe4dea0f6205ed8fcd78682c3c03 100644 --- a/www/templates/wot/lookup_form.html +++ b/www/templates/wot/lookup_form.html @@ -54,7 +54,7 @@ </h4> </div> - <div class=" pull-right hidden-xs hidden-sm"> + <div class="pull-right hidden-xs hidden-sm"> <a ng-if="enableFilter" class="button button-text button-small ink" ng-class="{'button-text-positive': search.type=='newcomers'}"