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 3012c18ed..8ddaa7901 100644
--- a/i18n/es.yml
+++ b/i18n/es.yml
@@ -30,7 +30,8 @@ actions:
No se puede guardar el plan: este plan no se pudo guardar debido a la
falta de capacidad en uno o más vehículos. Por favor, vuelva a planificar
su viaje.
- maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados válidos
+ maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados
+ válidos
saveItinerariesError: "No se pudieron guardar los itinerarios: {err}"
setDateError: "Error al establecer la fecha:"
setGroupSizeError: "No se pudo establecer el tamaño del grupo:"
@@ -52,9 +53,11 @@ actions:
authTokenError: Error al obtener un token de autorización.
confirmDeleteMonitoredTrip: ¿Desea eliminar este viaje?
confirmDeletePlace: ¿Quiere eliminar este lugar?
- emailVerificationResent: El mensaje de verificación de correo electrónico ha sido reenviado.
+ emailVerificationResent: El mensaje de verificación de correo electrónico ha sido
+ reenviado.
genericError: "Se ha encontrado un error: {err}"
- itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado es posible.
+ itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado
+ es posible.
preferencesSaved: Sus preferencias se han guardado.
smsInvalidCode: El código introducido no es válido. Por favor, inténtelo de nuevo.
smsResendThrottled: >-
@@ -106,7 +109,7 @@ common:
"no": "No"
print: Imprimir
save: Guardar
- startOver: Comenzar de nuevo
+ startOver: Reiniciar
submitting: Enviando…
"yes": Sí
itineraryDescriptions:
@@ -240,7 +243,10 @@ components:
destination: destino
origin: origen
planTripTooltip: Planificar viaje
- validationMessage: "Por favor, defina los siguientes campos para planificar un viaje: {issues}"
+ validationMessage: "Por favor, defina los siguientes campos para planificar un
+ viaje: {issues}"
+ invalidModeSelection: No se puede planificar un viaje utilizando los modos seleccionados.
+ Prueba a incluir el transporte publico en la selección de modos.
BeforeSignInScreen:
mainTitle: Iniciando sesión
message: >
@@ -268,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
@@ -542,7 +545,8 @@ components:
header: ¡La sesión está a punto de terminar!
keepSession: Continuar sesión
SimpleRealtimeAnnotation:
- usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo real
+ usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo
+ real
StackedPaneDisplay:
savePreferences: Guardar preferencias
StopScheduleTable:
@@ -605,16 +609,19 @@ components:
travelingAt: Viajando a {milesPerHour}
vehicleName: Vehículo {vehicleNumber}
TripBasicsPane:
- checkingItineraryExistence: Comprobación de la existencia de itinerarios para cada día de la semana…
+ checkingItineraryExistence: Comprobación de la existencia de itinerarios para
+ cada día de la semana…
selectAtLeastOneDay: Por favor, seleccione al menos un día para el seguimiento.
tripDaysPrompt: ¿Qué días hace este viaje?
- tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana indicados anteriormente.
+ tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana
+ indicados anteriormente.
tripNamePrompt: "Por favor, indique un nombre para este viaje:"
tripNotAvailableOnDay: El viaje no está disponible el {repeatedDay}
unsavedChangesExistingTrip: >-
Todavía no ha guardado su viaje. Si abandona la página, los cambios se
perderán.
- unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página, se perderá.
+ unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página,
+ se perderá.
TripNotificationsPane:
advancedSettings: Configuración avanzada
altRouteRecommended: Se recomienda una ruta alternativa o un punto de transferencia
@@ -721,7 +728,7 @@ components:
*** DETALLES TÉCNICOS ***
- reportIssue: Reportar un problema
+ reportIssue: ¿Un problema?
TripViewer:
accessible: Accesible
bicyclesAllowed: Permitido
@@ -774,6 +781,8 @@ components:
switcher: Botón de cambio
WelcomeScreen:
prompt: ¿A donde quiere ir?
+ SequentialPaneDisplay:
+ stepNumber: Paso {step} de {total}
config:
accessModes:
bicycle: Tránsito + Bicicleta Personal
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 f7cf241bb..ba852fde4 100644
--- a/i18n/vi.yml
+++ b/i18n/vi.yml
@@ -5,7 +5,8 @@ actions:
callQuerySaveError: "Lỗi khi lưu trữ các truy vấn cuộc gọi: {err}"
callSaveError: "Không thể lưu cuộc gọi: {err}"
checkSessionError: "Lỗi khi thiết lập phiên ủy quyền: {err}"
- couldNotFindCallError: Không thể tìm thấy cuộc gọi. Đang hủy yêu cầu lưu truy vấn.
+ couldNotFindCallError: Không thể tìm thấy cuộc gọi. Đang hủy yêu cầu lưu truy
+ vấn.
fetchCallsError: "Lỗi khi tìm nạp cuộc gọi: {err}"
queryFetchError: "Lỗi khi tìm nạp các truy vấn: {err}"
fieldTrip:
@@ -28,14 +29,16 @@ actions:
Không thể lưu kế hoạch chuyến đi: Không thể lưu kế hoạch chuyến đi này do
thiếu sức chứa trên một hoặc nhiều xe. Vui lòng lên kế hoạch lại chuyến đi
của bạn.
- maxTripRequestsExceeded: Đã vượt quá số lượng yêu cầu chuyến đi mà không có kết quả hợp lệ
+ maxTripRequestsExceeded: Đã vượt quá số lượng yêu cầu chuyến đi mà không có kết
+ quả hợp lệ
saveItinerariesError: "Không lưu được hành trình: {err}"
setDateError: "Lỗi khi cài đặt ngày:"
setGroupSizeError: "Lỗi khi cài đặt kích thước nhóm:"
setPaymentError: "Lỗi khi cài đặt thông tin thanh toán:"
setRequestStatusError: "Lỗi khi cài đặt trạng thái yêu cầu:"
location:
- geolocationNotSupportedError: Định vị địa lý không được hỗ trợ bởi trình duyệt của bạn
+ geolocationNotSupportedError: Định vị địa lý không được hỗ trợ bởi trình duyệt
+ của bạn
unknownPositionError: Lỗi không xác định khi tìm vị trí
map:
currentLocation: (Vị trí hiện tại)
@@ -46,7 +49,8 @@ actions:
confirmDeletePlace: Bạn có muốn loại bỏ nơi này không?
emailVerificationResent: Thông báo xác minh email đã được gửi lại.
genericError: "Phát sinh lỗi: {err}"
- itineraryExistenceCheckFailed: Lỗi kiểm tra xem chuyến đi được chọn của bạn là có thể.
+ itineraryExistenceCheckFailed: Lỗi kiểm tra xem chuyến đi được chọn của bạn là
+ có thể.
preferencesSaved: Những sở thích của bạn đã được lưu lại.
smsInvalidCode: Mã bạn nhập không hợp lệ. Vui lòng thử lại.
smsResendThrottled: >-
@@ -156,12 +160,14 @@ common:
{} other {# giây}}
components:
A11yPrefs:
- accessibilityRoutingByDefault: Thích những chuyến đi có thể truy cập theo mặc định
+ accessibilityRoutingByDefault: Thích những chuyến đi có thể truy cập theo mặc
+ định
AccountSetupFinishPane:
message: Bạn đã sẵn sàng để bắt đầu lên kế hoạch cho các chuyến đi của bạn.
AddPlaceButton:
addPlace: Thêm địa điểm
- needOriginDestination: Xác định nguồn gốc hoặc đích đến để thêm các địa điểm trung gian
+ needOriginDestination: Xác định nguồn gốc hoặc đích đến để thêm các địa điểm trung
+ gian
tooManyPlaces: Địa điểm trung gian tối đa đạt được
AdvancedOptions:
bannedRoutes: Chọn các tuyến đường bị cấm…
@@ -250,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
@@ -275,7 +278,8 @@ components:
editPlaceGeneric: Chỉnh sửa vị trí
invalidAddress: Vui lòng cài đặt một vị trí cho nơi này.
invalidName: Vui lòng nhập tên cho nơi này.
- nameAlreadyUsed: Bạn đã sử dụng tên này cho một nơi khác. Vui lòng nhập một tên khác.
+ nameAlreadyUsed: Bạn đã sử dụng tên này cho một nơi khác. Vui lòng nhập một tên
+ khác.
placeNotFound: Không tìm thấy địa điểm
placeNotFoundDescription: Xin lỗi, địa điểm được yêu cầu không được tìm thấy.
FormNavigationButtons:
@@ -517,17 +521,22 @@ components:
travelingAt: di chuyển với tốc độ {milesPerHour}
vehicleName: Phương tiện giao thông {vehicleNumber}
TripBasicsPane:
- checkingItineraryExistence: Kiểm tra sự tồn tại của hành trình cho mỗi ngày trong tuần…
+ checkingItineraryExistence: Kiểm tra sự tồn tại của hành trình cho mỗi ngày trong
+ tuần…
selectAtLeastOneDay: Vui lòng chọn ít nhất một ngày để theo dõi.
tripDaysPrompt: Bạn thực hiện chuyến đi này vào những ngày nào?
- tripIsAvailableOnDaysIndicated: Chuyến đi của bạn có sẵn vào những ngày trong tuần như đã nêu ở trên.
+ tripIsAvailableOnDaysIndicated: Chuyến đi của bạn có sẵn vào những ngày trong
+ tuần như đã nêu ở trên.
tripNamePrompt: "Vui lòng cung cấp tên cho chuyến đi này:"
tripNotAvailableOnDay: Chuyến đi không có sẵn vào {repeatedDay}
- unsavedChangesExistingTrip: Bạn chưa lưu chuyến đi của mình. Nếu bạn rời đi, những thay đổi sẽ bị mất.
- unsavedChangesNewTrip: Bạn chưa lưu chuyến đi mới của mình. Nếu bạn rời đi, nó sẽ bị mất.
+ unsavedChangesExistingTrip: Bạn chưa lưu chuyến đi của mình. Nếu bạn rời đi, những
+ thay đổi sẽ bị mất.
+ unsavedChangesNewTrip: Bạn chưa lưu chuyến đi mới của mình. Nếu bạn rời đi, nó
+ sẽ bị mất.
TripNotificationsPane:
advancedSettings: Cài đặt nâng cao
- altRouteRecommended: Một tuyến đường hoặc điểm trung chuyển thay thế được khuyến nghị
+ altRouteRecommended: Một tuyến đường hoặc điểm trung chuyển thay thế được khuyến
+ nghị
delaysAboveThreshold: Có sự chậm trễ hoặc gián đoạn của hơn
howToReceiveAlerts: >
Để nhận thông báo cho các chuyến đi đã lưu của bạn, bật thông báo trong
@@ -536,7 +545,8 @@ components:
notificationsTurnedOff: Thông báo được tắt cho tài khoản của bạn.
notifyViaChannelWhen: "Thông báo cho tôi qua {channel} khi:"
oneHour: 1 tiếng
- realtimeAlertFlagged: Có một cảnh báo thời gian thực được gắn cờ trên hành trình của tôi
+ realtimeAlertFlagged: Có một cảnh báo thời gian thực được gắn cờ trên hành trình
+ của tôi
timeBefore: "{time} trước"
TripStatus:
alerts: "{alerts, plural, one {# cảnh báo!} other {# cảnh báo!}}"
@@ -549,7 +559,8 @@ components:
earlyHeading: >-
Chuyến đi đang diễn ra và sẽ đến sớm hơn {formattedDuration} so với dự
kiến!
- noDataHeading: Chuyến đi đang được tiến hành (không có cập nhật thời gian thực có sẵn).
+ noDataHeading: Chuyến đi đang được tiến hành (không có cập nhật thời gian thực
+ có sẵn).
onTimeHeading: Chuyến đi đang được tiến hành và đúng giờ.
base:
lastCheckedDefaultText: Thời gian được kiểm tra lần cuối không xác định
@@ -592,7 +603,8 @@ components:
tripStartIsEarly: >-
Thời gian bắt đầu chuyến đi đang diễn ra sớm hơn ${duration} so với dự
kiến!
- tripStartsSoonNoUpdates: Chuyến đi đang bắt đầu sớm (không có cập nhật về thời gian thực).
+ tripStartsSoonNoUpdates: Chuyến đi đang bắt đầu sớm (không có cập nhật về thời
+ gian thực).
tripStartsSoonOnTime: Chuyến đi đang bắt đầu sớm và sắp đúng giờ.
TripSummary:
arriveAt: "Đến nơi "
@@ -604,7 +616,7 @@ components:
trình
notificationsDisabled: "Thông báo: Đã tắt"
TripTools:
- copyLink: Sao chép đường dẫn
+ copyLink: Sao chép URL
linkCopied: Đã sao chép
reportEmailSubject: Báo cáo sự cố với OpenTripPlanner
reportEmailTemplate: |
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
+}