Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,38 @@
[v-cloak] {
display: none;
}

#autocomplete {
min-height: calc(1.5em + .75rem + 2px);
}

.location-search-control {
display: block;
width: 100%;
color-scheme: light;
}

.location-search-control::part(input) {
min-height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
border: 1px solid #ced4da;
border-radius: .25rem;
background-color: #fff;
color: #495057;
font: inherit;
line-height: 1.5;
}

.location-search-control::part(prediction-list) {
border: 1px solid rgba(0, 0, 0, .15);
border-radius: .25rem;
box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
}

.location-search-disabled {
opacity: .65;
pointer-events: none;
}
</style>

<title>praytime | masjid iqamah times</title>
Expand Down Expand Up @@ -64,10 +96,10 @@ <h1>praytime
<small class="text-muted">masjid iqamah times</small>
</h1>
<div id="locationField">
<form>
<form id="location-search-form">
<div class="row">
<div class="col">
<input type="text" class="form-control" id="autocomplete" placeholder="Enter a location" :disabled="inputDisabled"/>
<div id="autocomplete"></div>
</div>
<div class="col">
<button class="form-control btn btn-primary btn-sm" :disabled="inputDisabled" onclick="getCurrentPosition();return false;">Use current location</button>
Expand Down Expand Up @@ -170,4 +202,3 @@ <h6 class="card-subtitle mb-2 text-muted"><address>{{ event.address }}</address>
</body>

</html>

149 changes: 133 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ setOptions({
v: 'weekly'
})

let placeAutocomplete = null
let syncLocationSearchDisabled = function () {}
let lastPlaceAutocompleteSelectionAt = 0
let lastManualLocationSearchAt = 0
let lastManualLocationSearchQuery = ''

const gmapsLoaderPromise = Promise.all([
importLibrary('core'),
importLibrary('places'),
Expand All @@ -28,26 +34,72 @@ const gmapsLoaderPromise = Promise.all([

gmapsLoaderPromise.then(google => {
const geocoder = new google.maps.Geocoder()
const autocomplete = new google.maps.places.Autocomplete(document.getElementById('autocomplete'), {
types: ['geocode'],
fields: ['name', 'geometry.location']
const locationSearchContainer = document.getElementById('autocomplete')
const locationSearchForm = document.getElementById('location-search-form')

placeAutocomplete = new google.maps.places.PlaceAutocompleteElement({
name: 'location',
placeholder: 'Enter a location'
})
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace()
if (place.geometry) {
// got results
getPrayerTimesForLocation(place.name, place.geometry.location, google)

placeAutocomplete.id = 'place-autocomplete'
placeAutocomplete.classList.add('location-search-control')
locationSearchContainer.replaceChildren(placeAutocomplete)

syncLocationSearchDisabled = function (disabled = vApp && vApp.inputDisabled) {
const isDisabled = Boolean(disabled)
placeAutocomplete.classList.toggle('location-search-disabled', isDisabled)
placeAutocomplete.setAttribute('aria-disabled', String(isDisabled))
if (isDisabled) {
placeAutocomplete.setAttribute('inert', '')
} else {
// need to do a search
geocoder.geocode({ address: document.getElementById('autocomplete').value }, (results, status) => {
if (status === 'OK') {
getPrayerTimesForLocation(results[0].formatted_address, results[0].geometry.location, google)
} else {
vApp.message = 'Not found: ' + status
vApp.messageClass = 'text-warning'
}
placeAutocomplete.removeAttribute('inert')
}
}
syncLocationSearchDisabled()

placeAutocomplete.addEventListener('gmp-select', async ({ placePrediction }) => {
lastPlaceAutocompleteSelectionAt = Date.now()

try {
const place = placePrediction.toPlace()
await place.fetchFields({
fields: ['displayName', 'formattedAddress', 'location']
})

if (place.location) {
getPrayerTimesForLocation(
place.displayName || place.formattedAddress || getLocationSearchQuery(),
normalizeLocation(place.location, google),
google
)
return
}
} catch (err) {
console.error('[place autocomplete] selection error:', err)
}

geocodeLocationSearch(geocoder, getLocationSearchQuery(), google)
})

placeAutocomplete.addEventListener('gmp-error', (event) => {
console.error('[place autocomplete] backend error:', event)
vApp.message = 'Location search is temporarily unavailable. Please try again.'
vApp.messageClass = 'text-warning'
})

placeAutocomplete.addEventListener('keydown', (event) => {
if (event.key !== 'Enter' || event.defaultPrevented) {
return
}

queueManualLocationSearch(getLocationSearchQuery(), geocoder, google)
})

locationSearchForm.addEventListener('submit', (event) => {
event.preventDefault()

queueManualLocationSearch(getLocationSearchQuery(), geocoder, google)
})
}).catch(err => {
console.error('[gmaps loader] error:', err)
Expand Down Expand Up @@ -92,6 +144,11 @@ const vApp = new Vue({
messageClass: 'text-secondary',
events: []
},
watch: {
inputDisabled: function (disabled) {
syncLocationSearchDisabled(disabled)
}
},
methods: {
toggleNotification: function (event) {
console.log('toggle notification', event.id)
Expand Down Expand Up @@ -417,6 +474,66 @@ function getCurrentPosition () {
// https://stackoverflow.com/a/56805689
window.getCurrentPosition = getCurrentPosition

function getLocationSearchQuery () {
return placeAutocomplete && typeof placeAutocomplete.value === 'string'
? placeAutocomplete.value.trim()
: ''
}

function geocodeLocationSearch (geocoder, query, google) {
const trimmedQuery = (query || '').trim()
if (!trimmedQuery) {
return
}

geocoder.geocode({ address: trimmedQuery }, (results, status) => {
if (status === 'OK' && results && results[0] && results[0].geometry && results[0].geometry.location) {
const locationDescription = results[0].formatted_address || trimmedQuery
if (placeAutocomplete) {
placeAutocomplete.value = locationDescription
}
getPrayerTimesForLocation(locationDescription, results[0].geometry.location, google)
} else {
vApp.message = 'Not found: ' + status
vApp.messageClass = 'text-warning'
}
})
}

function queueManualLocationSearch (query, geocoder, google) {
window.setTimeout(() => {
const trimmedQuery = (query || '').trim()
if (!trimmedQuery) {
return
}

const now = Date.now()
if (now - lastPlaceAutocompleteSelectionAt < 250) {
return
}

if (trimmedQuery === lastManualLocationSearchQuery && now - lastManualLocationSearchAt < 250) {
return
}

lastManualLocationSearchAt = now
lastManualLocationSearchQuery = trimmedQuery
geocodeLocationSearch(geocoder, trimmedQuery, google)
}, 0)
}

function normalizeLocation (location, google) {
if (location && typeof location.lat === 'function' && typeof location.lng === 'function') {
return location
}

if (location && typeof location.lat === 'number' && typeof location.lng === 'number') {
return new google.maps.LatLng(location.lat, location.lng)
}

throw new Error('Selected place did not include a valid location')
}

function docToEvent (doc) {
const evt = doc.data()

Expand Down
Loading