Skip to content

Commit

Permalink
Improve geolocation (#382)
Browse files Browse the repository at this point in the history
* Represent denied location with lower opacity and not clickable
* Show help cursor and toast explanation on second click
* Toast errors
* Enable high accuracy geolocation
  • Loading branch information
wbazant authored Jun 4, 2024
1 parent 683c5ed commit f94656d
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
7 changes: 6 additions & 1 deletion src/components/map/ConnectedGeolocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { useGeolocation } from 'react-use'
import { geolocationChange } from '../../redux/mapSlice'

export const ConnectedGeolocation = () => {
const geolocation = useGeolocation()
const geolocation = useGeolocation({
// Ask the device to use the most accurate means of geolocation
// We want to help with precise annotation of locations
// and to provide a good zoomed-in view
enableHighAccuracy: true,
})
const dispatch = useDispatch()

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/map/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ const Map = ({
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps)}
yesIWantToUseGoogleMapApiInternals
>
{geolocation && !geolocation.loading && (
{geolocation && !geolocation.loading && !geolocation.error && (
<Geolocation
onClick={onGeolocationClick}
lat={geolocation.latitude}
Expand Down
35 changes: 23 additions & 12 deletions src/components/map/TrackLocationButton.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CurrentLocation, LoaderAlt } from '@styled-icons/boxicons-regular'
import { XCircle } from '@styled-icons/boxicons-solid'
import { useDispatch, useSelector } from 'react-redux'
import { toast } from 'react-toastify'
import { keyframes } from 'styled-components'
import styled from 'styled-components/macro'

Expand All @@ -20,12 +20,12 @@ const SpinningLoader = styled(LoaderAlt)`
animation: 1s linear ${spin} infinite;
`

const getTrackLocationColor = ({ disabled, $active }) =>
$active && !disabled ? 'blue' : 'tertiaryText'
const getTrackLocationColor = ({ userDeniedLocation, $active }) =>
$active && !userDeniedLocation ? 'blue' : 'tertiaryText'

const TrackLocationIcon = ({ disabled, $loading, ...props }) => {
if (disabled) {
return <XCircle {...props} /> // TODO: replace this with a specific "disabled geolocation" icon, like Google Maps has
const TrackLocationIcon = ({ userDeniedLocation, $loading, ...props }) => {
if (userDeniedLocation) {
return <CurrentLocation opacity="0.5" {...props} />
} else if ($loading) {
return <SpinningLoader {...props} />
} else {
Expand All @@ -39,9 +39,8 @@ const TrackLocationPrependButton = styled.button.attrs((props) => ({
padding-left: 3px;
padding-right: 8px;
&:enabled {
cursor: pointer;
}
cursor: ${({ userDeniedLocation }) =>
userDeniedLocation ? 'help' : 'pointer'};
svg {
color: ${({ theme, ...props }) => theme[getTrackLocationColor(props)]};
Expand All @@ -59,6 +58,8 @@ const TrackLocationIconButton = styled(IconButton).attrs((props) => ({
svg {
padding: 10px;
}
cursor: ${({ userDeniedLocation }) =>
userDeniedLocation ? 'help' : 'pointer'};
position: absolute;
bottom: 84px;
Expand All @@ -72,18 +73,28 @@ const TrackLocationButton = ({ isIcon }) => {
const isTrackingLocation = useSelector(
(state) => state.map.isTrackingLocation,
)
const userDeniedLocation = useSelector(
(state) => state.map.userDeniedLocation,
)

const TrackLocationBtn = isIcon
? TrackLocationIconButton
: TrackLocationPrependButton

return (
<TrackLocationBtn
disabled={geolocation?.error}
userDeniedLocation={userDeniedLocation}
$loading={geolocation?.loading}
$active={isTrackingLocation}
onClick={() => {
dispatch(startTrackingLocation())
onClick={(event) => {
if (userDeniedLocation) {
toast.info(
'Permission to use your location was denied. To enable geolocation, please allow location sharing in your browser settings and refresh the page.'
)
} else {
dispatch(startTrackingLocation())
}
event.stopPropagation()
}}
/>
)
Expand Down
22 changes: 20 additions & 2 deletions src/redux/mapSlice.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { fitBounds } from 'google-map-react'
import { eqBy, prop, unionWith } from 'ramda'
import { toast } from 'react-toastify'

import { VISIBLE_CLUSTER_ZOOM_LIMIT } from '../constants/map'
import { getClusters, getLocations } from '../utils/api'
import { parseUrl } from '../utils/getInitialUrl'
import { searchView } from './searchView'
import { selectParams } from './selectParams'
import { updateSelection } from './updateSelection'

/**
* Initial view state of the map.
* @constant {Object}
Expand Down Expand Up @@ -61,6 +61,7 @@ export const mapSlice = createSlice({
geolocation: null,
isTrackingLocation: false,
justStartedTrackingLocation: false,
userDeniedLocation: false,
locationRequested: false,
streetView: false,
location: null,
Expand Down Expand Up @@ -111,7 +112,24 @@ export const mapSlice = createSlice({
if (action.payload.loading) {
// Loading
} else if (action.payload.error) {
// TODO: send a toast that geolocation isn't working
if (action.payload.error.code === 1) {
// code 1 of GeolocationPositionError means user denied location request
// browsers will block subsequent requests so disable the setting
state.userDeniedLocation = true
} else {
// Treat code 2, internal error, as fatal
// Toast the message and suggest a retry
//
// The last value is code 3, timeout, is unreachable as we use the default of no timeout
// @see src/components/map/ConnectedGeolocation.js
if (!state.geolocation.error) {
toast.error(
`Geolocation failed: ${action.payload.error.message}. Please refresh the page and retry`,
)
} else {
// We already toasted
}
}
state.isTrackingLocation = false
state.justStartedTrackingLocation = false
} else if (state.isTrackingLocation) {
Expand Down

0 comments on commit f94656d

Please sign in to comment.