2019-07-30 10:35:29 +02:00
|
|
|
|
<template>
|
2019-12-20 13:04:34 +01:00
|
|
|
|
<div class="address-autocomplete">
|
2019-11-08 19:37:14 +01:00
|
|
|
|
<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>
|
2019-08-22 15:57:44 +02:00
|
|
|
|
<b-autocomplete
|
2019-11-20 13:49:57 +01:00
|
|
|
|
:data="addressData"
|
2019-08-22 15:57:44 +02:00
|
|
|
|
v-model="queryText"
|
2019-09-12 11:34:01 +02:00
|
|
|
|
:placeholder="$t('e.g. 10 Rue Jangot')"
|
2019-11-08 19:37:14 +01:00
|
|
|
|
field="fullName"
|
2019-08-22 15:57:44 +02:00
|
|
|
|
:loading="isFetching"
|
2019-11-20 13:49:57 +01:00
|
|
|
|
@typing="fetchAsyncData"
|
2019-10-14 13:07:50 +02:00
|
|
|
|
icon="map-marker"
|
2019-11-08 19:37:14 +01:00
|
|
|
|
expanded
|
|
|
|
|
@select="updateSelected">
|
2019-08-22 15:57:44 +02:00
|
|
|
|
|
|
|
|
|
<template slot-scope="{option}">
|
2019-11-08 19:37:14 +01:00
|
|
|
|
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
|
|
|
|
<b>{{ option.poiInfos.name }}</b><br />
|
|
|
|
|
<small>{{ option.poiInfos.alternativeName }}</small>
|
2019-08-22 15:57:44 +02:00
|
|
|
|
</template>
|
|
|
|
|
<template slot="empty">
|
2019-11-08 19:37:14 +01:00
|
|
|
|
<span v-if="isFetching">{{ $t('Searching…') }}</span>
|
2019-11-20 13:49:57 +01:00
|
|
|
|
<div v-else-if="queryText.length >= 3" class="is-enabled">
|
2019-11-19 17:59:04 +01:00
|
|
|
|
<span>{{ $t('No results for "{queryText}"') }}</span>
|
|
|
|
|
<span>{{ $t('You can try another search term or drag and drop the marker on the map', { queryText }) }}</span>
|
2019-11-08 19:37:14 +01:00
|
|
|
|
<!-- <p class="control" @click="openNewAddressModal">-->
|
|
|
|
|
<!-- <button type="button" class="button is-primary">{{ $t('Add') }}</button>-->
|
|
|
|
|
<!-- </p>-->
|
2019-08-22 15:57:44 +02:00
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</b-autocomplete>
|
|
|
|
|
</b-field>
|
2019-11-08 19:37:14 +01:00
|
|
|
|
<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 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>-->
|
|
|
|
|
|
|
|
|
|
<!-- <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>-->
|
|
|
|
|
<!-- </b-field>-->
|
|
|
|
|
<!-- </form>-->
|
|
|
|
|
<!-- </section>-->
|
|
|
|
|
<!-- <footer class="modal-card-foot">-->
|
|
|
|
|
<!-- <button class="button" type="button" @click="resetPopup()">{{ $t('Clear') }}</button>-->
|
|
|
|
|
<!-- </footer>-->
|
|
|
|
|
<!-- </div>-->
|
|
|
|
|
<!-- </b-modal>-->
|
2019-08-22 15:57:44 +02:00
|
|
|
|
</div>
|
2019-07-30 10:35:29 +02:00
|
|
|
|
</template>
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
2019-08-22 15:57:44 +02:00
|
|
|
|
import { Address, IAddress } from '@/types/address.model';
|
2019-11-08 19:37:14 +01:00
|
|
|
|
import { ADDRESS, REVERSE_GEOCODE } from '@/graphql/address';
|
2019-08-22 15:57:44 +02:00
|
|
|
|
import { Modal } from 'buefy/dist/components/dialog';
|
2019-11-08 19:37:14 +01:00
|
|
|
|
import { LatLng } from 'leaflet';
|
2019-11-20 13:49:57 +01:00
|
|
|
|
import { debounce } from 'lodash';
|
|
|
|
|
import { CONFIG } from '@/graphql/config';
|
|
|
|
|
import { IConfig } from '@/types/config.model';
|
2019-11-08 19:37:14 +01:00
|
|
|
|
|
2019-08-22 15:57:44 +02:00
|
|
|
|
@Component({
|
|
|
|
|
components: {
|
2019-11-08 19:37:14 +01:00
|
|
|
|
'map-leaflet': () => import(/* webpackChunkName: "map" */ '@/components/Map.vue'),
|
2019-08-22 15:57:44 +02:00
|
|
|
|
Modal,
|
|
|
|
|
},
|
2019-11-20 13:49:57 +01:00
|
|
|
|
apollo: {
|
|
|
|
|
config: CONFIG,
|
|
|
|
|
},
|
2019-08-22 15:57:44 +02:00
|
|
|
|
})
|
2019-07-30 10:35:29 +02:00
|
|
|
|
export default class AddressAutoComplete extends Vue {
|
|
|
|
|
|
2019-11-08 19:37:14 +01:00
|
|
|
|
@Prop({ required: true }) value!: IAddress;
|
2019-07-30 10:35:29 +02:00
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
addressData: IAddress[] = [];
|
|
|
|
|
selected: IAddress = new Address();
|
2019-07-30 10:35:29 +02:00
|
|
|
|
isFetching: boolean = false;
|
2019-12-03 11:29:51 +01:00
|
|
|
|
queryText: string = (this.value && (new Address(this.value)).fullName) || '';
|
2019-08-22 15:57:44 +02:00
|
|
|
|
addressModalActive: boolean = false;
|
2019-11-08 19:37:14 +01:00
|
|
|
|
private gettingLocation: boolean = false;
|
|
|
|
|
private location!: Position;
|
|
|
|
|
private gettingLocationError: any;
|
|
|
|
|
private mapDefaultZoom: number = 15;
|
2019-11-20 13:49:57 +01:00
|
|
|
|
config!: IConfig;
|
2019-11-08 19:37:14 +01:00
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
// We put this in data because of issues like https://github.com/vuejs/vue-class-component/issues/263
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
2019-12-20 11:39:30 +01:00
|
|
|
|
fetchAsyncData: debounce(this.asyncData, 200),
|
2019-11-20 13:49:57 +01:00
|
|
|
|
};
|
2019-11-08 19:37:14 +01:00
|
|
|
|
}
|
2019-07-30 10:35:29 +02:00
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
async asyncData(query: String) {
|
2019-11-08 19:37:14 +01:00
|
|
|
|
if (!query.length) {
|
2019-11-20 13:49:57 +01:00
|
|
|
|
this.addressData = [];
|
2019-11-08 19:37:14 +01:00
|
|
|
|
this.selected = new Address();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (query.length < 3) {
|
2019-11-20 13:49:57 +01:00
|
|
|
|
this.addressData = [];
|
2019-07-30 10:35:29 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-11-20 13:49:57 +01:00
|
|
|
|
|
2019-07-30 10:35:29 +02:00
|
|
|
|
this.isFetching = true;
|
|
|
|
|
const result = await this.$apollo.query({
|
|
|
|
|
query: ADDRESS,
|
2019-11-08 19:37:14 +01:00
|
|
|
|
fetchPolicy: 'network-only',
|
|
|
|
|
variables: {
|
|
|
|
|
query,
|
|
|
|
|
locale: this.$i18n.locale,
|
|
|
|
|
},
|
2019-07-30 10:35:29 +02:00
|
|
|
|
});
|
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
this.addressData = result.data.searchAddress.map(address => new Address(address));
|
2019-08-22 15:57:44 +02:00
|
|
|
|
this.isFetching = false;
|
2019-07-30 10:35:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
@Watch('config')
|
|
|
|
|
watchConfig(config: IConfig) {
|
|
|
|
|
if (!config.geocoding.autocomplete) {
|
|
|
|
|
// If autocomplete is disabled, we put a larger debounce value so that we don't request with incomplete address
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
this.fetchAsyncData = debounce(this.asyncData, 2000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Watch('value')
|
|
|
|
|
updateEditing() {
|
2019-12-03 11:29:51 +01:00
|
|
|
|
if (!(this.value && this.value.id)) return;
|
2019-11-20 13:49:57 +01:00
|
|
|
|
this.selected = this.value;
|
|
|
|
|
const address = new Address(this.selected);
|
|
|
|
|
this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 19:37:14 +01:00
|
|
|
|
updateSelected(option) {
|
|
|
|
|
if (option == null) return;
|
|
|
|
|
this.selected = option;
|
2019-07-30 10:35:29 +02:00
|
|
|
|
this.$emit('input', this.selected);
|
|
|
|
|
}
|
2019-08-22 15:57:44 +02:00
|
|
|
|
|
|
|
|
|
resetPopup() {
|
|
|
|
|
this.selected = new Address();
|
|
|
|
|
}
|
2019-11-08 19:37:14 +01:00
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-20 13:49:57 +01:00
|
|
|
|
this.addressData = result.data.reverseGeocode.map(address => new Address(address));
|
|
|
|
|
const defaultAddress = new Address(this.addressData[0]);
|
2019-11-08 19:37:14 +01:00
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-07-30 10:35:29 +02:00
|
|
|
|
}
|
|
|
|
|
</script>
|
2019-08-22 15:57:44 +02:00
|
|
|
|
<style lang="scss">
|
2019-12-20 13:04:34 +01:00
|
|
|
|
.address-autocomplete {
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-08 19:37:14 +01:00
|
|
|
|
.autocomplete {
|
|
|
|
|
.dropdown-menu {
|
|
|
|
|
z-index: 2000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dropdown-item.is-disabled {
|
|
|
|
|
opacity: 1 !important;
|
|
|
|
|
cursor: auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.read-only {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.map {
|
|
|
|
|
height: 400px;
|
|
|
|
|
width: 100%;
|
2019-08-22 15:57:44 +02:00
|
|
|
|
}
|
|
|
|
|
</style>
|