Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4a12934
fix(connected-route-viewer-overlay): don't crash on viewing route wit…
miles-grant-ibigroup Oct 15, 2021
4220102
refactor(connected-park-and-ride-overlay): avoid crash on empty Park …
miles-grant-ibigroup Oct 18, 2021
d1d8528
feature(default-itinerary): add flex itinerary notice
miles-grant-ibigroup Oct 19, 2021
7a3293e
refactor(tabbed-itineraries): show flex status
miles-grant-ibigroup Oct 19, 2021
fd44b48
refactor(tabbed-itineraries): move to a11y compliant red
miles-grant-ibigroup Oct 19, 2021
2d68378
refactor(tabbed-itineraries): move to a11y compliant red
miles-grant-ibigroup Oct 19, 2021
2a700d5
refactor(tabbed-itineraries): update to match mockups
miles-grant-ibigroup Oct 20, 2021
23087cd
improvement(default-itinerary): add dynamic flex notice
miles-grant-ibigroup Oct 20, 2021
26b4e9c
refactor(flex-indicator): internationalize messages
miles-grant-ibigroup Oct 21, 2021
70e83a7
chore(i18n/en-US): add flex strings
miles-grant-ibigroup Oct 21, 2021
98a7a4c
Merge branch 'dev' into support-gtfs-flex
miles-grant-ibigroup Oct 21, 2021
99be28a
refactor(flex-indicator): set variable names to match logic
miles-grant-ibigroup Oct 21, 2021
19f53db
refactor(connected-park-and-ride-overlay): make overlay more robust t…
miles-grant-ibigroup Oct 21, 2021
eeec5c8
refactor(example-config): add flex strings
miles-grant-ibigroup Oct 21, 2021
f37cce2
improvement(flex-indicator): support scrolling environments
miles-grant-ibigroup Oct 26, 2021
391bfe1
improvement(flex-indicator): support shrinking flex indicator
miles-grant-ibigroup Oct 26, 2021
42103cd
feat(actions/api): route only flex requests to otp2 server
miles-grant-ibigroup Oct 26, 2021
a0ce199
improvement(flex-indicator): display flex phone number from bookingInfo
miles-grant-ibigroup Nov 1, 2021
e550df2
refactor: adjust to latest core-utils changes
miles-grant-ibigroup Nov 5, 2021
51cd157
Merge branch 'dev' into support-gtfs-flex
miles-grant-ibigroup Nov 5, 2021
61d4a11
refactor(default-itinerary): don't crash on missing bookingInfo
miles-grant-ibigroup Nov 9, 2021
be71977
fix(default-itinerary): show detailsHint at correct spot
miles-grant-ibigroup Nov 9, 2021
690dadd
fix(default-itinerary): don't shrink flexIndicator
miles-grant-ibigroup Nov 9, 2021
28debeb
improvement(actions/api): re-attempt failed calls with otp2 instance …
miles-grant-ibigroup Nov 9, 2021
0ae72fb
Merge branch 'otp-1-and-2-in-parallel' into support-gtfs-flex
miles-grant-ibigroup Nov 12, 2021
fa1112d
chore(deps): update otp-ui
miles-grant-ibigroup Nov 19, 2021
e40c5ae
Merge branch 'dev' into support-gtfs-flex
miles-grant-ibigroup Nov 19, 2021
2a1e3db
chore(i18n): Match PR changes to PR file.
binh-dam-ibigroup Nov 22, 2021
debeffc
chore(i18n): Add with-constraint comment to call ahead msg.
binh-dam-ibigroup Nov 22, 2021
d755e4f
refactor(default-itinerary): address pr feedback
miles-grant-ibigroup Nov 23, 2021
838eb0b
Merge branch 'support-gtfs-flex' of github.com:opentripplanner/otp-re…
miles-grant-ibigroup Nov 23, 2021
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
13 changes: 13 additions & 0 deletions example-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ api:
host: http://localhost
path: /otp/routers/default
port: 8001

# Support OTP-2 in parallel for certain requests
# api_v2:
# host: http://localhost
# path: /otp/routers/default
# port: 8002

# Add suggested locations to be shown as options in the form view.
# locations:
# - id: 'airport'
Expand Down Expand Up @@ -339,6 +346,12 @@ itinerary:
# accessModes:
# bikeshare: Blue Bike
# config:
# menuItems:
# demo-item: Demo Item
# flex:
# flex-service: Flex Service
# call-ahead: "Call to reserve: (555) 555-5555"
# continuous-dropoff: Communicate with operator about stop
# acessibilityScore:
# gradationMap:
# 0.0: 'Not Accessible'
Expand Down
2 changes: 2 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ components:
trips: Trips
TabbedItineraries:
optionNumber: "Option {optionNum, number}"
# Note to translator: This text is width-constrained.
mustCallAhead: Must call ahead!
fareCost: >
{hasMaxFare, select,
true {{minTotalFare}+}
Expand Down
2 changes: 2 additions & 0 deletions i18n/fr-FR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ components:
trips: Trajets
TabbedItineraries:
optionNumber: "Option {optionNum, number}"
# Note to translator: This text is width-constrained.
mustCallAhead: Résa. obligatoire
fareCost: >
{hasMaxFare, select,
true {À partir de {minTotalFare}}
Expand Down
14 changes: 12 additions & 2 deletions lib/actions/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,15 @@ function getOtpFetchOptions (state, includeToken = false) {
function constructRoutingQuery (state, ignoreRealtimeUpdates, injectedParams = {}) {
const { config, currentQuery } = state.otp
const routingType = currentQuery.routingType
const routingMode = injectedParams?.mode || currentQuery.mode

// Check for routingType-specific API config; if none, use default API
const rt = config.routingTypes && config.routingTypes.find(rt => rt.key === routingType)
const api = (rt && rt.api) || config.api

// Certain requests will require OTP-2. If an OTP-2 host is specified, set it to be used
const useOtp2 = !!config.api_v2 && routingMode.includes('FLEX')

const api = (rt && rt.api) || (useOtp2 && config.api_v2) || config.api
const planEndpoint = `${api.host}${api.port
? ':' + api.port
: ''}${api.path}/plan`
Expand Down Expand Up @@ -922,6 +928,10 @@ function createQueryAction (endpoint, responseAction, errorAction, options = {})
try {
const response = await fetch(url, getOtpFetchOptions(state))
if (response.status >= 400) {
// If a second endpoint is configured, try that before failing
if (!!config.api_v2 && !options.v2) {
return dispatch(createQueryAction(endpoint, responseAction, errorAction, {...options, v2: true}))
}
const error = new Error('Received error from server')
error.response = response
throw error
Expand Down Expand Up @@ -961,7 +971,7 @@ function makeApiUrl (config, endpoint, options) {
console.log('Using alt service for ' + options.serviceId)
url = config.alternateTransitIndex.apiRoot + endpoint
} else {
const api = config.api
const api = options.v2 ? config.api_v2 : config.api
url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}`
}
return url
Expand Down
10 changes: 8 additions & 2 deletions lib/components/map/connected-park-and-ride-overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ class ConnectedParkAndRideOverlay extends Component {
// connect to the redux store

const mapStateToProps = (state, ownProps) => {
const { locations } = state.otp.overlay?.parkAndRide

// object type indicates error
if (typeof locations === 'object') {
return {}
}

return {
parkAndRideLocations: state.otp.overlay.parkAndRide &&
state.otp.overlay.parkAndRide.locations
parkAndRideLocations: locations
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/components/map/connected-route-viewer-overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const mapStateToProps = (state, ownProps) => {
viewedRoute && state.otp.transitIndex.routes
? state.otp.transitIndex.routes[viewedRoute.routeId]
: null
let filteredPatterns = routeData?.patterns
let filteredPatterns = routeData?.patterns || []

// If a pattern is selected, hide all other patterns
if (viewedRoute?.patternId && routeData?.patterns) {
Expand Down
48 changes: 35 additions & 13 deletions lib/components/narrative/default/default-itinerary.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../../../util/accessibility-routing'
import Icon from '../../util/icon'

import { FlexIndicator } from './flex-indicator'
import ItinerarySummary from './itinerary-summary'

const { isBicycle, isMicromobility, isTransit } = coreUtils.itinerary
Expand Down Expand Up @@ -209,7 +210,21 @@ class DefaultItinerary extends NarrativeItinerary {
format: timeFormat,
offset: coreUtils.itinerary.getTimeZoneOffset(itinerary)
}
const isFlexItinerary = itinerary.legs.some(coreUtils.itinerary.isFlex)
const isCallAhead = itinerary.legs.some(coreUtils.itinerary.isReservationRequired)
const isContinuousDropoff = itinerary.legs.some(coreUtils.itinerary.isContinuousDropoff)

// Use first leg's agency as a fallback
const agency = itinerary.legs.map(leg => leg?.agencyName).filter(name => !!name)[0]
let phone = `contact ${agency}`

if (isCallAhead) {
// Picking 0 ensures that if multiple flex legs with
// different phone numbers, the first leg is prioritized
phone = itinerary.legs
.map((leg) => leg.pickupBookingInfo?.contactInfo?.phoneNumber)
.filter((number) => !!number)[0]
}
return (
<div
className={`option default-itin${active ? ' active' : ''}${
Expand All @@ -226,7 +241,22 @@ class DefaultItinerary extends NarrativeItinerary {
>
<div className='title'>
<ItineraryDescription itinerary={itinerary} />
<ItinerarySummary itinerary={itinerary} LegIcon={LegIcon} />
{itineraryHasAccessibilityScores(itinerary) && (
<AccessibilityRating
gradationMap={accessibilityScoreGradationMap}
large
score={getAccessibilityScoreForItinerary(itinerary)}
/>
)}
</div>
{isFlexItinerary && (
<FlexIndicator
isCallAhead={isCallAhead}
isContinuousDropoff={isContinuousDropoff}
phoneNumber={phone}
/>
)}
<ul className='list-unstyled itinerary-attributes'>
{ITINERARY_ATTRIBUTES.sort((a, b) => {
const aSelected = this._isSortingOnAttribute(a)
Expand Down Expand Up @@ -254,20 +284,7 @@ class DefaultItinerary extends NarrativeItinerary {
)
})}
</ul>
<ItinerarySummary itinerary={itinerary} LegIcon={LegIcon} />
{itineraryHasAccessibilityScores(itinerary) && (
<AccessibilityRating
gradationMap={accessibilityScoreGradationMap}
large
score={getAccessibilityScoreForItinerary(itinerary)}
/>
)}
<FieldTripGroupSize itinerary={itinerary} />
{active && !expanded && (
<DetailsHint>
<FormattedMessage id='components.DefaultItinerary.clickDetails' />
</DetailsHint>
)}
</button>
{active && expanded && (
<>
Expand All @@ -281,6 +298,11 @@ class DefaultItinerary extends NarrativeItinerary {
/>
</>
)}
{active && !expanded && (
<DetailsHint>
<FormattedMessage id='components.DefaultItinerary.clickDetails' />
</DetailsHint>
)}
</div>
)
}
Expand Down
99 changes: 99 additions & 0 deletions lib/components/narrative/default/flex-indicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Craco will require this. FIXME: remove line once migrated to craco
// eslint-disable-next-line no-unused-vars
import { barberPole } from '@opentripplanner/itinerary-body/lib/otp-react-redux/line-column-content'
import { FormattedMessage } from 'react-intl'
import React from 'react'
import styled from 'styled-components'
import tinycolor from 'tinycolor2'

import Icon from '../../util/icon'

export const FLEX_COLOR = '#FA6400'
const FLEX_COLOR_LIGHT = tinycolor(FLEX_COLOR).lighten(40).toHexString()

const FlexNotice = ({ faKey, showText, text }) => (
<>
<Icon name={faKey} />
{showText && <p>{text}</p>}
</>
)

const FlexIndicatorWrapper = styled.div`
background: ${FLEX_COLOR_LIGHT};
border-bottom-right-radius: 8px;
border-top-right-radius: 8px;
color: ${FLEX_COLOR};
display: grid;
grid-template-columns: 1fr 2fr 3fr 2fr;
grid-template-rows: 1fr 2fr;
grid-column-gap: ${(props) => (props.shrink ? '8px' : 'inherit')};
height: ${(props) => (props.shrink ? '40px' : '80px')};
margin-right: 1em;
max-width: ${(props) => (props.shrink ? '60px' : '190px')};
padding-right: 0.25em;
padding-top: 0.25em;

/* "Flex Service" text */
h4 {
grid-column: 3 / span 2;
grid-row: 1;
margin: 0;
padding-top: 2px;
text-align: left;
}

/* Icon (phone or hand) */
span {
font-size: 32px;
grid-column: 2;
grid-row: 2;
height: 100%;
width: 100%;
}

/* Description text */
p {
font-size: 13px;
grid-column: 3 / span 2;
grid-row: 2;
text-overflow: ellipsis;
overflow-y: hidden;
}

/* Barber pole at left */
&::before {
background: ${barberPole(FLEX_COLOR)};
content: '';
display: block;
grid-row: 1 / span 2;
height: inherit;
margin-top: -0.25em;
position: relative;
width: 20px;
}
`

export const FlexIndicator = ({ isCallAhead, isContinuousDropoff, phoneNumber, shrink }) => (
<FlexIndicatorWrapper shrink={shrink}>
{!shrink && (
<h4>
<FormattedMessage id='config.flex.flex-service' />
</h4>
)}
{isCallAhead && (
<FlexNotice
faKey='phone'
showText={!shrink}
text={<FormattedMessage id='config.flex.call-ahead' values={{ phoneNumber }} />}
/>
)}
{/* Only show continuous dropoff message if call ahead message isn't shown */}
{isContinuousDropoff && !isCallAhead && (
<FlexNotice
faKey='hand-paper-o'
showText={!shrink}
text={<FormattedMessage id='config.flex.continuous-dropoff' />}
/>
)}
</FlexIndicatorWrapper>
)
17 changes: 11 additions & 6 deletions lib/components/narrative/default/itinerary.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@
.otp .option.default-itin > .header {
font-size: 16px;
cursor: pointer;
display: flex;
justify-content: space-between;
}

.otp .option.default-itin > .header > .title {
font-weight: 500;
font-size: large;
display: inline;
flex: 2;
}

.otp .option.default-itin > .header > .itinerary-attributes {
Expand All @@ -54,23 +57,25 @@
font-size: x-large;
}

.otp .option.default-itin > .header > .summary {
.otp .option.default-itin > .header > .title > .summary {
/* text-align: center; */
margin: 8px 0px;
margin: 0px 0px;
}

.otp .option.default-itin > .header > .summary > .summary-block {
.otp .option.default-itin > .header > .title > .summary > .summary-block {
display: inline-block;
vertical-align: middle;
margin: 0px 2px;
}
.otp .option.default-itin > .header > .title > .summary > .summary-block > svg {
margin-bottom: 2px;
}

.otp .option.default-itin > .header > .summary > .mode-block {
.otp .option.default-itin > .header > .title > .summary > .mode-block {
height: 18px;
width: 18px;
}

.otp .option.default-itin > .header > .summary > .arrow-block {
.otp .option.default-itin > .header > .title > .summary > .arrow-block {
padding-top: 4px;
font-size: 13px;
}
Expand Down
11 changes: 10 additions & 1 deletion lib/components/narrative/tabbed-itineraries.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { ComponentContext } from '../../util/contexts'
import { getTimeFormat } from '../../util/i18n'
import { getActiveSearch, getRealtimeEffects } from '../../util/state'
import FormattedDuration from '../util/formatted-duration'
import Icon from '../util/icon'

import { FLEX_COLOR } from './default/flex-indicator'

const { calculateFares, calculatePhysicalActivity } = coreUtils.itinerary

Expand Down Expand Up @@ -130,6 +133,8 @@ class TabButton extends Component {
// TODO: support non-USD
const transitFare = (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0
const minTotalFare = minTNCFare * 100 + transitFare
const mustCallAhead = itinerary.legs.some(coreUtils.itinerary.isReservationRequired)

if (isActive) classNames.push('selected')
return (
<Button
Expand All @@ -138,12 +143,16 @@ class TabButton extends Component {
onClick={this._onClick}
>
<span className='title'>
{mustCallAhead && <Icon name='volume-control-phone' style={{ color: FLEX_COLOR }} />}
<FormattedMessage
id='components.TabbedItineraries.optionNumber'
values={{optionNum: index + 1}}
values={{ optionNum: index + 1 }}
/>
</span>
<span className='details'>
{mustCallAhead && <span style={{ color: FLEX_COLOR }}>
<FormattedMessage id='components.TabbedItineraries.mustCallAhead' />
</span>}
<span>
{/* The itinerary duration in hrs/mins */}
<FormattedDuration duration={itinerary.duration} />
Expand Down
Loading