/* eslint-disable ts/no-this-alias */ import type { VNode } from 'vue'; import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item'; import type { FormItemWrapperArgs, MarginType } from '../form/form-item-wrapper'; import { Prop, toNative } from 'vue-facing-decorator'; import { globalState } from '../../app/global-state'; import PowerduckState from '../../app/powerduck-state'; import TsxComponent, { Component } from '../../app/vuetsx'; import { AppHttpProvider } from '../../common/api-http'; import { CountryISOMapping } from '../../common/country-iso-mapping'; import { isNullOrEmpty } from '../../common/utils/is-null-or-empty'; import FormItemWrapper from '../form/form-item-wrapper'; import GoogleMapsApiLoader from './ts/google-maps-api'; interface GooglePlacesAutocompleteValue { latitude: string; longitude: string; name: string; googlePlace: any; } interface TimezoneData { dstOffset: number; gmtOffset: number; timezoneId: string; } interface GooglePlacesAutocompleteArgs extends FormItemWrapperArgs { value?: string; placeholder?: string; changed: (newValue: GooglePlacesChangedArgs) => void; bound: () => void; resultType?: GooglePlacesResultType; timezoneRequired?: boolean; } interface GooglePlacesAutocompleteSingletonArgs { element: HTMLElement; resultType: GooglePlacesResultType; changed: (args: GooglePlacesChangedArgs) => void; } export interface GooglePlacesChangedArgs { name: string; street: string; zip: string; city: string; country: string; gpsLatitude: string; gpsLongitude: string; fullName: string; inputValue: string; dstOffset?: number; gmtOffset?: number; timezoneName?: string; } export enum GooglePlacesResultType { All = 0, Address = 1, City = 2, } @Component class GooglePlacesAutocompleteComponent extends TsxComponent implements GooglePlacesAutocompleteArgs { @Prop() label!: string | VNode; @Prop() labelButtons!: DropdownButtonItemArgs[]; @Prop() subtitle!: string; @Prop() placeholder!: string; @Prop() mandatory!: boolean; @Prop() wrap!: boolean; @Prop() showClearValueButton!: boolean; @Prop() hint: string; @Prop() appendIcon: string; @Prop() prependIcon: string; @Prop() maxWidth?: number; @Prop() marginType?: MarginType; @Prop() resultType: GooglePlacesResultType; @Prop() appendClicked: () => void; @Prop() prependClicked: () => void; @Prop() bound: () => void; @Prop() changed: (newValue: GooglePlacesChangedArgs) => void; @Prop() value: string; @Prop() timezoneRequired: boolean; currentValue: string = null; raiseChangeEvent(e: GooglePlacesChangedArgs) { this.populateValidationDeclaration(); if (this.changed != null) { this.changed(e); } } trySetInputValue(val: string): void { try { (this.$refs.googleInput as HTMLInputElement).value = val; } catch (e) { } } mounted() { const mySelf = this; this.currentValue = this.value; GoogleMapsApiLoader.load().then((result) => { if (result) { GooglePlacesAutocompleteWrapper.bind({ element: this.$el.querySelector('input'), resultType: this.resultType || GooglePlacesResultType.All, changed(changeArgs) { if (mySelf.timezoneRequired != true) { mySelf.raiseChangeEvent(changeArgs); } else { GooglePlacesUtils.getTimezone(changeArgs).then((data) => { changeArgs.dstOffset = data.dstOffset; changeArgs.gmtOffset = data.gmtOffset; changeArgs.timezoneName = data.timezoneId; if (changeArgs.timezoneName != null && changeArgs.timezoneName.toLowerCase().includes('bratislava')) { changeArgs.timezoneName = 'Europe/Vienna'; } else if (changeArgs.dstOffset == 120 && changeArgs.gmtOffset == 60) { changeArgs.timezoneName = 'Europe/Vienna'; } mySelf.raiseChangeEvent(changeArgs); }); } }, }); } }); this.$nextTick(() => { (this.$refs.googleInput as HTMLInputElement).value = this.currentValue; }); } render(h) { return ( ); } } class GooglePlacesUtils { static getName(place: any, type: GooglePlacesResultType): string { if (type != GooglePlacesResultType.Address) { if (place.types && place.types.includes('establishment')) { return place.name; } if (!isNullOrEmpty(place.adr_address)) { try { return place.adr_address.split('')[0].split('>')[1]; } catch (e) { } } return place.name; } else { return GooglePlacesUtils.getStreet(place); } } static getStreet(place: any): string { const ac: any[] = place.address_components; let houseNo: string = ''; let street: string = ''; if (!isNullOrEmpty(ac)) { ac.forEach((acItem) => { if (acItem.types) { if (acItem.types.contains('street_number')) { houseNo = acItem.long_name; } else if (acItem.types.contains('route')) { street = acItem.long_name; } } }); } return (`${street} ${houseNo}`).trim(); } static getCity(place: any, resultType: GooglePlacesResultType): string { if (resultType == GooglePlacesResultType.City) { return GooglePlacesUtils.getName(place, resultType); } let retVal: string; if (!isNullOrEmpty(place.address_components)) { let adminArea: string; for (let i = 0; i < place.address_components.length; i++) { const ac = place.address_components[i]; if (ac.types) { if (ac.types.contains('locality')) { retVal = ac.short_name; } else { for (let j = 0; j < ac.types.length; j++) { const acType = ac.types[j]; if (acType.contains('administrative_area') && isNullOrEmpty(adminArea)) { adminArea = ac.short_name; } } } } } if (isNullOrEmpty(retVal)) { retVal = adminArea; } } if (!isNullOrEmpty(retVal)) { const rlc = retVal.toLowerCase(); if (rlc.includes('bratislava')) { return 'Bratislava'; } else if (rlc.includes('praha')) { return 'Praha'; } return retVal; } return GooglePlacesUtils.getName(place, resultType); } static getZIP(place: any): string { const ac: any[] = place.address_components; if (!isNullOrEmpty(ac)) { for (let i = 0, len = ac.length; i < len; i++) { const acItem = ac[i]; if (acItem.types.contains('postal_code')) { return acItem.short_name.replaceAll(' ', ''); } } } return null; } static getCountry(place: any): string { const ac: any[] = place.address_components; if (!isNullOrEmpty(ac)) { for (let i = 0, len = ac.length; i < len; i++) { const acItem = ac[i]; if (acItem.types.contains('country')) { return CountryISOMapping[acItem.short_name] || acItem.short_name; } } } return null; } static getPlacesRestrictionCode(resultType: GooglePlacesResultType): string { if (resultType == GooglePlacesResultType.Address) { return 'address'; } else if (resultType == GooglePlacesResultType.City) { return '(cities)'; } return null; } static getTimezone(args: GooglePlacesChangedArgs): Promise { return new Promise((resolve, reject) => { AppHttpProvider .callAjax({ type: 'GET', url: `https://secure.geonames.org/timezoneJSON?lat=${args.gpsLatitude}&lng=${args.gpsLongitude}&username=${PowerduckState.getGeonamesUsername()}`, }) .then((data: any) => { resolve({ dstOffset: data.dstOffset * 60, gmtOffset: data.gmtOffset * 60, timezoneId: data.timezoneId, }); }); }); } } class GooglePlacesAutocompleteWrapper { static bind(args: GooglePlacesAutocompleteSingletonArgs): void { let acArgs: any; if (args.resultType != GooglePlacesResultType.All) { acArgs = { types: [GooglePlacesUtils.getPlacesRestrictionCode(args.resultType)], }; } if (!globalState.windowExists) { return; } const autocomplete = new globalState.google.maps.places.Autocomplete(args.element as any, acArgs); autocomplete.addListener('place_changed', () => { const place = autocomplete.getPlace(); const name = GooglePlacesUtils.getName(place, args.resultType); (args.element as HTMLInputElement).value = name; args.changed({ name, fullName: place.formatted_address, inputValue: (args.element as HTMLInputElement).value, street: GooglePlacesUtils.getStreet(place), city: GooglePlacesUtils.getCity(place, args.resultType), zip: GooglePlacesUtils.getZIP(place), country: GooglePlacesUtils.getCountry(place), gpsLatitude: place.geometry.location.lat() as any, gpsLongitude: place.geometry.location.lng() as any, }); }); } } const GooglePlacesAutocomplete = toNative(GooglePlacesAutocompleteComponent); export default GooglePlacesAutocomplete;