diff --git a/lib/actions/location.js b/lib/actions/location.tsx similarity index 64% rename from lib/actions/location.js rename to lib/actions/location.tsx index 5b26ba3f1..a336efb18 100644 --- a/lib/actions/location.js +++ b/lib/actions/location.tsx @@ -1,4 +1,7 @@ +// @ts-expect-error TODO: add @types/redux-actions (will break other other stuff). import { createAction } from 'redux-actions' +import { Dispatch } from 'redux' +import { IntlShape } from 'react-intl' import { setLocationToCurrent } from './map' @@ -7,35 +10,42 @@ export const receivedPositionError = createAction('POSITION_ERROR') export const fetchingPosition = createAction('POSITION_FETCHING') export const receivedPositionResponse = createAction('POSITION_RESPONSE') -export function getCurrentPosition (intl, setAsType = null, onSuccess) { - return async function (dispatch, getState) { +export const PLACE_EDITOR_LOCATION = 'placeeditor' + +export function getCurrentPosition( + intl: IntlShape, + setAsType?: string | null, + onSuccess?: (position: GeolocationPosition) => void +) { + return function (dispatch: Dispatch): void { if (navigator.geolocation) { dispatch(fetchingPosition({ type: setAsType })) navigator.geolocation.getCurrentPosition( // On success - position => { + (position) => { if (position) { console.log('current loc', position, setAsType) dispatch(receivedPositionResponse({ position })) - if (setAsType) { + if (setAsType && setAsType !== PLACE_EDITOR_LOCATION) { console.log('setting location to current position') + // @ts-expect-error Action below is not typed yet. dispatch(setLocationToCurrent({ locationType: setAsType }, intl)) - onSuccess && onSuccess() } + onSuccess && onSuccess(position) } else { dispatch( receivedPositionError({ error: { - message: intl.formatMessage( - { id: 'actions.location.unknownPositionError' } - ) + message: intl.formatMessage({ + id: 'actions.location.unknownPositionError' + }) } }) ) } }, // On error - error => { + (error) => { console.log('error getting current position', error) // FIXME, analyze error code to produce better error message. // See https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError @@ -49,9 +59,9 @@ export function getCurrentPosition (intl, setAsType = null, onSuccess) { dispatch( receivedPositionError({ error: { - message: intl.formatMessage( - { id: 'actions.location.geolocationNotSupportedError' } - ) + message: intl.formatMessage({ + id: 'actions.location.geolocationNotSupportedError' + }) } }) ) diff --git a/lib/components/user/places/place-editor.tsx b/lib/components/user/places/place-editor.tsx index c49b9c408..8dc509f74 100644 --- a/lib/components/user/places/place-editor.tsx +++ b/lib/components/user/places/place-editor.tsx @@ -1,3 +1,4 @@ +import { connect } from 'react-redux' import { ControlLabel, FormControl, @@ -5,12 +6,20 @@ import { HelpBlock } from 'react-bootstrap' import { Field, FormikProps } from 'formik' -import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl' +import { + FormattedMessage, + injectIntl, + IntlShape, + WrappedComponentProps +} from 'react-intl' +import { Location } from '@opentripplanner/types' import { LocationSelectedEvent } from '@opentripplanner/location-field/lib/types' import coreUtils from '@opentripplanner/core-utils' +import getGeocoder, { GeocoderConfig } from '@opentripplanner/geocoder' import React, { Component, Fragment } from 'react' import styled from 'styled-components' +import * as locationActions from '../../../actions/location' import { capitalizeFirst, getErrorStates } from '../../../util/ui' import { ComponentContext } from '../../../util/contexts' import { CUSTOM_PLACE_TYPES, isHomeOrWork } from '../../../util/user' @@ -23,7 +32,14 @@ import InvisibleA11yLabel from '../../util/invisible-a11y-label' import { PlaceLocationField } from './place-location-field' -type Props = WrappedComponentProps & FormikProps +type Props = WrappedComponentProps & + FormikProps & { + geocoderConfig: GeocoderConfig + getCurrentPosition: ( + ...args: Parameters + ) => void + intl: IntlShape + } const { isMobile } = coreUtils.ui @@ -67,16 +83,48 @@ function makeLocationFieldLocation(favoriteLocation: UserSavedLocation) { class PlaceEditor extends Component { static contextType = ComponentContext - _handleLocationChange = (e: LocationSelectedEvent) => { - const { setTouched, setValues, values } = this.props - const { lat, lon, name } = e.location + _setLocation = (location: Location) => { + const { intl, setValues, values } = this.props + const { category, lat, lon, name } = location setValues({ ...values, - address: name, + address: + // If the raw current location is passed without a name attribute (i.e. the address), + // set the "address" as the formatted coordinates of the current location at that time. + category === 'CURRENT_LOCATION' + ? intl.formatMessage({ id: 'common.coordinates' }, { lat, lon }) + : name, lat, lon }) - setTouched({ address: true }) + } + + _handleLocationChange = (e: LocationSelectedEvent) => { + this._setLocation(e.location) + } + + _handleGetCurrentPosition = () => { + const { geocoderConfig, getCurrentPosition, intl } = this.props + getCurrentPosition( + intl, + locationActions.PLACE_EDITOR_LOCATION, + ({ coords }) => { + const { latitude: lat, longitude: lon } = coords + // Populate the "address" field with the coordinates at first. + // If geocoding succeeds, the resulting address will appear there. + this._setLocation({ + category: 'CURRENT_LOCATION', + lat, + lon + }) + getGeocoder(geocoderConfig) + .reverse({ point: coords }) + .then(this._setLocation) + .catch((err: Error) => { + console.warn(err) + }) + } + ) } render() { @@ -168,6 +216,7 @@ class PlaceEditor extends Component { { } } -export default injectIntl(PlaceEditor) +const mapStateToProps = (state: any) => { + return { + geocoderConfig: state.otp.config.geocoder + } +} + +const mapDispatchToProps = { + getCurrentPosition: locationActions.getCurrentPosition +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(PlaceEditor)) diff --git a/lib/components/user/places/place-location-field.ts b/lib/components/user/places/place-location-field.ts index ab606f647..711be8ae5 100644 --- a/lib/components/user/places/place-location-field.ts +++ b/lib/components/user/places/place-location-field.ts @@ -55,9 +55,14 @@ const StyledLocationField = styled(LocationField)` props.static ? 'padding-left: 10px; padding-right: 5px; width: 100%' : ''} } ` + /** * Styled LocationField for setting a favorite place locations using the geocoder. */ export const PlaceLocationField = connectLocationField(StyledLocationField, { + actions: { + // Set to null so that PlaceEditor can set its own handler. + getCurrentPosition: null + }, excludeSavedLocations: true }) diff --git a/package.json b/package.json index e2007e840..e29b6357e 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,11 @@ "@opentripplanner/core-utils": "^9.0.3", "@opentripplanner/endpoints-overlay": "^2.0.7", "@opentripplanner/from-to-location-picker": "^2.1.7", - "@opentripplanner/geocoder": "^1.4.1", + "@opentripplanner/geocoder": "^1.4.2", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^2.0.3", "@opentripplanner/itinerary-body": "^5.0.1", - "@opentripplanner/location-field": "^2.0.6", + "@opentripplanner/location-field": "^2.0.7", "@opentripplanner/location-icon": "^1.4.1", "@opentripplanner/map-popup": "^2.0.4", "@opentripplanner/otp2-tile-overlay": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index d4067ffc5..8e2a78e8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2413,26 +2413,7 @@ lodash.isequal "^4.5.0" qs "^6.9.1" -"@opentripplanner/core-utils@^9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-9.0.2.tgz#a2fdce7ae99533de623f1fe9266b60068cc02f76" - integrity sha512-xNNxbInolf4V2IzR2IXiPRsnxRqRMHf9qTc6n4k3dfHO4tVXFakk9frD4t6i+XgboXLhgQqn1ZMQaGsp8N9Xjw== - dependencies: - "@conveyal/lonlat" "^1.4.1" - "@mapbox/polyline" "^1.1.0" - "@opentripplanner/geocoder" "^1.4.1" - "@styled-icons/foundation" "^10.34.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - chroma-js "^2.4.2" - date-fns "^2.28.0" - date-fns-tz "^1.2.2" - graphql "^16.6.0" - lodash.clonedeep "^4.5.0" - lodash.isequal "^4.5.0" - qs "^6.9.1" - -"@opentripplanner/core-utils@^9.0.3": +"@opentripplanner/core-utils@^9.0.0", "@opentripplanner/core-utils@^9.0.2", "@opentripplanner/core-utils@^9.0.3": version "9.0.3" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-9.0.3.tgz#c1ebdcc3ad5999fb28427102c9be7d7268f6bd37" integrity sha512-8P3Bi41jF7z18P/soo6lEw+nrqarsyGMAxivsF1/kMJdRo4wnakp0zcrVZjDXTxoR6LPtj6Kkuxv3JQFO9jKiw== @@ -2478,10 +2459,10 @@ "@opentripplanner/location-icon" "^1.4.1" flat "^5.0.2" -"@opentripplanner/geocoder@1.4.1", "@opentripplanner/geocoder@^1.4.0", "@opentripplanner/geocoder@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/geocoder/-/geocoder-1.4.1.tgz#ee1aa00ce6938ab7a7b9ed4e0fa894a226352ee1" - integrity sha512-P2tvUkJRYcuT71UMC5MalJuHwVCT+JXw/C/rAVTrwhvFXn+4mceHlBBNLXGzQyzVceBE5lER0xC1PB7xBQeL0w== +"@opentripplanner/geocoder@^1.4.0", "@opentripplanner/geocoder@^1.4.1", "@opentripplanner/geocoder@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/geocoder/-/geocoder-1.4.2.tgz#0f827dffca42c7f7a23063b54990a291dd028b80" + integrity sha512-yzMVrKXEHO6Y50j9kntk1+odvHaqn9K9D4aKJAd+EabhiZckesfScLb0updmWRUloEWjN45nuDSFto8fbU7Uiw== dependencies: "@conveyal/geocoder-arcgis-geojson" "^0.0.3" "@conveyal/lonlat" "^1.4.1" @@ -2555,14 +2536,14 @@ react-resize-detector "^4.2.1" string-similarity "^4.0.4" -"@opentripplanner/location-field@^2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-2.0.6.tgz#449b93b2dcb565a5f994bd92673bff94682bcb1c" - integrity sha512-e/XjqT94gwMN/SxnWPiCQrWppih+0FIs9q5sarIhJNyKMhf6p5V8vnqz8ywI7YSohnkHyKZepow9l6eTLhwS0w== +"@opentripplanner/location-field@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-2.0.7.tgz#a4479041c0c82d8f469076a47bc9874de2330efb" + integrity sha512-yexAnUk4CEnxDhAzTmA4rR4xF7aw18THFhdstf6Z7TdceVUxUdIFJv3Pa6A4IjudURN947hi6euuY+qUU0TBqw== dependencies: "@conveyal/geocoder-arcgis-geojson" "^0.0.3" - "@opentripplanner/core-utils" "^9.0.0" - "@opentripplanner/geocoder" "1.4.1" + "@opentripplanner/core-utils" "^9.0.2" + "@opentripplanner/geocoder" "^1.4.2" "@opentripplanner/humanize-distance" "^1.2.0" "@opentripplanner/location-icon" "^1.4.1" "@styled-icons/fa-solid" "^10.34.0"