Refactor addressautocomplete components into a mixin
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
8a58f5ba7c
commit
bc1f71e742
|
@ -11,6 +11,7 @@
|
||||||
icon="map-marker"
|
icon="map-marker"
|
||||||
expanded
|
expanded
|
||||||
@select="updateSelected"
|
@select="updateSelected"
|
||||||
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<template #default="{ option }">
|
<template #default="{ option }">
|
||||||
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
||||||
|
@ -20,7 +21,11 @@
|
||||||
</template>
|
</template>
|
||||||
</b-autocomplete>
|
</b-autocomplete>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field v-if="canDoGeoLocation">
|
<b-field
|
||||||
|
v-if="canDoGeoLocation"
|
||||||
|
:message="fieldErrors"
|
||||||
|
:type="{ 'is-danger': fieldErrors.length }"
|
||||||
|
>
|
||||||
<b-button
|
<b-button
|
||||||
type="is-text"
|
type="is-text"
|
||||||
v-if="!gettingLocation"
|
v-if="!gettingLocation"
|
||||||
|
@ -52,26 +57,16 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
|
||||||
import { LatLng } from "leaflet";
|
|
||||||
import debounce from "lodash/debounce";
|
|
||||||
import { DebouncedFunc } from "lodash";
|
|
||||||
import { Address, IAddress } from "../../types/address.model";
|
import { Address, IAddress } from "../../types/address.model";
|
||||||
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
|
import AddressAutoCompleteMixin from "@/mixins/AddressAutoCompleteMixin";
|
||||||
import { CONFIG } from "../../graphql/config";
|
|
||||||
import { IConfig } from "../../types/config.model";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
inheritAttrs: false,
|
||||||
"map-leaflet": () =>
|
|
||||||
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
|
||||||
},
|
|
||||||
apollo: {
|
|
||||||
config: CONFIG,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export default class AddressAutoComplete extends Vue {
|
export default class AddressAutoComplete extends Mixins(
|
||||||
@Prop({ required: true }) value!: IAddress;
|
AddressAutoCompleteMixin
|
||||||
|
) {
|
||||||
@Prop({ required: false, default: false }) type!: string | false;
|
@Prop({ required: false, default: false }) type!: string | false;
|
||||||
@Prop({ required: false, default: true, type: Boolean })
|
@Prop({ required: false, default: true, type: Boolean })
|
||||||
doGeoLocation!: boolean;
|
doGeoLocation!: boolean;
|
||||||
|
@ -80,84 +75,20 @@ export default class AddressAutoComplete extends Vue {
|
||||||
|
|
||||||
selected: IAddress = new Address();
|
selected: IAddress = new Address();
|
||||||
|
|
||||||
isFetching = false;
|
|
||||||
|
|
||||||
initialQueryText = "";
|
initialQueryText = "";
|
||||||
|
|
||||||
addressModalActive = false;
|
addressModalActive = false;
|
||||||
|
|
||||||
showmap = false;
|
showmap = false;
|
||||||
|
|
||||||
private gettingLocation = false;
|
get queryText2(): string {
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
private location!: GeolocationPosition;
|
|
||||||
|
|
||||||
private gettingLocationError: any;
|
|
||||||
|
|
||||||
private mapDefaultZoom = 15;
|
|
||||||
|
|
||||||
config!: IConfig;
|
|
||||||
|
|
||||||
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
|
|
||||||
|
|
||||||
// We put this in data because of issues like
|
|
||||||
// https://github.com/vuejs/vue-class-component/issues/263
|
|
||||||
data(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
fetchAsyncData: debounce(this.asyncData, 200),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async asyncData(query: string): Promise<void> {
|
|
||||||
if (!query.length) {
|
|
||||||
this.addressData = [];
|
|
||||||
this.selected = new Address();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.length < 3) {
|
|
||||||
this.addressData = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFetching = true;
|
|
||||||
const variables: { query: string; locale: string; type?: string } = {
|
|
||||||
query,
|
|
||||||
locale: this.$i18n.locale,
|
|
||||||
};
|
|
||||||
if (this.type) {
|
|
||||||
variables.type = this.type;
|
|
||||||
}
|
|
||||||
const result = await this.$apollo.query({
|
|
||||||
query: ADDRESS,
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
variables,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addressData = result.data.searchAddress.map(
|
|
||||||
(address: IAddress) => new Address(address)
|
|
||||||
);
|
|
||||||
this.isFetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("config")
|
|
||||||
watchConfig(config: IConfig): void {
|
|
||||||
if (!config.geocoding.autocomplete) {
|
|
||||||
// If autocomplete is disabled, we put a larger debounce value
|
|
||||||
// so that we don't request with incomplete address
|
|
||||||
this.fetchAsyncData = debounce(this.asyncData, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get queryText(): string {
|
|
||||||
if (this.value !== undefined) {
|
if (this.value !== undefined) {
|
||||||
return new Address(this.value).fullName;
|
return new Address(this.value).fullName;
|
||||||
}
|
}
|
||||||
return this.initialQueryText;
|
return this.initialQueryText;
|
||||||
}
|
}
|
||||||
|
|
||||||
set queryText(queryText: string) {
|
set queryText2(queryText: string) {
|
||||||
this.initialQueryText = queryText;
|
this.initialQueryText = queryText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,80 +117,6 @@ export default class AddressAutoComplete extends Vue {
|
||||||
this.showmap = !this.showmap;
|
this.showmap = !this.showmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reverseGeoCode(e: LatLng, zoom: number): Promise<void> {
|
|
||||||
// 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.addressData = result.data.reverseGeocode.map(
|
|
||||||
(address: IAddress) => new Address(address)
|
|
||||||
);
|
|
||||||
if (this.addressData.length > 0) {
|
|
||||||
const defaultAddress = new Address(this.addressData[0]);
|
|
||||||
this.selected = defaultAddress;
|
|
||||||
this.$emit("input", this.selected);
|
|
||||||
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCurrentPosition(e: LatLng): boolean {
|
|
||||||
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.location = await AddressAutoComplete.getLocation();
|
|
||||||
this.mapDefaultZoom = 12;
|
|
||||||
this.reverseGeoCode(
|
|
||||||
new LatLng(
|
|
||||||
this.location.coords.latitude,
|
|
||||||
this.location.coords.longitude
|
|
||||||
),
|
|
||||||
12
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
this.gettingLocationError = e.message;
|
|
||||||
}
|
|
||||||
this.gettingLocation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
static async getLocation(): Promise<GeolocationPosition> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
get isSecureContext(): boolean {
|
|
||||||
return window.isSecureContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
get canDoGeoLocation(): boolean {
|
get canDoGeoLocation(): boolean {
|
||||||
return this.isSecureContext && this.doGeoLocation;
|
return this.isSecureContext && this.doGeoLocation;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="address-autocomplete">
|
<div class="address-autocomplete">
|
||||||
<b-field expanded>
|
<b-field
|
||||||
|
:label-for="id"
|
||||||
|
expanded
|
||||||
|
:message="fieldErrors"
|
||||||
|
:type="{ 'is-danger': fieldErrors.length }"
|
||||||
|
>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
{{ actualLabel }}
|
{{ actualLabel }}
|
||||||
<b-button
|
<b-button
|
||||||
|
@ -8,8 +13,13 @@
|
||||||
size="is-small"
|
size="is-small"
|
||||||
icon-right="map-marker"
|
icon-right="map-marker"
|
||||||
@click="locateMe"
|
@click="locateMe"
|
||||||
|
:title="$t('Use my location')"
|
||||||
/>
|
/>
|
||||||
<span v-else-if="gettingLocation">{{ $t("Getting location") }}</span>
|
<span
|
||||||
|
class="is-size-6 has-text-weight-normal"
|
||||||
|
v-else-if="gettingLocation"
|
||||||
|
>{{ $t("Getting location") }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<b-autocomplete
|
<b-autocomplete
|
||||||
:data="addressData"
|
:data="addressData"
|
||||||
|
@ -21,6 +31,8 @@
|
||||||
icon="map-marker"
|
icon="map-marker"
|
||||||
expanded
|
expanded
|
||||||
@select="updateSelected"
|
@select="updateSelected"
|
||||||
|
v-bind="$attrs"
|
||||||
|
:id="id"
|
||||||
>
|
>
|
||||||
<template #default="{ option }">
|
<template #default="{ option }">
|
||||||
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
<b-icon :icon="option.poiInfos.poiIcon.icon" />
|
||||||
|
@ -51,6 +63,7 @@
|
||||||
@click="resetAddress"
|
@click="resetAddress"
|
||||||
class="reset-area"
|
class="reset-area"
|
||||||
icon-left="close"
|
icon-left="close"
|
||||||
|
:title="$t('Clear address field')"
|
||||||
/>
|
/>
|
||||||
</b-field>
|
</b-field>
|
||||||
<div class="map" v-if="selected && selected.geom && selected.poiInfos">
|
<div class="map" v-if="selected && selected.geom && selected.poiInfos">
|
||||||
|
@ -109,95 +122,29 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Prop, Watch, Mixins } from "vue-property-decorator";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
import debounce from "lodash/debounce";
|
|
||||||
import { DebouncedFunc } from "lodash";
|
|
||||||
import { Address, IAddress } from "../../types/address.model";
|
import { Address, IAddress } from "../../types/address.model";
|
||||||
import { ADDRESS, REVERSE_GEOCODE } from "../../graphql/address";
|
import AddressAutoCompleteMixin from "../../mixins/AddressAutoCompleteMixin";
|
||||||
import { CONFIG } from "../../graphql/config";
|
|
||||||
import { IConfig } from "../../types/config.model";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
inheritAttrs: false,
|
||||||
"map-leaflet": () =>
|
|
||||||
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
|
||||||
},
|
|
||||||
apollo: {
|
|
||||||
config: CONFIG,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
export default class FullAddressAutoComplete extends Vue {
|
export default class FullAddressAutoComplete extends Mixins(
|
||||||
@Prop({ required: true }) value!: IAddress;
|
AddressAutoCompleteMixin
|
||||||
|
) {
|
||||||
@Prop({ required: false, default: "" }) label!: string;
|
@Prop({ required: false, default: "" }) label!: string;
|
||||||
|
|
||||||
addressData: IAddress[] = [];
|
|
||||||
|
|
||||||
selected: IAddress = new Address();
|
|
||||||
|
|
||||||
isFetching = false;
|
|
||||||
|
|
||||||
queryText: string = (this.value && new Address(this.value).fullName) || "";
|
|
||||||
|
|
||||||
addressModalActive = false;
|
addressModalActive = false;
|
||||||
|
|
||||||
private gettingLocation = false;
|
private static componentId = 0;
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
created(): void {
|
||||||
private location!: GeolocationPosition;
|
FullAddressAutoComplete.componentId += 1;
|
||||||
|
|
||||||
private gettingLocationError: any;
|
|
||||||
|
|
||||||
private mapDefaultZoom = 15;
|
|
||||||
|
|
||||||
config!: IConfig;
|
|
||||||
|
|
||||||
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
|
|
||||||
|
|
||||||
// We put this in data because of issues like
|
|
||||||
// https://github.com/vuejs/vue-class-component/issues/263
|
|
||||||
data(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
fetchAsyncData: debounce(this.asyncData, 200),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async asyncData(query: string): Promise<void> {
|
get id(): string {
|
||||||
if (!query.length) {
|
return `full-address-autocomplete-${FullAddressAutoComplete.componentId}`;
|
||||||
this.addressData = [];
|
|
||||||
this.selected = new Address();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.length < 3) {
|
|
||||||
this.addressData = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFetching = true;
|
|
||||||
const result = await this.$apollo.query({
|
|
||||||
query: ADDRESS,
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
variables: {
|
|
||||||
query,
|
|
||||||
locale: this.$i18n.locale,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addressData = result.data.searchAddress.map(
|
|
||||||
(address: IAddress) => new Address(address)
|
|
||||||
);
|
|
||||||
this.isFetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("config")
|
|
||||||
watchConfig(config: IConfig): void {
|
|
||||||
if (!config.geocoding.autocomplete) {
|
|
||||||
// If autocomplete is disabled, we put a larger debounce value
|
|
||||||
// so that we don't request with incomplete address
|
|
||||||
this.fetchAsyncData = debounce(this.asyncData, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch("value")
|
@Watch("value")
|
||||||
|
@ -225,30 +172,6 @@ export default class FullAddressAutoComplete extends Vue {
|
||||||
this.addressModalActive = true;
|
this.addressModalActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reverseGeoCode(e: LatLng, zoom: number): Promise<void> {
|
|
||||||
// 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.addressData = result.data.reverseGeocode.map(
|
|
||||||
(address: IAddress) => new Address(address)
|
|
||||||
);
|
|
||||||
if (this.addressData.length > 0) {
|
|
||||||
const defaultAddress = new Address(this.addressData[0]);
|
|
||||||
this.selected = defaultAddress;
|
|
||||||
this.$emit("input", this.selected);
|
|
||||||
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCurrentPosition(e: LatLng): boolean {
|
checkCurrentPosition(e: LatLng): boolean {
|
||||||
if (!this.selected || !this.selected.geom) return false;
|
if (!this.selected || !this.selected.geom) return false;
|
||||||
const lat = parseFloat(this.selected.geom.split(";")[1]);
|
const lat = parseFloat(this.selected.geom.split(";")[1]);
|
||||||
|
@ -257,25 +180,6 @@ export default class FullAddressAutoComplete extends Vue {
|
||||||
return e.lat === lat && e.lng === lon;
|
return e.lat === lat && e.lng === lon;
|
||||||
}
|
}
|
||||||
|
|
||||||
async locateMe(): Promise<void> {
|
|
||||||
this.gettingLocation = true;
|
|
||||||
try {
|
|
||||||
this.gettingLocation = false;
|
|
||||||
this.location = await FullAddressAutoComplete.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get actualLabel(): string {
|
get actualLabel(): string {
|
||||||
return this.label || (this.$t("Find an address") as string);
|
return this.label || (this.$t("Find an address") as string);
|
||||||
}
|
}
|
||||||
|
@ -285,24 +189,6 @@ export default class FullAddressAutoComplete extends Vue {
|
||||||
return window.isSecureContext;
|
return window.isSecureContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
static async getLocation(): Promise<GeolocationPosition> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("queryText")
|
@Watch("queryText")
|
||||||
resetAddressOnEmptyField(queryText: string): void {
|
resetAddressOnEmptyField(queryText: string): void {
|
||||||
if (queryText === "" && this.selected?.id) {
|
if (queryText === "" && this.selected?.id) {
|
||||||
|
|
188
js/src/mixins/AddressAutoCompleteMixin.ts
Normal file
188
js/src/mixins/AddressAutoCompleteMixin.ts
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
import { mixins } from "vue-class-component";
|
||||||
|
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
|
||||||
|
import { LatLng } from "leaflet";
|
||||||
|
import { Address, IAddress } from "../types/address.model";
|
||||||
|
import { ADDRESS, REVERSE_GEOCODE } from "../graphql/address";
|
||||||
|
import { CONFIG } from "../graphql/config";
|
||||||
|
import { IConfig } from "../types/config.model";
|
||||||
|
import debounce from "lodash/debounce";
|
||||||
|
import { DebouncedFunc } from "lodash";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
"map-leaflet": () =>
|
||||||
|
import(/* webpackChunkName: "map" */ "@/components/Map.vue"),
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
config: CONFIG,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class AddressAutoCompleteMixin extends mixins(Vue) {
|
||||||
|
@Prop({ required: true }) value!: IAddress;
|
||||||
|
gettingLocationError: string | null = null;
|
||||||
|
|
||||||
|
gettingLocation = false;
|
||||||
|
|
||||||
|
mapDefaultZoom = 15;
|
||||||
|
|
||||||
|
addressData: IAddress[] = [];
|
||||||
|
|
||||||
|
selected: IAddress = new Address();
|
||||||
|
|
||||||
|
queryText: string = (this.value && new Address(this.value).fullName) || "";
|
||||||
|
|
||||||
|
config!: IConfig;
|
||||||
|
|
||||||
|
isFetching = false;
|
||||||
|
|
||||||
|
fetchAsyncData!: DebouncedFunc<(query: string) => Promise<void>>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
protected location!: GeolocationPosition;
|
||||||
|
|
||||||
|
// We put this in data because of issues like
|
||||||
|
// https://github.com/vuejs/vue-class-component/issues/263
|
||||||
|
data(): Record<string, unknown> {
|
||||||
|
return {
|
||||||
|
fetchAsyncData: debounce(this.asyncData, 200),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("config")
|
||||||
|
watchConfig(config: IConfig): void {
|
||||||
|
if (!config.geocoding.autocomplete) {
|
||||||
|
// If autocomplete is disabled, we put a larger debounce value
|
||||||
|
// so that we don't request with incomplete address
|
||||||
|
this.fetchAsyncData = debounce(this.asyncData, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async asyncData(query: string): Promise<void> {
|
||||||
|
if (!query.length) {
|
||||||
|
this.addressData = [];
|
||||||
|
this.selected = new Address();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 3) {
|
||||||
|
this.addressData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFetching = true;
|
||||||
|
const result = await this.$apollo.query({
|
||||||
|
query: ADDRESS,
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
variables: {
|
||||||
|
query,
|
||||||
|
locale: this.$i18n.locale,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addressData = result.data.searchAddress.map(
|
||||||
|
(address: IAddress) => new Address(address)
|
||||||
|
);
|
||||||
|
this.isFetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async locateMe(): Promise<void> {
|
||||||
|
this.gettingLocation = true;
|
||||||
|
this.gettingLocationError = null;
|
||||||
|
try {
|
||||||
|
this.location = await this.getLocation();
|
||||||
|
this.mapDefaultZoom = 12;
|
||||||
|
this.reverseGeoCode(
|
||||||
|
new LatLng(
|
||||||
|
this.location.coords.latitude,
|
||||||
|
this.location.coords.longitude
|
||||||
|
),
|
||||||
|
12
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
this.gettingLocationError = e.message;
|
||||||
|
}
|
||||||
|
this.gettingLocation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async reverseGeoCode(e: LatLng, zoom: number): Promise<void> {
|
||||||
|
// 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.addressData = result.data.reverseGeocode.map(
|
||||||
|
(address: IAddress) => new Address(address)
|
||||||
|
);
|
||||||
|
if (this.addressData.length > 0) {
|
||||||
|
const defaultAddress = new Address(this.addressData[0]);
|
||||||
|
this.selected = defaultAddress;
|
||||||
|
this.$emit("input", this.selected);
|
||||||
|
this.queryText = `${defaultAddress.poiInfos.name} ${defaultAddress.poiInfos.alternativeName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCurrentPosition(e: LatLng): boolean {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
async getLocation(): Promise<GeolocationPosition> {
|
||||||
|
let errorMessage = this.$t("Failed to get location.");
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!("geolocation" in navigator)) {
|
||||||
|
reject(new Error(errorMessage as string));
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
resolve(pos);
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
switch (err.code) {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
case GeolocationPositionError.PERMISSION_DENIED:
|
||||||
|
errorMessage = this.$t("The geolocation prompt was denied.");
|
||||||
|
break;
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
case GeolocationPositionError.POSITION_UNAVAILABLE:
|
||||||
|
errorMessage = this.$t("Your position was not available.");
|
||||||
|
break;
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
case GeolocationPositionError.TIMEOUT:
|
||||||
|
errorMessage = this.$t("Geolocation was not determined in time.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage = err.message;
|
||||||
|
}
|
||||||
|
reject(new Error(errorMessage as string));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get fieldErrors(): Array<Record<string, boolean>> {
|
||||||
|
const errors = [];
|
||||||
|
if (this.gettingLocationError) {
|
||||||
|
errors.push({
|
||||||
|
[this.gettingLocationError]: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
get isSecureContext(): boolean {
|
||||||
|
return window.isSecureContext;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue