Introduce Mimirsbrunn geocoder and improve addresses & maps
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -1,125 +1,242 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-field :label="$t('Find an address')">
|
||||
<b-field expanded>
|
||||
<template slot="label">
|
||||
{{ $t('Find an address') }}
|
||||
<b-button v-if="!gettingLocation" size="is-small" icon-right="map-marker" @click="locateMe" />
|
||||
<span v-else>{{ $t('Getting location') }}</span>
|
||||
</template>
|
||||
<b-autocomplete
|
||||
:data="data"
|
||||
v-model="queryText"
|
||||
:placeholder="$t('e.g. 10 Rue Jangot')"
|
||||
field="description"
|
||||
field="fullName"
|
||||
:loading="isFetching"
|
||||
@typing="getAsyncData"
|
||||
icon="map-marker"
|
||||
@select="option => selected = option">
|
||||
expanded
|
||||
@select="updateSelected">
|
||||
|
||||
<template slot-scope="{option}">
|
||||
<b>{{ option.description }}</b><br />
|
||||
<i v-if="option.url != null">Local</i>
|
||||
<p>
|
||||
<small>{{ option.street }},  {{ option.postalCode }} {{ option.locality }}</small>
|
||||
</p>
|
||||
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
||||
<b>{{ option.poiInfos.name }}</b><br />
|
||||
<small>{{ option.poiInfos.alternativeName }}</small>
|
||||
</template>
|
||||
<template slot="empty">
|
||||
<span v-if="queryText.length < 5">{{ $t('Please type at least 5 characters') }}</span>
|
||||
<span v-else-if="isFetching">{{ $t('Searching…') }}</span>
|
||||
<span v-if="isFetching">{{ $t('Searching…') }}</span>
|
||||
<div v-else class="is-enabled">
|
||||
<span>{{ $t('No results for "{queryText}"', { queryText }) }}</span>
|
||||
<p class="control" @click="addressModalActive = true">
|
||||
<button type="button" class="button is-primary">{{ $t('Add') }}</button>
|
||||
</p>
|
||||
<span>{{ $t('No results for "{queryText}". You can try another search term or drag and drop the marker on the map', { queryText }) }}</span>
|
||||
<!-- <p class="control" @click="openNewAddressModal">-->
|
||||
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
|
||||
<!-- </p>-->
|
||||
</div>
|
||||
</template>
|
||||
</b-autocomplete>
|
||||
</b-field>
|
||||
<b-modal :active.sync="addressModalActive" :width="640" has-modal-card scroll="keep">
|
||||
<div class="modal-card" style="width: auto">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">{{ $t('Add an address') }}</p>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<form>
|
||||
<b-field :label="$t('Name')">
|
||||
<b-input aria-required="true" required v-model="selected.description" />
|
||||
</b-field>
|
||||
<div class="map" v-if="selected && selected.geom">
|
||||
<map-leaflet
|
||||
:coords="selected.geom"
|
||||
:marker="{ text: [selected.poiInfos.name, selected.poiInfos.alternativeName], icon: selected.poiInfos.poiIcon.icon}"
|
||||
:updateDraggableMarkerCallback="reverseGeoCode"
|
||||
:options="{ zoom: mapDefaultZoom }"
|
||||
:readOnly="false"
|
||||
/>
|
||||
</div>
|
||||
<!-- <b-modal v-if="selected" :active.sync="addressModalActive" :width="640" has-modal-card scroll="keep">-->
|
||||
<!-- <div class="modal-card" style="width: auto">-->
|
||||
<!-- <header class="modal-card-head">-->
|
||||
<!-- <p class="modal-card-title">{{ $t('Add an address') }}</p>-->
|
||||
<!-- </header>-->
|
||||
<!-- <section class="modal-card-body">-->
|
||||
<!-- <form>-->
|
||||
<!-- <b-field :label="$t('Name')">-->
|
||||
<!-- <b-input aria-required="true" required v-model="selected.description" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<b-field :label="$t('Street')">
|
||||
<b-input v-model="selected.street" />
|
||||
</b-field>
|
||||
<!-- <b-field :label="$t('Street')">-->
|
||||
<!-- <b-input v-model="selected.street" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<b-field :label="$t('Postal Code')">
|
||||
<b-input v-model="selected.postalCode" />
|
||||
</b-field>
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Postal Code')">-->
|
||||
<!-- <b-input v-model="selected.postalCode" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<b-field :label="$t('Locality')">
|
||||
<b-input v-model="selected.locality" />
|
||||
</b-field>
|
||||
<!-- <b-field :label="$t('Locality')">-->
|
||||
<!-- <b-input v-model="selected.locality" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<b-field :label="$t('Region')">
|
||||
<b-input v-model="selected.region" />
|
||||
</b-field>
|
||||
<!-- <b-field grouped>-->
|
||||
<!-- <b-field :label="$t('Region')">-->
|
||||
<!-- <b-input v-model="selected.region" />-->
|
||||
<!-- </b-field>-->
|
||||
|
||||
<b-field :label="$t('Country')">
|
||||
<b-input v-model="selected.country" />
|
||||
</b-field>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
<!-- <b-field :label="$t('Country')">-->
|
||||
<!-- <b-input v-model="selected.country" />-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </b-field>-->
|
||||
<!-- </form>-->
|
||||
<!-- </section>-->
|
||||
<!-- <footer class="modal-card-foot">-->
|
||||
<!-- <button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>-->
|
||||
<!-- </footer>-->
|
||||
<!-- </div>-->
|
||||
<!-- </b-modal>-->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { Address, IAddress } from '@/types/address.model';
|
||||
import { ADDRESS } from '@/graphql/address';
|
||||
import { ADDRESS, REVERSE_GEOCODE } from '@/graphql/address';
|
||||
import { Modal } from 'buefy/dist/components/dialog';
|
||||
import { LatLng } from 'leaflet';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
'map-leaflet': () => import(/* webpackChunkName: "map" */ '@/components/Map.vue'),
|
||||
Modal,
|
||||
},
|
||||
})
|
||||
export default class AddressAutoComplete extends Vue {
|
||||
|
||||
@Prop({ required: false, default: () => [] }) initialData!: IAddress[];
|
||||
@Prop({ required: false }) value!: IAddress;
|
||||
@Prop({ required: true }) value!: IAddress;
|
||||
|
||||
data: IAddress[] = this.initialData;
|
||||
selected: IAddress|null = new Address();
|
||||
data: IAddress[] = [];
|
||||
selected!: IAddress;
|
||||
isFetching: boolean = false;
|
||||
queryText: string = this.value && this.value.description || '';
|
||||
queryText: string = this.value && (new Address(this.value)).fullName || '';
|
||||
addressModalActive: boolean = false;
|
||||
private gettingLocation: boolean = false;
|
||||
private location!: Position;
|
||||
private gettingLocationError: any;
|
||||
private mapDefaultZoom: number = 15;
|
||||
|
||||
@Watch('value')
|
||||
updateEditing() {
|
||||
this.selected = this.value;
|
||||
const address = new Address(this.selected);
|
||||
this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
|
||||
}
|
||||
|
||||
async getAsyncData(query) {
|
||||
if (query.length < 5) {
|
||||
if (!query.length) {
|
||||
this.data = [];
|
||||
this.selected = new Address();
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.length < 3) {
|
||||
this.data = [];
|
||||
return;
|
||||
}
|
||||
this.isFetching = true;
|
||||
const result = await this.$apollo.query({
|
||||
query: ADDRESS,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables: { query },
|
||||
fetchPolicy: 'network-only',
|
||||
variables: {
|
||||
query,
|
||||
locale: this.$i18n.locale,
|
||||
},
|
||||
});
|
||||
|
||||
this.data = result.data.searchAddress as IAddress[];
|
||||
this.data = result.data.searchAddress.map(address => new Address(address));
|
||||
this.isFetching = false;
|
||||
}
|
||||
|
||||
// Watch deep because of subproperties
|
||||
@Watch('selected', { deep: true })
|
||||
updateSelected() {
|
||||
updateSelected(option) {
|
||||
if (option == null) return;
|
||||
this.selected = option;
|
||||
console.log('update selected', this.selected);
|
||||
this.$emit('input', this.selected);
|
||||
}
|
||||
|
||||
resetPopup() {
|
||||
this.selected = new Address();
|
||||
}
|
||||
|
||||
openNewAddressModal() {
|
||||
this.resetPopup();
|
||||
this.addressModalActive = true;
|
||||
}
|
||||
|
||||
async reverseGeoCode(e: LatLng, zoom: Number) {
|
||||
// If the position has been updated through autocomplete selection, no need to geocode it !
|
||||
if (this.checkCurrentPosition(e)) return;
|
||||
const result = await this.$apollo.query({
|
||||
query: REVERSE_GEOCODE,
|
||||
variables: {
|
||||
latitude: e.lat,
|
||||
longitude: e.lng,
|
||||
zoom,
|
||||
locale: this.$i18n.locale,
|
||||
},
|
||||
});
|
||||
|
||||
this.data = result.data.reverseGeocode.map(address => new Address(address));
|
||||
const defaultAddress = new Address(this.data[0]);
|
||||
this.selected = defaultAddress;
|
||||
this.$emit('input', this.selected);
|
||||
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
|
||||
}
|
||||
|
||||
checkCurrentPosition(e: LatLng) {
|
||||
if (!this.selected || !this.selected.geom) return false;
|
||||
const lat = parseFloat(this.selected.geom.split(';')[1]);
|
||||
const lon = parseFloat(this.selected.geom.split(';')[0]);
|
||||
|
||||
return e.lat === lat && e.lng === lon;
|
||||
}
|
||||
|
||||
async locateMe(): Promise<void> {
|
||||
|
||||
this.gettingLocation = true;
|
||||
try {
|
||||
this.gettingLocation = false;
|
||||
this.location = await this.getLocation();
|
||||
this.mapDefaultZoom = 12;
|
||||
this.reverseGeoCode(new LatLng(this.location.coords.latitude, this.location.coords.longitude), 12);
|
||||
} catch (e) {
|
||||
this.gettingLocation = false;
|
||||
this.gettingLocationError = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async getLocation(): Promise<Position> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!('geolocation' in navigator)) {
|
||||
reject(new Error('Geolocation is not available.'));
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(pos => {
|
||||
resolve(pos);
|
||||
}, err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.autocomplete .dropdown-item.is-disabled .is-enabled {
|
||||
opacity: 1 !important;
|
||||
cursor: auto;
|
||||
.autocomplete {
|
||||
.dropdown-menu {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.dropdown-item.is-disabled {
|
||||
opacity: 1 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.read-only {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,40 +5,54 @@
|
||||
:style="`height: ${mergedOptions.height}; width: ${mergedOptions.width}`"
|
||||
class="leaflet-map"
|
||||
:center="[lat, lon]"
|
||||
@click="clickMap"
|
||||
@update:zoom="updateZoom"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
|
||||
attribution="© OpenStreetMap contributors"
|
||||
url="https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
|
||||
:attribution="$t('© The OpenStreetMap Contributors')"
|
||||
>
|
||||
|
||||
</l-tile-layer>
|
||||
<l-marker :lat-lng="[lat, lon]" >
|
||||
<l-popup v-if="popup">{{ popup }}</l-popup>
|
||||
<v-locatecontrol :options="{icon: 'mdi mdi-map-marker'}"/>
|
||||
<l-marker :lat-lng="[lat, lon]" @add="openPopup" @update:latLng="updateDraggableMarkerPosition" :draggable="!readOnly">
|
||||
<l-popup v-if="popupMultiLine">
|
||||
<span v-for="line in popupMultiLine" :key="line">{{ line }}<br /></span>
|
||||
</l-popup>
|
||||
</l-marker>
|
||||
</l-map>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Icon } from 'leaflet';
|
||||
import { Icon, LatLng, LeafletMouseEvent } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { LMap, LTileLayer, LMarker, LPopup } from 'vue2-leaflet';
|
||||
import { LMap, LTileLayer, LMarker, LPopup, LIcon } from 'vue2-leaflet';
|
||||
import Vue2LeafletLocateControl from '@/components/Map/Vue2LeafletLocateControl.vue';
|
||||
|
||||
@Component({
|
||||
components: { LTileLayer, LMap, LMarker, LPopup },
|
||||
components: { LTileLayer, LMap, LMarker, LPopup, LIcon, 'v-locatecontrol': Vue2LeafletLocateControl },
|
||||
})
|
||||
export default class Map extends Vue {
|
||||
@Prop({ type: Boolean, required: false, default: true }) readOnly!: boolean;
|
||||
@Prop({ type: String, required: true }) coords!: string;
|
||||
@Prop({ type: String, required: false }) popup!: string;
|
||||
@Prop({ type: Object, required: false }) marker!: { text: String|String[], icon: String };
|
||||
@Prop({ type: Object, required: false }) options!: object;
|
||||
@Prop({ type: Function, required: false, default: () => {} }) updateDraggableMarkerCallback!: Function;
|
||||
|
||||
defaultOptions: object = {
|
||||
defaultOptions: {
|
||||
zoom: Number;
|
||||
height: String;
|
||||
width: String;
|
||||
} = {
|
||||
zoom: 15,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
zoom = this.defaultOptions.zoom;
|
||||
|
||||
mounted() {
|
||||
// this part resolve an issue where the markers would not appear
|
||||
// @ts-ignore
|
||||
@@ -51,12 +65,38 @@ export default class Map extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
openPopup(event) {
|
||||
this.$nextTick(() => {
|
||||
event.target.openPopup();
|
||||
});
|
||||
}
|
||||
|
||||
get mergedOptions(): object {
|
||||
return { ...this.defaultOptions, ...this.options };
|
||||
}
|
||||
|
||||
get lat() { return this.$props.coords.split(';')[1]; }
|
||||
get lon() { return this.$props.coords.split(';')[0]; }
|
||||
|
||||
get popupMultiLine() {
|
||||
if (Array.isArray(this.marker.text)) {
|
||||
return this.marker.text;
|
||||
}
|
||||
return [this.marker.text];
|
||||
}
|
||||
|
||||
clickMap(event: LeafletMouseEvent) {
|
||||
this.updateDraggableMarkerPosition(event.latlng);
|
||||
}
|
||||
|
||||
updateDraggableMarkerPosition(e: LatLng) {
|
||||
console.log('updateDraggableMarkerPosition', e);
|
||||
this.updateDraggableMarkerCallback(e, this.zoom);
|
||||
}
|
||||
|
||||
updateZoom(zoom: Number) {
|
||||
this.zoom = zoom;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
47
js/src/components/Map/Vue2LeafletLocateControl.vue
Normal file
47
js/src/components/Map/Vue2LeafletLocateControl.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div style="display: none;">
|
||||
<slot v-if="ready"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Fork of https://github.com/domoritz/leaflet-locatecontrol to try to trigger location manually (not done ATM)
|
||||
*/
|
||||
|
||||
import L, { DomEvent } from 'leaflet';
|
||||
import { findRealParent, propsBinder } from 'vue2-leaflet';
|
||||
import 'leaflet.locatecontrol';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component({
|
||||
beforeDestroy() {
|
||||
// @ts-ignore
|
||||
this.parentContainer.removeLayer(this);
|
||||
},
|
||||
})
|
||||
export default class Vue2LeafletLocateControl extends Vue {
|
||||
@Prop({ type: Object, default: () => { return {}; } }) options;
|
||||
@Prop({ type: Boolean, default: true }) visible = true;
|
||||
ready: boolean = false;
|
||||
mapObject!: any;
|
||||
parentContainer: any;
|
||||
|
||||
mounted() {
|
||||
this.mapObject = L.control.locate(this.options);
|
||||
DomEvent.on(this.mapObject, this.$listeners as any);
|
||||
propsBinder(this, this.mapObject, this.$props);
|
||||
this.ready = true;
|
||||
this.parentContainer = findRealParent(this.$parent);
|
||||
this.mapObject.addTo(this.parentContainer.mapObject, !this.visible);
|
||||
}
|
||||
|
||||
public locate() {
|
||||
this.mapObject.start();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "~leaflet.locatecontrol/dist/L.Control.Locate.css";
|
||||
</style>
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -5,7 +5,13 @@ query {
|
||||
config {
|
||||
name,
|
||||
description,
|
||||
registrationsOpen
|
||||
registrationsOpen,
|
||||
countryCode,
|
||||
location {
|
||||
latitude,
|
||||
longitude,
|
||||
accuracyRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -24,7 +24,9 @@ const physicalAddressQuery = `
|
||||
region,
|
||||
country,
|
||||
geom,
|
||||
id
|
||||
type,
|
||||
id,
|
||||
originId
|
||||
`;
|
||||
|
||||
const tagsQuery = `
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 <b>trapped inside the tech giants’ platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves up</b> inside MeetUp?": "De l’anniversaire entre ami·e·s à une marche pour le climat, aujourd’hui, les bonnes raisons de se rassembler sont <b>captées par les géants du web</b>. Comment s’organiser, comment cliquer sur « je participe » sans <b>livrer des données intimes</b> à Facebook ou<b> s’enfermer</b> 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 <b>their own spaces</b> to publish events in order to better emancipate themselves from tech giants.": "Mobilizon est un logiciel libre qui permettra à des communautés de <b>créer leurs propres espaces</b> 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"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,10 @@ export interface IConfig {
|
||||
description: string;
|
||||
|
||||
registrationsOpen: boolean;
|
||||
countryCode: string;
|
||||
location: {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
accuracyRadius: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
22
js/src/utils/.editorconfig
Normal file
22
js/src/utils/.editorconfig
Normal file
@@ -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
|
||||
61
js/src/utils/poiIcons.ts
Normal file
61
js/src/utils/poiIcons.ts
Normal file
@@ -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',
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import {ParticipantRole} from "@/types/event.model";
|
||||
<template>
|
||||
<div class="container">
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
@@ -15,7 +14,7 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
<div class="title-and-informations">
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
<span>
|
||||
<router-link v-if="actorIsOrganizer" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}">
|
||||
<router-link v-if="actorIsOrganizer && event.draft === false" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}">
|
||||
<small v-if="event.participantStats.going > 0 && !actorIsParticipant">
|
||||
{{ $tc('One person is going', event.participantStats.going, {approved: event.participantStats.going}) }}
|
||||
</small>
|
||||
@@ -111,23 +110,27 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
</p>
|
||||
</div>
|
||||
<div class="address-wrapper">
|
||||
<b-icon icon="map" />
|
||||
<span v-if="!event.physicalAddress">{{ $t('No address defined') }}</span>
|
||||
<div class="address" v-if="event.physicalAddress">
|
||||
<address>
|
||||
<span class="addressDescription" :title="event.physicalAddress.description">{{ event.physicalAddress.description }}</span>
|
||||
<span>{{ event.physicalAddress.street }}</span>
|
||||
<span>{{ event.physicalAddress.postalCode }} {{ event.physicalAddress.locality }}</span>
|
||||
</address>
|
||||
<span class="map-show-button" @click="showMap = !showMap" v-if="event.physicalAddress && event.physicalAddress.geom">
|
||||
<span v-if="!physicalAddress">
|
||||
<b-icon icon="map" />
|
||||
{{ $t('No address defined') }}
|
||||
</span>
|
||||
<div class="address" v-if="physicalAddress">
|
||||
<span>
|
||||
<b-icon :icon="physicalAddress.poiInfos.poiIcon.icon" />
|
||||
<address>
|
||||
<span class="addressDescription" :title="physicalAddress.poiInfos.name">{{ physicalAddress.poiInfos.name }}</span>
|
||||
<span>{{ physicalAddress.poiInfos.alternativeName }}</span>
|
||||
</address>
|
||||
</span>
|
||||
<span class="map-show-button" @click="showMap = !showMap" v-if="physicalAddress && physicalAddress.geom">
|
||||
{{ $t('Show map') }}
|
||||
</span>
|
||||
</div>
|
||||
<b-modal v-if="event.physicalAddress && event.physicalAddress.geom" :active.sync="showMap" scroll="keep">
|
||||
<b-modal v-if="physicalAddress && physicalAddress.geom" :active.sync="showMap" scroll="keep">
|
||||
<div class="map">
|
||||
<map-leaflet
|
||||
:coords="event.physicalAddress.geom"
|
||||
:popup="event.physicalAddress.description"
|
||||
:coords="physicalAddress.geom"
|
||||
:marker="{ text: physicalAddress.fullName, icon: physicalAddress.poiInfos.poiIcon.icon }"
|
||||
/>
|
||||
</div>
|
||||
</b-modal>
|
||||
@@ -254,7 +257,7 @@ import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import ParticipationButton from '@/components/Event/ParticipationButton.vue';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { RouteName } from '@/router';
|
||||
import HTML = Mocha.reporters.HTML;
|
||||
import { Address } from '@/types/address.model';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -596,11 +599,13 @@ export default class Event extends EventMixin {
|
||||
}
|
||||
|
||||
get eventCapacityOK(): boolean {
|
||||
if (this.event.draft) return true;
|
||||
if (!this.event.options.maximumAttendeeCapacity) return true;
|
||||
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participant;
|
||||
}
|
||||
|
||||
get numberOfPlacesStillAvailable(): number {
|
||||
if (this.event.draft) return this.event.options.maximumAttendeeCapacity;
|
||||
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participant;
|
||||
}
|
||||
|
||||
@@ -611,6 +616,11 @@ export default class Event extends EventMixin {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
get physicalAddress(): Address|null {
|
||||
if (!this.event.physicalAddress) return null;
|
||||
return new Address(this.event.physicalAddress);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -664,25 +674,33 @@ export default class Event extends EventMixin {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
address {
|
||||
font-style: normal;
|
||||
flex-wrap: wrap;
|
||||
span:first-child {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
span.addressDescription {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 0 auto;
|
||||
min-width: 100%;
|
||||
max-width: 4rem;
|
||||
overflow: hidden;
|
||||
span.icon {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
:not(.addressDescription) {
|
||||
color: rgba(46, 62, 72, .6);
|
||||
flex: 1;
|
||||
min-width: 100%;
|
||||
address {
|
||||
font-style: normal;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
span.addressDescription {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1 0 auto;
|
||||
min-width: 100%;
|
||||
max-width: 4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:not(.addressDescription) {
|
||||
color: rgba(46, 62, 72, .6);
|
||||
flex: 1;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { ApolloLink, Observable } from 'apollo-link';
|
||||
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import { defaultDataIdFromObject, InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import { onError } from 'apollo-link-error';
|
||||
import { createLink } from 'apollo-absinthe-upload-link';
|
||||
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from './api/_entrypoint';
|
||||
@@ -132,6 +132,13 @@ const link = authMiddleware
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
fragmentMatcher,
|
||||
dataIdFromObject: object => {
|
||||
if (object.__typename === 'Address') {
|
||||
// @ts-ignore
|
||||
return object.origin_id;
|
||||
}
|
||||
return defaultDataIdFromObject(object);
|
||||
},
|
||||
});
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
|
||||
Reference in New Issue
Block a user