Skip to content

Commit

Permalink
feat(@dpc-sdp/ripple-tide-search): added geolocate button to custom c…
Browse files Browse the repository at this point in the history
…ollections/maps
  • Loading branch information
jeffdowdle committed May 24, 2024
1 parent 99a5811 commit 0e48279
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 17 deletions.
109 changes: 99 additions & 10 deletions packages/ripple-tide-search/components/global/TideCustomCollection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ const mapResultsMappingFn = (result) => {
const filtersExpanded = ref(props.searchListingConfig?.showFiltersOnLoad)
const isGettingLocation = ref<boolean>(false)
const geolocationError = ref<string | null>(null)
const {
isBusy,
searchError,
Expand All @@ -204,7 +207,8 @@ const {
locationQuery,
activeTab,
changeActiveTab,
firstLoad
firstLoad,
userGeolocation
} = useTideSearch({
queryConfig: props.queryConfig,
userFilters: props.userFilters,
Expand Down Expand Up @@ -289,7 +293,7 @@ onMapResultsHook.value = () => {
)
}
hookFn(rplMapRef.value, mapResults.value, locationQuery.value)
hookFn(rplMapRef.value, mapResults.value, locationOrGeolocation.value)
}
const emitSearchEvent = (event) => {
Expand Down Expand Up @@ -463,6 +467,54 @@ const reverseFields = computed(
(reverseTheme.value && !altBackground.value) ||
(altBackground.value && !reverseTheme.value)
)
const handleGeolocateClick = () => {
isGettingLocation.value = true
geolocationError.value = null
}
const handleGeolocateSuccess = (pos: GeolocationPosition) => {
isGettingLocation.value = false
geolocationError.value = null
userGeolocation.value = {
name: 'Current Location',
center: [pos.coords.longitude, pos.coords.latitude]
}
handleLocationSearch({
useGeolocation: true,
id: `__geo${pos.timestamp}`
})
}
const handleGeolocateError = (error: GeolocationPositionError) => {
let message
switch (error.code) {
case error.PERMISSION_DENIED:
message = 'User denied the request for Geolocation.'
break
case error.POSITION_UNAVAILABLE:
message = 'Location information is unavailable.'
break
case error.TIMEOUT:
message = 'The request to get user location timed out.'
break
default:
message = 'An unknown error occurred.'
break
}
isGettingLocation.value = false
geolocationError.value = message
}
const locationOrGeolocation = computed(() => {
return locationQuery.value?.useGeolocation && userGeolocation.value
? userGeolocation.value
: locationQuery.value
})
</script>

<template>
Expand Down Expand Up @@ -495,16 +547,34 @@ const reverseFields = computed(
:placeholder="searchListingConfig.labels?.placeholder"
:inputValue="locationQuery"
:resultsloaded="mapFeatures.length > 0"
:isGettingLocation="isGettingLocation"
:userGeolocation="userGeolocation"
@update="handleLocationSearch"
/>

<RplSearchBarRefine
v-if="userFilters && userFilters.length > 0"
class="tide-search-refine-btn"
:expanded="filtersExpanded"
@click="handleToggleFilters"
>{{ toggleFiltersLabel }}</RplSearchBarRefine
>
<div class="tide-search-util-bar">
<RplMapGeolocateButton
v-if="locationQueryConfig?.showGeolocationButton"
:isBusy="isGettingLocation"
:error="geolocationError"
@click="handleGeolocateClick"
@success="handleGeolocateSuccess"
@error="handleGeolocateError"
>
Use my location
</RplMapGeolocateButton>
<div class="tide-search-refine-wrapper">
<RplSearchBarRefine
v-if="userFilters && userFilters.length > 0"
class="tide-search-refine-btn"
:expanded="filtersExpanded"
@click="handleToggleFilters"
>
{{ toggleFiltersLabel }}
</RplSearchBarRefine>
</div>
</div>

<RplExpandable
v-if="userFilters && userFilters.length > 0"
:expanded="filtersExpanded"
Expand Down Expand Up @@ -679,7 +749,6 @@ const reverseFields = computed(
.tide-search-refine-btn {
align-self: flex-end;
padding: 0;
margin-top: var(--rpl-sp-5);
}
.tide-search-results--loading {
Expand All @@ -694,4 +763,24 @@ const reverseFields = computed(
margin-top: var(--rpl-sp-5);
}
}
.tide-search-util-bar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
align-items: flex-start;
row-gap: var(--rpl-sp-4);
column-gap: var(--rpl-sp-8);
margin-top: var(--rpl-sp-3);
@media (--rpl-bp-s) {
margin-top: var(--rpl-sp-5);
}
}
.tide-search-refine-wrapper {
flex-grow: 1;
display: flex;
justify-content: flex-end;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
:showLabel="true"
variant="reverse"
:submitLabel="false"
:inputValue="inputValue"
:inputValue="
inputValue?.useGeolocation ? userGeolocation || null : inputValue
"
:suggestions="results"
:showNoResults="true"
:debounce="5000"
:maxSuggestionsDisplayed="8"
:placeholder="placeholder"
:getOptionId="(itm:any) => itm?.id || itm?.name"
:getSuggestionVal="(itm:any) => itm?.name || ''"
:getOptionLabel="(itm:any) => itm?.name || ''"
:isBusy="isGettingLocation"
:isFreeText="false"
:submitOnClear="true"
@submit="submitAction"
Expand Down Expand Up @@ -42,7 +46,7 @@
import { inject, watch } from 'vue'
import { ref, getSingleResultValue } from '#imports'
import { useDebounceFn } from '@vueuse/core'
import { transformExtent } from 'ol/proj'
import { transformExtent, fromLonLat } from 'ol/proj'
import { Extent } from 'ol/extent'
// TODO must add analytics events
// import { useRippleEvent } from '@dpc-sdp/ripple-ui-core'
Expand All @@ -57,6 +61,8 @@ interface Props {
placeholder?: string
tagsComponent?: string
mapResultsFnName?: string
isGettingLocation?: boolean
userGeolocation: any
}
const props = withDefaults(defineProps<Props>(), {
Expand All @@ -68,7 +74,9 @@ const props = withDefaults(defineProps<Props>(), {
label: 'Search by postcode or suburb',
placeholder: 'Enter postcode or suburb',
tagsComponent: undefined,
mapResultsFnName: ''
mapResultsFnName: '',
isGettingLocation: false,
userGeolocation: null
})
const results = ref([])
Expand All @@ -90,7 +98,7 @@ const { rplMapRef, deadSpace } = inject('rplMapInstance')
const pendingZoomAnimation = ref(false)
async function submitAction(e: any) {
const item = e.value
const item = e.payload
// The search bar component sometimes returns a string, sometimes an object, we just ignore the non-empty strings
if (item && typeof item === 'string') {
Expand Down Expand Up @@ -248,6 +256,20 @@ async function centerMapOnLocation(
return
}
// Zoom to the user's geolocation if enabled
if (location?.useGeolocation && props.userGeolocation) {
const center = [
props.userGeolocation.center[0],
props.userGeolocation.center[1]
]
const zoom = 16
centerMap(map, fromLonLat(center), zoom, deadSpace.value, null)
return
}
if (map && location?.bbox) {
// fetch the geometry of the postcode so we can zoom to its extent
if (location?.bbox) {
Expand Down
14 changes: 11 additions & 3 deletions packages/ripple-tide-search/composables/useTideSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default ({
return pageSize.value ? Math.ceil(totalResults.value / pageSize.value) : 0
})

const userGeolocation = ref<any>(null)
const mapResults = ref([])

const onAggregationUpdateHook = ref()
Expand Down Expand Up @@ -156,7 +157,7 @@ export default ({
}

const transformedDSL = await transformFn(
locationQuery.value,
locationOrGeolocation.value,
filterForm.value
)

Expand Down Expand Up @@ -217,7 +218,7 @@ export default ({
)
}

const sortDSL = sortFn(locationQuery.value, filterForm.value)
const sortDSL = sortFn(locationOrGeolocation.value, filterForm.value)

return sortDSL
}
Expand Down Expand Up @@ -819,6 +820,12 @@ export default ({
return getFiltersFromRoute(route)
})

const locationOrGeolocation = computed(() => {
return locationQuery.value?.useGeolocation && userGeolocation.value
? userGeolocation.value
: locationQuery.value
})

onMounted(() => {
// Read the url on first mount to kick of the initial search
searchFromRoute(route, true)
Expand Down Expand Up @@ -863,6 +870,7 @@ export default ({
activeTab,
changeActiveTab,
locationQuery,
firstLoad
firstLoad,
userGeolocation
}
}
1 change: 1 addition & 0 deletions packages/ripple-tide-search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export type TideSearchLocationQueryConfig = {
[key: string]: unknown
}
dslTransformFn?: (location: any) => any
showGeolocationButton?: boolean
}

export type TideSearchListingTab = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.rpl-map-geolocate-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--rpl-sp-2);
box-sizing: border-box;
color: var(--rpl-clr-link);

.rpl-icon {
transition: transform var(--rpl-motion-speed-7) linear;
}

span {
text-decoration: underline;
}

&:hover {
span {
text-decoration: none;
}
}

&:focus-visible {
span {
text-decoration: none;
}
}

&[disabled] {
color: var(--rpl-clr-link-disabled);
cursor: not-allowed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
Canvas,
Meta,
Story,
ArgsTable
} from '@storybook/addon-docs'
import RplMapGeolocateButton from './RplMapGeolocateButton.vue'
import { action } from '@storybook/addon-actions';
import '@dpc-sdp/ripple-ui-core/style/components'

export const SingleTemplate = (args) => ({
components: { RplMapGeolocateButton },
setup() {
return { args, callAction: action }
},
template: `<RplMapGeolocateButton @onGeolocate="geolocateAction" v-bind="args"></RplMapGeolocateButton>`,
methods: {
geolocateAction: action('onGeolocate'),
}
})

<Meta
title='Maps/Geolocate Button'
component={RplMapGeolocateButton}
argTypes={{
title: {
control: { type: 'text' },
}
}}
args={{
title: 'Legend'
}}
/>

# Tag

<ArgsTable of={RplMapGeolocateButton} />

## Default

<Canvas>
<Story
name='Default'
>
{SingleTemplate.bind()}
</Story>
</Canvas>
Loading

0 comments on commit 0e48279

Please sign in to comment.