Skip to content

Commit

Permalink
Merge pull request #1276 from opentripplanner/pass-operator-logos-to-…
Browse files Browse the repository at this point in the history
…map-popup

Pass transit operator logos to map-popup
  • Loading branch information
amy-corson-ibigroup authored Oct 4, 2024
2 parents 4848925 + a0d87c5 commit 17598ad
Show file tree
Hide file tree
Showing 9 changed files with 4,032 additions and 197 deletions.
3,963 changes: 3,829 additions & 134 deletions __tests__/components/viewers/__snapshots__/nearby-view.js.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ exports[`components > viewers > stop viewer should render with initial stop id a
</div>
<styled.div>
<div
className="sc-liccgK jihWTk"
className="sc-cuWdqJ jtAZHv"
>
<styled.div>
<div
Expand Down
49 changes: 44 additions & 5 deletions lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,13 @@ export const fetchNearby = (position, radius) => {
export const findStopTimesForStop = (params) =>
function (dispatch, getState) {
dispatch(fetchingStopTimesForStop(params))
const { date, stopId } = params
const { date, onlyRequestForOperators, stopId } = params
const timeZone = getState().otp.config.homeTimezone

// Create a service date timestamp from 3:30am local.
const serviceDay = getServiceStart(date, timeZone).getTime() / 1000

return dispatch(
createGraphQLQueryAction(
`query StopTimes(
const fullStopTimesQuery = `query StopTimes(
$serviceDay: Long!
$stopId: String!
) {
Expand Down Expand Up @@ -567,7 +565,48 @@ export const findStopTimesForStop = (params) =>
}
}
}
}`,
}`

const shorterStopTimesQueryForOperators = `query StopTimes(
$stopId: String!
) {
stop(id: $stopId) {
gtfsId
code
routes {
id: gtfsId
agency {
gtfsId
name
}
patterns {
id
headsign
}
}
stoptimesForPatterns(numberOfDepartures: 100, omitNonPickups: true, omitCanceled: false) {
pattern {
desc: name
headsign
id: code
route {
agency {
gtfsId
}
gtfsId
}
}
}
}
}`

const query = onlyRequestForOperators
? shorterStopTimesQueryForOperators
: fullStopTimesQuery

return dispatch(
createGraphQLQueryAction(
query,
{
serviceDay,
stopId
Expand Down
18 changes: 17 additions & 1 deletion lib/components/map/default-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// @ts-nocheck
import { connect } from 'react-redux'
import { GeolocateControl, NavigationControl } from 'react-map-gl'
import { getCurrentDate } from '@opentripplanner/core-utils/lib/time'
import { injectIntl } from 'react-intl'
import BaseMap from '@opentripplanner/base-map'
import generateOTP2TileLayers from '@opentripplanner/otp2-tile-overlay'
Expand All @@ -13,6 +14,7 @@ import {
assembleBasePath,
bikeRentalQuery,
carRentalQuery,
findStopTimesForStop,
vehicleRentalQuery
} from '../../actions/api'
import { ComponentContext } from '../../util/contexts'
Expand All @@ -22,6 +24,7 @@ import { MainPanelContent } from '../../actions/ui-constants'
import { setLocation, setMapPopupLocationAndGeocode } from '../../actions/map'
import { setViewedStop } from '../../actions/ui'
import { updateOverlayVisibility } from '../../actions/config'
import TransitOperatorIcons from '../util/connected-transit-operator-icons'

import ElevationPointMarker from './elevation-point-marker'
import EndpointsOverlay from './connected-endpoints-overlay'
Expand Down Expand Up @@ -153,6 +156,17 @@ class DefaultMap extends Component {
}
}

// Generate operator logos to pass through OTP tile layer to map-popup
getEntityPrefix = (entity) => {
const stopId = entity.gtfsId
this.props.findStopTimesForStop({
date: getCurrentDate(),
onlyRequestForOperators: true,
stopId
})
return <TransitOperatorIcons stopId={stopId} />
}

/**
* Checks whether the modes have changed between old and new queries and
* whether to update the map overlays accordingly (e.g., to show rental vehicle
Expand Down Expand Up @@ -407,7 +421,8 @@ class DefaultMap extends Component {
setLocation,
setViewedStop,
viewedRouteStops,
config.companies
config.companies,
this.getEntityPrefix
)
default:
return null
Expand Down Expand Up @@ -468,6 +483,7 @@ const mapStateToProps = (state) => {
const mapDispatchToProps = {
bikeRentalQuery,
carRentalQuery,
findStopTimesForStop,
getCurrentPosition,
setLocation,
setMapPopupLocationAndGeocode,
Expand Down
38 changes: 38 additions & 0 deletions lib/components/util/connected-transit-operator-icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { connect } from 'react-redux'
import { TransitOperator } from '@opentripplanner/types'
import React from 'react'

import { AppReduxState } from '../../util/state-types'
import { FETCH_STATUS } from '../../util/constants'

import { StopData } from './types'
import TransitOperatorLogos from './transit-operator-icons'

interface Props {
stopData?: StopData
transitOperators: TransitOperator[]
}

function TransitOperatorIcons({ stopData, transitOperators }: Props) {
const loading = stopData?.fetchStatus === FETCH_STATUS.FETCHING
return (
<TransitOperatorLogos
loading={loading}
stopData={stopData}
transitOperators={transitOperators}
/>
)
}

const mapStateToProps = (
state: AppReduxState,
ownProps: Props & { stopId: string }
) => {
const stops = state.otp.transitIndex.stops
return {
stopData: stops?.[ownProps.stopId],
transitOperators: state.otp.config.transitOperators || []
}
}

export default connect(mapStateToProps)(TransitOperatorIcons)
16 changes: 14 additions & 2 deletions lib/components/util/operator-logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@ import { TransitOperator } from '@opentripplanner/types'
import React from 'react'
import styled from 'styled-components'

const OperatorImg = styled.img`
const OperatorImg = styled.img<{ marginRight?: number; maxHeight?: number }>`
&:not(:last-of-type) {
margin-right: 0.5ch;
}
width: 25px;
`

const StyledOperatorImg = styled(OperatorImg)`
margin-right: 0.5ch;
max-height: 1em;
// Make sure icons stay square
max-width: 1em;
`

type Props = {
alt?: string
operator?: TransitOperator
styled?: boolean
}

const OperatorLogo = ({ alt, operator }: Props): JSX.Element | null => {
const OperatorLogo = ({ alt, operator, styled }: Props): JSX.Element | null => {
if (!operator?.logo) return null
if (styled) {
return <StyledOperatorImg alt={alt || operator.name} src={operator.logo} />
}

return <OperatorImg alt={alt || operator.name} src={operator.logo} />
}

Expand Down
83 changes: 83 additions & 0 deletions lib/components/util/transit-operator-icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { MapPin } from '@styled-icons/fa-solid'
import { useIntl } from 'react-intl'
import React from 'react'
import Skeleton from 'react-loading-skeleton'
import type { TransitOperator } from '@opentripplanner/types'

import InvisibleA11yLabel from './invisible-a11y-label'
import OperatorLogo from './operator-logo'
import type { StopData } from './types'

const Operator = ({ operator }: { operator?: TransitOperator }) => {
const intl = useIntl()

if (!operator) {
return null
} else {
const operatorLogoAriaLabel = intl.formatMessage(
{
id: 'components.StopViewer.operatorLogoAriaLabel'
},
{
operatorName: operator.name
}
)
return operator.logo ? (
// Span with agency classname allows optional contrast/customization in user
// config for logos with poor contrast. Class name is hyphenated agency name
// e.g. "sound-transit"
<span
className={
operator.name ? operator.name.replace(/\s+/g, '-').toLowerCase() : ''
}
>
<OperatorLogo alt={operatorLogoAriaLabel} operator={operator} styled />
</span>
) : (
// If operator exists but logo is missing,
// we still need to announce the operator name to screen readers.
<>
<MapPin />
<InvisibleA11yLabel>{operatorLogoAriaLabel}</InvisibleA11yLabel>
</>
)
}
}

const TransitOperatorLogos = ({
loading = false,
stopData,
transitOperators
}: {
loading?: boolean
stopData?: StopData
transitOperators?: TransitOperator[]
}): JSX.Element => {
const agencies =
(stopData &&
stopData.stoptimesForPatterns?.reduce<Set<string>>((prev, cur) => {
// @ts-expect-error The agency type is not yet compatible with OTP2
const agencyGtfsId = cur.pattern.route.agency?.gtfsId
return agencyGtfsId ? prev.add(agencyGtfsId) : prev
}, new Set())) ||
new Set()

return (
<>
{loading ? (
<Skeleton height={20} style={{ marginRight: '0.5ch' }} width={20} />
) : (
transitOperators
?.filter((to) => Array.from(agencies).includes(to.agencyId))
// Second pass to remove duplicates based on name
.filter(
(to, index, arr) =>
index === arr.findIndex((t) => t?.name === to?.name)
)
.map((to) => <Operator key={to.agencyId} operator={to} />)
)}
</>
)
}

export default TransitOperatorLogos
59 changes: 6 additions & 53 deletions lib/components/viewers/nearby/stop-card-header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { connect } from 'react-redux'
import { FormattedMessage, useIntl } from 'react-intl'
import { MapPin } from '@styled-icons/fa-solid'
import { Search } from '@styled-icons/fa-solid/Search'
import { TransitOperator } from '@opentripplanner/types'
import React, { ComponentType } from 'react'
Expand All @@ -10,8 +9,8 @@ import { Icon, IconWithText } from '../../util/styledIcon'
import { StopData } from '../../util/types'
import InvisibleA11yLabel from '../../util/invisible-a11y-label'
import Link from '../../util/link'
import OperatorLogo from '../../util/operator-logo'
import Strong from '../../util/strong-text'
import TransitOperatorLogos from '../../util/transit-operator-icons'

import { CardBody, CardHeader, CardTitle } from './styled'
import DistanceDisplay from './distance-display'
Expand All @@ -28,41 +27,6 @@ type Props = {
transitOperators?: TransitOperator[]
}

const Operator = ({ operator }: { operator?: TransitOperator }) => {
const intl = useIntl()
if (!operator) {
return null
} else {
const operatorLogoAriaLabel = intl.formatMessage(
{
id: 'components.StopViewer.operatorLogoAriaLabel'
},
{
operatorName: operator.name
}
)
return operator.logo ? (
// Span with agency classname allows optional contrast/customization in user
// config for logos with poor contrast. Class name is hyphenated agency name
// e.g. "sound-transit"
<span
className={
operator.name ? operator.name.replace(/\s+/g, '-').toLowerCase() : ''
}
>
<OperatorLogo alt={operatorLogoAriaLabel} operator={operator} />
</span>
) : (
// If operator exists but logo is missing,
// we still need to announce the operator name to screen readers.
<>
<MapPin />
<InvisibleA11yLabel>{operatorLogoAriaLabel}</InvisibleA11yLabel>
</>
)
}
}

const StopCardHeader = ({
actionIcon,
actionParams,
Expand All @@ -75,12 +39,7 @@ const StopCardHeader = ({
transitOperators
}: Props): JSX.Element => {
const intl = useIntl()
const agencies =
stopData.stoptimesForPatterns?.reduce<Set<string>>((prev, cur) => {
// @ts-expect-error The agency type is not yet compatible with OTP2
const agencyGtfsId = cur.pattern.route.agency?.gtfsId
return agencyGtfsId ? prev.add(agencyGtfsId) : prev
}, new Set()) || new Set()

const zoomButtonText = onZoomClick
? intl.formatMessage({
id: 'components.StopViewer.zoomToStop'
Expand All @@ -92,16 +51,10 @@ const StopCardHeader = ({
<CardHeader>
{/* @ts-expect-error The 'as' prop in styled-components is not listed for TypeScript. */}
<CardTitle as={titleAs}>
{transitOperators
?.filter((to) => Array.from(agencies).includes(to.agencyId))
// Second pass to remove duplicates based on name
.filter(
(to, index, arr) =>
index === arr.findIndex((t) => t?.name === to?.name)
)
.map((to) => (
<Operator key={to.agencyId} operator={to} />
))}
<TransitOperatorLogos
stopData={stopData}
transitOperators={transitOperators}
/>
<span>{stopData.name}</span>
</CardTitle>
<DistanceDisplay distance={stopData.distance} />
Expand Down
1 change: 0 additions & 1 deletion lib/components/viewers/nearby/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const CardTitle = styled.p`
display: flex;
font-size: 22px;
font-weight: 600;
gap: 0.5ch;
grid-column: 1;
margin: 0;
/* Prevent svg and images to be taller than the text. */
Expand Down

0 comments on commit 17598ad

Please sign in to comment.