diff --git a/i18n/en-US.yml b/i18n/en-US.yml
index 81d7aee92..f94236acb 100644
--- a/i18n/en-US.yml
+++ b/i18n/en-US.yml
@@ -269,9 +269,6 @@ components:
nonTransit: Alternative options
DeleteUser:
deleteMyAccount: Delete my account
- EnhancedStopMarker:
- stopID: "Stop ID:"
- stopViewer: Stop Viewer
ErrorMessage:
header: Could Not Plan Trip
warning: Warning
diff --git a/i18n/es.yml b/i18n/es.yml
index 5cb005567..8ddaa7901 100644
--- a/i18n/es.yml
+++ b/i18n/es.yml
@@ -274,9 +274,6 @@ components:
nonTransit: Alternativas
DeleteUser:
deleteMyAccount: Eliminar mi cuenta
- EnhancedStopMarker:
- stopID: "Parada:"
- stopViewer: Visor de paradas
ErrorMessage:
header: No se pudo planificar el viaje
warning: Advertencia
diff --git a/i18n/fr.yml b/i18n/fr.yml
index a9d07c904..f034e6b44 100644
--- a/i18n/fr.yml
+++ b/i18n/fr.yml
@@ -282,9 +282,6 @@ components:
nonTransit: Alternatives
DeleteUser:
deleteMyAccount: Supprimer mon compte
- EnhancedStopMarker:
- stopID: Arrêt nº
- stopViewer: Info arrêt
ErrorMessage:
header: Impossible de planifier le trajet
warning: Attention
diff --git a/i18n/ko.yml b/i18n/ko.yml
index 1b339d183..c36644127 100644
--- a/i18n/ko.yml
+++ b/i18n/ko.yml
@@ -240,9 +240,6 @@ components:
multiModeSummary: "{accessMode} + {transitMode}"
DeleteUser:
deleteMyAccount: 내 계정 삭제
- EnhancedStopMarker:
- stopID: "정류장 ID:"
- stopViewer: 정류장 뷰어
ErrorMessage:
header: 트립을 계획할 수 없음
warning: 경고
diff --git a/i18n/nb_NO.yml b/i18n/nb_NO.yml
index 8bc2acc78..83be84573 100644
--- a/i18n/nb_NO.yml
+++ b/i18n/nb_NO.yml
@@ -74,9 +74,6 @@ components:
now: Nå
DeleteUser:
deleteMyAccount: Slett kontoen min
- EnhancedStopMarker:
- stopID: "Stopp-ID:"
- stopViewer: Stopp-viser
ErrorMessage:
header: Kunne ikke planlegge tur
warning: Advarsel
diff --git a/i18n/vi.yml b/i18n/vi.yml
index 69f5c7f9e..ba852fde4 100644
--- a/i18n/vi.yml
+++ b/i18n/vi.yml
@@ -256,9 +256,6 @@ components:
multiModeSummary: "{accessMode} + {transitMode}"
DeleteUser:
deleteMyAccount: Xóa tài khoản của tôi
- EnhancedStopMarker:
- stopID: Điểm dừng số
- stopViewer: Xem điểm dừng
ErrorMessage:
header: Không thể lên kế hoạch cho chuyến đi
warning: Cảnh báo
diff --git a/i18n/zh.yml b/i18n/zh.yml
index 58e9a6d0c..021e91a97 100644
--- a/i18n/zh.yml
+++ b/i18n/zh.yml
@@ -240,9 +240,6 @@ components:
multiModeSummary: "{accessMode} + {transitMode}"
DeleteUser:
deleteMyAccount: 删除我的账户
- EnhancedStopMarker:
- stopID: "车站 ID:"
- stopViewer: 车站查看器
ErrorMessage:
header: 无法计划行程
warning: 警告
diff --git a/lib/components/map/connected-park-and-ride-overlay.tsx b/lib/components/map/connected-park-and-ride-overlay.tsx
index bc6a465e7..5ca13ce1e 100644
--- a/lib/components/map/connected-park-and-ride-overlay.tsx
+++ b/lib/components/map/connected-park-and-ride-overlay.tsx
@@ -5,6 +5,7 @@ import React, { useEffect } from 'react'
import { parkAndRideQuery } from '../../actions/api'
import { setLocation } from '../../actions/map'
+import { SetLocationHandler } from '../util/types'
type ParkAndRideParams = {
maxTransitDistance?: number
@@ -14,11 +15,7 @@ type Props = ParkAndRideParams & {
keyboard?: boolean
parkAndRideLocations?: { name: string; x: number; y: number }[]
parkAndRideQuery: (params: ParkAndRideParams) => void
- setLocation: (location: {
- location: Location
- locationType: string
- reverseGeocode: boolean
- }) => void
+ setLocation: SetLocationHandler
}
function ConnectedParkAndRideOverlay(props: Props): JSX.Element {
diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx
index 500f7bcbc..265a5fd50 100644
--- a/lib/components/map/default-map.tsx
+++ b/lib/components/map/default-map.tsx
@@ -47,6 +47,12 @@ const MapContainer = styled.div`
* {
box-sizing: unset;
}
+
+ .maplibregl-popup-content,
+ .mapboxgl-popup-content {
+ border-radius: 10px;
+ box-shadow: 0 3px 14px 4px rgb(0 0 0 / 20%);
+ }
`
/**
* Get the configured display names for the specified company ids.
diff --git a/lib/components/map/enhanced-stop-marker.js b/lib/components/map/enhanced-stop-marker.js
deleted file mode 100644
index ad5d7fa4f..000000000
--- a/lib/components/map/enhanced-stop-marker.js
+++ /dev/null
@@ -1,183 +0,0 @@
-// TYPESCRIPT TODO: all props here are missing types
-/* eslint-disable react/prop-types */
-import {
- Styled as BaseMapStyled,
- MarkerWithPopup
-} from '@opentripplanner/base-map'
-import { connect } from 'react-redux'
-import { FormattedMessage } from 'react-intl'
-import { MapMarker } from '@styled-icons/fa-solid/MapMarker'
-import { Styled as MapPopupStyled } from '@opentripplanner/map-popup'
-import coreUtils from '@opentripplanner/core-utils'
-import FromToLocationPicker from '@opentripplanner/from-to-location-picker'
-import React, { Component } from 'react'
-import styled from 'styled-components'
-import tinycolor from 'tinycolor2'
-
-import * as mapActions from '../../actions/map'
-import * as uiActions from '../../actions/ui'
-import { ComponentContext } from '../../util/contexts'
-import { getModeFromStop, getStopName } from '../../util/viewer'
-
-const { ViewStopButton } = MapPopupStyled
-
-const iconPixels = 32
-const iconPadding = 5
-const caretPixels = iconPixels / 2 + iconPadding
-const borderPixels = (props) => (props?.active ? 3 : 1)
-const leftPixels = (props) => caretPixels / 2 - borderPixels(props) / 2
-const bottomPixels = (props) =>
- -((caretPixels * 1.4142) / 4) - borderPixels(props) + iconPadding / 2
-
-const DEFAULT_COLOR = '#a6a6a6'
-const strokeColor = (props) => (props?.active ? props.mainColor : DEFAULT_COLOR)
-
-const BaseStopIcon = styled.div`
- background: #fff;
- border: ${borderPixels}px solid ${strokeColor};
- border-radius: 50%;
- fill: ${strokeColor};
- height: ${iconPixels}px;
- padding: ${iconPadding}px;
- position: relative;
- width: ${iconPixels}px;
-
- svg {
- max-height: 100%;
- max-width: 100%;
- position: relative;
- z-index: 200;
- }
-
- &::after {
- background: #fff;
- border-bottom: ${borderPixels}px solid ${strokeColor};
- border-right: ${borderPixels}px solid ${strokeColor};
- bottom: ${bottomPixels}px;
- box-sizing: content-box;
- content: '';
- display: block;
- height: ${caretPixels}px;
- left: ${leftPixels}px;
- position: absolute;
- transform: rotate(45deg);
- width: ${caretPixels}px;
- }
-`
-
-class EnhancedStopMarker extends Component {
- static contextType = ComponentContext
-
- onClickView = () => {
- const { setViewedStop, stop } = this.props
- setViewedStop({ stopId: stop.id })
- }
-
- onFromClick = () => {
- this.setLocation('from')
- }
-
- onToClick = () => {
- this.setLocation('to')
- }
-
- setLocation(locationType) {
- const { setLocation, stop } = this.props
- const { lat, lon, name } = stop
- setLocation({
- location: { lat, lon, name },
- locationType,
- reverseGeocode: false
- })
- }
-
- render() {
- const { activeStopId, highlight, languageConfig, modeColors, stop } =
- this.props
- const { id, lat, lon, name } = stop
- const displayedStopId = coreUtils.itinerary.getDisplayedStopId(stop)
- if (!displayedStopId) return null
-
- const mode = getModeFromStop(stop)
- let color = modeColors && modeColors[mode] ? modeColors[mode] : '#121212'
- if (highlight) {
- // Generates a pretty variant of the color
- color = tinycolor(color).monochromatic()[3].toHexString()
- }
-
- const { ModeIcon } = this.context
- const stopIcon = mode ? :
-
- const icon = (
-
- {stopIcon}
-
- )
-
- return (
-
- {name}
-
-
-
-
- {' '}
- {displayedStopId}
-
-
- {languageConfig.stopViewer || (
-
- )}
-
-
-
- {/* The 'Set as [from/to]' ButtonGroup */}
-
-
-
-
- )
- }
- position={[lat, lon]}
- >
- {icon}
-
- )
- }
-}
-
-const mapStateToProps = (state, ownProps) => {
- const { highlightedStop } = state.otp.ui
-
- const transitModes = state.otp.config.modes.transitModes
- const modeColors = {}
- transitModes.forEach((mode) => {
- modeColors[mode.mode] = mode.color
- })
-
- return {
- activeStopId: state.otp.ui.viewedStop?.stopId,
- highlight: highlightedStop === ownProps.stop.id,
- languageConfig: state.otp.config.language,
- modeColors
- }
-}
-
-const mapDispatchToProps = {
- setLocation: mapActions.setLocation,
- setViewedStop: uiActions.setViewedStop
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(EnhancedStopMarker)
diff --git a/lib/components/map/enhanced-stop-marker.tsx b/lib/components/map/enhanced-stop-marker.tsx
new file mode 100644
index 000000000..82beaafa6
--- /dev/null
+++ b/lib/components/map/enhanced-stop-marker.tsx
@@ -0,0 +1,190 @@
+import {
+ Styled as BaseMapStyled,
+ MarkerWithPopup
+} from '@opentripplanner/base-map'
+import { connect } from 'react-redux'
+import { MapMarker } from '@styled-icons/fa-solid/MapMarker'
+import { Stop } from '@opentripplanner/types'
+import coreUtils from '@opentripplanner/core-utils'
+import React, { Component } from 'react'
+import StopPopup from '@opentripplanner/map-popup'
+import styled from 'styled-components'
+import tinycolor from 'tinycolor2'
+
+import * as mapActions from '../../actions/map'
+import * as uiActions from '../../actions/ui'
+import { ComponentContext } from '../../util/contexts'
+import {
+ ConfiguredTransitMode,
+ SetLocationHandler,
+ SetViewedStopHandler
+} from '../util/types'
+import { getModeFromStop, getStopName } from '../../util/viewer'
+
+interface OwnProps {
+ stop: Stop
+}
+
+type ModeColors = Record
+
+interface Props extends OwnProps {
+ activeStopId?: string
+ highlight: boolean
+ modeColors: ModeColors
+ setLocation: SetLocationHandler
+ setViewedStop: SetViewedStopHandler
+}
+
+interface MarkerProps {
+ active?: boolean
+ mainColor?: string
+}
+
+const iconPixels = 32
+const iconPadding = 5
+const caretPixels = iconPixels / 2 + iconPadding
+const borderPixels = (props: MarkerProps) => (props?.active ? 3 : 1)
+const leftPixels = (props: MarkerProps) =>
+ caretPixels / 2 - borderPixels(props) / 2
+const bottomPixels = (props: MarkerProps) =>
+ -((caretPixels * 1.4142) / 4) - borderPixels(props) + iconPadding / 2
+
+const DEFAULT_COLOR = '#a6a6a6'
+const strokeColor = (props: MarkerProps) =>
+ props?.active ? props.mainColor : DEFAULT_COLOR
+
+const BaseStopIcon = styled.div`
+ background: #fff;
+ border: ${borderPixels}px solid ${strokeColor};
+ border-radius: 50%;
+ fill: ${strokeColor};
+ height: ${iconPixels}px;
+ padding: ${iconPadding}px;
+ position: relative;
+ width: ${iconPixels}px;
+
+ svg {
+ max-height: 100%;
+ max-width: 100%;
+ position: relative;
+ z-index: 200;
+ }
+
+ &::after {
+ background: #fff;
+ border-bottom: ${borderPixels}px solid ${strokeColor};
+ border-right: ${borderPixels}px solid ${strokeColor};
+ bottom: ${bottomPixels}px;
+ box-sizing: content-box;
+ content: '';
+ display: block;
+ height: ${caretPixels}px;
+ left: ${leftPixels}px;
+ position: absolute;
+ transform: rotate(45deg);
+ width: ${caretPixels}px;
+ }
+`
+
+const activeContentId = 'enh-stop-popup'
+
+class EnhancedStopMarker extends Component {
+ static contextType = ComponentContext
+
+ onMarkerClick = () => {
+ // Make a copy because getElementsByClassName returns a live collection.
+ const closeButtons = Array.from(
+ document.getElementsByClassName('maplibregl-popup-close-button')
+ )
+ // HACK: If an OTP2 tile stop is right underneath the marker, the tile event handler in OTP-UI
+ // will fire before this one, and the popup from the regular stop layer will appear
+ // (even if we render the StopViewerOverlay after the OTP2 overlays in DefaultMap),
+ // so there will be two (duplicate) stop popups.
+ // We want to show the popup for the enhanced marker instead of the one from the tile handler
+ // because the stop marker has a much larger UI surface than the circle from the tile layer.
+ // FIXME: One case that escapes this trick deals with when the active stop marker is below an inactive stop marker.
+ // When clicking the corner of the inactive stop marker, this handler is not triggered,
+ // and two popups for two different stops will be shown.
+ closeButtons.forEach((btn) => {
+ const popup = btn.parentElement?.firstChild?.firstChild as HTMLElement
+ if (popup?.id !== activeContentId) {
+ const button = btn as HTMLButtonElement
+ button.click()
+ }
+ })
+ }
+
+ render() {
+ const {
+ activeStopId,
+ highlight,
+ modeColors,
+ setLocation,
+ setViewedStop,
+ stop
+ } = this.props
+ const { id, lat, lon } = stop
+ const displayedStopId = coreUtils.itinerary.getDisplayedStopId(stop)
+ if (!displayedStopId) return null
+
+ const mode = getModeFromStop(stop)
+ let color = modeColors && modeColors[mode] ? modeColors[mode] : '#121212'
+ if (highlight) {
+ // Generates a pretty variant of the color
+ color = tinycolor(color).monochromatic()[3].toHexString()
+ }
+
+ const { ModeIcon } = this.context
+ const stopIcon = mode ? :
+
+ return (
+
+
+
+ )
+ }
+ position={[lat, lon]}
+ >
+
+ {stopIcon}
+
+
+ )
+ }
+}
+
+const mapStateToProps = (state: any, ownProps: OwnProps) => {
+ const { highlightedStop } = state.otp.ui
+
+ const transitModes = state.otp.config.modes.transitModes
+ const modeColors: ModeColors = {}
+ transitModes.forEach((mode: ConfiguredTransitMode) => {
+ modeColors[mode.mode] = mode.color
+ })
+
+ return {
+ activeStopId: state.otp.ui.viewedStop?.stopId,
+ highlight: highlightedStop === ownProps.stop.id,
+ modeColors
+ }
+}
+
+const mapDispatchToProps = {
+ setLocation: mapActions.setLocation,
+ setViewedStop: uiActions.setViewedStop
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(EnhancedStopMarker)
diff --git a/lib/components/map/point-popup.tsx b/lib/components/map/point-popup.tsx
index e8c91cee0..8422aff0d 100644
--- a/lib/components/map/point-popup.tsx
+++ b/lib/components/map/point-popup.tsx
@@ -11,6 +11,7 @@ import type { Location } from '@opentripplanner/types'
import * as mapActions from '../../actions/map'
import { Icon } from '../util/styledIcon'
import { renderCoordinates } from '../../util/i18n'
+import { SetLocationHandler } from '../util/types'
const PopupTitleWrapper = styled.div`
align-items: flex-start;
@@ -34,7 +35,7 @@ const ZoomButton = styled.button`
type Props = {
clearMapPopupLocation: () => void
mapPopupLocation: Location
- setLocation: (arg: unknown) => void
+ setLocation: SetLocationHandler
zoomToPlace: (
map?: MapRef,
place?: { lat: number; lon: number },
diff --git a/lib/components/mobile/stop-viewer.tsx b/lib/components/mobile/stop-viewer.tsx
index f48553786..628d5b42d 100644
--- a/lib/components/mobile/stop-viewer.tsx
+++ b/lib/components/mobile/stop-viewer.tsx
@@ -3,6 +3,7 @@ import { useIntl } from 'react-intl'
import React, { useCallback } from 'react'
import * as uiActions from '../../actions/ui'
+import { SetViewedStopHandler } from '../util/types'
import DefaultMap from '../map/default-map'
import StopViewer from '../viewers/stop-viewer'
@@ -10,7 +11,7 @@ import MobileContainer from './container'
import MobileNavigationBar from './navigation-bar'
interface Props {
- setViewedStop: (payload: { stopId: string } | null) => void
+ setViewedStop: SetViewedStopHandler
}
const MobileStopViewer = ({ setViewedStop }: Props) => {
diff --git a/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx
index 884caa8ef..1ffc8b3a2 100644
--- a/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx
+++ b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx
@@ -4,15 +4,16 @@ import React, { Component } from 'react'
import TransitLegSubheader from '@opentripplanner/itinerary-body/lib/otp-react-redux/transit-leg-subheader'
import { setMainPanelContent, setViewedStop } from '../../../actions/ui'
+import { SetViewedStopHandler } from '../../util/types'
interface Props {
leg: Leg
setMainPanelContent: (content: number | null) => void
- setViewedStop: (payload: { stopId: string }) => void
+ setViewedStop: SetViewedStopHandler
}
class ConnectedTransitLegSubheader extends Component {
- onClick = (payload: { stopId: string }) => {
+ onClick: SetViewedStopHandler = (payload) => {
const { setMainPanelContent, setViewedStop } = this.props
setMainPanelContent(null)
setViewedStop(payload)
diff --git a/lib/components/util/types.ts b/lib/components/util/types.ts
index febaa585d..8988c7126 100644
--- a/lib/components/util/types.ts
+++ b/lib/components/util/types.ts
@@ -1,4 +1,4 @@
-import { Route } from '@opentripplanner/types'
+import { MapLocationActionArg, Route } from '@opentripplanner/types'
// TYPESCRIPT TODO: move this to a larger shared types file, preferably within otp-ui
export interface StopData {
@@ -92,4 +92,13 @@ export interface ViewedRouteObject extends Route {
export type SetViewedRouteHandler = (route?: ViewedRouteState) => void
-export type SetViewedStopHandler = (payload: { stopId: string }) => void
+export type SetViewedStopHandler = (payload: { stopId: string } | null) => void
+
+export type SetLocationHandler = (payload: MapLocationActionArg) => void
+
+export interface ConfiguredTransitMode {
+ color?: string
+ label?: string
+ mode: string
+ showWheelchairSetting?: boolean
+}