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 +}