Skip to content

Commit fe48e46

Browse files
authored
Merge pull request #5216 from HSLdevcom/reroute-from-stop
Proper rerouting logic and misc improvements
2 parents 00f5859 + 3960390 commit fe48e46

File tree

6 files changed

+137
-93
lines changed

6 files changed

+137
-93
lines changed

app/component/itinerary/navigator/NaviCardContainer.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getAdditionalMessages,
1212
getItineraryAlerts,
1313
getTransitLegState,
14+
itinerarySearchPath,
1415
LEGTYPE,
1516
DESTINATION_RADIUS,
1617
} from './NaviUtils';
@@ -92,6 +93,17 @@ function NaviCardContainer(
9293
config,
9394
);
9495

96+
const makeNewItinerarySearch = () => {
97+
const path = itinerarySearchPath(
98+
time,
99+
currentLeg,
100+
nextLeg,
101+
position,
102+
match.params.to,
103+
);
104+
router.push(path);
105+
};
106+
95107
useEffect(() => {
96108
updateClient(getNaviTopics(), context);
97109
}, []);
@@ -116,8 +128,7 @@ function NaviCardContainer(
116128
origin,
117129
intl,
118130
messages,
119-
match.params,
120-
router,
131+
makeNewItinerarySearch,
121132
),
122133
);
123134

@@ -170,11 +181,10 @@ function NaviCardContainer(
170181
// handle initial focus when not tracking
171182
if (currentLeg) {
172183
focusToLeg(currentLeg);
173-
destCountRef.current = 0;
174184
} else if (time < legTime(firstLeg.start)) {
175185
focusToLeg(firstLeg);
176186
} else {
177-
focusToLeg(lastLeg);
187+
focusToLeg(nextLeg || lastLeg);
178188
}
179189
focusRef.current = true;
180190
}

app/component/itinerary/navigator/NaviUtils.js

Lines changed: 85 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { timeStr, epochToIso } from '../../../util/timeUtils';
77
import { getFaresFromLegs } from '../../../util/fareUtils';
88
import { ExtendedRouteTypes } from '../../../constants';
99
import { getItineraryPagePath } from '../../../util/path';
10+
import { locationToUri } from '../../../util/otpStrings';
1011

1112
const TRANSFER_SLACK = 60000;
1213
const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes
@@ -279,7 +280,10 @@ export const getTransitLegState = (leg, intl, messages, time) => {
279280
const departure = leg.trip.stoptimesForDate[0];
280281
const departed =
281282
1000 * (departure.serviceDay + departure.scheduledDeparture);
282-
if (time - departed < DISPLAY_MESSAGE_THRESHOLD) {
283+
if (
284+
time - departed < DISPLAY_MESSAGE_THRESHOLD &&
285+
time + DISPLAY_MESSAGE_THRESHOLD > legTime(leg.start)
286+
) {
283287
// vehicle just departed, maybe no realtime yet
284288
severity = 'INFO';
285289
} else {
@@ -330,28 +334,60 @@ export const getTransitLegState = (leg, intl, messages, time) => {
330334
return [{ severity, content, id: legId, expiresOn: legTime(start) }];
331335
};
332336

337+
export function itinerarySearchPath(time, leg, nextLeg, position, to) {
338+
let from;
339+
if (leg?.transitLeg) {
340+
from = leg.intermediatePlaces.find(
341+
p => legTime(p.arrival) > time + TRANSFER_SLACK,
342+
);
343+
if (!from) {
344+
from = leg.to;
345+
}
346+
} else {
347+
from = position || leg?.to || nextLeg?.from;
348+
}
349+
const location = { ...from, ...from.stop };
350+
351+
return getItineraryPagePath(locationToUri(location), to);
352+
}
353+
354+
function withNewSearchBtn(children, searchCallback) {
355+
return (
356+
<div className="navi-alert-content">
357+
{children}
358+
<FormattedMessage id="navigation-abort-trip" />
359+
<button
360+
className="new-itinerary-search"
361+
type="button"
362+
onClick={searchCallback}
363+
>
364+
<FormattedMessage id="settings-dropdown-open-label" />
365+
</button>
366+
</div>
367+
);
368+
}
369+
370+
function alertId(alert) {
371+
return `${alert.effectiveStartDate}-${alert.alertDescriptionText}`;
372+
}
373+
333374
export const getItineraryAlerts = (
334375
legs,
335376
time,
336377
position,
337378
origin,
338379
intl,
339380
messages,
340-
location,
341-
router,
381+
itinerarySearchCallback,
342382
) => {
343-
const canceled = legs.filter(
344-
leg => leg.realtimeState === 'CANCELED' && legTime(leg.start) > time,
345-
);
346-
let content;
347383
const alerts = legs.flatMap(leg => {
348384
return leg.alerts
349385
.filter(alert => {
350-
const { first } = getFirstLastLegs(legs);
351-
const startTime = legTime(first.start) / 1000;
352-
if (messages.get(alert.id)) {
386+
if (messages.get(alertId(alert))?.closed) {
353387
return false;
354388
}
389+
const { first } = getFirstLastLegs(legs);
390+
const startTime = legTime(first.start) / 1000;
355391
// show only alerts that are active when
356392
// the journey starts
357393
if (startTime < alert.effectiveStartDate) {
@@ -372,47 +408,36 @@ export const getItineraryAlerts = (
372408
<span className="header"> {alert.alertHeaderText}</span>
373409
</div>
374410
),
375-
id: `${alert.effectiveStartDate}-${alert.alertDescriptionText}`,
411+
id: alertId(alert),
376412
}));
377413
});
378-
const abortTrip = <FormattedMessage id="navigation-abort-trip" />;
379-
const withShowRoutesBtn = children => (
380-
<div className="alt-btn">
381-
{children}
382-
<button
383-
className="show-options"
384-
type="button"
385-
onClick={() => router.push(getItineraryPagePath('POS', location.to))}
386-
>
387-
<FormattedMessage id="settings-dropdown-open-label" />
388-
</button>
389-
</div>
414+
415+
const canceled = legs.filter(
416+
leg => leg.realtimeState === 'CANCELED' && legTime(leg.start) > time,
390417
);
391418

392-
if (canceled) {
419+
if (canceled.length) {
393420
// show routes button only for first canceled leg.
394421
canceled.forEach((leg, i) => {
395422
const { legId, mode, route } = leg;
396423

397424
const lMode = getLocalizedMode(mode, intl);
398425
const routeName = `${lMode} ${route.shortName}`;
426+
399427
const m = (
400428
<FormattedMessage
401429
id="navigation-mode-canceled"
402430
values={{ mode: routeName }}
403431
/>
404432
);
405433
// we want to show the show routes button only for the first canceled leg.
406-
if (i === 0) {
407-
content = withShowRoutesBtn(
408-
<div className="navi-alert-content">
409-
{m}
410-
{abortTrip}
411-
</div>,
434+
const content =
435+
i === 0 ? (
436+
withNewSearchBtn({ m }, itinerarySearchCallback)
437+
) : (
438+
<div className="navi-alert-content">{m}</div>
412439
);
413-
} else {
414-
content = <div className="navi-alert-content">{m}</div>;
415-
}
440+
416441
if (!messages.get(`canceled-${legId}`)) {
417442
alerts.push({
418443
severity: 'ALERT',
@@ -423,36 +448,33 @@ export const getItineraryAlerts = (
423448
});
424449
}
425450
});
426-
}
427-
428-
const transferProblems = findTransferProblems(legs, time, position, origin);
429-
if (transferProblems.length) {
430-
let prob = transferProblems.find(p => p.severity === 'ALERT');
431-
if (!prob) {
432-
// just take first
433-
[prob] = transferProblems;
434-
}
435-
const transferId = `transfer-${prob.fromLeg.legId}-${prob.toLeg.legId}}`;
436-
const alert = messages.get(transferId);
437-
if (!alert || alert.severity !== prob.severity) {
438-
content = withShowRoutesBtn(
439-
<div className="navi-alert-content">
440-
<FormattedMessage
441-
id="navigation-transfer-problem"
442-
values={{
443-
route1: prob.fromLeg.route.shortName,
444-
route2: prob.toLeg.route.shortName,
445-
}}
446-
/>
447-
{abortTrip}
448-
</div>,
449-
);
450-
alerts.push({
451-
severity: prob.severity,
452-
content,
453-
id: transferId,
454-
hideClose: prob.severity === 'ALERT',
455-
});
451+
} else {
452+
const transferProblems = findTransferProblems(legs, time, position, origin);
453+
if (transferProblems.length) {
454+
let prob = transferProblems.find(p => p.severity === 'ALERT');
455+
if (!prob) {
456+
// just take first
457+
[prob] = transferProblems;
458+
}
459+
const transferId = `transfer-${prob.fromLeg.legId}-${prob.toLeg.legId}}`;
460+
const alert = messages.get(transferId);
461+
if (!alert || alert.severity !== prob.severity) {
462+
alerts.push({
463+
severity: prob.severity,
464+
content: withNewSearchBtn(
465+
<FormattedMessage
466+
id="navigation-transfer-problem"
467+
values={{
468+
route1: prob.fromLeg.route.shortName,
469+
route2: prob.toLeg.route.shortName,
470+
}}
471+
/>,
472+
itinerarySearchCallback,
473+
),
474+
id: transferId,
475+
hideClose: prob.severity === 'ALERT',
476+
});
477+
}
456478
}
457479
}
458480
return alerts;

app/component/itinerary/navigator/hooks/useRealtimeLegs.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,21 @@ function matchLegEnds(legs) {
9191
}
9292
}
9393

94-
function getLegsOfInterest(initialLegs, time, previousFinishedLeg) {
95-
if (!initialLegs?.length) {
94+
function getLegsOfInterest(
95+
realTimeLegs,
96+
time,
97+
previousFinishedLeg,
98+
itineraryStarted,
99+
) {
100+
if (!realTimeLegs?.length) {
96101
return {
97102
firstLeg: undefined,
98103
lastLeg: undefined,
99104
currentLeg: undefined,
100105
nextLeg: undefined,
101106
};
102107
}
103-
104-
const legs = initialLegs.reduce((acc, curr, i, arr) => {
108+
const legs = realTimeLegs.reduce((acc, curr, i, arr) => {
105109
acc.push(curr);
106110
const next = arr[i + 1];
107111

@@ -130,7 +134,8 @@ function getLegsOfInterest(initialLegs, time, previousFinishedLeg) {
130134
isAnyLegPropertyIdentical(currentLeg, previousFinishedLeg, [
131135
'legId',
132136
'legGeometry.points',
133-
])
137+
]) &&
138+
itineraryStarted // prev and current are both undefined before itinerary starts
134139
) {
135140
previousLeg = currentLeg;
136141
currentLeg = nextLeg;
@@ -141,14 +146,15 @@ function getLegsOfInterest(initialLegs, time, previousFinishedLeg) {
141146
lastLeg: legs[legs.length - 1],
142147
previousLeg,
143148
currentLeg,
144-
nextLeg: initialLegs.find(({ start }) => legTime(start) >= nextStart),
149+
nextLeg: realTimeLegs.find(({ start }) => legTime(start) >= nextStart),
145150
};
146151
}
147152

148153
const useRealtimeLegs = (relayEnvironment, initialLegs = []) => {
149154
const [realTimeLegs, setRealTimeLegs] = useState();
150155
const [time, setTime] = useState(Date.now());
151156
const previousFinishedLeg = useRef(undefined);
157+
const itineraryStarted = useRef(false);
152158

153159
const origin = useMemo(
154160
() => GeodeticToEcef(initialLegs[0].from.lat, initialLegs[0].from.lon),
@@ -235,10 +241,17 @@ const useRealtimeLegs = (relayEnvironment, initialLegs = []) => {
235241
}, [fetchAndSetRealtimeLegs]);
236242

237243
const { firstLeg, lastLeg, currentLeg, nextLeg, previousLeg } =
238-
getLegsOfInterest(realTimeLegs, time, previousFinishedLeg.current);
244+
getLegsOfInterest(
245+
realTimeLegs,
246+
time,
247+
previousFinishedLeg.current,
248+
itineraryStarted.current,
249+
);
239250

240251
previousFinishedLeg.current = previousLeg;
241-
252+
if (currentLeg) {
253+
itineraryStarted.current = true;
254+
}
242255
// return wait legs as undefined as they are not a global concept
243256
return {
244257
realTimeLegs,

app/component/itinerary/navigator/navigator.scss

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -457,23 +457,16 @@ $fixed-width-padding: 16px;
457457
}
458458
}
459459

460-
.alt-btn {
461-
display: flex;
462-
flex-direction: column;
463-
width: 100%;
464-
465-
.show-options {
466-
padding: var(--space-s, 16px) var(--space-xs, 8px) var(--space-s, 16px)
467-
var(--space-s, 16px);
468-
background: #0074bf;
469-
color: #fff;
470-
border-radius: 999px; // var(--radius-radius-medium, 8px);
471-
margin-top: var(--space-xxs);
472-
473-
/* box-shadow-card-s-strong */
474-
box-shadow: 0 2px 4px 0
475-
var(--color-shadow-strong, rgba(51, 51, 51, 0.2));
476-
}
460+
.new-itinerary-search {
461+
padding: var(--space-s, 16px) var(--space-xs, 8px) var(--space-s, 16px)
462+
var(--space-s, 16px);
463+
background: #0074bf;
464+
color: #fff;
465+
border-radius: 999px; // var(--radius-radius-medium, 8px);
466+
margin-top: var(--space-xxs);
467+
468+
/* box-shadow-card-s-strong */
469+
box-shadow: 0 2px 4px 0 var(--color-shadow-strong, rgba(51, 51, 51, 0.2));
477470
}
478471

479472
&.slide-out-right {

app/component/itinerary/queries/LegQuery.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ const legQuery = graphql`
3030
time
3131
}
3232
}
33+
stop {
34+
gtfsId
35+
lat
36+
lon
37+
name
38+
}
3339
}
3440
to {
3541
vehicleRentalStation {

app/util/otpStrings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function locationToUri(location) {
5252
if (!location.lat) {
5353
return '-';
5454
}
55-
let address = location.address || '';
55+
let address = location.address || location.name || '';
5656
if (location.gtfsId) {
5757
address = `${address}**${location.gtfsId}`;
5858
}

0 commit comments

Comments
 (0)