From ec6541029bd7c995e0a5279bda1357338112f958 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 4 Oct 2024 16:38:11 +0300 Subject: [PATCH 01/60] Change configs for cars on ferries. --- app/configurations/config.default.js | 20 ++++++++++++++++++++ app/configurations/config.matka.js | 5 +++++ app/configurations/config.waltti.js | 3 +++ 3 files changed, 28 insertions(+) diff --git a/app/configurations/config.default.js b/app/configurations/config.default.js index 8b2176be66..86d37f73d9 100644 --- a/app/configurations/config.default.js +++ b/app/configurations/config.default.js @@ -833,5 +833,25 @@ export default { ], }, }, + { + showForCarWithPublic: true, + + id: 'externalCostWithCar', + + content: { + fi: [ + 'Kulkuneuvossa mahdollisuus kuljettaa autoa. ', + 'Tarkasta auton kuljettamisen mahdollinen maksullisuus operaattorilta.', + ], + en: [ + 'You can take your car on board. ', + 'Check with the transport operator if an additional fee will be charged for the transportation of cars.', + ], + sv: [ + 'Du kan ta med bilen ombord. ', + 'Kontrollera med trafikoperatören om det är avgiftsbelagt att transportera bilar.', + ], + }, + }, ], }; diff --git a/app/configurations/config.matka.js b/app/configurations/config.matka.js index 00fc3035bf..740c07bbbb 100644 --- a/app/configurations/config.matka.js +++ b/app/configurations/config.matka.js @@ -402,6 +402,11 @@ export default { FERRY: { showNotification: true }, BUS: { showNotification: true }, }, + + carBoardingModes: { + FERRY: { showNotification: true }, + }, + // Include both bike and park and bike and public, if bike is enabled includePublicWithBikePlan: true, diff --git a/app/configurations/config.waltti.js b/app/configurations/config.waltti.js index 9e3b9975f5..4c053c7fe8 100644 --- a/app/configurations/config.waltti.js +++ b/app/configurations/config.waltti.js @@ -264,4 +264,7 @@ export default { value: 600, }, ], + carBoardingModes: { + FERRY: { showNotification: true }, + }, }; From b6904f120b580eec994ffdb50fd64d1fee2c5ded Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 4 Oct 2024 16:39:43 +0300 Subject: [PATCH 02/60] Add icons for cars on ferries. --- static/assets/svg-sprite.default.svg | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/assets/svg-sprite.default.svg b/static/assets/svg-sprite.default.svg index 318716c945..ac8701d79e 100644 --- a/static/assets/svg-sprite.default.svg +++ b/static/assets/svg-sprite.default.svg @@ -273,6 +273,11 @@ + + + + + @@ -501,6 +506,9 @@ + + + From fc90478d61903bd58f544585590e636e78c3af49 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 4 Oct 2024 16:40:19 +0300 Subject: [PATCH 03/60] Add translations for cars on ferries. --- app/translations.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/translations.js b/app/translations.js index c1383a8fee..3da2dc6000 100644 --- a/app/translations.js +++ b/app/translations.js @@ -962,6 +962,9 @@ const translations = { car: 'Car', 'car-distance-duration': 'Drive {duration} ({distance})', 'car-distance-no-duration': 'Drive {distance}', + 'car-drive-from-transit-no-duration': + 'Drive your car from the {transportMode}', + 'car-drive-to-transit-no-duration': 'Drive your car to the {transportMode}', 'car-park': 'Park & ride', 'car-park-disclaimer': 'You can conveniently combine car and public transport: leave your car at a Park & Ride and continue by train, bus, metro or tram. Car parking is free of charge and there are no time limits.', @@ -1203,6 +1206,10 @@ const translations = { 'itinerary-summary.bikeAndPublic-subway-title': 'Take your bike to metro', 'itinerary-summary.bikeAndPublic-tram-title': 'Take your bike to tram', 'itinerary-summary.bikePark-title': 'Leave your bike at a Park & Ride', + 'itinerary-summary.car-boarding-information': + 'You can take your car on board. Check with the transport operator if an additional fee will be charged for the transportation of cars.', + 'itinerary-summary.carAndPublic-ferry-title': 'Drive your car to the ferry', + 'itinerary-summary.carAndPublic-rail-title': 'Drive your car to the train', 'itinerary-summary.interline-wait': 'The route number ({shortName}) and destination ({destination}) will change at the {stop} stop. Waiting time at the stop is {time}. Please wait onboard.', 'itinerary-summary.interline-wait-multiple-legs': @@ -1615,6 +1622,8 @@ const translations = { 'Show a combination of cycling and public transport. Duration {duration}, distance {length}', 'street-mode-car-aria': 'Show driving route. Duration {duration}, distance {length}', + 'street-mode-carandvehicle-aria': + 'Show a route that may include other modes of transport, such as car ferries. Duration {duration}, distance {length}', 'street-mode-parkandride-aria': 'Show park and ride route. Duration {duration}, driving distance {length}', 'street-mode-walk-aria': @@ -2201,6 +2210,8 @@ const translations = { car: 'Auto', 'car-distance-duration': 'Autoile {duration} ({distance})', 'car-distance-no-duration': 'Autoile {distance}', + 'car-drive-from-transit-no-duration': 'Aja autosi {transportMode}', + 'car-drive-to-transit-no-duration': 'Aja autosi {transportMode}', 'car-park': 'Liityntäpysäköinti', 'car-park-disclaimer': 'Voit yhdistää kätevästi joukkoliikenteen ja autoilun, kun jätät autosi liityntäpysäköintiin ja jatkat matkaasi junalla, bussilla, metrolla tai ratikalla. Auton pysäköinti on useissa paikoissa maksutonta eikä siinä ole aikarajaa.', @@ -2438,6 +2449,10 @@ const translations = { 'itinerary-summary.bikeAndPublic-tram-title': 'Ota pyöräsi mukaan raitiovaunuun', 'itinerary-summary.bikePark-title': 'Jätä pyöräsi liityntäpysäköintiin', + 'itinerary-summary.car-boarding-information': + 'Kulkuneuvossa mahdollisuus kuljettaa autoa. Tarkasta auton kuljettamisen mahdollinen maksullisuus operaattorilta.', + 'itinerary-summary.carAndPublic-ferry-title': 'Aja autosi lauttaan', + 'itinerary-summary.carAndPublic-rail-title': 'Aja autosi junaan', 'itinerary-summary.interline-wait': 'Linjatunnus ({shortName}) ja määränpää ({destination}) vaihtuvat pysäkillä {stop}. Matka jatkuu {time} odotuksen jälkeen. Odota kulkuneuvossa.', 'itinerary-summary.interline-wait-multiple-legs': @@ -2847,6 +2862,8 @@ const translations = { 'Näytä pyöräilyn ja julkisen liikenteen yhdistelmä. Kesto {duration}, pyöräilyn pituus {length}', 'street-mode-car-aria': 'Näytä ajoreitti. Kesto {duration}, pituus {length}', + 'street-mode-carandvehicle-aria': + 'Näytä ajoreitti, joka voi sisältää muita kulkuvälineitä kuten esimerkiksi autolauttoja. Kesto {duration}, pituus {length}', 'street-mode-parkandride-aria': 'Näytä liityntäpysäköintireitti. Kesto {duration}, autoilun pituus {length}', 'street-mode-walk-aria': @@ -5082,6 +5099,8 @@ const translations = { car: 'Bil', 'car-distance-duration': 'Kör {duration} ({distance})', 'car-distance-no-duration': 'Kör {distance}', + 'car-drive-from-transit-no-duration': 'Kör din bil av {transportMode}', + 'car-drive-to-transit-no-duration': 'Kör din bil ombord på {transportMode}', 'car-park': 'Infartsparkering', 'car-park-disclaimer': 'Du kan enkelt kombinera kollektivtrafiken med bilkörning, när du lämnar din bil i anslutningsparkering och fortsätter din resa med tåg, buss, metro eller spårvagn. Parkeringen är gratis och den har inte en begränsad parkeringstid.', @@ -5321,6 +5340,11 @@ const translations = { 'itinerary-summary.bikeAndPublic-tram-title': 'Ta cykeln med på spårvagnen', 'itinerary-summary.bikePark-title': 'Lämna din cykel till anslutningsparkeringen', + 'itinerary-summary.car-boarding-information': + 'Du kan ta med bilen ombord. Kontrollera med trafikoperatören om det är avgiftsbelagt att transportera bilar.', + 'itinerary-summary.carAndPublic-ferry-title': + 'Kör din bil ombord på färjan', + 'itinerary-summary.carAndPublic-rail-title': 'Kör din bil ombord på tåget', 'itinerary-summary.interline-wait': 'Linjenumret ({shortName}) och destinationen ({destination}) ändras vid hållplats {stop}. Resan fortsätter om {time}. Vänta ombord på bussen.', 'itinerary-summary.interline-wait-multiple-legs': @@ -5732,6 +5756,8 @@ const translations = { 'street-mode-bikeandvehicle-aria': 'Visa kombinationen av cykling och kollektivtrafik. Tid {duration}, längd {length}', 'street-mode-car-aria': 'Visa bilrutt. Tid {duration}, längd {length}', + 'street-mode-carandvehicle-aria': + 'Visa en rutt som kan innehålla andra trafikmedel, såsom bilfärjor. Varaktighet {duration}, längd {length}', 'street-mode-parkandride-aria': 'Visa anslutningsparkeringrutt. Tid {duration}, längd {length}', 'street-mode-walk-aria': From 6d83ac6de238a7a325d7900424433106d5c4ffd6 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 4 Oct 2024 16:42:06 +0300 Subject: [PATCH 04/60] Add functionality for cars on ferries. --- app/component/RouteNumber.js | 16 +- app/component/RouteNumberContainer.js | 4 + .../itinerary/AlternativeItineraryBar.js | 11 ++ app/component/itinerary/BicycleLeg.js | 9 +- app/component/itinerary/CarLeg.js | 69 ++++++++- app/component/itinerary/InterlineInfo.js | 11 +- app/component/itinerary/Itinerary.js | 38 ++++- .../itinerary/ItineraryCircleLineLong.js | 105 +++++++++++-- app/component/itinerary/ItineraryDetails.js | 21 ++- app/component/itinerary/ItineraryList.js | 37 ++++- .../itinerary/ItineraryListContainer.js | 1 + .../itinerary/ItineraryListHeader.js | 22 +++ app/component/itinerary/ItineraryPage.js | 55 ++++++- .../itinerary/ItineraryPageControls.js | 1 + app/component/itinerary/ItineraryPageUtils.js | 30 ++++ app/component/itinerary/Legs.js | 71 +++++---- .../itinerary/StreetModeSelectorButton.js | 20 ++- app/component/itinerary/TransitLeg.js | 15 +- app/component/itinerary/WaitLeg.js | 5 +- .../itinerary/itinerary-list-header.scss | 43 ++++++ .../itinerary/itinerary-summary.scss | 40 +++++ app/component/itinerary/itinerary.scss | 144 ++++++++++++++++-- .../street-mode-selector-button.scss | 9 ++ app/util/legUtils.js | 59 ++++++- app/util/path.js | 1 + app/util/planParamUtil.js | 25 +++ 26 files changed, 769 insertions(+), 93 deletions(-) diff --git a/app/component/RouteNumber.js b/app/component/RouteNumber.js index 0bb9a7cddd..db54ec341f 100644 --- a/app/component/RouteNumber.js +++ b/app/component/RouteNumber.js @@ -12,7 +12,7 @@ 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, text } = props; const isScooter = mode === TransportMode.Scooter.toLowerCase(); const textIsText = typeof text === 'string'; // can be also react node const longText = @@ -57,6 +57,12 @@ function RouteNumber(props, context) { className="itinerary-icon_with-bicycle" /> )} + {withCar && ( + + )} ); } @@ -83,6 +89,12 @@ function RouteNumber(props, context) { className="itinerary-icon_with-bicycle" /> )} + {withCar && ( + + )} ); }; @@ -206,6 +218,7 @@ RouteNumber.propTypes = { duration: PropTypes.number, isTransitLeg: PropTypes.bool, withBicycle: PropTypes.bool, + withCar: PropTypes.bool, card: PropTypes.bool, appendClass: PropTypes.string, occupancyStatus: PropTypes.string, @@ -228,6 +241,7 @@ RouteNumber.defaultProps = { isTransitLeg: false, renderModeIcons: false, withBicycle: false, + withCar: false, color: undefined, duration: undefined, occupancyStatus: undefined, diff --git a/app/component/RouteNumberContainer.js b/app/component/RouteNumberContainer.js index eac349b498..4688f2778c 100644 --- a/app/component/RouteNumberContainer.js +++ b/app/component/RouteNumberContainer.js @@ -12,6 +12,7 @@ const RouteNumberContainer = ( route, isCallAgency, withBicycle, + withCar, occupancyStatus, mode, ...props @@ -27,6 +28,7 @@ const RouteNumberContainer = ( mode={mode !== undefined ? mode : route.mode} text={getLegText(route, config, interliningWithRoute)} withBicycle={withBicycle} + withCar={withCar} occupancyStatus={occupancyStatus} {...props} /> @@ -41,6 +43,7 @@ RouteNumberContainer.propTypes = { className: PropTypes.string, fadeLong: PropTypes.bool, withBicycle: PropTypes.bool, + withCar: PropTypes.bool, occupancyStatus: PropTypes.string, mode: PropTypes.string, }; @@ -53,6 +56,7 @@ RouteNumberContainer.defaultProps = { fadeLong: false, className: '', withBicycle: false, + withCar: false, occupancyStatus: undefined, mode: undefined, }; diff --git a/app/component/itinerary/AlternativeItineraryBar.js b/app/component/itinerary/AlternativeItineraryBar.js index cc7401f344..ebeda81df6 100644 --- a/app/component/itinerary/AlternativeItineraryBar.js +++ b/app/component/itinerary/AlternativeItineraryBar.js @@ -15,6 +15,7 @@ export default function AlternativeItineraryBar( bikePlan, bikePublicPlan, carPlan, + carPublicPlan, parkRidePlan, loading, }, @@ -69,6 +70,14 @@ export default function AlternativeItineraryBar( onClick={selectStreetMode} /> )} + {carPublicPlan?.edges?.length > 0 && ( + + )} {config.emphasizeOneWayJourney && (
); } else if (bicycleWalkLeg) { - const modeClassNames = bicycleWalkLeg.to?.stop - ? [modeClassName, bicycleWalkLeg.mode.toLowerCase()] - : [bicycleWalkLeg.mode.toLowerCase(), modeClassName]; circleLine = ( - + ); } else if (mode === 'BICYCLE') { circleLine = ( diff --git a/app/component/itinerary/CarLeg.js b/app/component/itinerary/CarLeg.js index c019576969..ccf94ec325 100644 --- a/app/component/itinerary/CarLeg.js +++ b/app/component/itinerary/CarLeg.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { FormattedMessage, intlShape } from 'react-intl'; +import cx from 'classnames'; import { legShape, configShape } from '../../util/shapes'; import Icon from '../Icon'; import ItineraryMapAction from './ItineraryMapAction'; @@ -8,6 +9,7 @@ import { displayDistance } from '../../util/geo-utils'; import { durationToString } from '../../util/timeUtils'; import ItineraryCircleLineWithIcon from './ItineraryCircleLineWithIcon'; import { legTimeStr, legDestination } from '../../util/legUtils'; +import ItineraryCircleLineLong from './ItineraryCircleLineLong'; export default function CarLeg(props, { config, intl }) { const distance = displayDistance( @@ -19,6 +21,24 @@ export default function CarLeg(props, { config, intl }) { const firstLegClassName = props.index === 0 ? 'first' : ''; const modeClassName = 'car'; + let circleLine = ( + + ); + + if (props.carBoardingLeg !== undefined) { + circleLine = ( + + ); + } + const [address, place] = props.leg.from.name.split(/, (.+)/); // Splits the name-string to two parts from the first occurance of ', ' /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ @@ -42,11 +62,7 @@ export default function CarLeg(props, { config, intl }) { {legTimeStr(props.leg.start)}
- + {circleLine}
@@ -70,6 +86,26 @@ export default function CarLeg(props, { config, intl }) { focusAction={props.focusAction} />
+ {props.carBoardingLeg?.from.stop && ( +
+
+ + ), + }} + /> + +
+
+ )}
+ {props.carBoardingLeg?.to.stop && ( +
+
+ + ), + }} + /> + +
+
+ )} ); @@ -98,9 +154,10 @@ CarLeg.propTypes = { focusAction: PropTypes.func.isRequired, focusToLeg: PropTypes.func.isRequired, children: PropTypes.node, + carBoardingLeg: legShape, }; -CarLeg.defaultProps = { children: undefined }; +CarLeg.defaultProps = { children: undefined, carBoardingLeg: undefined }; CarLeg.contextTypes = { config: configShape.isRequired, diff --git a/app/component/itinerary/InterlineInfo.js b/app/component/itinerary/InterlineInfo.js index f000524f77..44b6301840 100644 --- a/app/component/itinerary/InterlineInfo.js +++ b/app/component/itinerary/InterlineInfo.js @@ -6,7 +6,7 @@ import { getHeadsignFromRouteLongName, legTime } from '../../util/legUtils'; import Icon from '../Icon'; import { legShape } from '../../util/shapes'; -const InterlineInfo = ({ legs, leg }) => { +const InterlineInfo = ({ legs, leg, usingOwnCarWholeTrip }) => { let totalWait = 0; const allLegs = [leg, ...legs]; const routes = []; @@ -18,11 +18,15 @@ const InterlineInfo = ({ legs, leg }) => { } }); } + let icon = 'icon-icon_wait'; + if (usingOwnCarWholeTrip) { + icon = 'icon-icon_wait-car'; + } return (
{legs.length === 1 && ( <> - + { )} {legs.length > 1 && ( <> - + { InterlineInfo.propTypes = { leg: legShape.isRequired, legs: PropTypes.arrayOf(legShape).isRequired, + usingOwnCarWholeTrip: PropTypes.bool.isRequired, }; export default InterlineInfo; diff --git a/app/component/itinerary/Itinerary.js b/app/component/itinerary/Itinerary.js index f23d646275..642119256f 100644 --- a/app/component/itinerary/Itinerary.js +++ b/app/component/itinerary/Itinerary.js @@ -1,6 +1,6 @@ import cx from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, { createRef, useLayoutEffect, useState } from 'react'; import { graphql, createFragmentContainer } from 'react-relay'; import { FormattedMessage, intlShape } from 'react-intl'; import { @@ -82,6 +82,7 @@ export function RouteLeg( interliningWithRoute, fitRouteNumber, withBicycle, + withCar, hasOneTransitLeg, }, { config }, @@ -125,6 +126,7 @@ export function RouteLeg( withBar isTransitLeg={isTransitLeg} withBicycle={withBicycle} + withCar={withCar} occupancyStatus={getOccupancyStatus()} /> ); @@ -149,6 +151,7 @@ RouteLeg.propTypes = { interliningWithRoute: PropTypes.string, isTransitLeg: PropTypes.bool, withBicycle: PropTypes.bool.isRequired, + withCar: PropTypes.bool.isRequired, hasOneTransitLeg: PropTypes.bool, }; @@ -277,6 +280,9 @@ const Itinerary = ( ); const usingOwnBicycleWholeTrip = usingOwnBicycle && itinerary.legs.every(leg => !leg.to?.vehicleParking); + const usingOwnCar = itinerary.legs.some(leg => getLegMode(leg) === 'CAR'); + const usingOwnCarWholeTrip = + usingOwnCar && itinerary.legs.every(leg => !leg.to?.vehicleParking); const { refTime } = props; const startTime = Date.parse(itinerary.start); const endTime = Date.parse(itinerary.end); @@ -552,6 +558,9 @@ const Itinerary = ( const withBicycle = usingOwnBicycleWholeTrip && config.bikeBoardingModes[leg.route.mode] !== undefined; + const withCar = + usingOwnCarWholeTrip && + config.carBoardingModes[leg.route.mode] !== undefined; if ( previousLeg && !previousLeg.intermediatePlace && @@ -576,6 +585,7 @@ const Itinerary = ( legLength={legLength} large={breakpoint === 'large'} withBicycle={withBicycle} + withCar={withCar} hasOneTransitLeg={hasOneTransitLeg(itinerary)} />, ); @@ -606,6 +616,7 @@ const Itinerary = ( isTransitLeg={false} mode="WAIT" large={breakpoint === 'large'} + icon={usingOwnCarWholeTrip ? 'icon-icon_wait-car' : undefined} />, ); } @@ -817,6 +828,20 @@ const Itinerary = ( co2value !== null && co2value >= 0 && !containsScooterLeg; + + const itineraryContainerOverflowRef = createRef(); + const [showOverflowIcon, setShowOverflowIcon] = useState(false); + useLayoutEffect(() => { + // If the itinerary length exceeds its boundaries an icon with dots is displayed. + if ( + itineraryContainerOverflowRef.current.clientWidth < + itineraryContainerOverflowRef.current.scrollWidth + ) { + setShowOverflowIcon(true); + } else { + setShowOverflowIcon(false); + } + }, [itineraryContainerOverflowRef]); return (

@@ -891,11 +916,20 @@ const Itinerary = ( aria-hidden="true" >
{legs}
+
+ {showOverflowIcon && ( + + )} +

{ 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 (
)} -
-
- {stopsDescription} - -
+
+ {stopsDescription} +
{bicycleWalkLeg && bicycleWalkLeg?.to.stop && ( -
-
- {bicycleWalkLeg.distance === -1 ? ( - - ), - }} - /> - ) : ( - - ), - duration: durationToString(bicycleWalkLeg.duration * 1000), - distance: displayDistance( - parseInt(bicycleWalkLeg.distance, 10), - config, - intl.formatNumber, - ), - }} - /> - )} - + {bicycleWalkLeg.distance === -1 ? ( + + ), + }} /> -
+ ) : ( + + ), + duration: durationToString(bicycleWalkLeg.duration * 1000), + distance: displayDistance( + parseInt(bicycleWalkLeg.distance, 10), + config, + intl.formatNumber, + ), + }} + /> + )} +
)} {isScooter && ( diff --git a/app/component/itinerary/BikeParkLeg.js b/app/component/itinerary/BikeParkLeg.js index 30c81c124b..783e392ac1 100644 --- a/app/component/itinerary/BikeParkLeg.js +++ b/app/component/itinerary/BikeParkLeg.js @@ -72,18 +72,22 @@ const BikeParkLeg = ( focusAction={focusAction} />
-
-
- - -
+
+ +
diff --git a/app/component/itinerary/CarLeg.js b/app/component/itinerary/CarLeg.js index 441dbce4af..40f1f1f059 100644 --- a/app/component/itinerary/CarLeg.js +++ b/app/component/itinerary/CarLeg.js @@ -83,60 +83,66 @@ export default function CarLeg(props, { config, intl }) { /> {props.carBoardingLeg?.from.stop && ( -
-
- - ), - }} - /> - -
-
- )} -
-
+
+ ), + }} />
+ )} +
+ +
{props.carBoardingLeg?.to.stop && ( -
-
- - ), - }} - /> - -
+
+ + ), + }} + /> +
)}
diff --git a/app/component/itinerary/CarParkLeg.js b/app/component/itinerary/CarParkLeg.js index 1e99e67836..9065b6c62e 100644 --- a/app/component/itinerary/CarParkLeg.js +++ b/app/component/itinerary/CarParkLeg.js @@ -90,18 +90,22 @@ function CarParkLeg(props, { config, intl }) { />
{!props.noWalk && ( -
-
- - -
+
+ +
)}
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} />
-
-
- {getDescription(props.leg.mode, distance, duration)} - -
+
+ {getDescription(props.leg.mode, distance, duration)} +
diff --git a/app/component/itinerary/WaitLeg.js b/app/component/itinerary/WaitLeg.js index 8608e80953..98aca48af8 100644 --- a/app/component/itinerary/WaitLeg.js +++ b/app/component/itinerary/WaitLeg.js @@ -63,15 +63,13 @@ function WaitLeg( focusAction={focusAction} /> -
-
- - -
+
+ +
diff --git a/app/component/itinerary/WalkLeg.js b/app/component/itinerary/WalkLeg.js index 963d177d5e..258a03bcd2 100644 --- a/app/component/itinerary/WalkLeg.js +++ b/app/component/itinerary/WalkLeg.js @@ -223,26 +223,20 @@ function WalkLeg( )} -
-
- {distance} - ) : ( - distance - ), - duration, - }} - defaultMessage="Walk {distance} ({duration})" - /> - -
+
+ {distance} : distance, + duration, + }} + defaultMessage="Walk {distance} ({duration})" + /> +
From c90f7d31365067b9c8909cb74aec29fff2a12af5 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 31 Oct 2024 09:41:08 +0200 Subject: [PATCH 10/60] Remove the ? from all uses of leg.to?.field and leg.from?.field. --- app/component/itinerary/Itinerary.js | 4 ++-- app/component/itinerary/ItineraryCircleLineLong.js | 8 ++++---- app/component/itinerary/ItineraryPage.js | 2 +- app/component/itinerary/ItineraryPageUtils.js | 2 +- app/component/itinerary/TransitLeg.js | 2 +- app/component/itinerary/WalkLeg.js | 4 ++-- app/util/legUtils.js | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/component/itinerary/Itinerary.js b/app/component/itinerary/Itinerary.js index 642119256f..5f73d5a6aa 100644 --- a/app/component/itinerary/Itinerary.js +++ b/app/component/itinerary/Itinerary.js @@ -279,10 +279,10 @@ const Itinerary = ( leg => getLegMode(leg) === 'BICYCLE' && leg.rentedBike === false, ); const usingOwnBicycleWholeTrip = - usingOwnBicycle && itinerary.legs.every(leg => !leg.to?.vehicleParking); + usingOwnBicycle && itinerary.legs.every(leg => !leg.to.vehicleParking); const usingOwnCar = itinerary.legs.some(leg => getLegMode(leg) === 'CAR'); const usingOwnCarWholeTrip = - usingOwnCar && itinerary.legs.every(leg => !leg.to?.vehicleParking); + usingOwnCar && itinerary.legs.every(leg => !leg.to.vehicleParking); const { refTime } = props; const startTime = Date.parse(itinerary.start); const endTime = Date.parse(itinerary.end); diff --git a/app/component/itinerary/ItineraryCircleLineLong.js b/app/component/itinerary/ItineraryCircleLineLong.js index bace8b25f4..fc7e04196c 100644 --- a/app/component/itinerary/ItineraryCircleLineLong.js +++ b/app/component/itinerary/ItineraryCircleLineLong.js @@ -38,18 +38,18 @@ const ItineraryCircleLineLong = props => { let secondModeClassName; let positionRelativeToTransit; if ( - props.boardingLeg.to?.stop !== null && - props.boardingLeg.from?.stop !== null + 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) { + } 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 + // props.boardingLeg.from.stop !== undefined positionRelativeToTransit = 'after-transit'; firstModeClassName = props.boardingLeg.mode.toLowerCase(); secondModeClassName = props.modeClassName.toLowerCase(); diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index 88afc7f413..cdee9730dd 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -1021,7 +1021,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) diff --git a/app/component/itinerary/ItineraryPageUtils.js b/app/component/itinerary/ItineraryPageUtils.js index 2e070e3c6b..d8d81d2255 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; diff --git a/app/component/itinerary/TransitLeg.js b/app/component/itinerary/TransitLeg.js index 58dae0b107..c4f92478e2 100644 --- a/app/component/itinerary/TransitLeg.js +++ b/app/component/itinerary/TransitLeg.js @@ -105,7 +105,7 @@ class TransitLeg extends React.Component { getZoneChange() { const { leg } = this.props; const startZone = leg.from.stop.zoneId; - const endZone = leg.to?.stop.zoneId || leg.to.stop.zoneId; + const endZone = leg.to.stop.zoneId || leg.to.stop.zoneId; const renderZoneIcons = () => { return ( this.context.config.zones.itinerary && diff --git a/app/component/itinerary/WalkLeg.js b/app/component/itinerary/WalkLeg.js index 258a03bcd2..d34fd4170c 100644 --- a/app/component/itinerary/WalkLeg.js +++ b/app/component/itinerary/WalkLeg.js @@ -68,12 +68,12 @@ function WalkLeg( } const destinationLabel = - leg.to?.name?.toLowerCase() === 'scooter' + leg.to.name?.toLowerCase() === 'scooter' ? intl.formatMessage({ id: 'e-scooter', defaultMessage: 'scooter', }) - : leg.to?.name; + : leg.to.name; return (
diff --git a/app/util/legUtils.js b/app/util/legUtils.js index 1a1d01c14c..e69931c110 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -191,7 +191,7 @@ export function compressLegs(originalLegs, keepBicycleWalk = false) { let compressedLeg; let bikeParked = false; originalLegs.forEach((currentLeg, i) => { - if (currentLeg.to?.vehicleParking && currentLeg.mode === LegMode.Bicycle) { + if (currentLeg.to.vehicleParking && currentLeg.mode === LegMode.Bicycle) { bikeParked = true; } if (!compressedLeg) { From edf8305660a518e96692a95a5d1c28ad9c4fc087 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 31 Oct 2024 09:43:55 +0200 Subject: [PATCH 11/60] Remove with-car-info-notification-icon and with-bike-info-notification-icon CSS classes. --- app/component/itinerary/itinerary-list-header.scss | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/component/itinerary/itinerary-list-header.scss b/app/component/itinerary/itinerary-list-header.scss index 4235b1fdb7..3df07c0157 100644 --- a/app/component/itinerary/itinerary-list-header.scss +++ b/app/component/itinerary/itinerary-list-header.scss @@ -30,11 +30,6 @@ } } - .with-bike-info-notification-icon { - align-self: baseline; - margin-top: 5px; - } - .with-bike-info-notification { flex: 1; color: #333; @@ -63,11 +58,6 @@ } } - .with-car-info-notification-icon { - align-self: baseline; - margin-top: 5px; - } - .with-car-info-notification { flex: 1; color: #333; From 201b2aff1163eeed2fc4be81b5271fccf58c4a19 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 31 Oct 2024 10:24:54 +0200 Subject: [PATCH 12/60] Fix use of settings.includeCarSuggestions. --- app/component/itinerary/ItineraryPage.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index cdee9730dd..2b2b6c120e 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -1234,16 +1234,15 @@ export default function ItineraryPage(props, context) { walking={walkPlan?.edges?.length > 0} biking={bikePlan?.edges?.length > 0 || !!bikePublicPlan?.edges?.length} driving={ - (settings.includeCarSuggestions && - (carPlan?.edges?.length > 0 || !!carPublicPlan?.edges?.length)) || + (settings.includeCarSuggestions && carPlan?.edges?.length > 0) || + !!carPublicPlan?.edges?.length || !!parkRidePlan?.edges?.length } /> ); } - const showCarPublicPlan = - settings.includeCarSuggestions && carPublicPlan.carPublicItineraryCount > 0; + const showCarPublicPlan = carPublicPlan.carPublicItineraryCount > 0; const showAltBar = !detailView && @@ -1254,8 +1253,8 @@ export default function ItineraryPage(props, context) { bikePlan?.edges?.length || bikePublicPlan?.edges?.length || parkRidePlan?.edges?.length || - (settings.includeCarSuggestions && - (carPlan?.edges?.length || carPublicPlan?.edges?.length))); + (settings.includeCarSuggestions && carPlan?.edges?.length) || + carPublicPlan?.edges?.length); const alternativeItineraryBar = showAltBar ? ( Date: Thu, 31 Oct 2024 10:41:14 +0200 Subject: [PATCH 13/60] Fix PLANTYPE.CARTRANSIT case in planParamUtil. --- app/util/planParamUtil.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/util/planParamUtil.js b/app/util/planParamUtil.js index fa68ba9517..37783b3218 100644 --- a/app/util/planParamUtil.js +++ b/app/util/planParamUtil.js @@ -204,11 +204,7 @@ export function planQueryNeeded( ); case PLANTYPE.CARTRANSIT: - return ( - transitModes.length > 0 && - settings.includeCarSuggestions && - config.carBoardingModes?.FERRY !== undefined - ); + return transitModes.length > 0 && settings.includeCarSuggestions; case PLANTYPE.SCOOTERTRANSIT: /* special logic: relaxed scooter query is made only if no networks allowed */ From d879234b3b306b66115848b3d8522d2ad3774430 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 31 Oct 2024 10:45:57 +0200 Subject: [PATCH 14/60] Add ? back to leg.to check to fix failing test. --- app/util/legUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/util/legUtils.js b/app/util/legUtils.js index e69931c110..1a1d01c14c 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -191,7 +191,7 @@ export function compressLegs(originalLegs, keepBicycleWalk = false) { let compressedLeg; let bikeParked = false; originalLegs.forEach((currentLeg, i) => { - if (currentLeg.to.vehicleParking && currentLeg.mode === LegMode.Bicycle) { + if (currentLeg.to?.vehicleParking && currentLeg.mode === LegMode.Bicycle) { bikeParked = true; } if (!compressedLeg) { From 21c4fbc138bc1c7fc517471d7641ca983b966a72 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 31 Oct 2024 14:23:05 +0200 Subject: [PATCH 15/60] Add checks for mergeCarDirectAndTransitPlans. --- app/component/itinerary/ItineraryPage.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index 2b2b6c120e..e6de741e92 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -852,9 +852,12 @@ export default function ItineraryPage(props, context) { // 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 + altStates[PLANTYPE.CARTRANSIT][0].loading === LOADSTATE.DONE && + settings.includeCarSuggestions && + config.carBoardingModes !== undefined ) { const plan = mergeCarDirectAndTransitPlans( altStates[PLANTYPE.CAR][0].plan, From 742b37683fe1036b23e75e195b58e03279f4d52d Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Mon, 16 Dec 2024 10:14:11 +0200 Subject: [PATCH 16/60] Prevent finished leg from reappearing as current --- .../navigator/hooks/useRealtimeLegs.js | 31 ++++++++++++++----- app/util/legUtils.js | 20 ++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js index 83bdc655d2..a3ac804665 100644 --- a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js +++ b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js @@ -1,8 +1,8 @@ import polyUtil from 'polyline-encoded'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { fetchQuery } from 'react-relay'; import { GeodeticToEcef, GeodeticToEnu } from '../../../../util/geo-utils'; -import { legTime } from '../../../../util/legUtils'; +import { legTime, isAnyLegPropertyIdentical } from '../../../../util/legUtils'; import { epochToIso } from '../../../../util/timeUtils'; import { legQuery } from '../../queries/LegQuery'; @@ -87,7 +87,7 @@ function matchLegEnds(legs) { } } -function getLegsOfInterest(legs, time) { +function getLegsOfInterest(legs, time, previousFinishedLeg) { if (!legs?.length) { return { firstLeg: undefined, @@ -99,12 +99,26 @@ function getLegsOfInterest(legs, time) { const firstLeg = legs[0]; const lastLeg = legs[legs.length - 1]; - const nextLeg = legs.find(({ start }) => legTime(start) > time); - const previousLeg = legs.findLast(({ end }) => legTime(end) < time); - const currentLeg = legs.find( + const nextLegIdx = legs.findIndex(({ start }) => legTime(start) > time); + const currentLegIdx = legs.findIndex( ({ start, end }) => legTime(start) <= time && legTime(end) >= time, ); + let previousLeg = legs.findLast(({ end }) => legTime(end) < time); + let nextLeg = legs[nextLegIdx]; + let currentLeg = legs[currentLegIdx]; + + if ( + isAnyLegPropertyIdentical(currentLeg, previousFinishedLeg, [ + 'legId', + 'legGeometry.points', + ]) + ) { + previousLeg = currentLeg; + currentLeg = nextLeg; + nextLeg = legs[nextLegIdx + 1]; + } + return { firstLeg, lastLeg, @@ -117,6 +131,7 @@ function getLegsOfInterest(legs, time) { const useRealtimeLegs = (relayEnvironment, initialLegs = []) => { const [realTimeLegs, setRealTimeLegs] = useState(); const [time, setTime] = useState(Date.now()); + const previousFinishedLeg = useRef(undefined); const origin = useMemo( () => GeodeticToEcef(initialLegs[0].from.lat, initialLegs[0].from.lon), @@ -200,7 +215,9 @@ const useRealtimeLegs = (relayEnvironment, initialLegs = []) => { }, [fetchAndSetRealtimeLegs]); const { firstLeg, lastLeg, currentLeg, nextLeg, previousLeg } = - getLegsOfInterest(realTimeLegs, time); + getLegsOfInterest(realTimeLegs, time, previousFinishedLeg.current); + + previousFinishedLeg.current = previousLeg; return { realTimeLegs, diff --git a/app/util/legUtils.js b/app/util/legUtils.js index 71f3dd35f0..f9f5c4c4ef 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -3,6 +3,26 @@ import get from 'lodash/get'; import { BIKEAVL_UNKNOWN } from './vehicleRentalUtils'; import { getRouteMode } from './modeUtils'; +function getNestedValue(obj, path) { + return path.split('.').reduce((acc, part) => acc && acc[part], obj); +} + +export function isAnyLegPropertyIdentical(leg1, leg2, properties) { + if (!leg1 || !leg2) { + return false; + } + + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + const val1 = getNestedValue(leg1, property); + const val2 = getNestedValue(leg2, property); + if (val1 && val2 && val1 === val2) { + return true; + } + } + return false; +} + /** * Get time as milliseconds since the Unix Epoch */ From f3550dd6aeafed40bf0d27e635001a98088035bc Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Tue, 17 Dec 2024 11:50:06 +0200 Subject: [PATCH 17/60] Filter out car and bike transit itineraries that have a higher duration than a direct itinerary if it exists. --- app/component/itinerary/ItineraryPage.js | 3 ++ app/component/itinerary/ItineraryPageUtils.js | 35 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index e6de741e92..aa39d957ea 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -836,16 +836,19 @@ export default function ItineraryPage(props, context) { // merge two separate bike + transit plans into one useEffect(() => { if ( + altStates[PLANTYPE.BIKE][0].loading === LOADSTATE.DONE && altStates[PLANTYPE.BIKEPARK][0].loading === LOADSTATE.DONE && altStates[PLANTYPE.BIKETRANSIT][0].loading === LOADSTATE.DONE ) { const plan = mergeBikeTransitPlans( + altStates[PLANTYPE.BIKE][0].plan, altStates[PLANTYPE.BIKEPARK][0].plan, altStates[PLANTYPE.BIKETRANSIT][0].plan, ); setBikePublicState({ plan }); } }, [ + altStates[PLANTYPE.BIKE][0].plan, altStates[PLANTYPE.BIKEPARK][0].plan, altStates[PLANTYPE.BIKETRANSIT][0].plan, ]); diff --git a/app/component/itinerary/ItineraryPageUtils.js b/app/component/itinerary/ItineraryPageUtils.js index d8d81d2255..5f596e66db 100644 --- a/app/component/itinerary/ItineraryPageUtils.js +++ b/app/component/itinerary/ItineraryPageUtils.js @@ -428,15 +428,28 @@ export function filterItineraries(edges, modes) { /** * Pick combination of itineraries for bike and transit */ -export function mergeBikeTransitPlans(bikeParkPlan, bikeTransitPlan) { +export function mergeBikeTransitPlans( + bikeDirectPlan, + bikeParkPlan, + bikeTransitPlan, +) { + const bikeDirectPlanEdges = bikeDirectPlan?.edges || []; // filter plain walking / biking away, and also no biking const bikeParkEdges = transitEdges(bikeParkPlan?.edges).filter( i => getTotalBikingDistance(i.node) > 0, ); - const bikePublicEdges = transitEdges(bikeTransitPlan?.edges).filter( + let bikePublicEdges = transitEdges(bikeTransitPlan?.edges).filter( i => getTotalBikingDistance(i.node) > 0, ); + // If the bike direct plan has a shorter duration than a transit plan, the transit plan is filtered out. + if (bikeDirectPlanEdges.length === 1) { + bikePublicEdges = bikePublicEdges.filter( + itinerary => + itinerary.node.duration <= bikeDirectPlanEdges[0].node.duration, + ); + } + // show 6 bike + transit itineraries, preferably 3 of both kind. // If there is not enough of a kind, take more from the other kind let n1 = bikeParkEdges.length; @@ -470,14 +483,22 @@ export function mergeBikeTransitPlans(bikeParkPlan, bikeTransitPlan) { /** * Merge the direct car plan with the car transit plan. */ -export function mergeCarDirectAndTransitPlans(carPlan, carTransitPlan) { - const carPlanEdges = carPlan?.edges || []; - const carPublicEdges = carTransitPlan?.edges || []; +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: [...carPlanEdges, ...carPublicEdges], - carDirectItineraryCount: carPlanEdges.length, + edges: [...carDirectPlanEdges, ...carPublicEdges], + carDirectItineraryCount: carDirectPlanEdges.length, carPublicItineraryCount: carPublicEdges.length, }; } From 8df1f990896fad91e560a8cbe7a2d75140622125 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 18 Dec 2024 09:33:36 +0200 Subject: [PATCH 18/60] fix: time based remaining traversal cannot exceed value 1 --- app/component/itinerary/navigator/NaviUtils.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index a21da09687..a2bbb11d48 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -91,7 +91,10 @@ export function getRemainingTraversal(leg, pos, origin, time) { return 1.0 - traversed; } // estimate from elapsed time - return Math.max((legTime(leg.end) - time) / (leg.duration * 1000), 0); + return Math.min( + Math.max((legTime(leg.end) - time) / (leg.duration * 1000), 0), + 1.0, + ); } function findTransferProblems(legs, time, position, origin) { From fa0a60b55a6103e2fc2dc9246fc291e0cc81a0b3 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 18 Dec 2024 09:41:03 +0200 Subject: [PATCH 19/60] fix: consider available time when estimating wal/transfer progress --- app/component/itinerary/navigator/NaviInstructions.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index 6f85368c03..a307d08bd7 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -25,8 +25,13 @@ export default function NaviInstructions( origin, time, ); - const duration = leg.duration * remainingTraversal; const distance = leg.distance * remainingTraversal; + let duration = leg.duration * remainingTraversal; + const maxDur = (legTime(leg.end) - time) / 1000; + if (maxDur < duration) { + // duration cannot exceed available time + duration = maxDur; + } return ( <> From ce7e9f56fb761ad98bcfdce814b5b2b053646672 Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Wed, 18 Dec 2024 09:52:09 +0200 Subject: [PATCH 20/60] Added jsdoc for new functions --- app/util/legUtils.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/app/util/legUtils.js b/app/util/legUtils.js index f9f5c4c4ef..cf3e8fdc8e 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -1,13 +1,34 @@ import cloneDeep from 'lodash/cloneDeep'; import get from 'lodash/get'; -import { BIKEAVL_UNKNOWN } from './vehicleRentalUtils'; import { getRouteMode } from './modeUtils'; +import { BIKEAVL_UNKNOWN } from './vehicleRentalUtils'; -function getNestedValue(obj, path) { - return path.split('.').reduce((acc, part) => acc && acc[part], obj); +/** + * Gets a (nested) property value from an object + * + * @param {Object.} obj object with properties i.e. { foo: 'bar', baz: {qux: 'quux'} } + * @param {string} propertyString string representation of object property i.e. foo, baz.qux + * @returns {Object} + */ +function getNestedValue(obj, propertyString) { + return propertyString.split('.').reduce((acc, part) => acc && acc[part], obj); } +/** + * Compares if given legs share any of the given properties. + * Can be used to check if two separate leg objects are identical + * Returns true if both legs are null|undefined + * + * @param {Object.|undefined} leg1 + * @param {Object.|undefined} leg2 + * @param {string[]} properties list of object fields to compare i.e. ['foo', 'bar.baz'] + * @returns {boolean} + */ export function isAnyLegPropertyIdentical(leg1, leg2, properties) { + if (leg1 === leg2) { + return true; + } + if (!leg1 || !leg2) { return false; } From 28d8a89c22f4595270f8de353e8af42054d7abe0 Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Wed, 18 Dec 2024 15:06:06 +0200 Subject: [PATCH 21/60] Added LegMode.Wait pseudomode --- app/component/itinerary/Itinerary.js | 3 ++- app/component/map/ItineraryLine.js | 3 ++- app/util/legUtils.js | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/Itinerary.js b/app/component/itinerary/Itinerary.js index acf5d4de2a..8394f51440 100644 --- a/app/component/itinerary/Itinerary.js +++ b/app/component/itinerary/Itinerary.js @@ -24,6 +24,7 @@ import { getTotalDistance, legTime, legTimeStr, + LegMode, } from '../../util/legUtils'; import { dateOrEmpty, isTomorrow, timeStr } from '../../util/timeUtils'; import withBreakpoint from '../../util/withBreakpoint'; @@ -612,7 +613,7 @@ const Itinerary = ( renderModeIcons={renderModeIcons} duration={waitingTimeinMin} isTransitLeg={false} - mode="WAIT" + mode={LegMode.Wait} large={breakpoint === 'large'} />, ); diff --git a/app/component/map/ItineraryLine.js b/app/component/map/ItineraryLine.js index 7aaf885779..fa96bc0ccf 100644 --- a/app/component/map/ItineraryLine.js +++ b/app/component/map/ItineraryLine.js @@ -9,6 +9,7 @@ import { getInterliningLegs, getLegText, isCallAgencyLeg, + LegMode, } from '../../util/legUtils'; import { getRouteMode } from '../../util/modeUtils'; import { configShape, legShape } from '../../util/shapes'; @@ -62,7 +63,7 @@ class ItineraryLine extends React.Component { const transitLegs = []; this.props.legs.forEach((leg, i) => { - if (!leg || leg.mode === 'WAIT') { + if (!leg || leg.mode === LegMode.Wait) { return; } const nextLeg = this.props.legs[i + 1]; diff --git a/app/util/legUtils.js b/app/util/legUtils.js index cf3e8fdc8e..366a8e833d 100644 --- a/app/util/legUtils.js +++ b/app/util/legUtils.js @@ -122,6 +122,7 @@ export const LegMode = { Walk: 'WALK', Car: 'CAR', Rail: 'RAIL', + Wait: 'WAIT', }; /** From 92f0117061a7fa8c986dfc2429723d283098ba48 Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Wed, 18 Dec 2024 16:46:11 +0200 Subject: [PATCH 22/60] Use dummy leg enriched leg data to deduce legs of interest --- .../navigator/hooks/useRealtimeLegs.js | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js index a3ac804665..30e7206033 100644 --- a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js +++ b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js @@ -2,7 +2,11 @@ import polyUtil from 'polyline-encoded'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { fetchQuery } from 'react-relay'; import { GeodeticToEcef, GeodeticToEnu } from '../../../../util/geo-utils'; -import { legTime, isAnyLegPropertyIdentical } from '../../../../util/legUtils'; +import { + isAnyLegPropertyIdentical, + legTime, + LegMode, +} from '../../../../util/legUtils'; import { epochToIso } from '../../../../util/timeUtils'; import { legQuery } from '../../queries/LegQuery'; @@ -87,8 +91,8 @@ function matchLegEnds(legs) { } } -function getLegsOfInterest(legs, time, previousFinishedLeg) { - if (!legs?.length) { +function getLegsOfInterest(initialLegs, time, previousFinishedLeg) { + if (!initialLegs?.length) { return { firstLeg: undefined, lastLeg: undefined, @@ -97,18 +101,28 @@ function getLegsOfInterest(legs, time, previousFinishedLeg) { }; } - const firstLeg = legs[0]; - const lastLeg = legs[legs.length - 1]; + const legs = initialLegs.reduce((acc, curr, i, arr) => { + acc.push(curr); + const next = arr[i + 1]; + + // A wait leg is added, if next leg exists but it does not start when current ends + if (next && legTime(curr.end) !== legTime(next.start)) { + acc.push({ mode: LegMode.Wait, start: curr.end, end: next.start }); + } + + return acc; + }, []); + const nextLegIdx = legs.findIndex(({ start }) => legTime(start) > time); - const currentLegIdx = legs.findIndex( + let currentLeg = legs.find( ({ start, end }) => legTime(start) <= time && legTime(end) >= time, ); - let previousLeg = legs.findLast(({ end }) => legTime(end) < time); let nextLeg = legs[nextLegIdx]; - let currentLeg = legs[currentLegIdx]; + // Indices are shifted by one if a previously completed leg reappears as current. if ( + nextLeg && isAnyLegPropertyIdentical(currentLeg, previousFinishedLeg, [ 'legId', 'legGeometry.points', @@ -119,12 +133,13 @@ function getLegsOfInterest(legs, time, previousFinishedLeg) { nextLeg = legs[nextLegIdx + 1]; } + // return wait legs as undefined as they are not a global concept return { - firstLeg, - lastLeg, - previousLeg, - currentLeg, - nextLeg, + firstLeg: legs[0], + lastLeg: legs[legs.length - 1], + previousLeg: previousLeg?.mode === LegMode.Wait ? undefined : previousLeg, + currentLeg: currentLeg?.mode === LegMode.Wait ? undefined : currentLeg, + nextLeg: nextLeg?.mode === LegMode.Wait ? undefined : nextLeg, }; } From c60bdaf2792655b6a279d046fb1825d2e2bce472 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Thu, 19 Dec 2024 09:42:37 +0200 Subject: [PATCH 23/60] Remove bike transit itinerary filtering functionality. --- app/component/itinerary/ItineraryPage.js | 3 --- app/component/itinerary/ItineraryPageUtils.js | 17 ++--------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index 70facbd40c..36741b43e2 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -878,19 +878,16 @@ export default function ItineraryPage(props, context) { // merge two separate bike + transit plans into one useEffect(() => { if ( - altStates[PLANTYPE.BIKE][0].loading === LOADSTATE.DONE && altStates[PLANTYPE.BIKEPARK][0].loading === LOADSTATE.DONE && altStates[PLANTYPE.BIKETRANSIT][0].loading === LOADSTATE.DONE ) { const plan = mergeBikeTransitPlans( - altStates[PLANTYPE.BIKE][0].plan, altStates[PLANTYPE.BIKEPARK][0].plan, altStates[PLANTYPE.BIKETRANSIT][0].plan, ); setBikePublicState({ plan }); } }, [ - altStates[PLANTYPE.BIKE][0].plan, altStates[PLANTYPE.BIKEPARK][0].plan, altStates[PLANTYPE.BIKETRANSIT][0].plan, ]); diff --git a/app/component/itinerary/ItineraryPageUtils.js b/app/component/itinerary/ItineraryPageUtils.js index 8f2be6b993..1048a54be2 100644 --- a/app/component/itinerary/ItineraryPageUtils.js +++ b/app/component/itinerary/ItineraryPageUtils.js @@ -428,28 +428,15 @@ export function filterItineraries(edges, modes) { /** * Pick combination of itineraries for bike and transit */ -export function mergeBikeTransitPlans( - bikeDirectPlan, - bikeParkPlan, - bikeTransitPlan, -) { - const bikeDirectPlanEdges = bikeDirectPlan?.edges || []; +export function mergeBikeTransitPlans(bikeParkPlan, bikeTransitPlan) { // filter plain walking / biking away, and also no biking const bikeParkEdges = transitEdges(bikeParkPlan?.edges).filter( i => getTotalBikingDistance(i.node) > 0, ); - let bikePublicEdges = transitEdges(bikeTransitPlan?.edges).filter( + const bikePublicEdges = transitEdges(bikeTransitPlan?.edges).filter( i => getTotalBikingDistance(i.node) > 0, ); - // If the bike direct plan has a shorter duration than a transit plan, the transit plan is filtered out. - if (bikeDirectPlanEdges.length === 1) { - bikePublicEdges = bikePublicEdges.filter( - itinerary => - itinerary.node.duration <= bikeDirectPlanEdges[0].node.duration, - ); - } - // show 6 bike + transit itineraries, preferably 3 of both kind. // If there is not enough of a kind, take more from the other kind let n1 = bikeParkEdges.length; From 2e3c7795c66df67e091494946925323d86459f26 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Thu, 19 Dec 2024 10:00:38 +0200 Subject: [PATCH 24/60] fix top card width fix broken alert styles small style fixes --- .../itinerary/navigator/NaviUtils.js | 3 +- .../itinerary/navigator/navigator.scss | 48 ++++++++++--------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index a21da09687..15ea24fbc8 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -321,7 +321,7 @@ export const getItineraryAlerts = ( {alert.alertHeaderText}
), - id: alert.id, + id: `${alert.effectiveStartDate}-${alert.alertDescriptionText}`, })); }); const abortTrip = ; @@ -368,6 +368,7 @@ export const getItineraryAlerts = ( content, id: `canceled-${legId}`, hideClose: true, + expiresOn: alert.effectiveEndDate, }); } }); diff --git a/app/component/itinerary/navigator/navigator.scss b/app/component/itinerary/navigator/navigator.scss index 19faa89d6e..2a50a02f99 100644 --- a/app/component/itinerary/navigator/navigator.scss +++ b/app/component/itinerary/navigator/navigator.scss @@ -51,9 +51,7 @@ } .navitop { - margin-bottom: 5px; - width: 92%; - margin-left: 8px; + margin: 0 8px 5px 8px; border-radius: 8px; min-height: 70px; color: black; @@ -62,6 +60,7 @@ align-items: center; letter-spacing: -0.3px; box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.2); + width: calc(100vw - 16px); &.expanded { max-height: unset; @@ -120,10 +119,9 @@ .wait-leg { display: flex; - align-items: center; margin-top: 3px; flex-direction: column; - align-self: flex-start; + align-items: flex-start; .route-info { display: flex; @@ -365,9 +363,10 @@ .info-stack { position: fixed; height: 69px; - margin-left: 8px; - width: 90%; letter-spacing: -0.3px; + width: calc(100% - 16px); + margin-right: 8px; + margin-left: 8px; div:first-child { margin-top: 0; @@ -418,6 +417,8 @@ .navi-alert-content { width: 100%; margin: 0 8px; + display: flex; + flex-direction: column; } } @@ -428,6 +429,7 @@ display: flex; flex-direction: column; margin: 0 8px; + width: 100%; span:first-child { font-weight: $font-weight-medium; @@ -443,24 +445,24 @@ font-size: $font-size-small; } } + } - .alt-btn { - display: flex; - flex-direction: column; - width: 100%; - - .show-options { - padding: var(--space-s, 16px) var(--space-xs, 8px) - var(--space-s, 16px) var(--space-s, 16px); - background: #0074bf; - color: #fff; - border-radius: 999px; // var(--radius-radius-medium, 8px); - margin-top: var(--space-xxs); + .alt-btn { + display: flex; + flex-direction: column; + width: 100%; - /* box-shadow-card-s-strong */ - box-shadow: 0 2px 4px 0 - var(--color-shadow-strong, rgba(51, 51, 51, 0.2)); - } + .show-options { + padding: var(--space-s, 16px) var(--space-xs, 8px) var(--space-s, 16px) + var(--space-s, 16px); + background: #0074bf; + color: #fff; + border-radius: 999px; // var(--radius-radius-medium, 8px); + margin-top: var(--space-xxs); + + /* box-shadow-card-s-strong */ + box-shadow: 0 2px 4px 0 + var(--color-shadow-strong, rgba(51, 51, 51, 0.2)); } } From b22a088e75ed72919e1c6850f5d8c7ab4d62e8be Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 19 Dec 2024 14:28:41 +0200 Subject: [PATCH 25/60] fix: do not use static leg duration for estimates --- .../itinerary/navigator/NaviInstructions.js | 12 +++--------- app/component/itinerary/navigator/NaviUtils.js | 6 ++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index a307d08bd7..7b0986866f 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -25,13 +25,7 @@ export default function NaviInstructions( origin, time, ); - const distance = leg.distance * remainingTraversal; - let duration = leg.duration * remainingTraversal; - const maxDur = (legTime(leg.end) - time) / 1000; - if (maxDur < duration) { - // duration cannot exceed available time - duration = maxDur; - } + const distance = remainingTraversal * leg.distance; return ( <> @@ -42,8 +36,8 @@ export default function NaviInstructions(
- {displayDistance(distance, config, intl.formatNumber)} ( - {durationToString(duration * 1000)}) + {displayDistance(distance, config, intl.formatNumber)} + {durationToString(legTime(leg.end) - time)}
); diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index a2bbb11d48..6cf7f54b9d 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -91,10 +91,8 @@ export function getRemainingTraversal(leg, pos, origin, time) { return 1.0 - traversed; } // estimate from elapsed time - return Math.min( - Math.max((legTime(leg.end) - time) / (leg.duration * 1000), 0), - 1.0, - ); + const duration = Math.max(legTime(leg.end) - legTime(leg.start), 1); // min 1 ms + return Math.min(Math.max((legTime(leg.end) - time) / duration, 0), 1.0); } function findTransferProblems(legs, time, position, origin) { From 3638a6d8f6b2c2ea0facd5d70a85f2b6e78681a1 Mon Sep 17 00:00:00 2001 From: Teemu Kalvas Date: Thu, 19 Dec 2024 14:54:45 +0200 Subject: [PATCH 26/60] disable via point routing for Waltti --- app/configurations/config.waltti.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/configurations/config.waltti.js b/app/configurations/config.waltti.js index a9f1e350b9..47f07781d2 100644 --- a/app/configurations/config.waltti.js +++ b/app/configurations/config.waltti.js @@ -303,6 +303,8 @@ export default { }, analyticsClass: 'plausible-event-name=Ticket+Purchase+Link', + viaPointsEnabled: false, + // features that should not be deployed to production experimental: { navigation: From 0d1563ddf8cefece528f4dacf4e2780e2579beb2 Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Thu, 19 Dec 2024 15:37:13 +0200 Subject: [PATCH 27/60] Fix issue in flakiness correction --- .../navigator/hooks/useRealtimeLegs.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js index 30e7206033..83dd513fe3 100644 --- a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js +++ b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js @@ -107,7 +107,13 @@ function getLegsOfInterest(initialLegs, time, previousFinishedLeg) { // A wait leg is added, if next leg exists but it does not start when current ends if (next && legTime(curr.end) !== legTime(next.start)) { - acc.push({ mode: LegMode.Wait, start: curr.end, end: next.start }); + acc.push({ + id: null, + legGeometry: { points: null }, + mode: LegMode.Wait, + start: curr.end, + end: next.start, + }); } return acc; @@ -122,7 +128,6 @@ function getLegsOfInterest(initialLegs, time, previousFinishedLeg) { // Indices are shifted by one if a previously completed leg reappears as current. if ( - nextLeg && isAnyLegPropertyIdentical(currentLeg, previousFinishedLeg, [ 'legId', 'legGeometry.points', @@ -130,16 +135,15 @@ function getLegsOfInterest(initialLegs, time, previousFinishedLeg) { ) { previousLeg = currentLeg; currentLeg = nextLeg; - nextLeg = legs[nextLegIdx + 1]; + nextLeg = nextLegIdx !== -1 ? legs[nextLegIdx + 1] : undefined; } - // return wait legs as undefined as they are not a global concept return { firstLeg: legs[0], lastLeg: legs[legs.length - 1], - previousLeg: previousLeg?.mode === LegMode.Wait ? undefined : previousLeg, - currentLeg: currentLeg?.mode === LegMode.Wait ? undefined : currentLeg, - nextLeg: nextLeg?.mode === LegMode.Wait ? undefined : nextLeg, + previousLeg, + currentLeg, + nextLeg, }; } @@ -234,15 +238,16 @@ const useRealtimeLegs = (relayEnvironment, initialLegs = []) => { previousFinishedLeg.current = previousLeg; + // return wait legs as undefined as they are not a global concept return { realTimeLegs, time, origin, firstLeg, lastLeg, - previousLeg, - currentLeg, - nextLeg, + previousLeg: previousLeg?.mode === LegMode.Wait ? undefined : previousLeg, + currentLeg: currentLeg?.mode === LegMode.Wait ? undefined : currentLeg, + nextLeg: nextLeg?.mode === LegMode.Wait ? undefined : nextLeg, }; }; From eaad04c9ef738e10b305e1b2bb9ba97efe7b9c6a Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Thu, 19 Dec 2024 15:38:26 +0200 Subject: [PATCH 28/60] Prevent negative remainingDuration --- app/component/itinerary/navigator/NaviInstructions.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index 6f85368c03..f66a4fceb5 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -48,7 +48,10 @@ export default function NaviInstructions( const hs = headsign || nextLeg.trip?.tripHeadsign; const localizedMode = getLocalizedMode(mode, intl); - const remainingDuration = Math.ceil((legTime(start) - time) / 60000); // ms to minutes + const remainingDuration = Math.max( + Math.ceil((legTime(start) - time) / 60000), + 0, + ); // ms to minutes const rt = nextLeg.realtimeState === 'UPDATED'; const values = { duration: withRealTime(rt, remainingDuration), From 69dfcb0ce22a5cb5cb81b9935d73e8954bdfb229 Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Thu, 19 Dec 2024 16:44:44 +0200 Subject: [PATCH 29/60] Added missing Math.max call --- app/component/itinerary/navigator/NaviInstructions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index f66a4fceb5..9676c0879f 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -51,7 +51,7 @@ export default function NaviInstructions( const remainingDuration = Math.max( Math.ceil((legTime(start) - time) / 60000), 0, - ); // ms to minutes + ); // ms to minutes, >= 0 const rt = nextLeg.realtimeState === 'UPDATED'; const values = { duration: withRealTime(rt, remainingDuration), @@ -104,7 +104,7 @@ export default function NaviInstructions( : intl.formatMessage({ id: 'navileg-from-stop' }); const localizedMode = getLocalizedMode(leg.mode, intl); - const remainingDuration = Math.ceil((t - time) / 60000); // ms to minutes + const remainingDuration = Math.max(Math.ceil((t - time) / 60000), 0); // ms to minutes, >= 0 const values = { stopOrStation, stop: leg.to.stop.name, From 981ea147544924c66352050da78448e04621974d Mon Sep 17 00:00:00 2001 From: Simo Partinen Date: Thu, 19 Dec 2024 18:14:11 +0200 Subject: [PATCH 30/60] Prevent locationPopup during navimode --- app/component/itinerary/ItineraryPage.js | 1 + app/component/map/ItineraryPageMap.js | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/component/itinerary/ItineraryPage.js b/app/component/itinerary/ItineraryPage.js index 36741b43e2..0df1e807e4 100644 --- a/app/component/itinerary/ItineraryPage.js +++ b/app/component/itinerary/ItineraryPage.js @@ -1103,6 +1103,7 @@ export default function ItineraryPage(props, context) { objectsToHide={objectsToHide} itinerary={explicitItinerary} showBackButton={!naviMode} + isLocationPopupEnabled={!naviMode} /> ); } diff --git a/app/component/map/ItineraryPageMap.js b/app/component/map/ItineraryPageMap.js index b17455e2af..b874b30d9d 100644 --- a/app/component/map/ItineraryPageMap.js +++ b/app/component/map/ItineraryPageMap.js @@ -32,6 +32,7 @@ function ItineraryPageMap( showDurationBubble, itinerary, showBackButton, + isLocationPopupEnabled, ...rest }, { match, router, executeAction, config }, @@ -99,13 +100,18 @@ function ItineraryPageMap( leafletObjs.push(); }); - // max 5 viapoints - const locationPopup = - config.viaPointsEnabled && viaPoints.length < 5 - ? 'all' - : 'origindestination'; - const onSelectLocation = (item, id) => - onLocationPopup(item, id, router, match, executeAction); + let locationPopup = 'none'; + let onSelectLocation; + + if (isLocationPopupEnabled) { + // max 5 viapoints + locationPopup = + config.viaPointsEnabled && viaPoints.length < 5 + ? 'all' + : 'origindestination'; + onSelectLocation = (item, id) => + onLocationPopup(item, id, router, match, executeAction); + } return ( Date: Fri, 20 Dec 2024 08:21:34 +0200 Subject: [PATCH 31/60] top card leg change animation navistack no longer needs top position calculation --- .../itinerary/navigator/NaviCardContainer.js | 74 ++++++++++--------- .../itinerary/navigator/NaviStack.js | 5 +- .../itinerary/navigator/navigator.scss | 19 +++++ 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index e0a616d884..0125f353bd 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -24,6 +24,26 @@ function addMessages(incominMessages, newMessages) { incominMessages.set(m.id, m); }); } + +const handleLegChange = (leg, firstLeg, time) => { + let legType; + if (time < legTime(firstLeg.start)) { + legType = LEGTYPE.PENDING; + } else if (leg) { + if (!leg.transitLeg) { + if (leg.current >= TIME_AT_DESTINATION) { + legType = LEGTYPE.WAIT; + } else { + legType = LEGTYPE.MOVE; + } + } else { + legType = LEGTYPE.TRANSIT; + } + } else { + legType = LEGTYPE.WAIT; + } + return legType; +}; function NaviCardContainer( { focusToLeg, @@ -45,8 +65,7 @@ function NaviCardContainer( const [messages, setMessages] = useState(new Map()); // notifications that are shown to the user. const [activeMessages, setActiveMessages] = useState([]); - const [topPosition, setTopPosition] = useState(0); - + const [legChanging, setLegChanging] = useState(false); const legRef = useRef(currentLeg); const focusRef = useRef(false); // Destination counter. How long user has been at the destination. * 10 seconds @@ -73,14 +92,6 @@ function NaviCardContainer( updateClient(topics, context); }, []); - useEffect(() => { - if (cardRef.current) { - const contentHeight = cardRef.current.getBoundingClientRect(); - // Navistack top position depending on main card height. - setTopPosition(contentHeight.bottom + TOPBAR_PADDING); - } - }, [currentLeg, cardExpanded]); - useEffect(() => { const incomingMessages = new Map(); @@ -116,6 +127,10 @@ function NaviCardContainer( if (legChanged) { updateClient(topics, context); setCardExpanded(false); + setLegChanging(true); + setTimeout(() => { + setLegChanging(false); + }, 3000); if (currentLeg) { focusToLeg?.(currentLeg); } @@ -163,30 +178,23 @@ function NaviCardContainer( } }, [time]); - let legType; - - if (time < legTime(firstLeg.start)) { - legType = LEGTYPE.PENDING; - } else if (currentLeg) { - if (!currentLeg.transitLeg) { - if (destCountRef.current >= TIME_AT_DESTINATION) { - legType = LEGTYPE.WAIT; - } else { - legType = LEGTYPE.MOVE; - } - } else { - legType = LEGTYPE.TRANSIT; - } - } else { - legType = LEGTYPE.WAIT; - } + // LegChange fires animation, we need to keep the old data until card goes ot of the view. + const l = legChanging ? lastLeg : currentLeg; + const legType = handleLegChange(l, firstLeg, time); const containerTopPosition = mapLayerRef.current.getBoundingClientRect().top + TOPBAR_PADDING; - + let className; + if (isJourneyCompleted) { + className = 'slide-out'; + } else if (legChanging) { + className = 'hide-card'; + } else { + className = 'show-card'; + } return (
{activeMessages.length > 0 && ( - + )}
); diff --git a/app/component/itinerary/navigator/NaviStack.js b/app/component/itinerary/navigator/NaviStack.js index 2e4ca62292..876407acc8 100644 --- a/app/component/itinerary/navigator/NaviStack.js +++ b/app/component/itinerary/navigator/NaviStack.js @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import NaviMessage from './NaviMessage'; -const NaviStack = ({ messages, handleRemove, topPosition }) => { +const NaviStack = ({ messages, handleRemove }) => { return ( -
+
{messages.map((notification, index) => ( Date: Fri, 20 Dec 2024 08:42:08 +0200 Subject: [PATCH 32/60] adjust animation time hide walk duration and distance while animation is running --- app/component/itinerary/navigator/NaviCard.js | 16 +++++++++++++++- .../itinerary/navigator/NaviCardContainer.js | 3 ++- .../itinerary/navigator/NaviInstructions.js | 14 +++++++++----- app/component/itinerary/navigator/navigator.scss | 4 ++-- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCard.js b/app/component/itinerary/navigator/NaviCard.js index 75eaac285f..0fee27a2b5 100644 --- a/app/component/itinerary/navigator/NaviCard.js +++ b/app/component/itinerary/navigator/NaviCard.js @@ -26,7 +26,17 @@ const iconMap = { }; export default function NaviCard( - { leg, nextLeg, legType, cardExpanded, startTime, time, position, origin }, + { + leg, + nextLeg, + legType, + cardExpanded, + startTime, + time, + position, + origin, + legChanging, + }, { config }, ) { if (legType === LEGTYPE.PENDING) { @@ -79,6 +89,7 @@ export default function NaviCard( time={time} position={position} origin={origin} + legChanging={legChanging} />
@@ -115,6 +126,8 @@ NaviCard.propTypes = { x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }).isRequired, + // Hide distance and duration caluclation when legchange animation is running + legChanging: PropTypes.bool, }; NaviCard.defaultProps = { cardExpanded: false, @@ -122,6 +135,7 @@ NaviCard.defaultProps = { nextLeg: undefined, startTime: '', position: undefined, + legChanging: false, }; NaviCard.contextTypes = { diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 0125f353bd..f59f42e5d9 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -130,7 +130,7 @@ function NaviCardContainer( setLegChanging(true); setTimeout(() => { setLegChanging(false); - }, 3000); + }, 2000); if (currentLeg) { focusToLeg?.(currentLeg); } @@ -213,6 +213,7 @@ function NaviCardContainer( time={time} position={position} origin={origin} + legChanging={legChanging} />
diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index 6f85368c03..d25273235f 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -11,7 +11,7 @@ import { durationToString } from '../../../util/timeUtils'; import { getRouteMode } from '../../../util/modeUtils'; export default function NaviInstructions( - { leg, nextLeg, instructions, legType, time, position, origin }, + { leg, nextLeg, instructions, legType, time, position, origin, legChanging }, { intl, config }, ) { const withRealTime = (rt, children) => ( @@ -36,10 +36,12 @@ export default function NaviInstructions( {legDestination(intl, leg, null, nextLeg)}
-
- {displayDistance(distance, config, intl.formatNumber)} ( - {durationToString(duration * 1000)}) -
+ {!legChanging && ( +
+ {displayDistance(distance, config, intl.formatNumber)} ( + {durationToString(duration * 1000)}) +
+ )} ); } @@ -145,6 +147,7 @@ NaviInstructions.propTypes = { x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }).isRequired, + legChanging: PropTypes.bool, }; NaviInstructions.defaultProps = { @@ -152,6 +155,7 @@ NaviInstructions.defaultProps = { leg: undefined, nextLeg: undefined, position: undefined, + legChanging: false, }; NaviInstructions.contextTypes = { intl: intlShape.isRequired, diff --git a/app/component/itinerary/navigator/navigator.scss b/app/component/itinerary/navigator/navigator.scss index ced8c81241..40388cd7cf 100644 --- a/app/component/itinerary/navigator/navigator.scss +++ b/app/component/itinerary/navigator/navigator.scss @@ -50,12 +50,12 @@ } &.hide-card { - animation: hideTopCard 3s ease-out forwards; + animation: hideTopCard 2s ease-out forwards; pointer-events: none; } &.show-card { - animation: slideDownFromTop 3s ease-out forwards; + animation: slideDownFromTop 2s ease-out forwards; } } From e75b3ec9418ed5c1ec9293aaba566930e6715cb7 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 20 Dec 2024 08:53:06 +0200 Subject: [PATCH 33/60] use previousleg, not last leg in animation --- app/component/itinerary/navigator/NaviCardContainer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index f59f42e5d9..64586ac065 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -56,6 +56,7 @@ function NaviCardContainer( nextLeg, firstLeg, lastLeg, + previousLeg, isJourneyCompleted, }, context, @@ -179,7 +180,7 @@ function NaviCardContainer( }, [time]); // LegChange fires animation, we need to keep the old data until card goes ot of the view. - const l = legChanging ? lastLeg : currentLeg; + const l = legChanging ? previousLeg : currentLeg; const legType = handleLegChange(l, firstLeg, time); const containerTopPosition = @@ -242,6 +243,7 @@ NaviCardContainer.propTypes = { nextLeg: legShape, firstLeg: legShape, lastLeg: legShape, + previousLeg: legShape, isJourneyCompleted: PropTypes.bool, /* @@ -256,6 +258,7 @@ NaviCardContainer.defaultProps = { nextLeg: undefined, firstLeg: undefined, lastLeg: undefined, + previousLeg: undefined, isJourneyCompleted: false, }; From d8828c0ad95774d07242878f76f6cdcdc7aa154c Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 20 Dec 2024 09:00:23 +0200 Subject: [PATCH 34/60] leg change control was unnecessary in naviInstructions --- app/component/itinerary/navigator/NaviCard.js | 16 +--------------- .../itinerary/navigator/NaviCardContainer.js | 1 - .../itinerary/navigator/NaviContainer.js | 1 + .../itinerary/navigator/NaviInstructions.js | 14 +++++--------- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCard.js b/app/component/itinerary/navigator/NaviCard.js index 0fee27a2b5..75eaac285f 100644 --- a/app/component/itinerary/navigator/NaviCard.js +++ b/app/component/itinerary/navigator/NaviCard.js @@ -26,17 +26,7 @@ const iconMap = { }; export default function NaviCard( - { - leg, - nextLeg, - legType, - cardExpanded, - startTime, - time, - position, - origin, - legChanging, - }, + { leg, nextLeg, legType, cardExpanded, startTime, time, position, origin }, { config }, ) { if (legType === LEGTYPE.PENDING) { @@ -89,7 +79,6 @@ export default function NaviCard( time={time} position={position} origin={origin} - legChanging={legChanging} />
@@ -126,8 +115,6 @@ NaviCard.propTypes = { x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }).isRequired, - // Hide distance and duration caluclation when legchange animation is running - legChanging: PropTypes.bool, }; NaviCard.defaultProps = { cardExpanded: false, @@ -135,7 +122,6 @@ NaviCard.defaultProps = { nextLeg: undefined, startTime: '', position: undefined, - legChanging: false, }; NaviCard.contextTypes = { diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 64586ac065..a2a21d7fc8 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -214,7 +214,6 @@ function NaviCardContainer( time={time} position={position} origin={origin} - legChanging={legChanging} />
diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index 505916eb41..227f32c486 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -86,6 +86,7 @@ function NaviContainer( firstLeg={firstLeg} lastLeg={lastLeg} isJourneyCompleted={isJourneyCompleted} + previousLeg={previousLeg} /> {isJourneyCompleted && isNavigatorIntroDismissed && ( ( @@ -36,12 +36,10 @@ export default function NaviInstructions( {legDestination(intl, leg, null, nextLeg)} - {!legChanging && ( -
- {displayDistance(distance, config, intl.formatNumber)} ( - {durationToString(duration * 1000)}) -
- )} +
+ {displayDistance(distance, config, intl.formatNumber)} ( + {durationToString(duration * 1000)}) +
); } @@ -147,7 +145,6 @@ NaviInstructions.propTypes = { x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, }).isRequired, - legChanging: PropTypes.bool, }; NaviInstructions.defaultProps = { @@ -155,7 +152,6 @@ NaviInstructions.defaultProps = { leg: undefined, nextLeg: undefined, position: undefined, - legChanging: false, }; NaviInstructions.contextTypes = { intl: intlShape.isRequired, From 864ab9c5319f8c6cb7a00067a4992ab1c487f114 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 20 Dec 2024 10:23:10 +0200 Subject: [PATCH 35/60] pr fixes --- app/component/itinerary/navigator/NaviCardContainer.js | 9 +++++++-- app/component/itinerary/navigator/navigator.scss | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index a2a21d7fc8..33548eca62 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -18,6 +18,7 @@ import { updateClient, getTopics } from '../ItineraryPageUtils'; const TIME_AT_DESTINATION = 3; // * 10 seconds const TOPBAR_PADDING = 8; // pixels +const HIDE_TOPCARD_DURATION = 2000; // milliseconds function addMessages(incominMessages, newMessages) { newMessages.forEach(m => { @@ -44,6 +45,7 @@ const handleLegChange = (leg, firstLeg, time) => { } return legType; }; + function NaviCardContainer( { focusToLeg, @@ -125,13 +127,14 @@ function NaviCardContainer( ...getAdditionalMessages(nextLeg, time, intl, config, messages), ]); } + let timeoutId; if (legChanged) { updateClient(topics, context); setCardExpanded(false); setLegChanging(true); - setTimeout(() => { + timeoutId = setTimeout(() => { setLegChanging(false); - }, 2000); + }, HIDE_TOPCARD_DURATION); if (currentLeg) { focusToLeg?.(currentLeg); } @@ -177,6 +180,8 @@ function NaviCardContainer( // Todo: this works in transit legs, but do we need additional logic for bikes / scooters? destCountRef.current = 0; } + + return () => clearTimeout(timeoutId); }, [time]); // LegChange fires animation, we need to keep the old data until card goes ot of the view. diff --git a/app/component/itinerary/navigator/navigator.scss b/app/component/itinerary/navigator/navigator.scss index 40388cd7cf..17e075c1d7 100644 --- a/app/component/itinerary/navigator/navigator.scss +++ b/app/component/itinerary/navigator/navigator.scss @@ -60,7 +60,7 @@ } .navitop { - margin: 0 8px 5px 8px; + margin: 0 var(--space-s) 5px var(--space-s); border-radius: 8px; min-height: 70px; color: black; @@ -69,7 +69,7 @@ align-items: center; letter-spacing: -0.3px; box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.2); - width: calc(100vw - 16px); + width: calc(100vw - var(--space-s) * 2); &.expanded { max-height: unset; @@ -373,7 +373,7 @@ position: fixed; height: 69px; letter-spacing: -0.3px; - width: calc(100% - 16px); + width: calc(100% - var(--space-s) * 2); margin-right: 8px; margin-left: 8px; From ed693c950a93964c022d3477f5c0d6bcdd38ab3f Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 20 Dec 2024 10:58:24 +0200 Subject: [PATCH 36/60] sss variable --- app/component/itinerary/navigator/navigator.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/navigator.scss b/app/component/itinerary/navigator/navigator.scss index 17e075c1d7..f219d79de1 100644 --- a/app/component/itinerary/navigator/navigator.scss +++ b/app/component/itinerary/navigator/navigator.scss @@ -1,3 +1,5 @@ +$fixed-width-padding: 16px; + .navi-start-container { padding: 0 10px; @@ -69,7 +71,7 @@ align-items: center; letter-spacing: -0.3px; box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.2); - width: calc(100vw - var(--space-s) * 2); + width: calc(100vw - #{$fixed-width-padding}); &.expanded { max-height: unset; @@ -373,7 +375,7 @@ position: fixed; height: 69px; letter-spacing: -0.3px; - width: calc(100% - var(--space-s) * 2); + width: calc(100% - #{$fixed-width-padding}); margin-right: 8px; margin-left: 8px; From 020de7c4b8b566410893cb0ad87b4a58d5222dfd Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 20 Dec 2024 12:13:42 +0200 Subject: [PATCH 37/60] feat: logging utility --- app/component/itinerary/navigator/NaviContainer.js | 8 +++++++- .../itinerary/navigator/NaviInstructions.js | 2 +- app/component/itinerary/navigator/NaviUtils.js | 13 +++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index 505916eb41..58546e1978 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -9,9 +9,10 @@ import NaviBottom from './NaviBottom'; import NaviCardContainer from './NaviCardContainer'; import { useRealtimeLegs } from './hooks/useRealtimeLegs'; import NavigatorOutroModal from './navigatoroutro/NavigatorOutroModal'; -import { DESTINATION_RADIUS } from './NaviUtils'; +import { DESTINATION_RADIUS, summaryString } from './NaviUtils'; const ADDITIONAL_ARRIVAL_TIME = 60000; // 60 seconds in ms +const LEGLOG = true; function NaviContainer( { @@ -70,6 +71,11 @@ function NaviContainer( const isJourneyCompleted = isDestinationReached || isPastExpectedArrival; + if (LEGLOG) { + // eslint-disable-next-line + console.log(summaryString(realTimeLegs, time), previousLeg?.mode, currentLeg?.mode, nextLeg?.mode); + } + return ( <>
- {displayDistance(distance, config, intl.formatNumber)} + {displayDistance(distance, config, intl.formatNumber)}  {durationToString(legTime(leg.end) - time)}
diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index 6cf7f54b9d..6f6e2f19f4 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -2,8 +2,8 @@ import React from 'react'; import distance from '@digitransit-search-util/digitransit-search-util-distance'; import { FormattedMessage } from 'react-intl'; import { GeodeticToEnu } from '../../../util/geo-utils'; -import { legTime } from '../../../util/legUtils'; -import { timeStr } from '../../../util/timeUtils'; +import { legTime, legTimeAcc } from '../../../util/legUtils'; +import { timeStr, epochToIso } from '../../../util/timeUtils'; import { getFaresFromLegs } from '../../../util/fareUtils'; import { ExtendedRouteTypes } from '../../../constants'; import { getItineraryPagePath } from '../../../util/path'; @@ -13,6 +13,15 @@ const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes export const DESTINATION_RADIUS = 20; // meters +export function summaryString(legs, time) { + const parts = epochToIso(time).split('T')[1].split('+'); + let msg = `${parts[0]}: `; + legs.forEach(l => { + msg += ` ${legTimeAcc(l.start)}-${legTimeAcc(l.end)}`; + }); + return msg; +} + function dist(p1, p2) { const dx = p2.x - p1.x; const dy = p2.y - p1.y; From 3b4c03e3374e5addd72478b6b90e7a6180d44fd9 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Fri, 20 Dec 2024 13:26:29 +0200 Subject: [PATCH 38/60] more pr fixes --- app/component/itinerary/navigator/NaviCardContainer.js | 2 +- app/component/itinerary/navigator/NaviUtils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 33548eca62..19e3d3851a 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -184,7 +184,7 @@ function NaviCardContainer( return () => clearTimeout(timeoutId); }, [time]); - // LegChange fires animation, we need to keep the old data until card goes ot of the view. + // LegChange fires animation, we need to keep the old data until card goes out of the view. const l = legChanging ? previousLeg : currentLeg; const legType = handleLegChange(l, firstLeg, time); diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index 15ea24fbc8..f305b805c7 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -368,7 +368,7 @@ export const getItineraryAlerts = ( content, id: `canceled-${legId}`, hideClose: true, - expiresOn: alert.effectiveEndDate, + expiresOn: alert.effectiveEndDate * 1000, }); } }); From 6919af712d4e824d8805867cf314c0938f8c0a86 Mon Sep 17 00:00:00 2001 From: Ville Pihlava Date: Fri, 20 Dec 2024 14:52:05 +0200 Subject: [PATCH 39/60] Add configurable RouteNumber text limit in itinerary list. --- app/component/RouteNumber.js | 46 ++++++++++++++++++++++----- app/component/RouteNumberContainer.js | 8 ++--- app/configurations/config.kela.js | 1 + app/configurations/config.matka.js | 2 +- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/component/RouteNumber.js b/app/component/RouteNumber.js index db54ec341f..efbaefa2cb 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, withCar, 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.isInItineraryList && + context.config.disabledLegTextModes?.includes(mode) && + props.className.includes('line') + ) { + filteredText = ''; + } + const textFieldIsText = typeof filteredText === 'string'; // can be also react node + if ( + props.isInItineraryList && + context.config.shortenLongTextInItineraryListThreshold && + filteredText && + textFieldIsText && + filteredText.length > context.config.shortenLongTextInItineraryListThreshold + ) { + filteredText = `${filteredText.substring( + 0, + context.config.shortenLongTextInItineraryListThreshold - 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 = ( @@ -140,7 +168,7 @@ function RouteNumber(props, context) { )} )} - {text && ( + {filteredText && (
- {props.text} + {filteredText} - {textIsText && ( - {text?.toLowerCase()} + {textFieldIsText && ( + {filteredText?.toLowerCase()} )}
)} @@ -222,6 +250,7 @@ RouteNumber.propTypes = { card: PropTypes.bool, appendClass: PropTypes.string, occupancyStatus: PropTypes.string, + isInItineraryList: PropTypes.bool, }; RouteNumber.defaultProps = { @@ -245,6 +274,7 @@ RouteNumber.defaultProps = { color: undefined, duration: undefined, occupancyStatus: undefined, + isInItineraryList: false, }; RouteNumber.contextTypes = { diff --git a/app/component/RouteNumberContainer.js b/app/component/RouteNumberContainer.js index 504d801d08..3d84755b72 100644 --- a/app/component/RouteNumberContainer.js +++ b/app/component/RouteNumberContainer.js @@ -24,12 +24,8 @@ const RouteNumberContainer = ( isCallAgency={isCallAgency} color={route.color ? `#${route.color}` : null} mode={mode !== undefined ? mode : route.mode} - text={ - config.disabledLegTextModes?.includes(route.mode) && - className.includes('line') - ? '' - : getLegText(route, config, interliningWithRoute) - } + text={getLegText(route, config, interliningWithRoute)} + isInItineraryList occupancyStatus={occupancyStatus} {...props} /> diff --git a/app/configurations/config.kela.js b/app/configurations/config.kela.js index 9ef8429630..b299e00ba3 100644 --- a/app/configurations/config.kela.js +++ b/app/configurations/config.kela.js @@ -156,4 +156,5 @@ export default { showCO2InItinerarySummary: false, useAssembledGeoJsonZones: 'isOnByDefault', locationSearchTargetsFromOTP: [], // remove stop/station location search + shortenLongTextInItineraryListThreshold: 10, }; diff --git a/app/configurations/config.matka.js b/app/configurations/config.matka.js index 8185c71530..37b6f37309 100644 --- a/app/configurations/config.matka.js +++ b/app/configurations/config.matka.js @@ -403,7 +403,7 @@ export default { FERRY: { showNotification: true }, }, - disabledLegTextModes: ['FERRY'], + disabledLegTextModes: ['ferry'], // Include both bike and park and bike and public, if bike is enabled includePublicWithBikePlan: true, From 45a9a6d008c8cccfe15801c25e1ba5c9a94c6c9e Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 20 Dec 2024 16:03:11 +0200 Subject: [PATCH 40/60] feat: colored itinerary logging --- .../itinerary/navigator/NaviContainer.js | 2 +- .../itinerary/navigator/NaviUtils.js | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index 77160edd74..889908f220 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -73,7 +73,7 @@ function NaviContainer( if (LEGLOG) { // eslint-disable-next-line - console.log(summaryString(realTimeLegs, time), previousLeg?.mode, currentLeg?.mode, nextLeg?.mode); + console.log(...summaryString(realTimeLegs, time, previousLeg, currentLeg, nextLeg)); } return ( diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index f6bb3661ce..7fcba14e20 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -13,13 +13,26 @@ const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes export const DESTINATION_RADIUS = 20; // meters -export function summaryString(legs, time) { +export function summaryString(legs, time, previousLeg, currentLeg, nextLeg) { const parts = epochToIso(time).split('T')[1].split('+'); - let msg = `${parts[0]}: `; + let msg = `${parts[0]}`; + const colors = []; + legs.forEach(l => { - msg += ` ${legTimeAcc(l.start)}-${legTimeAcc(l.end)}`; + if (legTime(l.start) <= time && time <= legTime(l.end)) { + colors.push('color:green'); + } else if (l.transitLeg) { + colors.push('color: #aaaaff'); + } else { + colors.push('color: #aaaaaa'); + } + msg += `%c ${legTimeAcc(l.start)}-${legTimeAcc(l.end)}`; }); - return msg; + colors.push('color: #bbbbbb'); + msg += `%c ${previousLeg?.mode} ${currentLeg?.mode} ${nextLeg?.mode}`; + colors.unshift(msg); + + return colors; } function dist(p1, p2) { From 1c702b95e46fb1ba5443950be0acf47450c5263a Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 20 Dec 2024 16:14:44 +0200 Subject: [PATCH 41/60] fix: correct scaling of transfer legs --- app/component/itinerary/navigator/hooks/useRealtimeLegs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js index 83dd513fe3..6a2b3a6db8 100644 --- a/app/component/itinerary/navigator/hooks/useRealtimeLegs.js +++ b/app/component/itinerary/navigator/hooks/useRealtimeLegs.js @@ -40,8 +40,8 @@ function scaleLegs(legs, i1, i2, k) { const leg = legs[j]; const s = legTime(leg.start); const e = legTime(leg.end); - leg.start.scheduledTime = epochToIso(s + k * (s - base)); - leg.end.scheduledTime = epochToIso(e + k * (e - base)); + leg.start.scheduledTime = epochToIso(base + k * (s - base)); + leg.end.scheduledTime = epochToIso(base + k * (e - base)); } } From 6aa9f7985c571711e0b64ebcaf4f738c42fac7d2 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Fri, 20 Dec 2024 16:23:35 +0200 Subject: [PATCH 42/60] chore: don't run fairly heavy topic assembly at every render --- .../itinerary/navigator/NaviCardContainer.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 19e3d3851a..d3a9a393b6 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -86,13 +86,14 @@ function NaviCardContainer( }; // track only relevant vehicles for the journey. - const topics = getTopics( - legs.filter(leg => legTime(leg.end) >= time), - config, - ); + const getNaviTopics = () => + getTopics( + legs.filter(leg => legTime(leg.end) >= time), + config, + ); useEffect(() => { - updateClient(topics, context); + updateClient(getNaviTopics(), context); }, []); useEffect(() => { @@ -129,7 +130,7 @@ function NaviCardContainer( } let timeoutId; if (legChanged) { - updateClient(topics, context); + updateClient(getNaviTopics(), context); setCardExpanded(false); setLegChanging(true); timeoutId = setTimeout(() => { From ab2faab55db1dcbaf6d29389af3509a8e31ea065 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Sat, 21 Dec 2024 15:45:59 +0200 Subject: [PATCH 43/60] fix: remove divider from botton sheet --- app/component/itinerary/navigator/NaviBottom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/component/itinerary/navigator/NaviBottom.js b/app/component/itinerary/navigator/NaviBottom.js index 097e5adcfa..9d6507b155 100644 --- a/app/component/itinerary/navigator/NaviBottom.js +++ b/app/component/itinerary/navigator/NaviBottom.js @@ -11,7 +11,6 @@ export default function NaviBottom( const remainingDuration = Math.ceil((arrival - time) / 60000); // ms to minutes return (
-