diff --git a/a11y/a11y.test.js b/a11y/a11y.test.js index 255b0c23a..c1823caf9 100644 --- a/a11y/a11y.test.js +++ b/a11y/a11y.test.js @@ -58,7 +58,7 @@ beforeAll(async () => { }) // Web security is disabled to allow requests to the mock OTP server browser = await puppeteer.launch({ - args: ['--disable-web-security'] + args: ['--disable-web-security', '--no-sandbox'] }) }) diff --git a/example-config.yml b/example-config.yml index 79a242954..d62b241ae 100644 --- a/example-config.yml +++ b/example-config.yml @@ -268,7 +268,11 @@ modes: iconName: wheelchair type: CHECKBOX # Possible options: CHECKBOX, SUBMODE, SLIDER, DROPDOWN transitModes: - - mode: BUS + # Mode can be a string or an array of strings. + - mode: ["TROLLEYBUS", "BUS"] + # When mode is an array an overrideMode must be provided. + # This specifies the displayed mode used for icon and URL param. + overrideMode: BUS label: Bus # A mode color can be added, used throughout the application, # most notably in the enhanced stop viewer bubble diff --git a/i18n/en-US.yml b/i18n/en-US.yml index b2fa5ad40..58c43556a 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -628,6 +628,8 @@ components: oneHour: 1 hour realtimeAlertFlagged: There is a realtime alert flagged on my journey timeBefore: "{time} before" + TripPreviewLayout: + previewTrip: Preview Trip TripStatus: alerts: "{alerts, plural, one {# alert!} other {# alerts!}}" deleteTrip: Delete Trip diff --git a/i18n/fr.yml b/i18n/fr.yml index c3a77e802..2d3e297ee 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -661,6 +661,8 @@ components: oneHour: 1 heure realtimeAlertFlagged: Une alerte en temps réel affecte mon trajet timeBefore: "{time} avant" + TripPreviewLayout: + previewTrip: Aperçu du trajet TripStatus: alerts: "{alerts, plural, =0 {# alerte !} one {# alerte !} other {# alertes !}}" deleteTrip: Supprimer le trajet diff --git a/lib/actions/form.js b/lib/actions/form.js index bfdc65fe7..61ef9b87f 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -43,7 +43,7 @@ export function resetForm(full = false) { const options = getTripOptionsFromQuery(defaultQuery) // Default mode is currently WALK,TRANSIT. We need to update this value // here to match the list of modes, otherwise the form will break. - options.mode = ['WALK', ...transitModes.map((m) => m.mode)].join(',') + options.mode = ['WALK', ...transitModes.flatMap((m) => m.mode)].join(',') dispatch(settingQueryParam(options)) } if (full) { diff --git a/lib/components/app/batch-routing-panel.tsx b/lib/components/app/batch-routing-panel.tsx index 5c6a69c73..6ca1f6da2 100644 --- a/lib/components/app/batch-routing-panel.tsx +++ b/lib/components/app/batch-routing-panel.tsx @@ -3,12 +3,14 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group' import { FormattedMessage, injectIntl, IntlShape } from 'react-intl' import React, { Component, FormEvent } from 'react' +import * as apiActions from '../../actions/api' import { advancedPanelClassName, mainPanelClassName, transitionDuration, TransitionStyles } from '../form/styled' +import { alertUserTripPlan } from '../form/util' import { getActiveSearch, getShowUserSettings } from '../../util/state' import { getPersistenceMode } from '../../util/user' import AdvancedSettingsPanel from '../form/advanced-settings-panel' @@ -22,9 +24,11 @@ import ViewerContainer from '../viewers/viewer-container' interface Props { activeSearch: any + currentQuery: any intl: IntlShape mainPanelContent: number mobile?: boolean + routingQuery: () => void showUserSettings: boolean } @@ -75,7 +79,13 @@ class BatchRoutingPanel extends Component { handleSubmit = (e: FormEvent) => e.preventDefault() handlePlanTripClick = () => { - this.setState({ planTripClicked: true }) + const { currentQuery, intl, routingQuery } = this.props + alertUserTripPlan( + intl, + currentQuery, + () => this.setState({ planTripClicked: true }), + routingQuery + ) } render() { @@ -128,6 +138,7 @@ class BatchRoutingPanel extends Component { > { (state.user.loggedInUser?.hasConsentedToTerms || getPersistenceMode(state.otp.config.persistence).isLocalStorage) const { mainPanelContent } = state.otp.ui + const currentQuery = state.otp.currentQuery return { activeSearch: getActiveSearch(state), + currentQuery, mainPanelContent, showUserSettings } } -export default connect(mapStateToProps)(injectIntl(BatchRoutingPanel)) +const mapDispatchToProps = { + routingQuery: apiActions.routingQuery +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(BatchRoutingPanel)) diff --git a/lib/components/app/print-layout.tsx b/lib/components/app/print-layout.tsx index d0365f015..7d8b2fd4e 100644 --- a/lib/components/app/print-layout.tsx +++ b/lib/components/app/print-layout.tsx @@ -1,65 +1,29 @@ -import { Button } from 'react-bootstrap' import { connect } from 'react-redux' import { FormattedMessage, injectIntl, IntlShape } from 'react-intl' import { Itinerary } from '@opentripplanner/types' -import { Map } from '@styled-icons/fa-solid/Map' -import { Print } from '@styled-icons/fa-solid/Print' -import { Times } from '@styled-icons/fa-solid/Times' -// @ts-expect-error not typescripted yet -import PrintableItinerary from '@opentripplanner/printable-itinerary' import React, { Component } from 'react' import * as apiActions from '../../actions/api' import * as formActions from '../../actions/form' -import { - addPrintViewClassToRootHtml, - clearClassFromRootHtml -} from '../../util/print' -import { ComponentContext } from '../../util/contexts' +import { AppReduxState } from '../../util/state-types' import { getActiveItinerary, getActiveSearch } from '../../util/state' -import { IconWithText } from '../util/styledIcon' import { summarizeQuery } from '../form/user-settings-i18n' +import { User } from '../user/types' import DefaultMap from '../map/default-map' -import PageTitle from '../util/page-title' -import SpanWithSpace from '../util/span-with-space' -import TripDetails from '../narrative/connected-trip-details' + +import TripPreviewLayoutBase from './trip-preview-layout-base' type Props = { // TODO: Typescript activeSearch type activeSearch: any - // TODO: Typescript config type - config: any - currentQuery: any intl: IntlShape itinerary: Itinerary location?: { search?: string } parseUrlQueryString: (params?: any, source?: string) => any - // TODO: Typescript user type - user: any -} - -type State = { - mapVisible?: boolean + user: User } -class PrintLayout extends Component { - static contextType = ComponentContext - - constructor(props: Props) { - super(props) - this.state = { - mapVisible: true - } - } - - _toggleMap = () => { - this.setState({ mapVisible: !this.state.mapVisible }) - } - - _print = () => { - window.print() - } - +class PrintLayout extends Component { _close = () => { window.location.replace(String(window.location).replace('print/', '')) } @@ -67,98 +31,45 @@ class PrintLayout extends Component { componentDidMount() { const { itinerary, location, parseUrlQueryString } = this.props - // Add print-view class to html tag to ensure that iOS scroll fix only applies - // to non-print views. - addPrintViewClassToRootHtml() // Parse the URL query parameters, if present if (!itinerary && location && location.search) { parseUrlQueryString() } - - // TODO: use currentQuery to pan/zoom to the correct part of the map - } - - componentWillUnmount() { - clearClassFromRootHtml() } render() { - const { activeSearch, config, intl, itinerary, user } = this.props - const { LegIcon } = this.context + const { activeSearch, intl, itinerary, user } = this.props const printVerb = intl.formatMessage({ id: 'common.forms.print' }) return ( -
- - {/* The header bar, including the Toggle Map and Print buttons */} -
-
- - - - - - - -
- -
- - {/* The map, if visible */} - {this.state.mapVisible && ( + } + itinerary={itinerary} + mapElement={
{/* FIXME: Improve reframing/setting map bounds when itinerary is received. */}
- )} - - {/* The main itinerary body */} - {itinerary && ( - <> - - - - )} -
+ } + onClose={this._close} + subTitle={ + activeSearch && + summarizeQuery(activeSearch.query, intl, user.savedLocations) + } + title={printVerb} + /> ) } } // connect to the redux store -// TODO: Typescript state -const mapStateToProps = (state: any) => { +const mapStateToProps = (state: AppReduxState) => { const activeSearch = getActiveSearch(state) const { localUser, loggedInUser } = state.user const user = loggedInUser || localUser return { activeSearch, - config: state.otp.config, - currentQuery: state.otp.currentQuery, itinerary: getActiveItinerary(state) as Itinerary, user } diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index dc7d26f72..537e13bc3 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -33,6 +33,7 @@ import BeforeSignInScreen from '../user/before-signin-screen' import Map from '../map/map' import MobileMain from '../mobile/main' import printRoutes from '../../util/webapp-print-routes' +import tripPreviewRoutes from '../../util/webapp-trip-preview-routes' import webAppRoutes from '../../util/webapp-routes' import withLoggedInUserSupport from '../user/with-logged-in-user-support' import withMap from '../map/with-map' @@ -43,7 +44,7 @@ import SessionTimeout from './session-timeout' const { isMobile } = coreUtils.ui -const routes = [...webAppRoutes, ...printRoutes] +const routes = [...webAppRoutes, ...printRoutes, ...tripPreviewRoutes] class ResponsiveWebapp extends Component { static propTypes = { diff --git a/lib/components/app/trip-preview-layout-base.tsx b/lib/components/app/trip-preview-layout-base.tsx new file mode 100644 index 000000000..14fbf080b --- /dev/null +++ b/lib/components/app/trip-preview-layout-base.tsx @@ -0,0 +1,138 @@ +import { Button } from 'react-bootstrap' +import { connect } from 'react-redux' +import { FormattedMessage } from 'react-intl' +import { Itinerary } from '@opentripplanner/types' +import { Map } from '@styled-icons/fa-solid/Map' +import { Print } from '@styled-icons/fa-solid/Print' +import { Times } from '@styled-icons/fa-solid/Times' +// @ts-expect-error not typescripted yet +import PrintableItinerary from '@opentripplanner/printable-itinerary' +import React, { Component, ReactNode } from 'react' + +import { + addPrintViewClassToRootHtml, + clearClassFromRootHtml +} from '../../util/print' +import { AppConfig } from '../../util/config-types' +import { AppReduxState } from '../../util/state-types' +import { ComponentContext } from '../../util/contexts' +import { IconWithText } from '../util/styledIcon' +import PageTitle from '../util/page-title' +import SpanWithSpace from '../util/span-with-space' +import TripDetails from '../narrative/connected-trip-details' + +type Props = { + config: AppConfig + header?: ReactNode + itinerary?: Itinerary + mapElement?: ReactNode + onClose?: () => void + subTitle?: string + title: string +} + +type State = { + mapVisible?: boolean +} + +class TripPreviewLayoutBase extends Component { + static contextType = ComponentContext + + constructor(props: Props) { + super(props) + this.state = { + mapVisible: true + } + } + + _toggleMap = () => { + this.setState({ mapVisible: !this.state.mapVisible }) + } + + _print = () => { + window.print() + } + + componentDidUpdate() { + // Add print-view class to html tag to ensure that iOS scroll fix only applies + // to non-print views. + addPrintViewClassToRootHtml() + } + + componentWillUnmount() { + clearClassFromRootHtml() + } + + render() { + const { + config, + header, + itinerary, + mapElement, + onClose, + subTitle = '', + title + } = this.props + const { LegIcon } = this.context + + return ( +
+ + {/* The header bar, including the Toggle Map and Print buttons */} +
+
+ + + + + + + {onClose && ( + + )} +
+ {header} +
+ + {/* The map, if visible */} + {this.state.mapVisible && mapElement} + + {/* The main itinerary body */} + {itinerary && ( + <> + + + + )} +
+ ) + } +} + +// connect to the redux store + +const mapStateToProps = (state: AppReduxState) => ({ + config: state.otp.config +}) + +export default connect(mapStateToProps)(TripPreviewLayoutBase) diff --git a/lib/components/app/trip-preview-layout.tsx b/lib/components/app/trip-preview-layout.tsx new file mode 100644 index 000000000..f00ff4d4c --- /dev/null +++ b/lib/components/app/trip-preview-layout.tsx @@ -0,0 +1,73 @@ +import { connect } from 'react-redux' +import { RouteComponentProps } from 'react-router' +import { useIntl } from 'react-intl' +import { withAuthenticationRequired } from '@auth0/auth0-react' +import React from 'react' +import styled from 'styled-components' + +import { AppReduxState } from '../../util/state-types' +import { MonitoredTrip } from '../user/types' +import { RETURN_TO_CURRENT_ROUTE } from '../../util/ui' +import SimpleMap from '../map/simple-map' +import withLoggedInUserSupport from '../user/with-logged-in-user-support' + +import TripPreviewLayoutBase from './trip-preview-layout-base' + +type Props = { + monitoredTrip?: MonitoredTrip +} + +const MapContainer = styled.div` + height: 100%; + width: 100%; + + .map { + height: 100%; + width: 100%; + } +` + +const TripPreviewLayout = ({ monitoredTrip }: Props) => { + const intl = useIntl() + const previewTripText = intl.formatMessage({ + id: 'components.TripPreviewLayout.previewTrip' + }) + const itinerary = + monitoredTrip?.journeyState?.matchingItinerary || monitoredTrip?.itinerary + + return ( + + + + } + subTitle={monitoredTrip?.tripName} + title={previewTripText} + /> + ) +} + +// connect to the redux store + +const mapStateToProps = ( + state: AppReduxState, + ownProps: Props & RouteComponentProps<{ id: string }> +) => { + const { loggedInUserMonitoredTrips: trips } = state.user + const tripId = ownProps.match.params.id + + return { + monitoredTrip: trips?.find((trip) => trip.id === tripId) + } +} + +export default withLoggedInUserSupport( + withAuthenticationRequired( + connect(mapStateToProps)(TripPreviewLayout), + RETURN_TO_CURRENT_ROUTE + ), + true +) diff --git a/lib/components/form/advanced-settings-panel.tsx b/lib/components/form/advanced-settings-panel.tsx index ea085d017..a55484b66 100644 --- a/lib/components/form/advanced-settings-panel.tsx +++ b/lib/components/form/advanced-settings-panel.tsx @@ -30,7 +30,8 @@ import { onSettingsUpdate, pipe, populateSettingWithIcon, - setModeButton + setModeButton, + tripPlannerValidationErrors } from './util' import { setModeButtonEnabled } from './batch-settings' import { styledCheckboxCss } from './styled' @@ -111,8 +112,11 @@ const DtSelectorContainer = styled.div` ` const AdvancedSettingsPanel = ({ + autoPlan, closeAdvancedSettings, + currentQuery, enabledModeButtons, + handlePlanTrip, innerRef, modeButtonOptions, modeSettingDefinitions, @@ -121,8 +125,11 @@ const AdvancedSettingsPanel = ({ setCloseAdvancedSettingsWithDelay, setQueryParam }: { + autoPlan: boolean closeAdvancedSettings: () => void + currentQuery: any enabledModeButtons: string[] + handlePlanTrip: () => void innerRef: RefObject modeButtonOptions: ModeButtonDefinition[] modeSettingDefinitions: ModeSetting[] @@ -177,11 +184,29 @@ const AdvancedSettingsPanel = ({ ) ) + + const tripFormErrors = tripPlannerValidationErrors(currentQuery, intl) + + const closePanel = useCallback(() => { + // Only autoplan if there are no validation errors + tripFormErrors.length === 0 && autoPlan && handlePlanTrip() + closeAdvancedSettings() + }, [autoPlan, closeAdvancedSettings, handlePlanTrip, tripFormErrors.length]) + + const handleModeButtonToggle = setModeButton( + enabledModeButtons, + onSettingsUpdate(setQueryParam) + ) + + const handleAllSubmodesDisabled = (modeButton: ModeButtonDefinition) => { + handleModeButtonToggle(modeButton.key, false) + } + const onSaveAndReturnClick = useCallback(async () => { await setCloseAdvancedSettingsWithDelay() setClosingBySave(true) - closeAdvancedSettings() - }, [closeAdvancedSettings, setCloseAdvancedSettingsWithDelay]) + closePanel() + }, [closePanel, setCloseAdvancedSettingsWithDelay]) return ( @@ -190,7 +215,7 @@ const AdvancedSettingsPanel = ({ aria-label={closeButtonText} id="close-advanced-settings-button" onClick={() => { - closeAdvancedSettings() + closePanel() }} title={closeButtonText} > @@ -221,11 +246,9 @@ const AdvancedSettingsPanel = ({ id: 'components.BatchSearchScreen.submodeSelectorLabel' })} modeButtons={processedModeButtons} + onAllSubmodesDisabled={handleAllSubmodesDisabled} onSettingsUpdate={onSettingsUpdate(setQueryParam)} - onToggleModeButton={setModeButton( - enabledModeButtons, - onSettingsUpdate(setQueryParam) - )} + onToggleModeButton={handleModeButtonToggle} /> {saveAndReturnButton && ( { state.otp.modeSettingDefinitions || [], modes?.initialState?.modeSettingValues || {} ) + + const { autoPlan } = state.otp.config const saveAndReturnButton = state.otp.config?.advancedSettingsPanel?.saveAndReturnButton return { + autoPlan: autoPlan !== false, currentQuery: state.otp.currentQuery, // TODO: Duplicated in apiv2.js enabledModeButtons: diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 23bd13a6c..47700fdeb 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -5,9 +5,8 @@ import { ModeButtonDefinition } from '@opentripplanner/types' import { Search } from '@styled-icons/fa-solid/Search' import { SyncAlt } from '@styled-icons/fa-solid/SyncAlt' import { useIntl } from 'react-intl' -import React, { useCallback, useContext, useState } from 'react' +import React, { useContext, useState } from 'react' -import * as apiActions from '../../actions/api' import * as formActions from '../../actions/form' import { ComponentContext } from '../../util/contexts' import { getActiveSearch, hasValidLocation } from '../../util/state' @@ -16,7 +15,6 @@ import { StyledIconWrapper } from '../util/styledIcon' import { addModeButtonIcon, - alertUserTripPlan, modesQueryParamConfig, onSettingsUpdate, pipe, @@ -39,7 +37,6 @@ type Props = { modeButtonOptions: ModeButtonDefinition[] onPlanTripClick: () => void openAdvancedSettings: () => void - routingQuery: any setQueryParam: (evt: any) => void spacedOutModeSelector?: boolean } @@ -64,7 +61,6 @@ function BatchSettings({ modeButtonOptions, onPlanTripClick, openAdvancedSettings, - routingQuery, setQueryParam, spacedOutModeSelector }: Props) { @@ -80,10 +76,6 @@ function BatchSettings({ pipe(addModeButtonIcon(ModeIcon), setModeButtonEnabled(enabledModeButtons)) ) - const _planTrip = useCallback(() => { - alertUserTripPlan(intl, currentQuery, onPlanTripClick, routingQuery) - }, [currentQuery, intl, onPlanTripClick, routingQuery]) - const baseColor = getBaseColor() const accentColor = getDarkenedBaseColor() @@ -109,7 +101,7 @@ function BatchSettings({ /> { } const mapDispatchToProps = { - routingQuery: apiActions.routingQuery, setQueryParam: formActions.setQueryParam } diff --git a/lib/components/form/util.tsx b/lib/components/form/util.tsx index 85dd3107d..c973af17c 100644 --- a/lib/components/form/util.tsx +++ b/lib/components/form/util.tsx @@ -72,13 +72,10 @@ export const setModeButton = ) } -export const alertUserTripPlan = ( - intl: IntlShape, +export const tripPlannerValidationErrors = ( currentQuery: any, - onPlanTripClick: () => void, - routingQuery: () => any -): void => { - // Check for any validation issues in query. + intl: IntlShape +): string[] => { const issues: string[] = [] if (!hasValidLocation(currentQuery, 'from')) { issues.push(intl.formatMessage({ id: 'components.BatchSettings.origin' })) @@ -88,6 +85,17 @@ export const alertUserTripPlan = ( intl.formatMessage({ id: 'components.BatchSettings.destination' }) ) } + return issues +} + +export const alertUserTripPlan = ( + intl: IntlShape, + currentQuery: any, + onPlanTripClick: () => void, + routingQuery: () => any +): void => { + // Check for any validation issues in query and alert user. + const issues = tripPlannerValidationErrors(currentQuery, intl) onPlanTripClick() if (issues.length > 0) { // TODO: replace with less obtrusive validation. diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 71af754b8..7ed9d628c 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -2,19 +2,14 @@ import { connect } from 'react-redux' import { injectIntl, IntlShape } from 'react-intl' import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' +import { AppReduxState } from '../../util/state-types' import { getActiveLeg, getTransitiveData } from '../../util/state' +import { TransitiveConfig } from '../../util/config-types' -type Props = { - intl?: IntlShape - labeledModes?: string[] - styles?: { - labels: Record - segmentLabels: Record - } -} +type Props = TransitiveConfig & IntlShape // connect to the redux store -const mapStateToProps = (state: Record, ownProps: Props) => { +const mapStateToProps = (state: AppReduxState, ownProps: Props) => { const { labeledModes, styles } = state.otp.config.map.transitive || {} const { viewedRoute } = state.otp.ui diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index b2e7b899f..5548f8171 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -158,7 +158,12 @@ class DefaultMap extends Component { // Generate operator logos to pass through OTP tile layer to map-popup getEntityPrefix = (entity) => { - const stopId = entity.gtfsId + // In the case that we are dealing with a station, use the first stop of the station + const firstStopOfStationId = entity.stops + ? JSON.parse(entity.stops)[0] + : false + + const stopId = firstStopOfStationId || entity.gtfsId this.props.findStopTimesForStop({ date: getCurrentDate(), onlyRequestForOperators: true, diff --git a/lib/components/map/simple-map.tsx b/lib/components/map/simple-map.tsx new file mode 100644 index 000000000..f9312a9a4 --- /dev/null +++ b/lib/components/map/simple-map.tsx @@ -0,0 +1,76 @@ +import { connect } from 'react-redux' +import { GeolocateControl, NavigationControl } from 'react-map-gl' +import { Itinerary } from '@opentripplanner/types' +import { useIntl } from 'react-intl' +import BaseMap from '@opentripplanner/base-map' +import EndpointsOverlay from '@opentripplanner/endpoints-overlay' +import React, { useContext } from 'react' +import TransitiveOverlay, { + itineraryToTransitive +} from '@opentripplanner/transitive-overlay' + +import { AppConfig } from '../../util/config-types' +import { AppReduxState } from '../../util/state-types' +import { ComponentContext } from '../../util/contexts' + +interface Props { + config: AppConfig + itinerary?: Itinerary +} + +/** Renders an optional itinerary with a given config. */ +const SimpleMap = ({ config, itinerary }: Props): JSX.Element => { + const intl = useIntl() + // @ts-expect-error ComponentContext not typed yet. + const { getTransitiveRouteLabel } = useContext(ComponentContext) + const { + baseLayers, + initLat = 0, + initLon = 0, + initZoom, + maxZoom, + navigationControlPosition = 'bottom-right', + transitive + } = config.map || {} + const baseLayerUrls = baseLayers?.map((bl) => bl.url) + const { disableFlexArc } = transitive || {} + const { legs = [] } = itinerary || {} + + return ( + 1 ? baseLayerUrls : baseLayerUrls?.[0] + } + center={[initLat, initLon]} + mapLibreProps={{ reuseMaps: true }} + maxZoom={maxZoom} + zoom={initZoom} + > + + + {itinerary && ( + + )} + + + + ) +} + +// connect to the redux store + +const mapStateToProps = (state: AppReduxState) => ({ + config: state.otp.config +}) + +export default connect(mapStateToProps)(SimpleMap) diff --git a/lib/components/mobile/batch-search-screen.tsx b/lib/components/mobile/batch-search-screen.tsx index c24b4dacb..5be2cc8c7 100644 --- a/lib/components/mobile/batch-search-screen.tsx +++ b/lib/components/mobile/batch-search-screen.tsx @@ -4,6 +4,7 @@ import { injectIntl, IntlShape } from 'react-intl' import React, { Component } from 'react' import styled from 'styled-components' +import * as apiActions from '../../actions/api' import * as uiActions from '../../actions/ui' import { advancedPanelClassName, @@ -11,6 +12,7 @@ import { transitionDuration, TransitionStyles } from '../form/styled' +import { alertUserTripPlan } from '../form/util' import { MobileScreens } from '../../actions/ui-constants' import AdvancedSettingsPanel from '../form/advanced-settings-panel' import BatchSettings from '../form/batch-settings' @@ -43,8 +45,10 @@ const MobileSearchSettings = styled.div<{ ` interface Props { + currentQuery: any intl: IntlShape map: React.ReactElement + routingQuery: any setMobileScreen: (screen: number) => void } @@ -63,7 +67,10 @@ class BatchSearchScreen extends Component { _advancedSettingRef = React.createRef() handlePlanTripClick = () => { - this.setState({ planTripClicked: true }) + const { currentQuery, intl, routingQuery } = this.props + alertUserTripPlan(intl, currentQuery, routingQuery, () => + this.setState({ planTripClicked: true }) + ) } openAdvancedSettings = () => { @@ -159,6 +166,7 @@ class BatchSearchScreen extends Component { > { // connect to the redux store +const mapStateToProps = (state: any) => { + const currentQuery = state.otp.currentQuery + return { + currentQuery + } +} + const mapDispatchToProps = { + routingQuery: apiActions.routingQuery, setMobileScreen: uiActions.setMobileScreen } -export default connect(null, mapDispatchToProps)(injectIntl(BatchSearchScreen)) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(BatchSearchScreen)) diff --git a/lib/components/user/monitored-trip/saved-trip-list.tsx b/lib/components/user/monitored-trip/saved-trip-list.tsx index 0df5ff909..03e33f80f 100644 --- a/lib/components/user/monitored-trip/saved-trip-list.tsx +++ b/lib/components/user/monitored-trip/saved-trip-list.tsx @@ -1,13 +1,15 @@ import { connect } from 'react-redux' +import { Edit, Map } from '@styled-icons/fa-solid' import { FormattedMessage, injectIntl, IntlShape, useIntl } from 'react-intl' import { Panel } from 'react-bootstrap' import { TriangleExclamation } from '@styled-icons/fa-solid/TriangleExclamation' import { withAuthenticationRequired } from '@auth0/auth0-react' import React, { Component } from 'react' +import styled from 'styled-components' import * as userActions from '../../../actions/user' import { AppReduxState } from '../../../util/state-types' -import { Edit } from '@styled-icons/fa-solid' +import { ComponentContext } from '../../../util/contexts' import { IconWithText } from '../../util/styledIcon' import { MonitoredTrip } from '../types' import { @@ -19,23 +21,17 @@ import { TripPanelTitle } from '../styled' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' -import { TRIPS_PATH } from '../../../util/constants' +import { TRIP_PREVIEW_PATH, TRIPS_PATH } from '../../../util/constants' import AccountPage from '../account-page' import AwaitingScreen from '../awaiting-screen' import BackToTripPlanner from '../back-to-trip-planner' -import PageTitle from '../../util/page-title' - -import styled from 'styled-components' - -import withLoggedInUserSupport from '../with-logged-in-user-support' - -import getRenderData from './trip-status-rendering-strategies' import InvisibleA11yLabel from '../../util/invisible-a11y-label' - -import { ComponentContext } from '../../../util/contexts' import Link from '../../util/link' import MetroItineraryRoutes from '../../narrative/metro/metro-itinerary-routes' +import PageTitle from '../../util/page-title' +import withLoggedInUserSupport from '../with-logged-in-user-support' +import getRenderData from './trip-status-rendering-strategies' import TripSummaryPane from './trip-summary-pane' interface ItemOwnProps { @@ -111,6 +107,12 @@ class TripListItem extends Component { const from = legs[0].from const to = legs[legs.length - 1].to const editTripPath = `${TRIPS_PATH}/${trip.id}` + const editTripText = intl.formatMessage({ + id: 'components.SavedTripEditor.editSavedTrip' + }) + const previewTripText = intl.formatMessage({ + id: 'components.TripPreviewLayout.previewTrip' + }) const { LegIcon } = this.context return ( @@ -120,15 +122,15 @@ class TripListItem extends Component { {trip.tripName} + + {previewTripText} + + - - - + {editTripText} diff --git a/lib/components/user/styled.ts b/lib/components/user/styled.ts index e1cf190dd..7139c93d0 100644 --- a/lib/components/user/styled.ts +++ b/lib/components/user/styled.ts @@ -52,7 +52,11 @@ export const TripHeader = styled.h3` export const TripPanelTitle = styled.div` align-items: center; display: flex; - justify-content: space-between; + gap: 10px; + + & > div:first-child { + flex-grow: 1; + } ` export const TripPanelHeading = styled(Panel.Heading)` diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index be04015dc..dd866b1f2 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -57,6 +57,10 @@ export interface ItineraryExistenceDay { export type ItineraryExistence = Record +export interface JourneyState { + matchingItinerary?: Itinerary +} + export type MonitoredTrip = Record & { arrivalVarianceMinutesThreshold: number departureVarianceMinutesThreshold: number @@ -65,6 +69,7 @@ export type MonitoredTrip = Record & { isActive: boolean itinerary: Itinerary itineraryExistence?: ItineraryExistence + journeyState?: JourneyState leadTimeInMinutes: number otp2QueryParams: Record queryParams: Record diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index fa0f082ef..c4b3a7811 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -135,10 +135,18 @@ export function getInitialState(userDefinedConfig) { const transitModeSettings = config?.modes?.transitModes.map((transitMode) => { const { mode, overrideMode } = transitMode + if (Array.isArray(mode) && !overrideMode) { + console.warn( + `Mode ${mode} is an array, but no overrideMode is specified.` + ) + } const displayedMode = overrideMode || mode + const addTransportModes = Array.isArray(mode) + ? mode.map((m) => ({ mode: m })) + : { mode } return { // This is the mode that gets added to the actual query to OTP - addTransportMode: { mode }, + addTransportMode: addTransportModes, applicableMode: 'TRANSIT', default: true, iconName: displayedMode.toLowerCase(), diff --git a/lib/util/config-types.ts b/lib/util/config-types.ts index 80f9cf10f..481a62582 100644 --- a/lib/util/config-types.ts +++ b/lib/util/config-types.ts @@ -15,6 +15,7 @@ import { TransitOperator, VehicleRentalMapOverlaySymbol } from '@opentripplanner/types' +import { ControlPosition } from 'react-map-gl' import { GeocoderConfig as GeocoderConfigOtpUI } from '@opentripplanner/geocoder' /** Accessibility threshold settings */ @@ -222,6 +223,15 @@ export type SupportedOverlays = | Otp1StopsOverlayConfig | MapTileLayerConfig +export interface TransitiveConfig { + disableFlexArc?: boolean + labeledModes?: string[] + styles?: { + labels: Record + segmentLabels: Record + } +} + export interface MapConfig { autoFlyOnTripFormUpdate?: boolean baseLayers?: BaseLayerConfig[] @@ -229,8 +239,9 @@ export interface MapConfig { initLon?: number initZoom?: number maxZoom?: number - navigationControlPosition?: string + navigationControlPosition?: ControlPosition overlays?: SupportedOverlays[] + transitive?: TransitiveConfig views?: MapViewConfig[] } @@ -312,7 +323,7 @@ export interface GeocoderConfig extends GeocoderConfigOtpUI { export interface TransitModeConfig { color?: string label?: string - mode: string + mode: string | string[] showWheelchairSetting?: boolean } diff --git a/lib/util/constants.js b/lib/util/constants.js index 07f0abf3e..0c4303772 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -23,6 +23,7 @@ export const CREATE_ACCOUNT_PLACES_PATH = `${CREATE_ACCOUNT_PATH}/places` export const CREATE_TRIP_PATH = `${TRIPS_PATH}/new` export const TERMS_OF_SERVICE_PATH = '/terms-of-service' export const TERMS_OF_STORAGE_PATH = '/terms-of-storage' +export const TRIP_PREVIEW_PATH = '/previewtrip' // Contains ignored actions when determining timeout, // such as actions triggered by a timer. diff --git a/lib/util/viewer.js b/lib/util/viewer.js index d407b356c..e5bcd85a2 100644 --- a/lib/util/viewer.js +++ b/lib/util/viewer.js @@ -21,13 +21,15 @@ export function getSecondsUntilDeparture( ) { let time if (useSchedule) { - time = useArrivalTime - ? stopTime.scheduledArrival - : stopTime.scheduledDeparture + time = + useArrivalTime && !!stopTime.scheduledArrival + ? stopTime.scheduledArrival + : stopTime.scheduledDeparture } else { - time = useArrivalTime - ? stopTime.realtimeArrival - : stopTime.realtimeDeparture + time = + useArrivalTime && !!stopTime.realtimeArrival + ? stopTime.realtimeArrival + : stopTime.realtimeDeparture } return time + stopTime.serviceDay - Date.now() / 1000 diff --git a/lib/util/webapp-trip-preview-routes.js b/lib/util/webapp-trip-preview-routes.js new file mode 100644 index 000000000..004fd1767 --- /dev/null +++ b/lib/util/webapp-trip-preview-routes.js @@ -0,0 +1,18 @@ +import TripPreviewLayout from '../components/app/trip-preview-layout' + +/** + * Contains mapping of the component(s) to display for the trip preview URL. + * + * Note: This file is separate from webapp-routes to isolate the import of trip preview components + * (YML file from @opentripplanner/trip-details). + * that cause build errors during the a11y test. + */ +const routes = [ + { + a11yIgnore: true, + component: TripPreviewLayout, + path: '/previewtrip/:id' + } +] + +export default routes diff --git a/package.json b/package.json index 7b69da5c0..62073cc3d 100644 --- a/package.json +++ b/package.json @@ -44,17 +44,17 @@ "@floating-ui/react": "^0.19.2", "@opentripplanner/base-map": "4.0.0", "@opentripplanner/building-blocks": "2.0.0", - "@opentripplanner/core-utils": "12.0.0", - "@opentripplanner/endpoints-overlay": "3.0.0", + "@opentripplanner/core-utils": "12.0.1", + "@opentripplanner/endpoints-overlay": "3.0.1", "@opentripplanner/from-to-location-picker": "3.0.0", "@opentripplanner/geocoder": "^3.0.2", "@opentripplanner/humanize-distance": "^1.2.0", - "@opentripplanner/icons": "3.0.0", + "@opentripplanner/icons": "3.0.1", "@opentripplanner/itinerary-body": "6.0.1", "@opentripplanner/location-field": "3.0.0", "@opentripplanner/location-icon": "^1.4.1", "@opentripplanner/map-popup": "5.1.0", - "@opentripplanner/otp2-tile-overlay": "2.1.0", + "@opentripplanner/otp2-tile-overlay": "2.1.1", "@opentripplanner/park-and-ride-overlay": "3.0.0", "@opentripplanner/printable-itinerary": "3.0.0", "@opentripplanner/route-viewer-overlay": "3.0.0", @@ -63,7 +63,7 @@ "@opentripplanner/transit-vehicle-overlay": "5.0.0", "@opentripplanner/transitive-overlay": "4.0.0", "@opentripplanner/trip-details": "6.0.0", - "@opentripplanner/trip-form": "4.0.0", + "@opentripplanner/trip-form": "4.1.0", "@opentripplanner/trip-viewer-overlay": "3.0.0", "@opentripplanner/vehicle-rental-overlay": "3.0.0", "@styled-icons/fa-regular": "^10.34.0", diff --git a/percy/percy.test.js b/percy/percy.test.js index 6e8e8b1e6..957f07e43 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -42,6 +42,12 @@ async function loadPath(otpPath) { return page } +const openEditIfNeeded = async (page, isMobile) => { + if (isMobile) { + await page.click('button.edit-search-button') + } +} + beforeAll(async () => { try { // Launch OTP-RR web server @@ -71,7 +77,7 @@ beforeAll(async () => { // Web security is disabled to allow requests to the mock OTP server browser = await puppeteer.launch({ - args: ['--disable-web-security'] + args: ['--disable-web-security', '--no-sandbox'] //, headless: false }) } catch (error) { @@ -114,9 +120,7 @@ async function executeTest(page, isMobile, isCallTaker) { if (!isCallTaker) { // Edit trip params [mobile-specific] - if (isMobile) { - await page.click('button.edit-search-button') - } + await openEditIfNeeded(page, isMobile) // Change the modes: Activate Transit and remove Bike. await page.click('label[title="Transit"]') @@ -148,6 +152,8 @@ async function executeTest(page, isMobile, isCallTaker) { await page.waitForTimeout(500) // Delete both origin and destination + await openEditIfNeeded(page, isMobile) + await page.click('.from-form-control') await page.waitForTimeout(300) // Click the clear button next to it diff --git a/yarn.lock b/yarn.lock index 1a65ef7a9..d475f12a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2408,15 +2408,15 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/building-blocks/-/building-blocks-2.0.0.tgz#8282c01dff7db5c7e809f6ea91cb52df559a2f9d" integrity sha512-N07rDaZL8fp552eI9/0j1udKjc0uOpvO0Wv1P19Ge0a4roques463MJgWJ026fbopRCi3uwbc/gYTlh4/ske9A== -"@opentripplanner/building-blocks@^1.0.3", "@opentripplanner/building-blocks@^1.2.2": +"@opentripplanner/building-blocks@^1.2.2": version "1.2.3" resolved "https://registry.yarnpkg.com/@opentripplanner/building-blocks/-/building-blocks-1.2.3.tgz#404e8f9038867d66d55f51adf8855b1326c51ed5" integrity sha512-I0AxiZrTZu+e7+av4u0tHW2ijqpxH0AkLHrhf75BHf1Ep2FOGxaul/v+8UT18mNYiM5eHNstOX3XiXaDjtCUaw== -"@opentripplanner/core-utils@12.0.0", "@opentripplanner/core-utils@^12.0.0": - version "12.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-12.0.0.tgz#cc40af92620b207f4dce817d08f99def0cdaea7a" - integrity sha512-udLF8XU+k7gxZ+yyyw7ASz6/4D540zYIv8a9GbUL61TF8HmgGhcMk3XOgBnm5jdOukuaNNpOFE4J3oJc5QsSBQ== +"@opentripplanner/core-utils@12.0.1": + version "12.0.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-12.0.1.tgz#2bafb78133393213b4943c76fec5d46436c0fb6d" + integrity sha512-QUTxEcpiOnbqaoiu6RQngTLlQHjSHO4PCMJqR9IRiaei08FnlTx2jgpvIaRla6u7tRNr12YCzptc37+a10ryww== dependencies: "@conveyal/lonlat" "^1.4.1" "@mapbox/polyline" "^1.1.0" @@ -2449,14 +2449,32 @@ lodash.isequal "^4.5.0" qs "^6.9.1" -"@opentripplanner/endpoints-overlay@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-3.0.0.tgz#17bb943f5bf2b8571cb808b12b5a2185fd18196a" - integrity sha512-st6vfLRCBzVollYS4nIXghbjApDq73lcExo7hZh60DCVSGESSZA9jsNE6ff0HmvhYHdag9QV9zbZOFuNxPIrng== +"@opentripplanner/core-utils@^12.0.0": + version "12.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-12.0.0.tgz#cc40af92620b207f4dce817d08f99def0cdaea7a" + integrity sha512-udLF8XU+k7gxZ+yyyw7ASz6/4D540zYIv8a9GbUL61TF8HmgGhcMk3XOgBnm5jdOukuaNNpOFE4J3oJc5QsSBQ== dependencies: - "@opentripplanner/base-map" "^3.2.2" - "@opentripplanner/building-blocks" "^1.2.2" - "@opentripplanner/core-utils" "^11.4.4" + "@conveyal/lonlat" "^1.4.1" + "@mapbox/polyline" "^1.1.0" + "@opentripplanner/geocoder" "^3.0.2" + "@styled-icons/foundation" "^10.34.0" + "@turf/along" "^6.0.1" + chroma-js "^2.4.2" + date-fns "^2.28.0" + date-fns-tz "^1.2.2" + graphql "^16.6.0" + lodash.clonedeep "^4.5.0" + lodash.isequal "^4.5.0" + qs "^6.9.1" + +"@opentripplanner/endpoints-overlay@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-3.0.1.tgz#b6b8e2f08ae41fbaad475fc0f0fe3e72d7d36463" + integrity sha512-X3T0GM8U+VU/mOOSNUgj6fVcjAKMeciKFYnQNbKiNgNeDHa5JltwvtXsM4x3wCLP2xAF6jH/HTWJmYmsfLPlAw== + dependencies: + "@opentripplanner/base-map" "^4.0.0" + "@opentripplanner/building-blocks" "^2.0.0" + "@opentripplanner/core-utils" "^12.0.0" "@opentripplanner/location-icon" "^1.4.1" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" @@ -2493,12 +2511,12 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.2.0.tgz#71cf5d5d1b756adef15300edbba0995ccd4b35ee" integrity sha512-x0QRXMDhypFeazZ6r6vzrdU8vhiV56nZ/WX6zUbxpgp6T9Oclw0gwR2Zdw6DZiiFpSYVNeVNxVzZwsu6NRGjcA== -"@opentripplanner/icons@3.0.0", "@opentripplanner/icons@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-3.0.0.tgz#f7293fd4dd2625eace3a4c82ecd573d6000d85d3" - integrity sha512-naSCdCsPwSyEiP7Vf6oN6dpgwpFIkeQFXfTJG7lp1Dg9emLTAYzRx/f+45e9Bh0zP0aA4DsN4VgHBQllyu82qQ== +"@opentripplanner/icons@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-3.0.1.tgz#62cf5ffd9ad42c5ba2ac64cf91e9f10c8300ff1e" + integrity sha512-pu96GWVR2ef6aMPPJRjdzHkIVlENGa3LRlqNaW3PEZQLjycsCsT1ZpJ6+zKwVfZsgoMl1L8mx2qjLO4RUZzGAA== dependencies: - "@opentripplanner/core-utils" "^11.4.4" + "@opentripplanner/core-utils" "^12.0.0" prop-types "^15.7.2" "@opentripplanner/icons@^2.0.12": @@ -2509,6 +2527,14 @@ "@opentripplanner/core-utils" "^11.4.4" prop-types "^15.7.2" +"@opentripplanner/icons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-3.0.0.tgz#f7293fd4dd2625eace3a4c82ecd573d6000d85d3" + integrity sha512-naSCdCsPwSyEiP7Vf6oN6dpgwpFIkeQFXfTJG7lp1Dg9emLTAYzRx/f+45e9Bh0zP0aA4DsN4VgHBQllyu82qQ== + dependencies: + "@opentripplanner/core-utils" "^11.4.4" + prop-types "^15.7.2" + "@opentripplanner/itinerary-body@6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-6.0.1.tgz#74139536b34083af5b324fb94e69be267ea6bbeb" @@ -2566,7 +2592,7 @@ "@styled-icons/fa-regular" "^10.34.0" "@styled-icons/fa-solid" "^10.34.0" -"@opentripplanner/map-popup@5.1.0": +"@opentripplanner/map-popup@5.1.0", "@opentripplanner/map-popup@^v5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@opentripplanner/map-popup/-/map-popup-5.1.0.tgz#cf6374bf7b69af69c026ec414a84719078c56e9e" integrity sha512-EShoMyFZa7Zb2ZZrJhEsJfuCAvs2jfQe5QstU+AEk5Jm1zc8LzU6PsXmizQ/RMVi6zIYLhlBoZ3u458tTA3VQA== @@ -2588,23 +2614,12 @@ "@opentripplanner/from-to-location-picker" "^2.1.14" flat "^5.0.2" -"@opentripplanner/map-popup@^v3.2.0-alpha.1": - version "3.2.0-alpha.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/map-popup/-/map-popup-3.2.0-alpha.1.tgz#dcad38c103500f7c5ad3c632398204849ed5885e" - integrity sha512-Z0RsyC7wkYU/aOLYQFsJI5tBhzooEE/sQZROX2WODkDWAv4Qfj95ppS8pvNkpoZ0N4fioFcj5aM2VGXVMSy0EA== - dependencies: - "@opentripplanner/base-map" "^3.2.2" - "@opentripplanner/building-blocks" "^1.2.2" - "@opentripplanner/core-utils" "^11.4.4" - "@opentripplanner/from-to-location-picker" "^2.1.14" - flat "^5.0.2" - -"@opentripplanner/otp2-tile-overlay@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/otp2-tile-overlay/-/otp2-tile-overlay-2.1.0.tgz#745cb6c80dbde767a0b5ac7b0b866193a18ec984" - integrity sha512-gkKS5OT/Ayc/987vcdSkFcGSH/YyvEBN9bZFWBHKRN5nbRykBRZu2GNFVfN5ITLoshrFw+YasIk9omfTKVJtRg== +"@opentripplanner/otp2-tile-overlay@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/otp2-tile-overlay/-/otp2-tile-overlay-2.1.1.tgz#3bd2f26caa01181eb4ca90bbd05ce784f9b05a7a" + integrity sha512-dAU8wd82ySTCHzc1uMbvN5k4WLAtK7AXY56rm6KZdnV3G2UQIFVcaatLj4bpTqS9j90trWnCIvRNm6FKo41f/w== dependencies: - "@opentripplanner/map-popup" "^v3.2.0-alpha.1" + "@opentripplanner/map-popup" "^v5.1.0" "@opentripplanner/park-and-ride-overlay@3.0.0": version "3.0.0" @@ -2698,14 +2713,14 @@ flat "^5.0.2" react-animate-height "^3.0.4" -"@opentripplanner/trip-form@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-4.0.0.tgz#27b36a0504e46cfda1cdf50c971b5cea03de8ae2" - integrity sha512-Cg8SlAVN8M+qoWpz8jAkwuqllIPgrL2PVewTPuDPsIQ8i5B7xo5KKE3TPo7cQUM+jE6WEshpvv0FIdMF+NAlNg== +"@opentripplanner/trip-form@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-4.1.0.tgz#83bd7135d1c814f1ea0be4f6896dd2494c9ddba9" + integrity sha512-VOWoyAfnnmwNnsolYLk7vfXRQvF3rj2XW5pXJ+OKwplGTgOhxJGNMKjZxau4legRedauyf0MHBNRpD3M3vsqRg== dependencies: "@floating-ui/react" "^0.19.2" - "@opentripplanner/building-blocks" "^1.0.3" - "@opentripplanner/core-utils" "^11.4.4" + "@opentripplanner/building-blocks" "^2.0.0" + "@opentripplanner/core-utils" "^12.0.0" "@styled-icons/bootstrap" "^10.34.0" "@styled-icons/boxicons-regular" "^10.38.0" "@styled-icons/fa-regular" "^10.37.0" @@ -7461,9 +7476,9 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.811: integrity sha512-bpLc4QU4B8PYmdO4MSu2ZBTMD8lAaEXRS43C09lB31BvYwuk9UxgBRXbY5OJBw7VuMGcg2MZG5FyTaP9u4PQnw== elliptic@^6.5.3, elliptic@^6.5.4: - version "6.5.7" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" - integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + version "6.6.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.0.tgz#5919ec723286c1edf28685aa89261d4761afa210" + integrity sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA== dependencies: bn.js "^4.11.9" brorand "^1.1.0"