From c599a47d5887ec953ee3defea669115332c69dc2 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 8 Nov 2019 19:37:14 +0100 Subject: [PATCH] Introduce Mimirsbrunn geocoder and improve addresses & maps Signed-off-by: Thomas Citharel --- config/config.exs | 3 + config/dev.exs | 2 +- js/package.json | 2 + .../components/Event/AddressAutoComplete.vue | 241 +++++++++++++----- js/src/components/Map.vue | 58 ++++- .../Map/Vue2LeafletLocateControl.vue | 47 ++++ js/src/graphql/address.ts | 38 ++- js/src/graphql/config.ts | 8 +- js/src/graphql/event.ts | 4 +- js/src/i18n/en_US.json | 7 +- js/src/i18n/fr_FR.json | 51 ++-- js/src/types/address.model.ts | 87 ++++++- js/src/types/config.model.ts | 6 + js/src/types/event.model.ts | 4 +- js/src/utils/.editorconfig | 22 ++ js/src/utils/poiIcons.ts | 61 +++++ js/src/views/Event/Event.vue | 78 +++--- js/src/vue-apollo.ts | 9 +- js/yarn.lock | 14 +- lib/mobilizon/addresses/address.ex | 5 +- lib/mobilizon/events/event.ex | 21 +- lib/mobilizon_web/resolvers/address.ex | 44 ++-- lib/mobilizon_web/resolvers/config.ex | 23 +- lib/mobilizon_web/schema/address.ex | 5 + lib/mobilizon_web/schema/config.ex | 8 + lib/service/geospatial/google_maps.ex | 8 +- lib/service/geospatial/mimirsbrunn.ex | 146 +++++++++++ lib/service/geospatial/nominatim.ex | 80 +++--- lib/service/geospatial/provider.ex | 1 + .../20191106141051_add_type_to_addresses.exs | 9 + schema.graphql | 14 +- .../geospatial/nominatim/geocode.json | 10 +- .../geospatial/nominatim/search.json | 10 +- .../service/geospatial/nominatim_test.exs | 67 ++--- .../resolvers/address_resolver_test.exs | 10 +- test/support/mocks/geospatial_mock.ex | 4 +- 36 files changed, 940 insertions(+), 267 deletions(-) create mode 100644 js/src/components/Map/Vue2LeafletLocateControl.vue create mode 100644 js/src/utils/.editorconfig create mode 100644 js/src/utils/poiIcons.ts create mode 100644 lib/service/geospatial/mimirsbrunn.ex create mode 100644 priv/repo/migrations/20191106141051_add_type_to_addresses.exs diff --git a/config/config.exs b/config/config.exs index 243c7c0b4..8bf3b54e3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -137,6 +137,9 @@ config :mobilizon, Mobilizon.Service.Geospatial.GoogleMaps, config :mobilizon, Mobilizon.Service.Geospatial.MapQuest, api_key: System.get_env("GEOSPATIAL_MAP_QUEST_API_KEY") || nil +config :mobilizon, Mobilizon.Service.Geospatial.Mimirsbrunn, + endpoint: System.get_env("GEOSPATIAL_MIMIRSBRUNN_ENDPOINT") || nil + config :mobilizon, Oban, repo: Mobilizon.Storage.Repo, prune: {:maxlen, 10_000}, diff --git a/config/dev.exs b/config/dev.exs index ce28a21d0..ac5f5a921 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -52,7 +52,7 @@ config :mobilizon, MobilizonWeb.Endpoint, # Do not include metadata nor timestamps in development logs config :logger, :console, format: "[$level] $message\n", level: :debug -config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.GoogleMaps +config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. diff --git a/js/package.json b/js/package.json index 7af8d1cf1..f5e1d6220 100644 --- a/js/package.json +++ b/js/package.json @@ -26,6 +26,7 @@ "graphql-tag": "^2.10.1", "intersection-observer": "^0.7.0", "leaflet": "^1.4.0", + "leaflet.locatecontrol": "^0.68.0", "lodash": "^4.17.11", "ngeohash": "^0.6.3", "register-service-worker": "^1.6.2", @@ -44,6 +45,7 @@ "devDependencies": { "@types/chai": "^4.2.3", "@types/leaflet": "^1.5.2", + "@types/leaflet.locatecontrol": "^0.60.7", "@types/lodash": "^4.14.141", "@types/mocha": "^5.2.6", "@vue/cli-plugin-babel": "^4.0.3", diff --git a/js/src/components/Event/AddressAutoComplete.vue b/js/src/components/Event/AddressAutoComplete.vue index c3b83fd3e..8b3e6f246 100644 --- a/js/src/components/Event/AddressAutoComplete.vue +++ b/js/src/components/Event/AddressAutoComplete.vue @@ -1,125 +1,242 @@ diff --git a/js/src/components/Map.vue b/js/src/components/Map.vue index d1c11d57e..9721cc763 100644 --- a/js/src/components/Map.vue +++ b/js/src/components/Map.vue @@ -5,40 +5,54 @@ :style="`height: ${mergedOptions.height}; width: ${mergedOptions.width}`" class="leaflet-map" :center="[lat, lon]" + @click="clickMap" + @update:zoom="updateZoom" > - - {{ popup }} + + + + {{ line }}
+
diff --git a/js/src/graphql/address.ts b/js/src/graphql/address.ts index b45790051..9f3857eb5 100644 --- a/js/src/graphql/address.ts +++ b/js/src/graphql/address.ts @@ -1,20 +1,34 @@ import gql from 'graphql-tag'; +const $addressFragment = ` +id, +description, +geom, +street, +locality, +postalCode, +region, +country, +type, +url, +originId +`; + export const ADDRESS = gql` - query($query:String!) { + query($query:String!, $locale: String) { searchAddress( - query: $query + query: $query, + locale: $locale ) { - id, - description, - geom, - street, - locality, - postalCode, - region, - country, - url, - originId + ${$addressFragment} + } + } +`; + +export const REVERSE_GEOCODE = gql` + query($latitude: Float!, $longitude: Float!, $zoom: Int, $locale: String) { + reverseGeocode(latitude: $latitude, longitude: $longitude, zoom: $zoom, locale: $locale) { + ${$addressFragment} } } `; diff --git a/js/src/graphql/config.ts b/js/src/graphql/config.ts index 3258080b1..ff1f3f227 100644 --- a/js/src/graphql/config.ts +++ b/js/src/graphql/config.ts @@ -5,7 +5,13 @@ query { config { name, description, - registrationsOpen + registrationsOpen, + countryCode, + location { + latitude, + longitude, + accuracyRadius + } } } `; diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts index a26250b19..b970e7300 100644 --- a/js/src/graphql/event.ts +++ b/js/src/graphql/event.ts @@ -24,7 +24,9 @@ const physicalAddressQuery = ` region, country, geom, - id + type, + id, + originId `; const tagsQuery = ` diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index fd0f92998..8955f1ede 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -110,6 +110,7 @@ "From the {startDate} to the {endDate}": "From the {startDate} to the {endDate}", "Gather ⋅ Organize ⋅ Mobilize": "Gather ⋅ Organize ⋅ Mobilize", "General information": "General information", + "Getting location": "Getting location", "Going as {name}": "Going as {name}", "Group List": "Group List", "Group full name": "Group full name", @@ -160,7 +161,7 @@ "No events found": "No events found", "No group found": "No group found", "No groups found": "No groups found", - "No results for \"{queryText}\"": "No results for \"{queryText}\"", + "No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map", "No user account with this email was found. Maybe you made a typo?": "No user account with this email was found. Maybe you made a typo?", "Number of places": "Number of places", "OK": "OK", @@ -195,7 +196,6 @@ "Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.", "Please read the full rules": "Please read the full rules", "Please refresh the page and retry.": "Please refresh the page and retry.", - "Please type at least 5 characters": "Please type at least 5 characters", "Postal Code": "Postal Code", "Private event": "Private event", "Private feeds": "Private feeds", @@ -327,5 +327,6 @@ "{count} participants": "No participants yet | One participant | {count} participants", "{count} requests waiting": "{count} requests waiting", "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.", - "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks" + "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks", + "© The OpenStreetMap Contributors": "© The OpenStreetMap Contributors" } diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 46075f759..1cd12072e 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -3,14 +3,14 @@ "A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Un outil convivial, émancipateur et éthique pour se rassembler, s'organiser et se mobiliser.", "A validation email was sent to {email}": "Un email de validation a été envoyé à {email}", "Abandon edition": "Abandonner l'édition", - "About": "À propos", "About Mobilizon": "À propos de Mobilizon", "About this event": "À propos de cet événement", "About this instance": "À propos de cette instance", - "Add": "Ajouter", + "About": "À propos", "Add an address": "Ajouter une adresse", "Add some tags": "Ajouter des tags", "Add to my calendar": "Ajouter à mon agenda", + "Add": "Ajouter", "Additional comments": "Commentaires additionnels", "Administration": "Administration", "All data will be deleted every 48 hours, so please don't use this for anything real.": "Toutes les données seront effacées toutes les 48 heures, donc n'utilisez pas ce site à des fins autres que de démonstration.", @@ -25,28 +25,27 @@ "Avatar": "Avatar", "Before you can login, you need to click on the link inside it to validate your account": "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte", "By {name}": "Par {name}", - "Cancel": "Annuler", "Cancel creation": "Annuler la création", "Cancel edition": "Annuler l'édition", "Cancel my participation request…": "Annuler ma demande de participation…", "Cancel my participation…": "Annuler ma participation…", - "Cancelled: Won't happen": "Annulé : N'aura pas lieu", + "Cancel": "Annuler", + "Cancelled: Won't happen": "Annulé : N'aura pas lieu", "Category": "Catégorie", - "Change": "Modifier", "Change my identity…": "Changer mon identité…", "Change my password": "Modifier mon mot de passe", "Change password": "Modifier mot de passe", + "Change": "Modifier", "Clear": "Effacer", "Click to select": "Cliquez pour sélectionner", "Click to upload": "Cliquez pour uploader", "Close comments for all (except for admins)": "Fermer les commentaires à tout le monde (excepté les administrateurs)", - "Comments": "Commentaires", "Comments on the event page": "Commentaires sur la page de l'événement", + "Comments": "Commentaires", "Confirm my particpation": "Confirmer ma participation", "Confirmed: Will happen": "Confirmé : aura lieu", "Continue editing": "Continuer l'édition", "Country": "Pays", - "Create": "Créer", "Create a new event": "Créer un nouvel événement", "Create a new group": "Créer un nouveau groupe", "Create a new identity": "Créer une nouvelle identité", @@ -57,16 +56,17 @@ "Create my profile": "Créer mon profil", "Create token": "Créer un jeton", "Create, edit or delete events": "Créer, modifier ou supprimer des événements", + "Create": "Créer", "Creator": "Créateur", "Current identity has been changed to {identityName} in order to manage this event.": "L'identité actuelle a été changée à {identityName} pour pouvoir gérer cet événement.", "Date and time settings": "Paramètres de date et d'heure", "Date parameters": "Paramètres de date", - "Delete": "Supprimer", "Delete event": "Supprimer un événement", "Delete this identity": "Supprimer cette identité", "Delete your identity": "Supprimer votre identité", "Delete {eventTitle}": "Supprimer {eventTitle}", "Delete {preferredUsername}": "Supprimer {preferredUsername}", + "Delete": "Supprimer", "Description": "Description", "Didn't receive the instructions ?": "Vous n'avez pas reçu les instructions ?", "Display name": "Nom affiché", @@ -84,7 +84,6 @@ "Error while communicating with the server.": "Erreur de communication avec le serveur.", "Error while saving report.": "Erreur lors de l'enregistrement du signalement.", "Error while validating account": "Erreur lors de la validation du compte", - "Event": "Événement", "Event already passed": "Événement déjà passé", "Event cancelled": "Événement annulé", "Event creation": "Création d'événement", @@ -95,6 +94,7 @@ "Event to be confirmed": "Événement à confirmer", "Event {eventTitle} deleted": "Événement {eventTitle} supprimé", "Event {eventTitle} reported": "Événement {eventTitle} signalé", + "Event": "Événement", "Events": "Événements", "Exclude": "Exclure", "Explore": "Explorer", @@ -102,14 +102,15 @@ "Features": "Fonctionnalités", "Find an address": "Trouver une adresse", "Find an instance": "Trouver une instance", - "For instance: London, Taekwondo, Architecture…": "Par exemple : Lyon, Taekwondo, Architecture…", + "For instance: London, Taekwondo, Architecture…": "Par exemple : Lyon, Taekwondo, Architecture…", "Forgot your password ?": "Mot de passe oublié ?", "From a birthday party with friends and family to a march for climate change, right now, our gatherings are trapped inside the tech giants’ platforms. How can we organize, how can we click “Attend,” without providing private data to Facebook or locking ourselves up inside MeetUp?": "De l’anniversaire entre ami·e·s à une marche pour le climat, aujourd’hui, les bonnes raisons de se rassembler sont captées par les géants du web. Comment s’organiser, comment cliquer sur « je participe » sans livrer des données intimes à Facebook ou s’enfermer dans MeetUp ?", - "From the {startDate} at {startTime} to the {endDate}": "Du {startDate} à {startTime} jusqu'au {endDate}", "From the {startDate} at {startTime} to the {endDate} at {endTime}": "Du {startDate} à {startTime} au {endDate} à {endTime}", + "From the {startDate} at {startTime} to the {endDate}": "Du {startDate} à {startTime} jusqu'au {endDate}", "From the {startDate} to the {endDate}": "Du {startDate} au {endDate}", "Gather ⋅ Organize ⋅ Mobilize": "Rassembler ⋅ Organiser ⋅ Mobiliser", "General information": "Informations générales", + "Getting location": "Récupération de la position", "Going as {name}": "En tant que {name}", "Group List": "Liste de groupes", "Group full name": "Nom complet du groupe", @@ -131,8 +132,8 @@ "Join {instance}, a Mobilizon instance": "Rejoignez {instance}, une instance Mobilizon", "Last published event": "Dernier événement publié", "Last week": "La semaine dernière", - "Learn more": "En apprendre plus", "Learn more about Mobilizon": "En apprendre plus à propos de Mobilizon", + "Learn more": "En apprendre plus", "Leave event": "Annuler ma participation à l'événement", "Leaving event \"{title}\"": "Annuler ma participation à l'événement", "Let's create a new common": "Créons un nouveau Common", @@ -142,8 +143,8 @@ "Locality": "Commune", "Log in": "Se connecter", "Log out": "Se déconnecter", - "Login": "Se connecter", "Login on Mobilizon!": "Se connecter sur Mobilizon !", + "Login": "Se connecter", "Manage participations": "Gérer les participations", "Members": "Membres", "Mobilizon is a free/libre software that will allow communities to create their own spaces to publish events in order to better emancipate themselves from tech giants.": "Mobilizon est un logiciel libre qui permettra à des communautés de créer leurs propres espaces de publication d’événements, afin de mieux s’émanciper des géants du web.", @@ -161,19 +162,20 @@ "No group found": "Aucun groupe trouvé", "No groups found": "Aucun groupe trouvé", "No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »", + "No results for \"{queryText}\". You can try another search term or drag and drop the marker on the map": "Pas de résultats pour « {queryText} ». Vous pouvez essayer avec d'autres termes de recherche ou bien glisser et déposer le marqueur sur la carte", "No user account with this email was found. Maybe you made a typo?": "Aucun compte utilisateur trouvé pour cet email. Peut-être avez-vous fait une faute de frappe ?", "Number of places": "Nombre de places", "OK": "OK", "Old password": "Ancien mot de passe", - "On {date}": "Le {date}", "On {date} ending at {endTime}": "Le {date}, se terminant à {endTime}", "On {date} from {startTime} to {endTime}": "Le {date} de {startTime} à {endTime}", "On {date} starting at {startTime}": "Le {date} à partir de {startTime}", + "On {date}": "Le {date}", "One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont", "Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)", "Opened reports": "Signalements ouverts", - "Organized": "Organisés", "Organized by {name}": "Organisé par {name}", + "Organized": "Organisés", "Organizer": "Organisateur", "Otherwise this identity will just be removed from the group administrators.": "Sinon cette identité sera juste supprimée des administrateurs du groupe.", "Page limited to my group (asks for auth)": "Accès limité à mon groupe (demande authentification)", @@ -184,10 +186,10 @@ "Participate": "Participer", "Participation approval": "Validation des participations", "Participation requested!": "Participation demandée !", - "Password": "Mot de passe", "Password (confirmation)": "Mot de passe (confirmation)", "Password change": "Changement de mot de passe", "Password reset": "Réinitialisation du mot de passe", + "Password": "Mot de passe", "Past events": "Événements passés", "Pick an identity": "Choisissez une identité", "Please check your spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.", @@ -209,23 +211,23 @@ "RSS/Atom Feed": "Flux RSS/Atom", "Read Framasoft’s statement of intent on the Framablog": "Lire la note d’intention de Framasoft sur le Framablog", "Region": "Région", - "Register": "S'inscrire", "Register an account on Mobilizon!": "S'inscrire sur Mobilizon !", "Register for an event by choosing one of your identities": "S'inscrire à un événement en choisissant une de vos identités", + "Register": "S'inscrire", "Registration is currently closed.": "Les inscriptions sont actuellement fermées.", "Reject": "Rejetter", - "Rejected": "Rejetés", "Rejected participations": "Participations rejetées", - "Report": "Signaler", + "Rejected": "Rejetés", "Report this event": "Signaler cet événement", + "Report": "Signaler", "Requests": "Requêtes", "Resend confirmation email": "Envoyer à nouveau l'email de confirmation", "Reset my password": "Réinitialiser mon mot de passe", - "Save": "Enregistrer", "Save draft": "Enregistrer le brouillon", - "Search": "Rechercher", + "Save": "Enregistrer", "Search events, groups, etc.": "Rechercher des événements, des groupes, etc.", - "Search results: \"{search}\"": "Résultats de recherche : « {search} »", + "Search results: \"{search}\"": "Résultats de recherche : « {search} »", + "Search": "Rechercher", "Searching…": "Recherche en cours…", "Send me an email to reset my password": "Envoyez-moi un email pour réinitialiser mon mot de passe", "Send me the confirmation email once again": "Envoyez-moi l'email de confirmation encore une fois", @@ -246,8 +248,8 @@ "The draft event has been updated": "L'événement brouillon a été mis à jour", "The event has been created as a draft": "L'événement a été créé en tant que brouillon", "The event has been published": "L'événement a été publié", - "The event has been updated": "L'événement a été mis à jour", "The event has been updated and published": "L'événement a été mis à jour et publié", + "The event has been updated": "L'événement a été mis à jour", "The event organizer didn't add any description.": "L'organisateur de l'événement n'a pas ajouté de description.", "The event title will be ellipsed.": "Le titre de l'événement sera ellipsé.", "The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.", @@ -327,5 +329,6 @@ "{count} participants": "Aucun⋅e participant⋅e | Un⋅e participant⋅e | {count} participant⋅e⋅s", "{count} requests waiting": "Une demande en attente|{count} demandes en attente", "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.", - "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines" + "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines", + "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap" } diff --git a/js/src/types/address.model.ts b/js/src/types/address.model.ts index 8db566691..d340e0db6 100644 --- a/js/src/types/address.model.ts +++ b/js/src/types/address.model.ts @@ -1,11 +1,14 @@ +import poiIcons from '@/utils/poiIcons'; + export interface IAddress { - id?: number; + id?: string; description: string; street: string; locality: string; postalCode: string; region: string; country: string; + type: string; geom?: string; url?: string; originId?: string; @@ -18,4 +21,86 @@ export class Address implements IAddress { postalCode: string = ''; region: string = ''; street: string = ''; + type: string = ''; + id?: string = ''; + originId?: string = ''; + url?: string = ''; + geom?: string = ''; + + constructor(hash?) { + if (!hash) return; + + this.id = hash.id; + this.description = hash.description; + this.street = hash.street; + this.locality = hash.locality; + this.postalCode = hash.postalCode; + this.region = hash.region; + this.country = hash.country; + this.type = hash.type; + this.geom = hash.geom; + this.url = hash.url; + this.originId = hash.originId; + } + + get poiInfos() { + /* generate name corresponding to poi type */ + let name = ''; + let alternativeName = ''; + let poiIcon = poiIcons.default; + // Google Maps doesn't have a type + if (this.type == null && this.description === this.street) this.type = 'house'; + + switch (this.type) { + case 'house': + name = this.description; + alternativeName = [this.postalCode, this.locality, this.country].filter(zone => zone).join(', '); + poiIcon = poiIcons.defaultAddress; + break; + case 'street': + case 'secondary': + name = this.description; + alternativeName = [this.postalCode, this.locality, this.country].filter(zone => zone).join(', '); + poiIcon = poiIcons.defaultStreet; + break; + case 'zone': + case 'city': + case 'administrative': + name = this.postalCode ? `${this.description} (${this.postalCode})` : this.description; + alternativeName = [this.region, this.country].filter(zone => zone).join(', '); + poiIcon = poiIcons.defaultAdministrative; + break; + default: + // POI + name = this.description; + alternativeName = ''; + if (this.street && this.street.trim()) { + alternativeName = `${this.street}`; + if (this.locality) { + alternativeName += ` (${this.locality})`; + } + } else if (this.locality && this.locality.trim()) { + alternativeName = `${this.locality}, ${this.region}, ${this.country}`; + } else { + alternativeName = `${this.region}, ${this.country}`; + } + poiIcon = this.iconForPOI; + break; + } + return { name, alternativeName, poiIcon }; + } + + get fullName() { + const { name, alternativeName } = this.poiInfos; + return `${name}, ${alternativeName}`; + } + + get iconForPOI() { + if (this.type == null) { + return poiIcons.default; + } + const type = this.type.split(':').pop() || ''; + if (poiIcons[type]) return poiIcons[type]; + return poiIcons.default; + } } diff --git a/js/src/types/config.model.ts b/js/src/types/config.model.ts index 84f15eb6d..8da0a0c90 100644 --- a/js/src/types/config.model.ts +++ b/js/src/types/config.model.ts @@ -3,4 +3,10 @@ export interface IConfig { description: string; registrationsOpen: boolean; + countryCode: string; + location: { + latitude: number; + longitude: number; + accuracyRadius: number; + }; } diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts index 494dbb45d..3c29374d1 100644 --- a/js/src/types/event.model.ts +++ b/js/src/types/event.model.ts @@ -1,5 +1,5 @@ import { Actor, IActor, IPerson } from './actor'; -import { IAddress } from '@/types/address.model'; +import { Address, IAddress } from '@/types/address.model'; import { ITag } from '@/types/tag.model'; import { IPicture } from '@/types/picture.model'; @@ -239,7 +239,7 @@ export class EventModel implements IEvent { this.onlineAddress = hash.onlineAddress; this.phoneAddress = hash.phoneAddress; - this.physicalAddress = hash.physicalAddress; + this.physicalAddress = new Address(hash.physicalAddress); this.participantStats = hash.participantStats; this.tags = hash.tags; diff --git a/js/src/utils/.editorconfig b/js/src/utils/.editorconfig new file mode 100644 index 000000000..b6b82f05c --- /dev/null +++ b/js/src/utils/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +trim_trailing_whitespace = true + +[*.ex] +indent_size = 2 +tab_width = 2 + +[*.scss] +indent_size = 2 + +[*.ts] +indent_size = 2 +tab_width = 2 diff --git a/js/src/utils/poiIcons.ts b/js/src/utils/poiIcons.ts new file mode 100644 index 000000000..eb384aa0e --- /dev/null +++ b/js/src/utils/poiIcons.ts @@ -0,0 +1,61 @@ +export default { + default: { + icon: 'map-marker', + color: '#5C6F84', + }, + defaultAdministrative: { + icon: 'city', + color: '#5c6f84', + }, + defaultStreet: { + icon: 'road-variant', + color: '#5c6f84', + }, + defaultAddress: { + icon: 'home', + color: '#5c6f84', + }, + place_house: { + icon: 'home', + color: '#5c6f84', + }, + theatre: { + icon: 'drama-masks', + }, + parking: { + icon: 'parking', + }, + police: { + icon: 'police-badge', + }, + post_office: { + icon: 'email', + }, + university: { + icon: 'school', + }, + college: { + icon: 'school', + }, + park: { + icon: 'pine-tree', + }, + garden: { + icon: 'pine-tree', + }, + bicycle_rental: { + icon: 'bicycle', + }, + hospital: { + icon: 'hospital-box', + }, + townhall: { + icon: 'office-building', + }, + toilets: { + icon: 'human-male-female', + }, + hairdresser: { + icon: 'content-cut', + }, +}; diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue index 465b3d8ec..50d2e0918 100644 --- a/js/src/views/Event/Event.vue +++ b/js/src/views/Event/Event.vue @@ -1,4 +1,3 @@ -import {ParticipantRole} from "@/types/event.model";