diff --git a/app/component/itinerary/PlanConnection.js b/app/component/itinerary/PlanConnection.js index aa950722d6..fd5a695f2b 100644 --- a/app/component/itinerary/PlanConnection.js +++ b/app/component/itinerary/PlanConnection.js @@ -117,6 +117,10 @@ const planConnection = graphql` name lat lon + vehicleMode + code + platformCode + zoneId parentStation { name } diff --git a/app/component/itinerary/navigator/NaviCard.js b/app/component/itinerary/navigator/NaviCard.js index b0c02dfb36..5e058913e5 100644 --- a/app/component/itinerary/navigator/NaviCard.js +++ b/app/component/itinerary/navigator/NaviCard.js @@ -1,4 +1,5 @@ import React from 'react'; +import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import { legShape } from '../../../util/shapes'; import Icon from '../../Icon'; @@ -20,18 +21,42 @@ const iconMap = { FERRY: 'icon-icon_ferry', }; -export default function NaviCard({ leg, nextLeg, legType, cardExpanded }) { +export default function NaviCard({ + leg, + nextLeg, + legType, + cardExpanded, + startTime, +}) { + if (legType === LEGTYPE.PENDING) { + return ( + + ); + } + if (legType === LEGTYPE.END) { + return ; + } + if (!leg && !nextLeg) { + return null; + } const iconName = legType === LEGTYPE.WAIT ? iconMap.WAIT : iconMap[leg.mode]; - let instructions = `navileg-${leg.mode.toLowerCase()}`; + + let instructions = ''; if (legType === LEGTYPE.TRANSIT) { instructions = `navileg-in-transit`; - } else if (isRental(leg, nextLeg)) { + } else if (legType !== LEGTYPE.WAIT && isRental(leg, nextLeg)) { if (leg.mode === 'WALK' && nextLeg?.mode === 'SCOOTER') { instructions = `navileg-rent-scooter`; } else { - instructions = `navileg-rent-cycle`; + instructions = 'rent-cycle-at'; } + } else if (legType === LEGTYPE.MOVE) { + instructions = `navileg-${leg?.mode.toLowerCase()}`; } + return (
@@ -51,18 +76,23 @@ export default function NaviCard({ leg, nextLeg, legType, cardExpanded }) {
- {cardExpanded && } + {cardExpanded && ( + + )} ); } NaviCard.propTypes = { - leg: legShape.isRequired, + leg: legShape, nextLeg: legShape, legType: PropTypes.string.isRequired, cardExpanded: PropTypes.bool, + startTime: PropTypes.string, }; NaviCard.defaultProps = { cardExpanded: false, + leg: undefined, nextLeg: undefined, + startTime: '', }; diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 3861540835..2ffc89828c 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage, intlShape } from 'react-intl'; +import { intlShape } from 'react-intl'; import distance from '@digitransit-search-util/digitransit-search-util-distance'; import { legShape, configShape } from '../../../util/shapes'; import { legTime, legTimeStr } from '../../../util/legUtils'; @@ -21,7 +21,9 @@ function getFirstLastLegs(legs) { const last = legs[legs.length - 1]; return { first, last }; } - +function getNextLeg(legs, time) { + return legs.find(leg => legTime(leg.start) > time); +} function NaviCardContainer( { focusToLeg, time, realTimeLegs, position }, { intl, config }, @@ -35,11 +37,25 @@ function NaviCardContainer( const focusRef = useRef(false); // Destination counter. How long user has been at the destination. * 10 seconds const destCountRef = useRef(0); + const [topPosition, setTopPosition] = useState(0); + const cardRef = useRef(null); + + const handleRemove = index => { + setActiveMessages(activeMessages.filter((_, i) => i !== index)); + }; const handleClick = () => { setCardExpanded(!cardExpanded); }; + useEffect(() => { + if (cardRef.current) { + const contentHeight = cardRef.current.clientHeight; + // Navistack top position depending on main card height. + setTopPosition(contentHeight + 86); + } + }, [currentLeg, cardExpanded]); + useEffect(() => { const newLeg = realTimeLegs.find(leg => { return legTime(leg.start) <= time && time <= legTime(leg.end); @@ -60,9 +76,8 @@ function NaviCardContainer( const l = currentLeg || newLeg; if (l) { - const nextLeg = realTimeLegs.find( - leg => legTime(leg.start) > legTime(l.start), - ); + const nextLeg = getNextLeg(realTimeLegs, legTime(l.start)); + if (nextLeg?.transitLeg) { // Messages for NaviStack. const transitLegState = getTransitLegState(nextLeg, intl, messages); @@ -143,18 +158,12 @@ function NaviCardContainer( const { first, last } = getFirstLastLegs(realTimeLegs); let legType; - let naviTopContent; + const t = currentLeg ? legTime(currentLeg.start) : time; + const nextLeg = getNextLeg(realTimeLegs, t); + if (time < legTime(first.start)) { - naviTopContent = ( - - ); + legType = LEGTYPE.PENDING; } else if (currentLeg) { - const nextLeg = realTimeLegs.find(leg => { - return legTime(leg.start) > legTime(currentLeg.start); - }); if (!currentLeg.transitLeg) { if (destCountRef.current >= TIME_AT_DESTINATION) { legType = LEGTYPE.WAIT; @@ -164,33 +173,11 @@ function NaviCardContainer( } else { legType = LEGTYPE.TRANSIT; } - naviTopContent = ( - - ); } else if (time > legTime(last.end)) { - naviTopContent = ; + legType = LEGTYPE.END; } else { - naviTopContent = ; - } - - // Card has 4 sizes: first leg collapsed, expanded - // and in transit collapsed, expanded. - let classPostfix = ''; - if (legType === LEGTYPE.TRANSIT && cardExpanded) { - classPostfix = 'expand-transit'; - } else if (legType === LEGTYPE.TRANSIT) { - classPostfix = 'transit'; - } else if (cardExpanded) { - classPostfix = 'expanded'; + legType = LEGTYPE.WAIT; } - const handleRemove = index => { - setActiveMessages(activeMessages.filter((_, i) => i !== index)); - }; return ( <> @@ -199,13 +186,21 @@ function NaviCardContainer( className={`navitop ${cardExpanded ? 'expanded' : ''}`} onClick={handleClick} > -
{naviTopContent}
+
+ +
{activeMessages.length > 0 && ( )} diff --git a/app/component/itinerary/navigator/NaviCardExtension.js b/app/component/itinerary/navigator/NaviCardExtension.js index 4bd9fba5e9..5ab1155ebb 100644 --- a/app/component/itinerary/navigator/NaviCardExtension.js +++ b/app/component/itinerary/navigator/NaviCardExtension.js @@ -12,15 +12,21 @@ import { getDestinationProperties, LEGTYPE } from './NaviUtils'; import RouteNumberContainer from '../../RouteNumberContainer'; -const NaviCardExtension = ({ legType, leg }, { config }) => { - const { stop, name } = leg.to; +const NaviCardExtension = ({ legType, leg, nextLeg }, { config }) => { + const { stop, name, rentalVehicle, vehicleParking, vehicleRentalStation } = + leg ? leg.to : nextLeg.from; const { code, platformCode, zoneId, vehicleMode } = stop || {}; const [place, address] = name?.split(/, (.+)/) || []; let destination = {}; if (stop) { - destination = getDestinationProperties(leg, stop, config); - destination.name = stop.name; + destination = getDestinationProperties( + rentalVehicle, + vehicleParking, + vehicleRentalStation, + stop, + config, + ); } else { destination.iconId = 'icon-icon_mapMarker-to'; destination.className = 'place'; @@ -28,7 +34,8 @@ const NaviCardExtension = ({ legType, leg }, { config }) => { } if (legType === LEGTYPE.TRANSIT) { - const { intermediatePlaces } = leg; + const { intermediatePlaces, headsign, trip } = leg; + const hs = headsign || trip.tripHeadsign; const now = Date.now(); const idx = intermediatePlaces.findIndex(p => legTime(p.arrival) > now); @@ -36,19 +43,20 @@ const NaviCardExtension = ({ legType, leg }, { config }) => { const stopCount = {count} ; const translationId = count === 1 ? 'navileg-one-stop-remaining' : 'navileg-stops-remaining'; + const mode = leg.mode.toLowerCase(); return (
-
{leg.to.name}
+
{hs}
{
); } - return (
@@ -98,12 +105,15 @@ const NaviCardExtension = ({ legType, leg }, { config }) => { }; NaviCardExtension.propTypes = { - leg: legShape.isRequired, + leg: legShape, + nextLeg: legShape, legType: PropTypes.string, }; NaviCardExtension.defaultProps = { legType: '', + leg: undefined, + nextLeg: undefined, }; NaviCardExtension.contextTypes = { diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index abecbe4e8f..2647cd8001 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -13,7 +13,6 @@ export default function NaviInstructions( { leg, nextLeg, instructions, legType }, { intl, config }, ) { - const { distance, duration } = leg; const [fadeOut, setFadeOut] = useState(false); useEffect(() => { @@ -27,6 +26,7 @@ export default function NaviInstructions( }, [leg]); if (legType === LEGTYPE.MOVE) { + const { distance, duration } = leg; return ( <>
@@ -43,33 +43,50 @@ export default function NaviInstructions( ); } - - if (legType === LEGTYPE.WAIT) { - const { mode, headsign, route } = nextLeg; - - const color = route.color ? route.color : 'currentColor'; + if (legType === LEGTYPE.WAIT && nextLeg.mode !== 'WALK') { + const { mode, headsign, route, end, start } = nextLeg; + const hs = headsign || nextLeg.trip?.tripHeadsign; + const color = route.color || 'currentColor'; const localizedMode = intl.formatMessage({ - id: `${mode.toLowerCase()}`, + id: `to-${mode.toLowerCase()}`, defaultMessage: `${mode}`, }); + const t = leg ? legTime(end) : legTime(start); + const remainingDuration = Math.ceil((t - Date.now()) / 60000); // ms to minutes + const rt = nextLeg.realtimeState === 'UPDATED'; + const values = { + duration: ( + {remainingDuration} + ), + legTime: {legTimeStr(end)}, + }; return ( <>
- -
{headsign}
+
+ +
{hs}
+
+
+ +
); @@ -120,14 +137,16 @@ export default function NaviInstructions( } NaviInstructions.propTypes = { - leg: legShape.isRequired, - nextLeg: legShape.isRequired, + leg: legShape, + nextLeg: legShape, instructions: PropTypes.string.isRequired, legType: PropTypes.string, }; NaviInstructions.defaultProps = { - legType: LEGTYPE.MOVE, + legType: '', + leg: undefined, + nextLeg: undefined, }; NaviInstructions.contextTypes = { intl: intlShape.isRequired, diff --git a/app/component/itinerary/navigator/NaviStack.js b/app/component/itinerary/navigator/NaviStack.js index 11c06df793..d588d9b631 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, classPostfix }) => { +const NaviStack = ({ messages, handleRemove, topPosition }) => { return ( -
+
{messages.map((notification, index) => ( { * Get the properties of the destination based on the leg. * */ -export const getDestinationProperties = (leg, stop, config) => { - const { rentalVehicle, vehicleParking, vehicleRentalStation } = leg.to; - const { vehicleMode, routes } = stop; - +export const getDestinationProperties = ( + rentalVehicle, + vehicleParking, + vehicleRentalStation, + stop, + config, +) => { + const { routes, vehicleMode } = stop; let destination = {}; let mode = vehicleMode; - if (routes && vehicleMode === 'BUS' && config.useExtendedRouteTypes) { if (routes.some(p => p.type === ExtendedRouteTypes.BusExpress)) { mode = 'bus-express'; @@ -255,4 +258,6 @@ export const LEGTYPE = { WAIT: 'WAIT', MOVE: 'MOVE', TRANSIT: 'TRANSIT', + PENDING: 'PENDING', + END: 'END', }; diff --git a/app/component/itinerary/navigator/navigator.scss b/app/component/itinerary/navigator/navigator.scss index 759879fc14..fe9c28cde9 100644 --- a/app/component/itinerary/navigator/navigator.scss +++ b/app/component/itinerary/navigator/navigator.scss @@ -75,11 +75,12 @@ width: 100%; .navi-top-card { + margin: 16px 24px 16px; + .main-card { display: flex; flex-direction: row; color: black; - margin: var(--space-s) var(--space-m) 0; .mode { width: var(--space-l); @@ -96,7 +97,6 @@ width: 100%; font-size: $font-size-normal; font-weight: $font-weight-medium; - margin-bottom: var(--space-s); &.expanded { margin-bottom: 0; @@ -104,7 +104,7 @@ .destination-header { font-size: $font-size-normal; - font-weight: $font-weight-medium; + font-weight: 500; display: flex; } @@ -120,12 +120,42 @@ display: flex; align-items: center; margin-top: 3px; + flex-direction: column; + align-self: flex-start; + + .route-info { + display: flex; + align-self: flex-start; + margin-bottom: var(--space-xcs); + } + + .wait-duration { + font-weight: $font-weight-book; + margin-top: var(--space-xs); + } + + .bar { + border-radius: $border-radius; + } + + .route-number { + .vcenter-children { + .vehicle-number { + color: white; + margin-top: 5px; + } + + display: flex; + } + } .headsign { margin-left: 10px; font-size: $font-size-small; font-weight: $font-weight-book; max-width: 120px; + text-align: left; + align-content: center; } .icon { @@ -155,14 +185,14 @@ .extension-divider { border: 1px solid #ddd; width: 75%; - margin-left: 50px; + margin-left: 35px; margin-top: var(--space-s); margin-bottom: var(--space-s); } .stop-count { display: flex; - margin-left: 48px; + margin-left: 35px; font-weight: $font-weight-book; } @@ -170,17 +200,19 @@ display: flex; flex-direction: row; font-weight: $font-weight-book; - margin-left: 48px; + margin-left: 35px; margin-bottom: var(--space-s); text-align: left; - .route-number { - min-width: 55px; + .bar { + border-radius: $border-radius; + } + .route-number { .vcenter-children { .vehicle-number { color: white; - margin-top: 4px; + margin-top: 5px; } display: flex; @@ -195,7 +227,7 @@ } } - .dest-name { + .headsign { display: flex; flex-direction: column; margin-left: var(--space-xs); @@ -206,7 +238,7 @@ .extension-walk { display: flex; - margin-left: var(--space-xxl); + margin-left: var(--space-xl); margin-bottom: var(--space-s); margin-top: var(--space-xs); } @@ -333,7 +365,7 @@ height: 69px; margin-left: 5%; width: 90%; - top: 155px; + top: 160px; div:first-child { margin-top: 0; @@ -343,18 +375,6 @@ margin-top: 5px; } - &.expanded { - top: 190px; - } - - &.expand-transit { - top: 235px; - } - - &.transit { - top: 162px; - } - &.slide-out { animation: slideOut 0.5s ease-out forwards; pointer-events: none; diff --git a/app/translations.js b/app/translations.js index 7a06f86353..56ba592ba4 100644 --- a/app/translations.js +++ b/app/translations.js @@ -1312,7 +1312,7 @@ const translations = { 'navigation-start': 'Start journey', 'navigation-ticket': 'Ticket', 'navigation-wait': 'Wait at the stop', - 'navigation-wait-mode': 'Odota {mode}a', + 'navigation-wait-mode': 'Nouse {mode}', 'navileg-bicycle': 'Cycle to', 'navileg-car': 'Drive to', 'navileg-from-station': 'asemalla', @@ -1683,6 +1683,7 @@ const translations = { 'to-ferry': 'ferry', 'to-frontpage': 'To the front page', 'to-rail': 'train', + 'to-speedtram': 'TODO_pikaraitiovaunuun', 'to-subway': 'subway', 'to-tram': 'tram', today: 'Today', @@ -2570,7 +2571,7 @@ const translations = { 'navigation-start': 'Matkalle', 'navigation-ticket': 'Lippu', 'navigation-wait': 'Odota pysäkillä', - 'navigation-wait-mode': 'Odota {mode}a', + 'navigation-wait-mode': 'Nouse {mode}', 'navileg-bicycle': 'Pyöräile', 'navileg-car': 'Aja', 'navileg-from-station': 'asemalla', @@ -2940,6 +2941,7 @@ const translations = { 'to-ferry': 'lauttaan', 'to-frontpage': 'Etusivulle', 'to-rail': 'junaan', + 'to-speedtram': 'TODO_pikaraitiovaunuun', 'to-subway': 'metroon', 'to-tram': 'raitiovaunuun', today: 'Tänään', @@ -5479,7 +5481,7 @@ const translations = { 'navigation-start': 'På resa', 'navigation-ticket': 'Biljett', 'navigation-wait': 'Vänta på hållplatsen', - 'navigation-wait-mode': 'Odota {mode}a', + 'navigation-wait-mode': 'Odota {mode}', 'navileg-bicycle': 'Cycla till', 'navileg-car': 'Kör till', 'navileg-from-station': 'TODO_asemalla', @@ -5852,6 +5854,7 @@ const translations = { 'to-ferry': 'färjan', 'to-frontpage': 'Till startsidan', 'to-rail': 'tåget', + 'to-speedtram': 'TODO_pikaraitiovaunuun', 'to-subway': 'metron', 'to-tram': 'spårvagnen', today: 'I dag', diff --git a/static/assets/svg-sprite.default.svg b/static/assets/svg-sprite.default.svg index 9ffdc699a9..eb90ad78d7 100644 --- a/static/assets/svg-sprite.default.svg +++ b/static/assets/svg-sprite.default.svg @@ -2847,8 +2847,10 @@ - - + + + + diff --git a/static/assets/svg-sprite.hsl.svg b/static/assets/svg-sprite.hsl.svg index 6be1983b93..44ccd31144 100644 --- a/static/assets/svg-sprite.hsl.svg +++ b/static/assets/svg-sprite.hsl.svg @@ -2792,7 +2792,9 @@ - - + + + +