diff --git a/app/component/RouteNumber.js b/app/component/RouteNumber.js
index 0bb9a7cddd..0f799fc9e5 100644
--- a/app/component/RouteNumber.js
+++ b/app/component/RouteNumber.js
@@ -12,15 +12,43 @@ const LONG_ROUTE_NUMBER_LENGTH = 6;
function RouteNumber(props, context) {
const mode = props.mode.toLowerCase();
- const { alertSeverityLevel, color, withBicycle, text } = props;
+ const { alertSeverityLevel, color, withBicycle, withCar } = props;
const isScooter = mode === TransportMode.Scooter.toLowerCase();
- const textIsText = typeof text === 'string'; // can be also react node
+
+ // Perform text-related processing
+ let filteredText = props.text;
+ if (
+ props.shortenLongText &&
+ context.config.disabledLegTextModes?.includes(mode) &&
+ props.className.includes('line')
+ ) {
+ filteredText = '';
+ }
+ const textFieldIsText = typeof filteredText === 'string'; // can be also react node
+ if (
+ props.shortenLongText &&
+ context.config.shortenLongTextThreshold &&
+ filteredText &&
+ textFieldIsText &&
+ filteredText.length > context.config.shortenLongTextThreshold
+ ) {
+ filteredText = `${filteredText.substring(
+ 0,
+ context.config.shortenLongTextThreshold - 3,
+ )}...`;
+ }
const longText =
- text && textIsText && text.length >= LONG_ROUTE_NUMBER_LENGTH;
+ filteredText &&
+ textFieldIsText &&
+ filteredText.length >= LONG_ROUTE_NUMBER_LENGTH;
// Checks if route only has letters without identifying numbers and
// length doesn't fit in the tab view
const hasNoShortName =
- text && textIsText && /^([^0-9]*)$/.test(text) && text.length > 3;
+ filteredText &&
+ textFieldIsText &&
+ /^([^0-9]*)$/.test(filteredText) &&
+ filteredText.length > 3;
+
const getColor = () => color || (props.isTransitLeg ? 'currentColor' : null);
const getIcon = (
@@ -57,6 +85,12 @@ function RouteNumber(props, context) {
className="itinerary-icon_with-bicycle"
/>
)}
+ {withCar && (
+
+ )}
);
}
@@ -83,6 +117,12 @@ function RouteNumber(props, context) {
className="itinerary-icon_with-bicycle"
/>
)}
+ {withCar && (
+
+ )}
);
};
@@ -128,7 +168,7 @@ function RouteNumber(props, context) {
)}
)}
- {text && (
+ {filteredText && (
- {props.text}
+ {filteredText}
- {textIsText && (
- {text?.toLowerCase()}
+ {textFieldIsText && (
+ {filteredText?.toLowerCase()}
)}
)}
@@ -206,9 +246,11 @@ RouteNumber.propTypes = {
duration: PropTypes.number,
isTransitLeg: PropTypes.bool,
withBicycle: PropTypes.bool,
+ withCar: PropTypes.bool,
card: PropTypes.bool,
appendClass: PropTypes.string,
occupancyStatus: PropTypes.string,
+ shortenLongText: PropTypes.bool,
};
RouteNumber.defaultProps = {
@@ -228,9 +270,11 @@ RouteNumber.defaultProps = {
isTransitLeg: false,
renderModeIcons: false,
withBicycle: false,
+ withCar: false,
color: undefined,
duration: undefined,
occupancyStatus: undefined,
+ shortenLongText: false,
};
RouteNumber.contextTypes = {
@@ -238,5 +282,4 @@ RouteNumber.contextTypes = {
config: configShape.isRequired,
};
-RouteNumber.displayName = 'RouteNumber';
export default RouteNumber;
diff --git a/app/component/RouteNumberContainer.js b/app/component/RouteNumberContainer.js
index c17b06c808..eee7a62fae 100644
--- a/app/component/RouteNumberContainer.js
+++ b/app/component/RouteNumberContainer.js
@@ -1,59 +1,30 @@
import PropTypes from 'prop-types';
import React from 'react';
import { routeShape, configShape } from '../util/shapes';
-import { getLegText } from '../util/legUtils';
+import { getRouteText } from '../util/legUtils';
import RouteNumber from './RouteNumber';
const RouteNumberContainer = (
- {
- alertSeverityLevel,
- interliningWithRoute,
- className,
- route,
- isCallAgency,
- withBicycle,
- occupancyStatus,
- mode,
- ...props
- },
+ { interliningWithRoute, route, mode, ...props },
{ config },
) =>
route && (
@@ -70,23 +82,69 @@ export default function CarLeg(props, { config, intl }) {
focusAction={props.focusAction}
/>
- {
const [imgUrl, setImgUrl] = useState('');
@@ -32,14 +33,39 @@ const ItineraryCircleLineLong = props => {
}
return null;
};
+
+ let firstModeClassName;
+ let secondModeClassName;
+ let positionRelativeToTransit;
+ if (
+ props.boardingLeg.to.stop !== null &&
+ props.boardingLeg.from.stop !== null
+ ) {
+ positionRelativeToTransit = 'between-transit';
+ firstModeClassName = props.boardingLeg.mode.toLowerCase();
+ secondModeClassName = props.modeClassName.toLowerCase();
+ } else if (props.boardingLeg.to.stop !== null) {
+ positionRelativeToTransit = 'before-transit';
+ firstModeClassName = props.modeClassName.toLowerCase();
+ secondModeClassName = props.boardingLeg.mode.toLowerCase();
+ } else {
+ // props.boardingLeg.from.stop !== undefined
+ positionRelativeToTransit = 'after-transit';
+ firstModeClassName = props.boardingLeg.mode.toLowerCase();
+ secondModeClassName = props.modeClassName.toLowerCase();
+ }
+
const topMarker = getMarker(true);
const bottomMarker = getMarker(false);
const legBeforeLineStyle = { color: props.color };
+ const carBoardingRouteNumber = (
+
+ );
// eslint-disable-next-line global-require
legBeforeLineStyle.backgroundImage = imgUrl;
return (
{
{topMarker}
-
-
+
+ {props.modeClassName === 'bicycle' ? (
+
+ ) : (
+ positionRelativeToTransit === 'before-transit' &&
+ carBoardingRouteNumber
+ )}
-
-
-
+
+ {props.modeClassName === 'bicycle' ? (
+
+ ) : (
+ (positionRelativeToTransit === 'after-transit' ||
+ positionRelativeToTransit === 'between-transit') &&
+ carBoardingRouteNumber
+ )}
+ {positionRelativeToTransit === 'between-transit' && (
+
+ )}
+ {positionRelativeToTransit === 'between-transit' &&
+ props.modeClassName === 'bicycle' && (
+
+
+
+ )}
+
{props.renderBottomMarker && bottomMarker}
@@ -77,8 +157,9 @@ const ItineraryCircleLineLong = props => {
ItineraryCircleLineLong.propTypes = {
index: PropTypes.number.isRequired,
color: PropTypes.string,
- modeClassNames: PropTypes.arrayOf(PropTypes.string).isRequired,
renderBottomMarker: PropTypes.bool,
+ modeClassName: PropTypes.string.isRequired,
+ boardingLeg: legShape.isRequired,
};
ItineraryCircleLineLong.defaultProps = {
diff --git a/app/component/itinerary/ItineraryDetails.js b/app/component/itinerary/ItineraryDetails.js
index 3f23422826..e50e84ca19 100644
--- a/app/component/itinerary/ItineraryDetails.js
+++ b/app/component/itinerary/ItineraryDetails.js
@@ -54,7 +54,8 @@ class ItineraryDetails extends React.Component {
changeHash: PropTypes.func,
openSettings: PropTypes.func.isRequired,
startNavigation: PropTypes.func,
- bikeAndPublicItineraryCount: PropTypes.number,
+ bikePublicItineraryCount: PropTypes.number,
+ carPublicItineraryCount: PropTypes.number,
relayEnvironment: relayShape,
};
@@ -62,7 +63,8 @@ class ItineraryDetails extends React.Component {
hideTitle: false,
currentLanguage: 'fi',
changeHash: () => {},
- bikeAndPublicItineraryCount: 0,
+ bikePublicItineraryCount: 0,
+ carPublicItineraryCount: 0,
carEmissions: undefined,
relayEnvironment: undefined,
startNavigation: undefined,
@@ -136,7 +138,8 @@ class ItineraryDetails extends React.Component {
itinerary,
currentLanguage,
isMobile,
- bikeAndPublicItineraryCount,
+ bikePublicItineraryCount,
+ carPublicItineraryCount,
} = this.props;
const { config } = this.context;
if (!itinerary?.legs[0]) {
@@ -145,6 +148,12 @@ class ItineraryDetails extends React.Component {
const fares = getFaresFromLegs(itinerary.legs, config);
const extraProps = this.getExtraProps(itinerary);
const { biking, walking, driving, futureText, isMultiRow } = extraProps;
+ // This does not take into account if the user is using a car at the time of using transit,
+ // instead this just calculates if the car is used for the whole trip.
+ // A smarter approach would be to store the current personal mode of transport (walk, bike, car)
+ // this could then be used to set the waiting icon legs that need it.
+ const usingOwnCarWholeTrip =
+ walking.distance === 0 && biking.distance === 0 && driving.distance > 0;
const legsWithRentalBike = compressLegs(itinerary.legs).filter(leg =>
legContainsRentalBike(leg),
);
@@ -157,8 +166,11 @@ class ItineraryDetails extends React.Component {
const containsBiking = biking.duration > 0 && biking.distance > 0;
const showBikeBoardingInformation =
containsBiking &&
- bikeAndPublicItineraryCount > 0 &&
+ bikePublicItineraryCount > 0 &&
legswithBikePark.length === 0;
+ const containsDriving = driving.duration > 0 && driving.distance > 0;
+ const showCarBoardingInformation =
+ containsDriving && carPublicItineraryCount > 0;
const rentalBikeNetworks = new Set();
let showRentalBikeDurationWarning = false;
if (legsWithRentalBike.length > 0) {
@@ -344,6 +356,8 @@ class ItineraryDetails extends React.Component {
tabIndex={itineraryIndex - 1}
openSettings={this.props.openSettings}
showBikeBoardingInformation={showBikeBoardingInformation}
+ showCarBoardingInformation={showCarBoardingInformation}
+ usingOwnCarWholeTrip={usingOwnCarWholeTrip}
relayEnvironment={this.props.relayEnvironment}
/>
diff --git a/app/component/itinerary/ItineraryList.js b/app/component/itinerary/ItineraryList.js
index d8408a149d..db5df2e8e3 100644
--- a/app/component/itinerary/ItineraryList.js
+++ b/app/component/itinerary/ItineraryList.js
@@ -8,7 +8,11 @@ import { configShape, planEdgeShape } from '../../util/shapes';
import Icon from '../Icon';
import Itinerary from './Itinerary';
import { isBrowser } from '../../util/browser';
-import { getExtendedMode, showBikeBoardingNote } from '../../util/legUtils';
+import {
+ getExtendedMode,
+ showBikeBoardingNote,
+ showCarBoardingNote,
+} from '../../util/legUtils';
import ItineraryListHeader from './ItineraryListHeader';
import ItinerariesNotFound from './ItinerariesNotFound';
import Loading from '../Loading';
@@ -29,6 +33,7 @@ function ItineraryList(
onSelectImmediately,
searchTime,
bikeParkItineraryCount,
+ carDirectItineraryCount,
showRelaxedPlanNotifier,
showRentalVehicleNotifier,
separatorPosition,
@@ -111,6 +116,34 @@ function ItineraryList(
);
}
}
+ if (hash === streetHash.carAndVehicle) {
+ // carDirectItineraryCount tells how many itineraries in array use the direct mode (should be 1 or 0).
+ if (planEdges.length > carDirectItineraryCount) {
+ // the rest use car + public
+ const mode =
+ getExtendedMode(
+ planEdges[carDirectItineraryCount].node.legs.find(l => l.transitLeg),
+ config,
+ ) || 'ferry';
+ const legs = planEdges
+ .slice(carDirectItineraryCount)
+ .flatMap(edge => edge.node.legs);
+ const showCarBoardingInfo = legs.some(leg =>
+ showCarBoardingNote(leg, config),
+ );
+
+ summaries.splice(
+ carDirectItineraryCount,
+ 0,
+
,
+ );
+ }
+ }
if (separatorPosition) {
summaries.splice(
separatorPosition,
@@ -204,6 +237,7 @@ ItineraryList.propTypes = {
onSelect: PropTypes.func.isRequired,
onSelectImmediately: PropTypes.func.isRequired,
bikeParkItineraryCount: PropTypes.number,
+ carDirectItineraryCount: PropTypes.number,
showRelaxedPlanNotifier: PropTypes.bool,
showRentalVehicleNotifier: PropTypes.bool,
separatorPosition: PropTypes.number,
@@ -213,6 +247,7 @@ ItineraryList.propTypes = {
ItineraryList.defaultProps = {
bikeParkItineraryCount: 0,
+ carDirectItineraryCount: 0,
planEdges: [],
showRelaxedPlanNotifier: false,
showRentalVehicleNotifier: false,
diff --git a/app/component/itinerary/ItineraryListContainer.js b/app/component/itinerary/ItineraryListContainer.js
index e0898778bf..cc8ce6ecd9 100644
--- a/app/component/itinerary/ItineraryListContainer.js
+++ b/app/component/itinerary/ItineraryListContainer.js
@@ -36,6 +36,7 @@ function ItineraryListContainer(
const modesWithSubpath = [
streetHash.bikeAndVehicle,
streetHash.parkAndRide,
+ streetHash.carAndVehicle,
];
const { hash } = params;
if (modesWithSubpath.includes(hash)) {
diff --git a/app/component/itinerary/ItineraryListHeader.js b/app/component/itinerary/ItineraryListHeader.js
index 770298923c..d6d64d9ffc 100644
--- a/app/component/itinerary/ItineraryListHeader.js
+++ b/app/component/itinerary/ItineraryListHeader.js
@@ -8,6 +8,7 @@ export default function ItineraryListHeader({
translationId,
defaultMessage,
showBikeBoardingInfo,
+ showCarBoardingInfo,
}) {
return (
@@ -22,12 +23,27 @@ export default function ItineraryListHeader({
/>
+
+ )}
+ {showCarBoardingInfo && (
+
)}
@@ -39,9 +55,11 @@ ItineraryListHeader.propTypes = {
translationId: PropTypes.string.isRequired,
defaultMessage: PropTypes.string,
showBikeBoardingInfo: PropTypes.bool,
+ showCarBoardingInfo: PropTypes.bool,
};
ItineraryListHeader.defaultProps = {
defaultMessage: '',
showBikeBoardingInfo: false,
+ showCarBoardingInfo: false,
};
diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js
index d1106a947d..0df1e807e4 100644
--- a/app/component/itinerary/ItineraryPage.js
+++ b/app/component/itinerary/ItineraryPage.js
@@ -64,6 +64,7 @@ import {
isEqualItineraries,
isStoredItineraryRelevant,
mergeBikeTransitPlans,
+ mergeCarDirectAndTransitPlans,
mergeScooterTransitPlan,
quitIteration,
reportError,
@@ -85,9 +86,14 @@ const streetHashes = [
streetHash.bike,
streetHash.bikeAndVehicle,
streetHash.car,
+ streetHash.carAndVehicle,
+ streetHash.parkAndRide,
+];
+const altTransitHash = [
+ streetHash.bikeAndVehicle,
+ streetHash.carAndVehicle,
streetHash.parkAndRide,
];
-const altTransitHash = [streetHash.bikeAndVehicle, streetHash.parkAndRide];
const noTransitHash = [streetHash.walk, streetHash.bike, streetHash.car];
const LOADSTATE = {
@@ -142,9 +148,12 @@ export default function ItineraryPage(props, context) {
[PLANTYPE.BIKEPARK]: useState(unset),
[PLANTYPE.BIKETRANSIT]: useState(unset),
[PLANTYPE.PARKANDRIDE]: useState(unset),
+ [PLANTYPE.CARTRANSIT]: useState(unset),
};
// combination of bikePark and bikeTransit
const [bikePublicState, setBikePublicState] = useState({ plan: {} });
+ // combination of direct car routing and cars with transit
+ const [carPublicState, setCarPublicState] = useState({ plan: {} });
const [settingsState, setSettingsState] = useState({
settingsOpen: false,
@@ -225,6 +234,8 @@ export default function ItineraryPage(props, context) {
return altStates[PLANTYPE.CAR][0].plan;
case streetHash.bikeAndVehicle:
return bikePublicState.plan;
+ case streetHash.carAndVehicle:
+ return carPublicState.plan;
case streetHash.parkAndRide:
return altStates[PLANTYPE.PARKANDRIDE][0].plan;
default:
@@ -851,6 +862,7 @@ export default function ItineraryPage(props, context) {
combinedState.plan,
relaxState.plan,
bikePublicState.plan,
+ carPublicState.plan,
altStates[PLANTYPE.PARKANDRIDE][0].plan,
location.state?.selectedItineraryIndex,
relaxScooterState.plan,
@@ -880,6 +892,23 @@ export default function ItineraryPage(props, context) {
altStates[PLANTYPE.BIKETRANSIT][0].plan,
]);
+ // merge direct car and car transit plans into one
+ useEffect(() => {
+ const settings = getSettings(config);
+ if (
+ altStates[PLANTYPE.CAR][0].loading === LOADSTATE.DONE &&
+ altStates[PLANTYPE.CARTRANSIT][0].loading === LOADSTATE.DONE &&
+ settings.includeCarSuggestions &&
+ config.carBoardingModes !== undefined
+ ) {
+ const plan = mergeCarDirectAndTransitPlans(
+ altStates[PLANTYPE.CAR][0].plan,
+ altStates[PLANTYPE.CARTRANSIT][0].plan,
+ );
+ setCarPublicState({ plan });
+ }
+ }, [altStates[PLANTYPE.CAR][0].plan, altStates[PLANTYPE.CARTRANSIT][0].plan]);
+
// merge the main plan and the scooter plan into one
useEffect(() => {
if (
@@ -1038,7 +1067,7 @@ export default function ItineraryPage(props, context) {
const itineraryContainsDepartureFromVehicleRentalStation = planEdges?.[
activeIndex
- ]?.node.legs.some(leg => leg.from?.vehicleRentalStation);
+ ]?.node.legs.some(leg => leg.from.vehicleRentalStation);
const mapLayerOptions = itineraryContainsDepartureFromVehicleRentalStation
? addBikeStationMapForRentalVehicleItineraries(planEdges)
@@ -1074,6 +1103,7 @@ export default function ItineraryPage(props, context) {
objectsToHide={objectsToHide}
itinerary={explicitItinerary}
showBackButton={!naviMode}
+ isLocationPopupEnabled={!naviMode}
/>
);
}
@@ -1097,6 +1127,7 @@ export default function ItineraryPage(props, context) {
const carPlan = altStates[PLANTYPE.CAR][0].plan;
const parkRidePlan = altStates[PLANTYPE.PARKANDRIDE][0].plan;
const bikePublicPlan = bikePublicState.plan;
+ const carPublicPlan = carPublicState.plan;
const settings = getSettings(config);
@@ -1217,7 +1248,8 @@ export default function ItineraryPage(props, context) {
focusToPoint={focusToPoint}
focusToLeg={focusToLeg}
carEmissions={carEmissions}
- bikeAndPublicItineraryCount={bikePublicPlan.bikePublicItineraryCount}
+ bikePublicItineraryCount={bikePublicPlan.bikePublicItineraryCount}
+ carPublicItineraryCount={carPublicPlan.carPublicItineraryCount}
openSettings={showSettingsPanel}
relayEnvironment={props.relayEnvironment}
startNavigation={navigateHook}
@@ -1242,6 +1274,7 @@ export default function ItineraryPage(props, context) {
planEdges={combinedEdges}
params={params}
bikeParkItineraryCount={bikePublicPlan.bikeParkItineraryCount}
+ carDirectItineraryCount={carPublicPlan.carDirectItineraryCount}
showRelaxedPlanNotifier={showRelaxedPlanNotifier}
showRentalVehicleNotifier={showRentalVehicleNotifier}
separatorPosition={hash ? undefined : state.separatorPosition}
@@ -1265,12 +1298,15 @@ export default function ItineraryPage(props, context) {
biking={bikePlan?.edges?.length > 0 || !!bikePublicPlan?.edges?.length}
driving={
(settings.includeCarSuggestions && carPlan?.edges?.length > 0) ||
+ !!carPublicPlan?.edges?.length ||
!!parkRidePlan?.edges?.length
}
/>
);
}
+ const showCarPublicPlan = carPublicPlan.carPublicItineraryCount > 0;
+
const showAltBar =
!detailView &&
!panelHidden &&
@@ -1280,7 +1316,8 @@ export default function ItineraryPage(props, context) {
bikePlan?.edges?.length ||
bikePublicPlan?.edges?.length ||
parkRidePlan?.edges?.length ||
- (settings.includeCarSuggestions && carPlan?.edges?.length));
+ (settings.includeCarSuggestions && carPlan?.edges?.length) ||
+ carPublicPlan?.edges?.length);
const alternativeItineraryBar = showAltBar ? (
) : null;
diff --git a/app/component/itinerary/ItineraryPageControls.js b/app/component/itinerary/ItineraryPageControls.js
index 4315766b9e..9e56c25318 100644
--- a/app/component/itinerary/ItineraryPageControls.js
+++ b/app/component/itinerary/ItineraryPageControls.js
@@ -76,6 +76,7 @@ class ItineraryPageControls extends React.Component {
iconClassName="arrow-icon"
fallback={
params.hash === streetHash.bikeAndVehicle ||
+ params.hash === streetHash.carAndVehicle ||
params.hash === streetHash.parkAndRide
? 'pop'
: undefined
diff --git a/app/component/itinerary/ItineraryPageUtils.js b/app/component/itinerary/ItineraryPageUtils.js
index d48171d14d..1048a54be2 100644
--- a/app/component/itinerary/ItineraryPageUtils.js
+++ b/app/component/itinerary/ItineraryPageUtils.js
@@ -324,7 +324,7 @@ export function getRentalStationsToHideOnMap(
const objectsToHide = { vehicleRentalStations: [] };
if (hasVehicleRentalStation) {
objectsToHide.vehicleRentalStations = selectedItinerary?.legs
- ?.filter(leg => leg.from?.vehicleRentalStation)
+ ?.filter(leg => leg.from.vehicleRentalStation)
.map(station => station.from?.vehicleRentalStation.stationId);
}
return objectsToHide;
@@ -467,6 +467,29 @@ export function mergeBikeTransitPlans(bikeParkPlan, bikeTransitPlan) {
};
}
+/**
+ * Merge the direct car plan with the car transit plan.
+ */
+export function mergeCarDirectAndTransitPlans(carDirectPlan, carTransitPlan) {
+ const carDirectPlanEdges = carDirectPlan?.edges || [];
+ let carPublicEdges = carTransitPlan?.edges || [];
+
+ // If the car direct plan has a shorter duration than a transit plan, the transit plan is filtered out.
+ if (carDirectPlanEdges.length === 1) {
+ carPublicEdges = carPublicEdges.filter(
+ itinerary =>
+ itinerary.node.duration <= carDirectPlanEdges[0].node.duration,
+ );
+ }
+
+ return {
+ searchDateTime: carTransitPlan.searchDateTime,
+ edges: [...carDirectPlanEdges, ...carPublicEdges],
+ carDirectItineraryCount: carDirectPlanEdges.length,
+ carPublicItineraryCount: carPublicEdges.length,
+ };
+}
+
/**
* Combine a scooter edge with the main transit edges.
*/
diff --git a/app/component/itinerary/Legs.js b/app/component/itinerary/Legs.js
index 48ab19a234..ebd5bd9c04 100644
--- a/app/component/itinerary/Legs.js
+++ b/app/component/itinerary/Legs.js
@@ -25,6 +25,7 @@ import {
isCallAgencyLeg,
isLegOnFoot,
legTime,
+ getBoardingLeg,
} from '../../util/legUtils';
import { getRouteMode } from '../../util/modeUtils';
import { addAnalyticsEvent } from '../../util/analyticsUtils';
@@ -47,6 +48,8 @@ export default class Legs extends React.Component {
tabIndex: PropTypes.number,
openSettings: PropTypes.func.isRequired,
showBikeBoardingInformation: PropTypes.bool,
+ showCarBoardingInformation: PropTypes.bool,
+ usingOwnCarWholeTrip: PropTypes.bool,
relayEnvironment: relayShape,
};
@@ -57,6 +60,8 @@ export default class Legs extends React.Component {
changeHash: undefined,
tabIndex: undefined,
showBikeBoardingInformation: false,
+ showCarBoardingInformation: false,
+ usingOwnCarWholeTrip: false,
relayEnvironment: undefined,
};
@@ -80,12 +85,19 @@ export default class Legs extends React.Component {
};
render() {
- const { itinerary, fares, showBikeBoardingInformation, relayEnvironment } =
- this.props;
+ const {
+ itinerary,
+ fares,
+ showBikeBoardingInformation,
+ showCarBoardingInformation,
+ relayEnvironment,
+ usingOwnCarWholeTrip,
+ } = this.props;
const { waitThreshold } = this.context.config.itinerary;
const compressedLegs = compressLegs(itinerary.legs, true).map(leg => ({
showBikeBoardingInformation,
+ showCarBoardingInformation,
...leg,
fare:
(leg.route &&
@@ -136,6 +148,7 @@ export default class Legs extends React.Component {
focusAction: this.focus(leg.from),
changeHash: this.props.changeHash,
tabIndex: this.props.tabIndex,
+ usingOwnCarWholeTrip,
};
let waitLeg;
@@ -146,7 +159,6 @@ export default class Legs extends React.Component {
waitTime > waitThresholdInMs &&
(nextLeg != null ? nextLeg.mode : null) !== 'AIRPLANE' &&
leg.mode !== 'AIRPLANE' &&
- leg.mode !== 'CAR' &&
!nextLeg.intermediatePlace &&
!isNextLegInterlining &&
leg.to.stop
@@ -158,6 +170,7 @@ export default class Legs extends React.Component {
start={leg.end}
waitTime={waitTime}
focusAction={this.focus(leg.to)}
+ icon={usingOwnCarWholeTrip ? 'icon-icon_wait-car' : undefined}
>
{stopCode(leg.to.stop)}
@@ -229,35 +242,15 @@ export default class Legs extends React.Component {
// currently bike walk leg is not rendered if there is waiting at stop, because
// 'walk bike to train and wait x minutes' is too confusing instruction
// This cannot be fixed as long as bicycle leg renders also bike walking
- if (
- !bikeParked &&
- ((nextLeg?.transitLeg && !waitLeg) || previousLeg?.transitLeg)
- ) {
- let { from, to } = leg;
- // don't render instructions to walk bike out from vehicle
- // if biking starts from stop (no transit first)
- if (!previousLeg?.transitLeg && leg.from.stop) {
- from = {
- ...from,
- stop: undefined,
- };
- }
- if ((!nextLeg?.transitLeg && leg.to.stop) || waitLeg) {
- to = {
- ...to,
- stop: undefined,
- };
- }
- bicycleWalkLeg = {
- duration: 0,
- start: leg.start,
- end: leg.start,
- distance: -1,
- rentedBike: leg.rentedBike,
- to,
- from,
- mode: 'BICYCLE_WALK',
- };
+ const boardingLeg = getBoardingLeg(
+ nextLeg,
+ previousLeg,
+ waitLeg,
+ leg,
+ 'BICYCLE_WALK',
+ );
+ if (!bikeParked && boardingLeg !== undefined) {
+ bicycleWalkLeg = boardingLeg;
}
legs.push(
,
);
} else if (leg.mode === 'CAR') {
- legs.push(
{stopCode(leg.from.stop)});
+ // If there is a transit leg after or before a car leg, render a car boarding leg without distance information.
+ const carBoardingLeg = getBoardingLeg(
+ nextLeg,
+ previousLeg,
+ waitLeg,
+ leg,
+ 'CAR_BOARDING',
+ );
+ legs.push(
+
+ {stopCode(leg.from.stop)}
+ ,
+ );
}
if (waitLeg) {
diff --git a/app/component/itinerary/StreetModeSelectorButton.js b/app/component/itinerary/StreetModeSelectorButton.js
index ff0fe5095b..cdbb8b5c35 100644
--- a/app/component/itinerary/StreetModeSelectorButton.js
+++ b/app/component/itinerary/StreetModeSelectorButton.js
@@ -38,6 +38,13 @@ export default function StreetModeSelectorButton(
intl.formatNumber,
);
break;
+ case streetHash.carAndVehicle:
+ distance = displayDistance(
+ getTotalDrivingDistance(itinerary),
+ config,
+ intl.formatNumber,
+ );
+ break;
case streetHash.parkAndRide:
distance = displayDistance(
getTotalDrivingDistance(itinerary),
@@ -57,7 +64,11 @@ export default function StreetModeSelectorButton(
let secondaryIcon;
let secondaryColor;
- if (name === streetHash.parkAndRide || name === streetHash.bikeAndVehicle) {
+ if (
+ name === streetHash.parkAndRide ||
+ name === streetHash.bikeAndVehicle ||
+ name === streetHash.carAndVehicle
+ ) {
const transitEdge = plan.edges.find(e =>
e.node.legs.find(l => l.transitLeg),
);
@@ -70,7 +81,9 @@ export default function StreetModeSelectorButton(
'rail';
secondaryIcon = `icon-icon_${mode}`;
secondaryColor =
- mode === 'subway' ? config.colors?.iconColors?.['mode-metro'] : '';
+ mode === 'subway'
+ ? config.colors?.iconColors?.['mode-metro']
+ : config.colors?.iconColors?.[`mode-${mode}`];
}
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
@@ -93,13 +106,18 @@ export default function StreetModeSelectorButton(
{name === streetHash.bikeAndVehicle ||
+ name === streetHash.carAndVehicle ||
name === streetHash.parkAndRide ? (
{
return (
this.context.config.zones.itinerary &&
@@ -235,6 +236,7 @@ class TransitLeg extends React.Component {
lang,
omitDivider,
interliningLegs,
+ usingOwnCarWholeTrip,
} = this.props;
const { config, intl } = this.context;
const startMs = legTime(leg.start);
@@ -325,7 +327,7 @@ class TransitLeg extends React.Component {
leg.intermediatePlaces.length,
);
}
- const { showBikeBoardingInformation } = leg;
+ const { showBikeBoardingInformation, showCarBoardingInformation } = leg;
const createNotification = notification => {
return (
@@ -385,6 +387,9 @@ class TransitLeg extends React.Component {
(showBikeBoardingInformation &&
notification.showForBikeWithPublic &&
showBikeBoardingNote(leg, config)) ||
+ (showCarBoardingInformation &&
+ notification.showForCarWithPublic &&
+ showCarBoardingNote(leg, config)) ||
notification.showForRoute?.(leg.route)
) {
routeNotifications.push(
@@ -578,7 +583,11 @@ class TransitLeg extends React.Component {
)}
{interliningLegs?.length > 0 ? (
-
+
) : (
!omitDivider &&
routeNotifications.length === 0 &&
@@ -675,6 +684,7 @@ TransitLeg.propTypes = {
omitDivider: PropTypes.bool,
changeHash: PropTypes.func,
tabIndex: PropTypes.number,
+ usingOwnCarWholeTrip: PropTypes.bool,
};
TransitLeg.defaultProps = {
@@ -683,6 +693,7 @@ TransitLeg.defaultProps = {
changeHash: undefined,
tabIndex: undefined,
children: undefined,
+ usingOwnCarWholeTrip: false,
};
TransitLeg.contextTypes = {
diff --git a/app/component/itinerary/ViaLeg.js b/app/component/itinerary/ViaLeg.js
index 3550011c46..9610c85624 100644
--- a/app/component/itinerary/ViaLeg.js
+++ b/app/component/itinerary/ViaLeg.js
@@ -129,11 +129,9 @@ function ViaLeg(props, { config, intl }) {
focusAction={props.focusAction}
/>
-