Skip to content

Commit

Permalink
Merge pull request #79 from opentripplanner/fix-scooter-placename
Browse files Browse the repository at this point in the history
Fix place names for eScooter rentals
  • Loading branch information
landonreed authored Jul 12, 2019
2 parents 642ac13 + b194bbe commit d886c1e
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 53 deletions.
10 changes: 6 additions & 4 deletions lib/components/form/plan-trip-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class PlanTripButton extends Component {
profileTrip: PropTypes.func
}

static defaultProps = {
disabled: false
}

_onClick = () => {
this.props.routingQuery()
if (typeof this.props.onClick === 'function') this.props.onClick()
Expand All @@ -23,10 +27,8 @@ class PlanTripButton extends Component {

render () {
const { currentQuery, text } = this.props
const disabled = this.props.disabled === undefined
? !currentQuery.from || !currentQuery.to
: this.props.disabled

const locationMissing = !currentQuery.from || !currentQuery.to
const disabled = locationMissing || this.props.disabled
return (
<Button
className='plan-trip-button'
Expand Down
3 changes: 3 additions & 0 deletions lib/components/narrative/default/access-leg.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { distanceString } from '../../../util/distance'
import { getStepInstructions } from '../../../util/itinerary'
import { formatDuration } from '../../../util/time'

/**
* Default access leg component for narrative itinerary.
*/
export default class AccessLeg extends Component {
static propTypes = {
activeStep: PropTypes.number,
Expand Down
2 changes: 1 addition & 1 deletion lib/components/narrative/itinerary-carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ItineraryCarousel extends Component {

render () {
const { activeItinerary, itineraries, itineraryClass, hideHeader, pending, showProfileSummary } = this.props
if (pending) return <Loading />
if (pending) return <Loading small />
if (!itineraries) return null

let views = []
Expand Down
42 changes: 35 additions & 7 deletions lib/components/narrative/line-itin/access-leg-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ import currencyFormatter from 'currency-formatter'
import LegDiagramPreview from '../leg-diagram-preview'

import { distanceString } from '../../../util/distance'
import { getLegModeLabel, getLegIcon, getPlaceName, getStepDirection, getStepStreetName } from '../../../util/itinerary'
import {
getLegModeLabel,
getLegIcon,
getPlaceName,
getStepDirection,
getStepStreetName
} from '../../../util/itinerary'
import { formatDuration, formatTime } from '../../../util/time'
import { isMobile } from '../../../util/ui'

import DirectionIcon from '../../icons/direction-icon'

/**
* Component for access (e.g. walk/bike/etc.) leg in narrative itinerary. This
* particular component is used in the line-itin (i.e., trimet-mod-otp) version
* of the narrative itinerary.
*/
export default class AccessLegBody extends Component {
static propTypes = {
leg: PropTypes.object,
Expand All @@ -32,15 +43,27 @@ export default class AccessLegBody extends Component {
}

render () {
const { customIcons, followsTransit, leg, timeOptions } = this.props
const { config, customIcons, followsTransit, leg, timeOptions } = this.props

if (leg.mode === 'CAR' && leg.hailedCar) {
return <TNCLeg leg={leg} onSummaryClick={this._onSummaryClick} timeOptions={timeOptions} followsTransit={followsTransit} customIcons={customIcons} />
return (
<TNCLeg
config={config}
leg={leg}
onSummaryClick={this._onSummaryClick}
timeOptions={timeOptions}
followsTransit={followsTransit}
customIcons={customIcons} />
)
}

return (
<div className='leg-body'>
<AccessLegSummary leg={leg} onSummaryClick={this._onSummaryClick} customIcons={customIcons} />
<AccessLegSummary
config={config}
leg={leg}
onSummaryClick={this._onSummaryClick}
customIcons={customIcons} />

<div onClick={this._onStepsHeaderClick} className='steps-header'>
{formatDuration(leg.duration)}
Expand All @@ -60,6 +83,7 @@ class TNCLeg extends Component {
render () {
// TODO: ensure that client ID fields are populated
const {
config,
LYFT_CLIENT_ID,
UBER_CLIENT_ID,
customIcons,
Expand All @@ -82,7 +106,11 @@ class TNCLeg extends Component {

<div className='leg-body'>
{/* The icon/summary row */}
<AccessLegSummary leg={leg} onSummaryClick={this.props.onSummaryClick} customIcons={customIcons} />
<AccessLegSummary
config={config}
leg={leg}
onSummaryClick={this.props.onSummaryClick}
customIcons={customIcons} />

{/* The "Book Ride" button */}
<div style={{ marginTop: 10, marginBottom: 10, height: 32, position: 'relative' }}>
Expand Down Expand Up @@ -125,7 +153,7 @@ class TNCLeg extends Component {

class AccessLegSummary extends Component {
render () {
const { customIcons, leg } = this.props
const { config, customIcons, leg } = this.props
return (
<div className='summary leg-description' onClick={this.props.onSummaryClick}>
{/* Mode-specific icon */}
Expand All @@ -136,7 +164,7 @@ class AccessLegSummary extends Component {
{getLegModeLabel(leg)}
{' '}
{leg.distance && <span> {distanceString(leg.distance)}</span>}
{` to ${getPlaceName(leg.to)}`}
{` to ${getPlaceName(leg.to, config.companies)}`}
</div>
</div>
)
Expand Down
1 change: 0 additions & 1 deletion lib/components/narrative/line-itin/itin-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export default class ItineraryBody extends Component {
place={leg.from}
time={leg.startTime}
leg={leg}
previousLeg={i > 0 ? itinerary.legs[i - 1] : null}
legIndex={i}
followsTransit={followsTransit}
{...this.props}
Expand Down
91 changes: 58 additions & 33 deletions lib/components/narrative/line-itin/place-row.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React, { Component, PureComponent } from 'react'
import { connect } from 'react-redux'

import LocationIcon from '../../icons/location-icon'
import ViewStopButton from '../../viewers/view-stop-button'
import { getPlaceName, isTransit } from '../../../util/itinerary'
import {
getCompanyForNetwork,
getModeForPlace,
getPlaceName
} from '../../../util/itinerary'
import { formatTime } from '../../../util/time'

import TransitLegBody from './transit-leg-body'
Expand All @@ -11,7 +16,7 @@ import AccessLegBody from './access-leg-body'
// TODO: make this a prop
const defaultRouteColor = '#008'

export default class PlaceRow extends Component {
class PlaceRow extends Component {
_createLegLine (leg) {
switch (leg.mode) {
case 'WALK': return <div className='leg-line leg-line-walk' />
Expand All @@ -31,9 +36,8 @@ export default class PlaceRow extends Component {

/* eslint-disable complexity */
render () {
const { customIcons, leg, legIndex, place, time, timeOptions, followsTransit, previousLeg } = this.props
const { config, customIcons, leg, legIndex, place, time, timeOptions, followsTransit } = this.props
const stackIcon = (name, color, size) => <i className={`fa fa-${name} fa-stack-1x`} style={{ color, fontSize: size + 'px' }} />

let icon
if (!leg) { // This is the itinerary destination
icon = (
Expand All @@ -57,57 +61,46 @@ export default class PlaceRow extends Component {
</span>
)
}

// NOTE: Previously there was a check for itineraries that changed vehicles
// at a single stop, which would render the stop place the same as the
// interline stop. However, this prevents the user from being able to click
// on the stop viewer in this case, which they may want to do in order to
// check the real-time arrival information for the next leg of their journey.
const interline = leg && leg.interlineWithPreviousLeg
const changeVehicles = previousLeg && previousLeg.to.stopId === leg.from.stopId && isTransit(previousLeg.mode) && isTransit(leg.mode)
const special = interline || changeVehicles
return (
<div className='place-row' key={this.rowKey++}>
<div className='time'>
{time && formatTime(time, timeOptions)}
</div>
<div className='line-container'>
{leg && this._createLegLine(leg) }
<div>{!special && icon}</div>
<div>{!interline && icon}</div>
</div>
<div className='place-details'>
{/* Dot separating interlined segments, if applicable */}
{special && <div className='interline-dot'>&bull;</div>}
{interline && <div className='interline-dot'>&bull;</div>}

{/* The place name */}
<div className='place-name'>
{interline
? <div className='interline-name'>Stay on Board at <b>{place.name}</b></div>
: changeVehicles
? <div className='interline-name'>Change Vehicles at <b>{place.name}</b></div>
: <div>{getPlaceName(place)}</div>
: <div>{getPlaceName(place, config.companies)}</div>
}
</div>

{/* Place subheading: Transit stop */}
{place.stopId && !special && (
{place.stopId && !interline && (
<div className='place-subheader'>
<span>Stop ID {place.stopId.split(':')[1]}</span>
<ViewStopButton stopId={place.stopId} />
</div>
)}

{/* Place subheading: rented bike pickup */}
{leg && leg.rentedBike && (
<div className='place-subheader'>
Pick up shared bike
</div>
{/* Place subheading: rented vehicle (e.g., scooter, bike, car) pickup */}
{leg && (leg.rentedVehicle || leg.rentedBike || leg.rentedCar) && (
<RentedVehicleLeg config={config} leg={leg} />
)}

{/* Place subheading: rented car pickup */}
{leg && leg.rentedCar && (
<div className='place-subheader'>
Pick up {leg.from.networks ? leg.from.networks.join('/') : 'rented car'} {leg.from.name}
</div>
)}

<RentedVehicleLeg leg={leg} />

{/* Show the leg, if present */}
{leg && (
leg.transitLeg
Expand All @@ -121,6 +114,7 @@ export default class PlaceRow extends Component {
)
: (/* This is an access (e.g. walk/bike/etc.) leg */
<AccessLegBody
config={config}
customIcons={customIcons}
followsTransit={followsTransit}
leg={leg}
Expand All @@ -137,6 +131,20 @@ export default class PlaceRow extends Component {
}
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
return {
// Pass config in order to give access to companies definition (used to
// determine proper place names for rental vehicles).
config: state.otp.config
}
}

const mapDispatchToProps = { }

export default connect(mapStateToProps, mapDispatchToProps)(PlaceRow)

/**
* A component to display vehicle rental data. The word "Vehicle" has been used
* because a future refactor is intended to combine car rental, bike rental
Expand All @@ -146,24 +154,41 @@ export default class PlaceRow extends Component {
*/
class RentedVehicleLeg extends PureComponent {
render () {
const {leg} = this.props
if (!leg || !leg.rentedVehicle) return null
const { config, leg } = this.props
const configCompanies = config.companies || []
if (leg.mode === 'WALK') {
return (
<div className='place-subheader'>
Walk vehicle along {leg.from.name}
</div>
)
}

if (leg.rentedVehicleData) {
if (leg.rentedVehicle || leg.rentedBike || leg.rentedCar) {
let pickUpString = 'Pick up'
if (leg.rentedBike) {
// TODO: Special case for TriMet may need to be refactored.
pickUpString += ` shared bike`
} else {
// Add company and vehicle labels.
const companies = leg.from.networks.map(n => getCompanyForNetwork(n, configCompanies))
const companyLabel = companies.map(co => co.label).join('/')
pickUpString += ` ${companyLabel}`
const modeString = getModeForPlace(leg.from)
// Only show vehicle name for car rentals. For bikes and eScooters, these
// IDs/names tend to be less relevant (or entirely useless) in this context.
const vehicleName = leg.rentedCar ? ` ${leg.from.name}` : ''
pickUpString += ` ${modeString}${vehicleName}`
}
// e.g., Pick up REACHNOW rented car XYZNDB OR
// Pick up SPIN eScooter
// Pick up shared bike
return (
<div className='place-subheader'>
Pick up {leg.rentedVehicleData.companies.join('/')} vehicle {leg.from.name}
{pickUpString}
</div>
)
}

// FIXME: Under what conditions would this be returned?
return (
<div className='place-subheader'>
Continue riding from {leg.from.name}
Expand Down
5 changes: 4 additions & 1 deletion lib/components/narrative/loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import Icon from './icon'

export default class Loading extends Component {
render () {
const { small } = this.props
return (
<p
style={{ marginTop: '15px' }}
className='text-center'>
<Icon className='fa-5x fa-spin' type='refresh' />
<Icon
className={`${small ? 'fa-3x' : 'fa-5x'} fa-spin`}
type='refresh' />
</p>
)
}
Expand Down
Loading

0 comments on commit d886c1e

Please sign in to comment.