From 49ca1217e9a428c37d0e9a554fa8edbefb204a55 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 3 Feb 2022 22:40:15 -0800 Subject: [PATCH 0001/1425] feature(DefaultItinerary): show CO2 for each trip option --- .../narrative/default/default-itinerary.js | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 1f74183ab..eda9c7fcf 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -29,7 +29,7 @@ import Icon from '../../util/icon' import { FlexIndicator } from './flex-indicator' import ItinerarySummary from './itinerary-summary' -const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff } = coreUtils.itinerary +const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff, calculateEmissions } = coreUtils.itinerary // Styled components const LegIconWrapper = styled.div` @@ -42,12 +42,7 @@ const LegIconWrapper = styled.div` /* Equivalent of a single space before the leg icon. */ &::before { content: ""; - margin: 0 0.125em; - } - - &::before { - content: ""; - margin: 0 0.125em; + margin: 0 ${props => props.noSpace ? '' : '0.125em'}; } ` @@ -148,12 +143,31 @@ const ITINERARY_ATTRIBUTES = [ // FIXME: For CAR mode, walk time considers driving time. <> - + ) } + }, + { + id: 'carbonEmission', + order: 4, + render: (itinerary, { co2Config }) => { + const { carbonIntensity, massUnit } = co2Config + const co2 = calculateEmissions(carbonIntensity, massUnit, itinerary) + return ( + <> + {' '} + CO2 + + ) + } } ] @@ -193,6 +207,7 @@ class DefaultItinerary extends NarrativeItinerary { const { accessibilityScoreGradationMap, active, + co2Config, configCosts, currency, defaultFareKey, @@ -201,7 +216,7 @@ class DefaultItinerary extends NarrativeItinerary { LegIcon, setActiveLeg, showRealtimeAnnotation, - timeFormat + timeFormat, } = this.props const timeOptions = { format: timeFormat, @@ -268,6 +283,7 @@ class DefaultItinerary extends NarrativeItinerary { options.isSelected = true options.selection = this.props.sort.type } + options.co2Config = co2Config options.LegIcon = LegIcon options.configCosts = configCosts options.currency = currency @@ -308,6 +324,17 @@ class DefaultItinerary extends NarrativeItinerary { const mapStateToProps = (state, ownProps) => { const { intl } = ownProps const gradationMap = state.otp.config.accessibilityScore?.gradationMap + const co2Config = state.otp.config.co2 || { + carbonIntensity: { + car: 0.283, + tram: 0.041, + subway: 0.041, + bus: 0.105, + walk: 0, + bike: 0 + }, + massUnit: 'ounce' + } // Generate icons based on fa icon keys in config // Override text fields if translation set @@ -330,6 +357,7 @@ const mapStateToProps = (state, ownProps) => { return { accessibilityScoreGradationMap: gradationMap, + co2Config, configCosts: state.otp.config.itinerary?.costs, // The configured (ambient) currency is needed for rendering the cost // of itineraries whether they include a fare or not, in which case From 91c45487fa9c1c3857b79acc62dbffc7de9ccc72 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 8 Feb 2022 14:53:22 -0800 Subject: [PATCH 0002/1425] feat(default-itinerary): add CO2 emissions --- .../narrative/default/default-itinerary.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index eda9c7fcf..5e76dfe4c 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -165,6 +165,9 @@ const ITINERARY_ATTRIBUTES = [ unitDisplay="narrow" />{' '} CO2 + + + ) } @@ -326,12 +329,21 @@ const mapStateToProps = (state, ownProps) => { const gradationMap = state.otp.config.accessibilityScore?.gradationMap const co2Config = state.otp.config.co2 || { carbonIntensity: { + walk: 0, + bicycle: 0, car: 0.283, tram: 0.041, subway: 0.041, + rail: 0.041, bus: 0.105, - walk: 0, - bike: 0 + ferry: 0.082, + cable_car: 0.021, + gondola: 0.021, + funicular: 0.041, + transit: 0.041, + leg_switch: 0, + airplane: 0.382, + micromobility: 0 }, massUnit: 'ounce' } From d5646c1aa0488146a525d845b7bf367dc79bc2dd Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 8 Feb 2022 18:05:33 -0800 Subject: [PATCH 0003/1425] refactor(default-itinerary): remove default carbon settings --- .../narrative/default/default-itinerary.js | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 5e76dfe4c..11d747989 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -154,6 +154,8 @@ const ITINERARY_ATTRIBUTES = [ id: 'carbonEmission', order: 4, render: (itinerary, { co2Config }) => { + if(!co2Config) return // If there is no CO2 Config, don't display this element + const { carbonIntensity, massUnit } = co2Config const co2 = calculateEmissions(carbonIntensity, massUnit, itinerary) return ( @@ -219,7 +221,7 @@ class DefaultItinerary extends NarrativeItinerary { LegIcon, setActiveLeg, showRealtimeAnnotation, - timeFormat, + timeFormat } = this.props const timeOptions = { format: timeFormat, @@ -327,27 +329,7 @@ class DefaultItinerary extends NarrativeItinerary { const mapStateToProps = (state, ownProps) => { const { intl } = ownProps const gradationMap = state.otp.config.accessibilityScore?.gradationMap - const co2Config = state.otp.config.co2 || { - carbonIntensity: { - walk: 0, - bicycle: 0, - car: 0.283, - tram: 0.041, - subway: 0.041, - rail: 0.041, - bus: 0.105, - ferry: 0.082, - cable_car: 0.021, - gondola: 0.021, - funicular: 0.041, - transit: 0.041, - leg_switch: 0, - airplane: 0.382, - micromobility: 0 - }, - massUnit: 'ounce' - } - + const co2Config = state.otp.config.co2 // Generate icons based on fa icon keys in config // Override text fields if translation set gradationMap && From 3b2074464a4feddb5209995f9fa2728c0439ba99 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 8 Feb 2022 18:09:54 -0800 Subject: [PATCH 0004/1425] docs(example-config): add example for CO2 --- example-config.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/example-config.yml b/example-config.yml index 123383bcd..0656ade60 100644 --- a/example-config.yml +++ b/example-config.yml @@ -332,6 +332,27 @@ itinerary: # - WALK # - BICYCLE +### Confugre here the carbon output in grams per meter for each mode. +### massUnits can be 'pound', 'kilogram', 'ounce', or 'gram' +# co2: +# carbonIntensity: +# walk: 0 +# bicycle: 0 +# car: 0.283 +# tram: 0.041 +# subway: 0.041 +# rail: 0.041 +# bus: 0.105 +# ferry: 0.082 +# cable_car: 0.021 +# gondola: 0.021 +# funicular: 0.041 +# transit: 0.041 +# leg_switch: 0 +# airplane: 0.382 +# micromobility: 0 +# massUnit: "ounce" + ### Language section to override strings. ### Strings can be set globally for all languages (e.g. for strings that are brands/nouns, ### e.g. TriMet's "TransitTracker") or by language. From c3e55d6e2820867db7985ff6d10df779ed5ae1ec Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 8 Feb 2022 18:11:25 -0800 Subject: [PATCH 0005/1425] docs(example-config): fix typo --- example-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example-config.yml b/example-config.yml index 0656ade60..4d58a2a5a 100644 --- a/example-config.yml +++ b/example-config.yml @@ -332,7 +332,7 @@ itinerary: # - WALK # - BICYCLE -### Confugre here the carbon output in grams per meter for each mode. +### Configure here the carbon output in grams per meter for each mode. ### massUnits can be 'pound', 'kilogram', 'ounce', or 'gram' # co2: # carbonIntensity: From af5083d527d18ba827e0865e537f471ef959276a Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 8 Feb 2022 18:19:48 -0800 Subject: [PATCH 0006/1425] refactor(default-itinerary): sort props --- lib/components/narrative/default/default-itinerary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 11d747989..3b7f08332 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -161,10 +161,10 @@ const ITINERARY_ATTRIBUTES = [ return ( <> {' '} CO2 From de72d177fa488980da46c4875cfd3194cb6f65cb Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 16 Feb 2022 14:18:25 -0800 Subject: [PATCH 0007/1425] refactor(itineraries): show co2 as relative amount --- i18n/en-US.yml | 6 +++ .../narrative/default/default-itinerary.js | 28 ++++++---- .../narrative/narrative-itineraries.js | 51 +++++++++++++++++-- 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 8216c7667..65db8ec84 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -757,6 +757,12 @@ common: calories: "{calories, number} Cal" noItineraryToDisplay: No itinerary to display. transfers: "{transfers, plural, =0 {} one {# transfer} other {# transfers}}" + # This string will have a formatted number before it, including units. + relativeCo2: > + {isMore, select, + true {more} + other {less} + } CO2 than driving alone. # Note to translator: the strings below are used in sentences such as: # "No trip found for bike, walk, and transit." diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 3b7f08332..28f5546b7 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -29,7 +29,7 @@ import Icon from '../../util/icon' import { FlexIndicator } from './flex-indicator' import ItinerarySummary from './itinerary-summary' -const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff, calculateEmissions } = coreUtils.itinerary +const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff } = coreUtils.itinerary // Styled components const LegIconWrapper = styled.div` @@ -153,20 +153,22 @@ const ITINERARY_ATTRIBUTES = [ { id: 'carbonEmission', order: 4, - render: (itinerary, { co2Config }) => { - if(!co2Config) return // If there is no CO2 Config, don't display this element - - const { carbonIntensity, massUnit } = co2Config - const co2 = calculateEmissions(carbonIntensity, massUnit, itinerary) + render: (itinerary, { co2VsBaseline, hideCo2IfHigher }) => { + if (!co2VsBaseline) return return ( <> {' '} - CO2 + 0 + }} + /> @@ -212,8 +214,8 @@ class DefaultItinerary extends NarrativeItinerary { const { accessibilityScoreGradationMap, active, - co2Config, configCosts, + co2Config, currency, defaultFareKey, expanded, @@ -242,6 +244,8 @@ class DefaultItinerary extends NarrativeItinerary { .map((leg) => leg.pickupBookingInfo?.contactInfo?.phoneNumber) .filter((number) => !!number)[0] } + const {co2VsBaseline} = itinerary + return (
{ + const { itineraries } = this.props + const isCarOnly = (itin) => + itin.legs.length === 1 && itin.legs[0].mode.startsWith('CAR') + return ( + !!itineraries.filter(isCarOnly).length && itineraries.filter(isCarOnly)[0] + ) + } + + _getBaselineCo2 = () => { + const { co2Config, itineraries } = this.props + // Sums the sum of the leg distances for each leg + const avgDistance = + itineraries.reduce( + (sum, itin) => + sum + itin.legs.reduce((legsum, leg) => legsum + leg.distance, 0), + 0 + ) / itineraries.length + + // If we do not have a drive yourself itinerary, estimate the distance based on avg of transit distances. + return coreUtils.itinerary.calculateEmissions( + this._getCarItin() || { legs: [{ distance: avgDistance, mode: 'CAR' }] }, + co2Config?.carbonIntensity + ) + } + render() { const { activeItinerary, activeLeg, activeSearch, activeStep, + co2Config, errorMessages, errors, itineraries, @@ -152,11 +180,27 @@ class NarrativeItineraries extends Component { realtimeEffects.isAffectedByRealtimeData && (realtimeEffects.exceedsThreshold || realtimeEffects.routesDiffer) + const baselineCo2 = this._getBaselineCo2() + + const itinerariesWithCo2 = + itineraries?.map((itin) => { + const emissions = coreUtils.itinerary.calculateEmissions( + itin, + co2Config?.carbonIntensity + ) + console.log('Emissions', emissions) + return { + ...itin, + co2: emissions, + co2VsBaseline: (emissions - baselineCo2) / baselineCo2 + } + }) || [] + return ( ) : ( <> - {itineraries.map((itinerary, index) => { + {itinerariesWithCo2.map((itinerary, index) => { const active = index === activeItinerary // Hide non-active itineraries. if (!active && itineraryIsExpanded) return null @@ -222,7 +266,7 @@ class NarrativeItineraries extends Component { const mapStateToProps = (state) => { const activeSearch = getActiveSearch(state) const activeItinerary = activeSearch && activeSearch.activeItinerary - const { errorMessages, modes } = state.otp.config + const { co2, errorMessages, modes } = state.otp.config const { sort } = state.otp.filter const pending = activeSearch ? Boolean(activeSearch.pending) : false const itineraries = getActiveItineraries(state) @@ -242,6 +286,7 @@ const mapStateToProps = (state) => { activeLeg: activeSearch && activeSearch.activeLeg, activeSearch, activeStep: activeSearch && activeSearch.activeStep, + co2Config: co2, errorMessages, errors: getResponsesWithErrors(state), itineraries, From 4c205323288c71d5207a6274f5ba5b96bd475a1a Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 16 Feb 2022 14:49:05 -0800 Subject: [PATCH 0008/1425] refactor(itinerary): add rounding for co2 --- lib/components/narrative/default/default-itinerary.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 57a359da6..0c813659f 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -160,13 +160,14 @@ const ITINERARY_ATTRIBUTES = [ order: 4, render: (itinerary, { co2VsBaseline, hideCo2IfHigher }) => { if (!co2VsBaseline) return + const ROUND_TO_NEAREST = 5 return ( <> {' '} Date: Wed, 16 Feb 2022 16:08:18 -0800 Subject: [PATCH 0009/1425] refactor(default-itinerary): check for enable --- lib/components/narrative/default/default-itinerary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 0c813659f..d49b08b75 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -300,7 +300,7 @@ class DefaultItinerary extends NarrativeItinerary { options.isSelected = true options.selection = this.props.sort.type } - if(co2Config?.showCo2IfHigher || co2VsBaseline < 0) { + if(co2Config?.showCo2IfHigher || co2VsBaseline < 0 && co2Config?.enabled) { options.co2VsBaseline = co2VsBaseline } options.LegIcon = LegIcon From 5e63c5f1e87d41b547eba12f581ec32c5cc3b275 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 16 Feb 2022 16:28:35 -0800 Subject: [PATCH 0010/1425] refactor(default-itinerary): move rounding --- lib/components/narrative/default/default-itinerary.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index d49b08b75..888310fc3 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -161,13 +161,15 @@ const ITINERARY_ATTRIBUTES = [ render: (itinerary, { co2VsBaseline, hideCo2IfHigher }) => { if (!co2VsBaseline) return const ROUND_TO_NEAREST = 5 + co2VsBaseline = Math.round(co2VsBaseline * 100 / ROUND_TO_NEAREST)*ROUND_TO_NEAREST + if(Math.abs(co2VsBaseline) <= 10) return // Only show greater than 10% return ( <> {' '} Date: Fri, 18 Feb 2022 16:43:09 -0800 Subject: [PATCH 0011/1425] refactor(itineraries): small PR fixes --- lib/components/narrative/default/default-itinerary.js | 5 +++-- lib/components/narrative/narrative-itineraries.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 888310fc3..34799321b 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -161,8 +161,9 @@ const ITINERARY_ATTRIBUTES = [ render: (itinerary, { co2VsBaseline, hideCo2IfHigher }) => { if (!co2VsBaseline) return const ROUND_TO_NEAREST = 5 - co2VsBaseline = Math.round(co2VsBaseline * 100 / ROUND_TO_NEAREST)*ROUND_TO_NEAREST - if(Math.abs(co2VsBaseline) <= 10) return // Only show greater than 10% + co2VsBaseline = + Math.round(co2VsBaseline * 100 / ROUND_TO_NEAREST) * ROUND_TO_NEAREST + if(Math.abs(co2VsBaseline) <= 10) return // Only show greater than 10% return ( <> Date: Fri, 18 Feb 2022 16:56:50 -0800 Subject: [PATCH 0012/1425] improvement(itinerary): Align attributes to the right --- lib/components/narrative/default/itinerary.css | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/components/narrative/default/itinerary.css b/lib/components/narrative/default/itinerary.css index 88fb4bdee..e9508e7a7 100644 --- a/lib/components/narrative/default/itinerary.css +++ b/lib/components/narrative/default/itinerary.css @@ -42,6 +42,7 @@ .otp .option.default-itin > .header .itinerary-attributes { float: right; + text-align: right; } .otp .option.default-itin > .header .itinerary-attributes > li { From d94ee6030bd5852a9e15742a1eaacd452d6b8851 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 18 Feb 2022 16:57:31 -0800 Subject: [PATCH 0013/1425] refactor(itinerary): PR fixes for CO2 --- example-config.yml | 1 + lib/components/narrative/default/default-itinerary.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/example-config.yml b/example-config.yml index 4d58a2a5a..210c379f7 100644 --- a/example-config.yml +++ b/example-config.yml @@ -335,6 +335,7 @@ itinerary: ### Configure here the carbon output in grams per meter for each mode. ### massUnits can be 'pound', 'kilogram', 'ounce', or 'gram' # co2: +# . enabled: true # carbonIntensity: # walk: 0 # bicycle: 0 diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 34799321b..6eb2a08bd 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -158,7 +158,7 @@ const ITINERARY_ATTRIBUTES = [ { id: 'carbonEmission', order: 4, - render: (itinerary, { co2VsBaseline, hideCo2IfHigher }) => { + render: (itinerary, { co2VsBaseline }) => { if (!co2VsBaseline) return const ROUND_TO_NEAREST = 5 co2VsBaseline = From 68bdec024e21c263fb5cdaf93e9b3a0e390904f8 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Mon, 7 Mar 2022 17:15:42 -0800 Subject: [PATCH 0014/1425] fix(batch-settings): only remove walk when walk is deselected --- lib/components/form/batch-settings.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 20f87bbd9..92f002ad7 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -85,6 +85,7 @@ class BatchSettings extends Component<{ config: any currentQuery: any intl: IntlShape + modeOptions: Mode[] possibleCombinations: Combination[] routingQuery: any setQueryParam: (queryParam: any) => void @@ -95,7 +96,8 @@ class BatchSettings extends Component<{ } _onClickMode = (mode: string) => { - const { currentQuery, possibleCombinations, setQueryParam } = this.props + const { currentQuery, modeOptions, possibleCombinations, setQueryParam } = + this.props const { selectedModes } = this.state const index = selectedModes.indexOf(mode) const enableMode = index === -1 @@ -105,11 +107,17 @@ class BatchSettings extends Component<{ // Update selected modes for mode buttons. this.setState({ selectedModes: newModes }) // Update the available mode combinations based on the new modes selection. - const possibleModes = getModeOptions(this.props.intl).map((m) => m.mode) - const disabledModes = possibleModes.filter((m) => !newModes.includes(m)) + const possibleModes = modeOptions.map((m) => m.mode) + const disabledModes = possibleModes.filter( + // WALK will be filtered out separately, later + // Since we don't want to remove walk+other combos when walk is deselected. + (m) => !newModes.includes(m) && m !== 'WALK' + ) // Do not include combination if any of its modes are found in disabled // modes list. const newCombinations = possibleCombinations + // Filter out WALK only mode if walk is disabled + .filter((c) => newModes.includes('WALK') || c.mode !== 'WALK') .filter((c) => !combinationHasAnyOfModes(c, disabledModes)) .map(replaceTransitMode(currentQuery.mode)) setQueryParam({ combinations: newCombinations, disabledModes }) @@ -153,13 +161,14 @@ class BatchSettings extends Component<{ _toggleSettings = () => this.setState(this._updateExpanded('SETTINGS')) render() { - const { config, currentQuery, intl } = this.props + const { config, currentQuery, intl, modeOptions } = this.props const { expanded, selectedModes } = this.state return ( <> @@ -183,6 +192,7 @@ class BatchSettings extends Component<{ @@ -216,6 +226,7 @@ class BatchSettings extends Component<{ const mapStateToProps = (state: any) => ({ config: state.otp.config, currentQuery: state.otp.currentQuery, + modeOptions: state.otp.config.modes.modeOptions, possibleCombinations: state.otp.config.modes.combinations }) From a9bcd9d835033ada9ffa27e2ac10dea4baf29c0e Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 8 Mar 2022 11:01:28 -0800 Subject: [PATCH 0015/1425] fix(stop-viewer): use stop code as ID when applicable --- lib/components/viewers/stop-viewer.js | 2 +- lib/util/state.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index b106e8c63..254178716 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -217,7 +217,7 @@ class StopViewer extends Component {
+ + + ) : null + } +} + +const mapStateToProps = (state) => { + const { config, lastActionMillis } = state.otp + const { sessionTimeoutSeconds } = config + return { + lastActionMillis, + sessionTimeoutSeconds + } +} + +const mapDispatchToProps = { + resetSessionTimeout: uiActions.resetSessionTimeout, + startOverFromInitialUrl: uiActions.startOverFromInitialUrl +} + +export default connect(mapStateToProps, mapDispatchToProps)(SessionTimeout) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 31423d356..6369c9197 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -158,6 +158,8 @@ export function getInitialState(userDefinedConfig) { type: isBatchRoutingEnabled(config) ? 'BEST' : null } }, + initialUrl: window.location.href, + lastActionMillis: new Date().valueOf(), location: { currentPosition: { coords: null, @@ -222,7 +224,10 @@ function createOtpReducer(config) { validateInitialState(initialState) /* eslint-disable-next-line complexity */ - return (state = initialState, action) => { + return (prevState = initialState, action) => { + const state = update(prevState, { + lastActionMillis: { $set: new Date().valueOf() } + }) const searchId = action.payload && action.payload.searchId const requestId = action.payload && action.payload.requestId const activeSearch = state.searches[searchId] From da35f94bdca5edc83d6a6809f66ebf271cb08a74 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 18 Mar 2022 08:32:58 -0700 Subject: [PATCH 0043/1425] refactor(percy): set HAR download dynamically --- .github/workflows/percy.yml | 1 + percy/percy.test.js | 17 ++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index 6a46fd3f4..f67d3649b 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -26,3 +26,4 @@ jobs: run: npx percy exec -- npx jest percy/percy.test.js --force-exit env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + HAR_URL: ${{ secrets.PERCY_HAR_URL }} diff --git a/percy/percy.test.js b/percy/percy.test.js index 52e288889..d201e8b6a 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -18,7 +18,9 @@ jest.setTimeout(600000) const PERCY_EXTRA_WAIT = 5000 const percySnapshotWithWait = async (page, name, enableJavaScript) => { await page.waitForTimeout(PERCY_EXTRA_WAIT) - await percySnapshot(page, name, { enableJavaScript }) + + const namePrefix = process.env.PERCY_OTP_CONFIG_OVERRIDE || 'Mock OTP1 Server' + await percySnapshot(page, `${namePrefix} - ${name}`, { enableJavaScript }) } let browser @@ -43,19 +45,16 @@ beforeAll(async () => { try { // Build OTP-RR main.js using new config file await execa('env', [ - `YAML_CONFIG=${OTP_RR_TEST_CONFIG_PATH}`, + `YAML_CONFIG=${ + process.env.PERCY_OTP_CONFIG_OVERRIDE || OTP_RR_TEST_CONFIG_PATH + }`, `JS_CONFIG=${OTP_RR_TEST_JS_CONFIG_PATH}`, 'yarn', 'build' ]) // grab ATL har file to tmp - await execa('curl', [ - 'https://otp-repo.s3.amazonaws.com/ATL-fix_03-09-2022-14-40-46_03-10-2022-09-08-49.har', - '-s', - '--output', - 'ATL.har' - ]) + await execa('curl', [process.env.HAR_URL, '-s', '--output', 'mock.har']) } catch (error) { console.log(error) } @@ -67,7 +66,7 @@ beforeAll(async () => { }).stdout.pipe(process.stdout) // Launch mock OTP server - execa('yarn', ['percy-har-express', '-p', '9999', 'ATL.har'], { + execa('yarn', ['percy-har-express', '-p', '9999', 'mock.har'], { signal: harAbortController.signal }).stdout.pipe(process.stdout) From cab50eddf0f9b33819b49f30e5aa01592fc2ba12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 15:34:25 +0000 Subject: [PATCH 0044/1425] chore(deps): bump nanoid from 3.1.25 to 3.3.1 Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.25 to 3.3.1. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.1.25...3.3.1) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f3b3e99e8..b86641399 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13707,9 +13707,9 @@ nan@^2.12.1: integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nanoid@^3.1.23: - version "3.1.25" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" - integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== nanomatch@^1.2.9: version "1.2.13" From fe46e6da366d07c637652b4d13ed70bc22be2d2b Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 18 Mar 2022 08:52:33 -0700 Subject: [PATCH 0045/1425] refactor(percy): run pixel tests on live otp2 server --- .github/workflows/percy otp2.yml | 33 ++++++++++++++++++++++++++++++++ percy/percy.test.js | 12 ++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/percy otp2.yml diff --git a/.github/workflows/percy otp2.yml b/.github/workflows/percy otp2.yml new file mode 100644 index 000000000..40c12d462 --- /dev/null +++ b/.github/workflows/percy otp2.yml @@ -0,0 +1,33 @@ +name: Percy OTP2 + +on: + push: + branches: + - master + pull_request: + +jobs: + test-build-release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + # This allows us to work with the repository during the lint step + fetch-depth: 2 + - name: Use Node.js 16.x + uses: actions/setup-node@v1 + with: + node-version: 16.x + - name: Install npm packages using cache + uses: bahmutov/npm-install@v1 + - name: Download OTP2 config file + run: curl $PERCY_OTP2_CONFIG_URL --output /tmp/otp2config.yml + env: + PERCY_OTP2_CONFIG_URL: ${{ secrets.PERCY_OTP2_CONFIG_URL }} + - name: Take Percy Snapshots + # Move everything from latest commit back to staged + run: npx percy exec -- npx jest percy/percy.test.js --force-exit + env: + PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + PERCY_OTP_CONFIG_OVERRIDE: /tmp/otp2config.yml diff --git a/percy/percy.test.js b/percy/percy.test.js index d201e8b6a..711f5261e 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -54,7 +54,9 @@ beforeAll(async () => { ]) // grab ATL har file to tmp - await execa('curl', [process.env.HAR_URL, '-s', '--output', 'mock.har']) + if (process.env.HAR_URL) { + await execa('curl', [process.env.HAR_URL, '-s', '--output', 'mock.har']) + } } catch (error) { console.log(error) } @@ -66,9 +68,11 @@ beforeAll(async () => { }).stdout.pipe(process.stdout) // Launch mock OTP server - execa('yarn', ['percy-har-express', '-p', '9999', 'mock.har'], { - signal: harAbortController.signal - }).stdout.pipe(process.stdout) + if (process.env.HAR_URL) { + execa('yarn', ['percy-har-express', '-p', '9999', 'mock.har'], { + signal: harAbortController.signal + }).stdout.pipe(process.stdout) + } // Web security is disabled to allow requests to the mock OTP server browser = await puppeteer.launch({ From a8d9e1f58aa6146dd784f7ca7c294c64610264c4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 11:52:43 -0400 Subject: [PATCH 0046/1425] improvement(SessionTimeout): Exclude unattended actions from timeout, exclude actions occuring befor --- lib/components/app/session-timeout.tsx | 54 ++++++++++++++++---------- lib/reducers/create-otp-reducer.js | 14 +++++-- lib/util/constants.js | 7 ++++ 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index dc3df4ec4..35a70d852 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -7,30 +7,47 @@ import * as uiActions from '../../actions/ui' class SessionTimeout extends Component { state = { - showTimeoutWarning: false + showTimeoutWarning: false, + timeoutObject: null, + timeoutStartMillis: 0 + } + + componentDidMount() { + console.log('Session Timeout Mounted!') + // Wait ~one second or so after loading before probing for changes + // so that initialization actions can complete. + setTimeout(this.handleAfterInitialActions, 1500) } componentWillUnmount() { - clearInterval(this.timeoutWatch) + clearInterval(this.state.timeoutObject) + } + + handleAfterInitialActions = () => { + this.setState({ + timeoutObject: setInterval(this.handleTimeoutWatch, 10000), + timeoutStartMillis: new Date().valueOf() + }) + console.log('timeout start millis: ' + new Date().valueOf()) } handleTimeoutWatch = () => { const { lastActionMillis, sessionTimeoutSeconds, startOverFromInitialUrl } = this.props - const idleMillis = new Date().valueOf() - lastActionMillis - const secondsToTimeout = sessionTimeoutSeconds - idleMillis / 1000 - console.log('Seconds to timeout: ' + secondsToTimeout) - if (secondsToTimeout >= 0 && secondsToTimeout <= 60) { - // If within a minute of timeout, display dialog - this.setState({ - showTimeoutWarning: true - }) - } else if (secondsToTimeout <= 0) { - startOverFromInitialUrl() - } else { - this.setState({ - showTimeoutWarning: false - }) + if (lastActionMillis > this.state.timeoutStartMillis) { + const idleMillis = new Date().valueOf() - lastActionMillis + const secondsToTimeout = sessionTimeoutSeconds - idleMillis / 1000 + console.log('Seconds to timeout: ' + secondsToTimeout) + + if (secondsToTimeout < 0) { + // Reload initial URL (page state is lost after this point) + startOverFromInitialUrl() + } else { + this.setState({ + // If within a minute of timeout, display dialog, don't otherwise. + showTimeoutWarning: secondsToTimeout >= 0 && secondsToTimeout <= 60 + }) + } } } @@ -41,11 +58,6 @@ class SessionTimeout extends Component { this.props.resetSessionTimeout() } - /** - * Check session timeout every 10 seconds. - */ - timeoutWatch = setInterval(this.handleTimeoutWatch, 10000) - render() { const { showTimeoutWarning } = this.state return showTimeoutWarning ? ( diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 6369c9197..301dd4303 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -4,7 +4,11 @@ import coreUtils from '@opentripplanner/core-utils' import objectPath from 'object-path' import update from 'immutability-helper' -import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE } from '../util/constants' +import { + FETCH_STATUS, + PERSIST_TO_LOCAL_STORAGE, + TIMEOUT_IGNORED_ACTIONS +} from '../util/constants' import { getTimestamp } from '../util/state' import { isBatchRoutingEnabled } from '../util/itinerary' import { MainPanelContent, MobileScreens } from '../actions/ui' @@ -225,9 +229,11 @@ function createOtpReducer(config) { /* eslint-disable-next-line complexity */ return (prevState = initialState, action) => { - const state = update(prevState, { - lastActionMillis: { $set: new Date().valueOf() } - }) + const state = TIMEOUT_IGNORED_ACTIONS.includes(action.type) + ? prevState + : update(prevState, { + lastActionMillis: { $set: new Date().valueOf() } + }) const searchId = action.payload && action.payload.searchId const requestId = action.payload && action.payload.requestId const activeSearch = state.searches[searchId] diff --git a/lib/util/constants.js b/lib/util/constants.js index b0bfdc53c..55f705d64 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -26,3 +26,10 @@ export const TERMS_OF_STORAGE_PATH = '/terms-of-storage' // Gets the root URL, e.g. https://otp-instance.example.com:8080, computed once for all. // TODO: support root URLs that involve paths or subfolders, as in https://otp-ui.example.com/path-to-ui/ export const URL_ROOT = `${window.location.protocol}//${window.location.host}` + +// Contains ignored actions when determining timeout, +// such as actions triggered by a timer. +export const TIMEOUT_IGNORED_ACTIONS = [ + 'FETCHING_STOP_TIMES_FOR_STOP', + 'FIND_STOP_TIMES_FOR_STOP_RESPONSE' +] From a0ff60c0231a6a53b4d409cd6bc18fb2cde65c4e Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 18 Mar 2022 09:13:29 -0700 Subject: [PATCH 0047/1425] refactor(percy): improve otp1/otp2 parity in tests --- .github/workflows/percy otp2.yml | 33 -------------------------------- .github/workflows/percy.yml | 26 ++++++++++++++++++++++++- percy/percy.test.js | 16 ++++++++++++++-- 3 files changed, 39 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/percy otp2.yml diff --git a/.github/workflows/percy otp2.yml b/.github/workflows/percy otp2.yml deleted file mode 100644 index 40c12d462..000000000 --- a/.github/workflows/percy otp2.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Percy OTP2 - -on: - push: - branches: - - master - pull_request: - -jobs: - test-build-release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - # This allows us to work with the repository during the lint step - fetch-depth: 2 - - name: Use Node.js 16.x - uses: actions/setup-node@v1 - with: - node-version: 16.x - - name: Install npm packages using cache - uses: bahmutov/npm-install@v1 - - name: Download OTP2 config file - run: curl $PERCY_OTP2_CONFIG_URL --output /tmp/otp2config.yml - env: - PERCY_OTP2_CONFIG_URL: ${{ secrets.PERCY_OTP2_CONFIG_URL }} - - name: Take Percy Snapshots - # Move everything from latest commit back to staged - run: npx percy exec -- npx jest percy/percy.test.js --force-exit - env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} - PERCY_OTP_CONFIG_OVERRIDE: /tmp/otp2config.yml diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index f67d3649b..bdfa86d3f 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - test-build-release: + run-pixel-tests-with-otp1-mock-server: runs-on: ubuntu-latest steps: @@ -27,3 +27,27 @@ jobs: env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} HAR_URL: ${{ secrets.PERCY_HAR_URL }} + run-pixel-tests-with-otp2-real-server: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + # This allows us to work with the repository during the lint step + fetch-depth: 2 + - name: Use Node.js 16.x + uses: actions/setup-node@v1 + with: + node-version: 16.x + - name: Install npm packages using cache + uses: bahmutov/npm-install@v1 + - name: Download OTP2 config file + run: curl $PERCY_OTP2_CONFIG_URL --output /tmp/otp2config.yml + env: + PERCY_OTP2_CONFIG_URL: ${{ secrets.PERCY_OTP2_CONFIG_URL }} + - name: Take Percy Snapshots + # Move everything from latest commit back to staged + run: npx percy exec -- npx jest percy/percy.test.js --force-exit + env: + PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + PERCY_OTP_CONFIG_OVERRIDE: /tmp/otp2config.yml diff --git a/percy/percy.test.js b/percy/percy.test.js index 711f5261e..d49a7617e 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -197,10 +197,22 @@ test('OTP-RR', async () => { await percySnapshotWithWait(page, 'Route Viewer Showing Route 410') // View multiple patterns - await page.select('#headsign-selector', '6:410:1:01') + // Click second option + const sugarloafOption = await page.$$eval( + 'option', + (options) => options.find((o) => o.innerText.includes('Sugarloaf'))?.value + ) + await page.select('select#headsign-selector', sugarloafOption) + await page.waitForSelector('#headsign-selector-label') await page.waitForTimeout(1000) - await page.select('#headsign-selector', '6:410:0:01') + + // Click first option + const lindberghOption = await page.$$eval( + 'option', + (options) => options.find((o) => o.innerText.includes('Lindbergh'))?.value + ) + await page.select('select#headsign-selector', lindberghOption) await page.waitForTimeout(1000) await percySnapshotWithWait(page, 'Pattern Viewer Showing Route 410') From 371d683d4c2a8a97de3aa17659646ea6262b83c3 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 18 Mar 2022 09:14:27 -0700 Subject: [PATCH 0048/1425] chore(percy): correct yaml --- .github/workflows/percy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index bdfa86d3f..fcc899d65 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -27,7 +27,7 @@ jobs: env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} HAR_URL: ${{ secrets.PERCY_HAR_URL }} - run-pixel-tests-with-otp2-real-server: + run-pixel-tests-with-otp2-real-server: runs-on: ubuntu-latest steps: From 41724005b1ea63a62876991d03cb61bd802fb0e2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:16:22 -0400 Subject: [PATCH 0049/1425] improvement(SessionTimeout): Localize component --- i18n/en-US.yml | 4 ++++ i18n/fr.yml | 4 ++++ lib/components/app/session-timeout.tsx | 23 +++++++++++++++++------ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 77c5977bd..0e855d464 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -412,6 +412,10 @@ components: signInTooltip: Please sign in to save trip. SearchScreen: header: Plan Your Trip + SessionTimeout: + body: Your session will expire within a minute. Press 'Keep Session' to keep your search. + header: Session about to timeout! + keepSession: Keep Session SettingsPreview: defaultPreviewText: "Transit Options\n& Preferences" SimpleRealtimeAnnotation: diff --git a/i18n/fr.yml b/i18n/fr.yml index d6c57601e..a96f418ca 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -396,6 +396,10 @@ components: signInTooltip: Veuillez vous connecter pour enregistrer ce trajet. SearchScreen: header: Planifiez votre trajet + SessionTimeout: + body: Votre session va expirer d'ici une minute. Appuyez sur 'Prolonger ma session' pour conserver votre recherche. + header: Votre session va expirer + keepSession: Prolonger ma session SettingsPreview: defaultPreviewText: "Choix du mode\n& préférences" SimpleRealtimeAnnotation: diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index 35a70d852..ccf20dc97 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -4,7 +4,14 @@ import { connect } from 'react-redux' import React, { Component } from 'react' import * as uiActions from '../../actions/ui' +import { FormattedMessage } from 'react-intl' +/** + * This component makes the current session timeout + * by displaying a timeout warning one minute before the timeout, + * and by reloading the initial URL if there is no user-initiated + * actions within the timeout window. + */ class SessionTimeout extends Component { state = { showTimeoutWarning: false, @@ -13,8 +20,7 @@ class SessionTimeout extends Component { } componentDidMount() { - console.log('Session Timeout Mounted!') - // Wait ~one second or so after loading before probing for changes + // Wait one second or so after loading before probing for changes // so that initialization actions can complete. setTimeout(this.handleAfterInitialActions, 1500) } @@ -59,21 +65,26 @@ class SessionTimeout extends Component { } render() { + const { startOverFromInitialUrl } = this.props const { showTimeoutWarning } = this.state return showTimeoutWarning ? ( - Session about to timeout! + + + - Your session will expire within a minute, unless you click 'Keep - Session'. + + From ed57939d03c386fbc03a702e8372166c5b3b5d47 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:30:27 -0400 Subject: [PATCH 0050/1425] refactor(SessionTimeout): Clean up code --- lib/components/app/session-timeout.tsx | 4 +--- lib/reducers/create-otp-reducer.js | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index ccf20dc97..bf07f26a3 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -1,10 +1,10 @@ /* eslint-disable react/prop-types */ import { Button, Modal } from 'react-bootstrap' import { connect } from 'react-redux' +import { FormattedMessage } from 'react-intl' import React, { Component } from 'react' import * as uiActions from '../../actions/ui' -import { FormattedMessage } from 'react-intl' /** * This component makes the current session timeout @@ -34,7 +34,6 @@ class SessionTimeout extends Component { timeoutObject: setInterval(this.handleTimeoutWatch, 10000), timeoutStartMillis: new Date().valueOf() }) - console.log('timeout start millis: ' + new Date().valueOf()) } handleTimeoutWatch = () => { @@ -43,7 +42,6 @@ class SessionTimeout extends Component { if (lastActionMillis > this.state.timeoutStartMillis) { const idleMillis = new Date().valueOf() - lastActionMillis const secondsToTimeout = sessionTimeoutSeconds - idleMillis / 1000 - console.log('Seconds to timeout: ' + secondsToTimeout) if (secondsToTimeout < 0) { // Reload initial URL (page state is lost after this point) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 301dd4303..daacc063b 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -229,6 +229,7 @@ function createOtpReducer(config) { /* eslint-disable-next-line complexity */ return (prevState = initialState, action) => { + // Update last action timestamp for eligible actions. const state = TIMEOUT_IGNORED_ACTIONS.includes(action.type) ? prevState : update(prevState, { From 72c00346bdb275a2d299acb4dc4674389d9e982d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:48:35 -0400 Subject: [PATCH 0051/1425] refactor(SessionTimeout): Move TODO comment from config to code. --- example-config.yml | 1 - lib/components/app/session-timeout.tsx | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/example-config.yml b/example-config.yml index f924f1394..82bbd1739 100644 --- a/example-config.yml +++ b/example-config.yml @@ -451,5 +451,4 @@ dateTime: ### Approximate duration in seconds after which, if there is no user activity, ### the UI is reset to an initial URL. -### TODO: If OTP middleware persistence is enabled, log out that user before resetting the page. # sessionTimeoutSeconds: 180 diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index bf07f26a3..71dd14651 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -44,6 +44,9 @@ class SessionTimeout extends Component { const secondsToTimeout = sessionTimeoutSeconds - idleMillis / 1000 if (secondsToTimeout < 0) { + // TODO: If OTP middleware persistence is enabled, + // log out the logged-in user before resetting the page. + // Reload initial URL (page state is lost after this point) startOverFromInitialUrl() } else { From e167ab96796de38b4d261a1419068413466f9aea Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:05:39 -0400 Subject: [PATCH 0052/1425] test(Update reducer snapshot): --- __tests__/reducers/__snapshots__/create-otp-reducer.js.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap index 0b0e5d149..7b4047280 100644 --- a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap +++ b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap @@ -58,6 +58,8 @@ Object { "type": null, }, }, + "initialUrl": "http://localhost:9966/", + "lastActionMillis": 1647622883532, "location": Object { "currentPosition": Object { "coords": null, From 62e82051bac474ad21e0a83e3bf3f76262e72c85 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 18 Mar 2022 10:09:11 -0700 Subject: [PATCH 0053/1425] refactor(percy): move otp2 tests to second percy project --- .github/workflows/percy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index fcc899d65..845cc2004 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -49,5 +49,5 @@ jobs: # Move everything from latest commit back to staged run: npx percy exec -- npx jest percy/percy.test.js --force-exit env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} + PERCY_TOKEN: ${{ secrets.PERCY_TOKEN_OTP2 }} PERCY_OTP_CONFIG_OVERRIDE: /tmp/otp2config.yml From 69ee44ae46c186f06907f5c60e91dd5a595df657 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:26:05 -0400 Subject: [PATCH 0054/1425] refactor(SessionTimeout): Add ts check waivers --- lib/components/app/session-timeout.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index 71dd14651..178415d8e 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable react/prop-types */ import { Button, Modal } from 'react-bootstrap' import { connect } from 'react-redux' @@ -26,6 +27,7 @@ class SessionTimeout extends Component { } componentWillUnmount() { + // @ts-ignore SessionTimeout is not typed yet clearInterval(this.state.timeoutObject) } @@ -37,6 +39,7 @@ class SessionTimeout extends Component { } handleTimeoutWatch = () => { + // @ts-ignore SessionTimeout is not typed yet const { lastActionMillis, sessionTimeoutSeconds, startOverFromInitialUrl } = this.props if (lastActionMillis > this.state.timeoutStartMillis) { @@ -59,14 +62,18 @@ class SessionTimeout extends Component { } handleKeepSession = () => { + // @ts-ignore SessionTimeout is not typed yet this.setState({ showTimeoutWarning: false }) + // @ts-ignore SessionTimeout is not typed yet this.props.resetSessionTimeout() } render() { + // @ts-ignore SessionTimeout is not typed yet const { startOverFromInitialUrl } = this.props + // @ts-ignore SessionTimeout is not typed yet const { showTimeoutWarning } = this.state return showTimeoutWarning ? ( @@ -93,6 +100,7 @@ class SessionTimeout extends Component { } } +// @ts-ignore SessionTimeout is not typed yet const mapStateToProps = (state) => { const { config, lastActionMillis } = state.otp const { sessionTimeoutSeconds } = config From 8b2706ba71ea2884f978b9c1dfd74b327918409b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:35:33 -0400 Subject: [PATCH 0055/1425] refactor(OTP reducer): Initialize last action timestamp to zero. --- __tests__/reducers/__snapshots__/create-otp-reducer.js.snap | 2 +- lib/reducers/create-otp-reducer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap index 7b4047280..b985a03f8 100644 --- a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap +++ b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap @@ -59,7 +59,7 @@ Object { }, }, "initialUrl": "http://localhost:9966/", - "lastActionMillis": 1647622883532, + "lastActionMillis": 0, "location": Object { "currentPosition": Object { "coords": null, diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index daacc063b..c3c866da3 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -163,7 +163,7 @@ export function getInitialState(userDefinedConfig) { } }, initialUrl: window.location.href, - lastActionMillis: new Date().valueOf(), + lastActionMillis: 0, location: { currentPosition: { coords: null, From 1d14087dc195a59c8316e7b528f6506deae7a9fe Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 21 Mar 2022 15:49:45 -0400 Subject: [PATCH 0056/1425] fix(DefaultMap): Prevent custom overlays from flashing when search is pending. --- lib/components/map/default-map.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index b71d12cb2..a8610437b 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -10,7 +10,7 @@ import { vehicleRentalQuery } from '../../actions/api' import { ComponentContext } from '../../util/contexts' -import { getActiveItinerary } from '../../util/state' +import { getActiveItinerary, getActiveSearch } from '../../util/state' import { setLocation, setMapPopupLocation, @@ -135,6 +135,7 @@ class DefaultMap extends Component { itinerary, mapConfig, mapPopupLocation, + pending, vehicleRentalQuery, vehicleRentalStations } = this.props @@ -220,9 +221,9 @@ class DefaultMap extends Component { return null } })} - {/* Render custom overlays, if set, and pass visibility argument. */} + {/* If set, custom overlays are shown if no active itinerary is shown or pending. */} {typeof getCustomMapOverlays === 'function' && - getCustomMapOverlays(!itinerary)} + getCustomMapOverlays(!itinerary && !pending)} ) @@ -232,10 +233,8 @@ class DefaultMap extends Component { // connect to the redux store const mapStateToProps = (state) => { - const overlays = - state.otp.config.map && state.otp.config.map.overlays - ? state.otp.config.map.overlays - : [] + const activeSearch = getActiveSearch(state) + const overlays = state.otp.config.map?.overlays || [] return { bikeRentalStations: state.otp.overlay.bikeRental.stations, @@ -244,6 +243,7 @@ const mapStateToProps = (state) => { mapConfig: state.otp.config.map, mapPopupLocation: state.otp.ui.mapPopupLocation, overlays, + pending: activeSearch ? Boolean(activeSearch.pending) : false, query: state.otp.currentQuery, vehicleRentalStations: state.otp.overlay.vehicleRental.stations } From f34c06f5463c2c5866a4c7f7b657e4d0e21ff5b8 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:29:34 -0400 Subject: [PATCH 0057/1425] test(DefaultMap): Disable type checks for now. --- lib/components/map/default-map.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index a8610437b..39cfb5b34 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -1,4 +1,6 @@ /* eslint-disable react/prop-types */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { connect } from 'react-redux' import BaseMap from '@opentripplanner/base-map' import React, { Component } from 'react' From 71e78f02d5c08d47d29cef0fa83d6a3de62b3c8f Mon Sep 17 00:00:00 2001 From: "philip.cline" Date: Tue, 22 Mar 2022 14:17:35 -0400 Subject: [PATCH 0058/1425] refactor(ConnectedItineraryBody): Address PR comments --- .../line-itin/connected-itinerary-body.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index ff302683b..9cf7d404a 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -61,31 +61,35 @@ class ConnectedItineraryBody extends Component { timeOptions } = this.props const { LegIcon } = this.context - const clonedItinerary = clone(itinerary) - let pickupAdded = false - let dropOffAdded = false // Support OTP1 flex messages in Trip Details - clonedItinerary.legs = clonedItinerary.legs.map((leg) => { - if (!!leg.boardRule && !leg.pickupBookingInfo && !pickupAdded) { - pickupAdded = true - leg.pickupBookingInfo = { - latestBookingTime: { - daysPrior: 0 - } + // Adding empty pickupBookingInfo and dropOffBookingInfo objects + // to a leg will trigger relevant flex pickup / dropoff descriptions in + // the Trip Details component. + const boardingRuleLeg = clonedItinerary.legs.find( + (leg) => !!leg.boardRule && !leg.pickupBookingInfo + ) + const alightingRuleLeg = clonedItinerary.legs.find( + (leg) => !!leg.alightRule && !leg.dropOffBookingInfo + ) + + // Add pickupBookingInfo for any boarding rules + if (boardingRuleLeg) { + boardingRuleLeg.pickupBookingInfo = { + latestBookingTime: { + daysPrior: 0 } } - if (!!leg.alightRule && !leg.dropOffBookingInfo && !dropOffAdded) { - dropOffAdded = true - leg.dropOffBookingInfo = { - latestBookingTime: { - daysPrior: 0 - } + } + // Add dropOffBookingInfo for any alighting rules + if (alightingRuleLeg) { + alightingRuleLeg.dropOffBookingInfo = { + latestBookingTime: { + daysPrior: 0 } } - return leg - }) + } return ( From 03c35e8c2eb5bb583ac31f17eef78e541bc41635 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 23 Mar 2022 10:07:27 -0400 Subject: [PATCH 0059/1425] chore(i18n): Match up French file. --- i18n/fr.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/i18n/fr.yml b/i18n/fr.yml index d6c57601e..6ede6ac4d 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -827,3 +827,11 @@ util: titleBarRouteId: "Ligne {routeId}" titleBarStopId: "Arrêt {stopId}" titleBarWithStatus: "{title} | {status}" + +# Default values for Flex Indicator (set in configuration as well) +config: + flex: + flex-service: Service Flex + both: Plus de détails au bas de l'itinéraire + call-ahead: Appelez pour réserver + continuous-dropoff: Demandez l'arrêt au conducteur \ No newline at end of file From 2d0fb1762f81c70170ca25bb48ed2fd081fbbfab Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 09:09:39 -0700 Subject: [PATCH 0060/1425] feat(connected-route-viewer-overlay): only focus route on active route change --- lib/actions/ui.js | 1 + lib/components/map/connected-route-viewer-overlay.js | 9 +++------ lib/reducers/create-otp-reducer.js | 3 +++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 6f50b72b6..021eb7d4b 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -62,6 +62,7 @@ const viewStop = createAction('SET_VIEWED_STOP') export const setHoveredStop = createAction('SET_HOVERED_STOP') export const setViewedTrip = createAction('SET_VIEWED_TRIP') const viewRoute = createAction('SET_VIEWED_ROUTE') +export const unfocusRoute = createAction('UNFOCUS_ROUTE') export const toggleAutoRefresh = createAction('TOGGLE_AUTO_REFRESH') const setPreviousItineraryView = createAction('SET_PREVIOUS_ITINERARY_VIEW') diff --git a/lib/components/map/connected-route-viewer-overlay.js b/lib/components/map/connected-route-viewer-overlay.js index eba2527b5..bd157b0a2 100644 --- a/lib/components/map/connected-route-viewer-overlay.js +++ b/lib/components/map/connected-route-viewer-overlay.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import RouteViewerOverlay from '@opentripplanner/route-viewer-overlay' -import { MainPanelContent } from '../../actions/ui' +import { unfocusRoute } from '../../actions/ui' // connect to the redux store @@ -22,16 +22,13 @@ const mapStateToProps = (state, ownProps) => { } return { - allowMapCentering: - // TODO: allow panning of the map after initial click, but before pattern - // viewer open - state.otp.ui?.mainPanelContent === MainPanelContent.ROUTE_VIEWER, + allowMapCentering: state.otp.ui?.focusRoute, clipToPatternStops: state.otp.config?.routeViewer?.hideRouteShapesWithinFlexZones, routeData: { ...routeData, patterns: filteredPatterns } } } -const mapDispatchToProps = {} +const mapDispatchToProps = { mapCenterCallback: unfocusRoute } export default connect(mapStateToProps, mapDispatchToProps)(RouteViewerOverlay) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0d7f65da7..82f1d4280 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -693,6 +693,7 @@ function createOtpReducer(config) { // If setting to a route (not null), also set main panel. return update(state, { ui: { + focusRoute: { $set: true }, mainPanelContent: { $set: MainPanelContent.ROUTE_VIEWER }, viewedRoute: { $set: action.payload } } @@ -703,6 +704,8 @@ function createOtpReducer(config) { ui: { viewedRoute: { $set: action.payload } } }) } + case 'UNFOCUS_ROUTE': + return update(state, { ui: { focusRoute: { $set: false } } }) case 'FIND_STOP_RESPONSE': return update(state, { transitIndex: { From 67e1dc094ba9047bede64664b66a399085257cd0 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 09:31:47 -0700 Subject: [PATCH 0061/1425] refactor: address pr feedback --- .github/workflows/codespell.yml | 2 +- percy/percy.test.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index da8f6edb8..9cb516a40 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,7 +12,7 @@ jobs: - uses: codespell-project/actions-codespell@master with: check_filenames: true - # skip git, yarn, and i18n non-english resources. + # skip git, yarn, pixel test script, and i18n non-english resources. # Also, the a11y test file has a false positive and the ignore list does not work # see https://github.com/opentripplanner/otp-react-redux/pull/436/checks?check_run_id=3369380014 skip: ./.git,yarn.lock,./a11y/a11y.test.js,./percy/percy.test.js,./i18n/fr* diff --git a/percy/percy.test.js b/percy/percy.test.js index d49a7617e..5642b1a7e 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -11,7 +11,7 @@ const OTP_RR_TEST_JS_CONFIG_PATH = './percy/har-mock-config.js' const MOCK_SERVER_PORT = 5000 -// Puppeteer can take a long time to load, espeically in some ci environments +// Puppeteer can take a long time to load, especially in some ci environments jest.setTimeout(600000) // How long to wait for each page to fully render before taking a screenshot @@ -86,7 +86,7 @@ beforeAll(async () => { const client = await targetPage.target().createCDPSession() await client.send('Runtime.evaluate', { expression: - 'Date.now = function() { return 1646835742000; };Date.getTime = function() { return 1646835742000; }' + 'Date.now = function() { return 1646835742000; }; Date.getTime = function() { return 1646835742000; }' }) }) } catch (error) { @@ -112,9 +112,10 @@ afterAll(async () => { jest.setTimeout(600000) /* These fixed routes allow us to test features that the static html screenshots - * don't allow us to test. Unfortuantley, they don't currently work + * don't allow us to test. This is disabled, as percy doesn't support transitive.js + * out of the box, even with javascript enabled. * - * TODO + * TODO: make transitive.js work with Percy, then complete this test suite */ // eslint-disable-next-line jest/no-commented-out-tests /* @@ -198,21 +199,21 @@ test('OTP-RR', async () => { // View multiple patterns // Click second option - const sugarloafOption = await page.$$eval( + const secondPatternOption = await page.$$eval( 'option', (options) => options.find((o) => o.innerText.includes('Sugarloaf'))?.value ) - await page.select('select#headsign-selector', sugarloafOption) + await page.select('select#headsign-selector', secondPatternOption) await page.waitForSelector('#headsign-selector-label') await page.waitForTimeout(1000) // Click first option - const lindberghOption = await page.$$eval( + const firstPatternOption = await page.$$eval( 'option', (options) => options.find((o) => o.innerText.includes('Lindbergh'))?.value ) - await page.select('select#headsign-selector', lindberghOption) + await page.select('select#headsign-selector', firstPatternOption) await page.waitForTimeout(1000) await percySnapshotWithWait(page, 'Pattern Viewer Showing Route 410') From 82caf1a4782fc51e417bdb712ea8b301f7ba12c6 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 09:32:46 -0700 Subject: [PATCH 0062/1425] chore(deps): update route-viewer-overlay to alpha build --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 13cd16415..29d1fed90 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.2", "@opentripplanner/printable-itinerary": "^1.3.1", - "@opentripplanner/route-viewer-overlay": "^1.3.0", + "@opentripplanner/route-viewer-overlay": "^1.4.0-alpha.1", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^3.4.0", "@opentripplanner/transit-vehicle-overlay": "^2.3.1", diff --git a/yarn.lock b/yarn.lock index af9aa32bc..228a50e14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2617,10 +2617,10 @@ "@opentripplanner/humanize-distance" "^1.1.0" prop-types "^15.7.2" -"@opentripplanner/route-viewer-overlay@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/route-viewer-overlay/-/route-viewer-overlay-1.3.0.tgz#9a50efea8c06719ff03b413e8451d7cc48f91915" - integrity sha512-FW0Evav6S9wf50iNrZ/gDej7VeCDUKYTQkpnAKQQFWrM1V/nIT1+oFuG9lNSpAOtlO5/HaPTGmerhnDUBwjdhA== +"@opentripplanner/route-viewer-overlay@^1.4.0-alpha.1": + version "1.4.0-alpha.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/route-viewer-overlay/-/route-viewer-overlay-1.4.0-alpha.1.tgz#58c9489d494031507b3153396e738fa1c008ad63" + integrity sha512-h9aV9buZc6xOLyM+ZQ2+ITqpzb31ZrdtNdCsqEOBiZLekjf31ucZUTq966LUv2fhUgk7x55r0ij3GiXh4UxZKA== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/core-utils" "^4.5.0" From 9d422cc0003342bf86653ae27e27d08402eb6226 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 09:41:28 -0700 Subject: [PATCH 0063/1425] chore(ci): attempt to force ci build --- percy/percy.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/percy/percy.test.js b/percy/percy.test.js index 5642b1a7e..d8071225c 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -1,6 +1,3 @@ -/* eslint-disable jest/expect-expect */ -/* We use a method to generate our assertions */ - import execa from 'execa' import puppeteer from 'puppeteer' @@ -130,6 +127,8 @@ test('OTP-RR Fixed Routes', async () => { }) */ +// Percy screenshot is not an assertion, but that's ok +// eslint-disable-next-line jest/expect-expect test('OTP-RR', async () => { const page = await loadPath('/') await page.setViewport({ From ceaeaeec08c31557e8a0e87ba9603e06ad9de83f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 10:33:32 -0700 Subject: [PATCH 0064/1425] chore: run autoformatter and move to typescript --- ...span-with-space.js => span-with-space.tsx} | 2 +- ...live-stop-times.js => live-stop-times.tsx} | 119 ++++++++++-------- .../{stop-time-cell.js => stop-time-cell.tsx} | 98 ++++++++------- 3 files changed, 125 insertions(+), 94 deletions(-) rename lib/components/util/{span-with-space.js => span-with-space.tsx} (73%) rename lib/components/viewers/{live-stop-times.js => live-stop-times.tsx} (67%) rename lib/components/viewers/{stop-time-cell.js => stop-time-cell.tsx} (70%) diff --git a/lib/components/util/span-with-space.js b/lib/components/util/span-with-space.tsx similarity index 73% rename from lib/components/util/span-with-space.js rename to lib/components/util/span-with-space.tsx index f34637c10..7f4df3a1e 100644 --- a/lib/components/util/span-with-space.js +++ b/lib/components/util/span-with-space.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components' -const SpanWithSpace = styled.span` +const SpanWithSpace = styled.span<{ margin: number }>` &::after { content: ''; margin: 0 ${(props) => props.margin}em; diff --git a/lib/components/viewers/live-stop-times.js b/lib/components/viewers/live-stop-times.tsx similarity index 67% rename from lib/components/viewers/live-stop-times.js rename to lib/components/viewers/live-stop-times.tsx index df55c5011..504a037d0 100644 --- a/lib/components/viewers/live-stop-times.js +++ b/lib/components/viewers/live-stop-times.tsx @@ -1,15 +1,18 @@ import 'moment-timezone' +import { FormattedMessage, FormattedTime } from 'react-intl' +// TODO: typescript core-utils, add common types (pattern, stop, configs) +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' -import { FormattedMessage, FormattedTime } from 'react-intl' -import Icon from '../util/icon' -import SpanWithSpace from '../util/span-with-space' import { getRouteIdForPattern, getStopTimesByPattern, routeIsValid } from '../../util/viewer' +import Icon from '../util/icon' +import SpanWithSpace from '../util/span-with-space' import AmenitiesPanel from './amenities-panel' import PatternRow from './pattern-row' @@ -22,14 +25,34 @@ const defaultState = { timer: null } +type Props = { + autoRefreshStopTimes: boolean + findStopTimesForStop: ({ stopId }: { stopId: string }) => void + homeTimezone?: string + nearbyStops: any // TODO: shared types + setHoveredStop: (stopId: string) => void + showNearbyStops: boolean + stopData: any // TODO: shared types + stopViewerArriving: any // TODO: shared types + stopViewerConfig: any // TODO: shared types + toggleAutoRefresh: (enable: boolean) => void + transitOperators: any // TODO: shared types + viewedStop: { stopId: string } +} + +type State = { + spin?: boolean + timer?: number | null +} + /** * Table showing next arrivals (refreshing every 10 seconds) for the specified * stop organized by route pattern. */ -class LiveStopTimes extends Component { +class LiveStopTimes extends Component { state = defaultState - _refreshStopTimes = () => { + _refreshStopTimes = (): void => { const { findStopTimesForStop, viewedStop } = this.props findStopTimesForStop({ stopId: viewedStop.stopId }) // FIXME: We need to refresh child stop arrivals, but this could be a ton of @@ -43,7 +66,7 @@ class LiveStopTimes extends Component { window.setTimeout(this._stopSpin, 1000) } - _onToggleAutoRefresh = () => { + _onToggleAutoRefresh = (): void => { const { autoRefreshStopTimes, toggleAutoRefresh } = this.props if (autoRefreshStopTimes) { toggleAutoRefresh(false) @@ -54,29 +77,30 @@ class LiveStopTimes extends Component { } } - _stopSpin = () => this.setState({ spin: false }) + _stopSpin = (): void => this.setState({ spin: false }) - _startAutoRefresh = () => { + _startAutoRefresh = (): void => { const timer = window.setInterval(this._refreshStopTimes, 10000) this.setState({ timer }) } - _stopAutoRefresh = () => { + _stopAutoRefresh = (): void => { + if (!this.state.timer) return window.clearInterval(this.state.timer) } - componentDidMount () { + componentDidMount(): void { this._refreshStopTimes() // Turn on stop times refresh if enabled. if (this.props.autoRefreshStopTimes) this._startAutoRefresh() } - componentWillUnmount () { + componentWillUnmount(): void { // Turn off auto refresh unconditionally (just in case). this._stopAutoRefresh() } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps: Props): void { if ( prevProps.viewedStop && this.props.viewedStop && @@ -85,11 +109,13 @@ class LiveStopTimes extends Component { this._refreshStopTimes() } // Handle stopping or starting the auto refresh timer. - if (prevProps.autoRefreshStopTimes && !this.props.autoRefreshStopTimes) this._stopAutoRefresh() - else if (!prevProps.autoRefreshStopTimes && this.props.autoRefreshStopTimes) this._startAutoRefresh() + if (prevProps.autoRefreshStopTimes && !this.props.autoRefreshStopTimes) + this._stopAutoRefresh() + else if (!prevProps.autoRefreshStopTimes && this.props.autoRefreshStopTimes) + this._startAutoRefresh() } - render () { + render(): JSX.Element { const { homeTimezone, nearbyStops, @@ -105,18 +131,16 @@ class LiveStopTimes extends Component { // construct a lookup table mapping pattern (e.g. 'ROUTE_ID-HEADSIGN') to // an array of stoptimes const stopTimesByPattern = getStopTimesByPattern(stopData) - const routeComparator = coreUtils.route.makeRouteComparator( - transitOperators - ) + const routeComparator = + coreUtils.route.makeRouteComparator(transitOperators) const patternHeadsignComparator = coreUtils.route.makeStringValueComparator( - pattern => pattern.pattern.headsign + // TODO: Shared types + (pattern: any) => pattern.pattern.headsign ) - const patternComparator = (patternA, patternB) => { + // TODO: Shared types + const patternComparator = (patternA: any, patternB: any) => { // first sort by routes - const routeCompareValue = routeComparator( - patternA.route, - patternB.route - ) + const routeCompareValue = routeComparator(patternA.route, patternB.route) if (routeCompareValue !== 0) return routeCompareValue // if same route, sort by headsign @@ -130,21 +154,18 @@ class LiveStopTimes extends Component { .sort(patternComparator) .map(({ id, pattern, route, times }) => { // Only add pattern if route info is returned by OTP. - return routeIsValid(route, getRouteIdForPattern(pattern)) - ? ( - - ) - : null - }) - } + return routeIsValid(route, getRouteIdForPattern(pattern)) ? ( + + ) : null + })}
{/* Auto update controls for realtime arrivals */} @@ -154,30 +175,26 @@ class LiveStopTimes extends Component { - + - {showNearbyStops && + {showNearbyStops && ( <> - } + )}
) diff --git a/lib/components/viewers/stop-time-cell.js b/lib/components/viewers/stop-time-cell.tsx similarity index 70% rename from lib/components/viewers/stop-time-cell.js rename to lib/components/viewers/stop-time-cell.tsx index 94d9225ef..03ad8457c 100644 --- a/lib/components/viewers/stop-time-cell.js +++ b/lib/components/viewers/stop-time-cell.tsx @@ -1,36 +1,54 @@ -import moment from 'moment' import 'moment-timezone' +import { FormattedMessage, FormattedTime } from 'react-intl' +// TODO: typescript core-utils, add common types (pattern, stop, configs) +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' +import moment from 'moment' import React from 'react' -import { FormattedMessage, FormattedTime } from 'react-intl' -import FormattedDuration from '../util/formatted-duration' +import { getSecondsUntilDeparture } from '../../util/viewer' import FormattedDayOfWeek from '../util/formatted-day-of-week' +import FormattedDuration from '../util/formatted-duration' import Icon from '../util/icon' -import { getSecondsUntilDeparture } from '../../util/viewer' -const { - getUserTimezone -} = coreUtils.time +const { getUserTimezone } = coreUtils.time const ONE_HOUR_IN_SECONDS = 3600 const ONE_DAY_IN_SECONDS = 86400 +type Props = { + /** If configured, the timezone of the area */ + homeTimezone?: string + /** A stopTime object as received from a transit index API */ + // TODO: common shared types + stopTime: any +} + /** * Renders a stop time as either schedule or countdown, with an optional status icon. * Stop time that apply to a different day have an additional text showing the day of departure. */ const StopTimeCell = ({ - homeTimezone, + homeTimezone = getUserTimezone(), stopTime -}) => { +}: Props): JSX.Element => { if (!homeTimezone || !stopTime) { - console.warn('Missing required prop(s) for StopTimeCell: homeTimezone, stopTime') - return
+ console.warn( + 'Missing required prop(s) for StopTimeCell: homeTimezone, stopTime' + ) + return ( +
+ +
+ ) } if (!moment.tz.zone(homeTimezone)) { console.warn(`homeTimezone '${homeTimezone}' is invalid`) - return
+ return ( +
+ +
+ ) } const userTimezone = getUserTimezone() const departureTime = stopTime.realtimeDeparture @@ -39,7 +57,9 @@ const StopTimeCell = ({ // Convert seconds after midnight to unix (milliseconds) for FormattedTime // Convert to userTimezone rather than spying on startOf for tests - const departureMoment = moment(stopTime.serviceDay * 1000).tz(userTimezone).startOf('day') + const departureMoment = moment(stopTime.serviceDay * 1000) + .tz(userTimezone) + .startOf('day') departureMoment.add(departureTime, 's') const departureTimestamp = departureMoment.valueOf() @@ -48,18 +68,22 @@ const StopTimeCell = ({ // this can handle the rare (and non-existent?) case where an arrival occurs // 48:00 hours (or more) from the start of the service day. const departureTimeRemainder = departureTime % ONE_DAY_IN_SECONDS - const daysAfterServiceDay = (departureTime - departureTimeRemainder) / ONE_DAY_IN_SECONDS + const daysAfterServiceDay = + (departureTime - departureTimeRemainder) / ONE_DAY_IN_SECONDS const departureDay = serviceDay.add(daysAfterServiceDay, 'day') const vehicleDepartsToday = now.dayOfYear() === departureDay?.dayOfYear() // Determine whether to show departure as countdown (e.g. "5 min") or as HH:mm // time, using realtime updates if available. - const secondsUntilDeparture = Math.round(getSecondsUntilDeparture(stopTime, false)) + const secondsUntilDeparture = Math.round( + getSecondsUntilDeparture(stopTime, false) + ) // Determine if vehicle arrives after midnight in order to advance the day of // the week when showing arrival time/day. const departsInFuture = secondsUntilDeparture > 0 // Show the exact time if the departure happens within an hour. - const showCountdown = secondsUntilDeparture < ONE_HOUR_IN_SECONDS && departsInFuture + const showCountdown = + secondsUntilDeparture < ONE_HOUR_IN_SECONDS && departsInFuture // We only want to show the day of the week if the arrival is on a // different day and we're not showing the countdown string. This avoids @@ -69,7 +93,7 @@ const StopTimeCell = ({ return (
-
+
- {showDayOfWeek && + {showDayOfWeek && (
- } + )}
- {showCountdown + {showCountdown ? ( // Show countdown string (e.g., 3 min or Due) - ? , + formattedDuration: ( + + ), isDue: secondsUntilDeparture < 60 }} /> + ) : ( // Show formatted time (with timezone if user is not in home timezone) - : - } + )}
) } -StopTimeCell.propTypes = { - /** If configured, the timezone of the area */ - homeTimezone: PropTypes.string, - /** The text to display for imminent departure times */ - soonText: PropTypes.string, - /** A stopTime object as received from a transit index API */ - stopTime: PropTypes.any.isRequired -} - -StopTimeCell.defaultProps = { - homeTimezone: getUserTimezone() -} - export default StopTimeCell From c070f5ed8a29bc047dc3178bf06171c0e76a6bb9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 25 Mar 2022 10:39:48 -0700 Subject: [PATCH 0065/1425] ci(percy): hide times that change --- lib/components/viewers/live-stop-times.tsx | 2 +- lib/components/viewers/stop-time-cell.tsx | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/viewers/live-stop-times.tsx b/lib/components/viewers/live-stop-times.tsx index 504a037d0..737a077be 100644 --- a/lib/components/viewers/live-stop-times.tsx +++ b/lib/components/viewers/live-stop-times.tsx @@ -183,7 +183,7 @@ class LiveStopTimes extends Component {
)} -
+
{showCountdown ? ( // Show countdown string (e.g., 3 min or Due) Date: Fri, 25 Mar 2022 10:43:26 -0700 Subject: [PATCH 0066/1425] test: update snapshots --- .../viewers/__snapshots__/stop-viewer.js.snap | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index dd3959953..62c54d07c 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -1385,7 +1385,9 @@ exports[`components > viewers > stop viewer should render countdown times after } } > -
+
viewers > stop viewer should render countdown times after
-
+
viewers > stop viewer should render times after midnight w
-
+
viewers > stop viewer should render with OTP transit index
-
+
viewers > stop viewer should render with OTP transit index
-
+
viewers > stop viewer should render with OTP transit index
-
+
viewers > stop viewer should render with OTP transit index
-
+
viewers > stop viewer should render with TriMet transit in @@ -70,9 +75,11 @@ const ModeButtons = ({ className, modeOptions, onClick, + intl, selectedModes = [] }: { className: string + intl: IntlShape modeOptions: Mode[] onClick: (mode: string) => void selectedModes: string[] @@ -82,6 +89,7 @@ const ModeButtons = ({ {modeOptions.map((item) => ( { return case 'rail': return + case 'rent': + return case 'subway': return case 'tram': diff --git a/lib/util/i18n.js b/lib/util/i18n.js index 9522409d9..b8778f69d 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -129,7 +129,7 @@ export function getTimeFormat(state) { */ // eslint-disable-next-line complexity export function getFormattedMode(mode, intl) { - switch (mode) { + switch (mode?.toLowerCase()) { case 'bicycle': return intl.formatMessage({ id: 'common.modes.bike' }) case 'bicycle_rent': @@ -146,6 +146,11 @@ export function getFormattedMode(mode, intl) { return intl.formatMessage({ id: 'common.modes.drive' }) case 'ferry': return intl.formatMessage({ id: 'common.modes.ferry' }) + case 'flex_direct': + case 'flex_egress': + case 'flex_access': + case 'flex': + return intl.formatMessage({ id: 'common.modes.flex' }) case 'funicular': return intl.formatMessage({ id: 'common.modes.funicular' }) case 'gondola': @@ -156,6 +161,8 @@ export function getFormattedMode(mode, intl) { return intl.formatMessage({ id: 'common.modes.micromobility_rent' }) case 'rail': return intl.formatMessage({ id: 'common.modes.rail' }) + case 'rent': + return intl.formatMessage({ id: 'common.modes.rent' }) case 'subway': return intl.formatMessage({ id: 'common.modes.subway' }) case 'tram': From d0d3834a3cdb388defa232dba882a09f5369f8e8 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 30 Mar 2022 10:12:02 -0700 Subject: [PATCH 0074/1425] docs(example-config): update for changes --- example-config.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/example-config.yml b/example-config.yml index d761e6bcf..43d4b0cec 100644 --- a/example-config.yml +++ b/example-config.yml @@ -175,17 +175,14 @@ geocoder: # Use this mode config for the enhanced Transit+ config modes: modeOptions: - - label: "Transit" - mode: TRANSIT - - label: Walk - mode: WALK - - label: Car - mode: CAR - - label: Bicycle - mode: BICYCLE - - label: Micromobility - mode: RENT + - mode: TRANSIT + - mode: WALK + - mode: CAR + - mode: BICYCLE + - mode: RENT icon: mobile + # This overrides the label generated from the i18n. It's not recommended. + # label: Rental transitModes: - mode: BUS From 3c1d00afdb26fb2e6b66327c00190e83bb7e8b5f Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 30 Mar 2022 11:34:42 -0700 Subject: [PATCH 0075/1425] refactor(batch-settings): remove unused functions --- lib/components/form/batch-settings.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index c22437438..81e6840a4 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -31,23 +31,6 @@ import ModeButtons, { import type { Combination } from './batch-preferences' import type { Mode } from './mode-buttons' -/** - * Simple utility to check whether a list of mode strings contains the provided - * mode. This handles exact match and prefix/suffix matches (i.e., checking - * 'BICYCLE' will return true if 'BICYCLE' or 'BICYCLE_RENT' is in the list). - * - * FIXME: This might need to be modified to be a bit looser in how it handles - * the 'contains' check. E.g., we might not want to remove WALK,TRANSIT if walk - * is turned off, but we DO want to remove it if TRANSIT is turned off. - */ -function listHasMode(modes: string[], mode: string) { - return modes.some((m: string) => mode.indexOf(m) !== -1) -} - -function combinationHasAnyOfModes(combination: Combination, modes: string[]) { - return combination.mode.split(',').some((m: string) => listHasMode(modes, m)) -} - const ModeButtonsFullWidthContainer = styled.div` display: flex; justify-content: space-between; From 916fc3ecde98e330daa9ccde3fec050a7be39067 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 30 Mar 2022 12:27:32 -0700 Subject: [PATCH 0076/1425] refactor: autoformat --- .../narrative/line-itin/itin-summary.js | 223 ---------------- .../narrative/line-itin/itin-summary.tsx | 245 ++++++++++++++++++ .../narrative/line-itin/line-itinerary.js | 20 +- 3 files changed, 256 insertions(+), 232 deletions(-) delete mode 100644 lib/components/narrative/line-itin/itin-summary.js create mode 100644 lib/components/narrative/line-itin/itin-summary.tsx diff --git a/lib/components/narrative/line-itin/itin-summary.js b/lib/components/narrative/line-itin/itin-summary.js deleted file mode 100644 index 3cb0c322c..000000000 --- a/lib/components/narrative/line-itin/itin-summary.js +++ /dev/null @@ -1,223 +0,0 @@ -import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import styled from 'styled-components' -import { connect } from 'react-redux' -import { FormattedMessage, FormattedNumber } from 'react-intl' - -import { ComponentContext } from '../../../util/contexts' -import FormattedDuration from '../../util/formatted-duration' - -// TODO: make this a prop -const defaultRouteColor = '#008' - -const Container = styled.div` - display: ${() => coreUtils.ui.isMobile() ? 'table' : 'none'}; - height: 60px; - margin-bottom: 15px; - padding-right: 5px; - width: 100%; -` - -const Detail = styled.div` - color: #676767; - font-size: 13px; -` - -const Details = styled.div` - display: table-cell; - vertical-align: top; -` - -const Header = styled.div` - font-size: 18px; - font-weight: bold; - margin-top: -3px; -` - -const LegIconContainer = styled.div` - height: 30px; - width: 30px; -` - -const NonTransitSpacer = styled.div` - height: 30px; - overflow: hidden -` - -const RoutePreview = styled.div` - display: inline-block; - margin-left: 8px; - vertical-align: top; -` - -const Routes = styled.div` - display: table-cell; - text-align: right; -` - -const ShortName = styled.div` - background-color: ${props => getRouteColorForBadge(props.leg)}; - border-radius: 15px; - border: 2px solid white; - box-shadow: 0 0 0.5em #000; - color: white; - font-size: 15px; - font-weight: 500; - height: 30px; - margin-top: 6px; - overflow: hidden; - padding-top: 4px; - text-align: center; - text-overflow: ellipsis; - white-space: nowrap; - width: 30px; -` - -export class ItinerarySummary extends Component { - static propTypes = { - itinerary: PropTypes.object - } - - static contextType = ComponentContext - - _onSummaryClicked = () => { - if (typeof this.props.onClick === 'function') this.props.onClick() - } - - render () { - const { currency, defaultFareKey, itinerary, timeOptions } = this.props - const { LegIcon } = this.context - - const { - maxTNCFare, - minTNCFare, - transitFares - } = coreUtils.itinerary.calculateFares(itinerary, true) - const transitFare = (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0 - // TODO: support non-USD - const minTotalFare = minTNCFare * 100 + transitFare - const maxTotalFare = maxTNCFare * 100 + transitFare - - const startTime = itinerary.startTime + timeOptions.offset - const endTime = itinerary.endTime + timeOptions.offset - - const { caloriesBurned } = coreUtils.itinerary.calculatePhysicalActivity(itinerary) - return ( - -
- {/* Travel time in hrs/mins */} -
- -
- - {/* Duration as time range */} - - - - - {/* Fare / Calories */} - - {minTotalFare > 0 && - - ), - minTotalFare: ( - - ), - useMaxFare: minTotalFare !== maxTotalFare - }} - /> - - } - - - - {/* Number of transfers, if applicable */} - - - - -
- - {itinerary.legs.filter(leg => { - return !(leg.mode === 'WALK' && itinerary.transitTime > 0) - }).map((leg, k) => { - return ( - - - - - {coreUtils.itinerary.isTransit(leg.mode) - ? ( - - {getRouteNameForBadge(leg)} - - ) - : () - } - - ) - })} - -
- ) - } -} - -// Helper functions - -function getRouteLongName (leg) { - return leg.routes && leg.routes.length > 0 - ? leg.routes[0].longName - : leg.routeLongName -} - -function getRouteNameForBadge (leg) { - const shortName = leg.routes && leg.routes.length > 0 - ? leg.routes[0].shortName : leg.routeShortName - - const longName = getRouteLongName(leg) - - // check for max - if (longName && longName.toLowerCase().startsWith('max')) return null - - // check for streetcar - if (longName && longName.startsWith('Portland Streetcar')) return longName.split('-')[1].trim().split(' ')[0] - - return shortName || longName -} - -function getRouteColorForBadge (leg) { - return leg.routeColor ? '#' + leg.routeColor : defaultRouteColor -} - -const mapStateToProps = (state, ownProps) => { - return { - currency: state.otp.config.localization?.currency || 'USD', - defaultFareKey: state.otp.config.itinerary?.defaultFareKey - } -} - -export default connect(mapStateToProps)(ItinerarySummary) diff --git a/lib/components/narrative/line-itin/itin-summary.tsx b/lib/components/narrative/line-itin/itin-summary.tsx new file mode 100644 index 000000000..94445d69f --- /dev/null +++ b/lib/components/narrative/line-itin/itin-summary.tsx @@ -0,0 +1,245 @@ +import { connect } from 'react-redux' +import { FormattedMessage, FormattedNumber } from 'react-intl' +// TYPESCRIPT TODO: add leg, itinerary types all over the place here +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import coreUtils from '@opentripplanner/core-utils' +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import styled from 'styled-components' + +import { ComponentContext } from '../../../util/contexts' +import FormattedDuration from '../../util/formatted-duration' + +// TODO: make this a prop +const defaultRouteColor = '#008' + +const Container = styled.div` + display: ${() => (coreUtils.ui.isMobile() ? 'table' : 'none')}; + height: 60px; + margin-bottom: 15px; + padding-right: 5px; + width: 100%; +` + +const Detail = styled.div` + color: #676767; + font-size: 13px; +` + +const Details = styled.div` + display: table-cell; + vertical-align: top; +` + +const Header = styled.div` + font-size: 18px; + font-weight: bold; + margin-top: -3px; +` + +const LegIconContainer = styled.div` + height: 30px; + width: 30px; +` + +const NonTransitSpacer = styled.div` + height: 30px; + overflow: hidden; +` + +const RoutePreview = styled.div` + display: inline-block; + margin-left: 8px; + vertical-align: top; +` + +const Routes = styled.div` + display: table-cell; + text-align: right; +` + +const ShortName = styled.div<{ leg: any }>` + background-color: ${(props) => getRouteColorForBadge(props.leg)}; + border-radius: 15px; + border: 2px solid white; + box-shadow: 0 0 0.5em #000; + color: white; + font-size: 15px; + font-weight: 500; + height: 30px; + margin-top: 6px; + overflow: hidden; + padding-top: 4px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + width: 30px; +` + +type Props = { + currency?: string + defaultFareKey: string + itinerary: any + // TODO unified types + onClick: () => void + timeOptions: { offset: number } +} + +export class ItinerarySummary extends Component { + static propTypes = { + itinerary: PropTypes.object + } + + static contextType = ComponentContext + + _onSummaryClicked = (): void => { + if (typeof this.props.onClick === 'function') this.props.onClick() + } + + render(): JSX.Element { + const { currency, defaultFareKey, itinerary, timeOptions } = this.props + const { LegIcon } = this.context + + const { maxTNCFare, minTNCFare, transitFares } = + coreUtils.itinerary.calculateFares(itinerary, true) + const transitFare = + (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0 + // TODO: support non-USD + const minTotalFare = minTNCFare * 100 + transitFare + const maxTotalFare = maxTNCFare * 100 + transitFare + + const startTime = itinerary.startTime + timeOptions.offset + const endTime = itinerary.endTime + timeOptions.offset + + const { caloriesBurned } = + coreUtils.itinerary.calculatePhysicalActivity(itinerary) + return ( + +
+ {/* Travel time in hrs/mins */} +
+ +
+ + {/* Duration as time range */} + + + + + {/* Fare / Calories */} + + {minTotalFare > 0 && ( + + + ), + minTotalFare: ( + + ), + useMaxFare: minTotalFare !== maxTotalFare + }} + /> + + + )} + + + + {/* Number of transfers, if applicable */} + + + +
+ + {itinerary.legs + .filter((leg: any) => { + return !(leg.mode === 'WALK' && itinerary.transitTime > 0) + }) + .map((leg: any, k: string) => { + return ( + + + + + {coreUtils.itinerary.isTransit(leg.mode) ? ( + {getRouteNameForBadge(leg)} + ) : ( + + )} + + ) + })} + +
+ ) + } +} + +// Helper functions + +function getRouteLongName(leg: any) { + return leg.routes && leg.routes.length > 0 + ? leg.routes[0].longName + : leg.routeLongName +} + +function getRouteNameForBadge(leg: any) { + const shortName = + leg.routes && leg.routes.length > 0 + ? leg.routes[0].shortName + : leg.routeShortName + + const longName = getRouteLongName(leg) + + // check for max + if (longName && longName.toLowerCase().startsWith('max')) return null + + // check for streetcar + if (longName && longName.startsWith('Portland Streetcar')) + return longName.split('-')[1].trim().split(' ')[0] + + return shortName || longName +} + +function getRouteColorForBadge(leg: any): string { + return leg.routeColor ? '#' + leg.routeColor : defaultRouteColor +} + +const mapStateToProps = (state: any) => { + return { + currency: state.otp.config.localization?.currency || 'USD', + defaultFareKey: state.otp.config.itinerary?.defaultFareKey + } +} + +export default connect(mapStateToProps)(ItinerarySummary) diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 6e33c4d4f..1c301cdbb 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -2,13 +2,13 @@ import coreUtils from '@opentripplanner/core-utils' import React from 'react' import styled from 'styled-components' +import { ComponentContext } from '../../../util/contexts' import NarrativeItinerary from '../narrative-itinerary' import SaveTripButton from '../save-trip-button' import SimpleRealtimeAnnotation from '../simple-realtime-annotation' -import { ComponentContext } from '../../../util/contexts' -import ItinerarySummary from './itin-summary' import ItineraryBody from './connected-itinerary-body' +import ItinerarySummary from './itin-summary' const { getLegModeLabel, getTimeZoneOffset, isTransit } = coreUtils.itinerary @@ -19,16 +19,17 @@ export const LineItineraryContainer = styled.div` export default class LineItinerary extends NarrativeItinerary { static contextType = ComponentContext - _headerText () { + _headerText() { const { itinerary } = this.props return itinerary.summary || this._getSummary(itinerary) } - _getSummary (itinerary) { + _getSummary(itinerary) { let summary = '' const transitModes = [] itinerary.legs.forEach((leg, index) => { if (isTransit(leg.mode)) { + /** TODO: remove use of deprecated unlocalized method once itinerary-body is localized */ const modeStr = getLegModeLabel(leg) if (transitModes.indexOf(modeStr) === -1) transitModes.push(modeStr) } @@ -36,6 +37,7 @@ export default class LineItinerary extends NarrativeItinerary { // check for access mode if (!isTransit(itinerary.legs[0].mode)) { + /** TODO: remove use of deprecated unlocalized method once itinerary-body is localized */ summary += getLegModeLabel(itinerary.legs[0]) } @@ -47,7 +49,7 @@ export default class LineItinerary extends NarrativeItinerary { return summary } - render () { + render() { const { active, companies, @@ -70,7 +72,7 @@ export default class LineItinerary extends NarrativeItinerary { } return ( - + {showRealtimeAnnotation && } - {active || expanded - ? - : null} + ) : null} {ItineraryFooter && } ) From 60c33a97dcdebffd509b85eaf945e3e2ab4a2eed Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 30 Mar 2022 12:33:15 -0700 Subject: [PATCH 0077/1425] refactor(mode-buttons): add warnings for missing config --- lib/components/form/batch-preferences.tsx | 3 +++ lib/components/form/batch-settings.tsx | 3 +++ lib/util/i18n.js | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/components/form/batch-preferences.tsx b/lib/components/form/batch-preferences.tsx index 08f34deba..6b3962e52 100644 --- a/lib/components/form/batch-preferences.tsx +++ b/lib/components/form/batch-preferences.tsx @@ -52,6 +52,9 @@ class BatchPreferences extends Component<{ } else { // This is for backwards compatability // In case a combination does not include requiredModes. + console.warn( + `Combination ${c.mode} does not have any specified required modes.` + ) const modesInCombination = c.mode.split(',') return modesInCombination.every((m) => enabledModes.includes(m)) } diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 81e6840a4..910604aa1 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -104,6 +104,9 @@ class BatchSettings extends Component<{ } else { // This is for backwards compatability // In case a combination does not include requiredModes. + console.warn( + `Combination ${c.mode} does not have any specified required modes.` + ) const modesInCombination = c.mode.split(',') return modesInCombination.every((m) => newModes.includes(m)) } diff --git a/lib/util/i18n.js b/lib/util/i18n.js index a3d3344cb..a3c60167e 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -170,7 +170,8 @@ export function getFormattedMode(mode, intl) { case 'walk': return intl.formatMessage({ id: 'common.modes.walk' }) default: - return null + console.warn(`Mode ${mode} does not have a corresponding translation.`) + return mode } } From d7348e122744d34c7a3b577ddae99bc9e66b19e1 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 30 Mar 2022 12:35:24 -0700 Subject: [PATCH 0078/1425] refactor(batch): fix spelling --- lib/components/form/batch-preferences.tsx | 2 +- lib/components/form/batch-settings.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/form/batch-preferences.tsx b/lib/components/form/batch-preferences.tsx index 6b3962e52..4f67f6ea2 100644 --- a/lib/components/form/batch-preferences.tsx +++ b/lib/components/form/batch-preferences.tsx @@ -50,7 +50,7 @@ class BatchPreferences extends Component<{ if (c.requiredModes) { return c.requiredModes.every((m) => enabledModes.includes(m)) } else { - // This is for backwards compatability + // This is for backwards compatibility // In case a combination does not include requiredModes. console.warn( `Combination ${c.mode} does not have any specified required modes.` diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 910604aa1..9ad947d8a 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -102,7 +102,7 @@ class BatchSettings extends Component<{ if (c.requiredModes) { return c.requiredModes.every((m) => newModes.includes(m)) } else { - // This is for backwards compatability + // This is for backwards compatibility // In case a combination does not include requiredModes. console.warn( `Combination ${c.mode} does not have any specified required modes.` From 59c0611c93909c96aeaa901af6c6cccf9d8ab012 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 30 Mar 2022 13:14:39 -0700 Subject: [PATCH 0079/1425] feat: remove use of deprecated core-utils methods --- i18n/en-US.yml | 2 + lib/components/form/user-settings.js | 21 ++++++- lib/components/map/point-popup.tsx | 27 +++++++-- .../narrative/default/access-leg.js | 1 + .../narrative/line-itin/itin-summary.tsx | 16 +++--- .../narrative/tabbed-itineraries.js | 14 ++--- lib/util/state.js | 57 ++++++++++++------- 7 files changed, 95 insertions(+), 43 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index e42ad6e51..86c79f53c 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -326,6 +326,7 @@ components: previous: Previous PlanTripButton: planTrip: Plan Trip + PointPopup: PrintLayout: itinerary: Itinerary toggleMap: Toggle Map @@ -734,6 +735,7 @@ otpUi: # Common messages that appear in multiple components and modules # are grouped below by topic. common: + coordinates: "{lat}, {lon}" dateExpressions: today: Today tomorrow: Tomorrow diff --git a/lib/components/form/user-settings.js b/lib/components/form/user-settings.js index 78aeeb0b1..f5d190a24 100644 --- a/lib/components/form/user-settings.js +++ b/lib/components/form/user-settings.js @@ -42,7 +42,26 @@ function findLocationType( ? getFormattedPlaces(match.type, intl) : null } -function summarizeQuery(query, intl, locations = []) { +export function summarizeQuery(query, intl, locations = []) { + if (!query.from.name) { + query.from.name = intl.formatMessage( + { id: 'common.coordinates' }, + { + lat: query.from.lat.toFixed(5), + lon: query.from.lon.toFixed(5) + } + ) + } + if (!query.to.name) { + query.to.name = intl.formatMessage( + { id: 'common.coordinates' }, + { + lat: query.to.lat.toFixed(5), + lon: query.to.lon.toFixed(5) + } + ) + } + const from = findLocationType(intl, query.from, locations) || query.from.name.split(',')[0] diff --git a/lib/components/map/point-popup.tsx b/lib/components/map/point-popup.tsx index 5f67dfb78..132a11c5b 100644 --- a/lib/components/map/point-popup.tsx +++ b/lib/components/map/point-popup.tsx @@ -1,4 +1,5 @@ import { connect } from 'react-redux' +import { injectIntl, WrappedComponentProps } from 'react-intl' // FIXME: typescript // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -18,17 +19,18 @@ const PopupTitle = styled.div` ` function MapPopup({ + intl, mapPopupLocation, onSetLocationFromPopup, setMapZoom, zoom }: { - mapPopupLocation: { name: string } + mapPopupLocation: { lat: number; lon: number; name?: string } // TODO: add types for this method onSetLocationFromPopup: () => void setMapZoom: ({ zoom }: { zoom: number }) => void zoom: number -}): JSX.Element { +} & WrappedComponentProps): JSX.Element { // Zoom out if zoomed in very far useEffect(() => { if (zoom > 15) { @@ -37,12 +39,22 @@ function MapPopup({ // Only check zoom if popup appears in a new place }, [mapPopupLocation, setMapZoom, zoom]) + const popupName = + mapPopupLocation?.name || + intl.formatMessage( + { id: 'common.coordinates' }, + { + lat: mapPopupLocation.lat.toFixed(5), + lon: mapPopupLocation.lon.toFixed(5) + } + ) + return ( - {mapPopupLocation.name.split(',').length > 3 - ? mapPopupLocation.name.split(',').splice(0, 3).join(',') - : mapPopupLocation.name} + {popupName.split(',').length > 3 + ? popupName.split(',').splice(0, 3).join(',') + : popupName}
Plan a trip: @@ -66,4 +78,7 @@ const mapDispatchToProps = { setMapZoom } -export default connect(mapStateToProps, mapDispatchToProps)(MapPopup) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(MapPopup)) diff --git a/lib/components/narrative/default/access-leg.js b/lib/components/narrative/default/access-leg.js index 3837510f5..f03842b74 100644 --- a/lib/components/narrative/default/access-leg.js +++ b/lib/components/narrative/default/access-leg.js @@ -79,6 +79,7 @@ export default class AccessLeg extends Component { {humanizeDistanceString(step.distance)} + {/** TODO: remove use of deprecated unlocalized method once itinerary-body is localized */} {coreUtils.itinerary.getStepInstructions(step)} diff --git a/lib/components/narrative/line-itin/itin-summary.tsx b/lib/components/narrative/line-itin/itin-summary.tsx index 94445d69f..7585ac30a 100644 --- a/lib/components/narrative/line-itin/itin-summary.tsx +++ b/lib/components/narrative/line-itin/itin-summary.tsx @@ -9,6 +9,7 @@ import React, { Component } from 'react' import styled from 'styled-components' import { ComponentContext } from '../../../util/contexts' +import { getFare } from '../../../util/state' import FormattedDuration from '../../util/formatted-duration' // TODO: make this a prop @@ -101,11 +102,12 @@ export class ItinerarySummary extends Component { const { currency, defaultFareKey, itinerary, timeOptions } = this.props const { LegIcon } = this.context - const { maxTNCFare, minTNCFare, transitFares } = - coreUtils.itinerary.calculateFares(itinerary, true) - const transitFare = - (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || 0 - // TODO: support non-USD + const { fareCurrency, maxTNCFare, minTNCFare, transitFare } = getFare( + itinerary, + defaultFareKey, + currency + ) + const minTotalFare = minTNCFare * 100 + transitFare const maxTotalFare = maxTNCFare * 100 + transitFare @@ -142,7 +144,7 @@ export class ItinerarySummary extends Component { values={{ maxTotalFare: ( { ), minTotalFare: ( - ), + ) }} /> ) @@ -203,10 +204,10 @@ export function getErrorMessage(error, intl) { /** * Helper to get the active search's non-realtime response - * - * This is not actively used, but may be again in the future to + * + * This is not actively used, but may be again in the future to * facilitate trip monitoring, which requires a non-realtime - * trip + * trip */ function getActiveSearchNonRealtimeResponse(state) { const search = getActiveSearch(state) @@ -230,11 +231,11 @@ function getActiveSearchRealtimeResponse(state) { * https://decembersoft.com/posts/error-selector-creators-expect-all-input-selectors-to-be-functions/ */ export const getActiveFieldTripRequest = createSelector( - state => state.callTaker?.fieldTrip.activeId, - state => state.callTaker?.fieldTrip.requests, + (state) => state.callTaker?.fieldTrip.activeId, + (state) => state.callTaker?.fieldTrip.requests, (activeId, requests) => { if (!activeId || !requests) return - return requests.data.find(req => req.id === activeId) + return requests.data.find((req) => req.id === activeId) } ) @@ -341,8 +342,14 @@ function sortItineraries(type, direction, a, b, config) { case 'COST': const configCosts = config.itinerary?.costs // Sort an itinerary without fare information last - const aTotal = getTotalFare(a, configCosts) === null ? Number.MAX_VALUE : getTotalFare(a, configCosts) - const bTotal = getTotalFare(b, configCosts) === null ? Number.MAX_VALUE : getTotalFare(b, configCosts) + const aTotal = + getTotalFare(a, configCosts) === null + ? Number.MAX_VALUE + : getTotalFare(a, configCosts) + const bTotal = + getTotalFare(b, configCosts) === null + ? Number.MAX_VALUE + : getTotalFare(b, configCosts) if (direction === 'ASC') return aTotal - bTotal else return bTotal - aTotal default: @@ -423,6 +430,22 @@ const DEFAULT_COSTS = { drivingCentsPerMile: 0.445 * 100 } +/** + * Parses OTP itinerary fare object and returns fares along with overridden currency + + */ +export function getFare(itinerary, defaultFareKey, currency) { + const { maxTNCFare, minTNCFare } = + coreUtils.itinerary.calculateTncFares(itinerary) + + const transitFares = itinerary.fare?.fare + const transitFare = + (transitFares?.[defaultFareKey] || transitFares?.regular)?.cents || 0 + const fareCurrency = transitFare.currency?.symbol || currency + + return { fareCurrency, maxTNCFare, minTNCFare, transitFare } +} + /** * Returns total fare for itinerary (in cents) * FIXME: Move to otp-ui? @@ -433,11 +456,8 @@ export function getTotalFare( configCosts = {}, defaultFareKey = 'regular' ) { - // Get transit/TNC fares. - const { maxTNCFare, transitFares } = calculateFares(itinerary, true) - const transitFare = - (transitFares?.[defaultFareKey] || transitFares.regular)?.transitFare || - null + // Get TNC fares. + const { maxTNCFare, transitFare } = getFare(itinerary, defaultFareKey) // Start with default cost values. const costs = DEFAULT_COSTS // If config contains values to override defaults, apply those. @@ -775,7 +795,7 @@ export function getTitle(state, intl) { const { config, ui } = state.otp const { localUser, loggedInUser } = state.user const user = loggedInUser || localUser - let title = config.title || DEFAULT_TITLE + const title = config.title || DEFAULT_TITLE const { mainPanelContent, viewedRoute, viewedStop } = ui let status switch (mainPanelContent) { @@ -794,10 +814,7 @@ export function getTitle(state, intl) { default: const activeSearch = getActiveSearch(state) if (activeSearch) { - status = coreUtils.query.summarizeQuery( - activeSearch.query, - user.savedLocations - ) + status = summarizeQuery(activeSearch.query, intl, user.savedLocations) } break } From e88cf049519537eb9fc211750cd3ceaa9eb45512 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 30 Mar 2022 13:17:08 -0700 Subject: [PATCH 0080/1425] test: update snapshots --- __tests__/actions/__snapshots__/api.js.snap | 4 +-- .../viewers/__snapshots__/stop-viewer.js.snap | 30 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/__tests__/actions/__snapshots__/api.js.snap b/__tests__/actions/__snapshots__/api.js.snap index af787deaa..c8b742c84 100644 --- a/__tests__/actions/__snapshots__/api.js.snap +++ b/__tests__/actions/__snapshots__/api.js.snap @@ -56,7 +56,7 @@ Array [ "error": [Error: Received error from server], "requestId": "abcd1238", "searchId": "abcd1236", - "url": "http://mock-host.com:80/api/plan?fromPlace=Origin%20%2812%2C34%29%3A%3A12%2C34&toPlace=Destination%20%2834%2C12%29%3A%3A34%2C12&mode=WALK%2CTRANSIT&ignoreRealtimeUpdates=false&batchId=abcd1236", + "url": "http://mock-host.com:80/api/plan?fromPlace=%2812%2C34%29%3A%3A12%2C34&toPlace=%2834%2C12%29%3A%3A34%2C12&mode=WALK%2CTRANSIT&ignoreRealtimeUpdates=false&batchId=abcd1236", }, "type": "ROUTING_ERROR", }, @@ -99,4 +99,4 @@ Array [ ] `; -exports[`actions > api routingQuery should make a query to OTP: OTP Query Path 1`] = `"/api/plan?fromPlace=Origin%20%2812%2C34%29%3A%3A12%2C34&toPlace=Destination%20%2834%2C12%29%3A%3A34%2C12&mode=WALK%2CTRANSIT&ignoreRealtimeUpdates=false&batchId=abcd1234"`; +exports[`actions > api routingQuery should make a query to OTP: OTP Query Path 1`] = `"/api/plan?fromPlace=%2812%2C34%29%3A%3A12%2C34&toPlace=%2834%2C12%29%3A%3A34%2C12&mode=WALK%2CTRANSIT&ignoreRealtimeUpdates=false&batchId=abcd1234"`; diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 65331a1bd..c0dea91ac 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -777,7 +777,7 @@ exports[`components > viewers > stop viewer should render countdown times after
viewers > stop viewer should render countdown times after margin={0.25} > viewers > stop viewer should render countdown times after >
P
@@ -2950,7 +2950,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
viewers > stop viewer should render countdown times for st margin={0.25} > viewers > stop viewer should render countdown times for st >
P
@@ -4646,7 +4646,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
viewers > stop viewer should render times after midnight w margin={0.25} > viewers > stop viewer should render times after midnight w >
P
@@ -7545,7 +7545,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
viewers > stop viewer should render with OTP transit index margin={0.25} > viewers > stop viewer should render with OTP transit index >
P
@@ -13025,7 +13025,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
viewers > stop viewer should render with TriMet transit in margin={0.25} > viewers > stop viewer should render with TriMet transit in >
P
From 07241bf2873aa3bb611db810b5d1e0cf2523aa25 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 07:53:56 -0700 Subject: [PATCH 0081/1425] ci(percy): more explict logging --- percy/percy.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/percy/percy.test.js b/percy/percy.test.js index d8071225c..6cc534655 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -49,10 +49,12 @@ beforeAll(async () => { 'yarn', 'build' ]) + console.log('Built OTP-RR') // grab ATL har file to tmp if (process.env.HAR_URL) { await execa('curl', [process.env.HAR_URL, '-s', '--output', 'mock.har']) + console.log('Downloaded HAR data') } } catch (error) { console.log(error) From 231ca563b4b96247f4944aa0b5a29d0d4edac40d Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 08:06:16 -0700 Subject: [PATCH 0082/1425] refactor: clean up --- i18n/en-US.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 86c79f53c..48eced3d2 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -326,7 +326,6 @@ components: previous: Previous PlanTripButton: planTrip: Plan Trip - PointPopup: PrintLayout: itinerary: Itinerary toggleMap: Toggle Map From 303f5c26be760837aabb75b67f857f973c68806f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 08:07:29 -0700 Subject: [PATCH 0083/1425] ci(percy): create dev baseline --- .github/workflows/percy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml index 845cc2004..00400da90 100644 --- a/.github/workflows/percy.yml +++ b/.github/workflows/percy.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - dev pull_request: jobs: From 6b63337b5c6beba0edf21a0a90fd47f00290b6f9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 08:40:03 -0700 Subject: [PATCH 0084/1425] chore(deps): upgrade to alpha core-utils to pass pixel tests --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c051c5cfd..bccc8a326 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^2.0.0", - "@opentripplanner/core-utils": "^4.8.0", + "@opentripplanner/core-utils": "^4.9.0-alpha.1", "@opentripplanner/endpoints-overlay": "^1.3.0", "@opentripplanner/from-to-location-picker": "^1.3.0", "@opentripplanner/geocoder": "^1.2.1", diff --git a/yarn.lock b/yarn.lock index 0a6ef06e4..bdf668de4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2482,10 +2482,10 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.8.0.tgz#54f42fe038c0a9e011f0e126c42fe2f31db3148a" - integrity sha512-vv1vDd5xTBpLoViPwa0fjYBS+u0OD/Sgp+97Q83HmRr4V8Lj/LXSdjtQWIWZHuN3TUIhDGH2Cuk+f1UVhGHJDw== +"@opentripplanner/core-utils@^4.9.0-alpha.1": + version "4.9.0-alpha.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.9.0-alpha.1.tgz#f3013c203042353b6b9beed86392f18e0ba0dae6" + integrity sha512-eGQ+VedbjeLns/49eYB6/VDUM1A7yM147u68I+6V0Dl9B9I2sYiXcUSGqKmSVfoyJ4Rg/qHRNoNMN9ZvmhcHtg== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/geocoder" "^1.1.2" From bfe701697e8cbceef1b004a198352f7504f4e6b0 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 08:46:07 -0700 Subject: [PATCH 0085/1425] refactor: address pr comments --- lib/util/i18n.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/i18n.js b/lib/util/i18n.js index 88af65829..cbc460caa 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -57,7 +57,7 @@ async function loadOtpUiLocaleData(matchedLocale) { const packageJson = await import('../../package.json') const otpUiMessages = await Promise.all( Object.keys(packageJson.dependencies) - .filter((pkg) => pkg.includes('@opentripplanner')) + .filter((pkg) => pkg.startsWith('@opentripplanner')) .map(async (pkg) => { try { const msgs = await import( @@ -109,8 +109,8 @@ export async function loadLocaleData(matchedLocale, customMessages) { // Merge custom strings into the standard language strings. const mergedMessages = { - ...flatten(messages), ...flatten(otpUiMessages), + ...flatten(messages), // Override the predefined strings with the custom ones, if any provided. ...flatten(customMessages.allLanguages || {}), ...flatten(customMessages[matchedLocale] || {}) From d8b6da98d36ea9f35acc4e8a795936634cb1ee63 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 31 Mar 2022 08:49:50 -0700 Subject: [PATCH 0086/1425] refactor: add note about deepmerge import --- lib/util/i18n.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util/i18n.js b/lib/util/i18n.js index cbc460caa..5fc000b10 100644 --- a/lib/util/i18n.js +++ b/lib/util/i18n.js @@ -1,5 +1,6 @@ import flatten from 'flat' +// deepmerge must be imported via `require`: see https://github.com/TehShrike/deepmerge#include const merge = require('deepmerge') /** From 9d0db1c70e45530411ae66fa3ae69ec42b563168 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 31 Mar 2022 13:15:32 -0700 Subject: [PATCH 0087/1425] refactor(batch): consoldate repeated code into function --- lib/components/form/batch-preferences.tsx | 15 ++-------- lib/components/form/batch-settings.tsx | 35 ++++++++++++++--------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/components/form/batch-preferences.tsx b/lib/components/form/batch-preferences.tsx index 4f67f6ea2..cdd8fef4a 100644 --- a/lib/components/form/batch-preferences.tsx +++ b/lib/components/form/batch-preferences.tsx @@ -7,6 +7,7 @@ import React, { Component } from 'react' import { ComponentContext } from '../../util/contexts' import { setQueryParam } from '../../actions/form' +import { combinationFilter } from './batch-settings' import { defaultModeOptions, Mode } from './mode-buttons' import { StyledBatchPreferences } from './batch-styled' @@ -46,19 +47,7 @@ class BatchPreferences extends Component<{ const { config, modeOptions, query, setQueryParam } = this.props const enabledModes = query.enabledModes || modeOptions const combinations = config.modes.combinations - .filter((c: Combination) => { - if (c.requiredModes) { - return c.requiredModes.every((m) => enabledModes.includes(m)) - } else { - // This is for backwards compatibility - // In case a combination does not include requiredModes. - console.warn( - `Combination ${c.mode} does not have any specified required modes.` - ) - const modesInCombination = c.mode.split(',') - return modesInCombination.every((m) => enabledModes.includes(m)) - } - }) + .filter(combinationFilter(enabledModes)) .map(replaceTransitMode(newQueryParams.mode)) setQueryParam({ ...newQueryParams, combinations }) } diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 9ad947d8a..9c3c99b24 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -31,6 +31,27 @@ import ModeButtons, { import type { Combination } from './batch-preferences' import type { Mode } from './mode-buttons' +/** + * A function that generates a filter to be used to filter a list of combinations. + * @param enabledModes A list of the modes enabled in the UI + * @returns Filter function to filter combinations + */ +export const combinationFilter = + (enabledModes: string[]) => + (c: Combination): boolean => { + if (c.requiredModes) { + return c.requiredModes.every((m) => enabledModes.includes(m)) + } else { + // This is for backwards compatibility + // In case a combination does not include requiredModes. + console.warn( + `Combination ${c.mode} does not have any specified required modes.` + ) + const modesInCombination = c.mode.split(',') + return modesInCombination.every((m) => enabledModes.includes(m)) + } + } + const ModeButtonsFullWidthContainer = styled.div` display: flex; justify-content: space-between; @@ -98,19 +119,7 @@ class BatchSettings extends Component<{ const disabledModes = possibleModes.filter((m) => !newModes.includes(m)) // Only include a combination if it every required mode is enabled. const newCombinations = possibleCombinations - .filter((c) => { - if (c.requiredModes) { - return c.requiredModes.every((m) => newModes.includes(m)) - } else { - // This is for backwards compatibility - // In case a combination does not include requiredModes. - console.warn( - `Combination ${c.mode} does not have any specified required modes.` - ) - const modesInCombination = c.mode.split(',') - return modesInCombination.every((m) => newModes.includes(m)) - } - }) + .filter(combinationFilter(newModes)) .map(replaceTransitMode(currentQuery.mode)) setQueryParam({ From ac96fa65af65f7ebeae82425af2fbd59efdbc246 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Apr 2022 09:42:29 -0700 Subject: [PATCH 0088/1425] feat: OTP2 realtime vehicle position support --- lib/actions/apiV1.js | 1 + lib/actions/apiV2.js | 69 ++++++++++++++++++++++++++++-- lib/reducers/create-otp-reducer.js | 2 + 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/lib/actions/apiV1.js b/lib/actions/apiV1.js index e255bd863..24e86229c 100644 --- a/lib/actions/apiV1.js +++ b/lib/actions/apiV1.js @@ -159,6 +159,7 @@ const getVehiclePositionsForRoute = (routeId) => receivedVehiclePositions, receivedVehiclePositionsError, { + noThrottle: true, rewritePayload: (payload) => { return { routeId: routeId, diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 41767d454..fe7b643a8 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -13,7 +13,9 @@ import { findTripError, findTripResponse, receivedNearbyStopsError, - receivedNearbyStopsResponse + receivedNearbyStopsResponse, + receivedVehiclePositions, + receivedVehiclePositionsError } from './api' import { setMapZoom } from './config' import { zoomToStop } from './map' @@ -428,9 +430,70 @@ const fetchStopInfo = (stop) => { ) } -const getVehiclePositionsForRoute = () => +const getVehiclePositionsForRoute = (routeId) => function (dispatch, getState) { - console.warn('OTP2 does not yet support vehicle positions for route!') + return dispatch( + createGraphQLQueryAction( + `{ + route(id: "${routeId}") { + patterns { + vehiclePositions { + vehicleId + label + lat + lon + stopRelationship { + status + stop { + name + gtfsId + } + } + speed + heading + lastUpdated + trip { + pattern { + id + } + } + } + } + } + }`, + {}, + receivedVehiclePositions, + receivedVehiclePositionsError, + { + noThrottle: true, + rewritePayload: (payload) => { + const vehicles = payload.data?.route?.patterns.reduce( + (prev, cur) => { + return prev.concat( + cur?.vehiclePositions?.map((position) => { + return { + heading: position?.heading, + label: position?.label, + lat: position?.lat, + lon: position?.lon, + nextStopId: position?.stopRelationship?.stop?.gtfsId, + nextStopName: position?.stopRelationship?.stop?.name, + patternId: position?.trip?.pattern?.id, + seconds: position?.lastUpdated, + speed: position?.speed || 0, + stopStatus: position?.stopRelationship?.status, + vehicleId: position?.vehicleId + } + }) + ) + }, + [] + ) + return { routeId, vehicles } + } + } + ) + ) } export const findRoute = (params) => diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0d7f65da7..2cdd56898 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -626,6 +626,8 @@ function createOtpReducer(config) { } }) case 'REALTIME_VEHICLE_POSITIONS_RESPONSE': + if (!action.payload?.vehicles) return state + return update(state, { transitIndex: { routes: { From 59fdb102582068713280d423057529884c2299d6 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Apr 2022 10:11:08 -0700 Subject: [PATCH 0089/1425] refactor: don't push blank vehicles --- lib/actions/apiV2.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index fe7b643a8..5b6bddb97 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -467,8 +467,8 @@ const getVehiclePositionsForRoute = (routeId) => { noThrottle: true, rewritePayload: (payload) => { - const vehicles = payload.data?.route?.patterns.reduce( - (prev, cur) => { + const vehicles = payload.data?.route?.patterns + .reduce((prev, cur) => { return prev.concat( cur?.vehiclePositions?.map((position) => { return { @@ -486,9 +486,8 @@ const getVehiclePositionsForRoute = (routeId) => } }) ) - }, - [] - ) + }, []) + .filter((vehicle) => !!vehicle) return { routeId, vehicles } } } From ca7b1ac7b56f75216cfc7fc6b242cc43a61f792b Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 4 Apr 2022 12:05:20 -0700 Subject: [PATCH 0090/1425] refactor: differentiate between scooter and bike --- lib/actions/apiV2.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 5b6bddb97..61adf4bac 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -126,6 +126,9 @@ export const vehicleRentalQuery = ( lat lon allowPickupNow + vehicleType { + formFactor + } network } } @@ -148,6 +151,8 @@ export const vehicleRentalQuery = ( return { allowPickup: vehicle.allowPickupNow, id: vehicle.vehicleId, + isFloatingBike: vehicle?.vehicleType?.formFactor === 'BICYCLE', + isFloatingVehicle: vehicle?.vehicleType?.formFactor === 'SCOOTER', name: vehicle.name, networks: [vehicle.network], x: vehicle.lon, From 583f9f8e03ec54f9cb74debec084c8f12a149f81 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 4 Apr 2022 12:16:04 -0700 Subject: [PATCH 0091/1425] refactor: clean up no fares compatibility with typescript changes --- .../narrative/tabbed-itineraries.js | 22 ------------------- lib/util/state.js | 4 ++-- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/components/narrative/tabbed-itineraries.js b/lib/components/narrative/tabbed-itineraries.js index ea98b5e21..6ee570706 100644 --- a/lib/components/narrative/tabbed-itineraries.js +++ b/lib/components/narrative/tabbed-itineraries.js @@ -60,28 +60,6 @@ class TabButton extends Component { ) if (isActive) classNames.push('selected') - const newLocal = minTotalFare > 0 && ( - <> - minTNCFare, - minTotalFare: ( - - ) - }} - /> - - - ) return ( @@ -73,9 +72,9 @@ export const StyledModeButton = styled(ModeButton)` const ModeButtons = ({ className, + intl, modeOptions, onClick, - intl, selectedModes = [] }: { className: string From c526c67eab94068db3ee80af711d9532f37329ea Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Mon, 11 Apr 2022 18:35:54 -0700 Subject: [PATCH 0110/1425] feat(mode-buttons): disable modes by default --- lib/components/form/batch-preferences.tsx | 3 ++- lib/components/form/batch-settings.tsx | 4 +++- lib/components/form/mode-buttons.tsx | 2 +- lib/reducers/create-otp-reducer.js | 17 +++++++++++++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/components/form/batch-preferences.tsx b/lib/components/form/batch-preferences.tsx index cdd8fef4a..e408fd23b 100644 --- a/lib/components/form/batch-preferences.tsx +++ b/lib/components/form/batch-preferences.tsx @@ -45,7 +45,8 @@ class BatchPreferences extends Component<{ */ onQueryParamChange = (newQueryParams: any) => { const { config, modeOptions, query, setQueryParam } = this.props - const enabledModes = query.enabledModes || modeOptions + const enabledModes = + query.enabledModes || modeOptions.filter((m) => !m.defaultUnselected) const combinations = config.modes.combinations .filter(combinationFilter(enabledModes)) .map(replaceTransitMode(newQueryParams.mode)) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 9c3c99b24..062c512d9 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -100,7 +100,9 @@ class BatchSettings extends Component<{ }> { state = { expanded: null, - selectedModes: this.props.modeOptions.map((m) => m.mode) + selectedModes: this.props.modeOptions + .filter((m) => !m.defaultUnselected) + .map((m) => m.mode) } _onClickMode = (mode: string) => { diff --git a/lib/components/form/mode-buttons.tsx b/lib/components/form/mode-buttons.tsx index ad893b4d8..2eb2ac2bd 100644 --- a/lib/components/form/mode-buttons.tsx +++ b/lib/components/form/mode-buttons.tsx @@ -6,10 +6,10 @@ import { buttonCss } from './batch-styled' import { ComponentContext } from '../../util/contexts' import { getFormattedMode } from '../../util/i18n' import { injectIntl, IntlProvider, IntlShape } from 'react-intl' -import FormattedMode from '../util/formatted-mode' import Icon from '../util/icon' export type Mode = { + defaultUnselected: boolean icon?: string label?: string mode: string diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0d7f65da7..572cba5d5 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -4,6 +4,7 @@ import coreUtils from '@opentripplanner/core-utils' import objectPath from 'object-path' import update from 'immutability-helper' +import { combinationFilter } from '../components/form/batch-settings' import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE } from '../util/constants' import { getTimestamp } from '../util/state' import { isBatchRoutingEnabled } from '../util/itinerary' @@ -142,9 +143,21 @@ export function getInitialState(userDefinedConfig) { if (currentQuery.routingType === 'ITINERARY') { queryModes = ensureSingleAccessMode(queryModes) } - if (config.modes && config.modes.combinations) { + if (config.modes?.combinations) { // Inject combinations from config for batch routing. - currentQuery.combinations = clone(config.modes.combinations) + // Filter options based on defaults if modeOptions is specified in config + let enabledCombinations + if (config.modes.modeOptions) { + const defaultModes = config.modes.modeOptions + ?.filter((m) => !m.defaultUnselected) + .map((m) => m.mode) + enabledCombinations = config.modes.combinations.filter( + combinationFilter(defaultModes) + ) + } else { + enabledCombinations = config.mode.combinations + } + currentQuery.combinations = clone(enabledCombinations) } return { From 8d0c158edade15b54b1003ef47ccf89864d28205 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 12 Apr 2022 11:02:48 -0400 Subject: [PATCH 0111/1425] refactor: address pr comments --- lib/components/narrative/narrative-itineraries.js | 5 ++--- lib/util/itinerary.js | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 85900fbbd..36d7c83e3 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -151,11 +151,10 @@ class NarrativeItineraries extends Component { if (!activeSearch) return null // Merge duplicate itineraries together and save multiple departure times - let nextIndex = 0 - const mergedItineraries = itineraries.reduce((prev, cur) => { + const mergedItineraries = itineraries.reduce((prev, cur, curIndex) => { const updatedItineraries = clone(prev) const updatedItinerary = clone(cur) - updatedItinerary.index = nextIndex++ + updatedItinerary.index = curIndex const duplicateIndex = updatedItineraries.findIndex((itin) => itinerariesAreEqual(itin, cur) diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index 088c481b3..2de4d3c79 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -87,7 +87,12 @@ export function getItineraryDefaultMonitoredDays(itinerary) { } function legLocationsAreEqual(legLocation, other) { - return legLocation.lat === other.lat && legLocation.lon === other.lon + return ( + !!legLocation && + !!other && + legLocation.lat === other.lat && + legLocation.lon === other.lon + ) } export function itinerariesAreEqual(itinerary, other) { return ( From bc6a17b9dc809f5d9d7a612321f446260d2ad244 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 12 Apr 2022 15:40:56 -0400 Subject: [PATCH 0112/1425] refactor: address pr comments (pair programming): --- lib/components/form/mode-buttons.tsx | 7 ++++--- lib/reducers/create-otp-reducer.js | 21 ++++++++------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/components/form/mode-buttons.tsx b/lib/components/form/mode-buttons.tsx index 2eb2ac2bd..7681f30a8 100644 --- a/lib/components/form/mode-buttons.tsx +++ b/lib/components/form/mode-buttons.tsx @@ -1,15 +1,16 @@ +import { injectIntl, IntlShape } from 'react-intl' import { OverlayTrigger, Tooltip } from 'react-bootstrap' import React, { useContext } from 'react' import styled from 'styled-components' -import { buttonCss } from './batch-styled' import { ComponentContext } from '../../util/contexts' import { getFormattedMode } from '../../util/i18n' -import { injectIntl, IntlProvider, IntlShape } from 'react-intl' import Icon from '../util/icon' +import { buttonCss } from './batch-styled' + export type Mode = { - defaultUnselected: boolean + defaultUnselected?: boolean icon?: string label?: string mode: string diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 572cba5d5..3e3cc0e40 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -143,23 +143,18 @@ export function getInitialState(userDefinedConfig) { if (currentQuery.routingType === 'ITINERARY') { queryModes = ensureSingleAccessMode(queryModes) } - if (config.modes?.combinations) { + if (config?.modes?.combinations) { // Inject combinations from config for batch routing. // Filter options based on defaults if modeOptions is specified in config - let enabledCombinations - if (config.modes.modeOptions) { - const defaultModes = config.modes.modeOptions - ?.filter((m) => !m.defaultUnselected) - .map((m) => m.mode) - enabledCombinations = config.modes.combinations.filter( - combinationFilter(defaultModes) - ) - } else { - enabledCombinations = config.mode.combinations - } + const defaultModes = config.modes.modeOptions + ?.filter((m) => !m.defaultUnselected) + .map((m) => m.mode) + const enabledCombinations = config.modes.modeOptions + ? config.modes.combinations.filter(combinationFilter(defaultModes)) + : config.modes.combinations + currentQuery.combinations = clone(enabledCombinations) } - return { activeSearchId: 0, config, From a41d37aa63258fbd7a3c3aef22d03d1bd3c69347 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 13 Apr 2022 13:13:53 -0400 Subject: [PATCH 0113/1425] fix(user-profiles): various regressions surrounding react-intl --- .../narrative/default/itinerary.css | 3 +++ .../user/monitored-trip/trip-basics-pane.tsx | 11 +++++------ lib/components/user/places/place-editor.tsx | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/components/narrative/default/itinerary.css b/lib/components/narrative/default/itinerary.css index 46f2ddb2f..134df27f7 100644 --- a/lib/components/narrative/default/itinerary.css +++ b/lib/components/narrative/default/itinerary.css @@ -59,6 +59,9 @@ /* text-align: center; */ margin: 0px 0px; } +.otp .option.default-itin > .header > .summary { + display: flex; +} .otp .option.default-itin > .header .title > .summary > .summary-block { display: inline-block; diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index 73237428c..2997084a3 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -200,12 +200,11 @@ class TripBasicsPane extends Component { : monitoredTrip[day] ? 'bg-primary' : '' - const notAvailableText = isDayDisabled - ? intl.formatMessage( - { id: 'components.TripBasicsPane.tripNotAvailableOnDay' }, - { repeatedDay: getFormattedDayOfWeekPlural(day, intl) } - ) - : null + const notAvailableText = intl.formatMessage( + { id: 'components.TripBasicsPane.tripNotAvailableOnDay' }, + { repeatedDay: getFormattedDayOfWeekPlural(day, intl) } + ) + return ( notAvailableText && ( { - _handleLocationChange = ({ - location - }: { - location: { - lat: number - lon: number - name: string + _handleLocationChange = ( + _: unknown, // Ignore intl object. TODO: localize name? + { + location + }: { + location: { + lat: number + lon: number + name: string + } } - }) => { + ) => { const { setValues, values } = this.props const { lat, lon, name } = location setValues({ From d65959e382a80113eaab904fddf9762cc89bfcea Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 14 Apr 2022 12:34:58 -0400 Subject: [PATCH 0114/1425] fix(batch-settings): correct issue that typescript should fix --- lib/components/form/batch-settings.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 062c512d9..8f88453d5 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -33,12 +33,19 @@ import type { Mode } from './mode-buttons' /** * A function that generates a filter to be used to filter a list of combinations. - * @param enabledModes A list of the modes enabled in the UI + * @param enabledModesMysteryType A list of the modes enabled in the UI * @returns Filter function to filter combinations */ export const combinationFilter = - (enabledModes: string[]) => + (enabledModesMysteryType: string[] | { mode: string }[]) => (c: Combination): boolean => { + // Ensure enabledModes is string array. This should be handled by typescript, + // but typescript is not fully enabled yet. + const enabledModes = enabledModesMysteryType.map((mode) => { + if (typeof mode === 'string') return mode + if (mode.mode) return mode.mode + }) + if (c.requiredModes) { return c.requiredModes.every((m) => enabledModes.includes(m)) } else { From e1d65d43300220059119b2fac60d8e295629058f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 18 Apr 2022 12:37:13 -0400 Subject: [PATCH 0115/1425] chore(deps): upgrade otp-ui --- package.json | 4 ++-- yarn.lock | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 29d1fed90..3dc2af3f1 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,11 @@ "@opentripplanner/humanize-distance": "^1.1.0", "@opentripplanner/icons": "^1.2.0", "@opentripplanner/itinerary-body": "^2.9.0", - "@opentripplanner/location-field": "1.12.0", + "@opentripplanner/location-field": "1.12.1", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.2", "@opentripplanner/printable-itinerary": "^1.3.1", - "@opentripplanner/route-viewer-overlay": "^1.4.0-alpha.1", + "@opentripplanner/route-viewer-overlay": "^1.4.0", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^3.4.0", "@opentripplanner/transit-vehicle-overlay": "^2.3.1", diff --git a/yarn.lock b/yarn.lock index 228a50e14..9dd6e24f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2577,10 +2577,10 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/location-field@1.12.0": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.12.0.tgz#29a2b2394c92c6ba2867eeebe099d3e82a49faa4" - integrity sha512-VueroM7SLLxnURTdFSBjnjAk9KXVb8as+nzYHNrlMx2XJqQS4I4qvTXhMjr8xjd4tRaGez6Zp3xB3bEtXeDAiw== +"@opentripplanner/location-field@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.12.1.tgz#07d13683d2fc3000f08ccf76df994dc1860d769f" + integrity sha512-sv+u1erMghZJvP2mMEDTSdYwJLObzHGG43dg13ck0kZyo7U8qezbSHHXUeoL1xSZdA3VKreFS3b9M6bRaveF1g== dependencies: "@conveyal/geocoder-arcgis-geojson" "^0.0.3" "@opentripplanner/core-utils" "^4.7.0" @@ -2588,7 +2588,6 @@ "@opentripplanner/humanize-distance" "^1.1.0" "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" - flat "^5.0.2" throttle-debounce "^2.1.0" "@opentripplanner/location-icon@^1.3.0", "@opentripplanner/location-icon@^1.4.0": @@ -2617,10 +2616,10 @@ "@opentripplanner/humanize-distance" "^1.1.0" prop-types "^15.7.2" -"@opentripplanner/route-viewer-overlay@^1.4.0-alpha.1": - version "1.4.0-alpha.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/route-viewer-overlay/-/route-viewer-overlay-1.4.0-alpha.1.tgz#58c9489d494031507b3153396e738fa1c008ad63" - integrity sha512-h9aV9buZc6xOLyM+ZQ2+ITqpzb31ZrdtNdCsqEOBiZLekjf31ucZUTq966LUv2fhUgk7x55r0ij3GiXh4UxZKA== +"@opentripplanner/route-viewer-overlay@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/route-viewer-overlay/-/route-viewer-overlay-1.4.0.tgz#b239407f9975109bdd61faa9a144bcf21dcad5e9" + integrity sha512-M+JNVIupXuvlG9mX7fGJxB3sflharjEQMjY3qtCBSC/ogLs8wJxOz9ZxelLUheFXjlMsp9RMe8yaLrw6Y6y5LA== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/core-utils" "^4.5.0" From 6d5bc90e57843822809b0dff397bad19d66aeb43 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 18 Apr 2022 13:37:16 -0400 Subject: [PATCH 0116/1425] refactor: address pr feedback --- lib/components/form/user-settings.js | 46 +++++++++++++++---- lib/components/map/point-popup.tsx | 11 ++--- .../narrative/line-itin/itin-summary.tsx | 26 +++++------ .../narrative/line-itin/line-itinerary.js | 32 +------------ lib/util/state.js | 1 - package.json | 1 + yarn.lock | 5 ++ 7 files changed, 59 insertions(+), 63 deletions(-) diff --git a/lib/components/form/user-settings.js b/lib/components/form/user-settings.js index f5d190a24..723dd3ea9 100644 --- a/lib/components/form/user-settings.js +++ b/lib/components/form/user-settings.js @@ -27,6 +27,37 @@ import { UnpaddedList } from './styled' const { matchLatLon } = coreUtils.map const { hasTransit, toSentenceCase } = coreUtils.itinerary + +/** + * Reformats a {lat, lon} object to be internationalized. + */ +export function renderCoordinates(intl, place) { + const MAX_FRAC_DIGITS = 5 + + return { + lat: intl.formatNumber(place.lat, { + maximumFractionDigits: MAX_FRAC_DIGITS + }), + lon: intl.formatNumber(place.lon, { + maximumFractionDigits: MAX_FRAC_DIGITS + }) + } +} + +/** + * Method to only strip everything after a comma if a name is getting split off, + * rather than numbers. This allows us to avoid only rendering the latitude + */ +function stripAllButNameOfAddress(string) { + if (typeof string !== 'string') return string + + // If string contains any letters, split the string! + if (/[a-zA-Z]/g.test(string)) { + return string.split(',')[0] + } + return string +} + /** * Version of summarizeQuery and helper function that supports i18n. * FIXME: replace when the original does support i18n. @@ -46,27 +77,22 @@ export function summarizeQuery(query, intl, locations = []) { if (!query.from.name) { query.from.name = intl.formatMessage( { id: 'common.coordinates' }, - { - lat: query.from.lat.toFixed(5), - lon: query.from.lon.toFixed(5) - } + renderCoordinates(intl, query.from) ) } if (!query.to.name) { query.to.name = intl.formatMessage( { id: 'common.coordinates' }, - { - lat: query.to.lat.toFixed(5), - lon: query.to.lon.toFixed(5) - } + renderCoordinates(intl, query.to) ) } const from = findLocationType(intl, query.from, locations) || - query.from.name.split(',')[0] + stripAllButNameOfAddress(query.from.name) const to = - findLocationType(intl, query.to, locations) || query.to.name.split(',')[0] + findLocationType(intl, query.to, locations) || + stripAllButNameOfAddress(query.to.name) const mode = hasTransit(query.mode) ? intl.formatMessage({ id: 'common.modes.transit' }) : toSentenceCase(query.mode) diff --git a/lib/components/map/point-popup.tsx b/lib/components/map/point-popup.tsx index 132a11c5b..879bfd898 100644 --- a/lib/components/map/point-popup.tsx +++ b/lib/components/map/point-popup.tsx @@ -6,7 +6,9 @@ import { injectIntl, WrappedComponentProps } from 'react-intl' import FromToLocationPicker from '@opentripplanner/from-to-location-picker' import React, { useEffect } from 'react' import styled from 'styled-components' +import type { Place } from '@opentripplanner/types' +import { renderCoordinates } from '../form/user-settings' import { setMapZoom } from '../../actions/config' const PopupContainer = styled.div` @@ -25,7 +27,7 @@ function MapPopup({ setMapZoom, zoom }: { - mapPopupLocation: { lat: number; lon: number; name?: string } + mapPopupLocation: Place // TODO: add types for this method onSetLocationFromPopup: () => void setMapZoom: ({ zoom }: { zoom: number }) => void @@ -43,16 +45,13 @@ function MapPopup({ mapPopupLocation?.name || intl.formatMessage( { id: 'common.coordinates' }, - { - lat: mapPopupLocation.lat.toFixed(5), - lon: mapPopupLocation.lon.toFixed(5) - } + renderCoordinates(intl, mapPopupLocation) ) return ( - {popupName.split(',').length > 3 + {typeof popupName === 'string' && popupName.split(',').length > 3 ? popupName.split(',').splice(0, 3).join(',') : popupName} diff --git a/lib/components/narrative/line-itin/itin-summary.tsx b/lib/components/narrative/line-itin/itin-summary.tsx index 7585ac30a..5cd5ba3a6 100644 --- a/lib/components/narrative/line-itin/itin-summary.tsx +++ b/lib/components/narrative/line-itin/itin-summary.tsx @@ -1,12 +1,12 @@ import { connect } from 'react-redux' import { FormattedMessage, FormattedNumber } from 'react-intl' -// TYPESCRIPT TODO: add leg, itinerary types all over the place here +// TYPESCRIPT TODO: wait for typescripted core-utils // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' import React, { Component } from 'react' import styled from 'styled-components' +import type { Itinerary, Leg, TimeOptions } from '@opentripplanner/types' import { ComponentContext } from '../../../util/contexts' import { getFare } from '../../../util/state' @@ -60,7 +60,7 @@ const Routes = styled.div` text-align: right; ` -const ShortName = styled.div<{ leg: any }>` +const ShortName = styled.div<{ leg: Leg }>` background-color: ${(props) => getRouteColorForBadge(props.leg)}; border-radius: 15px; border: 2px solid white; @@ -81,17 +81,12 @@ const ShortName = styled.div<{ leg: any }>` type Props = { currency?: string defaultFareKey: string - itinerary: any - // TODO unified types + itinerary: Itinerary onClick: () => void - timeOptions: { offset: number } + timeOptions: TimeOptions } export class ItinerarySummary extends Component { - static propTypes = { - itinerary: PropTypes.object - } - static contextType = ComponentContext _onSummaryClicked = (): void => { @@ -111,8 +106,8 @@ export class ItinerarySummary extends Component { const minTotalFare = minTNCFare * 100 + transitFare const maxTotalFare = maxTNCFare * 100 + transitFare - const startTime = itinerary.startTime + timeOptions.offset - const endTime = itinerary.endTime + timeOptions.offset + const startTime = itinerary.startTime + parseInt(timeOptions?.offset || '0') + const endTime = itinerary.endTime + parseInt(timeOptions?.offset || '0') const { caloriesBurned } = coreUtils.itinerary.calculatePhysicalActivity(itinerary) @@ -184,10 +179,10 @@ export class ItinerarySummary extends Component { {itinerary.legs - .filter((leg: any) => { + .filter((leg: Leg) => { return !(leg.mode === 'WALK' && itinerary.transitTime > 0) }) - .map((leg: any, k: string) => { + .map((leg: Leg, k: number) => { return ( @@ -209,6 +204,7 @@ export class ItinerarySummary extends Component { // Helper functions +// Leg is any for now until types package is updated with correct Leg type function getRouteLongName(leg: any) { return leg.routes && leg.routes.length > 0 ? leg.routes[0].longName @@ -233,7 +229,7 @@ function getRouteNameForBadge(leg: any) { return shortName || longName } -function getRouteColorForBadge(leg: any): string { +function getRouteColorForBadge(leg: Leg): string { return leg.routeColor ? '#' + leg.routeColor : defaultRouteColor } diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 1c301cdbb..4daedad9e 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -10,7 +10,7 @@ import SimpleRealtimeAnnotation from '../simple-realtime-annotation' import ItineraryBody from './connected-itinerary-body' import ItinerarySummary from './itin-summary' -const { getLegModeLabel, getTimeZoneOffset, isTransit } = coreUtils.itinerary +const { getTimeZoneOffset } = coreUtils.itinerary export const LineItineraryContainer = styled.div` margin-bottom: 20px; @@ -19,36 +19,6 @@ export const LineItineraryContainer = styled.div` export default class LineItinerary extends NarrativeItinerary { static contextType = ComponentContext - _headerText() { - const { itinerary } = this.props - return itinerary.summary || this._getSummary(itinerary) - } - - _getSummary(itinerary) { - let summary = '' - const transitModes = [] - itinerary.legs.forEach((leg, index) => { - if (isTransit(leg.mode)) { - /** TODO: remove use of deprecated unlocalized method once itinerary-body is localized */ - const modeStr = getLegModeLabel(leg) - if (transitModes.indexOf(modeStr) === -1) transitModes.push(modeStr) - } - }) - - // check for access mode - if (!isTransit(itinerary.legs[0].mode)) { - /** TODO: remove use of deprecated unlocalized method once itinerary-body is localized */ - summary += getLegModeLabel(itinerary.legs[0]) - } - - // append transit modes, if applicable - if (transitModes.length > 0) { - summary += ' to ' + transitModes.join(', ') - } - - return summary - } - render() { const { active, diff --git a/lib/util/state.js b/lib/util/state.js index 990617fe1..7363f239a 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -432,7 +432,6 @@ const DEFAULT_COSTS = { /** * Parses OTP itinerary fare object and returns fares along with overridden currency - */ export function getFare(itinerary, defaultFareKey, currency) { const { maxTNCFare, minTNCFare } = diff --git a/package.json b/package.json index d7d1289ca..bfd5e6466 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", + "@opentripplanner/types": "^1.2.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", diff --git a/yarn.lock b/yarn.lock index c8873e03c..184f6d093 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2703,6 +2703,11 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" +"@opentripplanner/types@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-1.2.0.tgz#1d9163ddb5ddc27b9ced52a6e1fe2941c195d5e9" + integrity sha512-kURxBFXV8z4pApducBwTHxKaAApdLJMewbkQ3cD9ykMt64Jw2TIHLK3D8d8okd2rAMykvZ9o0CPztfaLBwJrJQ== + "@opentripplanner/vehicle-rental-overlay@^1.2.1": version "1.3.0" resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.3.0.tgz#a77c3e040faf59091fdb3c8f9fb0ad46e1af8532" From 30aa54219c57f4ef195de2332e95ae6094ee2fd7 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 19 Apr 2022 08:01:43 -0400 Subject: [PATCH 0117/1425] refactor: address pr feedback --- lib/components/user/places/place-editor.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/components/user/places/place-editor.tsx b/lib/components/user/places/place-editor.tsx index b29fc8fde..ee68f1bac 100644 --- a/lib/components/user/places/place-editor.tsx +++ b/lib/components/user/places/place-editor.tsx @@ -6,7 +6,7 @@ import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap' -import { injectIntl } from 'react-intl' +import { injectIntl, IntlShape } from 'react-intl' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import type { WrappedComponentProps } from 'react-intl' @@ -15,6 +15,7 @@ import type { WrappedComponentProps } from 'react-intl' import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' import styled from 'styled-components' +import type { Location } from '@opentripplanner/types' import { capitalizeFirst, getErrorStates } from '../../../util/ui' import { CUSTOM_PLACE_TYPES, isHomeOrWork } from '../../../util/user' @@ -65,16 +66,8 @@ class PlaceEditor extends Component< } & WrappedComponentProps > { _handleLocationChange = ( - _: unknown, // Ignore intl object. TODO: localize name? - { - location - }: { - location: { - lat: number - lon: number - name: string - } - } + _: IntlShape, // Ignore intl object. TODO: localize name? + { location }: { location: Location } ) => { const { setValues, values } = this.props const { lat, lon, name } = location From 2f372fff869893aee502591d7963c2b77c19fc10 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 19 Apr 2022 08:03:18 -0400 Subject: [PATCH 0118/1425] chore(deps): add types package --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index aaf669795..e51708af8 100644 --- a/package.json +++ b/package.json @@ -124,6 +124,7 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", + "@opentripplanner/types": "^1.2.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", diff --git a/yarn.lock b/yarn.lock index 4f86592fb..d5aea663d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2703,6 +2703,11 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" +"@opentripplanner/types@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-1.2.0.tgz#1d9163ddb5ddc27b9ced52a6e1fe2941c195d5e9" + integrity sha512-kURxBFXV8z4pApducBwTHxKaAApdLJMewbkQ3cD9ykMt64Jw2TIHLK3D8d8okd2rAMykvZ9o0CPztfaLBwJrJQ== + "@opentripplanner/vehicle-rental-overlay@^1.2.1": version "1.3.0" resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.3.0.tgz#a77c3e040faf59091fdb3c8f9fb0ad46e1af8532" From 4649f7f65e0d297631899177ce96e22bd9c9d03b Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 19 Apr 2022 10:28:56 -0400 Subject: [PATCH 0119/1425] fix(leaflet): patch leaflet to avoid webkit crash --- package.json | 3 ++- patches/leaflet+1.7.1.patch | 13 ++++++++++ yarn.lock | 49 ++++++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 patches/leaflet+1.7.1.patch diff --git a/package.json b/package.json index 3dc2af3f1..668de6f48 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "unit": "jest __tests__/", "lint": "lint-staged", "lint-all": "eslint lib __tests__ a11y --quiet", - "postinstall": "husky install", + "postinstall": "patch-package && husky install", "prepublishOnly": "pinst --disable", "postpublish": "pinst --enable", "prestart": "yarn", @@ -161,6 +161,7 @@ "leaflet": "^1.6.0", "lint-staged": "^11.1.2", "nock": "^9.0.9", + "patch-package": "^6.4.7", "pinst": "^2.1.6", "prettier": "^2.3.2", "puppeteer": "^10.2.0", diff --git a/patches/leaflet+1.7.1.patch b/patches/leaflet+1.7.1.patch new file mode 100644 index 000000000..6174088b0 --- /dev/null +++ b/patches/leaflet+1.7.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/leaflet/dist/leaflet-src.js b/node_modules/leaflet/dist/leaflet-src.js +index 9acc7da..4fcaf9a 100644 +--- a/node_modules/leaflet/dist/leaflet-src.js ++++ b/node_modules/leaflet/dist/leaflet-src.js +@@ -2447,7 +2447,7 @@ + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + +- return el._leaflet_pos || new Point(0, 0); ++ return el && "_leaflet_pos" in el && el._leaflet_pos || new Point(0, 0); + } + + // @function disableTextSelection() diff --git a/yarn.lock b/yarn.lock index 9dd6e24f9..2b57b0876 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3846,6 +3846,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -7066,7 +7071,7 @@ create-react-context@^0.2.2: fbjs "^0.8.0" gud "^1.0.0" -cross-spawn@6.0.5, cross-spawn@^6.0.0: +cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -9400,6 +9405,13 @@ find-versions@^4.0.0: dependencies: semver-regex "^3.1.2" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + findup-sync@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" @@ -9601,7 +9613,7 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^7.0.0: +fs-extra@^7.0.0, fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -12531,6 +12543,13 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -14356,7 +14375,7 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.2: +open@^7.0.2, open@^7.4.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== @@ -14722,6 +14741,25 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -18031,6 +18069,11 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" From 36909e9b441bf6b9dc7bea471474ca1d80b39ee2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 19 Apr 2022 10:41:40 -0400 Subject: [PATCH 0120/1425] refactor(place-editor): reword todo comment --- lib/components/user/places/place-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/places/place-editor.tsx b/lib/components/user/places/place-editor.tsx index ee68f1bac..4d67743a7 100644 --- a/lib/components/user/places/place-editor.tsx +++ b/lib/components/user/places/place-editor.tsx @@ -66,7 +66,7 @@ class PlaceEditor extends Component< } & WrappedComponentProps > { _handleLocationChange = ( - _: IntlShape, // Ignore intl object. TODO: localize name? + _: IntlShape, // Ignore intl object. TODO: localize the name field of the returned location object? { location }: { location: Location } ) => { const { setValues, values } = this.props From dbb674da8fe86e9f36c27bd2cf49289b150ad8bd Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 21 Apr 2022 14:54:29 -0400 Subject: [PATCH 0121/1425] fix(connected-park-and-ride-overlay): Fix error check for P/R locations from otp state. --- .../map/connected-park-and-ride-overlay.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/components/map/connected-park-and-ride-overlay.tsx b/lib/components/map/connected-park-and-ride-overlay.tsx index 8dfffa0ba..6521e7c03 100644 --- a/lib/components/map/connected-park-and-ride-overlay.tsx +++ b/lib/components/map/connected-park-and-ride-overlay.tsx @@ -39,14 +39,12 @@ const mapStateToProps = (state: { }) => { const { locations } = state.otp.overlay?.parkAndRide - // object type indicates error - if (typeof locations === 'object') { - return {} - } - - return { - parkAndRideLocations: locations - } + // If locations is not an array, it is an error, in which case don't render anything. + return Array.isArray(locations) + ? { + parkAndRideLocations: locations + } + : {} } const mapDispatchToProps = { From cd17a9f94fc8fdb8ea7d4beb33746b0fb76bf425 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:44:16 -0400 Subject: [PATCH 0122/1425] chore(deps): Update itinerary package versions. --- package.json | 6 +++--- yarn.lock | 39 +++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index aaf669795..3581285d8 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,13 @@ "@opentripplanner/endpoints-overlay": "^1.3.0", "@opentripplanner/from-to-location-picker": "^1.3.0", "@opentripplanner/geocoder": "^1.2.1", - "@opentripplanner/humanize-distance": "^1.1.0", + "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^1.2.0", - "@opentripplanner/itinerary-body": "^2.9.0", + "@opentripplanner/itinerary-body": "^3.0.0", "@opentripplanner/location-field": "1.12.0", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.2", - "@opentripplanner/printable-itinerary": "^1.3.1", + "@opentripplanner/printable-itinerary": "^2.0.0", "@opentripplanner/route-viewer-overlay": "^1.3.0", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^3.4.1", diff --git a/yarn.lock b/yarn.lock index 4f86592fb..9b3eaa079 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2544,6 +2544,11 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.1.0.tgz#aa5ecdd70f33cfbdd214c76da4ba799a8ab473a5" integrity sha512-U+rANHgl+Fz6srX2lZ6LNLkDKPaUvWb27pZeVs9f8qsA/hLlx8X3FY1T9BrYaXwxgSlwAha+YzcKUC/2SfQjJQ== +"@opentripplanner/humanize-distance@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.2.0.tgz#71cf5d5d1b756adef15300edbba0995ccd4b35ee" + integrity sha512-x0QRXMDhypFeazZ6r6vzrdU8vhiV56nZ/WX6zUbxpgp6T9Oclw0gwR2Zdw6DZiiFpSYVNeVNxVzZwsu6NRGjcA== + "@opentripplanner/icons@^1.1.0", "@opentripplanner/icons@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-1.2.0.tgz#00751e17aad99620bd08b47af0c09c94cb99cf74" @@ -2560,20 +2565,19 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" -"@opentripplanner/itinerary-body@^2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-2.9.0.tgz#85623aa9e3605ba425b929d3eb949b5bcf36935a" - integrity sha512-aCwRwiq32uhWMBS0uMp5C3vEyNEkA/5C7sPBWgJRhytAR5HsSfdbGkUeM6EUbY+9Ki3QldWR4BHpNd7wqWvF9Q== +"@opentripplanner/itinerary-body@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.0.0.tgz#205474999acd4c791e9025fa10191908f582bb56" + integrity sha512-34uSwpnHcPEXEsUfOxz7uVSIlfSBAow2aFQKb9LXT2e+Grwb/QtuJZbvgYEVYzH5KAu/TSKUytxrEDvVfZv1Vw== dependencies: - "@opentripplanner/core-utils" "^4.5.0" - "@opentripplanner/humanize-distance" "^1.1.0" + "@opentripplanner/core-utils" "^4.8.0" + "@opentripplanner/humanize-distance" "^1.2.0" "@opentripplanner/icons" "^1.2.1" "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" "@styled-icons/foundation" "^10.34.0" - currency-formatter "^1.5.5" + flat "^5.0.2" moment "^2.24.0" - prop-types "^15.7.2" react-resize-detector "^4.2.1" velocity-react "^1.4.3" @@ -2608,14 +2612,12 @@ "@opentripplanner/from-to-location-picker" "^1.2.1" prop-types "^15.7.2" -"@opentripplanner/printable-itinerary@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-1.3.1.tgz#e82ecfc91ecd5fb8f4ec4d7d3cc1548f33b3da7e" - integrity sha512-3T+r/U+0Pfe6CvUSWbqBBFS5uUyq3T3v19xV3jb7KplsQpYLy4nKjQcqO2/doxf/fVpWZ3XPXAFMntFHqx7pTA== +"@opentripplanner/printable-itinerary@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-2.0.0.tgz#7a2d8bcc0db2bdcbabd71af53e93beeb30e6c3ff" + integrity sha512-mOVDU4bSDsgngjLn6VN17wKOcYlaX85NmEyJfHpKa9EX0eHg3zCew9pNAYPSylr7G5/SDceWqtx3uQJ5DnbzOQ== dependencies: - "@opentripplanner/core-utils" "^4.1.0" - "@opentripplanner/humanize-distance" "^1.1.0" - prop-types "^15.7.2" + "@opentripplanner/itinerary-body" "^3.0.0" "@opentripplanner/route-viewer-overlay@^1.3.0": version "1.3.0" @@ -2703,6 +2705,11 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" +"@opentripplanner/types@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-1.2.1.tgz#fbd193ab0b9ea8eb937e5df42dcdd2800b701b3e" + integrity sha512-K2JCBYNwaIJRrqt0OLmlL8g8cpOLujlnBUISqqZXtH6C3Uiw/QPlKTLI9FI0UG0xWpRDAnJIPeaTGHdi1dmwfw== + "@opentripplanner/vehicle-rental-overlay@^1.2.1": version "1.3.0" resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.3.0.tgz#a77c3e040faf59091fdb3c8f9fb0ad46e1af8532" @@ -7695,7 +7702,7 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== -currency-formatter@^1.4.2, currency-formatter@^1.5.5: +currency-formatter@^1.4.2: version "1.5.8" resolved "https://registry.yarnpkg.com/currency-formatter/-/currency-formatter-1.5.8.tgz#5c2a18673d966fc355bac901f7423ccce607b983" integrity sha512-/Oim4XnX82wf+JknrObifmBHpXnSQZC3FrCi+Oss4olXm3nqGWd8eY3sL2Tf0pZ/zuUqj1stU7f//5Hh27+cSQ== From 9ebe55c3fc3c594bed527c05672010a3a9b778c0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:28:47 -0400 Subject: [PATCH 0123/1425] chore(deps): Update core-utils version, dedupe yarn.lock. --- package.json | 2 +- yarn.lock | 416 ++++++--------------------------------------------- 2 files changed, 43 insertions(+), 375 deletions(-) diff --git a/package.json b/package.json index 3581285d8..9289533a7 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^2.0.0", - "@opentripplanner/core-utils": "^4.8.0", + "@opentripplanner/core-utils": "^4.10.0", "@opentripplanner/endpoints-overlay": "^1.3.0", "@opentripplanner/from-to-location-picker": "^1.3.0", "@opentripplanner/geocoder": "^1.2.1", diff --git a/yarn.lock b/yarn.lock index 9b3eaa079..fb253719c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -107,16 +107,7 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.12.1", "@babel/generator@^7.15.0", "@babel/generator@^7.7.2": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" - integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== - dependencies: - "@babel/types" "^7.15.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.15.4": +"@babel/generator@^7.12.1", "@babel/generator@^7.15.0", "@babel/generator@^7.15.4", "@babel/generator@^7.7.2": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.4.tgz#85acb159a267ca6324f9793986991ee2022a05b0" integrity sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw== @@ -125,14 +116,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" - integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA== - dependencies: - "@babel/types" "^7.14.5" - -"@babel/helper-annotate-as-pure@^7.15.4": +"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.14.5", "@babel/helper-annotate-as-pure@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz#3d0e43b00c5e49fdb6c57e421601a7a658d5f835" integrity sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA== @@ -157,19 +141,7 @@ browserslist "^4.16.6" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz#c9a137a4d137b2d0e2c649acf536d7ba1a76c0f7" - integrity sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q== - dependencies: - "@babel/helper-annotate-as-pure" "^7.14.5" - "@babel/helper-function-name" "^7.14.5" - "@babel/helper-member-expression-to-functions" "^7.15.0" - "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/helper-replace-supers" "^7.15.0" - "@babel/helper-split-export-declaration" "^7.14.5" - -"@babel/helper-create-class-features-plugin@^7.15.4": +"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.4.tgz#7f977c17bd12a5fba363cb19bea090394bf37d2e" integrity sha512-7ZmzFi+DwJx6A7mHRwbuucEYpyBwmh2Ca0RvI6z2+WLZYCqV0JOaLb+u0zbtmDicebgKBZgqbYfLaKNqSgv5Pw== @@ -224,16 +196,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-function-name@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" - integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== - dependencies: - "@babel/helper-get-function-arity" "^7.14.5" - "@babel/template" "^7.14.5" - "@babel/types" "^7.14.5" - -"@babel/helper-function-name@^7.15.4": +"@babel/helper-function-name@^7.14.5", "@babel/helper-function-name@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== @@ -242,13 +205,6 @@ "@babel/template" "^7.15.4" "@babel/types" "^7.15.4" -"@babel/helper-get-function-arity@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" - integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== - dependencies: - "@babel/types" "^7.14.5" - "@babel/helper-get-function-arity@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" @@ -256,27 +212,13 @@ dependencies: "@babel/types" "^7.15.4" -"@babel/helper-hoist-variables@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" - integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== - dependencies: - "@babel/types" "^7.14.5" - -"@babel/helper-hoist-variables@^7.15.4": +"@babel/helper-hoist-variables@^7.14.5", "@babel/helper-hoist-variables@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== dependencies: "@babel/types" "^7.15.4" -"@babel/helper-member-expression-to-functions@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b" - integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg== - dependencies: - "@babel/types" "^7.15.0" - "@babel/helper-member-expression-to-functions@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" @@ -305,14 +247,7 @@ "@babel/traverse" "^7.15.0" "@babel/types" "^7.15.0" -"@babel/helper-optimise-call-expression@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" - integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA== - dependencies: - "@babel/types" "^7.14.5" - -"@babel/helper-optimise-call-expression@^7.15.4": +"@babel/helper-optimise-call-expression@^7.14.5", "@babel/helper-optimise-call-expression@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== @@ -333,17 +268,7 @@ "@babel/helper-wrap-function" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4" - integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.0" - "@babel/helper-optimise-call-expression" "^7.14.5" - "@babel/traverse" "^7.15.0" - "@babel/types" "^7.15.0" - -"@babel/helper-replace-supers@^7.15.4": +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0", "@babel/helper-replace-supers@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== @@ -367,26 +292,14 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-split-export-declaration@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" - integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== - dependencies: - "@babel/types" "^7.14.5" - -"@babel/helper-split-export-declaration@^7.15.4": +"@babel/helper-split-export-declaration@^7.14.5", "@babel/helper-split-export-declaration@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== dependencies: "@babel/types" "^7.15.4" -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": - version "7.14.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" - integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== - -"@babel/helper-validator-identifier@^7.16.7": +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== @@ -424,21 +337,11 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.12.3", "@babel/parser@^7.15.0", "@babel/parser@^7.15.4", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.8.tgz#61c243a3875f7d0b0962b0543a33ece6ff2f1f17" integrity sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw== -"@babel/parser@^7.11.5", "@babel/parser@^7.12.3", "@babel/parser@^7.14.5", "@babel/parser@^7.15.0", "@babel/parser@^7.7.0", "@babel/parser@^7.7.2": - version "7.15.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" - integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== - -"@babel/parser@^7.15.4": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.6.tgz#043b9aa3c303c0722e5377fef9197f4cf1796549" - integrity sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q== - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e" @@ -1082,16 +985,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-typescript@^7.12.1": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.0.tgz#553f230b9d5385018716586fc48db10dd228eb7e" - integrity sha512-WIIEazmngMEEHDaPTx0IZY48SaAmjVWe3TRSX7cmJXn0bEv9midFzAjxiruOWYIVf5iQ10vFx7ASDpgEO08L5w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.15.0" - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-typescript" "^7.14.5" - -"@babel/plugin-transform-typescript@^7.15.0": +"@babel/plugin-transform-typescript@^7.12.1", "@babel/plugin-transform-typescript@^7.15.0": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.4.tgz#db7a062dcf8be5fc096bc0eeb40a13fbfa1fa251" integrity sha512-sM1/FEjwYjXvMwu1PJStH11kJ154zd/lpY56NQJ5qH2D0mabMv1CAy/kdvS9RP4Xgfj9fBBA3JiSLdDHgXdzOA== @@ -1349,30 +1243,14 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2": - version "7.15.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" - integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.10.4", "@babel/template@^7.14.5", "@babel/template@^7.3.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" - integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.14.5" - "@babel/types" "^7.14.5" - -"@babel/template@^7.15.4": +"@babel/template@^7.10.4", "@babel/template@^7.14.5", "@babel/template@^7.15.4", "@babel/template@^7.3.3": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== @@ -1381,22 +1259,7 @@ "@babel/parser" "^7.15.4" "@babel/types" "^7.15.4" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.11.5", "@babel/traverse@^7.12.1", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.15.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98" - integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.0" - "@babel/helper-function-name" "^7.14.5" - "@babel/helper-hoist-variables" "^7.14.5" - "@babel/helper-split-export-declaration" "^7.14.5" - "@babel/parser" "^7.15.0" - "@babel/types" "^7.15.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.15.4": +"@babel/traverse@^7.1.0", "@babel/traverse@^7.11.5", "@babel/traverse@^7.12.1", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.15.0", "@babel/traverse@^7.15.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== @@ -1411,23 +1274,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.1", "@babel/types@^7.12.6", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.15.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" - integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - -"@babel/types@^7.15.4": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - -"@babel/types@^7.3.0": +"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.1", "@babel/types@^7.12.6", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.15.0", "@babel/types@^7.15.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.16.8" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1" integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg== @@ -2428,64 +2275,10 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" -"@opentripplanner/core-utils@^4.1.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.4.0.tgz#3eb8f9718d32ce50e3b79d975d682559c5a7b0bf" - integrity sha512-qCd+NXa0PxwcN2DFR0HX1u/76RzQwHe2r0VGcaOS4GxvQ5HEL4zPyaT+lkxAyE6AFQAvNynyofhxlnMwbxtqEA== - dependencies: - "@mapbox/polyline" "^1.1.0" - "@opentripplanner/geocoder" "^1.1.1" - "@styled-icons/foundation" "^10.34.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - date-fns "^2.23.0" - date-fns-tz "^1.1.4" - lodash.clonedeep "^4.5.0" - lodash.isequal "^4.5.0" - moment "^2.24.0" - prop-types "^15.7.2" - qs "^6.9.1" - -"@opentripplanner/core-utils@^4.5.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.6.0.tgz#f0f9924a2312b89a3f84d0a34c1a2d9f6c1ca615" - integrity sha512-LQs8BrwqFXKHJOgRduKic9wEGqnNAcyrypTgKmx//Jzz6OmZyXNskrFNJwLR6HIgsps7isN/7hi3NuVFticgIA== - dependencies: - "@mapbox/polyline" "^1.1.0" - "@opentripplanner/geocoder" "^1.1.2" - "@styled-icons/foundation" "^10.34.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - date-fns "^2.23.0" - date-fns-tz "^1.1.4" - lodash.clonedeep "^4.5.0" - lodash.isequal "^4.5.0" - moment "^2.24.0" - prop-types "^15.7.2" - qs "^6.9.1" - -"@opentripplanner/core-utils@^4.7.0": - version "4.7.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.7.1.tgz#72e708d3818f6764726b489192d7501e86d012d3" - integrity sha512-AGtNEqqNLYMp+elLiQKVf6QcP4KIoMugN7oZ4+G1p5a1eMMtP5wrxk/2IidCkkz2c+wacljoPZ2pWF630gvj7w== - dependencies: - "@mapbox/polyline" "^1.1.0" - "@opentripplanner/geocoder" "^1.1.2" - "@styled-icons/foundation" "^10.34.0" - "@turf/along" "^6.0.1" - bowser "^2.7.0" - date-fns "^2.23.0" - date-fns-tz "^1.1.4" - lodash.clonedeep "^4.5.0" - lodash.isequal "^4.5.0" - moment "^2.24.0" - prop-types "^15.7.2" - qs "^6.9.1" - -"@opentripplanner/core-utils@^4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.8.0.tgz#54f42fe038c0a9e011f0e126c42fe2f31db3148a" - integrity sha512-vv1vDd5xTBpLoViPwa0fjYBS+u0OD/Sgp+97Q83HmRr4V8Lj/LXSdjtQWIWZHuN3TUIhDGH2Cuk+f1UVhGHJDw== +"@opentripplanner/core-utils@^4.1.0", "@opentripplanner/core-utils@^4.10.0", "@opentripplanner/core-utils@^4.5.0", "@opentripplanner/core-utils@^4.7.0", "@opentripplanner/core-utils@^4.8.0": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.11.1.tgz#62f58ab0e318a4ccd27fe5171973770650725f99" + integrity sha512-S//G5KllmmzYUn6n9I7cL3cvacWlBfjFAU2MyzdsMKIOY1zXBSPHSbe10eQAccJXp73IUeBbGkInAepyCp7eUw== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/geocoder" "^1.1.2" @@ -2519,17 +2312,7 @@ "@opentripplanner/location-icon" "^1.3.0" prop-types "^15.7.2" -"@opentripplanner/geocoder@^1.1.1", "@opentripplanner/geocoder@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@opentripplanner/geocoder/-/geocoder-1.1.2.tgz#fb4c939f5746705d0bebd9791b3f9d9eb055ce8e" - integrity sha512-XEHzueckoi6UM1wfgLj/VTjOilEoAj9WA5D26wAM992ItJfvlllis2XfSQecBHDAFc7YbRoKxT6IbsJOmqeMuQ== - dependencies: - "@conveyal/geocoder-arcgis-geojson" "^0.0.3" - "@conveyal/lonlat" "^1.4.1" - isomorphic-mapzen-search "^1.6.0" - lodash.memoize "^4.1.2" - -"@opentripplanner/geocoder@^1.2.0", "@opentripplanner/geocoder@^1.2.1": +"@opentripplanner/geocoder@^1.1.2", "@opentripplanner/geocoder@^1.2.0", "@opentripplanner/geocoder@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@opentripplanner/geocoder/-/geocoder-1.2.1.tgz#5ba3a3c7664f387b6f1cf7959b91ad77170bf2b7" integrity sha512-3RLeRQis9ptIhqeBfBxuPI04/WeGhiYcY5aJONZm9005eFhX4QVesGQpT/qcfC7Wjp55Pnikroh0fMcl+oFvFA== @@ -2539,25 +2322,12 @@ isomorphic-mapzen-search "^1.6.1" lodash.memoize "^4.1.2" -"@opentripplanner/humanize-distance@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.1.0.tgz#aa5ecdd70f33cfbdd214c76da4ba799a8ab473a5" - integrity sha512-U+rANHgl+Fz6srX2lZ6LNLkDKPaUvWb27pZeVs9f8qsA/hLlx8X3FY1T9BrYaXwxgSlwAha+YzcKUC/2SfQjJQ== - -"@opentripplanner/humanize-distance@^1.2.0": +"@opentripplanner/humanize-distance@^1.1.0", "@opentripplanner/humanize-distance@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.2.0.tgz#71cf5d5d1b756adef15300edbba0995ccd4b35ee" integrity sha512-x0QRXMDhypFeazZ6r6vzrdU8vhiV56nZ/WX6zUbxpgp6T9Oclw0gwR2Zdw6DZiiFpSYVNeVNxVzZwsu6NRGjcA== -"@opentripplanner/icons@^1.1.0", "@opentripplanner/icons@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-1.2.0.tgz#00751e17aad99620bd08b47af0c09c94cb99cf74" - integrity sha512-0wVJmBa7NLeurer8dq3BJdZhPOhrRomomSEB6gYODZsKL6ZqWm8PykXeT6ACDfz3e2nsrKBdsg+XvqYeL6eG7Q== - dependencies: - "@opentripplanner/core-utils" "^4.1.0" - prop-types "^15.7.2" - -"@opentripplanner/icons@^1.2.1": +"@opentripplanner/icons@^1.1.0", "@opentripplanner/icons@^1.2.0", "@opentripplanner/icons@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-1.2.1.tgz#aaaf4c7ac5c11a64cc3387be031fb1a71acb3806" integrity sha512-LMPRxc8S34CklMJEciOw/fGFx7YEc4lwJJqyFKofisslObTK2Tx1Q7dCFFYCO2APqKDGuGxTUIkvYyv5wlFaxQ== @@ -2705,11 +2475,6 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" -"@opentripplanner/types@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-1.2.1.tgz#fbd193ab0b9ea8eb937e5df42dcdd2800b701b3e" - integrity sha512-K2JCBYNwaIJRrqt0OLmlL8g8cpOLujlnBUISqqZXtH6C3Uiw/QPlKTLI9FI0UG0xWpRDAnJIPeaTGHdi1dmwfw== - "@opentripplanner/vehicle-rental-overlay@^1.2.1": version "1.3.0" resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.3.0.tgz#a77c3e040faf59091fdb3c8f9fb0ad46e1af8532" @@ -3593,16 +3358,7 @@ "@types/history" "*" "@types/react" "*" -"@types/react@*": - version "17.0.19" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991" - integrity sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@17": +"@types/react@*", "@types/react@17": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.38.tgz#f24249fefd89357d5fa71f739a686b8d7c7202bd" integrity sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ== @@ -4177,17 +3933,7 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^8.0.1: - version "8.6.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" - integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -ajv@^8.6.2: +ajv@^8.0.1, ajv@^8.6.2: version "8.10.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== @@ -4270,12 +4016,7 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-regex@^5.0.1: +ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -7187,12 +6928,7 @@ core-js-compat@^3.14.0, core-js-compat@^3.16.0, core-js-compat@^3.6.2: browserslist "^4.16.8" semver "7.0.0" -core-js-pure@^3.16.0: - version "3.16.4" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.16.4.tgz#8b23122628d88c560f209812b9b2d9ebbce5e29c" - integrity sha512-bY1K3/1Jy9D8Jd12eoeVahNXHLfHFb4TXWI8SQ4y8bImR9qDPmGITBAfmcffTkgUvbJn87r8dILOTWW5kZzkgA== - -core-js-pure@^3.8.1: +core-js-pure@^3.16.0, core-js-pure@^3.8.1: version "3.18.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.18.1.tgz#097d34d24484be45cea700a448d1e74622646c80" integrity sha512-kmW/k8MaSuqpvA1xm2l3TVlBuvW+XBkcaOroFUpO3D4lsTGQWBTb/tBDCf/PNkkPLrwgrkQRIYNPB0CeqGJWGQ== @@ -7254,12 +6990,7 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -country-flag-icons@^1.0.2: - version "1.4.7" - resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.4.7.tgz#6f9acb18fae373528ba38834ba80e30084f24e7f" - integrity sha512-tsF7fPalAub3f328OcZL6sUxC+g7Ndua+M4xXCm+Q3f/p2XtNmJNrZTopA9iyS82ddYyKZ5ClPZUhifWv6fn2g== - -country-flag-icons@^1.4.19: +country-flag-icons@^1.0.2, country-flag-icons@^1.4.19: version "1.4.19" resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.4.19.tgz#410879a4a1b06ab28d4fc56261b391f221eba957" integrity sha512-1hmXFJ4UURQt0Ex0990B7oOL4n9KLpT9NOSEmZoYh+/5DQ7/pikyqaptqCLUFFv/bYHyvYFeo0fqV82XxU6VOA== @@ -9378,18 +9109,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.1.1: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-glob@^3.2.9: +fast-glob@^3.1.1, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -10223,19 +9943,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.0, globby@^11.0.1, globby@^11.0.3: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -globby@^11.0.4: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -10780,12 +10488,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -ignore@^5.2.0: +ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -11563,15 +11266,6 @@ isomorphic-fetch@^3.0.0: node-fetch "^2.6.1" whatwg-fetch "^3.4.1" -isomorphic-mapzen-search@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/isomorphic-mapzen-search/-/isomorphic-mapzen-search-1.6.0.tgz#eb368756d6132016a32c76435d6f34a38da5279f" - integrity sha512-90Zp8jxWuMCk582S7d6b85lHO2Lj4Nybv8o6ShqQX2SRP7SFh1fzHx67eilNUGNjBeamKYQJHmijZ1/wSzFe3A== - dependencies: - "@conveyal/lonlat" "^1.4.1" - isomorphic-fetch "^3.0.0" - qs "^6.3.0" - isomorphic-mapzen-search@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/isomorphic-mapzen-search/-/isomorphic-mapzen-search-1.6.1.tgz#5310e8b845ba70ae4fdd2604c3964264939abe53" @@ -13740,12 +13434,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": - version "1.49.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" - integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== - -mime-db@1.51.0: +mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": version "1.51.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== @@ -13762,14 +13451,7 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.32" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" - integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== - dependencies: - mime-db "1.49.0" - -mime-types@^2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.34, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.34" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== @@ -14026,12 +13708,12 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -mute-stream@0.0.7, mute-stream@~0.0.4: +mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -mute-stream@0.0.8: +mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== @@ -15171,12 +14853,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -picomatch@^2.3.0: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -16537,12 +16214,12 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" -punycode@1.3.2, punycode@^1.2.4: +punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.3.2: +punycode@^1.2.4, punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -17989,12 +17666,12 @@ sade@^1.4.2: dependencies: mri "^1.1.0" -safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -18891,7 +18568,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.0.0, string-width@^4.2.2: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -18900,15 +18577,6 @@ string-width@^4.0.0, string-width@^4.2.2: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - string.prototype.matchall@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz#59370644e1db7e4c0c045277690cf7b01203c4da" @@ -18969,7 +18637,7 @@ stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -strip-ansi@6.0.0, strip-ansi@^6.0.0: +strip-ansi@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== @@ -18997,7 +18665,7 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== From 4262eaeaebb2507d0d44dae9b9be42954db74df1 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:49:39 -0400 Subject: [PATCH 0124/1425] chore(i18n): Match FR coordinates message --- i18n/fr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/fr.yml b/i18n/fr.yml index ea7f18cb2..35e9a28d0 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -685,6 +685,7 @@ otpUi: tripIncludesFlex: Ce trajet comprend un service à la demande (Flex). common: + coordinates: "{lat}; {lon}" dateExpressions: today: Aujourd'hui tomorrow: Demain From 490b071ca87eea355fbbd02e3c5ca45223b828c0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:56:37 -0400 Subject: [PATCH 0125/1425] fix(ConnectedItineraryBody): Use new itinerary body version. --- .../narrative/line-itin/connected-itinerary-body.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index 9cf7d404a..020ad1970 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -1,6 +1,7 @@ /* eslint-disable react/prop-types */ // TODO: Typescript (otp-rr config object) import { connect } from 'react-redux' +import { PlaceName } from '@opentripplanner/itinerary-body/lib/otp-react-redux' import { PlaceName as PlaceNameWrapper, PlaceRowWrapper @@ -9,7 +10,6 @@ import clone from 'clone' import isEqual from 'lodash.isequal' import ItineraryBody from '@opentripplanner/itinerary-body/lib/otp-react-redux/itinerary-body' import LineColumnContent from '@opentripplanner/itinerary-body/lib/otp-react-redux/line-column-content' -import PlaceName from '@opentripplanner/itinerary-body/lib/otp-react-redux/place-name' import React, { Component } from 'react' import RouteDescription from '@opentripplanner/itinerary-body/lib/otp-react-redux/route-description' import styled from 'styled-components' @@ -44,7 +44,7 @@ class ConnectedItineraryBody extends Component { static contextType = ComponentContext /** avoid rerendering if the itinerary to display hasn't changed */ - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps) { return !isEqual(this.props.itinerary, nextProps.itinerary) } @@ -57,8 +57,7 @@ class ConnectedItineraryBody extends Component { setActiveLeg, setLegDiagram, setMapillaryId, - setViewedTrip, - timeOptions + setViewedTrip } = this.props const { LegIcon } = this.context const clonedItinerary = clone(itinerary) @@ -114,7 +113,6 @@ class ConnectedItineraryBody extends Component { showRouteFares={config.itinerary && config.itinerary.showRouteFares} showViewTripButton TimeColumnContent={RealtimeTimeColumn} - timeOptions={timeOptions} toRouteAbbreviation={noop} TransitLegSubheader={TransitLegSubheader} TransitLegSummary={TransitLegSummary} @@ -126,7 +124,7 @@ class ConnectedItineraryBody extends Component { } } -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state) => { return { config: state.otp.config, diagramVisible: state.otp.ui.diagramLeg From 7752184c8eec673081d92c378e4bd63691eec165 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:11:30 -0400 Subject: [PATCH 0126/1425] fix(actions/form): Fix inaccurate state check. fix #597 --- lib/actions/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/form.js b/lib/actions/form.js index ca18c8ff0..1e4612b50 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -98,7 +98,7 @@ export function setQueryParam(payload, searchId) { export function parseUrlQueryString(params = getUrlParams(), source) { return function (dispatch, getState) { const state = getState() - if (state.otp.ui.mainPanelContent !== null) return state + if (state.otp.ui.mainPanelContent) return state // Filter out the OTP (i.e. non-UI) params and set the initial query const planParams = {} From ec4dd4abfde61bf9971bbfe44a02d99d65e163b1 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 26 Apr 2022 09:48:27 -0700 Subject: [PATCH 0127/1425] refactor: address pr feedback --- .../user/monitored-trip/trip-basics-pane.tsx | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.tsx b/lib/components/user/monitored-trip/trip-basics-pane.tsx index 2997084a3..e8ff2a321 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.tsx +++ b/lib/components/user/monitored-trip/trip-basics-pane.tsx @@ -200,35 +200,35 @@ class TripBasicsPane extends Component { : monitoredTrip[day] ? 'bg-primary' : '' - const notAvailableText = intl.formatMessage( - { id: 'components.TripBasicsPane.tripNotAvailableOnDay' }, - { repeatedDay: getFormattedDayOfWeekPlural(day, intl) } - ) + const notAvailableText = isDayDisabled + ? intl.formatMessage( + { id: 'components.TripBasicsPane.tripNotAvailableOnDay' }, + { repeatedDay: getFormattedDayOfWeekPlural(day, intl) } + ) + : '' return ( - notAvailableText && ( - - - - - { - // Let users save an existing trip, even though it may not be available on some days. - // TODO: improve checking trip availability. - isDayDisabled && isCreating ? ( - - ) : ( - - ) - } - - ) + + + + + { + // Let users save an existing trip, even though it may not be available on some days. + // TODO: improve checking trip availability. + isDayDisabled && isCreating ? ( + + ) : ( + + ) + } + ) })}
From f6c5b6e173f7c7f849ff834d5b2e3e9fb8044593 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Apr 2022 17:58:21 -0400 Subject: [PATCH 0128/1425] refactor(itinerary components): Remove unused props. --- .../narrative/default/default-itinerary.js | 1 - ...js => connected-transit-leg-subheader.tsx} | 31 ++++---- .../narrative/line-itin/line-itinerary.js | 1 - .../line-itin/realtime-time-column.js | 72 ------------------- .../line-itin/realtime-time-column.tsx | 61 ++++++++++++++++ 5 files changed, 76 insertions(+), 90 deletions(-) rename lib/components/narrative/line-itin/{connected-transit-leg-subheader.js => connected-transit-leg-subheader.tsx} (50%) delete mode 100644 lib/components/narrative/line-itin/realtime-time-column.js create mode 100644 lib/components/narrative/line-itin/realtime-time-column.tsx diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 14c7d5f26..294c5f86a 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -308,7 +308,6 @@ class DefaultItinerary extends NarrativeItinerary { itinerary={itinerary} LegIcon={LegIcon} setActiveLeg={setActiveLeg} - timeOptions={timeOptions} /> )} diff --git a/lib/components/narrative/line-itin/connected-transit-leg-subheader.js b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx similarity index 50% rename from lib/components/narrative/line-itin/connected-transit-leg-subheader.js rename to lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx index 130374022..b3ae7512c 100644 --- a/lib/components/narrative/line-itin/connected-transit-leg-subheader.js +++ b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx @@ -1,25 +1,26 @@ -import TransitLegSubheader from '@opentripplanner/itinerary-body/lib/otp-react-redux/transit-leg-subheader' -import React, { Component } from 'react' import { connect } from 'react-redux' +import { Leg } from '@opentripplanner/types' +import React, { Component } from 'react' +import TransitLegSubheader from '@opentripplanner/itinerary-body/lib/otp-react-redux/transit-leg-subheader' import { setMainPanelContent, setViewedStop } from '../../../actions/ui' -class ConnectedTransitLegSubheader extends Component { - onClick = (payload) => { +interface Props { + leg: Leg + setMainPanelContent: (any) => void + setViewedStop: ({ stopId: string }) => void +} + +class ConnectedTransitLegSubheader extends Component { + onClick = (payload: { stopId: string }) => { const { setMainPanelContent, setViewedStop } = this.props setMainPanelContent(null) setViewedStop(payload) } - render () { - const { languageConfig, leg } = this.props - return ( - - ) + render() { + const { leg } = this.props + return } } @@ -28,6 +29,4 @@ const mapDispatchToProps = { setViewedStop } -export default connect(null, mapDispatchToProps)( - ConnectedTransitLegSubheader -) +export default connect(null, mapDispatchToProps)(ConnectedTransitLegSubheader) diff --git a/lib/components/narrative/line-itin/line-itinerary.js b/lib/components/narrative/line-itin/line-itinerary.js index 4daedad9e..5373c0f7c 100644 --- a/lib/components/narrative/line-itin/line-itinerary.js +++ b/lib/components/narrative/line-itin/line-itinerary.js @@ -60,7 +60,6 @@ export default class LineItinerary extends NarrativeItinerary { // (will cause error when clicking on itinerary summary). // Use the one passed by NarrativeItineraries instead. setActiveLeg={setActiveLeg} - timeOptions={timeOptions} /> ) : null} {ItineraryFooter && } diff --git a/lib/components/narrative/line-itin/realtime-time-column.js b/lib/components/narrative/line-itin/realtime-time-column.js deleted file mode 100644 index 3e8c96429..000000000 --- a/lib/components/narrative/line-itin/realtime-time-column.js +++ /dev/null @@ -1,72 +0,0 @@ -import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' -import { - legType, - timeOptionsType -} from '@opentripplanner/core-utils/lib/types' -import PropTypes from 'prop-types' -import React from 'react' -import { FormattedTime } from 'react-intl' -import styled from 'styled-components' -import { connect } from 'react-redux' - -import RealtimeStatusLabel, { DelayText, MainContent } from '../../viewers/realtime-status-label' - -const StyledStatusLabel = styled(RealtimeStatusLabel)` - ${MainContent} { - font-size: 80%; - line-height: 1em; - } - ${DelayText} { - display: block; - } -` -/** - * This component displays the scheduled departure/arrival time for a leg, - * and, for transit legs, displays any delays or earliness where applicable. - */ -function RealtimeTimeColumn ({ - homeTimezone, - isDestination, - leg, - timeOptions -}) { - const time = isDestination ? leg.endTime : leg.startTime - const adjustedTime = time && time + timeOptions.offset - - const isTransitLeg = isTransit(leg.mode) - const isRealtimeTransitLeg = isTransitLeg && leg.realTime - // For non-transit legs show only the scheduled time. - if (!isTransitLeg) { - return (
) - } - - const delaySeconds = isDestination ? leg.arrivalDelay : leg.departureDelay - const originalTimeMillis = time - delaySeconds * 1000 - const adjustedOriginalTime = originalTimeMillis + timeOptions.offset - - return ( - - ) -} - -RealtimeTimeColumn.propTypes = { - isDestination: PropTypes.bool.isRequired, - leg: legType.isRequired, - timeOptions: timeOptionsType -} - -RealtimeTimeColumn.defaultProps = { - timeOptions: null -} - -const mapStateToProps = (state, ownProps) => { - return { - homeTimezone: state.otp.config.homeTimezone - } -} - -export default connect(mapStateToProps)(RealtimeTimeColumn) diff --git a/lib/components/narrative/line-itin/realtime-time-column.tsx b/lib/components/narrative/line-itin/realtime-time-column.tsx new file mode 100644 index 000000000..a9cf50fab --- /dev/null +++ b/lib/components/narrative/line-itin/realtime-time-column.tsx @@ -0,0 +1,61 @@ +import { connect } from 'react-redux' +import { FormattedTime } from 'react-intl' +import { + getTimeZoneOffset, + isTransit +} from '@opentripplanner/core-utils/lib/itinerary' +import { Leg } from '@opentripplanner/types' +import React, { ReactElement } from 'react' +import styled from 'styled-components' + +import RealtimeStatusLabel, { + DelayText, + MainContent +} from '../../viewers/realtime-status-label' + +interface Props { + isDestination: boolean + leg: Leg +} + +const StyledStatusLabel = styled(RealtimeStatusLabel)` + ${MainContent} { + font-size: 80%; + line-height: 1em; + } + ${DelayText} { + display: block; + } +` +/** + * This component displays the scheduled departure/arrival time for a leg, + * and, for transit legs, displays any delays or earliness where applicable. + */ +function RealtimeTimeColumn({ isDestination, leg }: Props): ReactElement { + const timeMillis = isDestination ? leg.endTime : leg.startTime + const isTransitLeg = isTransit(leg.mode) + const isRealtimeTransitLeg = isTransitLeg && leg.realTime + + // For non-transit legs show only the scheduled time. + if (!isTransitLeg) { + return ( +
+ +
+ ) + } + + const delaySeconds = isDestination ? leg.arrivalDelay : leg.departureDelay + const originalTimeMillis = timeMillis - delaySeconds * 1000 + + return ( + + ) +} + +export default RealtimeTimeColumn From 083df1269d8d4dd3b716c9317bc4077cbcabfe5f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 26 Apr 2022 18:07:16 -0400 Subject: [PATCH 0129/1425] refactor(itinerary components): Fix TypeScript errors --- .../line-itin/connected-transit-leg-subheader.tsx | 4 ++-- .../narrative/line-itin/realtime-time-column.tsx | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx index b3ae7512c..2c1f52063 100644 --- a/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx +++ b/lib/components/narrative/line-itin/connected-transit-leg-subheader.tsx @@ -7,8 +7,8 @@ import { setMainPanelContent, setViewedStop } from '../../../actions/ui' interface Props { leg: Leg - setMainPanelContent: (any) => void - setViewedStop: ({ stopId: string }) => void + setMainPanelContent: (content: any) => void + setViewedStop: (payload: { stopId: string }) => void } class ConnectedTransitLegSubheader extends Component { diff --git a/lib/components/narrative/line-itin/realtime-time-column.tsx b/lib/components/narrative/line-itin/realtime-time-column.tsx index a9cf50fab..2e2c6e0c1 100644 --- a/lib/components/narrative/line-itin/realtime-time-column.tsx +++ b/lib/components/narrative/line-itin/realtime-time-column.tsx @@ -1,9 +1,8 @@ -import { connect } from 'react-redux' import { FormattedTime } from 'react-intl' -import { - getTimeZoneOffset, - isTransit -} from '@opentripplanner/core-utils/lib/itinerary' +// TYPESCRIPT TODO: wait for typescripted core-utils +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' import { Leg } from '@opentripplanner/types' import React, { ReactElement } from 'react' import styled from 'styled-components' From 2faec757718e590ab048871e2c398db973b1ad95 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 26 Apr 2022 22:31:07 -0500 Subject: [PATCH 0130/1425] refactor(create-otp-reducer): PR style fix --- lib/reducers/create-otp-reducer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 3e3cc0e40..5ce8763b9 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -144,14 +144,15 @@ export function getInitialState(userDefinedConfig) { queryModes = ensureSingleAccessMode(queryModes) } if (config?.modes?.combinations) { + const configModes = config.modes.combinations // Inject combinations from config for batch routing. // Filter options based on defaults if modeOptions is specified in config - const defaultModes = config.modes.modeOptions + const defaultModes = configModes ?.filter((m) => !m.defaultUnselected) .map((m) => m.mode) const enabledCombinations = config.modes.modeOptions - ? config.modes.combinations.filter(combinationFilter(defaultModes)) - : config.modes.combinations + ? configModes.filter(combinationFilter(defaultModes)) + : configModes currentQuery.combinations = clone(enabledCombinations) } From 6ed811861298144636e57d714e74a0eab6104379 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 07:20:08 -0700 Subject: [PATCH 0131/1425] refactor: remove unneeded comment --- lib/components/user/places/place-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/places/place-editor.tsx b/lib/components/user/places/place-editor.tsx index 4d67743a7..42206bea9 100644 --- a/lib/components/user/places/place-editor.tsx +++ b/lib/components/user/places/place-editor.tsx @@ -66,7 +66,7 @@ class PlaceEditor extends Component< } & WrappedComponentProps > { _handleLocationChange = ( - _: IntlShape, // Ignore intl object. TODO: localize the name field of the returned location object? + _: IntlShape, // Ignore intl object. { location }: { location: Location } ) => { const { setValues, values } = this.props From 314374d254f4e3746d93d21d263b36a8514f93af Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 07:31:28 -0700 Subject: [PATCH 0132/1425] refactor: address pr feedback --- lib/components/form/batch-settings.tsx | 15 ++++++--- lib/components/form/mode-buttons.tsx | 2 ++ lib/reducers/create-otp-reducer.js | 5 +-- lib/util/itinerary.js | 45 +++++++++++++++----------- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 8f88453d5..7966e256d 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -33,15 +33,22 @@ import type { Mode } from './mode-buttons' /** * A function that generates a filter to be used to filter a list of combinations. - * @param enabledModesMysteryType A list of the modes enabled in the UI + * + * TS FIXME: use the ModeOption type that is currently defined + * in @opentripplanner/trip-form/types.ts (That type + * needs to be moved first to @opentripplanner/types first, + * with the defaultUnselected attribute added). + * + * @param enabledModesDirty A list of the modes enabled in the UI * @returns Filter function to filter combinations */ export const combinationFilter = - (enabledModesMysteryType: string[] | { mode: string }[]) => + (enabledModesDirty: string[] | { mode: string }[]) => (c: Combination): boolean => { - // Ensure enabledModes is string array. This should be handled by typescript, + // TS FIXME: Ensure enabledModes is string array. This should be handled by typescript, // but typescript is not fully enabled yet. - const enabledModes = enabledModesMysteryType.map((mode) => { + // For now, we "clean" the list + const enabledModes = enabledModesDirty.map((mode) => { if (typeof mode === 'string') return mode if (mode.mode) return mode.mode }) diff --git a/lib/components/form/mode-buttons.tsx b/lib/components/form/mode-buttons.tsx index 7681f30a8..1d987c4e9 100644 --- a/lib/components/form/mode-buttons.tsx +++ b/lib/components/form/mode-buttons.tsx @@ -9,6 +9,8 @@ import Icon from '../util/icon' import { buttonCss } from './batch-styled' +// TS TODO: merge this type with FullModeOption from +// @opentripplanner/trip-form/types.ts and move to @opentripplanner/types. export type Mode = { defaultUnselected?: boolean icon?: string diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 5ce8763b9..70a3c322a 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -143,8 +143,9 @@ export function getInitialState(userDefinedConfig) { if (currentQuery.routingType === 'ITINERARY') { queryModes = ensureSingleAccessMode(queryModes) } - if (config?.modes?.combinations) { - const configModes = config.modes.combinations + + const configModes = config?.modes?.combinations + if (configModes) { // Inject combinations from config for batch routing. // Filter options based on defaults if modeOptions is specified in config const defaultModes = configModes diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index 15a651f79..5d286c4f8 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -1,22 +1,22 @@ import { latLngBounds } from 'leaflet' -import moment from 'moment' import coreUtils from '@opentripplanner/core-utils' +import moment from 'moment' import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' -export function getLeafletItineraryBounds (itinerary) { +export function getLeafletItineraryBounds(itinerary) { return latLngBounds(coreUtils.itinerary.getItineraryBounds(itinerary)) } /** * Return a leaflet LatLngBounds object that encloses the given leg's geometry. */ -export function getLeafletLegBounds (leg) { +export function getLeafletLegBounds(leg) { return latLngBounds(coreUtils.itinerary.getLegBounds(leg)) } -export function isBatchRoutingEnabled (otpConfig) { - return Boolean(otpConfig.routingTypes.find(t => t.key === 'BATCH')) +export function isBatchRoutingEnabled(otpConfig) { + return Boolean(otpConfig.routingTypes.find((t) => t.key === 'BATCH')) } /** @@ -25,7 +25,7 @@ export function isBatchRoutingEnabled (otpConfig) { * and none of the legs is a rental or ride hail leg (e.g. CAR_RENT, CAR_HAIL, BICYCLE_RENT, etc.). * (We use the corresponding fields returned by OTP to get transit legs and rental/ride hail legs.) */ -export function itineraryCanBeMonitored (itinerary) { +export function itineraryCanBeMonitored(itinerary) { let hasTransit = false let hasRentalOrRideHail = false @@ -34,10 +34,12 @@ export function itineraryCanBeMonitored (itinerary) { if (leg.transitLeg) { hasTransit = true } - if (leg.rentedBike || - leg.rentedCar || - leg.rentedVehicle || - leg.hailedCar) { + if ( + leg.rentedBike || + leg.rentedCar || + leg.rentedVehicle || + leg.hailedCar + ) { hasRentalOrRideHail = true } } @@ -50,19 +52,20 @@ export function itineraryCanBeMonitored (itinerary) { * Get the first stop ID from the itinerary in the underscore format required by * the startTransitStopId query param (e.g., TRIMET_12345 instead of TRIMET:12345). */ -export function getFirstStopId (itinerary) { +export function getFirstStopId(itinerary) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define return getFirstTransitLeg(itinerary)?.from.stopId.replace(':', '_') } -export function getMinutesUntilItineraryStart (itinerary) { +export function getMinutesUntilItineraryStart(itinerary) { return moment(itinerary.startTime).diff(moment(), 'minutes') } /** * Gets the first transit leg of the given itinerary, or null if none found. */ -function getFirstTransitLeg (itinerary) { - return itinerary?.legs?.find(leg => leg.transitLeg) +function getFirstTransitLeg(itinerary) { + return itinerary?.legs?.find((leg) => leg.transitLeg) } /** @@ -74,14 +77,20 @@ function getFirstTransitLeg (itinerary) { * (*) For transit itineraries, the first transit leg is used to make * the determination. Otherwise, the itinerary startTime is used. */ -export function getItineraryDefaultMonitoredDays (itinerary) { +export function getItineraryDefaultMonitoredDays(itinerary) { const firstTransitLeg = getFirstTransitLeg(itinerary) const startMoment = firstTransitLeg ? moment(firstTransitLeg.serviceDate, 'YYYYMMDD') : moment(itinerary.startTime) const dayOfWeek = startMoment.day() - return (dayOfWeek === 0 || dayOfWeek === 6) - ? WEEKEND_DAYS - : WEEKDAYS + return dayOfWeek === 0 || dayOfWeek === 6 ? WEEKEND_DAYS : WEEKDAYS +} + +/** + * Method iterates through all configured modes and returns only those + * enabled by default + */ +export function getDefaultModes(configModes) { + return configModes?.filter((m) => !m.defaultUnselected).map((m) => m.mode) } From 7849389d5dd4f08eb865ec967106da112000f906 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 07:32:52 -0700 Subject: [PATCH 0133/1425] refactor: use new util method --- lib/reducers/create-otp-reducer.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 70a3c322a..aa0855247 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -6,8 +6,8 @@ import update from 'immutability-helper' import { combinationFilter } from '../components/form/batch-settings' import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE } from '../util/constants' +import { getDefaultModes, isBatchRoutingEnabled } from '../util/itinerary' import { getTimestamp } from '../util/state' -import { isBatchRoutingEnabled } from '../util/itinerary' import { MainPanelContent, MobileScreens } from '../actions/ui' const { getTransitModes, isTransit } = coreUtils.itinerary @@ -148,9 +148,7 @@ export function getInitialState(userDefinedConfig) { if (configModes) { // Inject combinations from config for batch routing. // Filter options based on defaults if modeOptions is specified in config - const defaultModes = configModes - ?.filter((m) => !m.defaultUnselected) - .map((m) => m.mode) + const defaultModes = getDefaultModes(configModes) const enabledCombinations = config.modes.modeOptions ? configModes.filter(combinationFilter(defaultModes)) : configModes From 647708e50a79e9185017b051388ff377b214dd83 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 11:30:35 -0700 Subject: [PATCH 0134/1425] refactor: resolve merge conflict --- lib/reducers/create-otp-reducer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 35685d09c..2474c731e 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -148,9 +148,10 @@ export function getInitialState(userDefinedConfig) { if (configModes) { // Inject combinations from config for batch routing. // Filter options based on defaults if modeOptions is specified in config - const defaultModes = getDefaultModes(configModes) const enabledCombinations = config.modes.modeOptions - ? configModes.filter(combinationFilter(defaultModes)) + ? configModes.filter( + combinationFilter(getDefaultModes(config.modes.modeOptions)) + ) : configModes currentQuery.combinations = clone(enabledCombinations) From 6ffad50f66e885e0f7db863d42cfe857bc14291b Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 11:33:29 -0700 Subject: [PATCH 0135/1425] refactor: address pr feedback --- lib/components/form/batch-settings.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 7966e256d..b2eba0449 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -10,6 +10,7 @@ import styled from 'styled-components' import * as apiActions from '../../actions/api' import * as formActions from '../../actions/form' +import { getDefaultModes } from '../../util/itinerary' import { hasValidLocation } from '../../util/state' import Icon from '../util/icon' @@ -114,9 +115,7 @@ class BatchSettings extends Component<{ }> { state = { expanded: null, - selectedModes: this.props.modeOptions - .filter((m) => !m.defaultUnselected) - .map((m) => m.mode) + selectedModes: getDefaultModes(this.props.modeOptions) } _onClickMode = (mode: string) => { From 820468c62705513a5cab0b9e635fd0ab50b1d54c Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 27 Apr 2022 14:17:36 -0700 Subject: [PATCH 0136/1425] refactor: address pr feedback --- lib/components/form/batch-settings.tsx | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index b2eba0449..0e773836f 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -99,12 +99,8 @@ const ModeButtonsCompressed = styled(ModeButtons)` border-radius: 0px 5px 5px 0px; } ` - -/** - * Main panel for the batch/trip comparison form. - */ // TYPESCRIPT TODO: better types -class BatchSettings extends Component<{ +type Props = { config: any currentQuery: any intl: IntlShape @@ -112,10 +108,23 @@ class BatchSettings extends Component<{ possibleCombinations: Combination[] routingQuery: any setQueryParam: (queryParam: any) => void -}> { - state = { - expanded: null, - selectedModes: getDefaultModes(this.props.modeOptions) +} + +type State = { + expanded?: string | null + selectedModes: string[] +} + +/** + * Main panel for the batch/trip comparison form. + */ +class BatchSettings extends Component { + constructor(props: Props) { + super(props) + this.state = { + expanded: null, + selectedModes: getDefaultModes(props.modeOptions) || [] + } } _onClickMode = (mode: string) => { From 0e22c7878a07624d1eee09d878b42f8795dc6892 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 28 Apr 2022 00:08:13 -0500 Subject: [PATCH 0137/1425] refactor(mode-settings): ensure combinationFilter only receives strings --- lib/components/form/batch-preferences.tsx | 3 ++- lib/components/form/batch-settings.tsx | 10 +--------- lib/util/itinerary.js | 7 +++++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/components/form/batch-preferences.tsx b/lib/components/form/batch-preferences.tsx index e408fd23b..2b56c1ce2 100644 --- a/lib/components/form/batch-preferences.tsx +++ b/lib/components/form/batch-preferences.tsx @@ -46,7 +46,8 @@ class BatchPreferences extends Component<{ onQueryParamChange = (newQueryParams: any) => { const { config, modeOptions, query, setQueryParam } = this.props const enabledModes = - query.enabledModes || modeOptions.filter((m) => !m.defaultUnselected) + query.enabledModes || + modeOptions.filter((m) => !m.defaultUnselected).map((m) => m.mode) const combinations = config.modes.combinations .filter(combinationFilter(enabledModes)) .map(replaceTransitMode(newQueryParams.mode)) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 0e773836f..623ed5aaa 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -44,16 +44,8 @@ import type { Mode } from './mode-buttons' * @returns Filter function to filter combinations */ export const combinationFilter = - (enabledModesDirty: string[] | { mode: string }[]) => + (enabledModes: string[]) => (c: Combination): boolean => { - // TS FIXME: Ensure enabledModes is string array. This should be handled by typescript, - // but typescript is not fully enabled yet. - // For now, we "clean" the list - const enabledModes = enabledModesDirty.map((mode) => { - if (typeof mode === 'string') return mode - if (mode.mode) return mode.mode - }) - if (c.requiredModes) { return c.requiredModes.every((m) => enabledModes.includes(m)) } else { diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index c6a886df0..84eeee707 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -90,8 +90,11 @@ export function getItineraryDefaultMonitoredDays(itinerary) { * Method iterates through all configured modes and returns only those * enabled by default */ -export function getDefaultModes(configModes) { - return configModes?.filter((m) => !m.defaultUnselected).map((m) => m.mode) +export function getDefaultModes(configModeOptions) { + return ( + configModeOptions?.filter((m) => !m.defaultUnselected).map((m) => m.mode) || + [] + ) } function legLocationsAreEqual(legLocation, other) { From bbf8b14e88f2c9064552f0fa20b56085cccf04f7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:31:52 -0400 Subject: [PATCH 0138/1425] fix(deps): Upgrade from-to-picker and overlays to support i18n. --- i18n/en-US.yml | 2 -- i18n/fr.yml | 2 -- lib/components/map/enhanced-stop-marker.js | 4 +-- lib/components/map/point-popup.tsx | 2 +- lib/components/viewers/stop-viewer.js | 4 +-- package.json | 6 ++-- yarn.lock | 37 ++++++++++++++-------- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 5a3464f41..9e10186bc 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -177,7 +177,6 @@ components: DeleteUser: deleteMyAccount: Delete my account EnhancedStopMarker: - planATrip: "Plan a trip:" stopID: "Stop ID:" stopViewer: Stop Viewer ErrorMessage: @@ -436,7 +435,6 @@ components: header: Stop Viewer loadingText: Loading Stop... noStopsFound: No stop times found for date. - planTrip: "Plan a trip:" timezoneWarning: "Departure times are shown in {timezoneCode}." viewTypeBtnText: > {scheduleView, select, diff --git a/i18n/fr.yml b/i18n/fr.yml index 35e9a28d0..b32edc826 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -165,7 +165,6 @@ components: DeleteUser: deleteMyAccount: Supprimer mon compte EnhancedStopMarker: - planATrip: "Planifier un trajet :" stopID: "Arrêt nº" stopViewer: Info arrêt ErrorMessage: @@ -420,7 +419,6 @@ components: header: Info arrêt loadingText: Chargement de l'arrêt... noStopsFound: Aucun horaire pour la date choisie. - planTrip: "Faire un trajet :" timezoneWarning: "Les horaires sont affichés dans le fuseau {timezoneCode}." viewTypeBtnText: > {scheduleView, select, diff --git a/lib/components/map/enhanced-stop-marker.js b/lib/components/map/enhanced-stop-marker.js index f321561a7..0ba0b6f3c 100644 --- a/lib/components/map/enhanced-stop-marker.js +++ b/lib/components/map/enhanced-stop-marker.js @@ -161,10 +161,8 @@ class EnhancedStopMarker extends Component { {/* The 'Set as [from/to]' ButtonGroup */} - - - diff --git a/lib/components/map/point-popup.tsx b/lib/components/map/point-popup.tsx index 879bfd898..66cb9c0fc 100644 --- a/lib/components/map/point-popup.tsx +++ b/lib/components/map/point-popup.tsx @@ -56,8 +56,8 @@ function MapPopup({ : popupName}
- Plan a trip: diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 254178716..62c0be11c 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -255,10 +255,8 @@ class StopViewer extends Component { /> )}
- - - diff --git a/package.json b/package.json index bfd5e6466..ebe5b9e34 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^2.0.0", "@opentripplanner/core-utils": "^4.10.0", - "@opentripplanner/endpoints-overlay": "^1.3.0", - "@opentripplanner/from-to-location-picker": "^1.3.0", + "@opentripplanner/endpoints-overlay": "^1.4.0", + "@opentripplanner/from-to-location-picker": "^2.1.0", "@opentripplanner/geocoder": "^1.2.1", "@opentripplanner/humanize-distance": "^1.1.0", "@opentripplanner/icons": "^1.2.0", @@ -54,7 +54,7 @@ "@opentripplanner/trip-details": "^2.0.0", "@opentripplanner/trip-form": "^1.10.1", "@opentripplanner/trip-viewer-overlay": "^1.1.1", - "@opentripplanner/vehicle-rental-overlay": "^1.2.1", + "@opentripplanner/vehicle-rental-overlay": "^1.4.0", "blob-stream": "^0.1.3", "bootstrap": "^3.3.7", "bowser": "^1.9.3", diff --git a/yarn.lock b/yarn.lock index 184f6d093..e569ec902 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2500,15 +2500,15 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/endpoints-overlay@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.3.0.tgz#cda25f81963d28a0580d4737f82798de2a143904" - integrity sha512-mvpNyJFMxE2GUcgyPQAo3kEOyySmY2TQHvD2taHi0DWTsIvErZ40YwngzqSZYNAavymkWa5Ue9oC1zdoWNfgCA== +"@opentripplanner/endpoints-overlay@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.4.0.tgz#00e6a94f1c39364eef6877916ff39dc135dce629" + integrity sha512-c3nJg+Pp6c/Kvu/pWsDWclBgZL/Nr1ZYho9astmF2xZ5c7ihMHs02DojmbJtlULz5ogVy64Mf/wWGXCcxwEYXg== dependencies: - "@opentripplanner/core-utils" "^4.1.0" - "@opentripplanner/location-icon" "^1.3.0" + "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" - prop-types "^15.7.2" + flat "^5.0.2" "@opentripplanner/from-to-location-picker@^1.2.1", "@opentripplanner/from-to-location-picker@^1.3.0": version "1.3.0" @@ -2519,6 +2519,14 @@ "@opentripplanner/location-icon" "^1.3.0" prop-types "^15.7.2" +"@opentripplanner/from-to-location-picker@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/from-to-location-picker/-/from-to-location-picker-2.1.0.tgz#cca855bf1fc711586e5d70733939056667f822ae" + integrity sha512-5x4HliDNCyOAiVQUcF2GRBQ0pVHu8ER6iMXqAyl8ncstVWxC2kmLmDTfHSuMtfDiLDGbRAUwk2Kz749up9ZQPg== + dependencies: + "@opentripplanner/location-icon" "^1.4.0" + flat "^5.0.2" + "@opentripplanner/geocoder@^1.1.1", "@opentripplanner/geocoder@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@opentripplanner/geocoder/-/geocoder-1.1.2.tgz#fb4c939f5746705d0bebd9791b3f9d9eb055ce8e" @@ -2708,15 +2716,16 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-1.2.0.tgz#1d9163ddb5ddc27b9ced52a6e1fe2941c195d5e9" integrity sha512-kURxBFXV8z4pApducBwTHxKaAApdLJMewbkQ3cD9ykMt64Jw2TIHLK3D8d8okd2rAMykvZ9o0CPztfaLBwJrJQ== -"@opentripplanner/vehicle-rental-overlay@^1.2.1": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.3.0.tgz#a77c3e040faf59091fdb3c8f9fb0ad46e1af8532" - integrity sha512-PbFyDFweag8GvTXUuyEBpff3g9E6/8KC1b2LxR8T0f2yoqynIqUEYitJKCVvXeQroQ5s0V6JxRr+kWU0vwNPyg== +"@opentripplanner/vehicle-rental-overlay@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.4.0.tgz#c02e386e4dbe24730d4876058b268a68cb21bca9" + integrity sha512-mAup3b1UeX50RvriWewn0OOHU47gSFA5GbdfTWbvIx2d3ziFj9ZIOJu5aYbhy8Yz2HyUKMYLjBSN/atGZHy2Bg== dependencies: - "@opentripplanner/core-utils" "^4.1.0" - "@opentripplanner/from-to-location-picker" "^1.2.1" - "@opentripplanner/zoom-based-markers" "^1.2.0" + "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/from-to-location-picker" "^2.1.0" + "@opentripplanner/zoom-based-markers" "^1.2.1" "@styled-icons/fa-solid" "^10.34.0" + flat "^5.0.2" lodash.memoize "^4.1.2" prop-types "^15.7.2" From 4f4efce9912aa607f1c3f676c3046d0ef2919f2e Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 29 Apr 2022 15:28:47 -0700 Subject: [PATCH 0139/1425] refactor: first outlines of metro itinerary view --- .../narrative/default/default-itinerary.js | 48 +--- .../default/itinerary-description.tsx | 60 +++++ .../narrative/metro/metro-itinerary.tsx | 245 ++++++++++++++++++ .../narrative-itineraries-header.tsx | 84 +++--- .../narrative/narrative-itineraries.js | 81 ++++-- lib/index.js | 2 + 6 files changed, 424 insertions(+), 96 deletions(-) create mode 100644 lib/components/narrative/default/itinerary-description.tsx create mode 100644 lib/components/narrative/metro/metro-itinerary.tsx diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 14c7d5f26..2cc6cb9a6 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -19,7 +19,6 @@ import NarrativeItinerary from '../narrative-itinerary' import ItineraryBody from '../line-itin/connected-itinerary-body' import SimpleRealtimeAnnotation from '../simple-realtime-annotation' import FormattedDuration from '../../util/formatted-duration' -import FormattedMode from '../../util/formatted-mode' import { getTotalFare } from '../../../util/state' import { getAccessibilityScoreForItinerary, @@ -29,8 +28,8 @@ import Icon from '../../util/icon' import { FlexIndicator } from './flex-indicator' import ItinerarySummary from './itinerary-summary' +import { getMainItineraryModes, ItineraryDescription } from './itinerary-description' -const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff } = coreUtils.itinerary // Styled components const LegIconWrapper = styled.div` @@ -64,38 +63,6 @@ const ItinerarySummaryWrapper = styled.div` justify-content: space-between; ` -/** - * Obtains the description of an itinerary in the given locale. - */ -function ItineraryDescription({ itinerary }) { - let primaryTransitDuration = 0 - let accessModeId = 'walk' - let transitMode - let primaryRoute - itinerary.legs.forEach((leg, i) => { - const { duration, mode, rentedBike, rentedVehicle, routeShortName } = leg - if (isTransit(mode) && duration > primaryTransitDuration) { - // TODO: convert OTP's TRAM mode to the correct wording for Portland - primaryTransitDuration = duration - transitMode = - primaryRoute = routeShortName - } - if (isBicycle(mode)) accessModeId = 'bicycle' - if (isMicromobility(mode)) accessModeId = 'micromobility' - if (rentedVehicle) accessModeId = 'micromobility_rent' - if (rentedBike) accessModeId = 'bicycle_rent' - if (mode === 'CAR') accessModeId = 'drive' - }) - - const mainMode = - return transitMode - ? - : mainMode -} - const ITINERARY_ATTRIBUTES = [ { alias: 'best', @@ -142,7 +109,7 @@ const ITINERARY_ATTRIBUTES = [ currency={fareCurrency || options.currency} currencyDisplay='narrowSymbol' style='currency' - value={fare} + value={fare} /> ) } @@ -225,17 +192,18 @@ class DefaultItinerary extends NarrativeItinerary { let phone = `contact ${agency}` if (isCallAhead) { - // Picking 0 ensures that if multiple flex legs with - // different phone numbers, the first leg is prioritized + // Picking 0 ensures that if multiple flex legs with + // different phone numbers, the first leg is prioritized phone = itinerary.legs .map((leg) => leg.pickupBookingInfo?.contactInfo?.phoneNumber) .filter((number) => !!number)[0] } + + const { mainMode, transitMode } = getMainItineraryModes(itinerary) return (
{ + const { duration, mode, rentedBike, rentedVehicle } = leg + if (isTransit(mode) && duration > primaryTransitDuration) { + // TODO: convert OTP's TRAM mode to the correct wording for Portland + primaryTransitDuration = duration + transitMode = ( + + ) + } + if (isBicycle(mode)) accessModeId = 'bicycle' + if (isMicromobility(mode)) accessModeId = 'micromobility' + if (rentedVehicle) accessModeId = 'micromobility_rent' + if (rentedBike) accessModeId = 'bicycle_rent' + if (mode === 'CAR') accessModeId = 'drive' + }) + + return { mainMode: , transitMode } +} + +export function ItineraryDescription(props: Props) { + const { mainMode, transitMode } = getMainItineraryModes(props) + + return transitMode ? ( + + ) : ( + mainMode + ) +} diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx new file mode 100644 index 000000000..812aee1c1 --- /dev/null +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -0,0 +1,245 @@ +// This is a large file being touched by open PRs. It should be typescripted +// in a separate PR. +/* eslint-disable */ +// @ts-expect-error not typescripted yet +import coreUtils from '@opentripplanner/core-utils' +import React from 'react' +import { + FormattedList, + FormattedMessage, + FormattedNumber, + FormattedTime, + injectIntl, + IntlShape +} from 'react-intl' +import { connect } from 'react-redux' +import styled from 'styled-components' +// @ts-expect-error not typescripted yet +import { AccessibilityRating } from '@opentripplanner/itinerary-body' + +import * as uiActions from '../../../actions/ui' +import NarrativeItinerary from '../narrative-itinerary' +import ItineraryBody from '../line-itin/connected-itinerary-body' +import SimpleRealtimeAnnotation from '../simple-realtime-annotation' +import FormattedDuration from '../../util/formatted-duration' +import FormattedMode from '../../util/formatted-mode' +import { getTotalFare } from '../../../util/state' +import { + getAccessibilityScoreForItinerary, + itineraryHasAccessibilityScores +} from '../../../util/accessibility-routing' +import Icon from '../../util/icon' + +import { FlexIndicator } from '../default/flex-indicator' +import ItinerarySummary from '../default/itinerary-summary' +import { Itinerary, Leg } from '@opentripplanner/types' + +const { ItineraryView } = uiActions +const { isBicycle, isMicromobility, isTransit, isFlex, isOptional, isContinuousDropoff } = coreUtils.itinerary + +// Styled components +const LegIconWrapper = styled.div` + display: inline-block; + height: 20px; + padding-bottom: 6px; + padding-left: 2px; + width: 20px; + + /* Equivalent of a single space before the leg icon. */ + &::before { + content: ""; + margin: 0 0.125em; + } + + &::before { + content: ""; + margin: 0 0.125em; + } +` + +const DetailsHint = styled.div` + clear: both; + color: #685c5c; + font-size: small; + text-align: center; +` + +const ItineraryWrapper = styled.div` + display: flex; + justify-content: space-between; + background: #fffffffb; + color: #333; + padding: 1em; + +` + + + +type Props = { + accessibilityScoreGradationMap: { [value: number]: string } + active: boolean, + expanded: boolean, + itinerary: Itinerary, + intl: IntlShape, + LegIcon: React.ReactNode, + setActiveLeg: (leg: Leg) => void, + setItineraryView: (view: string) => void, + showRealtimeAnnotation: () => void, + timeFormat: any // TODO +} + +class MetroItinerary extends NarrativeItinerary { + _onMouseEnter = () => { + const { active, index, setVisibleItinerary, visibleItinerary } = this.props + // Set this itinerary as visible if not already visible. + const visibleNotSet = + visibleItinerary === null || visibleItinerary === undefined + const isVisible = + visibleItinerary === index || (active === index && visibleNotSet) + if (typeof setVisibleItinerary === 'function' && !isVisible) { + setVisibleItinerary({ index }) + } + } + + _onMouseLeave = () => { + const { index, setVisibleItinerary, visibleItinerary } = this.props + if (typeof setVisibleItinerary === 'function' && visibleItinerary === index) { + setVisibleItinerary({ index: null }) + } + } + + render() { + const { + accessibilityScoreGradationMap, + active, + configCosts, + currency, + defaultFareKey, + expanded, + itinerary, + LegIcon, + setActiveLeg, + setItineraryView, + showRealtimeAnnotation, + timeFormat + } = this.props + const timeOptions = { + format: timeFormat, + offset: coreUtils.itinerary.getTimeZoneOffset(itinerary) + } + const isFlexItinerary = itinerary.legs.some(coreUtils.itinerary.isFlex) + const isCallAhead = itinerary.legs.some(coreUtils.itinerary.isReservationRequired) + const isContinuousDropoff = itinerary.legs.some(coreUtils.itinerary.isContinuousDropoff) + + // Use first leg's agency as a fallback + let phone = itinerary.legs.map((leg: Leg) => leg?.agencyName).filter((name: string) => !!name)[0] + + if (isCallAhead) { + // Picking 0 ensures that if multiple flex legs with + // different phone numbers, the first leg is prioritized + phone = itinerary.legs + .map((leg: Leg) => leg.pickupBookingInfo?.contactInfo?.phoneNumber) + .filter((number: string) => !!number)[0] + } + return ( +
+ + {active && expanded && ( + <> + {showRealtimeAnnotation && } + + + )} +
+ ) + } +} + +// TODO: state type +const mapStateToProps = (state: any, ownProps: Props) => { + const { intl } = ownProps + const gradationMap = state.otp.config.accessibilityScore?.gradationMap + + // Generate icons based on fa icon keys in config + // Override text fields if translation set + gradationMap && + Object.keys(gradationMap).forEach((key) => { + const { icon } = gradationMap[key] + if (icon && typeof icon === 'string') { + gradationMap[key].icon = + } + + // As these localization keys are in the config, rather than + // standard language files, the message ids must be dynamically generated + const localizationId = `config.acessibilityScore.gradationMap.${key}` + const localizedText = intl.formatMessage({ id: localizationId }) + // Override the config label if a localized label exists + if (localizationId !== localizedText) { + gradationMap[key].text = localizedText + } + }) + + return { + accessibilityScoreGradationMap: gradationMap, + configCosts: state.otp.config.itinerary?.costs, + // The configured (ambient) currency is needed for rendering the cost + // of itineraries whether they include a fare or not, in which case + // we show $0.00 or its equivalent in the configured currency and selected locale. + currency: state.otp.config.localization?.currency || 'USD', + defaultFareKey: state.otp.config.itinerary?.defaultFareKey + } +} + +// TS TODO: correct redux types +const mapDispatchToProps = (dispatch: any) => { + return { + setItineraryView: (payload: any) => + dispatch(uiActions.setItineraryView(payload)), + } +} + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MetroItinerary)) diff --git a/lib/components/narrative/narrative-itineraries-header.tsx b/lib/components/narrative/narrative-itineraries-header.tsx index f98f7cc65..60cbbc907 100644 --- a/lib/components/narrative/narrative-itineraries-header.tsx +++ b/lib/components/narrative/narrative-itineraries-header.tsx @@ -17,6 +17,7 @@ const IssueButton = styled.button` ` export default function NarrativeItinerariesHeader({ + customBatchUiBackground, errors, itineraries, itineraryIsExpanded, @@ -25,9 +26,11 @@ export default function NarrativeItinerariesHeader({ onToggleShowErrors, onViewAllOptions, pending, + showHeaderText = true, showingErrors, sort }: { + customBatchUiBackground?: boolean errors: unknown[] itineraries: unknown[] itineraryIsExpanded: boolean @@ -36,6 +39,7 @@ export default function NarrativeItinerariesHeader({ onToggleShowErrors: () => void onViewAllOptions: () => void pending: boolean + showHeaderText: boolean showingErrors: boolean sort: { direction: string; type: string } }): JSX.Element { @@ -70,45 +74,61 @@ export default function NarrativeItinerariesHeader({ ) : ( <> -
- - - - {errors.length > 0 && ( - - - - + + + + {errors.length > 0 && ( + + - - - )} -
-
+ + + + + )} +
+ )} +
+ ({travelDateAsMoment.fromNow()})
@@ -134,6 +151,7 @@ class FieldTripDetails extends Component { deleteFieldTripNote, intl, request, + setRequestDate, setRequestGroupSize, setRequestPaymentInfo, style @@ -256,6 +274,7 @@ const mapDispatchToProps = { deleteFieldTripNote: fieldTripActions.deleteFieldTripNote, editSubmitterNotes: fieldTripActions.editSubmitterNotes, setActiveFieldTrip: fieldTripActions.setActiveFieldTrip, + setRequestDate: fieldTripActions.setRequestDate, setRequestGroupSize: fieldTripActions.setRequestGroupSize, setRequestPaymentInfo: fieldTripActions.setRequestPaymentInfo, setRequestStatus: fieldTripActions.setRequestStatus, From f653ce7da5f33c4b0451012c1ff6005e59ab48ca Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:17:27 -0400 Subject: [PATCH 0271/1425] refactor(actions/field-trip): Combine common ft request post code. --- i18n/en-US.yml | 8 +- i18n/fr.yml | 8 +- lib/actions/field-trip.js | 146 ++++++++------------- lib/components/admin/field-trip-details.js | 1 - 4 files changed, 63 insertions(+), 100 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index f4834a3c2..d1f4cc8ae 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -48,10 +48,10 @@ actions: itineraryCapacityError: "Cannot Save Plan: This plan could not be saved due to a lack of capacity on one or more vehicles. Please re-plan your trip." maxTripRequestsExceeded: Number of trip requests exceeded without valid results saveItinerariesError: "Failed to save itineraries: {err}" - setDateError: "Error setting date: {err}" - setGroupSizeError: "Error setting group size: {err}" - setPaymentError: "Error setting payment info: {err}" - setRequestStatusError: "Error setting request status: {err}" + setDateError: "Error setting date:" + setGroupSizeError: "Error setting group size:" + setPaymentError: "Error setting payment info:" + setRequestStatusError: "Error setting request status:" location: geolocationNotSupportedError: Geolocation not supported by your browser unknownPositionError: Unknown error getting position diff --git a/i18n/fr.yml b/i18n/fr.yml index ad874f5ec..412d411cb 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -37,10 +37,10 @@ actions: itineraryCapacityError: "Impossible d'enregistrer les itinéraires : Capacité insuffisante dans un ou plusieurs véhicules. Veuillez relancer votre recherche." maxTripRequestsExceeded: Le nombre de requêtes sans résultats valables a été dépassé. saveItinerariesError: "Erreur lors de l'enregistrement des itinéraires : {err}" - setDateError: "Erreur sur la date : {err}" - setGroupSizeError: "Erreur sur la taille du groupe : {err}" - setPaymentError: "Erreur sur les coordonnées de paiement : {err}" - setRequestStatusError: "Erreur sur l'état de la requête : {err}" + setDateError: "Erreur sur la date :" + setGroupSizeError: "Erreur sur la taille du groupe :" + setPaymentError: "Erreur sur les coordonnées de paiement :" + setRequestStatusError: "Erreur sur l'état de la requête :" location: geolocationNotSupportedError: La géolocalisation n'est pas prise en charge par votre navigateur. unknownPositionError: Erreur non-gérée lors de la détection de votre emplacement. diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 8b0bf5806..23e807f82 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -1,4 +1,3 @@ -/* eslint-disable no-restricted-globals */ /* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable prettier/prettier */ /* eslint-disable sort-imports-es6-autofix/sort-imports-es6 */ @@ -324,7 +323,7 @@ function getEarliestStartTime (itineraries) { function overwriteExistingRequestTripsConfirmed (request, outbound, intl) { const preExistingTrip = getTripFromRequest(request, outbound) if (preExistingTrip) { - return confirm( + return window.confirm( intl.formatMessage( { id: 'actions.fieldTrip.confirmOverwriteItineraries' }, { outbound } @@ -890,121 +889,86 @@ export function viewRequestTripItineraries (request, outbound) { } /** - * Set group size for a field trip request. Group size consists of numStudents, - * numFreeStudents, and numChaperones. + * Updates a particular field/set of fields of a given field trip request. */ -export function setRequestGroupSize (request, groupSize, intl) { +export function updateFieldTripRequest(request, endpoint, updatedFields, intl, getErrorMessage) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - const groupSizeData = serialize({ - ...groupSize, + const { sessionId } = callTaker.session + const body = serialize({ requestId: request.id, - sessionId + sessionId, + ...updatedFields + }) + return fetch(`${datastoreUrl}/fieldtrip/${endpoint}`, { + body, method: 'POST' }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestGroupSize`, - {body: groupSizeData, method: 'POST'} - ) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.setGroupSizeError' }, - { err: JSON.stringify(err) } - ) - ) - }) + .catch((err) => alert(`${getErrorMessage()} ${JSON.stringify(err)}`)) } } +/** + * Set group size for a field trip request. Group size consists of numStudents, + * numFreeStudents, and numChaperones. + */ +export function setRequestGroupSize(request, groupSize, intl) { + return updateFieldTripRequest( + request, + 'setRequestGroupSize', + groupSize, + intl, + () => intl.formatMessage({ + id: 'actions.fieldTrip.setGroupSizeError' + }) + ) +} + /** * Sets the travel date for the requested field trip. */ export function setRequestDate(request, date, intl) { - return function (dispatch, getState) { - const { callTaker, otp } = getState() - const { datastoreUrl } = otp.config - if (sessionIsInvalid(callTaker.session)) return - const { sessionId } = callTaker.session - const requestData = serialize({ - date, - requestId: request.id, - sessionId - }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestDate`, { - body: requestData, - method: 'POST' + return updateFieldTripRequest( + request, + 'setRequestDate', + { date }, + intl, + () => intl.formatMessage({ + id: 'actions.fieldTrip.setDateError' }) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch((err) => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.setDateError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } + ) } /** * Set payment info for a field trip request. */ -export function setRequestPaymentInfo (request, paymentInfo, intl) { - return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config - if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - const paymentData = serialize({ - ...paymentInfo, - requestId: request.id, - sessionId +export function setRequestPaymentInfo(request, paymentInfo, intl) { + return updateFieldTripRequest( + request, + 'setRequestPaymentInfo', + paymentInfo, + intl, + () => intl.formatMessage({ + id: 'actions.fieldTrip.setPaymentError' }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestPaymentInfo`, - {body: paymentData, method: 'POST'} - ) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.setPaymentError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } + ) } /** * Set field trip request status (e.g., cancelled). */ -export function setRequestStatus (request, status, intl) { - return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config - if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - const statusData = serialize({ - requestId: request.id, - sessionId, - status +export function setRequestStatus(request, status, intl) { + return updateFieldTripRequest( + request, + 'setRequestStatus', + { status }, + intl, + () => intl.formatMessage({ + id: 'actions.fieldTrip.setRequestStatusError' }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestStatus`, - {body: statusData, method: 'POST'} - ) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.setRequestStatusError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } + ) } /** diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index a6de1db11..8b1f30ac1 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -151,7 +151,6 @@ class FieldTripDetails extends Component { deleteFieldTripNote, intl, request, - setRequestDate, setRequestGroupSize, setRequestPaymentInfo, style From 91f4adb32b3d2363093d97caee994acbc199d780 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:38:56 -0400 Subject: [PATCH 0272/1425] refactor(actions/field-trip): Reuse method to update ft fields for notes. --- lib/actions/field-trip.js | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 23e807f82..c39c5eee8 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -178,30 +178,16 @@ export function deleteFieldTripNote (request, noteId, intl) { /** * Edit teacher (AKA submitter) notes for a field trip request. */ -export function editSubmitterNotes (request, submitterNotes, intl) { - return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config - if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - const noteData = serialize({ - notes: submitterNotes, - requestId: request.id, - sessionId +export function editSubmitterNotes(request, submitterNotes, intl) { + return updateFieldTripRequest( + request, + 'editSubmitterNotes', + { notes: submitterNotes }, + intl, + () => intl.formatMessage({ + id: 'actions.fieldTrip.editSubmitterNotesError' }) - return fetch(`${datastoreUrl}/fieldtrip/editSubmitterNotes`, - {body: noteData, method: 'POST'} - ) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.editSubmitterNotesError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } + ) } /** From 219d03a93dff99077319e0c57fa953858566b1e1 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:49:41 -0400 Subject: [PATCH 0273/1425] style(actions/field-trip): Fix lint errors. --- lib/actions/field-trip.js | 1016 +++++++++++++++++++------------------ 1 file changed, 522 insertions(+), 494 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index c39c5eee8..e9f7c37f7 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -1,21 +1,19 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ -/* eslint-disable prettier/prettier */ -/* eslint-disable sort-imports-es6-autofix/sort-imports-es6 */ -import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' +import { createAction } from 'redux-actions' import { getRoutingParams, planParamsToQueryAsync } from '@opentripplanner/core-utils/lib/query' +import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' import { OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time' import { randId } from '@opentripplanner/core-utils/lib/storage' -import moment from 'moment' import { serialize } from 'object-to-formdata' +import moment from 'moment' import qs from 'qs' -import { createAction } from 'redux-actions' +import { getActiveItineraries, getActiveSearch } from '../util/state' import { getGroupSize, getTripFromRequest, @@ -24,18 +22,17 @@ import { sessionIsInvalid } from '../util/call-taker' import { getModuleConfig, Modules } from '../util/config' -import { getActiveItineraries, getActiveSearch } from '../util/state' +import { clearActiveSearch, resetForm, setQueryParam } from './form' import { routingQuery, setActiveItineraries, setPendingRequests, updateOtpUrlParams } from './api' -import {toggleCallHistory} from './call-taker' -import {clearActiveSearch, resetForm, setQueryParam} from './form' +import { toggleCallHistory } from './call-taker' -if (typeof (fetch) === 'undefined') require('isomorphic-fetch') +if (typeof fetch === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -44,8 +41,9 @@ const receiveTripHash = createAction('RECEIVE_TRIP_HASH') const requestingFieldTrips = createAction('REQUESTING_FIELD_TRIPS') const receivedFieldTripDetails = createAction('RECEIVED_FIELD_TRIP_DETAILS') const requestingFieldTripDetails = createAction('REQUESTING_FIELD_TRIP_DETAILS') -const setActiveItinerariesFromFieldTrip = - createAction('SET_ACTIVE_ITINERARIES_FROM_FIELD_TRIP') +const setActiveItinerariesFromFieldTrip = createAction( + 'SET_ACTIVE_ITINERARIES_FROM_FIELD_TRIP' +) // PUBLIC ACTIONS export const receivedFieldTrips = createAction('RECEIVED_FIELD_TRIPS') @@ -61,10 +59,62 @@ const FIELD_TRIP_DATE_FORMAT = 'MM/DD/YYYY' const FIELD_TRIP_DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss' const FIELD_TRIP_TIME_FORMAT = 'HH:mm:ss' +/** + * Returns the earliest start time (in unix epoch milliseconds) in the given + * itineraries. + */ +function getEarliestStartTime(itineraries) { + return itineraries.reduce( + (earliestStartTime, itinerary) => + Math.min(earliestStartTime, itinerary.startTime), + Number.POSITIVE_INFINITY + ) +} + +/** + * Checks that a group plan is valid for a given request, i.e., that it occurs + * on the requested travel date. + * + * @param request field trip request + * @param itineraries the currently active itineraries + * @param {Object} intl A format.js intl object + * @return true if invalid + */ +function fieldTripPlanIsInvalid(request, itineraries, intl) { + if (!itineraries || itineraries.length === 0) { + return true + } + + const earliestStartTime = getEarliestStartTime(itineraries) + + // FIXME: add back in offset? + const planDeparture = moment(earliestStartTime) // .add('hours', otp.config.timeOffset) + const requestDate = moment(request.travelDate) + + if ( + planDeparture.date() !== requestDate.date() || + planDeparture.month() !== requestDate.month() || + planDeparture.year() !== requestDate.year() + ) { + alert( + intl.formatMessage( + { id: 'actions.fieldTrip.incompatibleTripDateError' }, + { + requestDate: requestDate.format(FIELD_TRIP_DATE_FORMAT), + tripDate: planDeparture.format(FIELD_TRIP_DATE_FORMAT) + } + ) + ) + return true + } + + return false +} + /** * Fully reset form and toggle field trips (and close call history if open). */ -export function resetAndToggleFieldTrips () { +export function resetAndToggleFieldTrips() { return async function (dispatch, getState) { dispatch(resetForm(true)) dispatch(toggleFieldTrips()) @@ -72,19 +122,45 @@ export function resetAndToggleFieldTrips () { } } +/** + * Returns true if the user confirms the overwrite of an existing set of + * itineraries planned for the inbound or outbound part of the field trip. + * + * @param {Object} request The field trip request + * @param {boolean} outbound If true, save the current itineraries to the + * outbound field trip journey. + * @param {Object} intl A format.js intl object + */ +function overwriteExistingRequestTripsConfirmed(request, outbound, intl) { + const preExistingTrip = getTripFromRequest(request, outbound) + if (preExistingTrip) { + return window.confirm( + intl.formatMessage( + { id: 'actions.fieldTrip.confirmOverwriteItineraries' }, + { outbound } + ) + ) + } + return true +} + /** * Fetch all field trip requests (as summaries). */ -export function fetchFieldTrips (intl) { +export function fetchFieldTrips(intl) { return async function (dispatch, getState) { dispatch(requestingFieldTrips()) - const {callTaker, otp} = getState() + const { callTaker, otp } = getState() if (sessionIsInvalid(callTaker.session)) return - const {datastoreUrl} = otp.config - const {sessionId} = callTaker.session + const { datastoreUrl } = otp.config + const { sessionId } = callTaker.session let fieldTrips = [] try { - const res = await fetch(`${datastoreUrl}/fieldtrip/getRequestsSummary?${qs.stringify({sessionId})}`) + const res = await fetch( + `${datastoreUrl}/fieldtrip/getRequestsSummary?${qs.stringify({ + sessionId + })}` + ) fieldTrips = await res.json() } catch (err) { alert( @@ -94,24 +170,29 @@ export function fetchFieldTrips (intl) { ) ) } - dispatch(receivedFieldTrips({fieldTrips})) + dispatch(receivedFieldTrips({ fieldTrips })) } } /** * Fetch details for a particular field trip request. */ -export function fetchFieldTripDetails (requestId, intl) { +export function fetchFieldTripDetails(requestId, intl) { return function (dispatch, getState) { dispatch(requestingFieldTripDetails()) - const {callTaker, otp} = getState() + const { callTaker, otp } = getState() if (sessionIsInvalid(callTaker.session)) return - const {datastoreUrl} = otp.config - const {sessionId} = callTaker.session - fetch(`${datastoreUrl}/fieldtrip/getRequest?${qs.stringify({requestId, sessionId})}`) - .then(res => res.json()) - .then(fieldTrip => dispatch(receivedFieldTripDetails({fieldTrip}))) - .catch(err => { + const { datastoreUrl } = otp.config + const { sessionId } = callTaker.session + fetch( + `${datastoreUrl}/fieldtrip/getRequest?${qs.stringify({ + requestId, + sessionId + })}` + ) + .then((res) => res.json()) + .then((fieldTrip) => dispatch(receivedFieldTripDetails({ fieldTrip }))) + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.fetchFieldTripError' }, @@ -125,22 +206,23 @@ export function fetchFieldTripDetails (requestId, intl) { /** * Add note for field trip request. */ -export function addFieldTripNote (request, note, intl) { +export function addFieldTripNote(request, note, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId, username} = callTaker.session + const { sessionId, username } = callTaker.session const noteData = serialize({ - note: {...note, userName: username}, + note: { ...note, userName: username }, requestId: request.id, sessionId }) - return fetch(`${datastoreUrl}/fieldtrip/addNote`, - {body: noteData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/addNote`, { + body: noteData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.addNoteError' }, @@ -154,17 +236,18 @@ export function addFieldTripNote (request, note, intl) { /** * Delete a specific note for a field trip request. */ -export function deleteFieldTripNote (request, noteId, intl) { +export function deleteFieldTripNote(request, noteId, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteNote`, - {body: serialize({ noteId, sessionId }), method: 'POST'} - ) + const { sessionId } = callTaker.session + return fetch(`${datastoreUrl}/fieldtrip/deleteNote`, { + body: serialize({ noteId, sessionId }), + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.deleteNoteError' }, @@ -175,6 +258,35 @@ export function deleteFieldTripNote (request, noteId, intl) { } } +/** + * Updates a particular field/set of fields of a given field trip request. + */ +export function updateFieldTripRequest( + request, + endpoint, + updatedFields, + intl, + getErrorMessage +) { + return function (dispatch, getState) { + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config + if (sessionIsInvalid(callTaker.session)) return + const { sessionId } = callTaker.session + const body = serialize({ + requestId: request.id, + sessionId, + ...updatedFields + }) + return fetch(`${datastoreUrl}/fieldtrip/${endpoint}`, { + body, + method: 'POST' + }) + .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) + .catch((err) => alert(`${getErrorMessage()} ${JSON.stringify(err)}`)) + } +} + /** * Edit teacher (AKA submitter) notes for a field trip request. */ @@ -184,146 +296,56 @@ export function editSubmitterNotes(request, submitterNotes, intl) { 'editSubmitterNotes', { notes: submitterNotes }, intl, - () => intl.formatMessage({ - id: 'actions.fieldTrip.editSubmitterNotesError' - }) + () => + intl.formatMessage({ + id: 'actions.fieldTrip.editSubmitterNotesError' + }) ) } /** - * Validates and saves the currently active field trip itineraries to the - * appropriate inbound or outbound trip of a field trip request. - * - * @param {Object} request The field trip request - * @param {boolean} outbound If true, save the current itineraries to the - * outbound field trip journey. - * @param {Object} intl A format.js intl object - */ -export function saveRequestTripItineraries (request, outbound, intl) { - return async function (dispatch, getState) { - const state = getState() - const { session } = state.callTaker - if (sessionIsInvalid(session)) return - - const itineraries = getActiveItineraries(state) - - // If plan is not valid, return before persisting trip. - if (fieldTripPlanIsInvalid(request, itineraries, intl)) return - - // Show a confirmation dialog before overwriting existing plan - if (!overwriteExistingRequestTripsConfirmed(request, outbound, intl)) return - - // Send data to server for saving. - let text - try { - const res = await fetch( - `${state.otp.config.datastoreUrl}/fieldtrip/newTrip`, - { - body: makeSaveFieldTripItinerariesData(request, outbound, state), - method: 'POST' - } - ) - text = await res.text() - } catch (err) { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.saveItinerariesError' }, - { err: JSON.stringify(err) } - ) - ) - return - } - - if (text === '-1') { - alert( - intl.formatMessage({ id: 'actions.fieldTrip.itineraryCapacityError' }) - ) - return - } - - dispatch(fetchFieldTripDetails(request.id, intl)) - } -} - -/** - * Checks that a group plan is valid for a given request, i.e., that it occurs - * on the requested travel date. - * - * @param request field trip request - * @param itineraries the currently active itineraries - * @param {Object} intl A format.js intl object - * @return true if invalid + * Creates an OTP-style location string based on the location name, lat and lon. */ -function fieldTripPlanIsInvalid (request, itineraries, intl) { - if (!itineraries || itineraries.length === 0) { - return true - } - - const earliestStartTime = getEarliestStartTime(itineraries) - - // FIXME: add back in offset? - const planDeparture = moment(earliestStartTime) // .add('hours', otp.config.timeOffset) - const requestDate = moment(request.travelDate) - - if ( - planDeparture.date() !== requestDate.date() || - planDeparture.month() !== requestDate.month() || - planDeparture.year() !== requestDate.year() - ) { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.incompatibleTripDateError' }, - { - requestDate: requestDate.format(FIELD_TRIP_DATE_FORMAT), - tripDate: planDeparture.format(FIELD_TRIP_DATE_FORMAT) - } - ) - ) - return true - } - - return false +function getOtpLocationString(location) { + const latLonString = `${location.lat},${location.lon}` + return location.name ? `${location.name}::${latLonString}` : latLonString } -/** - * Returns the earliest start time (in unix epoch milliseconds) in the given - * itineraries. - */ -function getEarliestStartTime (itineraries) { - return itineraries.reduce( - (earliestStartTime, itinerary) => - Math.min(earliestStartTime, itinerary.startTime), - Number.POSITIVE_INFINITY - ) +// These can be overridden in the field trip module config. +const defaultFieldTripModeCapacities = { + BUS: 40, + CABLE_CAR: 20, + FERRY: 100, + FUNICULAR: 20, + GONDOLA: 15, + RAIL: 80, + SUBWAY: 120, + TRAM: 80 } +const unknownModeCapacity = 15 /** - * Returns true if the user confirms the overwrite of an existing set of - * itineraries planned for the inbound or outbound part of the field trip. + * Calculates the mode capacity based on the field trip module mode capacities + * (if it exists) or from the above default lookup of mode capacities or if + * given an unknown mode, then the unknownModeCapacity is returned. * - * @param {Object} request The field trip request - * @param {boolean} outbound If true, save the current itineraries to the - * outbound field trip journey. - * @param {Object} intl A format.js intl object + * @param {Object} fieldTripModuleConfig the field trip module config + * @param {string} mode the OTP mode */ -function overwriteExistingRequestTripsConfirmed (request, outbound, intl) { - const preExistingTrip = getTripFromRequest(request, outbound) - if (preExistingTrip) { - return window.confirm( - intl.formatMessage( - { id: 'actions.fieldTrip.confirmOverwriteItineraries' }, - { outbound } - ) - ) - } - return true +function getFieldTripGroupCapacityForMode(fieldTripModuleConfig, mode) { + const configModeCapacities = fieldTripModuleConfig?.modeCapacities + return ( + (configModeCapacities && configModeCapacities[mode]) || + defaultFieldTripModeCapacities[mode] || + unknownModeCapacity + ) } /** * Constructs data used to save a set of itineraries for the inbound or outbound * part of the field trip. */ -function makeSaveFieldTripItinerariesData (request, outbound, state) { +function makeSaveFieldTripItinerariesData(request, outbound, state) { const { fieldTrip, session } = state.callTaker const { tripHashLookup } = fieldTrip const { config, currentQuery } = state.otp @@ -338,8 +360,9 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { sessionId: session.sessionId, trip: { createdBy: session.username, - departure: moment(getEarliestStartTime(itineraries)) - .format(FIELD_TRIP_DATE_TIME_FORMAT), + departure: moment(getEarliestStartTime(itineraries)).format( + FIELD_TRIP_DATE_TIME_FORMAT + ), destination: getOtpLocationString(currentQuery.from), origin: getOtpLocationString(currentQuery.to), passengers: getGroupSize(request), @@ -358,11 +381,10 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { } const gtfsTripsForItinerary = [] - itinerary.legs.filter(leg => isTransit(leg.mode)) - .forEach(leg => { - let routeName = leg.routeShortName - ? `(${leg.routeShortName}) ` - : '' + itinerary.legs + .filter((leg) => isTransit(leg.mode)) + .forEach((leg) => { + let routeName = leg.routeShortName ? `(${leg.routeShortName}) ` : '' routeName = `${routeName}${leg.routeLongName}` const gtfsTrip = { agencyAndId: leg.tripId, @@ -391,25 +413,57 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { } /** - * Creates an OTP-style location string based on the location name, lat and lon. + * Validates and saves the currently active field trip itineraries to the + * appropriate inbound or outbound trip of a field trip request. + * + * @param {Object} request The field trip request + * @param {boolean} outbound If true, save the current itineraries to the + * outbound field trip journey. + * @param {Object} intl A format.js intl object */ -function getOtpLocationString (location) { - const latLonString = `${location.lat},${location.lon}` - return location.name - ? `${location.name}::${latLonString}` - : latLonString -} - -/** - * Begins the process of making trip requests to find suitable itineraries for - * either an inbound or outbound journey of a field trip. - */ -export function planTrip (request, outbound, intl) { +export function saveRequestTripItineraries(request, outbound, intl) { return async function (dispatch, getState) { - dispatch(clearSaveable()) - dispatch(setGroupSize(getGroupSize(request))) - await dispatch(prepareQueryParams(request, outbound)) - dispatch(makeFieldTripPlanRequests(request, outbound, intl)) + const state = getState() + const { session } = state.callTaker + if (sessionIsInvalid(session)) return + + const itineraries = getActiveItineraries(state) + + // If plan is not valid, return before persisting trip. + if (fieldTripPlanIsInvalid(request, itineraries, intl)) return + + // Show a confirmation dialog before overwriting existing plan + if (!overwriteExistingRequestTripsConfirmed(request, outbound, intl)) return + + // Send data to server for saving. + let text + try { + const res = await fetch( + `${state.otp.config.datastoreUrl}/fieldtrip/newTrip`, + { + body: makeSaveFieldTripItinerariesData(request, outbound, state), + method: 'POST' + } + ) + text = await res.text() + } catch (err) { + alert( + intl.formatMessage( + { id: 'actions.fieldTrip.saveItinerariesError' }, + { err: JSON.stringify(err) } + ) + ) + return + } + + if (text === '-1') { + alert( + intl.formatMessage({ id: 'actions.fieldTrip.itineraryCapacityError' }) + ) + return + } + + dispatch(fetchFieldTripDetails(request.id, intl)) } } @@ -417,9 +471,9 @@ export function planTrip (request, outbound, intl) { * Sets the appropriate request parameters for either the outbound or inbound * journey of a field trip. */ -function prepareQueryParams (request, outbound) { +function prepareQueryParams(request, outbound) { return async function (dispatch, getState) { - const {config} = getState().otp + const { config } = getState().otp const queryParams = { date: moment(request.travelDate).format(OTP_API_DATE_FORMAT) } @@ -430,148 +484,36 @@ function prepareQueryParams (request, outbound) { toPlace: request.endLocation } queryParams.departArrive = 'ARRIVE' - queryParams.time = moment(request.arriveDestinationTime) - .format(OTP_API_TIME_FORMAT) + queryParams.time = moment(request.arriveDestinationTime).format( + OTP_API_TIME_FORMAT + ) } else { locationsToGeocode = { fromPlace: request.endLocation, toPlace: request.startLocation } queryParams.departArrive = 'DEPART' - queryParams.time = moment(request.leaveDestinationTime) - .format(OTP_API_TIME_FORMAT) + queryParams.time = moment(request.leaveDestinationTime).format( + OTP_API_TIME_FORMAT + ) } const locations = await planParamsToQueryAsync(locationsToGeocode, config) return dispatch(setQueryParam({ ...locations, ...queryParams })) } } -/** - * Makes appropriate OTP requests until enough itineraries have been found to - * accommodate the field trip group. This is done as follows: - * - * 1. Fetch a list of all the existing transit trips that already have a field - * trip assigned to the trip. - * 2. In a loop of up to 10 times: - * i. Make a trip plan query to OTP for one additional itinerary - * ii. Calculate the trip hashes in the resulting itinerary by making requests - * to the OTP index API - * iii. Assign as many field trip travelers to the resulting itinerary as - * possible. - * iv. Check if there are still unassigned field trip travelers - * a. If there are still more to assign, ban each trip used in this - * itinerary in subsequent OTP plan requests. - * b. If all travelers have been assigned, exit the loop and cleanup - */ -function makeFieldTripPlanRequests (request, outbound, intl) { - return async function (dispatch, getState) { - const fieldTripModuleConfig = getModuleConfig( - getState(), - Modules.FIELD_TRIP - ) - - // request other known trip IDs that other field trips are using on the - // field trip request date - try { - await dispatch(getTripIdsForTravelDate(request)) - } catch (err) { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.fetchTripsForDateError' }, - { err: JSON.stringify(err) } - ) - ) - return - } - - // create a new searchId to use for making all requests - const searchId = randId() - - // set numItineraries param for field trip requests - dispatch(setQueryParam({ numItineraries: 1 })) - - // create a new set to keep track of trips that should be banned - const bannedTrips = new Set() - - // track number of requests made such that endless requesting doesn't occur - const maxRequests = fieldTripModuleConfig?.maxRequests || 10 - let numRequests = 0 - - let shouldContinueSearching = true - - // make requests until enough itineraries have been found to accommodate - // group - while (shouldContinueSearching) { - numRequests++ - if (numRequests > maxRequests) { - // max number of requests exceeded. Show error. - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.maxTripRequestsExceeded' } - ) - ) - return dispatch(doFieldTripPlanRequestCleanup(searchId)) - } - - // make next query. The second param instructs the action/reducer whether - // or not to override previous search results in the state. - await dispatch(routingQuery(searchId, numRequests > 1)) - - // set the pending amount of requests to be 2 so that there will always - // seem to be potentially additional requests that have to be made. If - // there aren't after making this next request, the pending amount will - // be set to 0. This needs to happen after the routingQuery so the search - // is defined. - dispatch(setPendingRequests({ pending: 2, searchId })) - - // obtain trip hashes from OTP Index API - await getMissingTripHashesForActiveItineraries() - - // check trip validity and calculate itinerary capacity - const { - assignedItinerariesByResponse, - remainingGroupSize, - tripsToBanInSubsequentSearches - } = checkValidityAndCapacity( - getState(), - request - ) - - // always update itineraries on each itineration - dispatch(setActiveItineraries({ - assignedItinerariesByResponse, - searchId - })) - - if (remainingGroupSize <= 0) { - // All members of the field trip group have been assigned! - shouldContinueSearching = false - dispatch(setSaveable(outbound)) - dispatch(doFieldTripPlanRequestCleanup(searchId)) - } else { - // Not enough acceptable itineraries have been generated. Request more. - - // Update banned trips - tripsToBanInSubsequentSearches.forEach(tripId => { - bannedTrips.add(tripId) - }) - dispatch(setQueryParam({ bannedTrips: [...bannedTrips].join(',') })) - } - } - } -} - /** * Makes a request to get data about other planned field trips happening on a * particular date. */ -function getTripIdsForTravelDate (request) { +function getTripIdsForTravelDate(request) { return async function (dispatch, getState) { const state = getState() - const {datastoreUrl} = state.otp.config - const {sessionId} = state.callTaker.session - const formattedTravelDate = moment(request.travelDate) - .format(FIELD_TRIP_DATE_FORMAT) + const { datastoreUrl } = state.otp.config + const { sessionId } = state.callTaker.session + const formattedTravelDate = moment(request.travelDate).format( + FIELD_TRIP_DATE_FORMAT + ) const params = { date: formattedTravelDate, sessionId @@ -584,9 +526,9 @@ function getTripIdsForTravelDate (request) { // add passengers and converted tripId to trips const trips = [] - fieldTrips.forEach(fieldTrip => { - fieldTrip.groupItineraries.forEach(groupItinerary => { - groupItinerary.trips.forEach(gtfsTrip => { + fieldTrips.forEach((fieldTrip) => { + fieldTrip.groupItineraries.forEach((groupItinerary) => { + groupItinerary.trips.forEach((gtfsTrip) => { // tripIds still stored as 'agencyAndId' in DB, so convert them to // be compatible with OTP responses gtfsTrip.tripId = gtfsTrip.agencyAndId.replace('_', ':') @@ -605,14 +547,14 @@ function getTripIdsForTravelDate (request) { * for any tripIds in the active itineraries that haven't already been obtained * from the OTP index API. */ -function getMissingTripHashesForActiveItineraries () { +function getMissingTripHashesForActiveItineraries() { return function (dispatch, getState) { const state = getState() const activeItineraries = getActiveItineraries(state) const { tripHashLookup } = state.otp.callTaker.fieldTrip const tripHashesToRequest = [] - activeItineraries.forEach(itinerary => { - itinerary.legs.forEach(leg => { + activeItineraries.forEach((itinerary) => { + itinerary.legs.forEach((leg) => { if (leg.tripId && !tripHashLookup[leg.tripId]) { tripHashesToRequest.push(leg.tripId) } @@ -622,14 +564,66 @@ function getMissingTripHashesForActiveItineraries () { const api = state.config.api const baseUrl = `${api.host}${api.port ? ':' + api.port : ''}${api.path}` - return Promise.all(tripHashesToRequest.map(tripId => { - return fetch(`${baseUrl}/index/trips/${tripId}/semanticHash`) - .then(res => res.text()) - .then(hash => dispatch(receiveTripHash({ hash, tripId }))) - })) + return Promise.all( + tripHashesToRequest.map((tripId) => { + return fetch(`${baseUrl}/index/trips/${tripId}/semanticHash`) + .then((res) => res.text()) + .then((hash) => dispatch(receiveTripHash({ hash, tripId }))) + }) + ) } } +/** + * Dispatches the appropriate cleanup actions after making requests to find the + * itineraries for an inbound or outbound field trip journey. + */ +function doFieldTripPlanRequestCleanup(searchId) { + return function (dispatch, getState) { + // set pending searches to 0 to indicate searching is finished + dispatch(setPendingRequests({ pending: 0, searchId })) + // clear banned trips query param + dispatch(setQueryParam({ bannedTrips: undefined })) + } +} + +/** + * Checks whether an existing trip in use by another field trip overlaps with a + * a trip identified by tripId. + * + * @param leg The leg information of the trip in the associated + * itinerary + * @param tripHashLookup The lookup of trip hashes + * @param tripId The tripId to analyze with respect to a trip in use + * @param tripInUse The trip in use by an existing field trip. This is an + * otp-datastore object. + * @return true if the trips overlap + */ +function tripsOverlap(leg, tripHashLookup, tripId, tripInUse) { + // check if the trip is being used by another field trip + let sameVehicleTrip = false + if (tripId in tripHashLookup && tripInUse.tripHash) { + // use the trip hashes if available + sameVehicleTrip = tripHashLookup[tripId] === tripInUse.tripHash + } else { + // as fallback, compare the tripId strings + sameVehicleTrip = tripId === tripInUse.tripId + } + // not used by another vehicle, so this trip/vehicle is free to use + if (!sameVehicleTrip) return false + + // check if the stop ranges overlap. It is OK if one field trip begins + // where the other ends. + if ( + leg.from.stopIndex >= tripInUse.toStopIndex || + leg.to.stopIndex <= tripInUse.fromStopIndex + ) { + // legs do not overlap, so this trip/vehicle is free to use + return false + } + return true +} + /** * Analyzes the current itineraries from each response in the active search to * appropriately assign the field trip group to subgroups that use each @@ -646,7 +640,7 @@ function getMissingTripHashesForActiveItineraries () { * representing tripIds that should be added to the list of tripIds to ban when * making requests for additional itineraries from OTP. */ -function checkValidityAndCapacity (state, request) { +function checkValidityAndCapacity(state, request) { const fieldTripModuleConfig = getModuleConfig(state, Modules.FIELD_TRIP) const minimumAllowableRemainingCapacity = fieldTripModuleConfig?.minimumAllowableRemainingCapacity || 10 @@ -673,60 +667,64 @@ function checkValidityAndCapacity (state, request) { // check each individual trip to see if there aren't any trips in this // itinerary that are already in use by another field trip - itinerary.legs.filter(leg => isTransit(leg.mode)).forEach(leg => { - const { tripId } = leg - - // this variable is used to track how many other field trips are using a - // particular trip - let capacityInUse = 0 - - // iterate over trips that are already being used by other field trips - // NOTE: In the use case of re-planning trips, there is currently no way - // to discern whether a tripInUse belongs to the current direction of - // the field trip being planned. Therefore, this will result in the - // re-planning of trips avoiding it's own previously planned trips - // that it currently has saved - travelDateTripsInUse.forEach(tripInUse => { - if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return - - // ranges overlap! Add number of passengers on this other field trip - // to total capacity in use - capacityInUse += tripInUse.passengers + itinerary.legs + .filter((leg) => isTransit(leg.mode)) + .forEach((leg) => { + const { tripId } = leg + + // this variable is used to track how many other field trips are using a + // particular trip + let capacityInUse = 0 + + // iterate over trips that are already being used by other field trips + // NOTE: In the use case of re-planning trips, there is currently no way + // to discern whether a tripInUse belongs to the current direction of + // the field trip being planned. Therefore, this will result in the + // re-planning of trips avoiding it's own previously planned trips + // that it currently has saved + travelDateTripsInUse.forEach((tripInUse) => { + if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return + + // ranges overlap! Add number of passengers on this other field trip + // to total capacity in use + capacityInUse += tripInUse.passengers + }) + + // check if the remaining capacity on this trip is enough to allow more + // field trip passengers on board + const legModeCapacity = getFieldTripGroupCapacityForMode( + fieldTripModuleConfig, + leg.mode + ) + let remainingTripCapacity = legModeCapacity - capacityInUse + if (remainingTripCapacity < minimumAllowableRemainingCapacity) { + // This trip is already too "full" to allow any addition field trips + // on board. Ban this trip in future searches and don't use this + // itinerary in final results (set trip and itinerary capacity to 0). + remainingTripCapacity = 0 + } + + // always ban trips found in itineraries so that subsequent searches + // don't encounter them. + // TODO: a more advanced way of doing things might be to ban trip + // sequences to not find the same exact sequence, but also + // individual trips that are too full. + tripsToBanInSubsequentSearches.push(tripId) + + itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity) }) - // check if the remaining capacity on this trip is enough to allow more - // field trip passengers on board - const legModeCapacity = getFieldTripGroupCapacityForMode( - fieldTripModuleConfig, - leg.mode - ) - let remainingTripCapacity = legModeCapacity - capacityInUse - if (remainingTripCapacity < minimumAllowableRemainingCapacity) { - // This trip is already too "full" to allow any addition field trips - // on board. Ban this trip in future searches and don't use this - // itinerary in final results (set trip and itinerary capacity to 0). - remainingTripCapacity = 0 - } - - // always ban trips found in itineraries so that subsequent searches - // don't encounter them. - // TODO: a more advanced way of doing things might be to ban trip - // sequences to not find the same exact sequence, but also - // individual trips that are too full. - tripsToBanInSubsequentSearches.push(tripId) - - itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity) - }) - if (itineraryCapacity > 0) { // itinerary has capacity, add to list and update remaining group size. // A field trip response is guaranteed to have only one itinerary, so it // ok to set the itinerary by response as an array with a single // itinerary. - assignedItinerariesByResponse[responseIdx] = [{ - ...itinerary, - fieldTripGroupSize: Math.min(itineraryCapacity, remainingGroupSize) - }] + assignedItinerariesByResponse[responseIdx] = [ + { + ...itinerary, + fieldTripGroupSize: Math.min(itineraryCapacity, remainingGroupSize) + } + ] remainingGroupSize -= itineraryCapacity } }) @@ -740,97 +738,147 @@ function checkValidityAndCapacity (state, request) { } /** - * Checks whether an existing trip in use by another field trip overlaps with a - * a trip identified by tripId. + * Makes appropriate OTP requests until enough itineraries have been found to + * accommodate the field trip group. This is done as follows: * - * @param leg The leg information of the trip in the associated - * itinerary - * @param tripHashLookup The lookup of trip hashes - * @param tripId The tripId to analyze with respect to a trip in use - * @param tripInUse The trip in use by an existing field trip. This is an - * otp-datastore object. - * @return true if the trips overlap + * 1. Fetch a list of all the existing transit trips that already have a field + * trip assigned to the trip. + * 2. In a loop of up to 10 times: + * i. Make a trip plan query to OTP for one additional itinerary + * ii. Calculate the trip hashes in the resulting itinerary by making requests + * to the OTP index API + * iii. Assign as many field trip travelers to the resulting itinerary as + * possible. + * iv. Check if there are still unassigned field trip travelers + * a. If there are still more to assign, ban each trip used in this + * itinerary in subsequent OTP plan requests. + * b. If all travelers have been assigned, exit the loop and cleanup */ -function tripsOverlap (leg, tripHashLookup, tripId, tripInUse) { - // check if the trip is being used by another field trip - let sameVehicleTrip = false - if (tripId in tripHashLookup && tripInUse.tripHash) { - // use the trip hashes if available - sameVehicleTrip = (tripHashLookup[tripId] === tripInUse.tripHash) - } else { - // as fallback, compare the tripId strings - sameVehicleTrip = (tripId === tripInUse.tripId) - } - // not used by another vehicle, so this trip/vehicle is free to use - if (!sameVehicleTrip) return false +function makeFieldTripPlanRequests(request, outbound, intl) { + return async function (dispatch, getState) { + const fieldTripModuleConfig = getModuleConfig( + getState(), + Modules.FIELD_TRIP + ) - // check if the stop ranges overlap. It is OK if one field trip begins - // where the other ends. - if ( - leg.from.stopIndex >= tripInUse.toStopIndex || - leg.to.stopIndex <= tripInUse.fromStopIndex - ) { - // legs do not overlap, so this trip/vehicle is free to use - return false - } - return true -} + // request other known trip IDs that other field trips are using on the + // field trip request date + try { + await dispatch(getTripIdsForTravelDate(request)) + } catch (err) { + alert( + intl.formatMessage( + { id: 'actions.fieldTrip.fetchTripsForDateError' }, + { err: JSON.stringify(err) } + ) + ) + return + } -// These can be overridden in the field trip module config. -const defaultFieldTripModeCapacities = { - 'BUS': 40, - 'CABLE_CAR': 20, - 'FERRY': 100, - 'FUNICULAR': 20, - 'GONDOLA': 15, - 'RAIL': 80, - 'SUBWAY': 120, - 'TRAM': 80 -} -const unknownModeCapacity = 15 + // create a new searchId to use for making all requests + const searchId = randId() -/** - * Calculates the mode capacity based on the field trip module mode capacities - * (if it exists) or from the above default lookup of mode capacities or if - * given an unknown mode, then the unknownModeCapacity is returned. - * - * @param {Object} fieldTripModuleConfig the field trip module config - * @param {string} mode the OTP mode - */ -function getFieldTripGroupCapacityForMode (fieldTripModuleConfig, mode) { - const configModeCapacities = fieldTripModuleConfig?.modeCapacities - return (configModeCapacities && configModeCapacities[mode]) || - defaultFieldTripModeCapacities[mode] || - unknownModeCapacity + // set numItineraries param for field trip requests + dispatch(setQueryParam({ numItineraries: 1 })) + + // create a new set to keep track of trips that should be banned + const bannedTrips = new Set() + + // track number of requests made such that endless requesting doesn't occur + const maxRequests = fieldTripModuleConfig?.maxRequests || 10 + let numRequests = 0 + + let shouldContinueSearching = true + + // make requests until enough itineraries have been found to accommodate + // group + while (shouldContinueSearching) { + numRequests++ + if (numRequests > maxRequests) { + // max number of requests exceeded. Show error. + alert( + intl.formatMessage({ + id: 'actions.fieldTrip.maxTripRequestsExceeded' + }) + ) + return dispatch(doFieldTripPlanRequestCleanup(searchId)) + } + + // make next query. The second param instructs the action/reducer whether + // or not to override previous search results in the state. + await dispatch(routingQuery(searchId, numRequests > 1)) + + // set the pending amount of requests to be 2 so that there will always + // seem to be potentially additional requests that have to be made. If + // there aren't after making this next request, the pending amount will + // be set to 0. This needs to happen after the routingQuery so the search + // is defined. + dispatch(setPendingRequests({ pending: 2, searchId })) + + // obtain trip hashes from OTP Index API + await getMissingTripHashesForActiveItineraries() + + // check trip validity and calculate itinerary capacity + const { + assignedItinerariesByResponse, + remainingGroupSize, + tripsToBanInSubsequentSearches + } = checkValidityAndCapacity(getState(), request) + + // always update itineraries on each itineration + dispatch( + setActiveItineraries({ + assignedItinerariesByResponse, + searchId + }) + ) + + if (remainingGroupSize <= 0) { + // All members of the field trip group have been assigned! + shouldContinueSearching = false + dispatch(setSaveable(outbound)) + dispatch(doFieldTripPlanRequestCleanup(searchId)) + } else { + // Not enough acceptable itineraries have been generated. Request more. + + // Update banned trips + tripsToBanInSubsequentSearches.forEach((tripId) => { + bannedTrips.add(tripId) + }) + dispatch(setQueryParam({ bannedTrips: [...bannedTrips].join(',') })) + } + } + } } /** - * Dispatches the appropriate cleanup actions after making requests to find the - * itineraries for an inbound or outbound field trip journey. + * Begins the process of making trip requests to find suitable itineraries for + * either an inbound or outbound journey of a field trip. */ -function doFieldTripPlanRequestCleanup (searchId) { - return function (dispatch, getState) { - // set pending searches to 0 to indicate searching is finished - dispatch(setPendingRequests({ pending: 0, searchId })) - // clear banned trips query param - dispatch(setQueryParam({ bannedTrips: undefined })) +export function planTrip(request, outbound, intl) { + return async function (dispatch, getState) { + dispatch(clearSaveable()) + dispatch(setGroupSize(getGroupSize(request))) + await dispatch(prepareQueryParams(request, outbound)) + dispatch(makeFieldTripPlanRequests(request, outbound, intl)) } } /** * Removes the planned journey associated with the given id. */ -export function deleteRequestTripItineraries (request, tripId, intl) { +export function deleteRequestTripItineraries(request, tripId, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteTrip`, - {body: serialize({ id: tripId, sessionId }), method: 'POST'} - ) + const { sessionId } = callTaker.session + return fetch(`${datastoreUrl}/fieldtrip/deleteTrip`, { + body: serialize({ id: tripId, sessionId }), + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.deleteItinerariesError' }, @@ -846,7 +894,7 @@ export function deleteRequestTripItineraries (request, tripId, intl) { * saved itineraries from the field trip request and sets these loaded * itineraries as if they appeared from a new OTP trip plan request. */ -export function viewRequestTripItineraries (request, outbound) { +export function viewRequestTripItineraries(request, outbound) { return async function (dispatch, getState) { // set the appropriate query parameters as if the trip were being planned await dispatch(prepareQueryParams(request, outbound)) @@ -855,17 +903,20 @@ export function viewRequestTripItineraries (request, outbound) { const trip = getTripFromRequest(request, outbound) // decode the saved itineraries - const itineraries = trip.groupItineraries?.map(groupItin => - JSON.parse(lzwDecode(groupItin.itinData)) - ) || [] + const itineraries = + trip.groupItineraries?.map((groupItin) => + JSON.parse(lzwDecode(groupItin.itinData)) + ) || [] const searchId = randId() // set the itineraries in a new OTP response - dispatch(setActiveItinerariesFromFieldTrip({ - response: [{ plan: { itineraries } }], - searchId - })) + dispatch( + setActiveItinerariesFromFieldTrip({ + response: [{ plan: { itineraries } }], + searchId + }) + ) // appropriately initialize the URL params. If this doesn't happen, it won't // be possible to set an active itinerary properly due to funkiness with the @@ -874,28 +925,6 @@ export function viewRequestTripItineraries (request, outbound) { } } -/** - * Updates a particular field/set of fields of a given field trip request. - */ -export function updateFieldTripRequest(request, endpoint, updatedFields, intl, getErrorMessage) { - return function (dispatch, getState) { - const { callTaker, otp } = getState() - const { datastoreUrl } = otp.config - if (sessionIsInvalid(callTaker.session)) return - const { sessionId } = callTaker.session - const body = serialize({ - requestId: request.id, - sessionId, - ...updatedFields - }) - return fetch(`${datastoreUrl}/fieldtrip/${endpoint}`, { - body, method: 'POST' - }) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch((err) => alert(`${getErrorMessage()} ${JSON.stringify(err)}`)) - } -} - /** * Set group size for a field trip request. Group size consists of numStudents, * numFreeStudents, and numChaperones. @@ -906,9 +935,10 @@ export function setRequestGroupSize(request, groupSize, intl) { 'setRequestGroupSize', groupSize, intl, - () => intl.formatMessage({ - id: 'actions.fieldTrip.setGroupSizeError' - }) + () => + intl.formatMessage({ + id: 'actions.fieldTrip.setGroupSizeError' + }) ) } @@ -916,12 +946,8 @@ export function setRequestGroupSize(request, groupSize, intl) { * Sets the travel date for the requested field trip. */ export function setRequestDate(request, date, intl) { - return updateFieldTripRequest( - request, - 'setRequestDate', - { date }, - intl, - () => intl.formatMessage({ + return updateFieldTripRequest(request, 'setRequestDate', { date }, intl, () => + intl.formatMessage({ id: 'actions.fieldTrip.setDateError' }) ) @@ -936,9 +962,10 @@ export function setRequestPaymentInfo(request, paymentInfo, intl) { 'setRequestPaymentInfo', paymentInfo, intl, - () => intl.formatMessage({ - id: 'actions.fieldTrip.setPaymentError' - }) + () => + intl.formatMessage({ + id: 'actions.fieldTrip.setPaymentError' + }) ) } @@ -951,9 +978,10 @@ export function setRequestStatus(request, status, intl) { 'setRequestStatus', { status }, intl, - () => intl.formatMessage({ - id: 'actions.fieldTrip.setRequestStatusError' - }) + () => + intl.formatMessage({ + id: 'actions.fieldTrip.setRequestStatusError' + }) ) } @@ -961,7 +989,7 @@ export function setRequestStatus(request, status, intl) { * Clears and resets all relevant data after a field trip loses focus (upon * closing the field trip details window) */ -export function clearActiveFieldTrip () { +export function clearActiveFieldTrip() { return function (dispatch, getState) { dispatch(setActiveFieldTrip(null)) dispatch(clearActiveSearch()) From df4e6e7aeca2855c86dcd157bc1001d7d21ecf77 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:26:51 -0400 Subject: [PATCH 0274/1425] refactor(actions/field-trip): Make delete methods reuse generic datastore code. --- i18n/en-US.yml | 6 +- i18n/fr.yml | 6 +- lib/actions/field-trip.js | 119 +++++++++++++++++++------------------- 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index d1f4cc8ae..b9bde9244 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -38,9 +38,9 @@ actions: true {outbound} other {inbound} } itinerary for this request. Do you wish to continue? - deleteItinerariesError: "Error deleting field trip plan: {err}" - deleteNoteError: "Error deleting field trip note: {err}" - editSubmitterNotesError: "Error editing submitter notes: {err}" + deleteItinerariesError: "Error deleting field trip plan:" + deleteNoteError: "Error deleting field trip note:" + editSubmitterNotesError: "Error editing submitter notes:" fetchFieldTripError: "Error fetching field trip: {err}" fetchFieldTripsError: "Error fetching field trips: {err}" fetchTripsForDateError: "Error fetching trips for field trip travel date: {err}" diff --git a/i18n/fr.yml b/i18n/fr.yml index 412d411cb..c1f133191 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -27,9 +27,9 @@ actions: true {aller} other {retour} } planifié préalablement pour cette demande. Voulez-vous continuer ? - deleteItinerariesError: "Erreur lors de la suppression d'un itinéraire de groupe : {err}" - deleteNoteError: "Erreur lors de la suppression d'une note sur le groupe : {err}" - editSubmitterNotesError: "Erreur lors de la modification des notes du demandeur : {err}" + deleteItinerariesError: "Erreur lors de la suppression d'un itinéraire de groupe :" + deleteNoteError: "Erreur lors de la suppression d'une note sur le groupe :" + editSubmitterNotesError: "Erreur lors de la modification des notes du demandeur :" fetchFieldTripError: "Erreur de chargement de l'itinéraire du groupe: {err}" fetchFieldTripsError: "Error fetching des itinéraires du groupe : {err}" fetchTripsForDateError: "Error fetching des itinéraires du groupe pour les dates de sorties : {err}" diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index e9f7c37f7..949efe987 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -233,53 +233,31 @@ export function addFieldTripNote(request, note, intl) { } } -/** - * Delete a specific note for a field trip request. - */ -export function deleteFieldTripNote(request, noteId, intl) { - return function (dispatch, getState) { - const { callTaker, otp } = getState() - const { datastoreUrl } = otp.config - if (sessionIsInvalid(callTaker.session)) return - const { sessionId } = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteNote`, { - body: serialize({ noteId, sessionId }), - method: 'POST' - }) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch((err) => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.deleteNoteError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } -} - /** * Updates a particular field/set of fields of a given field trip request. */ -export function updateFieldTripRequest( +function updateFieldTripRequest( request, endpoint, - updatedFields, + data, intl, - getErrorMessage + getErrorMessage, + includeRequestId ) { return function (dispatch, getState) { const { callTaker, otp } = getState() const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return const { sessionId } = callTaker.session - const body = serialize({ - requestId: request.id, + const payload = { sessionId, - ...updatedFields - }) + ...data + } + if (includeRequestId) { + payload.requestId = request.id + } return fetch(`${datastoreUrl}/fieldtrip/${endpoint}`, { - body, + body: serialize(payload), method: 'POST' }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) @@ -287,6 +265,23 @@ export function updateFieldTripRequest( } } +/** + * Delete a specific note for a field trip request. + */ +export function deleteFieldTripNote(request, noteId, intl) { + return updateFieldTripRequest( + request, + 'deleteNote', + { noteId }, + intl, + () => + intl.formatMessage({ + id: 'actions.fieldTrip.deleteNoteError' + }), + false // don't include requestId + ) +} + /** * Edit teacher (AKA submitter) notes for a field trip request. */ @@ -299,7 +294,8 @@ export function editSubmitterNotes(request, submitterNotes, intl) { () => intl.formatMessage({ id: 'actions.fieldTrip.editSubmitterNotesError' - }) + }), + true ) } @@ -868,25 +864,17 @@ export function planTrip(request, outbound, intl) { * Removes the planned journey associated with the given id. */ export function deleteRequestTripItineraries(request, tripId, intl) { - return function (dispatch, getState) { - const { callTaker, otp } = getState() - const { datastoreUrl } = otp.config - if (sessionIsInvalid(callTaker.session)) return - const { sessionId } = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteTrip`, { - body: serialize({ id: tripId, sessionId }), - method: 'POST' - }) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch((err) => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.deleteItinerariesError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } + return updateFieldTripRequest( + request, + 'deleteTrip', + { id: tripId }, + intl, + () => + intl.formatMessage({ + id: 'actions.fieldTrip.deleteItinerariesError' + }), + false // don't include requestId + ) } /** @@ -938,7 +926,8 @@ export function setRequestGroupSize(request, groupSize, intl) { () => intl.formatMessage({ id: 'actions.fieldTrip.setGroupSizeError' - }) + }), + true ) } @@ -946,10 +935,16 @@ export function setRequestGroupSize(request, groupSize, intl) { * Sets the travel date for the requested field trip. */ export function setRequestDate(request, date, intl) { - return updateFieldTripRequest(request, 'setRequestDate', { date }, intl, () => - intl.formatMessage({ - id: 'actions.fieldTrip.setDateError' - }) + return updateFieldTripRequest( + request, + 'setRequestDate', + { date }, + intl, + () => + intl.formatMessage({ + id: 'actions.fieldTrip.setDateError' + }), + true ) } @@ -965,7 +960,8 @@ export function setRequestPaymentInfo(request, paymentInfo, intl) { () => intl.formatMessage({ id: 'actions.fieldTrip.setPaymentError' - }) + }), + true ) } @@ -981,7 +977,8 @@ export function setRequestStatus(request, status, intl) { () => intl.formatMessage({ id: 'actions.fieldTrip.setRequestStatusError' - }) + }), + true ) } @@ -990,7 +987,7 @@ export function setRequestStatus(request, status, intl) { * closing the field trip details window) */ export function clearActiveFieldTrip() { - return function (dispatch, getState) { + return function (dispatch) { dispatch(setActiveFieldTrip(null)) dispatch(clearActiveSearch()) dispatch(setQueryParam({ numItineraries: undefined })) From 84bc8f1e26ceaac58029292161881eb614e36ff4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 14 Jun 2022 09:38:37 -0400 Subject: [PATCH 0275/1425] refactor(actions/field-trip): Tweak comment. --- lib/actions/field-trip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 949efe987..b18cb92a4 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -234,7 +234,7 @@ export function addFieldTripNote(request, note, intl) { } /** - * Updates a particular field/set of fields of a given field trip request. + * Invokes OTP Datatstore to update a field trip request or its related notes or itineraries. */ function updateFieldTripRequest( request, From 7c4c364113f4cab61c235683f18dfa3e7db0a1c5 Mon Sep 17 00:00:00 2001 From: David Emory Date: Tue, 14 Jun 2022 15:10:40 -0400 Subject: [PATCH 0276/1425] Fix calltaker trip count disparity (https://github.com/ibi-group/trimet-mod-otp/issues/308) --- lib/actions/field-trip.js | 9 ++++++++- lib/components/admin/field-trip-details.js | 2 ++ lib/util/call-taker.js | 6 +++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 4a8534582..13ef5b4f4 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -886,10 +886,17 @@ export function viewRequestTripItineraries (request, outbound) { } /** - * Set group size for a field trip request. Group size consists of numStudents, + * Set group size for a field trip request. Group size consists of numPaidStudents, * numFreeStudents, and numChaperones. */ export function setRequestGroupSize (request, groupSize, intl) { + // rewrite to format used by database + groupSize = { + numStudents: groupSize.numPaidStudents + groupSize.numFreeStudents, + numFreeStudents: groupSize.numFreeStudents, + numChaperones: groupSize.numChaperones + } + return function (dispatch, getState) { const {callTaker, otp} = getState() const {datastoreUrl} = otp.config diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 0d55ec26d..a2fe710be 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -139,6 +139,8 @@ class FieldTripDetails extends Component { style } = this.props if (!request) return null + request.numPaidStudents = request.numStudents - request.numFreeStudents + const { invoiceRequired, notes, diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index f82bf1358..f9b2f532b 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -25,7 +25,7 @@ const positiveIntInputProps = { export const GROUP_FIELDS = [ { - fieldName: 'numStudents', + fieldName: 'numPaidStudents', inputProps: positiveIntInputProps, label: 'students 7 or older' }, @@ -202,8 +202,8 @@ export function searchToQuery(search, call, otpConfig) { export function getGroupSize(request, requireTickets = false) { if (request) { const { numChaperones = 0, numFreeStudents = 0, numStudents = 0 } = request - if (requireTickets) return numChaperones + numStudents - else return numChaperones + numFreeStudents + numStudents + if (requireTickets) return numChaperones + numStudents - numFreeStudents + else return numChaperones + numStudents } return 0 } From c8fef059176027ec65f5ca954869095a49bbde9d Mon Sep 17 00:00:00 2001 From: David Emory Date: Tue, 14 Jun 2022 15:13:05 -0400 Subject: [PATCH 0277/1425] Fix calltaker map crash when planning scooter trips --- lib/components/map/default-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 5cf893918..e6e5657d4 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -145,7 +145,7 @@ class DefaultMap extends Component { const overlayCompany = oConfig.companies[0] // TODO: handle multi-company overlays if (added.includes(overlayMode)) { // Company-based mode was just selected; enable overlay iff overlay's company is active - if (newQuery.companies.includes(overlayCompany)) { + if (newQuery.companies && newQuery.companies.includes(overlayCompany)) { overlayVisibility.push({ overlay: oConfig, visible: true From b3c4d5139a037a6d741460c46df88efc06476ecc Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 14 Jun 2022 16:20:12 -0700 Subject: [PATCH 0278/1425] refactor(ConnectedTripDetails): add co2config --- lib/components/narrative/connected-trip-details.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/components/narrative/connected-trip-details.js b/lib/components/narrative/connected-trip-details.js index 0227d4140..f1ee5f77d 100644 --- a/lib/components/narrative/connected-trip-details.js +++ b/lib/components/narrative/connected-trip-details.js @@ -11,8 +11,9 @@ const TripDetails = styled(TripDetailsBase)` // Connect imported TripDetails class to redux store. const mapStateToProps = (state) => { - const { itinerary } = state.otp.config + const { co2Config, itinerary } = state.otp.config return { + co2Config, defaultFareKey: itinerary?.defaultFareKey || undefined, fareKeyNameMap: itinerary?.fareKeyNameMap || {} } From dc291d9505503809202cbc091612e6a0d25d14f0 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 14 Jun 2022 17:11:03 -0700 Subject: [PATCH 0279/1425] chore(connected-trip-details): fix co2 --- lib/components/narrative/connected-trip-details.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/narrative/connected-trip-details.js b/lib/components/narrative/connected-trip-details.js index f1ee5f77d..82de883bc 100644 --- a/lib/components/narrative/connected-trip-details.js +++ b/lib/components/narrative/connected-trip-details.js @@ -11,9 +11,9 @@ const TripDetails = styled(TripDetailsBase)` // Connect imported TripDetails class to redux store. const mapStateToProps = (state) => { - const { co2Config, itinerary } = state.otp.config + const { co2, itinerary } = state.otp.config return { - co2Config, + co2Config: co2, defaultFareKey: itinerary?.defaultFareKey || undefined, fareKeyNameMap: itinerary?.fareKeyNameMap || {} } From 4e9b9c01f0722b79f6338abb2e43d2ab806f9b34 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 10:14:13 -0400 Subject: [PATCH 0280/1425] fix(StopScheduleTable): Display times using homeTimezone. --- .../viewers/stop-schedule-table.tsx | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/components/viewers/stop-schedule-table.tsx b/lib/components/viewers/stop-schedule-table.tsx index ef112d9b5..2844e88e9 100644 --- a/lib/components/viewers/stop-schedule-table.tsx +++ b/lib/components/viewers/stop-schedule-table.tsx @@ -1,5 +1,10 @@ +import { connect } from 'react-redux' import { FormattedMessage, FormattedTime } from 'react-intl' -import moment from 'moment' +import { zonedTimeToUtc } from 'date-fns-tz' +import addSeconds from 'date-fns/addSeconds' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore TYPESCRIPT TODO: wait for typescripted core-utils +import coreUtils from '@opentripplanner/core-utils' import React, { Component, createRef } from 'react' import styled from 'styled-components' @@ -57,6 +62,7 @@ const TimeCell = styled.td` */ class StopScheduleTable extends Component<{ date: string + homeTimezone: string showBlockIds: boolean // TODO TYPESCRIPT: move this type to a shared type stopData: StopData @@ -64,7 +70,7 @@ class StopScheduleTable extends Component<{ /** * Link to the DOM for the next departure row, so we can scroll to it if needed. */ - targetDepartureRef = createRef() + targetDepartureRef = createRef() /** * Scroll to the first stop time that is departing from now. @@ -86,14 +92,18 @@ class StopScheduleTable extends Component<{ } render(): JSX.Element { - const { date, showBlockIds, stopData } = this.props + const { date, homeTimezone, showBlockIds, stopData } = this.props // Show loading spinner if times are still being fetched. if (stopData.fetchStatus === FETCH_STATUS.FETCHING) { return } const mergedStopTimes = mergeAndSortStopTimes(stopData) - const today = moment().startOf('day').format('YYYY-MM-DD') + const today = coreUtils.time.getCurrentDate(homeTimezone) + + // Compute the UTC date that corresponds to midnight (00:00) of the given date in homeTimezone + const startOfDate = zonedTimeToUtc(date, homeTimezone) + // Find the next stop time that is departing. // We will scroll to that stop time entry (if showing schedules for today). const shouldHighlightFirstDeparture = @@ -138,18 +148,14 @@ class StopScheduleTable extends Component<{ ? nextStopTime === highlightedStopTime : highlightRow const routeName = route.shortName ? route.shortName : route.longName - - // Convert to milliseconds for FormattedTime - const departureTimestamp = moment() - .startOf('day') - .add(stopTime.scheduledDeparture, 's') - .valueOf() + const departureTimestamp = addSeconds( + startOfDate, + stopTime.scheduledDeparture + ) // Add ref to scroll to the first stop time departing from now. const refProp = scrollToRow ? this.targetDepartureRef : undefined return ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore TYEPSCRIPT TODO: ref {showBlockIds && ( {blockId} @@ -168,4 +174,8 @@ class StopScheduleTable extends Component<{ } } -export default StopScheduleTable +const mapStateToProps = (state: Record) => ({ + homeTimezone: state.otp.config.homeTimezone +}) + +export default connect(mapStateToProps)(StopScheduleTable) From d8a7c593aed26b80beda99bab62d0321c6dbe716 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 11:26:22 -0400 Subject: [PATCH 0281/1425] fix(StopTimeCell): Display times in the agency local time. --- lib/components/viewers/departure-time.tsx | 26 +++++++++++++++++++ .../viewers/stop-schedule-table.tsx | 16 ++++-------- lib/components/viewers/stop-time-cell.tsx | 24 +++++------------ 3 files changed, 37 insertions(+), 29 deletions(-) create mode 100644 lib/components/viewers/departure-time.tsx diff --git a/lib/components/viewers/departure-time.tsx b/lib/components/viewers/departure-time.tsx new file mode 100644 index 000000000..973f3b318 --- /dev/null +++ b/lib/components/viewers/departure-time.tsx @@ -0,0 +1,26 @@ +import { FormattedTime } from 'react-intl' +import addSeconds from 'date-fns/addSeconds' +import React from 'react' + +import type { Time } from '../util/types' + +interface Props { + realTime?: boolean + stopTime: Time +} + +/** + * Displays a formatted departure time (expressed in the agency time zone) for the given stop time. + */ +const DepartureTime = ({ realTime, stopTime }: Props): JSX.Element => { + // stopTime.serviceDay already corresponds to midnight in the agency's home timezone so no extra conversion is needed. + const startOfDate = new Date(stopTime.serviceDay * 1000) + const departureTimestamp = addSeconds( + startOfDate, + realTime ? stopTime.realtimeDeparture : stopTime.scheduledDeparture + ) + + return +} + +export default DepartureTime diff --git a/lib/components/viewers/stop-schedule-table.tsx b/lib/components/viewers/stop-schedule-table.tsx index 2844e88e9..5ed2a8b7c 100644 --- a/lib/components/viewers/stop-schedule-table.tsx +++ b/lib/components/viewers/stop-schedule-table.tsx @@ -1,7 +1,5 @@ import { connect } from 'react-redux' -import { FormattedMessage, FormattedTime } from 'react-intl' -import { zonedTimeToUtc } from 'date-fns-tz' -import addSeconds from 'date-fns/addSeconds' +import { FormattedMessage } from 'react-intl' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore TYPESCRIPT TODO: wait for typescripted core-utils import coreUtils from '@opentripplanner/core-utils' @@ -16,6 +14,8 @@ import { import Loading from '../narrative/loading' import type { StopData } from '../util/types' +import DepartureTime from './departure-time' + // Styles for the schedule table and its contents. const StyledTable = styled.table` border-spacing: collapse; @@ -101,9 +101,6 @@ class StopScheduleTable extends Component<{ const today = coreUtils.time.getCurrentDate(homeTimezone) - // Compute the UTC date that corresponds to midnight (00:00) of the given date in homeTimezone - const startOfDate = zonedTimeToUtc(date, homeTimezone) - // Find the next stop time that is departing. // We will scroll to that stop time entry (if showing schedules for today). const shouldHighlightFirstDeparture = @@ -148,10 +145,7 @@ class StopScheduleTable extends Component<{ ? nextStopTime === highlightedStopTime : highlightRow const routeName = route.shortName ? route.shortName : route.longName - const departureTimestamp = addSeconds( - startOfDate, - stopTime.scheduledDeparture - ) + // Add ref to scroll to the first stop time departing from now. const refProp = scrollToRow ? this.targetDepartureRef : undefined @@ -163,7 +157,7 @@ class StopScheduleTable extends Component<{ {routeName} {headsign} - + ) diff --git a/lib/components/viewers/stop-time-cell.tsx b/lib/components/viewers/stop-time-cell.tsx index 464498e2f..6e41b25fa 100644 --- a/lib/components/viewers/stop-time-cell.tsx +++ b/lib/components/viewers/stop-time-cell.tsx @@ -1,5 +1,5 @@ import 'moment-timezone' -import { FormattedMessage, FormattedTime } from 'react-intl' +import { FormattedMessage } from 'react-intl' // TODO: typescript core-utils, add common types (pattern, stop, configs) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -8,10 +8,13 @@ import moment from 'moment' import React from 'react' import { getSecondsUntilDeparture } from '../../util/viewer' +import { Time } from '../util/types' import FormattedDayOfWeek from '../util/formatted-day-of-week' import FormattedDuration from '../util/formatted-duration' import Icon from '../util/icon' +import DepartureTime from './departure-time' + const { getUserTimezone } = coreUtils.time const ONE_HOUR_IN_SECONDS = 3600 const ONE_DAY_IN_SECONDS = 86400 @@ -20,8 +23,7 @@ type Props = { /** If configured, the timezone of the area */ homeTimezone?: string /** A stopTime object as received from a transit index API */ - // TODO: common shared types - stopTime: any + stopTime: Time } /** @@ -50,19 +52,10 @@ const StopTimeCell = ({
) } - const userTimezone = getUserTimezone() const departureTime = stopTime.realtimeDeparture const now = moment().tz(homeTimezone) const serviceDay = moment(stopTime.serviceDay * 1000).tz(homeTimezone) - // Convert seconds after midnight to unix (milliseconds) for FormattedTime - // Convert to userTimezone rather than spying on startOf for tests - const departureMoment = moment(stopTime.serviceDay * 1000) - .tz(userTimezone) - .startOf('day') - departureMoment.add(departureTime, 's') - const departureTimestamp = departureMoment.valueOf() - // Determine if arrival occurs on different day, making sure to account for // any extra days added to the service day if it arrives after midnight. Note: // this can handle the rare (and non-existent?) case where an arrival occurs @@ -119,12 +112,7 @@ const StopTimeCell = ({ ) ) : ( - // Show formatted time (with timezone if user is not in home timezone) - + )}
From 0ec9ec3b81d401b84eb3b4cea5efce01454addc4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:08:17 -0400 Subject: [PATCH 0282/1425] refactor(StopTimeCell): Replace moment dependency with date-fns. --- .../viewers/stop-schedule-table.tsx | 7 +---- lib/components/viewers/stop-time-cell.tsx | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/components/viewers/stop-schedule-table.tsx b/lib/components/viewers/stop-schedule-table.tsx index 5ed2a8b7c..2def061d6 100644 --- a/lib/components/viewers/stop-schedule-table.tsx +++ b/lib/components/viewers/stop-schedule-table.tsx @@ -1,4 +1,3 @@ -import { connect } from 'react-redux' import { FormattedMessage } from 'react-intl' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore TYPESCRIPT TODO: wait for typescripted core-utils @@ -168,8 +167,4 @@ class StopScheduleTable extends Component<{ } } -const mapStateToProps = (state: Record) => ({ - homeTimezone: state.otp.config.homeTimezone -}) - -export default connect(mapStateToProps)(StopScheduleTable) +export default StopScheduleTable diff --git a/lib/components/viewers/stop-time-cell.tsx b/lib/components/viewers/stop-time-cell.tsx index 6e41b25fa..e66e74e6d 100644 --- a/lib/components/viewers/stop-time-cell.tsx +++ b/lib/components/viewers/stop-time-cell.tsx @@ -1,17 +1,18 @@ -import 'moment-timezone' +import { format, getTimezoneOffset, utcToZonedTime } from 'date-fns-tz' import { FormattedMessage } from 'react-intl' +import addDays from 'date-fns/addDays' // TODO: typescript core-utils, add common types (pattern, stop, configs) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import coreUtils from '@opentripplanner/core-utils' -import moment from 'moment' +import isSameDay from 'date-fns/isSameDay' import React from 'react' import { getSecondsUntilDeparture } from '../../util/viewer' -import { Time } from '../util/types' import FormattedDayOfWeek from '../util/formatted-day-of-week' import FormattedDuration from '../util/formatted-duration' import Icon from '../util/icon' +import type { Time } from '../util/types' import DepartureTime from './departure-time' @@ -44,7 +45,7 @@ const StopTimeCell = ({
) } - if (!moment.tz.zone(homeTimezone)) { + if (isNaN(getTimezoneOffset(homeTimezone))) { console.warn(`homeTimezone '${homeTimezone}' is invalid`) return (
@@ -53,8 +54,11 @@ const StopTimeCell = ({ ) } const departureTime = stopTime.realtimeDeparture - const now = moment().tz(homeTimezone) - const serviceDay = moment(stopTime.serviceDay * 1000).tz(homeTimezone) + const now = utcToZonedTime(Date.now(), homeTimezone) + const serviceDay = utcToZonedTime( + new Date(stopTime.serviceDay * 1000), + homeTimezone + ) // Determine if arrival occurs on different day, making sure to account for // any extra days added to the service day if it arrives after midnight. Note: @@ -63,8 +67,8 @@ const StopTimeCell = ({ const departureTimeRemainder = departureTime % ONE_DAY_IN_SECONDS const daysAfterServiceDay = (departureTime - departureTimeRemainder) / ONE_DAY_IN_SECONDS - const departureDay = serviceDay.add(daysAfterServiceDay, 'day') - const vehicleDepartsToday = now.dayOfYear() === departureDay?.dayOfYear() + const departureDay = addDays(serviceDay, daysAfterServiceDay) + const vehicleDepartsToday = isSameDay(now, departureDay) // Determine whether to show departure as countdown (e.g. "5 min") or as HH:mm // time, using realtime updates if available. @@ -99,7 +103,11 @@ const StopTimeCell = ({ {showDayOfWeek && (
)} From 55830ec3b8d309a329a29d2e62c4756899768e27 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:14:32 -0400 Subject: [PATCH 0283/1425] test(StopViewer): Update snapshots --- .../viewers/__snapshots__/stop-viewer.js.snap | 210 +++++++++++++++--- 1 file changed, 174 insertions(+), 36 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 179608c3f..baad2593a 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -5285,13 +5285,36 @@ exports[`components > viewers > stop viewer should render times after midnight w
- - 12:51 AM - + + 12:51 AM + +
@@ -8440,13 +8463,36 @@ exports[`components > viewers > stop viewer should render with OTP transit index
- - 6:00 PM - + + 6:00 PM + +
@@ -8839,13 +8885,36 @@ exports[`components > viewers > stop viewer should render with OTP transit index
- - 4:11 PM - + + 4:11 PM + +
@@ -9238,13 +9307,36 @@ exports[`components > viewers > stop viewer should render with OTP transit index
- - 3:22 PM - + + 3:22 PM + +
@@ -9745,13 +9837,36 @@ exports[`components > viewers > stop viewer should render with OTP transit index
- - 2:28 PM - + + 2:28 PM + +
@@ -13921,13 +14036,36 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
- - 5:45 PM - + + 5:45 PM + +
From bc8e7e4ed18d088378da7419840d3c35b13c9bce Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:28:27 -0400 Subject: [PATCH 0284/1425] ci(har-mock-config): Set timezone to US Eastern --- percy/har-mock-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/percy/har-mock-config.yml b/percy/har-mock-config.yml index 97fb9c3fc..fade442e3 100644 --- a/percy/har-mock-config.yml +++ b/percy/har-mock-config.yml @@ -1,6 +1,6 @@ branding: HAR test title: test environment -homeTimezone: America/Chicago +homeTimezone: America/New_York # Default OTP API api: From 1b98b40b4f3738334d659ccd914d0f84c2b8af07 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 15 Jun 2022 12:44:40 -0700 Subject: [PATCH 0285/1425] refactor(transitive): use new arg layout for transitive --- .../map/connected-transitive-overlay.tsx | 7 ++-- lib/components/map/stylized-map.js | 37 +++++++++++-------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 42f60225b..14cc2be4a 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -44,13 +44,12 @@ const ConnectedTransitiveOverlay = (props: Props) => { if (hasResponse) { if (hasItineraryResponse) { transitiveData = itineraryToRender - ? itineraryToTransitive( - itineraryToRender, + ? itineraryToTransitive(itineraryToRender, { companies, - getTransitiveRouteLabel, disableFlexArc, + getTransitiveRouteLabel, intl - ) + }) : null } else if (otpResponse) { transitiveData = otpResponse diff --git a/lib/components/map/stylized-map.js b/lib/components/map/stylized-map.js index 1d4041e24..f1306074e 100644 --- a/lib/components/map/stylized-map.js +++ b/lib/components/map/stylized-map.js @@ -1,14 +1,15 @@ -import { select, event } from 'd3-selection' +import { connect } from 'react-redux' +import { event, select } from 'd3-selection' +import { itineraryToTransitive } from '@opentripplanner/transitive-overlay' import { zoom } from 'd3-zoom' import coreUtils from '@opentripplanner/core-utils' import PropTypes from 'prop-types' import React, { Component } from 'react' -import { connect } from 'react-redux' import Transitive from 'transitive-js' -import { getActiveSearch, getActiveItineraries } from '../../util/state' +import { getActiveItineraries, getActiveSearch } from '../../util/state' -var STYLES = {} +const STYLES = {} STYLES.places = { display: function (display, place) { @@ -44,7 +45,7 @@ class StylizedMap extends Component { toggleName: 'Stylized' } - componentDidMount () { + componentDidMount() { const el = document.getElementById('trn-canvas') this._transitive = new Transitive({ display: 'svg', @@ -63,21 +64,23 @@ class StylizedMap extends Component { }) this._transitive.render() - select(el).call(zoom() - .scaleExtent([1 / 2, 4]) - .on('zoom', () => { - this._transitive.setTransform(event.transform) - }) + select(el).call( + zoom() + .scaleExtent([1 / 2, 4]) + .on('zoom', () => { + this._transitive.setTransform(event.transform) + }) ) } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { if (prevProps.transitiveData !== this.props.transitiveData) { this._transitive.updateData(this.props.transitiveData, true) this._transitive.render() } - if ( // this block only applies for profile trips where active option changed + if ( + // this block only applies for profile trips where active option changed this.props.routingType === 'PROFILE' && prevProps.activeItinerary !== this.props.activeItinerary ) { @@ -95,10 +98,10 @@ class StylizedMap extends Component { } } - render () { + render() { return (
) @@ -117,7 +120,8 @@ const mapStateToProps = (state, ownProps) => { ) { const itins = getActiveItineraries(state) const visibleItinerary = itins[activeSearch.activeItinerary] - if (visibleItinerary) transitiveData = coreUtils.map.itineraryToTransitive(visibleItinerary) + if (visibleItinerary) + transitiveData = itineraryToTransitive(visibleItinerary) } else if ( activeSearch && activeSearch.response && @@ -128,7 +132,8 @@ const mapStateToProps = (state, ownProps) => { return { activeItinerary: activeSearch && activeSearch.activeItinerary, - routingType: activeSearch && activeSearch.query && activeSearch.query.routingType, + routingType: + activeSearch && activeSearch.query && activeSearch.query.routingType, transitiveData } } From a3c957c14e811b258dafc91bea4f3e3ea6862bd9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:14:30 -0400 Subject: [PATCH 0286/1425] fix(TripViewer): Display stop times using homeTimezone. --- lib/components/viewers/departure-time.tsx | 10 +++++++-- lib/components/viewers/trip-viewer.js | 27 +++++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/components/viewers/departure-time.tsx b/lib/components/viewers/departure-time.tsx index 973f3b318..16dbe1c4a 100644 --- a/lib/components/viewers/departure-time.tsx +++ b/lib/components/viewers/departure-time.tsx @@ -5,6 +5,7 @@ import React from 'react' import type { Time } from '../util/types' interface Props { + originDate?: Date realTime?: boolean stopTime: Time } @@ -12,9 +13,14 @@ interface Props { /** * Displays a formatted departure time (expressed in the agency time zone) for the given stop time. */ -const DepartureTime = ({ realTime, stopTime }: Props): JSX.Element => { +const DepartureTime = ({ + originDate, + realTime, + stopTime +}: Props): JSX.Element => { + // If an originDate is defined, use that, otherwise use stopTime.serviceDay. // stopTime.serviceDay already corresponds to midnight in the agency's home timezone so no extra conversion is needed. - const startOfDate = new Date(stopTime.serviceDay * 1000) + const startOfDate = originDate || new Date(stopTime.serviceDay * 1000) const departureTimestamp = addSeconds( startOfDate, realTime ? stopTime.realtimeDeparture : stopTime.scheduledDeparture diff --git a/lib/components/viewers/trip-viewer.js b/lib/components/viewers/trip-viewer.js index 070ce514b..3b49db26f 100644 --- a/lib/components/viewers/trip-viewer.js +++ b/lib/components/viewers/trip-viewer.js @@ -2,9 +2,9 @@ /* eslint-disable jsx-a11y/label-has-for */ import { Button, Label } from 'react-bootstrap' import { connect } from 'react-redux' -import { FormattedMessage, FormattedTime } from 'react-intl' +import { FormattedMessage } from 'react-intl' +import { toDate } from 'date-fns-tz' import coreUtils from '@opentripplanner/core-utils' -import moment from 'moment' import PropTypes from 'prop-types' import React, { Component } from 'react' @@ -15,14 +15,16 @@ import Icon from '../util/icon' import SpanWithSpace from '../util/span-with-space' import Strong from '../util/strong-text' +import DepartureTime from './departure-time' import ViewStopButton from './view-stop-button' -const { getUserTimezone } = coreUtils.time +const { getCurrentDate } = coreUtils.time class TripViewer extends Component { static propTypes = { findTrip: apiActions.findTrip.type, hideBackButton: PropTypes.bool, + homeTimezone: PropTypes.string, setViewedTrip: uiActions.setViewedTrip.type, tripData: PropTypes.object, viewedTrip: PropTypes.object @@ -39,7 +41,10 @@ class TripViewer extends Component { } render() { - const { hideBackButton, tripData, viewedTrip } = this.props + const { hideBackButton, homeTimezone, tripData, viewedTrip } = this.props + const startOfDay = toDate(getCurrentDate(homeTimezone), { + timeZone: homeTimezone + }) return (
@@ -121,20 +126,13 @@ class TripViewer extends Component { highlightClass = 'strip-map-highlight-last' } - // Convert to unix (millisceonds) for FormattedTime - // Use timezone to avoid spying on startOf in future tests - const userTimezone = getUserTimezone() - const departureMoment = moment().tz(userTimezone).startOf('day') - departureMoment.add(tripData.stopTimes[i].scheduledDeparture, 's') - const departureTimestamp = departureMoment.valueOf() - return (
{/* the departure time */}
-
@@ -173,6 +171,7 @@ class TripViewer extends Component { const mapStateToProps = (state) => { const viewedTrip = state.otp.ui.viewedTrip return { + homeTimezone: state.otp.config.homeTimezone, tripData: state.otp.transitIndex.trips[viewedTrip.tripId], viewedTrip } From 6e100a9668589a8df638dd676f2871f80eb5fd2b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:31:41 -0400 Subject: [PATCH 0287/1425] refactor(TripViewer): Remove unnecessary lint exceptions. --- lib/components/viewers/trip-viewer.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/components/viewers/trip-viewer.js b/lib/components/viewers/trip-viewer.js index 3b49db26f..e2ed80064 100644 --- a/lib/components/viewers/trip-viewer.js +++ b/lib/components/viewers/trip-viewer.js @@ -1,6 +1,4 @@ -// FIXME: Remove this eslint rule exception. -/* eslint-disable jsx-a11y/label-has-for */ -import { Button, Label } from 'react-bootstrap' +import { Label as BsLabel, Button } from 'react-bootstrap' import { connect } from 'react-redux' import { FormattedMessage } from 'react-intl' import { toDate } from 'date-fns-tz' @@ -88,17 +86,17 @@ class TripViewer extends Component { {/* Wheelchair/bike accessibility badges, if applicable */}

{tripData.wheelchairAccessible === 1 && ( - + )} {tripData.bikesAllowed === 1 && ( - + )}

From daadfb5b98f6d989796a6993687d1866e3add049 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 16 Jun 2022 14:21:06 -0700 Subject: [PATCH 0288/1425] refactor: clean up / sort props --- .../narrative/metro/metro-itinerary.tsx | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx index ee6f92e47..456fd9692 100644 --- a/lib/components/narrative/metro/metro-itinerary.tsx +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -42,20 +42,19 @@ const { ItineraryView } = uiActions const ItineraryWrapper = styled.div.attrs((props) => { return { 'aria-label': props['aria-label'] } })` - color: #333; - padding: 0; - border-bottom: 0.1ch solid #33333333; + color: #333; display: grid; /* We don't use grid here, but "block" and "inline" cause problems with Firefox */ + padding: 0; ` const DepartureTimes = styled.span` + color: #0909098f; font-size: 14px; - width: 100%; - text-overflow: ellipsis; overflow: hidden; + text-overflow: ellipsis; white-space: pre; - color: #0909098f; + width: 100%; .first { color: #090909ee; @@ -63,15 +62,15 @@ const DepartureTimes = styled.span` ` const PrimaryInfo = styled.span` - font-weight: 600; - font-size: 22px; color: #000000cc; + font-size: 22px; + font-weight: 600; text-align: right; ` const SecondaryInfo = styled.span` - font-size: 12px; color: #090909cc; + font-size: 12px; opacity: 0.7; text-align: right; ` @@ -105,7 +104,6 @@ const ItineraryGrid = styled.div` display: grid; grid-template-columns: repeat(10, 1fr); grid-template-rows: minmax(8px, fit-content); - padding: 10px 1em; ${DepartureTimes} { @@ -113,26 +111,25 @@ const ItineraryGrid = styled.div` } ${Routes} { - grid-row: 1 / 8; grid-column: 1 / 8; + grid-row: 1 / 8; } ${PrimaryInfo} { - grid-row: 1 / span 3; grid-column: 8 / 11; + grid-row: 1 / span 3; } ${Spacer} { - grid-row: span 1; grid-column: 8 / 11; + grid-row: span 1; } ${SecondaryInfo} { grid-column: 8 / 11; grid-row: span 2; - - text-overflow: ellipsis; overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; &.flex { @@ -154,20 +151,18 @@ const ItineraryGrid = styled.div` ` const ItineraryGridSmall = styled.div` + border-radius: 10px; display: grid; - grid-template-columns: 1fr 3fr; gap: 0 2px; + grid-template-columns: 1fr 3fr; grid-template-rows: repeat(10, 8px); - padding: 10px 1em; - border-radius: 10px; - ${PrimaryInfo} { + font-size: 100%; grid-column: 2; grid-row: 2 / 5; line-height: 1; - font-size: 100%; } ${SecondaryInfo} { From b91061db2f0153389608cde723cf12942f290e86 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 16 Jun 2022 14:21:45 -0700 Subject: [PATCH 0289/1425] refactor: clean up / sort props --- .../narrative/metro/route-block.tsx | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/lib/components/narrative/metro/route-block.tsx b/lib/components/narrative/metro/route-block.tsx index a4aa8b735..849706b8f 100644 --- a/lib/components/narrative/metro/route-block.tsx +++ b/lib/components/narrative/metro/route-block.tsx @@ -22,27 +22,27 @@ type Props = { } const Wrapper = styled.span` - display: grid; align-items: center; column-gap: 4px; - margin-left: -4px; /* counteract gap */ + display: grid; grid-template-columns: fit-content(100%); + margin-left: -4px; /* counteract gap */ footer { align-self: center; - justify-self: center; - grid-column: 1 / span 2; font-size: 12px; + grid-column: 1 / span 2; + justify-self: center; opacity: 0.7; } ` const MultiWrapper = styled.span<{ multi?: boolean }>` - flex-direction: row; display: flex; + flex-direction: row; gap: 5px; - grid-row: 1; grid-column: 2; + grid-row: 1; ${({ multi }) => multi @@ -50,32 +50,31 @@ const MultiWrapper = styled.span<{ multi?: boolean }>` /* All Route blocks start with only right side triangulated */ section { clip-path: polygon(0% 0, 100% 0%, 75% 100%, 0% 100%); - padding-right: 10px; - padding-left: 5px; margin-right: 2px; max-width: 100px; + padding-left: 5px; + padding-right: 10px; } section:first-of-type { - border-top-right-radius: 0!important; border-bottom-right-radius: 0!important; + border-top-right-radius: 0!important; } /* Middle route block(s), with both sides triangulated */ section:not(:first-of-type):not(:last-of-type) { + border-radius: 0; clip-path: polygon(25% 0, 100% 0%, 75% 100%, 0% 100%); + margin-left: -10px; padding-left: 11px; padding-right: 11px; - margin-left: -10px; - border-radius: 0; } /* Last route block, with only left side triangulated */ section:last-of-type { + border-bottom-left-radius: 0!important; + border-top-left-radius: 0!important; clip-path: polygon(25% 0, 100% 0%, 100% 100%, 0% 100%); - padding-left: 10px; margin-left: -10px; + padding-left: 10px; padding-right: 5px; - - border-bottom-left-radius: 0!important; - border-top-left-radius: 0!important; } ` : ''} @@ -86,21 +85,20 @@ const LegIconWrapper = styled.span` ` const MultiRouteLongName = styled.div` - display: flex; - justify-content: space-between; align-items: baseline; + align-self: center; + display: flex; gap: 5px; - - grid-row: 1; grid-column: 3; - align-self: center; + grid-row: 1; + justify-content: space-between; ` const Divider = styled.span` - display: flex; align-items: center; - opacity: 0.4; + display: flex; margin: 0 -5px; + opacity: 0.4; ` const RouteBlock = ({ From ef60fe2f667e46bea640a92bfa672f6949f1da16 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 17 Jun 2022 15:30:32 -0400 Subject: [PATCH 0290/1425] chore(deps): Upgrade trip-form to 1.11.4. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2cd1e200f..30ab7dd78 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@opentripplanner/transit-vehicle-overlay": "^2.3.1", "@opentripplanner/transitive-overlay": "^1.1.3", "@opentripplanner/trip-details": "^2.0.0", - "@opentripplanner/trip-form": "^1.11.3", + "@opentripplanner/trip-form": "^1.11.4", "@opentripplanner/trip-viewer-overlay": "^1.1.1", "@opentripplanner/vehicle-rental-overlay": "^1.4.2", "blob-stream": "^0.1.3", diff --git a/yarn.lock b/yarn.lock index e03291764..f7ca07d70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2450,10 +2450,10 @@ flat "^5.0.2" velocity-react "^1.4.3" -"@opentripplanner/trip-form@^1.11.3": - version "1.11.3" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.11.3.tgz#a341f169102eacd32037e29d596adde3a14becd7" - integrity sha512-InP6VCRdeKFQ7AKyQEzW9cdNmO4hk+vGdKyY4hGscaUD+G4jY7DVCyFIejxz5olhBRiQWeA2cF5idWLxa1PyYg== +"@opentripplanner/trip-form@^1.11.4": + version "1.11.4" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.11.4.tgz#6a9c672f6b4c6e28d5af3c212dadd7d657bcfe20" + integrity sha512-Ej/gYjJjqIUIxLvOECKjyUUY/FxDPy8D9hechA2mLBisZVQDfPPXOu5JThNuTkwMbtAGoTCPN3x3iIBrGAs7dw== dependencies: "@opentripplanner/core-utils" "^4.11.2" "@opentripplanner/icons" "^1.2.2" From 284967b456910192ab890afea3f9163957f0abc5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:34:54 -0400 Subject: [PATCH 0291/1425] test(i18n): Add i18n checks from @opentripplanner/scripts. --- package.json | 6 +- yarn.lock | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 335 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9180e3375..16c5ba9df 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "a11y-test": "jest a11y/ --runInBand --force-exit", "build": "craco build", + "check:i18n": "node node_modules/@opentripplanner/scripts/lib/run-validate-i18n.js lib i18n/en-US.yml i18n/fr.yml", "unit": "jest __tests__/", "lint": "lint-staged", "lint-all": "eslint lib __tests__ a11y --quiet", @@ -12,7 +13,7 @@ "prepublishOnly": "pinst --disable", "postpublish": "pinst --enable", "prestart": "yarn", - "test": "yarn run lint && yarn run typecheck && yarn run unit", + "test": "yarn run lint && yarn run typecheck && yarn check:i18n && yarn run unit", "typecheck": "yarn tsc", "semantic-release": "semantic-release", "start": "craco start", @@ -41,12 +42,12 @@ "@opentripplanner/geocoder": "^1.2.1", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^1.2.0", - "@opentripplanner/route-viewer-overlay": "^1.4.0", "@opentripplanner/itinerary-body": "^3.0.4", "@opentripplanner/location-field": "1.12.2", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.4", "@opentripplanner/printable-itinerary": "^2.0.1", + "@opentripplanner/route-viewer-overlay": "^1.4.0", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^4.0.0", "@opentripplanner/transit-vehicle-overlay": "^2.3.1", @@ -124,6 +125,7 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", + "@opentripplanner/scripts": "^1.0.1", "@opentripplanner/types": "^1.2.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index e03291764..539eaff94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -342,6 +342,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.8.tgz#61c243a3875f7d0b0962b0543a33ece6ff2f1f17" integrity sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw== +"@babel/parser@^7.16.4": + version "7.18.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" + integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e" @@ -1472,6 +1477,36 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@formatjs/cli@^4.2.33": + version "4.8.4" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-4.8.4.tgz#c4f4e589b8c77c950b659948dbf0e877a4d910fd" + integrity sha512-zZI8QYVl5CHaT6j9OHjS+0mMnWzopBVH0un4n5b4IhIJRzIKnxwFTkxBp5Ifqj6FntrwzIGqP+D6v8u7MPYsmw== + dependencies: + "@formatjs/icu-messageformat-parser" "2.1.0" + "@formatjs/ts-transformer" "3.9.4" + "@types/estree" "^0.0.50" + "@types/fs-extra" "^9.0.1" + "@types/json-stable-stringify" "^1.0.32" + "@types/node" "14" + "@vue/compiler-core" "^3.2.23" + chalk "^4.0.0" + commander "8" + fast-glob "^3.2.7" + fs-extra "10" + json-stable-stringify "^1.0.1" + loud-rejection "^2.2.0" + tslib "^2.1.0" + typescript "^4.5" + vue "^3.2.23" + +"@formatjs/ecma402-abstract@1.11.4": + version "1.11.4" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz#b962dfc4ae84361f9f08fbce411b4e4340930eda" + integrity sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw== + dependencies: + "@formatjs/intl-localematcher" "0.2.25" + tslib "^2.1.0" + "@formatjs/ecma402-abstract@1.9.8": version "1.9.8" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.9.8.tgz#f3dad447fbc7f063f88e2a148b7a353161740e74" @@ -1496,6 +1531,15 @@ "@formatjs/icu-skeleton-parser" "1.2.12" tslib "^2.1.0" +"@formatjs/icu-messageformat-parser@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz#a54293dd7f098d6a6f6a084ab08b6d54a3e8c12d" + integrity sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw== + dependencies: + "@formatjs/ecma402-abstract" "1.11.4" + "@formatjs/icu-skeleton-parser" "1.3.6" + tslib "^2.1.0" + "@formatjs/icu-skeleton-parser@1.2.12": version "1.2.12" resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.2.12.tgz#45426eb1448c0c08c931eb9f0672283c0e4d0062" @@ -1504,6 +1548,14 @@ "@formatjs/ecma402-abstract" "1.9.8" tslib "^2.1.0" +"@formatjs/icu-skeleton-parser@1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz#4ce8c0737d6f07b735288177049e97acbf2e8964" + integrity sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg== + dependencies: + "@formatjs/ecma402-abstract" "1.11.4" + tslib "^2.1.0" + "@formatjs/intl-displaynames@5.2.3": version "5.2.3" resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-5.2.3.tgz#a0cebc81e89c5414177ade71a2f2388d799ee6e8" @@ -1529,6 +1581,13 @@ dependencies: tslib "^2.1.0" +"@formatjs/intl-localematcher@0.2.25": + version "0.2.25" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz#60892fe1b271ec35ba07a2eb018a2dd7bca6ea3a" + integrity sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA== + dependencies: + tslib "^2.1.0" + "@formatjs/intl@1.14.1": version "1.14.1" resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.14.1.tgz#03e12f7e2cf557defdd1a5aeb1c143efb8cfbc7b" @@ -1542,6 +1601,17 @@ intl-messageformat "9.9.1" tslib "^2.1.0" +"@formatjs/ts-transformer@3.9.4": + version "3.9.4" + resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-3.9.4.tgz#14b43628d082cb8cd8bc15c4893197b59903ec2c" + integrity sha512-S5q/zsTodaKtxVxNvbRQ9APenJtm5smXE76usS+5yF2vWQdZHkagmOKWfgvfIbesP4SR2B+i3koqlnlpqSIp5w== + dependencies: + "@formatjs/icu-messageformat-parser" "2.1.0" + "@types/node" "14 || 16 || 17" + chalk "^4.0.0" + tslib "^2.1.0" + typescript "^4.5" + "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" @@ -2397,6 +2467,17 @@ point-in-polygon "^1.1.0" prop-types "^15.7.2" +"@opentripplanner/scripts@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/scripts/-/scripts-1.0.1.tgz#23b7eb0fa524bc38ddcb63766897b4695688ae20" + integrity sha512-PI8chuZQWkhNLI6rBhVEJeui9dHjhC1pQrn/2dDiphzNq4vSH57BGDyWgQuWRcEUOgg1+NU7ycV80U/wfVaDGg== + dependencies: + "@formatjs/cli" "^4.2.33" + flat "^5.0.2" + glob "^8.0.3" + glob-promise "^4.2.2" + js-yaml "^4.1.0" + "@opentripplanner/stop-viewer-overlay@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@opentripplanner/stop-viewer-overlay/-/stop-viewer-overlay-1.1.1.tgz#58755b0335ae7bab4372160582f5cbf649cc9d31" @@ -3189,7 +3270,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*": +"@types/estree@*", "@types/estree@^0.0.50": version "0.0.50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== @@ -3199,6 +3280,13 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/fs-extra@^9.0.1": + version "9.0.13" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== + dependencies: + "@types/node" "*" + "@types/geojson@7946.0.8": version "7946.0.8" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" @@ -3212,6 +3300,14 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/glob@^7.1.3": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -3277,6 +3373,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/json-stable-stringify@^1.0.32": + version "1.0.34" + resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75" + integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -3302,6 +3403,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@types/node@14": + version "14.18.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41" + integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q== + +"@types/node@14 || 16 || 17": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -3591,6 +3702,96 @@ "@typescript-eslint/types" "4.30.0" eslint-visitor-keys "^2.0.0" +"@vue/compiler-core@3.2.37", "@vue/compiler-core@^3.2.23": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a" + integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + source-map "^0.6.1" + +"@vue/compiler-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5" + integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ== + dependencies: + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/compiler-sfc@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4" + integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.37" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-ssr" "3.2.37" + "@vue/reactivity-transform" "3.2.37" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + magic-string "^0.25.7" + postcss "^8.1.10" + source-map "^0.6.1" + +"@vue/compiler-ssr@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff" + integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw== + dependencies: + "@vue/compiler-dom" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/reactivity-transform@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca" + integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg== + dependencies: + "@babel/parser" "^7.16.4" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" + estree-walker "^2.0.2" + magic-string "^0.25.7" + +"@vue/reactivity@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848" + integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A== + dependencies: + "@vue/shared" "3.2.37" + +"@vue/runtime-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3" + integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ== + dependencies: + "@vue/reactivity" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/runtime-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd" + integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw== + dependencies: + "@vue/runtime-core" "3.2.37" + "@vue/shared" "3.2.37" + csstype "^2.6.8" + +"@vue/server-renderer@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc" + integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA== + dependencies: + "@vue/compiler-ssr" "3.2.37" + "@vue/shared" "3.2.37" + +"@vue/shared@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702" + integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw== + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -4130,6 +4331,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + argv-formatter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9" @@ -4170,6 +4376,11 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -5655,6 +5866,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -6627,6 +6845,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@8: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + commander@^2.11.0, commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -7439,6 +7662,11 @@ csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e" integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A== +csstype@^2.6.8: + version "2.6.20" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" + integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== + csstype@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" @@ -7453,6 +7681,13 @@ currency-formatter@^1.4.2: locale-currency "0.0.2" object-assign "^4.1.1" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== + dependencies: + array-find-index "^1.0.1" + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -8837,6 +9072,11 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -9120,7 +9360,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.1.1, fast-glob@^3.2.9: +fast-glob@^3.1.1, fast-glob@^3.2.7, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -9584,6 +9824,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@10: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@8.1.0, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -9860,6 +10109,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-promise@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-4.2.2.tgz#15f44bcba0e14219cd93af36da6bb905ff007877" + integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== + dependencies: + "@types/glob" "^7.1.3" + glob@7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" @@ -9884,6 +10140,17 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -12307,6 +12574,13 @@ js-yaml@^3.13.1, js-yaml@^3.9.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -13127,6 +13401,14 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" +loud-rejection@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-2.2.0.tgz#4255eb6e9c74045b0edc021fa7397ab655a8517c" + integrity sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ== + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.2" + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -13540,6 +13822,13 @@ minimatch@3.0.4, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0, minimist-options@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -13753,6 +14042,11 @@ nanoid@^3.1.23: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -14897,6 +15191,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -15963,6 +16262,15 @@ postcss@^8.1.0: nanoid "^3.1.23" source-map-js "^0.6.2" +postcss@^8.1.10: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -18269,6 +18577,11 @@ source-map-js@^0.6.2: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -19552,6 +19865,11 @@ typescript@^4.4.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== +typescript@^4.5: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + ua-parser-js@^0.7.18: version "0.7.28" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" @@ -19975,6 +20293,17 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vue@^3.2.23: + version "3.2.37" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e" + integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ== + dependencies: + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-sfc" "3.2.37" + "@vue/runtime-dom" "3.2.37" + "@vue/server-renderer" "3.2.37" + "@vue/shared" "3.2.37" + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" From 6670bcb1c5c9417724fe23e97e33b9dbd86fbb28 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:47:54 -0400 Subject: [PATCH 0292/1425] chore(i18n): Remove unused messages. --- i18n/en-US.yml | 1 - i18n/fr.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index fccb278cf..2f70b595f 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -649,7 +649,6 @@ common: forms: back: Back cancel: Cancel - clear: Clear close: Close error: error! defaultValue: "{value} (default)" diff --git a/i18n/fr.yml b/i18n/fr.yml index f64325af3..78a336ece 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -628,7 +628,6 @@ common: forms: back: Retour cancel: Annuler - clear: Effacer close: Fermer error: erreur ! defaultValue: "{value} (défaut)" From 93ce1d67362de7e7a11478c731c5283bba640837 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:12:45 -0400 Subject: [PATCH 0293/1425] refactor(DefaultItinerary): Remove unused function arg --- lib/components/narrative/default/default-itinerary.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 4d0b13231..ac7fdf4fc 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -108,9 +108,7 @@ const ITINERARY_ATTRIBUTES = [ alias: 'best', id: 'duration', order: 0, - render: (itinerary /*, options */) => ( - - ) + render: (itinerary) => }, { alias: 'departureTime', From 87cba3d881bc92a2e9ae650da68b9cd7af985473 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:00:28 -0400 Subject: [PATCH 0294/1425] test(percy): Update OTP request time to match mock response. --- percy/percy.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/percy/percy.test.js b/percy/percy.test.js index 7febb8252..f3a68c6a5 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -85,7 +85,7 @@ beforeAll(async () => { const client = await targetPage.target().createCDPSession() await client.send('Runtime.evaluate', { expression: - 'Date.now = function() { return 1646835742000; }; Date.getTime = function() { return 1646835742000; }' + 'Date.now = function() { return 1646832142000; }; Date.getTime = function() { return 1646832142000; }' }) }) } catch (error) { @@ -120,7 +120,7 @@ jest.setTimeout(600000) /* test('OTP-RR Fixed Routes', async () => { const transitive = await loadPath( - '/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-03-09&time=09%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900' + '/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-03-09&time=08%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900' ) await percySnapshotWithWait(transitive, 'Itinerary (with transitive)', true) @@ -142,7 +142,7 @@ test('OTP-RR', async () => { // Plan a trip await page.goto( - `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-05-09&time=09%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900` + `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-05-09&time=08%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900` ) await page.waitForNavigation({ waitUntil: 'networkidle2' }) await page.waitForSelector('.title') From 499cc2955fe49238ba045aaa9f27ea5608b23ee5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 22 Jun 2022 11:46:13 -0400 Subject: [PATCH 0295/1425] Revert "test(percy): Update OTP request time to match mock response." This reverts commit 87cba3d881bc92a2e9ae650da68b9cd7af985473. --- percy/percy.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/percy/percy.test.js b/percy/percy.test.js index f3a68c6a5..7febb8252 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -85,7 +85,7 @@ beforeAll(async () => { const client = await targetPage.target().createCDPSession() await client.send('Runtime.evaluate', { expression: - 'Date.now = function() { return 1646832142000; }; Date.getTime = function() { return 1646832142000; }' + 'Date.now = function() { return 1646835742000; }; Date.getTime = function() { return 1646835742000; }' }) }) } catch (error) { @@ -120,7 +120,7 @@ jest.setTimeout(600000) /* test('OTP-RR Fixed Routes', async () => { const transitive = await loadPath( - '/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-03-09&time=08%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900' + '/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-03-09&time=09%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900' ) await percySnapshotWithWait(transitive, 'Itinerary (with transitive)', true) @@ -142,7 +142,7 @@ test('OTP-RR', async () => { // Plan a trip await page.goto( - `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-05-09&time=08%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900` + `http://localhost:${MOCK_SERVER_PORT}/#/?ui_activeSearch=5rzujqghc&ui_activeItinerary=0&fromPlace=Opus Music Store%2C Decatur%2C GA%3A%3A33.77505%2C-84.300178&toPlace=Five Points Station (MARTA Stop ID 908981)%3A%3A33.753837%2C-84.391397&date=2022-05-09&time=09%3A58&arriveBy=false&mode=WALK%2CBUS%2CSUBWAY%2CTRAM%2CFLEX_EGRESS%2CFLEX_ACCESS%2CFLEX_DIRECT&showIntermediateStops=true&maxWalkDistance=1207&optimize=QUICK&walkSpeed=1.34&ignoreRealtimeUpdates=true&wheelchair=false&numItineraries=3&otherThanPreferredRoutesPenalty=900` ) await page.waitForNavigation({ waitUntil: 'networkidle2' }) await page.waitForSelector('.title') From b83817c4bee0bbfd30fb3821cdd44d239993e433 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:25:30 -0400 Subject: [PATCH 0296/1425] test(percy): Change ambient date/time to 2022-03-14T14:22:22Z This date is after the US daylight saving change. --- percy/percy.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/percy/percy.test.js b/percy/percy.test.js index 7febb8252..739e43630 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -79,13 +79,13 @@ beforeAll(async () => { // headless: false }) - // Fix time + // Fix time to Monday, March 14, 2022 14:22:22 GMT (10:22:22 AM EDT). browser.on('targetchanged', async (target) => { const targetPage = await target.page() const client = await targetPage.target().createCDPSession() await client.send('Runtime.evaluate', { expression: - 'Date.now = function() { return 1646835742000; }; Date.getTime = function() { return 1646835742000; }' + 'Date.now = function() { return 1647267742000; }; Date.getTime = function() { return 1647267742000; }' }) }) } catch (error) { From df521871feb1f8896b2cd74c05be8f6489a0681d Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 23 Jun 2022 16:09:24 +0100 Subject: [PATCH 0297/1425] refactor(metro-ui): account for new lint rules caused by merge --- i18n/en-US.yml | 1 - i18n/fr.yml | 8 ++++++++ lib/components/viewers/next-arrival-for-pattern.tsx | 4 ++-- lib/components/viewers/pattern-row.tsx | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 33c224dea..c66392bad 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -226,7 +226,6 @@ components: streets: Streets MetroUI: leaveAt: You leave - arriveAt: You arrive arriveAtTime: "Arrive at {time}" timeWalking: "{time} walking" fromStop: "from {stop}" diff --git a/i18n/fr.yml b/i18n/fr.yml index 78a336ece..c80cde446 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -212,6 +212,13 @@ components: satellite: Satellite stops: Arrêts et stations streets: Plan des rues + MetroUI: + leaveAt: You leave + arriveAtTime: "Arrive at {time}" + timeWalking: "{time} walking" + fromStop: "from {stop}" + orAlternatives: or other routes in the same direction + itineraryDescription: "{time} itinerary using {routes}" MobileOptions: header: Options de recherche ModeDropdown: @@ -755,6 +762,7 @@ config: # Default values for Flex Indicator (set in configuration as well) flex: flex-service: Service Flex + flex-service-colon: "Service Flex:" both: Plus de détails au bas de l'itinéraire call-ahead: Appelez pour réserver continuous-dropoff: Demandez l'arrêt au conducteur \ No newline at end of file diff --git a/lib/components/viewers/next-arrival-for-pattern.tsx b/lib/components/viewers/next-arrival-for-pattern.tsx index 6d14a707c..cd175acc2 100644 --- a/lib/components/viewers/next-arrival-for-pattern.tsx +++ b/lib/components/viewers/next-arrival-for-pattern.tsx @@ -9,7 +9,7 @@ import { stopTimeComparator } from '../../util/viewer' import DefaultRouteRenderer from '../narrative/metro/default-route-renderer' -import type { Pattern, StopTime } from '../util/types' +import type { Pattern, Time } from '../util/types' import StopTimeCell from './stop-time-cell' @@ -18,7 +18,7 @@ type Props = { intl: IntlShape pattern: Pattern route: Route - stopTimes: StopTime[] + stopTimes: Time[] stopViewerArriving: React.ReactNode stopViewerConfig: { numberOfDepartures: number } } diff --git a/lib/components/viewers/pattern-row.tsx b/lib/components/viewers/pattern-row.tsx index 4bc779755..d37442687 100644 --- a/lib/components/viewers/pattern-row.tsx +++ b/lib/components/viewers/pattern-row.tsx @@ -9,7 +9,7 @@ import { generateFakeLegForRouteRenderer, stopTimeComparator } from '../../util/viewer' -import { Pattern, StopTime } from '../util/types' +import { Pattern, Time } from '../util/types' import DefaultRouteRenderer from '../narrative/metro/default-route-renderer' import Icon from '../util/icon' import Strong from '../util/strong-text' @@ -22,7 +22,7 @@ type Props = { intl: IntlShape pattern: Pattern route: Route - stopTimes: StopTime[] + stopTimes: Time[] stopViewerArriving: React.ReactNode stopViewerConfig: { numberOfDepartures: number } } From 3fd9750d6363e061e561e390c25bf2a73be976c2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 23 Jun 2022 16:34:13 +0100 Subject: [PATCH 0298/1425] refactor(metro-ui): address pr feedback, remove tnc fare support in fare preview --- .../line-itin/connected-itinerary-body.js | 2 +- .../narrative/metro/metro-itinerary.tsx | 22 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index ded9460a6..6091457eb 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -28,7 +28,7 @@ import TransitLegSubheader from './connected-transit-leg-subheader' const noop = () => {} const ItineraryBodyContainer = styled.div` - padding: 0px 0px; + padding: 0px 10px; ` const StyledItineraryBody = styled(ItineraryBody)` diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx index 456fd9692..6d867a8b9 100644 --- a/lib/components/narrative/metro/metro-itinerary.tsx +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -239,13 +239,11 @@ class MetroItinerary extends NarrativeItinerary { const { isCallAhead, isContinuousDropoff, isFlexItinerary, phone } = getFlexAttirbutes(itinerary) - const { fareCurrency, maxTNCFare, minTNCFare, transitFare } = getFare( + const { fareCurrency, transitFare } = getFare( itinerary, defaultFareKey, currency ) - const minTotalFare = minTNCFare * 100 + transitFare - const maxTotalFare = maxTNCFare * 100 + transitFare const firstTransitStop = getFirstTransitLegStop(itinerary) @@ -349,22 +347,14 @@ class MetroItinerary extends NarrativeItinerary { )} - {maxTotalFare === null || maxTotalFare < 0 ? ( + {transitFare === null || transitFare < 0 ? ( ) : ( - ), + // TODO: re-implement TNC fares for metro UI? + maxTotalFare: null, minTotalFare: ( extends NarrativeItinerary { // This isn't a "real" style prop // eslint-disable-next-line react/style-prop-object style="currency" - value={minTotalFare / 100} + value={transitFare / 100} /> ), - useMaxFare: minTotalFare !== maxTotalFare + useMaxFare: false }} /> )} From 364f5ca186662b8fefd74c30c590b5e694afcb13 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 24 Jun 2022 12:44:30 +0100 Subject: [PATCH 0299/1425] refactor(metro-ui): show accessibility label correctly even on small itinerary block --- lib/components/narrative/default/itinerary.css | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/components/narrative/default/itinerary.css b/lib/components/narrative/default/itinerary.css index c4a53e00f..039fbc7ca 100644 --- a/lib/components/narrative/default/itinerary.css +++ b/lib/components/narrative/default/itinerary.css @@ -46,12 +46,19 @@ } /* Sadly this hack is the only way to select the AccessibilityRating */ -.otp .option.metro-itin button .itin-wrapper div:not(.itinerary-grid) { +.otp .option.metro-itin button .itin-wrapper div:not(.itinerary-grid), +.otp .option.metro-itin button .itin-wrapper-small div:not(.itinerary-grid) { max-width: inherit; border-radius: 0; margin-top: 0; } -.otp .option.metro-itin button .itin-wrapper div:not(.itinerary-grid) svg { +.otp .option.metro-itin button .itin-wrapper div:not(.itinerary-grid) svg, +.otp + .option.metro-itin + button + .itin-wrapper-small + div:not(.itinerary-grid) + svg { flex: inherit !important; } .otp .option.metro-itin button .itin-wrapper, From 894b82e25bbb8ee4c26c39e5ea5d2a90282c27c4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:34:53 -0400 Subject: [PATCH 0300/1425] style(Remove es-lint exception and fix prettier issue.): --- lib/components/narrative/default/default-itinerary.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 9466fed1d..021a3f315 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -1,6 +1,5 @@ // This is a large file being touched by open PRs. It should be typescripted // in a separate PR. -/* eslint-disable */ /* eslint-disable react/prop-types */ import { AccessibilityRating } from '@opentripplanner/itinerary-body' import { connect } from 'react-redux' @@ -89,8 +88,7 @@ function ItineraryDescription({ itinerary }) { // If custom TransitModes have been defined for the given mode/leg, attempt to use them, // otherwise fall back on built-in mode rendering. - transitMode = TransitModes - ? ( + transitMode = TransitModes ? ( ) : ( From cb12d2c25e8366a53f7aaf63a1cc9860e3f90bbe Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 27 Jun 2022 11:50:05 +0100 Subject: [PATCH 0301/1425] feat: migrate from Leaflet to MapLibreGL BREAKING CHANGE: Leaflet is removed, some overlays are removed --- .../__snapshots__/create-otp-reducer.js.snap | 1 - index.css | 2 +- lib/actions/apiV1.js | 4 +- lib/actions/map.js | 2 +- lib/actions/ui.js | 1 - lib/components/map/bounds-updating-overlay.js | 213 ------------ .../map/connected-park-and-ride-overlay.tsx | 2 +- .../map/connected-route-viewer-overlay.js | 5 +- lib/components/map/connected-stop-marker.js | 36 -- .../map/connected-stop-viewer-overlay.js | 152 +++----- lib/components/map/connected-stops-overlay.js | 18 +- .../map/connected-transit-vehicle-overlay.js | 140 -------- .../map/connected-transitive-overlay.js | 2 +- .../map/connected-vehicle-rental-overlay.js | 32 +- lib/components/map/default-map.js | 44 +-- ...t-marker.js => elevation-point-marker.tsx} | 41 ++- lib/components/map/enhanced-stop-marker.js | 113 +++--- lib/components/map/gtfs-rt-vehicle-overlay.js | 237 ------------- lib/components/map/osm-base-layer.js | 14 - lib/components/map/route-preview-overlay.js | 95 ----- lib/components/map/tile-overlay.js | 17 - lib/components/map/zipcar-overlay.js | 142 -------- lib/components/viewers/RouteRow.js | 10 +- lib/index.js | 6 - lib/reducers/create-otp-reducer.js | 4 - lib/util/itinerary.js | 13 - package.json | 26 +- yarn.lock | 328 +++++++++++++----- 28 files changed, 434 insertions(+), 1266 deletions(-) delete mode 100644 lib/components/map/bounds-updating-overlay.js delete mode 100644 lib/components/map/connected-stop-marker.js delete mode 100644 lib/components/map/connected-transit-vehicle-overlay.js rename lib/components/map/{elevation-point-marker.js => elevation-point-marker.tsx} (63%) delete mode 100644 lib/components/map/gtfs-rt-vehicle-overlay.js delete mode 100644 lib/components/map/osm-base-layer.js delete mode 100644 lib/components/map/route-preview-overlay.js delete mode 100644 lib/components/map/tile-overlay.js delete mode 100644 lib/components/map/zipcar-overlay.js diff --git a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap index 1960b703e..685ad7f55 100644 --- a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap +++ b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap @@ -9,7 +9,6 @@ Object { "mobile": "BOTH_LOCATIONS_CHANGED", }, "debouncePlanTimeMs": 0, - "focusRoute": false, "homeTimezone": "America/Los_Angeles", "language": Object {}, "onTimeThresholdSeconds": 60, diff --git a/index.css b/index.css index 86d599b91..87c1943ba 100644 --- a/index.css +++ b/index.css @@ -1,6 +1,6 @@ @import url(node_modules/bootstrap/dist/css/bootstrap.min.css); -@import url(https://unpkg.com/leaflet@1.0.3/dist/leaflet.css); +@import url(node_modules/maplibre-gl/dist/maplibre-gl.css); @import url(node_modules/font-awesome/css/font-awesome.css); @import url(node_modules/transitive-js/lib/transitive.css); diff --git a/lib/actions/apiV1.js b/lib/actions/apiV1.js index 24e86229c..68103d0ff 100644 --- a/lib/actions/apiV1.js +++ b/lib/actions/apiV1.js @@ -1,4 +1,4 @@ -import L from 'leaflet' +import maplibregl from 'maplibre-gl' import qs from 'qs' import { @@ -56,7 +56,7 @@ export function vehicleRentalQuery( function findNearbyAmenities({ lat, lon, radius = 300 }, stopId) { return function (dispatch, getState) { - const bounds = L.latLng(lat, lon).toBounds(radius) + const bounds = new maplibregl.LngLat(lon, lat).toBounds(radius) const { lat: low, lng: left } = bounds.getSouthWest() const { lat: up, lng: right } = bounds.getNorthEast() dispatch( diff --git a/lib/actions/map.js b/lib/actions/map.js index b92c10acd..56080a3a1 100644 --- a/lib/actions/map.js +++ b/lib/actions/map.js @@ -148,7 +148,7 @@ export const setElevationPoint = createAction('SET_ELEVATION_POINT') export const setMapPopupLocation = createAction('SET_MAP_POPUP_LOCATION') export function setMapPopupLocationAndGeocode(mapEvent) { - const location = coreUtils.map.constructLocation(mapEvent.latlng) + const location = coreUtils.map.constructLocation(mapEvent.lngLat) return function (dispatch, getState) { dispatch(setMapPopupLocation({ location })) getGeocoder(getState().otp.config.geocoder) diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 021eb7d4b..6f50b72b6 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -62,7 +62,6 @@ const viewStop = createAction('SET_VIEWED_STOP') export const setHoveredStop = createAction('SET_HOVERED_STOP') export const setViewedTrip = createAction('SET_VIEWED_TRIP') const viewRoute = createAction('SET_VIEWED_ROUTE') -export const unfocusRoute = createAction('UNFOCUS_ROUTE') export const toggleAutoRefresh = createAction('TOGGLE_AUTO_REFRESH') const setPreviousItineraryView = createAction('SET_PREVIOUS_ITINERARY_VIEW') diff --git a/lib/components/map/bounds-updating-overlay.js b/lib/components/map/bounds-updating-overlay.js deleted file mode 100644 index 93c32d0c3..000000000 --- a/lib/components/map/bounds-updating-overlay.js +++ /dev/null @@ -1,213 +0,0 @@ -import { connect } from 'react-redux' -import { MapLayer, withLeaflet } from 'react-leaflet' -import coreUtils from '@opentripplanner/core-utils' -import isEqual from 'lodash.isequal' -import L from 'leaflet' - -import { getActiveItinerary, getActiveSearch } from '../../util/state' -import { - getLeafletItineraryBounds, - getLeafletLegBounds -} from '../../util/itinerary' - -/** - * Utility to extend input Leaflet bounds to include the list of places. - */ -function extendBoundsByPlaces(bounds, places = []) { - places - .filter((place) => place) - .forEach((place) => { - const coords = [place.lat, place.lon] - if (coreUtils.map.isValidLatLng(coords)) bounds.extend(coords) - }) -} - -/** Padding around itinerary bounds and map bounds. */ -const BOUNDS_PADDING = [30, 30] - -/** - * This MapLayer component will automatically update the leaflet bounds - * depending on what data is in the redux store. This component does not - * "render" anything on the map. - */ -class BoundsUpdatingOverlay extends MapLayer { - // Required for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - createLeafletElement() {} - - // Required for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - updateLeafletElement() {} - - componentDidMount() { - this.updateBounds(null, this.props) - } - - componentDidUpdate(prevProps) { - this.updateBounds(prevProps, this.props) - } - - // Required for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - componentWillUnmount() {} - - _fitItineraryViewToMap(newProps, bounds, map) { - // If itineraryView has changed (currently: only in mobile batch results), - // force a resize of the map before re-fitting the active itinerary or active leg, - // and do that after a delay to ensure that canvas heights have stabilized in the DOM. - setTimeout(() => { - map.invalidateSize(true) - - const { activeLeg, itinerary } = newProps - if (itinerary) { - if (activeLeg !== null) { - // Fit to active leg if set. - map.fitBounds(getLeafletLegBounds(itinerary.legs[activeLeg]), { - ITINERARY_MAP_PADDING: BOUNDS_PADDING - }) - } else { - // Fit to whole itinerary otherwise. - map.fitBounds(bounds, { ITINERARY_MAP_PADDING: BOUNDS_PADDING }) - } - } - }, 250) - } - - /* eslint-disable-next-line complexity */ - updateBounds(oldProps, newProps) { - // TODO: maybe setting bounds ought to be handled in map props... - - oldProps = oldProps || {} - newProps = newProps || {} - - // Don't auto-fit if popup us active - if (oldProps.popupLocation || newProps.popupLocation) return - - const { map } = newProps.leaflet - if (!map) return - - const itineraryShown = newProps.mainPanelContent === null - if (!itineraryShown) return - - // Fit map to to entire itinerary if active itinerary bounds changed - const newFrom = newProps.query && newProps.query.from - const newItinBounds = - newProps.itinerary && getLeafletItineraryBounds(newProps.itinerary) - const newTo = newProps.query && newProps.query.to - const oldFrom = oldProps.query && oldProps.query.from - const oldItinBounds = - oldProps.itinerary && getLeafletItineraryBounds(oldProps.itinerary) - const oldTo = oldProps.query && oldProps.query.to - const fromChanged = !isEqual(oldFrom, newFrom) - const toChanged = !isEqual(oldTo, newTo) - const oldIntermediate = oldProps.query && oldProps.query.intermediatePlaces - const newIntermediate = newProps.query && newProps.query.intermediatePlaces - const intermediateChanged = !isEqual(oldIntermediate, newIntermediate) - const oldMapConfig = oldProps.mapConfig - const newMapConfig = newProps.mapConfig - - // Also refit map if itineraryView prop has changed. - const itineraryViewChanged = - oldProps.itineraryView !== newProps.itineraryView - const isMobile = coreUtils.ui.isMobile() - - if (oldMapConfig !== newMapConfig && !isMobile) { - setTimeout(() => { - map.invalidateSize(true) - map.setZoom(newMapConfig.initZoom || 13) - }, 250) - } - if (itineraryViewChanged) { - this._fitItineraryViewToMap(newProps, newItinBounds, map) - } else if ( - (!oldItinBounds && newItinBounds) || - (oldItinBounds && newItinBounds && !oldItinBounds.equals(newItinBounds)) - ) { - map.fitBounds(newItinBounds, { padding: BOUNDS_PADDING }) - } else if ( - newProps.itinerary && - newProps.activeLeg !== oldProps.activeLeg && - newProps.activeLeg !== null - ) { - // Pan to to itinerary leg if made active (clicked); newly active leg must be non-null - map.fitBounds( - getLeafletLegBounds(newProps.itinerary.legs[newProps.activeLeg]), - { padding: BOUNDS_PADDING } - ) - } else if (newFrom && newTo && (fromChanged || toChanged)) { - // If no itinerary update but from/to locations are present, fit to those - - // On certain mobile devices (e.g., Android + Chrome), setting from and to - // locations via the location search component causes issues for this - // fitBounds invocation. The map does not appear to be visible when these - // prop changes are detected, so for now we should perhaps just skip this - // fitBounds on mobile. - // See https://github.com/opentripplanner/otp-react-redux/issues/133 for - // more info. - // TODO: Fix this so mobile devices will also update the bounds to the - // from/to locations. - if (!coreUtils.ui.isMobile()) { - const bounds = L.bounds([ - [newFrom.lat, newFrom.lon], - [newTo.lat, newTo.lon] - ]) - // Ensure bounds extend to include intermediatePlaces - extendBoundsByPlaces(bounds, newIntermediate) - const { x: left, y: bottom } = bounds.getBottomLeft() - const { x: right, y: top } = bounds.getTopRight() - map.fitBounds( - [ - [left, bottom], - [right, top] - ], - { padding: BOUNDS_PADDING } - ) - } - } else if (newFrom && fromChanged) { - // If only from or to is set, pan to that - map.panTo([newFrom.lat, newFrom.lon]) - } else if (newTo && toChanged) { - map.panTo([newTo.lat, newTo.lon]) - - // If intermediate place is added, extend bounds. - } else if (newIntermediate && intermediateChanged) { - const bounds = map.getBounds() - extendBoundsByPlaces(bounds, newIntermediate) - map.fitBounds(bounds) - } else if ( - newProps.itinerary && - newProps.activeLeg !== null && - newProps.activeStep !== null && - newProps.activeStep !== oldProps.activeStep - ) { - // Pan to to itinerary step if made active (clicked) - const leg = newProps.itinerary.legs[newProps.activeLeg] - const step = leg.steps[newProps.activeStep] - map.panTo([step.lat, step.lon]) - } - } -} - -// connect to the redux store - -const mapStateToProps = (state) => { - const activeSearch = getActiveSearch(state) - const urlParams = coreUtils.query.getUrlParams() - - return { - activeLeg: activeSearch && activeSearch.activeLeg, - activeStep: activeSearch && activeSearch.activeStep, - itinerary: getActiveItinerary(state), - itineraryView: urlParams.ui_itineraryView, - mainPanelContent: state.otp.ui.mainPanelContent, - mapConfig: state.otp.config.map, - popupLocation: state.otp.ui.mapPopupLocation, - query: state.otp.currentQuery - } -} - -const mapDispatchToProps = {} - -export default withLeaflet( - connect(mapStateToProps, mapDispatchToProps)(BoundsUpdatingOverlay) -) diff --git a/lib/components/map/connected-park-and-ride-overlay.tsx b/lib/components/map/connected-park-and-ride-overlay.tsx index 4339026f7..5b8d80619 100644 --- a/lib/components/map/connected-park-and-ride-overlay.tsx +++ b/lib/components/map/connected-park-and-ride-overlay.tsx @@ -1,5 +1,4 @@ import { connect } from 'react-redux' -// @ts-expect-error ParkAndRideOverlay is not typescripted yet import ParkAndRideOverlay from '@opentripplanner/park-and-ride-overlay' import React, { Component } from 'react' @@ -25,6 +24,7 @@ class ConnectedParkAndRideOverlay extends Component< } render() { + // @ts-expect-error TODO: re-write this component as a functional component and properly type return } } diff --git a/lib/components/map/connected-route-viewer-overlay.js b/lib/components/map/connected-route-viewer-overlay.js index cc7add61a..a11101a22 100644 --- a/lib/components/map/connected-route-viewer-overlay.js +++ b/lib/components/map/connected-route-viewer-overlay.js @@ -1,8 +1,6 @@ import { connect } from 'react-redux' import RouteViewerOverlay from '@opentripplanner/route-viewer-overlay' -import { unfocusRoute } from '../../actions/ui' - // connect to the redux store const mapStateToProps = (state, ownProps) => { @@ -22,13 +20,12 @@ const mapStateToProps = (state, ownProps) => { } return { - allowMapCentering: state.otp.ui.focusRoute, clipToPatternStops: state.otp.config?.routeViewer?.hideRouteShapesWithinFlexZones, routeData: { ...routeData, patterns: filteredPatterns } } } -const mapDispatchToProps = { mapCenterCallback: unfocusRoute } +const mapDispatchToProps = {} export default connect(mapStateToProps, mapDispatchToProps)(RouteViewerOverlay) diff --git a/lib/components/map/connected-stop-marker.js b/lib/components/map/connected-stop-marker.js deleted file mode 100644 index 977163e25..000000000 --- a/lib/components/map/connected-stop-marker.js +++ /dev/null @@ -1,36 +0,0 @@ -import { connect } from 'react-redux' -import DefaultStopMarker from '@opentripplanner/stops-overlay/lib/default-stop-marker' - -import { setLocation } from '../../actions/map' -import { setViewedStop } from '../../actions/ui' - -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - const { highlightedStop, viewedRoute, viewedStop } = state.otp.ui - const routeData = - viewedRoute && state.otp.transitIndex.routes?.[viewedRoute.routeId] - const hoverColor = routeData?.routeColor || '#333' - let fillColor = highlightedStop === ownProps.entity.id ? hoverColor : '#FFF' - if (viewedStop?.stopId === ownProps.entity.id) { - fillColor = 'cyan' - } - - return { - languageConfig: state.otp.config.language, - leafletPath: { - color: '#000', - fillColor, - fillOpacity: 1, - weight: 1 - }, - stop: ownProps.entity - } -} - -const mapDispatchToProps = { - setLocation, - setViewedStop -} - -export default connect(mapStateToProps, mapDispatchToProps)(DefaultStopMarker) diff --git a/lib/components/map/connected-stop-viewer-overlay.js b/lib/components/map/connected-stop-viewer-overlay.js index 589aa9b77..000900a6e 100644 --- a/lib/components/map/connected-stop-viewer-overlay.js +++ b/lib/components/map/connected-stop-viewer-overlay.js @@ -1,115 +1,68 @@ +// TYPESCRIPT TODO: all props here are missing types +/* eslint-disable react/prop-types */ import { connect } from 'react-redux' -import { FeatureGroup, MapLayer, withLeaflet } from 'react-leaflet' import ParkAndRideOverlay from '@opentripplanner/park-and-ride-overlay' import React from 'react' import VehicleRentalOverlay from '@opentripplanner/vehicle-rental-overlay' -import ZoomBasedMarkers from '@opentripplanner/zoom-based-markers' import * as mapActions from '../../actions/map' import EnhancedStopMarker from './enhanced-stop-marker' -// Minimum zoom to show stop-viewer overlay content const MIN_ZOOM = 17 - /** * An overlay to view a collection of stops. */ -class StopViewerOverlay extends MapLayer { - // Needed for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - componentDidMount() {} - - // Needed for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - componentWillUnmount() {} - - // Needed for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - createLeafletElement() {} - - // Needed for Leaflet - // eslint-disable-next-line @typescript-eslint/no-empty-function - updateLeafletElement() {} +const StopViewerOverlay = (props) => { + const { configCompanies, mapConfig, setLocation, stopData, stops } = props + if (!stopData) return null + const { bikeRental, parkAndRideLocations, vehicleRental } = stopData - componentDidUpdate(prevProps) { - const { leaflet, stopData } = this.props - // If a new stop is clicked, close the stop viewer popup - if (stopData?.id !== prevProps?.stopData?.id) { - leaflet.map && leaflet.map.closePopup() - } - } - - render() { - const { - configCompanies, - leaflet, - mapConfig, - setLocation, - stopData, - stops - } = this.props - if (!stopData) return null - const { bikeRental, parkAndRideLocations, vehicleRental } = stopData - // Don't render if no map or no stops are defined. - // (ZoomBasedMarkers will also not render below the minimum zoom threshold defined in the symbols prop.) - if (!leaflet || !leaflet.map) { - return - } - const stopSymbols = [ - { - minZoom: MIN_ZOOM, - symbol: EnhancedStopMarker - } - ] - const zoom = leaflet.map.getZoom() - // if (zoom < stopSymbols[0].minZoom) return - return ( - - {stops && stops.length > 0 && ( - + {stops && + stops.length > 0 && + stops.map((stop) => ( + - )} - - {mapConfig.overlays && - mapConfig.overlays.map((overlayConfig, k) => { - switch (overlayConfig.type) { - case 'bike-rental': - case 'otp2-bike-rental': - return ( - = MIN_ZOOM - 1} - /> - ) - case 'micromobility-rental': - case 'otp2-micromobility-rental': - return ( - = MIN_ZOOM - 1} - /> - ) - default: - return null - } - })} - - ) - } + ))} + + {mapConfig.overlays && + mapConfig.overlays.map((overlayConfig, k) => { + switch (overlayConfig.type) { + case 'bike-rental': + case 'otp2-bike-rental': + return ( + + ) + case 'micromobility-rental': + case 'otp2-micromobility-rental': + return ( + + ) + default: + return null + } + })} + + ) } // connect to the redux store @@ -133,7 +86,4 @@ const mapDispatchToProps = { setLocation: mapActions.setLocation } -export default connect( - mapStateToProps, - mapDispatchToProps -)(withLeaflet(StopViewerOverlay)) +export default connect(mapStateToProps, mapDispatchToProps)(StopViewerOverlay) diff --git a/lib/components/map/connected-stops-overlay.js b/lib/components/map/connected-stops-overlay.js index 60401ef80..5b7a5d1b5 100644 --- a/lib/components/map/connected-stops-overlay.js +++ b/lib/components/map/connected-stops-overlay.js @@ -2,13 +2,13 @@ import { connect } from 'react-redux' import StopsOverlay from '@opentripplanner/stops-overlay' import { findStopsWithinBBox } from '../../actions/api' - -import StopMarker from './connected-stop-marker' +import { MainPanelContent, setViewedStop } from '../../actions/ui' +import { setLocation } from '../../actions/map' // connect to the redux store const mapStateToProps = (state, ownProps) => { - const { viewedRoute } = state.otp.ui + const { mainPanelContent, viewedRoute } = state.otp.ui const { routes } = state.otp.transitIndex let { stops } = state.otp.overlay.transit @@ -49,18 +49,16 @@ const mapStateToProps = (state, ownProps) => { } return { + minZoom, stops, - symbols: [ - { - minZoom, - symbol: StopMarker - } - ] + visible: mainPanelContent !== MainPanelContent.STOP_VIEWER } } const mapDispatchToProps = { - refreshStops: findStopsWithinBBox + refreshStops: findStopsWithinBBox, + setLocation, + setViewedStop } export default connect(mapStateToProps, mapDispatchToProps)(StopsOverlay) diff --git a/lib/components/map/connected-transit-vehicle-overlay.js b/lib/components/map/connected-transit-vehicle-overlay.js deleted file mode 100644 index af9607544..000000000 --- a/lib/components/map/connected-transit-vehicle-overlay.js +++ /dev/null @@ -1,140 +0,0 @@ -// TODO: Typescript -/* eslint-disable react/prop-types */ -/** - * This overlay is similar to gtfs-rt-vehicle-overlay in that it shows - * realtime positions of vehicles on a route using the otp-ui/transit-vehicle-overlay. - * - * However, this overlay differs in a few ways: - * 1) This overlay retrieves vehicle locations from OTP - * 2) This overlay renders vehicles as blobs rather than a custom shape - * 3) This overlay does not handle updating positions - * 4) This overlay does not render route paths - * 5) This overlay has a custom popup on vehicle hover - */ -import { - Circle, - CircledVehicle -} from '@opentripplanner/transit-vehicle-overlay/lib/components/markers/ModeCircles' -import { connect } from 'react-redux' -import { FormattedMessage, FormattedNumber, injectIntl } from 'react-intl' -import { Tooltip } from 'react-leaflet' -import React from 'react' -import TransitVehicleOverlay from '@opentripplanner/transit-vehicle-overlay' - -import FormattedTransitVehicleStatus from '../util/formatted-transit-vehicle-status' - -const vehicleSymbols = [ - { - minZoom: 0, - symbol: Circle - }, - { - minZoom: 10, - symbol: CircledVehicle - } -] - -function VehicleTooltip(props) { - const { direction, intl, permanent, vehicle } = props - - let vehicleLabel = vehicle?.label - // If a vehicle's label is less than 5 characters long, we can assume it is a vehicle - // number. If this is the case, prepend "vehicle" to it. - // Otherwise, the label itself is enough - if (vehicleLabel !== null && vehicleLabel?.length <= 5) { - vehicleLabel = intl.formatMessage( - { id: 'components.TransitVehicleOverlay.vehicleName' }, - { vehicleNumber: vehicleLabel } - ) - } else { - vehicleLabel = '' - } - - const stopStatus = vehicle?.stopStatus || 'in_transit_to' - - // FIXME: This may not be timezone adjusted as reported seconds may be in the wrong timezone. - // All needed info to fix this is available via route.agency.timezone - // However, the needed coreUtils methods are not updated to support this - return ( - - - {/* - FIXME: move back to core-utils for time handling - */} - {m}, - vehicleNameOrBlank: vehicleLabel - }} - /> - - {stopStatus !== 'STOPPED_AT' && vehicle?.speed > 0 && ( -
- - ) - }} - /> -
- )} - {vehicle?.nextStopName && ( -
- -
- )} -
- ) -} -// connect to the redux store - -const mapStateToProps = (state) => { - const viewedRoute = state.otp.ui.viewedRoute - const route = state.otp.transitIndex?.routes?.[viewedRoute?.routeId] - - let vehicleList = [] - - // Add missing fields to vehicle list - if (viewedRoute?.routeId) { - vehicleList = route?.vehicles?.map((vehicle) => { - vehicle.routeType = route?.mode - vehicle.routeColor = route?.color - vehicle.textColor = route?.routeTextColor - return vehicle - }) - - // Remove all vehicles not on pattern being currently viewed - if (viewedRoute.patternId && vehicleList) { - vehicleList = vehicleList.filter( - (vehicle) => vehicle.patternId === viewedRoute.patternId - ) - } - } - return { - symbols: vehicleSymbols, - TooltipSlot: injectIntl(VehicleTooltip), - vehicleList - } -} - -const mapDispatchToProps = {} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TransitVehicleOverlay) diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 05bf97b0f..32ad1d336 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -1,5 +1,5 @@ -import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { connect } from 'react-redux' +import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { getTransitiveData } from '../../util/state' diff --git a/lib/components/map/connected-vehicle-rental-overlay.js b/lib/components/map/connected-vehicle-rental-overlay.js index c98f01b88..50ea8d87a 100644 --- a/lib/components/map/connected-vehicle-rental-overlay.js +++ b/lib/components/map/connected-vehicle-rental-overlay.js @@ -1,31 +1,12 @@ -import VehicleRentalOverlay from '@opentripplanner/vehicle-rental-overlay' -import React, { Component } from 'react' import { connect } from 'react-redux' +import React, { Component } from 'react' +import VehicleRentalOverlay from '@opentripplanner/vehicle-rental-overlay' import { setLocation } from '../../actions/map' class ConnectedVehicleRentalOverlay extends Component { - constructor (props) { - super(props) - this.state = { visible: props.visible } - } - - componentDidMount () { - this.props.registerOverlay(this) - } - - onOverlayAdded = () => { - this.setState({ visible: true }) - } - - onOverlayRemoved = () => { - this.setState({ visible: false }) - } - - render () { - return ( - - ) + render() { + return } } @@ -42,4 +23,7 @@ const mapDispatchToProps = { setLocation } -export default connect(mapStateToProps, mapDispatchToProps)(ConnectedVehicleRentalOverlay) +export default connect( + mapStateToProps, + mapDispatchToProps +)(ConnectedVehicleRentalOverlay) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 53b19dc10..86f2c8124 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -1,6 +1,7 @@ /* eslint-disable react/prop-types */ import { connect } from 'react-redux' import { injectIntl } from 'react-intl' +import { Popup } from 'react-map-gl' import BaseMap from '@opentripplanner/base-map' import React, { Component } from 'react' import styled from 'styled-components' @@ -18,21 +19,16 @@ import { } from '../../actions/map' import { updateOverlayVisibility } from '../../actions/config' -import BoundsUpdatingOverlay from './bounds-updating-overlay' import ElevationPointMarker from './elevation-point-marker' import EndpointsOverlay from './connected-endpoints-overlay' import ParkAndRideOverlay from './connected-park-and-ride-overlay' import PointPopup from './point-popup' -import RoutePreviewOverlay from './route-preview-overlay' import RouteViewerOverlay from './connected-route-viewer-overlay' import StopsOverlay from './connected-stops-overlay' import StopViewerOverlay from './connected-stop-viewer-overlay' -import TileOverlay from './tile-overlay' import TransitiveOverlay from './connected-transitive-overlay' -import TransitVehicleOverlay from './connected-transit-vehicle-overlay' import TripViewerOverlay from './connected-trip-viewer-overlay' import VehicleRentalOverlay from './connected-vehicle-rental-overlay' -import ZipcarOverlay from './zipcar-overlay' const MapContainer = styled.div` height: 100%; @@ -234,15 +230,23 @@ class DefaultMap extends Component { ? [mapConfig.initLat, mapConfig.initLon] : null - const popup = mapPopupLocation && { - contents: ( + const popup = mapPopupLocation && ( + { + this.props.setMapPopupLocation({ location: null }) + }} + > - ), - location: [mapPopupLocation.lat, mapPopupLocation.lon] - } + + ) const bikeStations = [ ...bikeRentalStations.filter( @@ -267,21 +271,20 @@ class DefaultMap extends Component { baseLayers={baseLayersWithNames} center={center} maxZoom={mapConfig.maxZoom} - onClick={this.onMapClick} - onPopupClosed={this.onPopupClosed} - popup={popup} + onContextMenu={this.onMapClick} zoom={mapConfig.initZoom || 13} > + {popup} {/* The default overlays */} - + {/* */} - + {/* */} - + {/* */} @@ -289,6 +292,7 @@ class DefaultMap extends Component { {mapConfig.overlays?.map((overlayConfig, k) => { const namedLayerProps = { ...overlayConfig, + id: k, key: k, name: getLayerName(overlayConfig, config, intl) } @@ -313,8 +317,8 @@ class DefaultMap extends Component { return case 'stops': return - case 'tile': - return + // case 'tile': + // return case 'micromobility-rental': return ( ) - case 'zipcar': - return + // case 'zipcar': + // return case 'otp2-micromobility-rental': return ( { + render() { const { diagramLeg, elevationPoint, showElevationProfile } = this.props // Compute the elevation point marker, if activeLeg and elevation profile is enabled. let elevationPointMarker = null @@ -22,16 +29,14 @@ class ElevationPointMarker extends Component { ) if (pos) { elevationPointMarker = ( - + + + ) } } @@ -39,7 +44,8 @@ class ElevationPointMarker extends Component { } } -const mapStateToProps = (state, ownProps) => { +// TODO: OTP-RR State Type +const mapStateToProps = (state: any) => { return { diagramLeg: state.otp.ui.diagramLeg, elevationPoint: state.otp.ui.elevationPoint, @@ -49,4 +55,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = {} -export default connect(mapStateToProps, mapDispatchToProps)(ElevationPointMarker) +export default connect( + mapStateToProps, + mapDispatchToProps +)(ElevationPointMarker) diff --git a/lib/components/map/enhanced-stop-marker.js b/lib/components/map/enhanced-stop-marker.js index dd4ad5398..60cdfb14f 100644 --- a/lib/components/map/enhanced-stop-marker.js +++ b/lib/components/map/enhanced-stop-marker.js @@ -1,14 +1,14 @@ -// FIXME: Typescript once dependencies are typed +// TYPESCRIPT TODO: all props here are missing types /* eslint-disable react/prop-types */ -import { Styled as BaseMapStyled } from '@opentripplanner/base-map' +import { + Styled as BaseMapStyled, + MarkerWithPopup +} from '@opentripplanner/base-map' import { connect } from 'react-redux' -import { divIcon } from 'leaflet' import { FormattedMessage } from 'react-intl' -import { Marker, Popup } from 'react-leaflet' import { Styled as StopsOverlayStyled } from '@opentripplanner/stops-overlay' import FromToLocationPicker from '@opentripplanner/from-to-location-picker' import React, { Component } from 'react' -import ReactDOMServer from 'react-dom/server' import styled from 'styled-components' import tinycolor from 'tinycolor2' @@ -120,60 +120,55 @@ class EnhancedStopMarker extends Component { ) - const icon = divIcon({ - className: '', - html: ReactDOMServer.renderToStaticMarkup( - - {stopIcon} - - ), - // Anchor of [0, 0] places the top-left corner of the bubble at the stop location. - // Instead, we want the tip of the caret at the bottom center of the bubble - // to be at the stop location. - // Add some margins so the stop marker (which may be unintentionally offset) remains visible. - iconAnchor: [ - bubblePixels(this.props) / 2 + 4, - bubblePixels(this.props) + caretVisibleHeight + 8 - ] - }) + const icon = ( + + {stopIcon} + + ) return ( - - - - {name} - - - - - {' '} - {stopCode} - - - {languageConfig.stopViewer || ( - - )} - - - - {/* The 'Set as [from/to]' ButtonGroup */} - - - - - - + + {name} + + + + + {' '} + {stopCode} + + + {languageConfig.stopViewer || ( + + )} + + + + {/* The 'Set as [from/to]' ButtonGroup */} + + + + + ) + } + position={[lat, lon]} + > + {icon} + ) } } @@ -189,10 +184,10 @@ const mapStateToProps = (state, ownProps) => { return { activeStopId: state.otp.ui.viewedStop?.stopId, - highlight: highlightedStop === ownProps.entity.id, + highlight: highlightedStop === ownProps.stop.id, languageConfig: state.otp.config.language, modeColors, - stop: ownProps.entity + stop: ownProps.stop } } diff --git a/lib/components/map/gtfs-rt-vehicle-overlay.js b/lib/components/map/gtfs-rt-vehicle-overlay.js deleted file mode 100644 index 166f725e8..000000000 --- a/lib/components/map/gtfs-rt-vehicle-overlay.js +++ /dev/null @@ -1,237 +0,0 @@ -import { transit_realtime as transitRealtime } from 'gtfs-realtime-bindings' -import L from 'leaflet' -import TransitVehicleOverlay from '@opentripplanner/transit-vehicle-overlay' -import PropTypes from 'prop-types' -import React from 'react' -import ReactDOMServer from 'react-dom/server' -import { FeatureGroup, MapLayer, Marker, Polyline } from 'react-leaflet' -import styled from 'styled-components' - -import { getModeFromRoute } from '../../util/viewer' - -export const VehicleShape = styled.span` - background-color: #${props => props.route?.route_color || '000000'}; - border: 1px solid #000000; - border-radius: 50%; - color: #${props => props.route?.route_text_color || 'ffffff'}; - display: block; - height: 24px; - left: -6px; - line-height: 24px; - margin-left: auto; - margin-right: auto; - position: relative; - text-align: center; - top: -6px; - width: 24px; -` - -/** - * Convert GTFS-rt entities to vehicle location from OTP-UI. - * @param vehicle The GTFS-rt vehicle position entity to convert. - * @param routes Optional GTFS-like routes list. - */ -function gtfsRtToTransitVehicle (gtfsRtVehicle, routes) { - const { position, stopId, timestamp, trip, vehicle } = gtfsRtVehicle.vehicle - const route = routes.find(r => r.route_id === trip.routeId) - let routeShortName - let routeType = 'BUS' - if (route) { - routeShortName = route.route_short_name - // Obtain the OTP route type from the GTFS route_type enum value. - routeType = getModeFromRoute({ type: route.route_type }) - } - return { - heading: position.bearing, - id: vehicle.id, - lat: position.latitude, - lon: position.longitude, - reportDate: new Date(parseInt(timestamp, 10)).toLocaleString(), - routeShortName, - routeType, - stopId, - tripId: trip.tripId, - vehicleId: vehicle.id - } -} - -/** - * Generate a vehicle shape component (a colored dot) based on the provided routes list. - */ -function makeVehicleShape (routes) { - return props => { - const { lat, lon, routeShortName } = props.vehicle - const route = routes.find(r => r.route_short_name === routeShortName) || {} - const iconHtml = ReactDOMServer.renderToStaticMarkup( - - {route.route_short_name} - - ) - return ( - - ) - } -} - -/** - * This component is a composite overlay that renders GTFS-rt vehicle positions - * that it downloads from the liveFeedUrl prop, using the - * @opentripplanner/transit-vehicle-overlay package. - * - * For transit routes not defined in a known GTFS feed, this component also - * renders transit route shapes if available from the routeDefinitionUrl prop. - */ -class GtfsRtVehicleOverlay extends MapLayer { - static propTypes = { - /** URL to GTFS-rt feed in protocol buffer format. */ - liveFeedUrl: PropTypes.string, - /** URL to GTFS-like route list in JSON format. */ - routeDefinitionUrl: PropTypes.string, - /** Sets whether the layer is initially visible. */ - visible: PropTypes.bool - } - - constructor (props) { - super(props) - this.state = { - routes: [], - vehicleLocations: [], - // Set to undefined to fall back on the default symbols, - // unless a route definition is provided. - vehicleSymbols: undefined, - visible: props.visible - } - } - - async componentDidMount () { - const { liveFeedUrl, name, registerOverlay, routeDefinitionUrl } = this.props - registerOverlay(this) - if (!routeDefinitionUrl) { - console.warn(`routeDefinitionUrl prop is missing for overlay '${name}'.`) - } else { - // If route definitions are provided, wait until they are - // fetched so they can be used rendering vehicle shapes. - await this._fetchRoutes() - } - if (!liveFeedUrl) { - console.warn(`liveFeedUrl prop is missing for overlay '${name}'.`) - } else if (this.state.visible) { - // If layer is initially visible, start getting the vehicle positions. - this._startRefreshing() - } - } - - componentWillUnmount () { - this._stopRefreshing() - } - - onOverlayAdded = () => { - this.setState({ visible: true }) - this._startRefreshing() - } - - onOverlayRemoved = () => { - this.setState({ visible: false }) - this._stopRefreshing() - } - - createLeafletElement () {} - - updateLeafletElement () {} - - /** - * Fetches GTFS-rt vehicle positions (protocol buffer) and converts to - * OTP-UI transitVehicleType before saving to component state. - */ - _fetchVehiclePositions = async () => { - const { liveFeedUrl } = this.props - if (liveFeedUrl) { - try { - const response = await fetch(liveFeedUrl) - if (response.status >= 400) { - const error = new Error('Received error from server') - error.response = response - throw error - } - const buffer = await response.arrayBuffer() - const view = new Uint8Array(buffer) - const feed = transitRealtime.FeedMessage.decode(view) - - const vehicleLocations = feed.entity.map(vehicle => gtfsRtToTransitVehicle(vehicle, this.state.routes)) - this.setState({ vehicleLocations }) - } catch (err) { - console.log(err) - } - } - } - - /** - * Fetches GTFS-like route definitions (JSON). - * (called once when component is mounted) - */ - _fetchRoutes = async () => { - const { routeDefinitionUrl } = this.props - if (routeDefinitionUrl) { - try { - const response = await fetch(routeDefinitionUrl) - if (response.status >= 400) { - const error = new Error('Received error from server') - error.response = response - throw error - } - const routes = await response.json() - - // Generate the symbols for the overlay at this time - // so it renders the route colors for each vehicle. - const vehicleSymbols = [ - { - minZoom: 0, - symbol: makeVehicleShape(routes) - } - ] - - this.setState({ routes, vehicleSymbols }) - } catch (err) { - console.log(err) - } - } - } - - _startRefreshing () { - // initial vehicle retrieval - this._fetchVehiclePositions() - - // set up timer to refresh vehicle positions periodically. - // defaults to every 30 sec. - this._refreshTimer = setInterval(this._fetchVehiclePositions, 30000) - } - - _stopRefreshing () { - if (this._refreshTimer) clearInterval(this._refreshTimer) - } - - render () { - const { routes, vehicleLocations, vehicleSymbols, visible } = this.state - return ( - - {routes.map(r => ( - - ))} - - - ) - } -} - -export default GtfsRtVehicleOverlay diff --git a/lib/components/map/osm-base-layer.js b/lib/components/map/osm-base-layer.js deleted file mode 100644 index a7a28a410..000000000 --- a/lib/components/map/osm-base-layer.js +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Component } from 'react' -import { TileLayer } from 'react-leaflet' - -export default class OsmBaseLayer extends Component { - render () { - return ( - - ) - } -} diff --git a/lib/components/map/route-preview-overlay.js b/lib/components/map/route-preview-overlay.js deleted file mode 100644 index 69eb64e72..000000000 --- a/lib/components/map/route-preview-overlay.js +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { connect } from 'react-redux' -import { FeatureGroup, MapLayer, Polyline, withLeaflet } from 'react-leaflet' -import polyline from '@mapbox/polyline' -import React from 'react' - -import { - getActiveItinerary, - getActiveSearch, - getVisibleItineraryIndex -} from '../../util/state' - -/** - * This overlay will display thin gray lines for a set of geometries. It's to be used - * as a stopgap until we make full use of Transitive! - */ -class GeometryPreviewOverlay extends MapLayer { - componentDidMount() {} - - // TODO: determine why the default MapLayer componentWillUnmount() method throws an error - componentWillUnmount() {} - - createLeafletElement() {} - - updateLeafletElement() {} - - render() { - const { geometries, leafletPath } = this.props - - if (!geometries) return - - const uniqueGeometries = Array.from(new Set(geometries)) - return ( - - {uniqueGeometries.map((geometry, index) => { - if (!geometry) return null - const pts = polyline.decode(geometry) - if (!pts) return null - - return ( - - ) - })} - - ) - } -} - -const mapStateToProps = (state, ownProps) => { - const { activeSearchId, config } = state.otp - // Only show this overlay if the metro UI is explicitly enabled - if (config.itinerary?.showFirstResultByDefault !== false) { - return {} - } - if (!activeSearchId) return {} - - const visibleItinerary = getVisibleItineraryIndex(state) - const activeItinerary = getActiveItinerary(state) - - const geometries = getActiveSearch(state)?.response.flatMap( - (serverResponse) => - serverResponse?.plan?.itineraries?.flatMap((itinerary) => { - return itinerary.legs?.map((leg) => leg.legGeometry.points) - }) - ) - - return { - geometries, - - leafletPath: { - color: 'gray', - dashArray: '3, 8', - opacity: - // We need an explicit check for undefined and null because 0 - // is for us true - (visibleItinerary === undefined || visibleItinerary === null) && - (activeItinerary === undefined || activeItinerary === null) - ? 0.5 - : 0, - weight: 4 - } - } -} - -const mapDispatchToProps = {} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(withLeaflet(GeometryPreviewOverlay)) diff --git a/lib/components/map/tile-overlay.js b/lib/components/map/tile-overlay.js deleted file mode 100644 index 48fcd640b..000000000 --- a/lib/components/map/tile-overlay.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { TileLayer } from 'react-leaflet' - -export default class TileOverlay extends Component { - static propTypes = { - tileUrl: PropTypes.string - } - - componentWillUnmount () { } - - render () { - return this.props.tileUrl - ? - : null - } -} diff --git a/lib/components/map/zipcar-overlay.js b/lib/components/map/zipcar-overlay.js deleted file mode 100644 index c8dbdcac9..000000000 --- a/lib/components/map/zipcar-overlay.js +++ /dev/null @@ -1,142 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { FeatureGroup, MapLayer, Marker, Popup, withLeaflet } from 'react-leaflet' -import { divIcon } from 'leaflet' - -import { setLocation } from '../../actions/map' -import { zipcarLocationsQuery } from '../../actions/zipcar' - -import SetFromToButtons from './set-from-to' - -const zipcarIcon = 'zipcar-icon' - -class ZipcarOverlay extends MapLayer { - static propTypes = { - api: PropTypes.string, - locations: PropTypes.array, - setLocation: PropTypes.func, - zipcarLocationsQuery: PropTypes.func - } - - _startRefreshing () { - // ititial station retrieval - this.props.zipcarLocationsQuery(this.props.api) - - // set up timer to refresh stations periodically - this._refreshTimer = setInterval(() => { - this.props.zipcarLocationsQuery(this.props.api) - }, 30000) // defaults to every 30 sec. TODO: make this configurable?*/ - } - - _stopRefreshing () { - if (this._refreshTimer) clearInterval(this._refreshTimer) - } - - componentDidMount () { - this.props.registerOverlay(this) - } - - onOverlayAdded = () => { - this._startRefreshing() - } - - onOverlayRemoved = () => { - this._stopRefreshing() - } - - componentWillUnmount () { - this._stopRefreshing() - } - - componentDidUpdate (prevProps) { - if (!prevProps.visible && this.props.visible) { - this._startRefreshing() - } else if (prevProps.visible && !this.props.visible) { - this._stopRefreshing() - } - } - - createLeafletElement () {} - - updateLeafletElement () {} - - render () { - const { locations } = this.props - if (!locations || locations.length === 0) return - - const markerIcon = divIcon({ - className: '', - html: zipcarIcon, - iconSize: [24, 24], - popupAnchor: [0, -12] - }) - - const bulletIconStyle = { - color: 'gray', - fontSize: 12, - width: 15 - } - - return ( - - {locations.map((location) => { - return ( - - -
- {/* Popup title */} -
- Zipcar Location -
- - {/* Location info bullet */} -
- {location.display_name} -
- - {/* Vehicle-count bullet */} -
- {location.num_vehicles} Vehicles -
- - {/* Set as from/to toolbar */} -
- -
-
-
-
- ) - })} -
- ) - } -} - -// connect to the redux store - -const mapStateToProps = (state, ownProps) => { - return { - locations: state.otp.overlay.zipcar && state.otp.overlay.zipcar.locations - } -} - -const mapDispatchToProps = { - setLocation, - zipcarLocationsQuery -} - -export default connect(mapStateToProps, mapDispatchToProps)(withLeaflet(ZipcarOverlay)) diff --git a/lib/components/viewers/RouteRow.js b/lib/components/viewers/RouteRow.js index db9136fc2..d785701a8 100644 --- a/lib/components/viewers/RouteRow.js +++ b/lib/components/viewers/RouteRow.js @@ -102,19 +102,13 @@ export class RouteRow extends PureComponent { } _onClick = () => { - const { - findRoute, - getVehiclePositionsForRoute, - isActive, - route, - setViewedRoute - } = this.props + const { getVehiclePositionsForRoute, isActive, route, setViewedRoute } = + this.props if (isActive) { // Deselect current route if active. setViewedRoute({ patternId: null, routeId: null }) } else { // Otherwise, set active and fetch route patterns. - findRoute({ routeId: route.id }) getVehiclePositionsForRoute(route.id) setViewedRoute({ routeId: route.id }) } diff --git a/lib/index.js b/lib/index.js index d8fe5fa67..a2f568eea 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,11 +13,8 @@ import PlanTripButton from './components/form/plan-trip-button' import SettingsPreview from './components/form/settings-preview' import SwitchButton from './components/form/switch-button' import DefaultMap from './components/map/default-map' -import GtfsRtVehicleOverlay from './components/map/gtfs-rt-vehicle-overlay' import Map from './components/map/map' import StylizedMap from './components/map/stylized-map' -import OsmBaseLayer from './components/map/osm-base-layer' -import TileOverlay from './components/map/tile-overlay' import DefaultItinerary from './components/narrative/default/default-itinerary' import MetroItinerary from './components/narrative/metro/metro-itinerary' import ItineraryCarousel from './components/narrative/itinerary-carousel' @@ -76,11 +73,8 @@ export { // map components DefaultMap, - GtfsRtVehicleOverlay, ItineraryCarousel, Map, - OsmBaseLayer, - TileOverlay, // narrative components DefaultItinerary, diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 40a0e4c56..b4c40a4e1 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -67,7 +67,6 @@ export function getInitialState(userDefinedConfig) { mobile: 'BOTH_LOCATIONS_CHANGED' }, debouncePlanTimeMs: 0, - focusRoute: false, language: {}, onTimeThresholdSeconds: 60, realtimeEffectsDisplayThreshold: 120, @@ -706,7 +705,6 @@ function createOtpReducer(config) { // If setting to a route (not null), also set main panel. return update(state, { ui: { - focusRoute: { $set: true }, mainPanelContent: { $set: MainPanelContent.ROUTE_VIEWER }, viewedRoute: { $set: action.payload } } @@ -717,8 +715,6 @@ function createOtpReducer(config) { ui: { viewedRoute: { $set: action.payload } } }) } - case 'UNFOCUS_ROUTE': - return update(state, { ui: { focusRoute: { $set: false } } }) case 'FIND_STOP_RESPONSE': return update(state, { transitIndex: { diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index 84eeee707..edc7cf55e 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -1,20 +1,7 @@ -import { latLngBounds } from 'leaflet' -import coreUtils from '@opentripplanner/core-utils' import moment from 'moment' import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' -export function getLeafletItineraryBounds(itinerary) { - return latLngBounds(coreUtils.itinerary.getItineraryBounds(itinerary)) -} - -/** - * Return a leaflet LatLngBounds object that encloses the given leg's geometry. - */ -export function getLeafletLegBounds(leg) { - return latLngBounds(coreUtils.itinerary.getLegBounds(leg)) -} - export function isBatchRoutingEnabled(otpConfig) { return Boolean(otpConfig.routingTypes.find((t) => t.key === 'BATCH')) } diff --git a/package.json b/package.json index 42e63cc04..bf5df3cf0 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,9 @@ "homepage": "", "dependencies": { "@auth0/auth0-react": "^1.1.0", - "@opentripplanner/base-map": "^2.0.0", + "@opentripplanner/base-map": "../otp-ui/packages/base-map", "@opentripplanner/core-utils": "^5.0.2", - "@opentripplanner/endpoints-overlay": "^1.4.1", + "@opentripplanner/endpoints-overlay": "../otp-ui/packages/endpoints-overlay", "@opentripplanner/from-to-location-picker": "^2.1.2", "@opentripplanner/geocoder": "^1.2.1", "@opentripplanner/humanize-distance": "^1.2.0", @@ -44,17 +44,17 @@ "@opentripplanner/itinerary-body": "^3.0.5", "@opentripplanner/location-field": "1.12.3", "@opentripplanner/location-icon": "^1.4.0", - "@opentripplanner/park-and-ride-overlay": "^1.2.4", + "@opentripplanner/park-and-ride-overlay": "../otp-ui/packages/park-and-ride-overlay", "@opentripplanner/printable-itinerary": "^2.0.1", - "@opentripplanner/route-viewer-overlay": "^1.4.0", - "@opentripplanner/stop-viewer-overlay": "^1.1.1", - "@opentripplanner/stops-overlay": "^4.0.0", + "@opentripplanner/route-viewer-overlay": "../otp-ui/packages/route-viewer-overlay", + "@opentripplanner/stop-viewer-overlay": "../otp-ui/packages/stop-viewer-overlay", + "@opentripplanner/stops-overlay": "../otp-ui/packages/stops-overlay", "@opentripplanner/transit-vehicle-overlay": "^2.4.0", - "@opentripplanner/transitive-overlay": "^1.1.4", + "@opentripplanner/transitive-overlay": "../otp-ui/packages/transitive-overlay", "@opentripplanner/trip-details": "^2.0.0", "@opentripplanner/trip-form": "^1.11.3", - "@opentripplanner/trip-viewer-overlay": "^1.1.1", - "@opentripplanner/vehicle-rental-overlay": "^1.4.3", + "@opentripplanner/trip-viewer-overlay": "../otp-ui/packages/trip-viewer-overlay", + "@opentripplanner/vehicle-rental-overlay": "../otp-ui/packages/vehicle-rental-overlay", "blob-stream": "^0.1.3", "bootstrap": "^3.3.7", "bowser": "^1.9.3", @@ -81,6 +81,7 @@ "lodash.isempty": "^4.4.0", "lodash.isequal": "^4.5.0", "lodash.memoize": "^4.1.2", + "maplibre-gl": "^2.1.9", "moment": "^2.17.1", "moment-timezone": "^0.5.33", "object-hash": "^1.3.1", @@ -97,6 +98,7 @@ "react-ga": "^3.3.0", "react-intl": "^5.20.10", "react-loading-skeleton": "^2.1.1", + "react-map-gl": "^7.0.15", "react-phone-number-input": "^3.1.0", "react-redux": "^7.1.0", "react-resize-detector": "^2.1.0", @@ -165,14 +167,12 @@ "husky": "^6.0.0", "jest-transform-stub": "^2.0.0", "jest-yaml-transform": "^0.2.0", - "leaflet": "^1.6.0", "lint-staged": "^11.1.2", "nock": "^9.0.9", "patch-package": "^6.4.7", "pinst": "^2.1.6", "prettier": "^2.3.2", "puppeteer": "^10.2.0", - "react-leaflet": "^2.6.1", "react-refresh": "^0.10.0", "react-scripts": "^4.0.3", "redux-mock-store": "^1.5.3", @@ -183,10 +183,8 @@ "yaml-loader": "^0.6.0" }, "peerDependencies": { - "leaflet": "^1.6.0", "react": ">=15.0.0", - "react-dom": ">=15.0.0", - "react-leaflet": "^2.6.1" + "react-dom": ">=15.0.0" }, "jest": { "moduleNameMapper": { diff --git a/yarn.lock b/yarn.lock index 464f558f5..e88cffbbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1987,13 +1987,58 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@mapbox/polyline@^1.1.0": +"@mapbox/geojson-rewind@^0.5.1": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz#591a5d71a9cd1da1a0bf3420b3bea31b0fc7946a" + integrity sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA== + dependencies: + get-stream "^6.0.1" + minimist "^1.2.6" + +"@mapbox/jsonlint-lines-primitives@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" + integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ== + +"@mapbox/mapbox-gl-supported@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz#c15367178d8bfe4765e6b47b542fe821ce259c7b" + integrity sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ== + +"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" + integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ== + +"@mapbox/polyline@^1.1.0", "@mapbox/polyline@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@mapbox/polyline/-/polyline-1.1.1.tgz#ab96e5e6936f4847a4894e14558daf43e40e3bd2" integrity sha512-z9Sl7NYzsEIrAza658H92mc0OvpBjQwjp7Snv4xREKhsCMat7m1IKdWJMjQ5opiPYa0veMf7kCaSd1yx55AhmQ== dependencies: meow "^6.1.1" +"@mapbox/tiny-sdf@^2.0.4": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.5.tgz#cdba698d3d65087643130f9af43a2b622ce0b372" + integrity sha512-OhXt2lS//WpLdkqrzo/KwB7SRD8AiNTFFzuo9n14IBupzIMa67yGItcK7I2W9D8Ghpa4T04Sw9FWsKCJG50Bxw== + +"@mapbox/unitbezier@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz#d32deb66c7177e9e9dfc3bbd697083e2e657ff01" + integrity sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw== + +"@mapbox/vector-tile@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" + integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== + dependencies: + "@mapbox/point-geometry" "~0.1.0" + +"@mapbox/whoots-js@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" + integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2267,15 +2312,25 @@ dependencies: "@octokit/openapi-types" "^10.0.0" -"@opentripplanner/base-map@^2.0.0": +"@opentripplanner/base-map@../otp-ui/packages/base-map": version "2.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/base-map/-/base-map-2.0.0.tgz#782743255e65080d3c52e4f29fe21d41dbc2e5b3" - integrity sha512-LlYJiXQF4mvGLlYLKYQDBPGB40Ivj3MZJfGJ5oDk0atzL74EJ625pWmqnNlBzw7LJdStG6NpIzaL1ZTqTA63rQ== dependencies: - "@opentripplanner/core-utils" "^4.1.0" - prop-types "^15.7.2" + "@opentripplanner/types" "^2.0.0" + mapbox-gl "npm:empty-npm-package@1.0.0" + maplibre-gl "^2.1.9" + react-map-gl "^7.0.15" -"@opentripplanner/core-utils@^4.1.0", "@opentripplanner/core-utils@^4.11.0", "@opentripplanner/core-utils@^4.11.2", "@opentripplanner/core-utils@^4.5.0": +"@opentripplanner/base-map@^3.0.0-alpha.1": + version "3.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/base-map/-/base-map-3.0.0-alpha.1.tgz#6ab96ce1f33ae377367777e2fc7f4b0d6789cb24" + integrity sha512-GMiWSITX4hbs6darpOJEVL0De97ghI5f3FhZS6XyQlrlB1NS7o/ixbEFIpCLCF4dewjcNE9nKOCBijZPaF3mAw== + dependencies: + "@opentripplanner/types" "^2.0.0" + mapbox-gl "npm:empty-npm-package@1.0.0" + maplibre-gl "^2.1.9" + react-map-gl "^7.0.15" + +"@opentripplanner/core-utils@^4.1.0", "@opentripplanner/core-utils@^4.11.0", "@opentripplanner/core-utils@^4.11.2", "@opentripplanner/core-utils@^4.11.5", "@opentripplanner/core-utils@^4.5.0": version "4.11.5" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.11.5.tgz#ac90d443009fab02aae971d4ed6c1e0fc7da134f" integrity sha512-oVVfbQqzHaY82cYLRm/GpybCo5LsDa8PTJQDU/w/DJLqSqyYm2I2sitQxk6jvmP6Zq80asdxMRvM8Si8yI77aQ== @@ -2311,7 +2366,7 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^5.0.2": +"@opentripplanner/core-utils@^5.0.1", "@opentripplanner/core-utils@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-5.0.2.tgz#5340897aefeebc1cf3ff8b8fdd588659a524d370" integrity sha512-gqNCLYhgeZmABMAbmiHoK2JPKhp+UwPLSnl3tyaxpsHt40qz51FFa27XkEFMR8+NZdWKSfjSlTO2njuY2WsTEA== @@ -2329,17 +2384,16 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/endpoints-overlay@^1.4.1": +"@opentripplanner/endpoints-overlay@../otp-ui/packages/endpoints-overlay": version "1.4.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.4.1.tgz#67c6ed98b0c08d70fc2aef22ccc97ef5b69f5cc9" - integrity sha512-aghe6THmdUDgudOdXbYpezWwbONyTxWrrGIrcgIO4/WoRSUFdmSSlWtSM2CmfAbYPMLUo2J5t7r4gjIAS4tLOA== dependencies: - "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^4.11.5" "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" -"@opentripplanner/from-to-location-picker@^2.1.0", "@opentripplanner/from-to-location-picker@^2.1.2": +"@opentripplanner/from-to-location-picker@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@opentripplanner/from-to-location-picker/-/from-to-location-picker-2.1.2.tgz#c19044311dc9f511125a4146da281f5eec5b8cf1" integrity sha512-Pkc2cSbuiSdw8ZCcMdSTk5jyPSN7JzAoIqO5/ab1CPQZznjG5Ny7XVloGvdYGv0+wF7wR/KPH8/xBX7T7b2ykw== @@ -2423,14 +2477,11 @@ "@styled-icons/fa-regular" "^10.34.0" "@styled-icons/fa-solid" "^10.34.0" -"@opentripplanner/park-and-ride-overlay@^1.2.4": +"@opentripplanner/park-and-ride-overlay@../otp-ui/packages/park-and-ride-overlay": version "1.2.4" - resolved "https://registry.yarnpkg.com/@opentripplanner/park-and-ride-overlay/-/park-and-ride-overlay-1.2.4.tgz#4c9707193eb7dd769b511b960e0af2363b94abd9" - integrity sha512-tXysCpnb3jGkPm77UCqZqZ1o0o8SyqIpKvaFLF7OUuQJ/cjjOM9exLjkJ1vQMPLyXo4DG8Hvnby3dPjm2cp05w== dependencies: - "@opentripplanner/core-utils" "^4.5.0" - "@opentripplanner/from-to-location-picker" "^2.1.0" - prop-types "^15.7.2" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/from-to-location-picker" "^2.1.2" "@opentripplanner/printable-itinerary@^2.0.1": version "2.0.1" @@ -2439,32 +2490,27 @@ dependencies: "@opentripplanner/itinerary-body" "^3.0.1" -"@opentripplanner/route-viewer-overlay@^1.4.0": +"@opentripplanner/route-viewer-overlay@../otp-ui/packages/route-viewer-overlay": version "1.4.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/route-viewer-overlay/-/route-viewer-overlay-1.4.0.tgz#b239407f9975109bdd61faa9a144bcf21dcad5e9" - integrity sha512-M+JNVIupXuvlG9mX7fGJxB3sflharjEQMjY3qtCBSC/ogLs8wJxOz9ZxelLUheFXjlMsp9RMe8yaLrw6Y6y5LA== dependencies: "@mapbox/polyline" "^1.1.0" + "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^4.5.0" + maplibre-gl "^2.1.9" point-in-polygon "^1.1.0" - prop-types "^15.7.2" + react-map-gl "^7.0.15" -"@opentripplanner/stop-viewer-overlay@^1.1.1": +"@opentripplanner/stop-viewer-overlay@../otp-ui/packages/stop-viewer-overlay": version "1.1.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/stop-viewer-overlay/-/stop-viewer-overlay-1.1.1.tgz#58755b0335ae7bab4372160582f5cbf649cc9d31" - integrity sha512-thJ28KjEmf75i0bDebC6+wCdpJSaTLoncS4OOtGaAltRH09RxUwLb+E6OllArcb6GE6InRZSE6Z5zgy2vhOuoA== dependencies: - "@opentripplanner/core-utils" "^4.1.0" - prop-types "^15.7.2" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^4.5.0" -"@opentripplanner/stops-overlay@^4.0.0": +"@opentripplanner/stops-overlay@../otp-ui/packages/stops-overlay": version "4.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/stops-overlay/-/stops-overlay-4.0.0.tgz#326a68dd04e0273d2813a0e29004ff0397e4cd4e" - integrity sha512-oDIDiqSjfKO5lLsnntTKAi4aTXLx2ozOi70GXfAJErdOBTpEPCiJEjEjVqgm208SPtcKyi1MPNwyIYP2CHtaJA== dependencies: - "@opentripplanner/core-utils" "^4.5.0" - "@opentripplanner/from-to-location-picker" "^2.1.0" - "@opentripplanner/zoom-based-markers" "^1.2.1" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/from-to-location-picker" "^2.1.2" flat "^5.0.2" "@opentripplanner/transit-vehicle-overlay@^2.4.0": @@ -2483,14 +2529,14 @@ lodash.clonedeep "^4.5.0" throttle-debounce "^2.1.0" -"@opentripplanner/transitive-overlay@^1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.1.4.tgz#569d00b07bd32d78af178a2b64bbc5d211e01c2a" - integrity sha512-2kCgLg447H3l27qpX0ZquubqdkQQNWQwuuscLY135KwCkQOmqjwznw0yGN9E9EdA+1LOmMiZA//gnc6GkB3yUg== +"@opentripplanner/transitive-overlay@../otp-ui/packages/transitive-overlay": + version "1.1.5" dependencies: - "@opentripplanner/core-utils" "^4.5.0" + "@mapbox/polyline" "^1.1.1" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^5.0.1" lodash.isequal "^4.5.0" - transitive-js "^0.13.7" + transitive-js "^0.14.1" "@opentripplanner/trip-details@^2.0.0": version "2.0.0" @@ -2518,32 +2564,28 @@ react-indiana-drag-scroll "^2.0.1" react-inlinesvg "^2.3.0" -"@opentripplanner/trip-viewer-overlay@^1.1.1": +"@opentripplanner/trip-viewer-overlay@../otp-ui/packages/trip-viewer-overlay": version "1.1.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-viewer-overlay/-/trip-viewer-overlay-1.1.1.tgz#2b7741f5e3749d4047f827cbca7ed0c1022726fe" - integrity sha512-TKI0qA0Bs1gpQG5dChDM348Oe6ZDXiyiZikitQdXd2NQOVzxtW+n4EVKcaNFlgCNIcI+fYNJ9zi2qpFrFxPCHw== dependencies: "@mapbox/polyline" "^1.1.0" - "@opentripplanner/core-utils" "^4.1.0" - prop-types "^15.7.2" + "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^4.5.0" "@opentripplanner/types@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@opentripplanner/types/-/types-2.0.0.tgz#81c937530c9f9ae7e66dfb0aab222d2508ea9f99" integrity sha512-7O4OeYs8jfLQQ1B3kRokFzwzyvMonOXkhJD8pgxrLqgbMIBet21X5LCbgctRW1qK6FV4tCkgejHNvQizWfovyw== -"@opentripplanner/vehicle-rental-overlay@^1.4.3": +"@opentripplanner/vehicle-rental-overlay@../otp-ui/packages/vehicle-rental-overlay": version "1.4.3" - resolved "https://registry.yarnpkg.com/@opentripplanner/vehicle-rental-overlay/-/vehicle-rental-overlay-1.4.3.tgz#7e78faa50af10c21ecc39fdbd5502a9333543d4d" - integrity sha512-tqDNG/zmeDh4HTsgy32er6kbk2kObYLvu5W43W80J/PDQ4Rb38MIx/7qLzaPFKEXFgNZh+EELayykIkncN8wxA== dependencies: + "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^4.5.0" "@opentripplanner/from-to-location-picker" "^2.1.2" - "@opentripplanner/zoom-based-markers" "^1.2.1" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" lodash.memoize "^4.1.2" - prop-types "^15.7.2" + react-map-gl "^7.0.15" "@opentripplanner/zoom-based-markers@^1.2.1": version "1.2.1" @@ -3251,7 +3293,7 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/geojson@7946.0.8": +"@types/geojson@*", "@types/geojson@7946.0.8", "@types/geojson@^7946.0.8": version "7946.0.8" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== @@ -3339,6 +3381,27 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-3.0.32.tgz#f4e5af31e9e9b196d8e5fca8a5e2e20aa3d60b69" integrity sha512-ZXyOOm83p7X8p3s0IYM3VeueNmHpkk/yMlP8CLeOnEcu6hIwPH7YjZBvhQkR0ZFS2DqZAxKtJ/M5fcuv3OU5BA== +"@types/mapbox-gl@^2.6.0": + version "2.7.3" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-2.7.3.tgz#d75049251f1cb5f5a5453d6c20ffb9d2de6cbd75" + integrity sha512-XdveeJptNNZw7ZoeiAJ2/dupNtWaV6qpBG/SOFEpQNQAc+oiO6qUznX85n+W1XbLeD8SVRVfVORKuR+I4CHDZw== + dependencies: + "@types/geojson" "*" + +"@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz#488a9b76e8457d6792ea2504cdd4ecdd9860a27e" + integrity sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA== + +"@types/mapbox__vector-tile@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz#8fa1379dbaead1e1b639b8d96cfd174404c379d6" + integrity sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g== + dependencies: + "@types/geojson" "*" + "@types/mapbox__point-geometry" "*" + "@types/pbf" "*" + "@types/minimatch@*": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -3364,6 +3427,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/pbf@*", "@types/pbf@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.2.tgz#8d291ad68b4b8c533e96c174a2e3e6399a59ed61" + integrity sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ== + "@types/prettier@^2.0.0", "@types/prettier@^2.1.5": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" @@ -7328,6 +7396,11 @@ css@^2.0.0: source-map-resolve "^0.5.2" urix "^0.1.0" +csscolorparser@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" + integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w== + cssdb@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" @@ -8120,6 +8193,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +earcut@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.3.tgz#d44ced2ff5a18859568e327dd9c7d46b16f55cf4" + integrity sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug== + easy-bem@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03" @@ -9795,6 +9873,11 @@ geojson-rbush@3.x: "@types/geojson" "7946.0.8" rbush "^3.0.1" +geojson-vt@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" + integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== + get-assigned-identifiers@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" @@ -9853,7 +9936,7 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -9882,6 +9965,11 @@ git-log-parser@^1.2.0: through2 "~2.0.0" traverse "~0.6.6" +gl-matrix@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -10536,7 +10624,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -12555,6 +12643,11 @@ just-diff@^3.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-3.1.1.tgz#d50c597c6fd4776495308c63bdee1b6839082647" integrity sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ== +kdbush@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" + integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== + keycode@^2.1.7, keycode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" @@ -12643,11 +12736,6 @@ leaflet-rotatedmarker@^0.2.0: resolved "https://registry.yarnpkg.com/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz#4467f49f98d1bfd56959bd9c6705203dd2601277" integrity sha1-RGf0n5jRv9VpWb2cZwUgPdJgEnc= -leaflet@^1.6.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" - integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -13285,6 +13373,40 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +"mapbox-gl@npm:empty-npm-package@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/empty-npm-package/-/empty-npm-package-1.0.0.tgz#fda29eb6de5efa391f73d578697853af55f6793a" + integrity sha512-q4Mq/+XO7UNDdMiPpR/LIBIW1Zl4V0Z6UT9aKGqIAnBCtCb3lvZJM1KbDbdzdC8fKflwflModfjR29Nt0EpcwA== + +maplibre-gl@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-2.1.9.tgz#042f3ef4224fa890ecf7a410145243f1fc943dcd" + integrity sha512-pnWJmILeZpgA5QSI7K7xFK3yrkyYTd9srw3fCi2Ca52Phm78hsznPwUErEQcZLfxXKn/1h9t8IPdj0TH0NBNbg== + dependencies: + "@mapbox/geojson-rewind" "^0.5.1" + "@mapbox/jsonlint-lines-primitives" "^2.0.2" + "@mapbox/mapbox-gl-supported" "^2.0.1" + "@mapbox/point-geometry" "^0.1.0" + "@mapbox/tiny-sdf" "^2.0.4" + "@mapbox/unitbezier" "^0.0.1" + "@mapbox/vector-tile" "^1.3.1" + "@mapbox/whoots-js" "^3.1.0" + "@types/geojson" "^7946.0.8" + "@types/mapbox__point-geometry" "^0.1.2" + "@types/mapbox__vector-tile" "^1.3.0" + "@types/pbf" "^3.0.2" + csscolorparser "~1.0.3" + earcut "^2.2.3" + geojson-vt "^3.2.1" + gl-matrix "^3.4.3" + murmurhash-js "^1.0.0" + pbf "^3.2.1" + potpack "^1.0.2" + quickselect "^2.0.0" + supercluster "^7.1.4" + tinyqueue "^2.0.3" + vt-pbf "^3.1.3" + marked-terminal@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-4.1.1.tgz#34a6f063cd6cfe26bffaf5bac3724e24242168a9" @@ -13606,6 +13728,11 @@ minimist@1.2.5, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + minipass-collect@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" @@ -13785,6 +13912,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +murmurhash-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" + integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -14918,6 +15050,14 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== +pbf@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a" + integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ== + dependencies: + ieee754 "^1.1.12" + resolve-protobuf-schema "^2.1.0" + pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" @@ -16015,6 +16155,11 @@ postcss@^8.1.0: nanoid "^3.1.23" source-map-js "^0.6.2" +potpack@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" + integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -16250,6 +16395,11 @@ protobufjs@6.8.6: "@types/node" "^8.9.4" long "^4.0.0" +protocol-buffers-schema@^3.3.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" + integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== + proxy-addr@~2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -16679,16 +16829,6 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-leaflet@^2.6.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-2.8.0.tgz#14bfc77b6ae8263d3a62e0ba7a39539c05973c6f" - integrity sha512-Y7oHtNrrlRH8muDttXf+jZ2Ga/X7jneSGi1GN8uEdeCfLProTqgG2Zoa5TfloS3ZnY20v7w+DIenMG59beFsQw== - dependencies: - "@babel/runtime" "^7.12.1" - fast-deep-equal "^3.1.3" - hoist-non-react-statics "^3.3.2" - warning "^4.0.3" - react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -16701,6 +16841,13 @@ react-loading-skeleton@^2.1.1: dependencies: "@emotion/core" "^10.0.22" +react-map-gl@^7.0.15: + version "7.0.15" + resolved "https://registry.yarnpkg.com/react-map-gl/-/react-map-gl-7.0.15.tgz#ded08ccff49012099a9945b6c2ef7f266dfbde49" + integrity sha512-l7x8lBhIEcHTreSgrc7hsKv5HsMY1wQg2PVXuKAPmQtgRZc9C3NGwurVJFe24gOlAwzta5UavAHWDiZdU1ZNCw== + dependencies: + "@types/mapbox-gl" "^2.6.0" + react-modal@^3.12.1: version "3.14.3" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.3.tgz#7eb7c5ec85523e5843e2d4737cc17fc3f6aeb1c0" @@ -17505,6 +17652,13 @@ resolve-pathname@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== +resolve-protobuf-schema@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758" + integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ== + dependencies: + protocol-buffers-schema "^3.3.1" + resolve-url-loader@^3.1.2: version "3.1.4" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.4.tgz#3c16caebe0b9faea9c7cc252fa49d2353c412320" @@ -18870,6 +19024,13 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +supercluster@^7.1.4: + version "7.1.5" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3" + integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg== + dependencies: + kdbush "^3.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -19215,6 +19376,11 @@ tinycolor2@^1.4.2: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinyqueue@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" + integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -19320,23 +19486,6 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" -transitive-js@^0.13.7: - version "0.13.8" - resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.13.8.tgz#da06b459c53f8248ef861d913bb847a2c7e92071" - integrity sha512-cLDsOHfuNikFUPMZeCrVbJJCfmJCItlZnyAWsknr5TAnsZrnAP5hZqabyVFf1UAhBhNlcXktT3Y4kihaqcT/1Q== - dependencies: - augment "4.3.0" - component-each "0.2.6" - component-emitter "1.2.1" - d3 "^3.5.8" - debug "^2.5.1" - lodash.foreach "^4.5.0" - measure-text "^0.0.4" - priorityqueuejs "1.0.0" - rounded-rect "^0.0.1" - sphericalmercator "^1.0.5" - svg.js "^2.6.5" - transitive-js@^0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.14.1.tgz#0826ad3cc3b5cc950266ca8031e264974eae9124" @@ -20044,6 +20193,15 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vt-pbf@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac" + integrity sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA== + dependencies: + "@mapbox/point-geometry" "0.1.0" + "@mapbox/vector-tile" "^1.3.1" + pbf "^3.2.1" + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" From c15ce6e262513972ce0a8684bf17ef4feb9e3245 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 27 Jun 2022 18:48:46 +0100 Subject: [PATCH 0302/1425] refactor: avoid senseless re-render --- lib/actions/ui.js | 9 +++++++++ lib/components/map/default-map.js | 1 - lib/components/viewers/RouteRow.js | 6 ++---- lib/components/viewers/route-details.js | 15 +-------------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 6f50b72b6..7924315bc 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -91,6 +91,15 @@ export function routeTo(url, replaceSearch, routingMethod = push) { export function setViewedRoute(payload) { return function (dispatch, getState) { + const { otp } = getState() + const { viewedRoute } = otp.ui + if ( + viewedRoute && + payload?.viewedRoute?.routeId === viewedRoute.routeId && + payload?.viewedRoute?.patternId === viewedRoute.patternId + ) + return + dispatch(viewRoute(payload)) const path = getPathFromParts( diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 86f2c8124..f11d3061e 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -276,7 +276,6 @@ class DefaultMap extends Component { > {popup} {/* The default overlays */} - {/* */} {/* */} diff --git a/lib/components/viewers/RouteRow.js b/lib/components/viewers/RouteRow.js index d785701a8..f942f0b13 100644 --- a/lib/components/viewers/RouteRow.js +++ b/lib/components/viewers/RouteRow.js @@ -102,14 +102,12 @@ export class RouteRow extends PureComponent { } _onClick = () => { - const { getVehiclePositionsForRoute, isActive, route, setViewedRoute } = - this.props + const { isActive, route, setViewedRoute } = this.props if (isActive) { // Deselect current route if active. setViewedRoute({ patternId: null, routeId: null }) } else { // Otherwise, set active and fetch route patterns. - getVehiclePositionsForRoute(route.id) setViewedRoute({ routeId: route.id }) } } @@ -157,7 +155,7 @@ export class RouteRow extends PureComponent { enter={{ animation: 'slideDown' }} leave={{ animation: 'slideUp' }} > - {isActive && } + {isActive && } ) diff --git a/lib/components/viewers/route-details.js b/lib/components/viewers/route-details.js index 98b55ef55..7281738b8 100644 --- a/lib/components/viewers/route-details.js +++ b/lib/components/viewers/route-details.js @@ -42,23 +42,10 @@ class RouteDetails extends Component { } componentDidMount = () => { - const { getVehiclePositionsForRoute, pattern, route } = this.props + const { getVehiclePositionsForRoute, route } = this.props if (!route.vehicles) { getVehiclePositionsForRoute(route.id) } - if (!pattern?.stops) { - this.getStops() - } - } - - componentDidUpdate = (prevProps) => { - if ( - prevProps.pattern?.id !== this.props.pattern?.id || - !this.props.pattern?.stops || - this.props.pattern?.stops?.length === 0 - ) { - this.getStops() - } } /** From bf9780da87d2936b887710e24c56933483fb0a19 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 28 Jun 2022 14:02:18 +0100 Subject: [PATCH 0303/1425] refactor(metro-ui): support custom vector tile base layers --- lib/components/map/default-map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index f11d3061e..bc5264a33 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -268,7 +268,8 @@ class DefaultMap extends Component { return ( Date: Tue, 28 Jun 2022 11:23:19 -0400 Subject: [PATCH 0304/1425] Use shorthand in default-map --- lib/components/map/default-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index e6e5657d4..52ab3be43 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -145,7 +145,7 @@ class DefaultMap extends Component { const overlayCompany = oConfig.companies[0] // TODO: handle multi-company overlays if (added.includes(overlayMode)) { // Company-based mode was just selected; enable overlay iff overlay's company is active - if (newQuery.companies && newQuery.companies.includes(overlayCompany)) { + if (newQuery.companies?.includes(overlayCompany)) { overlayVisibility.push({ overlay: oConfig, visible: true From d01467dda794ad69e515cd26c612a05dae6e83bf Mon Sep 17 00:00:00 2001 From: David Emory Date: Tue, 28 Jun 2022 14:24:15 -0700 Subject: [PATCH 0305/1425] Fix linter issues --- lib/components/admin/field-trip-list.js | 282 ++++++++++++------------ 1 file changed, 139 insertions(+), 143 deletions(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index e2b96c0da..d8cdd3bbe 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -1,37 +1,113 @@ +/* eslint-disable react/prop-types */ +import { Badge, Button } from 'react-bootstrap' +import { connect } from 'react-redux' +import { injectIntl } from 'react-intl' import moment from 'moment' import qs from 'qs' import React, { Component } from 'react' -import { Badge, Button } from 'react-bootstrap' -import { injectIntl } from 'react-intl' -import { connect } from 'react-redux' import * as fieldTripActions from '../../actions/field-trip' -import Loading from '../narrative/loading' -import {getVisibleRequests, TABS} from '../../util/call-taker' -import {FETCH_STATUS} from '../../util/constants' +import { FETCH_STATUS } from '../../util/constants' +import { getVisibleRequests, TABS } from '../../util/call-taker' import Icon from '../util/icon' +import Loading from '../narrative/loading' -import FieldTripStatusIcon from './field-trip-status-icon' -import {FieldTripRecordButton, WindowHeader} from './styled' +import { FieldTripRecordButton, WindowHeader } from './styled' import DraggableWindow from './draggable-window' +import FieldTripStatusIcon from './field-trip-status-icon' + +function Tab({ data, filter, onClick, tab }) { + const count = React.useMemo(() => data.filter(tab.filter).length, [data, tab]) + const active = tab.id === filter.tab + const style = { + backgroundColor: active ? 'navy' : undefined, + borderRadius: 5, + color: active ? 'white' : undefined, + padding: '2px 3px' + } + return ( + + ) +} + +class FieldTripRequestRecord extends Component { + _onClick = () => { + const { onClick, request } = this.props + onClick(request) + } + + render() { + const { active, request } = this.props + const style = { + backgroundColor: active ? 'lightgrey' : undefined, + borderBottom: '1px solid grey' + } + const { + endLocation, + id, + inboundTripStatus, + outboundTripStatus, + schoolName, + startLocation, + teacherName, + timeStamp, + travelDate + } = request + const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') + return ( +
  • + + + {schoolName} Trip (#{id}) + + + + Outbound + + + Inbound + + + + Submitted by {teacherName} on {timeStamp} + + + {startLocation} to {endLocation} on {formattedDate} + + +
  • + ) + } +} /** * Displays a searchable list of field trip requests in a draggable window. */ class FieldTripList extends Component { - constructor (props) { + constructor(props) { super(props) this.state = { date: moment().startOf('day').format('YYYY-MM-DD') } } + _onClickFieldTrip = (request) => { - const { - callTaker, - fetchFieldTripDetails, - intl, - setActiveFieldTrip - } = this.props + const { callTaker, fetchFieldTripDetails, intl, setActiveFieldTrip } = + this.props if (request.id === callTaker.fieldTrip.activeId) { this._onCloseActiveFieldTrip() } else { @@ -50,10 +126,10 @@ class FieldTripList extends Component { } _getReportUrl = () => { - const {datastoreUrl} = this.props - const {date} = this.state + const { datastoreUrl } = this.props + const { date } = this.state const [year, month, day] = date.split('-') - const params = {day, month, year} + const params = { day, month, year } return `${datastoreUrl}/fieldtrip/opsReport?${qs.stringify(params)}` } @@ -62,36 +138,36 @@ class FieldTripList extends Component { * each time the search input changes (on TriMet's production instance there * are thousands of field trip requests). */ - _handleSearchKeyUp = e => { - const {callTaker, setFieldTripFilter} = this.props - const {search} = callTaker.fieldTrip.filter + _handleSearchKeyUp = (e) => { + const { callTaker, setFieldTripFilter } = this.props + const { search } = callTaker.fieldTrip.filter const newSearch = e.target.value // Update filter if Enter is pressed or search value is entirely cleared. const newSearchEntered = e.keyCode === 13 && newSearch !== search const searchCleared = search && !newSearch if (newSearchEntered || searchCleared) { - setFieldTripFilter({search: newSearch}) + setFieldTripFilter({ search: newSearch }) } } - _onTabChange = e => { - this.props.setFieldTripFilter({tab: e.currentTarget.name}) + _onTabChange = (e) => { + this.props.setFieldTripFilter({ tab: e.currentTarget.name }) } - _updateReportDate = evt => this.setState({ date: evt.target.value }) + _updateReportDate = (evt) => this.setState({ date: evt.target.value }) - render () { - const {callTaker, style, toggleFieldTrips, visibleRequests} = this.props - const {fieldTrip} = callTaker - const {activeId, filter} = fieldTrip - const {search} = filter - const {date} = this.state + render() { + const { callTaker, style, toggleFieldTrips, visibleRequests } = this.props + const { fieldTrip } = callTaker + const { activeId, filter } = fieldTrip + const { search } = filter + const { date } = this.state return ( - @@ -115,19 +187,19 @@ class FieldTripList extends Component { header={ <> - Field Trip Requests{' '} - + Field Trip Requests{' '} + - {TABS.map(tab => + {TABS.map((tab) => ( - )} + tab={tab} + /> + ))} } onClickClose={toggleFieldTrips} style={style} > - {fieldTrip.requests.status === FETCH_STATUS.FETCHING - ? - : visibleRequests.length > 0 - ? visibleRequests.map((request, i) => ( - - )) - :
    No field trips found.
    - } + {fieldTrip.requests.status === FETCH_STATUS.FETCHING ? ( + + ) : visibleRequests.length > 0 ? ( + visibleRequests.map((request) => ( + + )) + ) : ( +
    No field trips found.
    + )}
    ) } } -function Tab ({data, filter, onClick, tab}) { - const count = React.useMemo(() => data.filter(tab.filter).length, [data, tab]) - const active = tab.id === filter.tab - const style = { - backgroundColor: active ? 'navy' : undefined, - borderRadius: 5, - color: active ? 'white' : undefined, - padding: '2px 3px' - } - return ( - - ) -} - -class FieldTripRequestRecord extends Component { - _onClick = () => { - const {onClick, request} = this.props - onClick(request) - } - - render () { - const {active, request} = this.props - const style = { - backgroundColor: active ? 'lightgrey' : undefined, - borderBottom: '1px solid grey' - } - const { - endLocation, - id, - inboundTripStatus, - outboundTripStatus, - schoolName, - startLocation, - teacherName, - timeStamp, - travelDate - } = request - const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') - return ( -
  • - - - {schoolName} Trip (#{id}) - - - - Outbound - - - Inbound - - - - Submitted by {teacherName} on {timeStamp} - - - {startLocation} to {endLocation} on {formattedDate} - - -
  • - ) - } -} - -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state) => { return { callTaker: state.callTaker, currentQuery: state.otp.currentQuery, @@ -265,6 +260,7 @@ const mapDispatchToProps = { toggleFieldTrips: fieldTripActions.toggleFieldTrips } -export default connect(mapStateToProps, mapDispatchToProps)( - injectIntl(FieldTripList) -) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(FieldTripList)) From 9022070be50fda76118210491529ba0181f15d3c Mon Sep 17 00:00:00 2001 From: David Emory Date: Tue, 28 Jun 2022 14:25:55 -0700 Subject: [PATCH 0306/1425] Check that field trip date exists before formatting --- lib/components/admin/field-trip-list.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index d8cdd3bbe..4d53b52a7 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -61,7 +61,11 @@ class FieldTripRequestRecord extends Component { timeStamp, travelDate } = request - const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') + + const formattedDate = travelDate + ? travelDate.split(' ').splice(0, 3).join(' ') + : 'N/A' + return (
  • Date: Wed, 29 Jun 2022 12:56:58 +0100 Subject: [PATCH 0307/1425] refactor: automatic bounds updating --- .../map/connected-stop-viewer-overlay.js | 15 ++++++++++++++- lib/components/viewers/RouteRow.js | 5 ++++- yarn.lock | 9 +++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/components/map/connected-stop-viewer-overlay.js b/lib/components/map/connected-stop-viewer-overlay.js index 000900a6e..eaebbb437 100644 --- a/lib/components/map/connected-stop-viewer-overlay.js +++ b/lib/components/map/connected-stop-viewer-overlay.js @@ -1,8 +1,9 @@ // TYPESCRIPT TODO: all props here are missing types /* eslint-disable react/prop-types */ import { connect } from 'react-redux' +import { useMap } from 'react-map-gl' import ParkAndRideOverlay from '@opentripplanner/park-and-ride-overlay' -import React from 'react' +import React, { useEffect } from 'react' import VehicleRentalOverlay from '@opentripplanner/vehicle-rental-overlay' import * as mapActions from '../../actions/map' @@ -15,6 +16,18 @@ const MIN_ZOOM = 17 */ const StopViewerOverlay = (props) => { const { configCompanies, mapConfig, setLocation, stopData, stops } = props + const { mainMap } = useMap() + + useEffect(() => { + if (stopData?.lat && stopData?.lon) { + mainMap?.flyTo({ + center: { lat: stopData.lat, lon: stopData.lon }, + duration: 500, + zoom: 16 + }) + } + }, [stopData?.lat, stopData?.lon, mainMap]) + if (!stopData) return null const { bikeRental, parkAndRideLocations, vehicleRental } = stopData diff --git a/lib/components/viewers/RouteRow.js b/lib/components/viewers/RouteRow.js index f942f0b13..bf2cc8cdf 100644 --- a/lib/components/viewers/RouteRow.js +++ b/lib/components/viewers/RouteRow.js @@ -113,7 +113,7 @@ export class RouteRow extends PureComponent { } render() { - const { intl, isActive, operator, route } = this.props + const { findRoute, intl, isActive, operator, route } = this.props const { ModeIcon } = this.context return ( @@ -122,6 +122,9 @@ export class RouteRow extends PureComponent { className="clear-button-formatting" isActive={isActive} onClick={this._onClick} + onMouseEnter={() => { + findRoute({ routeId: route.id }) + }} > {operator && operator.logo && ( diff --git a/yarn.lock b/yarn.lock index 088014a83..ff6ad2241 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2616,6 +2616,7 @@ "@mapbox/polyline" "^1.1.1" "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^5.0.1" + "@turf/bbox" "^6.5.0" lodash.isequal "^4.5.0" transitive-js "^0.14.1" @@ -3240,6 +3241,14 @@ "@turf/helpers" "^6.4.0" "@turf/meta" "^6.4.0" +"@turf/bbox@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-6.5.0.tgz#bec30a744019eae420dac9ea46fb75caa44d8dc5" + integrity sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw== + dependencies: + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + "@turf/bearing@^6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@turf/bearing/-/bearing-6.5.0.tgz#462a053c6c644434bdb636b39f8f43fb0cd857b0" From 8ebe1c125d977bc084055d622cd429e24bbd4c05 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 29 Jun 2022 15:55:55 +0100 Subject: [PATCH 0308/1425] refactor: address pr feedback --- lib/components/form/batch-settings.tsx | 10 +++++++++- .../narrative/metro/default-route-renderer.tsx | 2 +- lib/components/narrative/styled.js | 2 +- lib/components/viewers/stop-time-cell.tsx | 2 +- lib/components/viewers/viewers.css | 2 +- lib/util/viewer.js | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/components/form/batch-settings.tsx b/lib/components/form/batch-settings.tsx index 8c4f2d9fa..85bde8fc5 100644 --- a/lib/components/form/batch-settings.tsx +++ b/lib/components/form/batch-settings.tsx @@ -222,7 +222,15 @@ class BatchSettings extends Component { id: 'components.BatchSettings.planTripTooltip' })} > - + {expanded === 'DATE_TIME' && ( diff --git a/lib/components/narrative/metro/default-route-renderer.tsx b/lib/components/narrative/metro/default-route-renderer.tsx index 2d7c0d23e..b16650a59 100644 --- a/lib/components/narrative/metro/default-route-renderer.tsx +++ b/lib/components/narrative/metro/default-route-renderer.tsx @@ -6,7 +6,7 @@ import styled from 'styled-components' // doesn't allow us to style it from within the RouteBlock const Block = styled.section<{ color: string }>` background: #${(props) => props.color}1A; - padding: 2px 5px; + padding: 3px 7px; border-top: 5px solid #${(props) => props.color}; border-radius: 5px; text-align: center; diff --git a/lib/components/narrative/styled.js b/lib/components/narrative/styled.js index 6b95e8fe3..2b6c149cb 100644 --- a/lib/components/narrative/styled.js +++ b/lib/components/narrative/styled.js @@ -20,7 +20,7 @@ export const SingleModeRowContainer = styled.section` display: grid; grid-template-columns: 1fr 1fr 1fr; padding: 15px; - gap: 15px; + gap: 10px; &:first-of-type { margin-top: 25px; diff --git a/lib/components/viewers/stop-time-cell.tsx b/lib/components/viewers/stop-time-cell.tsx index 7585ae1ed..c71465316 100644 --- a/lib/components/viewers/stop-time-cell.tsx +++ b/lib/components/viewers/stop-time-cell.tsx @@ -1,5 +1,5 @@ import { format, getTimezoneOffset, utcToZonedTime } from 'date-fns-tz' -import { FormattedMessage, FormattedTime } from 'react-intl' +import { FormattedMessage } from 'react-intl' import addDays from 'date-fns/addDays' import coreUtils from '@opentripplanner/core-utils' import isSameDay from 'date-fns/isSameDay' diff --git a/lib/components/viewers/viewers.css b/lib/components/viewers/viewers.css index 397cf7a67..0969a541a 100644 --- a/lib/components/viewers/viewers.css +++ b/lib/components/viewers/viewers.css @@ -83,7 +83,7 @@ /* stop viewer styles */ .otp .stop-viewer .route-row { - border: 1px solid black; + border: 1px solid #111; margin-top: 10px; border-radius: 5px; } diff --git a/lib/util/viewer.js b/lib/util/viewer.js index de4802736..2aa2e7741 100644 --- a/lib/util/viewer.js +++ b/lib/util/viewer.js @@ -314,7 +314,7 @@ export function getTripStatus( * one leg with realtime info */ export function containsRealtimeLeg(itinerary) { - return !!itinerary.legs.find((leg) => leg?.realTime) + return itinerary.legs.some((leg) => leg?.realtime) } /** From f91f9104f516de71f829ff4a9d7b74253e284854 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 29 Jun 2022 16:00:13 +0100 Subject: [PATCH 0309/1425] test: update snapshots --- .../viewers/__snapshots__/stop-viewer.js.snap | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index bdb414409..8c838a55e 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -1317,7 +1317,7 @@ exports[`components > viewers > stop viewer should render countdown times after color="333333" >
    20 @@ -3240,7 +3240,7 @@ exports[`components > viewers > stop viewer should render countdown times for st color="333333" >
    20 @@ -5244,7 +5244,7 @@ exports[`components > viewers > stop viewer should render times after midnight w color="333333" >
    20 @@ -8447,7 +8447,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    20 @@ -8894,7 +8894,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    36 @@ -9341,7 +9341,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -9896,7 +9896,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -14120,7 +14120,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in color="333333" >
    20 From 51ece5e9d2fb7e6b600eaa310c16dd8c6bce0006 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 29 Jun 2022 16:02:57 +0100 Subject: [PATCH 0310/1425] chore: trigger github checks From cc08b77dabce87b7f7f1728e8cdddb8b9976447f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 30 Jun 2022 18:19:38 +0100 Subject: [PATCH 0311/1425] refactor: bring back transit-vehicle-overlay --- .../map/connected-transit-vehicle-overlay.js | 124 ++++++++++++++++++ lib/components/map/default-map.js | 3 +- package.json | 2 +- yarn.lock | 95 +------------- 4 files changed, 134 insertions(+), 90 deletions(-) create mode 100644 lib/components/map/connected-transit-vehicle-overlay.js diff --git a/lib/components/map/connected-transit-vehicle-overlay.js b/lib/components/map/connected-transit-vehicle-overlay.js new file mode 100644 index 000000000..1bd323422 --- /dev/null +++ b/lib/components/map/connected-transit-vehicle-overlay.js @@ -0,0 +1,124 @@ +// TODO: Typescript +/* eslint-disable react/prop-types */ +/** + * This overlay is similar to gtfs-rt-vehicle-overlay in that it shows + * realtime positions of vehicles on a route using the otp-ui/transit-vehicle-overlay. + * + * However, this overlay differs in a few ways: + * 1) This overlay retrieves vehicle locations from OTP + * 2) This overlay renders vehicles as blobs rather than a custom shape + * 3) This overlay does not handle updating positions + * 4) This overlay does not render route paths + * 5) This overlay has a custom popup on vehicle hover + */ + +import { connect } from 'react-redux' +import { FormattedMessage, FormattedNumber, injectIntl } from 'react-intl' +import React from 'react' +import TransitVehicleOverlay from '@opentripplanner/transit-vehicle-overlay' + +import FormattedTransitVehicleStatus from '../util/formatted-transit-vehicle-status' + +function VehicleTooltip(props) { + const { intl, vehicle } = props + + let vehicleLabel = vehicle?.label + // If a vehicle's label is less than 5 characters long, we can assume it is a vehicle + // number. If this is the case, prepend "vehicle" to it. + // Otherwise, the label itself is enough + if (vehicleLabel !== null && vehicleLabel?.length <= 5) { + vehicleLabel = intl.formatMessage( + { id: 'components.TransitVehicleOverlay.vehicleName' }, + { vehicleNumber: vehicleLabel } + ) + } else { + vehicleLabel = '' + } + + const stopStatus = vehicle?.stopStatus || 'in_transit_to' + + // FIXME: This may not be timezone adjusted as reported seconds may be in the wrong timezone. + // All needed info to fix this is available via route.agency.timezone + // However, the needed coreUtils methods are not updated to support this + return ( + <> + + {/* + FIXME: move back to core-utils for time handling + */} + {m}, + vehicleNameOrBlank: vehicleLabel + }} + /> + + {stopStatus !== 'STOPPED_AT' && vehicle?.speed > 0 && ( +
    + + ) + }} + /> +
    + )} + {vehicle?.nextStopName && ( +
    + +
    + )} + + ) +} +// connect to the redux store + +const mapStateToProps = (state) => { + const viewedRoute = state.otp.ui.viewedRoute + const route = state.otp.transitIndex?.routes?.[viewedRoute?.routeId] + + let vehicleList = [] + + // Add missing fields to vehicle list + if (viewedRoute?.routeId) { + vehicleList = route?.vehicles?.map((vehicle) => { + vehicle.routeType = route?.mode + vehicle.routeColor = route?.color + vehicle.textColor = route?.routeTextColor + return vehicle + }) + + // Remove all vehicles not on pattern being currently viewed + if (viewedRoute.patternId && vehicleList) { + vehicleList = vehicleList.filter( + (vehicle) => vehicle.patternId === viewedRoute.patternId + ) + } + } + return { + TooltipSlot: injectIntl(VehicleTooltip), + vehicles: vehicleList + } +} + +const mapDispatchToProps = {} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(TransitVehicleOverlay) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index bc5264a33..3b00d0864 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -27,6 +27,7 @@ import RouteViewerOverlay from './connected-route-viewer-overlay' import StopsOverlay from './connected-stops-overlay' import StopViewerOverlay from './connected-stop-viewer-overlay' import TransitiveOverlay from './connected-transitive-overlay' +import TransitVehicleOverlay from './connected-transit-vehicle-overlay' import TripViewerOverlay from './connected-trip-viewer-overlay' import VehicleRentalOverlay from './connected-vehicle-rental-overlay' @@ -279,7 +280,7 @@ class DefaultMap extends Component { {/* The default overlays */} - {/* */} + Date: Fri, 1 Jul 2022 09:06:03 +0100 Subject: [PATCH 0312/1425] refactor(connected-transit-vehicle-overlay): support route color --- lib/components/map/connected-transit-vehicle-overlay.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/components/map/connected-transit-vehicle-overlay.js b/lib/components/map/connected-transit-vehicle-overlay.js index 1bd323422..2d90b7fa2 100644 --- a/lib/components/map/connected-transit-vehicle-overlay.js +++ b/lib/components/map/connected-transit-vehicle-overlay.js @@ -111,6 +111,7 @@ const mapStateToProps = (state) => { } } return { + color: '#' + route?.color, TooltipSlot: injectIntl(VehicleTooltip), vehicles: vehicleList } From 5bcd9c554b519c95614fb9862582c18b2b14adb9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Jul 2022 09:41:58 +0100 Subject: [PATCH 0313/1425] refactor(maplibregl): don't refresh map --- lib/components/map/default-map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 3b00d0864..b630ad248 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -272,6 +272,7 @@ class DefaultMap extends Component { // Only 1 base layer is supported at the moment baseLayer={baseLayersWithNames?.[0]?.url} center={center} + mapLibreProps={{ reuseMaps: true }} maxZoom={mapConfig.maxZoom} onContextMenu={this.onMapClick} zoom={mapConfig.initZoom || 13} From 5636ce8b0571fca53ed62a289c12ecca288d7171 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Jul 2022 10:48:31 +0100 Subject: [PATCH 0314/1425] refactor(maplibregl): hack to support redux based panning --- .../map/connected-stop-viewer-overlay.js | 3 +-- lib/components/map/default-map.js | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/components/map/connected-stop-viewer-overlay.js b/lib/components/map/connected-stop-viewer-overlay.js index eaebbb437..f2ab83834 100644 --- a/lib/components/map/connected-stop-viewer-overlay.js +++ b/lib/components/map/connected-stop-viewer-overlay.js @@ -10,7 +10,6 @@ import * as mapActions from '../../actions/map' import EnhancedStopMarker from './enhanced-stop-marker' -const MIN_ZOOM = 17 /** * An overlay to view a collection of stops. */ @@ -26,7 +25,7 @@ const StopViewerOverlay = (props) => { zoom: 16 }) } - }, [stopData?.lat, stopData?.lon, mainMap]) + }, [stopData, mainMap]) if (!stopData) return null const { bikeRental, parkAndRideLocations, vehicleRental } = stopData diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index b630ad248..335b0719f 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -17,7 +17,11 @@ import { setMapPopupLocation, setMapPopupLocationAndGeocode } from '../../actions/map' -import { updateOverlayVisibility } from '../../actions/config' +import { + setMapCenter, + setMapZoom, + updateOverlayVisibility +} from '../../actions/config' import ElevationPointMarker from './elevation-point-marker' import EndpointsOverlay from './connected-endpoints-overlay' @@ -221,6 +225,8 @@ class DefaultMap extends Component { intl, mapConfig, mapPopupLocation, + setMapCenter, + setMapZoom, vehicleRentalQuery, vehicleRentalStations } = this.props @@ -229,7 +235,7 @@ class DefaultMap extends Component { const center = mapConfig && mapConfig.initLat && mapConfig.initLon ? [mapConfig.initLat, mapConfig.initLon] - : null + : [0, 0] const popup = mapPopupLocation && ( { + // This hack is needed to get the zoom functionality to work + // Redux only fires the render method if the prop changes, + // so it must be set to null so it can be "re-set" to an + // actual value. + if (mapConfig.initLat || mapConfig.initLon) { + setMapCenter({ lat: null, lon: null }) + } + }} zoom={mapConfig.initZoom || 13} > {popup} @@ -385,8 +400,10 @@ const mapDispatchToProps = { bikeRentalQuery, carRentalQuery, setLocation, + setMapCenter, setMapPopupLocation, setMapPopupLocationAndGeocode, + setMapZoom, updateOverlayVisibility, vehicleRentalQuery } From 5f49a41924f20c227997231d64ae99e3ecd21c99 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Jul 2022 11:02:52 +0100 Subject: [PATCH 0315/1425] refactor(maplibregl): show trip viewer --- lib/components/map/connected-trip-viewer-overlay.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/components/map/connected-trip-viewer-overlay.js b/lib/components/map/connected-trip-viewer-overlay.js index f0c0c6325..e3d90b1bc 100644 --- a/lib/components/map/connected-trip-viewer-overlay.js +++ b/lib/components/map/connected-trip-viewer-overlay.js @@ -1,5 +1,5 @@ -import TripViewerOverlay from '@opentripplanner/trip-viewer-overlay' import { connect } from 'react-redux' +import TripViewerOverlay from '@opentripplanner/trip-viewer-overlay' // connect to the redux store @@ -8,7 +8,8 @@ const mapStateToProps = (state, ownProps) => { return { tripData: viewedTrip ? state.otp.transitIndex.trips[viewedTrip.tripId] - : null + : null, + visible: true } } From edcd13944a9b22cd122bb501f1eebcc918c8aa13 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 1 Jul 2022 11:03:17 +0100 Subject: [PATCH 0316/1425] refactor(maplibregl): attempt at making zoom hack less troublesome --- lib/components/map/default-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 335b0719f..8fc0e1057 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -235,7 +235,7 @@ class DefaultMap extends Component { const center = mapConfig && mapConfig.initLat && mapConfig.initLon ? [mapConfig.initLat, mapConfig.initLon] - : [0, 0] + : [null, null] const popup = mapPopupLocation && ( Date: Tue, 5 Jul 2022 21:00:18 -0700 Subject: [PATCH 0317/1425] deps: update uuid-related packages --- package.json | 6 ++--- yarn.lock | 70 ++++++++++++++++++++++++++-------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 6788d5227..0956e1880 100644 --- a/package.json +++ b/package.json @@ -36,13 +36,13 @@ "dependencies": { "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^2.0.0", - "@opentripplanner/core-utils": "^5.0.2", + "@opentripplanner/core-utils": "^6.0.0", "@opentripplanner/endpoints-overlay": "^1.4.1", "@opentripplanner/from-to-location-picker": "^2.1.2", "@opentripplanner/geocoder": "^1.2.1", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^1.2.2", - "@opentripplanner/itinerary-body": "^3.0.5", + "@opentripplanner/itinerary-body": "^4.0.0", "@opentripplanner/location-field": "1.12.3", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.4", @@ -51,7 +51,7 @@ "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^4.0.0", "@opentripplanner/transit-vehicle-overlay": "^2.4.0", - "@opentripplanner/transitive-overlay": "^1.1.4", + "@opentripplanner/transitive-overlay": "^2.0.0", "@opentripplanner/trip-details": "^2.0.0", "@opentripplanner/trip-form": "^1.11.4", "@opentripplanner/trip-viewer-overlay": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 1e1c2f69c..c5ce0a01e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2363,10 +2363,10 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-5.0.0.tgz#15665da74d3a216576b95f48905efb7f9a780ed0" - integrity sha512-wxuIdsLi0JzRDiJPKxOsdVBAuDDaZZRqwVVWWMmZEMi1sVLt90G5vtcpEbzx3ry7FAmYYki+DWzJLLs5+dGP/g== +"@opentripplanner/core-utils@^5.0.0", "@opentripplanner/core-utils@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-5.0.2.tgz#5340897aefeebc1cf3ff8b8fdd588659a524d370" + integrity sha512-gqNCLYhgeZmABMAbmiHoK2JPKhp+UwPLSnl3tyaxpsHt40qz51FFa27XkEFMR8+NZdWKSfjSlTO2njuY2WsTEA== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/geocoder" "^1.2.2" @@ -2381,10 +2381,10 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^5.0.2": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-5.0.2.tgz#5340897aefeebc1cf3ff8b8fdd588659a524d370" - integrity sha512-gqNCLYhgeZmABMAbmiHoK2JPKhp+UwPLSnl3tyaxpsHt40qz51FFa27XkEFMR8+NZdWKSfjSlTO2njuY2WsTEA== +"@opentripplanner/core-utils@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-6.0.0.tgz#0e1fd2d6070146c6e11569933fa7a65c75bc69b6" + integrity sha512-iIpDK9v+9BRtawWl+4rxBA+wu6/a1WZeJ08RNHEDjBVuTSL146YqwfOeMMKtTvGOkEvH9NjlPAK5IESwYhww3g== dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/geocoder" "^1.2.2" @@ -2456,10 +2456,26 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/itinerary-body@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.0.5.tgz#f3239c4aaf8649b5a48b12cd08c293a10bcf277e" - integrity sha512-ixvXxIv4mFWt1IdwUrgOtGmbyBb5DzEXSkPSzeF4qq5jsVUsB58omtHNCmTxnm5V+JVXoMPiyoY9YE7SfNK6OQ== +"@opentripplanner/itinerary-body@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.1.1.tgz#854e83022de09cb023e7f96ae38bb6e625db82b5" + integrity sha512-fGKu7xSHok5FcKK5lEHtintHiEpcsUm1NE35ZZHRW7b5ejqBedq8/Be4Fo5WocEWZz9MWb1yyG2YqctceBYofw== + dependencies: + "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/humanize-distance" "^1.2.0" + "@opentripplanner/icons" "^1.2.2" + "@opentripplanner/location-icon" "^1.4.0" + "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" + flat "^5.0.2" + moment "^2.24.0" + react-resize-detector "^4.2.1" + velocity-react "^1.4.3" + +"@opentripplanner/itinerary-body@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-4.0.0.tgz#ddddb9279c62d38613fbfc6c8e51a4a25975479b" + integrity sha512-rGv8ZRiQxQ9lzXJsGZd67DMl7pc2byqJ7wUUYgSjUZ53uTvcXmdg16kHRLxEOdW09fjpMQ1Dv3ZYHrgvIhwhpw== dependencies: "@opentripplanner/core-utils" "^5.0.0" "@opentripplanner/humanize-distance" "^1.2.0" @@ -2564,14 +2580,15 @@ lodash.clonedeep "^4.5.0" throttle-debounce "^2.1.0" -"@opentripplanner/transitive-overlay@^1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.1.4.tgz#569d00b07bd32d78af178a2b64bbc5d211e01c2a" - integrity sha512-2kCgLg447H3l27qpX0ZquubqdkQQNWQwuuscLY135KwCkQOmqjwznw0yGN9E9EdA+1LOmMiZA//gnc6GkB3yUg== +"@opentripplanner/transitive-overlay@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-2.0.0.tgz#b4ba2dd8a3566232625671d0a216bdbf65553623" + integrity sha512-CXR0vbWdj5Ccie9ugDAG5t7ivomNPRkALq6ITGqlcOy8IybUxDxXfSEB9uaUVvfisYi+oBK36dx2Fe+CL6r6iw== dependencies: - "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/core-utils" "^5.0.1" + "@opentripplanner/itinerary-body" "^3.1.1" lodash.isequal "^4.5.0" - transitive-js "^0.13.7" + transitive-js "^0.14.1" "@opentripplanner/trip-details@^2.0.0": version "2.0.0" @@ -19633,23 +19650,6 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" -transitive-js@^0.13.7: - version "0.13.8" - resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.13.8.tgz#da06b459c53f8248ef861d913bb847a2c7e92071" - integrity sha512-cLDsOHfuNikFUPMZeCrVbJJCfmJCItlZnyAWsknr5TAnsZrnAP5hZqabyVFf1UAhBhNlcXktT3Y4kihaqcT/1Q== - dependencies: - augment "4.3.0" - component-each "0.2.6" - component-emitter "1.2.1" - d3 "^3.5.8" - debug "^2.5.1" - lodash.foreach "^4.5.0" - measure-text "^0.0.4" - priorityqueuejs "1.0.0" - rounded-rect "^0.0.1" - sphericalmercator "^1.0.5" - svg.js "^2.6.5" - transitive-js@^0.14.1: version "0.14.1" resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.14.1.tgz#0826ad3cc3b5cc950266ca8031e264974eae9124" From 6b45fa0b6466e1668deff5eea7d360faacddbc0f Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 6 Jul 2022 12:28:17 -0700 Subject: [PATCH 0318/1425] refactor(transitive-overlay): fix rendering bug --- .../map/connected-transitive-overlay.tsx | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 14cc2be4a..08192dac2 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -9,16 +9,18 @@ import TransitiveCanvasOverlay, { import { getTransitiveData } from '../../util/state' +type ItineraryData = { + companies?: Company[] + disableFlexArc: boolean + getTransitiveRouteLabel: (leg: Leg) => string + hasItineraryResponse: boolean + hasResponse: boolean + itineraryToRender: Itinerary + otpResponse: unknown +} + type Props = { - itineraryData: { - companies: Company[] - disableFlexArc: boolean - getTransitiveRouteLabel: (leg: Leg) => string - hasItineraryResponse: boolean - hasResponse: boolean - itineraryToRender: Itinerary - otpResponse: unknown - } + itineraryData: ItineraryData labeledModes: string[] styles: { labels: Record @@ -28,6 +30,8 @@ type Props = { const ConnectedTransitiveOverlay = (props: Props) => { const { itineraryData, labeledModes, styles } = props + const intl = useIntl() + if (!itineraryData) return null const { companies, disableFlexArc, @@ -38,8 +42,6 @@ const ConnectedTransitiveOverlay = (props: Props) => { otpResponse } = itineraryData - const intl = useIntl() - let transitiveData if (hasResponse) { if (hasItineraryResponse) { @@ -78,19 +80,11 @@ const mapStateToProps = (state: Record, ownProps: Props) => { return {} } - const transitiveData: { - companies: Company[] - disableFlexArc: boolean - getTransitiveRouteLabel: (leg: Leg) => string - hasItineraryResponse: boolean - hasResponse: boolean - itineraryToRender: Itinerary - otpResponse: unknown - // @ts-expect-error state.js is not typescripted - } = getTransitiveData(state, ownProps) + // @ts-expect-error state.js is not typescripted + const itineraryData: ItineraryData = getTransitiveData(state, ownProps) const obj = { - itineraryData: transitiveData, + itineraryData, labeledModes, styles } // generate implicit type From ad82e3effc9b95970ddeec09c2edd204f4736ff1 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 15:50:00 -0400 Subject: [PATCH 0319/1425] style(call-taker action/redux): Apply lint/prettier rules. --- lib/actions/call-taker.js | 253 +++++++++++++++++++------------------ lib/reducers/call-taker.js | 41 +++--- 2 files changed, 157 insertions(+), 137 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index b98cbc57d..d8e8bd23f 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -1,17 +1,17 @@ +import { createAction } from 'redux-actions' import { getUrlParams } from '@opentripplanner/core-utils/lib/query' import { serialize } from 'object-to-formdata' import qs from 'qs' -import { createAction } from 'redux-actions' -import {searchToQuery, sessionIsInvalid} from '../util/call-taker' -import {isModuleEnabled, Modules} from '../util/config' -import {URL_ROOT} from '../util/constants' -import {getTimestamp} from '../util/state' +import { getTimestamp } from '../util/state' +import { isModuleEnabled, Modules } from '../util/config' +import { searchToQuery, sessionIsInvalid } from '../util/call-taker' +import { URL_ROOT } from '../util/constants' -import {resetForm} from './form' -import {toggleFieldTrips} from './field-trip' +import { resetForm } from './form' +import { toggleFieldTrips } from './field-trip' -if (typeof (fetch) === 'undefined') require('isomorphic-fetch') +if (typeof fetch === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -31,7 +31,7 @@ export const toggleMailables = createAction('TOGGLE_MAILABLES') /** * Fully reset form and toggle call history (and close field trips if open). */ -export function resetAndToggleCallHistory () { +export function resetAndToggleCallHistory() { return function (dispatch, getState) { dispatch(resetForm(true)) dispatch(toggleCallHistory()) @@ -45,10 +45,10 @@ export function resetAndToggleCallHistory () { * - a call is not in progress, and * - the field trip window is not visible. */ -export function beginCallIfNeeded () { +export function beginCallIfNeeded() { return function (dispatch, getState) { const state = getState() - const {activeCall, fieldTrip} = state.callTaker + const { activeCall, fieldTrip } = state.callTaker const callTakerEnabled = isModuleEnabled(state, Modules.CALL_TAKER) if (callTakerEnabled && !activeCall && !fieldTrip.visible) { dispatch(beginCall()) @@ -57,44 +57,44 @@ export function beginCallIfNeeded () { } /** - * End the active call and store the queries made during the call. + * Handle initializing a new Trinet session by redirecting to Trinet auth and + * returning once authenticated successfully. */ -export function endCall (intl) { - return async function (dispatch, getState) { - const {callTaker, otp} = getState() - const {activeCall, session} = callTaker - const { sessionId } = session - if (sessionIsInvalid(session)) return - // Make POST request to store new call. - const callData = serialize({ - call: { - endTime: getTimestamp(), - startTime: activeCall.startTime - }, - sessionId +function newSession(datastoreUrl, verifyLoginUrl, redirect) { + fetch(datastoreUrl + '/auth/newSession') + .then((res) => res.json()) + .then((data) => { + const { sessionId: session } = data + const windowUrl = `${verifyLoginUrl}?${qs.stringify({ + redirect, + session + })}` + // Redirect to login url. + window.location = windowUrl }) - try { - const fetchResult = await fetch(`${otp.config.datastoreUrl}/calltaker/call`, - {body: callData, method: 'POST'} - ) - const id = await fetchResult.json() + .catch((error) => { + console.error('newSession error', error) + }) +} - // Inject call ID into active call and save queries. - await dispatch(saveQueriesForCall({...activeCall, id}, intl)) - // Wait until query was saved before re-fetching queries for this call. - await dispatch(fetchCalls(intl)) - } catch (err) { - console.error(err) - alert( - intl.formatMessage( - { id: 'actions.callTaker.callSaveError' }, - { err: JSON.stringify(err) } +/** + * Check that a particular session ID is valid and store resulting session + * data. + */ +function checkSession(datastoreUrl, sessionId, intl) { + return function (dispatch, getState) { + fetch(datastoreUrl + `/auth/checkSession?sessionId=${sessionId}`) + .then((res) => res.json()) + .then((session) => dispatch(storeSession({ session }))) + .catch((err) => { + dispatch(storeSession({ session: null })) + alert( + intl.formatMessage( + { id: 'actions.callTaker.checkSessionError' }, + { err: JSON.stringify(err) } + ) ) - ) - } - // Clear itineraries shown when ending call. - dispatch(resetForm(true)) - dispatch(endingCall()) + }) } } @@ -103,9 +103,9 @@ export function endCall (intl) { * query param against sessions in the datastore backend or initializing a new * session via Trinet. */ -export function initializeModules (intl) { +export function initializeModules(intl) { return function (dispatch, getState) { - const {datastoreUrl, trinetReDirect} = getState().otp.config + const { datastoreUrl, trinetReDirect } = getState().otp.config // Initialize session if datastore enabled. if (datastoreUrl && trinetReDirect) { // TODO: Generalize for non-TriNet instances. @@ -121,60 +121,23 @@ export function initializeModules (intl) { } } -/** - * Handle initializing a new Trinet session by redirecting to Trinet auth and - * returning once authenticated successfully. - */ -function newSession (datastoreUrl, verifyLoginUrl, redirect) { - fetch(datastoreUrl + '/auth/newSession') - .then(res => res.json()) - .then(data => { - const {sessionId: session} = data - const windowUrl = `${verifyLoginUrl}?${qs.stringify({redirect, session})}` - // Redirect to login url. - window.location = windowUrl - }) - .catch(error => { - console.error('newSession error', error) - }) -} - -/** - * Check that a particular session ID is valid and store resulting session - * data. - */ -function checkSession (datastoreUrl, sessionId, intl) { - return function (dispatch, getState) { - fetch(datastoreUrl + `/auth/checkSession?sessionId=${sessionId}`) - .then(res => res.json()) - .then(session => dispatch(storeSession({session}))) - .catch(err => { - dispatch(storeSession({session: null})) - alert( - intl.formatMessage( - { id: 'actions.callTaker.checkSessionError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } -} - /** * Fetch latest calls for a particular session. */ -export function fetchCalls (intl) { +export function fetchCalls(intl) { return async function (dispatch, getState) { dispatch(requestingCalls()) - const {callTaker, otp} = getState() + const { callTaker, otp } = getState() if (sessionIsInvalid(callTaker.session)) return - const {datastoreUrl} = otp.config - const {sessionId} = callTaker.session + const { datastoreUrl } = otp.config + const { sessionId } = callTaker.session const limit = 1000 try { - const fetchResult = await fetch(`${datastoreUrl}/calltaker/call?${qs.stringify({limit, sessionId})}`) + const fetchResult = await fetch( + `${datastoreUrl}/calltaker/call?${qs.stringify({ limit, sessionId })}` + ) const calls = await fetchResult.json() - dispatch(receivedCalls({calls})) + dispatch(receivedCalls({ calls })) } catch (err) { alert( intl.formatMessage( @@ -190,10 +153,10 @@ export function fetchCalls (intl) { * Store the trip queries made over the course of a call (to be called when the * call terminates). */ -function saveQueriesForCall (call, intl) { +function saveQueriesForCall(call, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return if (!call) { alert( @@ -201,43 +164,91 @@ function saveQueriesForCall (call, intl) { ) return } - return Promise.all(call.searches.map(searchId => { - const search = otp.searches[searchId] - const query = searchToQuery(search, call, otp.config) - const {sessionId} = callTaker.session - const queryData = serialize({ query, sessionId }) - return fetch(`${datastoreUrl}/calltaker/callQuery?sessionId=${sessionId}`, - { body: queryData, method: 'POST' } - ) - .then(res => res.json()) - .catch(err => { - alert( - intl.formatMessage( - { id: 'actions.callTaker.callQuerySaveError' }, - { err: JSON.stringify(err) } + return Promise.all( + call.searches.map((searchId) => { + const search = otp.searches[searchId] + const query = searchToQuery(search, call, otp.config) + const { sessionId } = callTaker.session + const queryData = serialize({ query, sessionId }) + return fetch( + `${datastoreUrl}/calltaker/callQuery?sessionId=${sessionId}`, + { body: queryData, method: 'POST' } + ) + .then((res) => res.json()) + .catch((err) => { + alert( + intl.formatMessage( + { id: 'actions.callTaker.callQuerySaveError' }, + { err: JSON.stringify(err) } + ) ) - ) - }) - })) + }) + }) + ) + } +} + +/** + * End the active call and store the queries made during the call. + */ +export function endCall(intl) { + return async function (dispatch, getState) { + const { callTaker, otp } = getState() + const { activeCall, session } = callTaker + const { sessionId } = session + if (sessionIsInvalid(session)) return + // Make POST request to store new call. + const callData = serialize({ + call: { + endTime: getTimestamp(), + startTime: activeCall.startTime + }, + sessionId + }) + try { + const fetchResult = await fetch( + `${otp.config.datastoreUrl}/calltaker/call`, + { body: callData, method: 'POST' } + ) + const id = await fetchResult.json() + + // Inject call ID into active call and save queries. + await dispatch(saveQueriesForCall({ ...activeCall, id }, intl)) + // Wait until query was saved before re-fetching queries for this call. + await dispatch(fetchCalls(intl)) + } catch (err) { + console.error(err) + alert( + intl.formatMessage( + { id: 'actions.callTaker.callSaveError' }, + { err: JSON.stringify(err) } + ) + ) + } + // Clear itineraries shown when ending call. + dispatch(resetForm(true)) + dispatch(endingCall()) } } /** * Fetch the trip queries that were made during a particular call. */ -export function fetchQueries (callId, intl) { +export function fetchQueries(callId, intl) { return function (dispatch, getState) { dispatch(requestingQueries()) - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - fetch(`${datastoreUrl}/calltaker/callQuery?sessionId=${sessionId}&call.id=${callId}`) - .then(res => res.json()) - .then(queries => { - dispatch(receivedQueries({callId, queries})) + const { sessionId } = callTaker.session + fetch( + `${datastoreUrl}/calltaker/callQuery?sessionId=${sessionId}&call.id=${callId}` + ) + .then((res) => res.json()) + .then((queries) => { + dispatch(receivedQueries({ callId, queries })) }) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.callTaker.queryFetchError' }, diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index ca44f952e..fa9dd03a9 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -1,12 +1,15 @@ -import update from 'immutability-helper' import moment from 'moment' +import update from 'immutability-helper' -import {constructNewCall} from '../util/call-taker' -import {FETCH_STATUS} from '../util/constants' -import {getModuleConfig, Modules} from '../util/config' +import { constructNewCall } from '../util/call-taker' +import { FETCH_STATUS } from '../util/constants' +import { getModuleConfig, Modules } from '../util/config' -function createCallTakerReducer (config) { - const calltakerConfig = getModuleConfig({otp: {config}}, Modules.CALL_TAKER) +function createCallTakerReducer(config) { + const calltakerConfig = getModuleConfig( + { otp: { config } }, + Modules.CALL_TAKER + ) const initialState = { activeCall: null, callHistory: { @@ -94,8 +97,10 @@ function createCallTakerReducer (config) { }) } case 'RECEIVED_FIELD_TRIP_DETAILS': { - const {fieldTrip} = action.payload - const index = state.fieldTrip.requests.data.findIndex(req => req.id === fieldTrip.id) + const { fieldTrip } = action.payload + const index = state.fieldTrip.requests.data.findIndex( + (req) => req.id === fieldTrip.id + ) return update(state, { fieldTrip: { requests: { data: { [index]: { $set: fieldTrip } } } } }) @@ -119,29 +124,33 @@ function createCallTakerReducer (config) { case 'SET_SAVEABLE': { // The payload represents whether the saveable set of itineraries are for // the inbound or outbound journey. - const saveableUpdate = { [action.payload ? 'outbound' : 'inbound']: true } + const saveableUpdate = { + [action.payload ? 'outbound' : 'inbound']: true + } return update(state, { fieldTrip: { saveable: { $set: saveableUpdate } } }) } case 'RECEIVED_QUERIES': { - const {callId, queries} = action.payload - const {data} = state.callHistory.calls - const index = data.findIndex(call => call.id === callId) - const call = {...data[index], queries} + const { callId, queries } = action.payload + const { data } = state.callHistory.calls + const index = data.findIndex((call) => call.id === callId) + const call = { ...data[index], queries } return update(state, { callHistory: { calls: { data: { [index]: { $set: call } } } } }) } case 'ROUTING_RESPONSE': { - const {searchId} = action.payload + const { searchId } = action.payload if (state.activeCall) { // If call is in progress, record search ID when a routing response is // fulfilled, except in the case where the // searchId contains _CALL and call history window is visible, which indicates that a user is viewing a // past call record // TODO: How should we handle routing errors. - if (!(state.callHistory.visible && searchId.indexOf('_CALL') !== -1)) { + if ( + !(state.callHistory.visible && searchId.indexOf('_CALL') !== -1) + ) { return update(state, { activeCall: { searches: { $push: [searchId] } } }) @@ -151,7 +160,7 @@ function createCallTakerReducer (config) { return state } case 'STORE_SESSION': { - const {session} = action.payload + const { session } = action.payload if (!session || !session.username) { const sessionId = session ? session.sessionId : 'N/A' // Session is invalid if username is missing. From 65cb0826fc71cce3e6be1de65228edfa5e402ddb Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:16:34 -0400 Subject: [PATCH 0320/1425] refactor(util/state): Replace moment dependency with date-fns-tz. --- lib/actions/call-taker.js | 4 ++-- lib/reducers/call-taker.js | 9 +++++++-- lib/reducers/create-otp-reducer.js | 9 +++++---- lib/util/call-taker.js | 11 ----------- lib/util/state.js | 11 +++++------ 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index d8e8bd23f..264d4bb36 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -3,7 +3,7 @@ import { getUrlParams } from '@opentripplanner/core-utils/lib/query' import { serialize } from 'object-to-formdata' import qs from 'qs' -import { getTimestamp } from '../util/state' +import { getISOLikeTimestamp } from '../util/state' import { isModuleEnabled, Modules } from '../util/config' import { searchToQuery, sessionIsInvalid } from '../util/call-taker' import { URL_ROOT } from '../util/constants' @@ -200,7 +200,7 @@ export function endCall(intl) { // Make POST request to store new call. const callData = serialize({ call: { - endTime: getTimestamp(), + endTime: getISOLikeTimestamp(otp.config.homeTimezone), startTime: activeCall.startTime }, sessionId diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index fa9dd03a9..f8a96262c 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -1,8 +1,9 @@ +import { randId } from '@opentripplanner/core-utils/lib/storage' import moment from 'moment' import update from 'immutability-helper' -import { constructNewCall } from '../util/call-taker' import { FETCH_STATUS } from '../util/constants' +import { getISOLikeTimestamp } from '../util/state' import { getModuleConfig, Modules } from '../util/config' function createCallTakerReducer(config) { @@ -41,7 +42,11 @@ function createCallTakerReducer(config) { return (state = initialState, action) => { switch (action.type) { case 'BEGIN_CALL': { - const newCall = constructNewCall() + const newCall = { + id: randId(), + searches: [], + startTime: getISOLikeTimestamp(config.homeTimezone) + } // Initialize new call and show call history window. return update(state, { activeCall: { $set: newCall }, diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 40a0e4c56..a72ef2cd6 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -7,7 +7,7 @@ import update from 'immutability-helper' import { combinationFilter } from '../components/form/batch-settings' import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE } from '../util/constants' import { getDefaultModes, isBatchRoutingEnabled } from '../util/itinerary' -import { getTimestamp } from '../util/state' +import { getISOLikeTimestamp } from '../util/state' import { MainPanelContent, MobileScreens } from '../actions/ui' const { getTransitModes, isTransit } = coreUtils.itinerary @@ -237,6 +237,7 @@ function createOtpReducer(config) { const searchId = action.payload && action.payload.searchId const requestId = action.payload && action.payload.requestId const activeSearch = state.searches[searchId] + const timestamp = getISOLikeTimestamp(config.homeTimezone) switch (action.type) { case 'ROUTING_REQUEST': const { activeItinerary, pending, updateSearchInReducer } = @@ -251,7 +252,7 @@ function createOtpReducer(config) { query: { $set: clone(state.currentQuery) }, // omit requests reset to make sure requests can be added to this // search - timestamp: { $set: getTimestamp() } + timestamp: { $set: timestamp } } : { $set: { @@ -262,7 +263,7 @@ function createOtpReducer(config) { // FIXME: get query from action payload? query: clone(state.currentQuery), response: [], - timestamp: getTimestamp() + timestamp } } return update(state, { @@ -357,7 +358,7 @@ function createOtpReducer(config) { // FIXME: get query from action payload? query: clone(state.currentQuery), response: action.payload.response, - timestamp: getTimestamp() + timestamp } } }, diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index f82bf1358..e587df825 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,9 +1,6 @@ import { getRoutingParams } from '@opentripplanner/core-utils/lib/query' -import { randId } from '@opentripplanner/core-utils/lib/storage' import moment from 'moment' -import { getTimestamp } from './state' - export const TICKET_TYPES = { hop_new: 'Will purchase new Hop Card', hop_reload: 'Will reload existing Hop Card', @@ -108,14 +105,6 @@ const SEARCH_FIELDS = [ 'teacherName' ] -export function constructNewCall() { - return { - id: randId(), - searches: [], - startTime: getTimestamp() - } -} - function placeToLatLonStr(place) { return `${place.lat.toFixed(6)},${place.lon.toFixed(6)}` } diff --git a/lib/util/state.js b/lib/util/state.js index 9b4a149b5..46e7121d2 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -1,13 +1,13 @@ // REMOVE THIS LINE BEFORE EDITING THIS FILE /* eslint-disable */ import { createSelector } from 'reselect' +import { format } from 'date-fns-tz' import { FormattedList, FormattedMessage } from 'react-intl' import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' import coreUtils from '@opentripplanner/core-utils' import hash from 'object-hash' import isEqual from 'lodash.isequal' import memoize from 'lodash.memoize' -import moment from 'moment' import qs from 'qs' import React from 'react' import styled from 'styled-components' @@ -32,12 +32,11 @@ export function getActiveSearch(state) { } /** - * Get timestamp in the expected format used by otp-datastore (call taker - * back end). Defaults to now. - * TODO: move to OTP-UI? + * Get timestamp in the ISO-like format used by otp-datastore (call taker back end), + * in the form 2021-07-10T13:46:12, expressed in the specified time zone. Defaults to now. */ -export function getTimestamp(time = moment()) { - return time.format('YYYY-MM-DDTHH:mm:ss') +export function getISOLikeTimestamp(timeZone, date = Date.now()) { + return format(date, "yyyy-MM-dd'T'HH:mm:ss", { timeZone }) } /** From 39a5f1d7fe2dacd77045d873351f9cd55f4c8779 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:19:13 -0400 Subject: [PATCH 0321/1425] style(util/mailables): Apply lint/prettier rules. --- lib/util/mailables.js | 152 +++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 77 deletions(-) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 8bb53ee1f..95913b77f 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -9,56 +9,60 @@ import PDFDocument from './PDFDocumentWithTables' */ export const LETTER_FIELDS = [ [ - {fieldName: 'firstname', placeholder: 'First name'}, - {fieldName: 'lastname', placeholder: 'Last name'} + { fieldName: 'firstname', placeholder: 'First name' }, + { fieldName: 'lastname', placeholder: 'Last name' } ], [ - {fieldName: 'address1', placeholder: 'Address 1'}, - {fieldName: 'address2', placeholder: 'Address 2'} + { fieldName: 'address1', placeholder: 'Address 1' }, + { fieldName: 'address2', placeholder: 'Address 2' } ], [ - {fieldName: 'city', placeholder: 'City'}, - {fieldName: 'state', placeholder: 'State'}, - {fieldName: 'zip', placeholder: 'Zip'} + { fieldName: 'city', placeholder: 'City' }, + { fieldName: 'state', placeholder: 'State' }, + { fieldName: 'zip', placeholder: 'Zip' } ] ] /** - * Generate a PDF letter for the provided form data/config. - * This depends on the headerGraphic config field being provided and a valid URL - * to a .png image file. + * Handle preparing a new page for the PDF doc, adding a footer, header image, + * and resetting the currsor to the correct position. */ -export function createLetter (formData, mailablesConfig) { - const imageUrl = mailablesConfig.headerGraphic - // A valid URL must be provided in the config in order for the create letter - // approach to function properly. - if (!imageUrl) { - return alert(`Error constructing letter. Mailables headerGraphic config property is missing.`) +function preparePage(doc, imageData, otpConfig) { + const { footer, headerGraphic, headerGraphicHeight, headerGraphicWidth } = + otpConfig + // Store true bottom of page while bottom is temporarily moved to 0. + const bottom = doc.page.margins.bottom + doc.page.margins.bottom = 0 + // Add footer to page. + if (footer) { + doc + .fontSize(9) + .text(footer, 0.5 * (doc.page.width - 500), doc.page.height - 50, { + align: 'center', + lineBreak: false, + width: 500 + }) } - // This is a very goofy approach to convert an image URL to its image data for - // writing to the PDF, but it seems to be a solid approach. - const img = new Image() - img.crossOrigin = 'anonymous' - img.onload = () => { - var canvas = document.createElement('canvas') - canvas.width = img.width - canvas.height = img.height - - const ctx = canvas.getContext('2d') - ctx.drawImage(img, 0, 0) - - const data = canvas.toDataURL('image/png') - const imageData = {data, height: img.height, width: img.width} - writePDF(formData, imageData, mailablesConfig) + // Add header image. + if (headerGraphic) { + const width = headerGraphicWidth || (72 * imageData.width) / 300 + const height = headerGraphicHeight || (72 * imageData.height) / 300 + doc.image(imageData.data, 0.5 * (doc.page.width - 300), 40, { + align: 'center', + height, + width + }) } - img.src = imageUrl + // Reset font size, bottom margin, and text writer position. + doc.fontSize(12).text('', doc.page.margins.left, doc.page.margins.top) + doc.page.margins.bottom = bottom } /** * Write the provided formData and imageData (header image) to a PDF file and * open as a new tab. */ -function writePDF (formData, imageData, otpConfig) { +function writePDF(formData, imageData, otpConfig) { const { address1 = '', address2, @@ -69,14 +73,14 @@ function writePDF (formData, imageData, otpConfig) { state = '', zip = '' } = formData - const {horizontalMargin, verticalMargin} = otpConfig + const { horizontalMargin, verticalMargin } = otpConfig const margins = { bottom: verticalMargin, left: horizontalMargin, right: horizontalMargin, top: verticalMargin } - const doc = new PDFDocument({margins}) + const doc = new PDFDocument({ margins }) const stream = doc.pipe(blobStream()) preparePage(doc, imageData, otpConfig) doc.on('pageAdded', () => preparePage(doc, imageData, otpConfig)) @@ -85,7 +89,8 @@ function writePDF (formData, imageData, otpConfig) { doc.text(moment().format('MMMM D, YYYY')) // recipient address - doc.moveDown() + doc + .moveDown() .moveDown() .moveDown() .text(firstname.toUpperCase() + ' ' + lastname.toUpperCase()) @@ -96,22 +101,21 @@ function writePDF (formData, imageData, otpConfig) { doc.text(city.toUpperCase() + ', ' + state.toUpperCase() + ' ' + zip) // introduction block - doc.moveDown() - .moveDown() - .text(otpConfig.introduction) + doc.moveDown().moveDown().text(otpConfig.introduction) // table header - doc.font('Helvetica-Bold') + doc + .font('Helvetica-Bold') .moveDown() .moveDown() - .text('SUMMARY BY ITEM', {align: 'center'}) + .text('SUMMARY BY ITEM', { align: 'center' }) .moveDown() .moveDown() const tableData = { headers: ['Item', 'Quantity'], - rows: mailables.map(mailable => { - let {largeFormat, largePrint, name, quantity} = mailable + rows: mailables.map((mailable) => { + let { largeFormat, largePrint, name, quantity } = mailable if (largePrint && largeFormat) { name += ' (LARGE PRINT)' } @@ -125,10 +129,7 @@ function writePDF (formData, imageData, otpConfig) { }) // conclusion block - doc.moveDown() - .moveDown() - .font('Helvetica') - .text(otpConfig.conclusion) + doc.moveDown().moveDown().font('Helvetica').text(otpConfig.conclusion) doc.end() stream.on('finish', () => { @@ -138,37 +139,34 @@ function writePDF (formData, imageData, otpConfig) { } /** - * Handle preparing a new page for the PDF doc, adding a footer, header image, - * and resetting the currsor to the correct position. + * Generate a PDF letter for the provided form data/config. + * This depends on the headerGraphic config field being provided and a valid URL + * to a .png image file. */ -function preparePage (doc, imageData, otpConfig) { - const {footer, headerGraphic, headerGraphicHeight, headerGraphicWidth} = otpConfig - // Store true bottom of page while bottom is temporarily moved to 0. - const bottom = doc.page.margins.bottom - doc.page.margins.bottom = 0 - // Add footer to page. - if (footer) { - doc.fontSize(9) - .text( - footer, - 0.5 * (doc.page.width - 500), - doc.page.height - 50, - {align: 'center', lineBreak: false, width: 500} - ) - } - // Add header image. - if (headerGraphic) { - const width = headerGraphicWidth || 72 * imageData.width / 300 - const height = headerGraphicHeight || 72 * imageData.height / 300 - doc.image( - imageData.data, - 0.5 * (doc.page.width - 300), - 40, - {align: 'center', height, width} +export function createLetter(formData, mailablesConfig) { + const imageUrl = mailablesConfig.headerGraphic + // A valid URL must be provided in the config in order for the create letter + // approach to function properly. + if (!imageUrl) { + return alert( + 'Error constructing letter. Mailables headerGraphic config property is missing.' ) } - // Reset font size, bottom margin, and text writer position. - doc.fontSize(12) - .text('', doc.page.margins.left, doc.page.margins.top) - doc.page.margins.bottom = bottom + // This is a very goofy approach to convert an image URL to its image data for + // writing to the PDF, but it seems to be a solid approach. + const img = new Image() + img.crossOrigin = 'anonymous' + img.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = img.width + canvas.height = img.height + + const ctx = canvas.getContext('2d') + ctx.drawImage(img, 0, 0) + + const data = canvas.toDataURL('image/png') + const imageData = { data, height: img.height, width: img.width } + writePDF(formData, imageData, mailablesConfig) + } + img.src = imageUrl } From cccd3439eb0cf20b9809ffdc98ce99a0cc693862 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:29:12 -0400 Subject: [PATCH 0322/1425] refactor(mailables): Replace momentjs dependency with date-fns. --- lib/util/mailables.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 95913b77f..6610ee7ea 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -1,5 +1,5 @@ +import { format } from 'date-fns' import blobStream from 'blob-stream' -import moment from 'moment' import PDFDocument from './PDFDocumentWithTables' @@ -85,8 +85,8 @@ function writePDF(formData, imageData, otpConfig) { preparePage(doc, imageData, otpConfig) doc.on('pageAdded', () => preparePage(doc, imageData, otpConfig)) - // current date - doc.text(moment().format('MMMM D, YYYY')) + // Current date in the user's time zone (no need to use the config's homeTimezone for this purpose) + doc.text(format(Date.now(), 'MMMM d, yyyy')) // recipient address doc From e7885015f02366e05cb298290d9694a09bc4b236 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:35:43 -0400 Subject: [PATCH 0323/1425] refactor(CallTimeCounter): Remove reference to momentjs. Convert to TypeScript. --- ...-time-counter.js => call-time-counter.tsx} | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) rename lib/components/admin/{call-time-counter.js => call-time-counter.tsx} (77%) diff --git a/lib/components/admin/call-time-counter.js b/lib/components/admin/call-time-counter.tsx similarity index 77% rename from lib/components/admin/call-time-counter.js rename to lib/components/admin/call-time-counter.tsx index 1383687a4..f42b16e5c 100644 --- a/lib/components/admin/call-time-counter.js +++ b/lib/components/admin/call-time-counter.tsx @@ -1,20 +1,23 @@ -// import moment from 'moment' import React, { Component } from 'react' +interface State { + counterString: number +} + /** * Component that displays the call time (ticking with each second) * for an active call (assumes that mount time corresponds with call start). */ -export default class CallTimeCounter extends Component { +export default class CallTimeCounter extends Component { state = { counterString: 0 } - componentDidMount () { + componentDidMount() { this._startRefresh() } - componentWillUnmount () { + componentWillUnmount() { this._stopRefresh() } @@ -29,7 +32,7 @@ export default class CallTimeCounter extends Component { _refreshCounter = () => { const counterString = this.state.counterString + 1 - this.setState({counterString}) + this.setState({ counterString }) } _startRefresh = () => { @@ -42,14 +45,14 @@ export default class CallTimeCounter extends Component { window.clearInterval(this.state.timer) } - render () { - const {className} = this.props + render() { + const { className } = this.props return (
    {this._formatSeconds(this.state.counterString)}
    From 3a22ead3bb89adb469558769a37549e9fba99414 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:15:09 -0400 Subject: [PATCH 0324/1425] refactor(reducers/call-taker): Refactor end time sorting function. --- lib/reducers/call-taker.js | 6 +++--- lib/util/call-taker.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index f8a96262c..8cf4010d6 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -1,7 +1,7 @@ import { randId } from '@opentripplanner/core-utils/lib/storage' -import moment from 'moment' import update from 'immutability-helper' +import { compareEndTimes } from '../util/call-taker' import { FETCH_STATUS } from '../util/constants' import { getISOLikeTimestamp } from '../util/state' import { getModuleConfig, Modules } from '../util/config' @@ -61,7 +61,7 @@ function createCallTakerReducer(config) { case 'RECEIVED_CALLS': { const data = action.payload.calls const calls = { - data: data.sort((a, b) => moment(b.endTime) - moment(a.endTime)), + data: data.sort(compareEndTimes), status: FETCH_STATUS.FETCHED } return update(state, { @@ -76,7 +76,7 @@ function createCallTakerReducer(config) { case 'RECEIVED_FIELD_TRIPS': { const data = action.payload.fieldTrips const requests = { - data: data.sort((a, b) => moment(b.endTime) - moment(a.endTime)), + data: data.sort(compareEndTimes), status: FETCH_STATUS.FETCHED } return update(state, { diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index e587df825..94fd86284 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,3 +1,4 @@ +import { compareAsc, parse } from 'date-fns' import { getRoutingParams } from '@opentripplanner/core-utils/lib/query' import moment from 'moment' @@ -287,3 +288,19 @@ export function lzwDecode(s) { } return out.join('') } + +const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' + +/** + * Compares end times of two call/field trip entities. + * @param {*} a call or field trip entity to compare. + * @param {*} b call or field trip entity to compare. + * @returns 1 if the first end time is after the second, -1 if the first end time is before the second or 0 if end times are equal. + */ +export function compareEndTimes(a, b) { + const now = Date.now() + return compareAsc( + parse(b.endTime, ENTITY_DATE_TIME_FORMAT, now), + parse(a.endTime, ENTITY_DATE_TIME_FORMAT, now) + ) +} From 96132c438ae626340b4439a905b1b57c023c3767 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 7 Jul 2022 18:29:39 -0400 Subject: [PATCH 0325/1425] refactor(util/call-taker): Replace momentjs with date-fns. --- lib/util/call-taker.js | 46 +++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index 94fd86284..5747c3336 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,6 +1,21 @@ -import { compareAsc, parse } from 'date-fns' +import { compareAsc, differenceInCalendarDays, parse } from 'date-fns' import { getRoutingParams } from '@opentripplanner/core-utils/lib/query' -import moment from 'moment' +import { getUserTimezone } from '@opentripplanner/core-utils/lib/time' +import { toDate } from 'date-fns-tz' + +import { getISOLikeTimestamp } from './state' +const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' + +/** + * Only used in parseDate below. + */ +const referenceDateForDateFns = Date.now() +/** + * Date parsing helper, specific to calltaker/field trip entities. + */ +function parseDate(date) { + parse(date, ENTITY_DATE_TIME_FORMAT, referenceDateForDateFns) +} export const TICKET_TYPES = { hop_new: 'Will purchase new Hop Card', @@ -76,7 +91,8 @@ export const TABS = [ }, { filter: (req) => - req.travelDate && moment(req.travelDate).diff(moment(), 'days') < 0, + req.travelDate && + differenceInCalendarDays(parseDate(req.travelDate), Date.now()) < 0, id: 'past', label: 'Past' }, @@ -126,7 +142,7 @@ export function sessionIsInvalid(session) { * search terms. */ export function getVisibleRequests(state) { - const { callTaker } = state + const { callTaker, otp } = state const { fieldTrip } = callTaker const { filter } = fieldTrip const { search, tab } = filter @@ -149,9 +165,17 @@ export function getVisibleRequests(state) { // Potential date format detected in search term. const [month, day] = splitBySlash if (!isNaN(month) && !isNaN(day)) { - // If month and day seem to be numbers, check against request date. - const date = moment(request.travelDate) - return date.month() + 1 === +month && date.date() === +day + // If month and day seem to be numbers, check against request date + // in the the configured homeTimezone. + const dateInBrowserTz = parseDate(request.travelDate) + const isoDateWithoutTz = getISOLikeTimestamp( + getUserTimezone(), + dateInBrowserTz + ) + const date = toDate(isoDateWithoutTz, { + timeZone: otp.config.homeTimezone + }) + return date.getMonth() + 1 === +month && date.getDate() === +day } } return SEARCH_FIELDS.some((key) => { @@ -289,8 +313,6 @@ export function lzwDecode(s) { return out.join('') } -const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' - /** * Compares end times of two call/field trip entities. * @param {*} a call or field trip entity to compare. @@ -298,9 +320,5 @@ const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' * @returns 1 if the first end time is after the second, -1 if the first end time is before the second or 0 if end times are equal. */ export function compareEndTimes(a, b) { - const now = Date.now() - return compareAsc( - parse(b.endTime, ENTITY_DATE_TIME_FORMAT, now), - parse(a.endTime, ENTITY_DATE_TIME_FORMAT, now) - ) + return compareAsc(parseDate(b.endTime), parseDate(a.endTime)) } From 0f68abb5e83589e75e6504d4b117f20f37e77cd0 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 8 Jul 2022 01:00:58 -0700 Subject: [PATCH 0326/1425] refactor(transitive): intl injection --- .../map/connected-transitive-overlay.tsx | 67 ++----------------- lib/util/state.js | 22 ++++-- 2 files changed, 22 insertions(+), 67 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 08192dac2..a02975041 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -1,26 +1,13 @@ import { Company, Itinerary, Leg } from '@opentripplanner/types' import { connect } from 'react-redux' -import { useIntl } from 'react-intl' +import { injectIntl, IntlShape, useIntl } from 'react-intl' import React from 'react' -import TransitiveCanvasOverlay, { - itineraryToTransitive - // @ts-expect-error transitive-overlay is not typescripted -} from '@opentripplanner/transitive-overlay' +import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { getTransitiveData } from '../../util/state' -type ItineraryData = { - companies?: Company[] - disableFlexArc: boolean - getTransitiveRouteLabel: (leg: Leg) => string - hasItineraryResponse: boolean - hasResponse: boolean - itineraryToRender: Itinerary - otpResponse: unknown -} - type Props = { - itineraryData: ItineraryData + intl: IntlShape labeledModes: string[] styles: { labels: Record @@ -28,44 +15,6 @@ type Props = { } } -const ConnectedTransitiveOverlay = (props: Props) => { - const { itineraryData, labeledModes, styles } = props - const intl = useIntl() - if (!itineraryData) return null - const { - companies, - disableFlexArc, - getTransitiveRouteLabel, - hasItineraryResponse, - hasResponse, - itineraryToRender, - otpResponse - } = itineraryData - - let transitiveData - if (hasResponse) { - if (hasItineraryResponse) { - transitiveData = itineraryToRender - ? itineraryToTransitive(itineraryToRender, { - companies, - disableFlexArc, - getTransitiveRouteLabel, - intl - }) - : null - } else if (otpResponse) { - transitiveData = otpResponse - } - } - return ( - - ) -} - // connect to the redux store const mapStateToProps = (state: Record, ownProps: Props) => { const { labeledModes, styles } = state.otp.config.map.transitive || {} @@ -80,15 +29,13 @@ const mapStateToProps = (state: Record, ownProps: Props) => { return {} } - // @ts-expect-error state.js is not typescripted - const itineraryData: ItineraryData = getTransitiveData(state, ownProps) - const obj = { - itineraryData, labeledModes, - styles + styles, + // @ts-expect-error state.js is not typescripted + transitiveData: getTransitiveData(state, ownProps) } // generate implicit type return obj } -export default connect(mapStateToProps)(ConnectedTransitiveOverlay) +export default injectIntl(connect(mapStateToProps)(TransitiveCanvasOverlay)) diff --git a/lib/util/state.js b/lib/util/state.js index 26bce506d..ac1c01a33 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -3,6 +3,7 @@ import { createSelector } from 'reselect' import { FormattedList, FormattedMessage } from 'react-intl' import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' +import { itineraryToTransitive } from '@opentripplanner/transitive-overlay' import coreUtils from '@opentripplanner/core-utils' import hash from 'object-hash' import isEqual from 'lodash.isequal' @@ -644,6 +645,7 @@ export const getTransitiveData = createSelector( (state) => state.otp.config.companies, (state) => state.otp.config.map.transitive?.disableFlexArc, (state, props) => props.getTransitiveRouteLabel, + (state, props) => props.intl, ( hasResponse, otpResponse, @@ -651,16 +653,22 @@ export const getTransitiveData = createSelector( itineraryToRender, companies, disableFlexArc, - getTransitiveRouteLabel + getTransitiveRouteLabel, + intl ) => { - return { - hasResponse, - otpResponse, - hasItineraryResponse, - itineraryToRender, + if (hasResponse) { + if (hasItineraryResponse) { + return itineraryToRender + ? itineraryToTransitive(itineraryToRender, { companies, disableFlexArc, - getTransitiveRouteLabel + getTransitiveRouteLabel, + intl + }) + : null + } else if (otpResponse) { + return otpResponse + } } } ) From 554bc34ebe10c0e639688d1887a35ba6ac05ecf2 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 8 Jul 2022 01:09:55 -0700 Subject: [PATCH 0327/1425] refactor(state): remove eslint-disable --- lib/util/state.js | 313 +++++++++++++++++++++++----------------------- 1 file changed, 159 insertions(+), 154 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index ac1c01a33..15e03fb11 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -1,5 +1,4 @@ // REMOVE THIS LINE BEFORE EDITING THIS FILE -/* eslint-disable */ import { createSelector } from 'reselect' import { FormattedList, FormattedMessage } from 'react-intl' import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' @@ -273,157 +272,6 @@ const hashItinerary = memoize((itinerary) => hash(itinerary, { excludeKeys: (key) => blackListedKeyLookup[key] }) ) -/** - * Get the active itineraries for the active search, which is dependent on - * whether realtime or non-realtime results should be displayed - * @param {Object} state the redux state object - * @return {Array} array of itinerary objects from the OTP plan response, - * or null if there is no active search - */ -export const getActiveItineraries = createSelector( - (state) => state.otp.config, - getActiveSearchNonRealtimeResponse, - (state) => state.otp.filter, - getActiveSearchRealtimeResponse, - getActiveFieldTripRequest, - ( - config, - nonRealtimeResponse, - itinerarySortSettings, - realtimeResponse, - activeFieldTripRequest - ) => { - const response = realtimeResponse || nonRealtimeResponse - const itineraries = [] - // keep track of itinerary hashes in order to not include duplicate - // itineraries. Duplicate itineraries can occur in batch routing where a walk - // to transit trip can sometimes still be the most optimal trip even when - // additional modes such as bike rental were also requested - const seenItineraryHashes = {} - if (response) { - response.forEach((res) => { - if (res && res.plan) { - res.plan.itineraries.forEach((itinerary) => { - // hashing takes a while on itineraries - const itineraryHash = hashItinerary(itinerary) - if (!seenItineraryHashes[itineraryHash]) { - itineraries.push(itinerary) - seenItineraryHashes[itineraryHash] = true - } - }) - } - }) - } - const { sort } = itinerarySortSettings - const { direction, type } = sort - // If no sort type is provided (e.g., because batch routing is not enabled), - // do not sort itineraries (default sort from API response is used). - // Also, do not sort itineraries if a field trip request is active - return !type || Boolean(activeFieldTripRequest) - ? itineraries - : itineraries.sort((a, b) => - sortItineraries(type, direction, a, b, config) - ) - } -) - -/** - * Array sort function for itineraries (in batch routing context) that attempts - * to sort based on the type/direction specified. - */ -/* eslint-disable-next-line complexity */ -function sortItineraries(type, direction, a, b, config) { - switch (type) { - case 'WALKTIME': - if (direction === 'ASC') return a.walkTime - b.walkTime - else return b.walkTime - a.walkTime - case 'ARRIVALTIME': - if (direction === 'ASC') return a.endTime - b.endTime - else return b.endTime - a.endTime - case 'DEPARTURETIME': - if (direction === 'ASC') return a.startTime - b.startTime - else return b.startTime - a.startTime - case 'DURATION': - if (direction === 'ASC') return a.duration - b.duration - else return b.duration - a.duration - case 'COST': - const configCosts = config.itinerary?.costs - // Sort an itinerary without fare information last - const aTotal = - getTotalFare(a, configCosts) === null - ? Number.MAX_VALUE - : getTotalFare(a, configCosts) - const bTotal = - getTotalFare(b, configCosts) === null - ? Number.MAX_VALUE - : getTotalFare(b, configCosts) - if (direction === 'ASC') return aTotal - bTotal - else return bTotal - aTotal - default: - if (type !== 'BEST') - console.warn(`Sort (${type}) not supported. Defaulting to BEST.`) - // FIXME: Fully implement default sort algorithm. - const aCost = calculateItineraryCost(a, config) - const bCost = calculateItineraryCost(b, config) - if (direction === 'ASC') return aCost - bCost - else return bCost - aCost - } -} - -/** - * Default constants for calculating itinerary "cost", i.e., how preferential a - * particular itinerary is based on factors like wait time, total fare, drive - * time, etc. - */ -const DEFAULT_WEIGHTS = { - driveReluctance: 2, - durationFactor: 0.25, - fareFactor: 0.5, - transferReluctance: 0.9, - waitReluctance: 0.1, - walkReluctance: 0.1 -} - -/** - * This calculates the "cost" (not the monetary cost, but the cost according to - * multiple factors like duration, total fare, and walking distance) for a - * particular itinerary, for use in sorting itineraries. - * FIXME: Do major testing to get this right. - */ -function calculateItineraryCost(itinerary, config = {}) { - // Initialize weights to default values. - const weights = DEFAULT_WEIGHTS - // If config contains values to override defaults, apply those. - const configWeights = config.itinerary && config.itinerary.weights - if (configWeights) Object.assign(weights, configWeights) - return ( - getTotalFare( - itinerary, - config.itinerary?.costs, - config.itinerary?.defaultFareKey - ) * - weights.fareFactor + - itinerary.duration * weights.durationFactor + - itinerary.walkDistance * weights.walkReluctance + - getDriveTime(itinerary) * weights.driveReluctance + - itinerary.waitingTime * weights.waitReluctance + - itinerary.transfers * weights.transferReluctance - ) -} - -/** - * Get total drive time (i.e., total duration for legs with mode=CAR) for an - * itinerary. - */ -function getDriveTime(itinerary) { - if (!itinerary) return 0 - let driveTime = 0 - itinerary.legs.forEach((leg) => { - if (leg.mode === 'CAR') driveTime += leg.duration - }) - return driveTime -} - /** * Default costs for modes that currently have no costs evaluated in * OpenTripPlanner. @@ -498,6 +346,162 @@ export function getTotalFare( return bikeshareCost + drivingCost + transitFare + maxTNCFare * 100 } +/** + * Get total drive time (i.e., total duration for legs with mode=CAR) for an + * itinerary. + */ +function getDriveTime(itinerary) { + if (!itinerary) return 0 + let driveTime = 0 + itinerary.legs.forEach((leg) => { + if (leg.mode === 'CAR') driveTime += leg.duration + }) + return driveTime +} + +/** + * Default constants for calculating itinerary "cost", i.e., how preferential a + * particular itinerary is based on factors like wait time, total fare, drive + * time, etc. + */ +const DEFAULT_WEIGHTS = { + driveReluctance: 2, + durationFactor: 0.25, + fareFactor: 0.5, + transferReluctance: 0.9, + waitReluctance: 0.1, + walkReluctance: 0.1 +} + +/** + * This calculates the "cost" (not the monetary cost, but the cost according to + * multiple factors like duration, total fare, and walking distance) for a + * particular itinerary, for use in sorting itineraries. + * FIXME: Do major testing to get this right. + */ +function calculateItineraryCost(itinerary, config = {}) { + // Initialize weights to default values. + const weights = DEFAULT_WEIGHTS + // If config contains values to override defaults, apply those. + const configWeights = config.itinerary && config.itinerary.weights + if (configWeights) Object.assign(weights, configWeights) + return ( + getTotalFare( + itinerary, + config.itinerary?.costs, + config.itinerary?.defaultFareKey + ) * + weights.fareFactor + + itinerary.duration * weights.durationFactor + + itinerary.walkDistance * weights.walkReluctance + + getDriveTime(itinerary) * weights.driveReluctance + + itinerary.waitingTime * weights.waitReluctance + + itinerary.transfers * weights.transferReluctance + ) +} + +/** + * Array sort function for itineraries (in batch routing context) that attempts + * to sort based on the type/direction specified. + */ +/* eslint-disable-next-line complexity */ +function sortItineraries(type, direction, a, b, config) { + switch (type) { + case 'WALKTIME': + if (direction === 'ASC') return a.walkTime - b.walkTime + else return b.walkTime - a.walkTime + case 'ARRIVALTIME': + if (direction === 'ASC') return a.endTime - b.endTime + else return b.endTime - a.endTime + case 'DEPARTURETIME': + if (direction === 'ASC') return a.startTime - b.startTime + else return b.startTime - a.startTime + case 'DURATION': + if (direction === 'ASC') return a.duration - b.duration + else return b.duration - a.duration + case 'COST': + // eslint-disable-next-line no-case-declarations + const configCosts = config.itinerary?.costs + // Sort an itinerary without fare information last + // eslint-disable-next-line no-case-declarations + const aTotal = + getTotalFare(a, configCosts) === null + ? Number.MAX_VALUE + : getTotalFare(a, configCosts) + // eslint-disable-next-line no-case-declarations + const bTotal = + getTotalFare(b, configCosts) === null + ? Number.MAX_VALUE + : getTotalFare(b, configCosts) + if (direction === 'ASC') return aTotal - bTotal + else return bTotal - aTotal + default: + if (type !== 'BEST') + console.warn(`Sort (${type}) not supported. Defaulting to BEST.`) + // FIXME: Fully implement default sort algorithm. + // eslint-disable-next-line no-case-declarations + const aCost = calculateItineraryCost(a, config) + // eslint-disable-next-line no-case-declarations + const bCost = calculateItineraryCost(b, config) + if (direction === 'ASC') return aCost - bCost + else return bCost - aCost + } +} + +/** + * Get the active itineraries for the active search, which is dependent on + * whether realtime or non-realtime results should be displayed + * @param {Object} state the redux state object + * @return {Array} array of itinerary objects from the OTP plan response, + * or null if there is no active search + */ +export const getActiveItineraries = createSelector( + (state) => state.otp.config, + getActiveSearchNonRealtimeResponse, + (state) => state.otp.filter, + getActiveSearchRealtimeResponse, + getActiveFieldTripRequest, + ( + config, + nonRealtimeResponse, + itinerarySortSettings, + realtimeResponse, + activeFieldTripRequest + ) => { + const response = realtimeResponse || nonRealtimeResponse + const itineraries = [] + // keep track of itinerary hashes in order to not include duplicate + // itineraries. Duplicate itineraries can occur in batch routing where a walk + // to transit trip can sometimes still be the most optimal trip even when + // additional modes such as bike rental were also requested + const seenItineraryHashes = {} + if (response) { + response.forEach((res) => { + if (res && res.plan) { + res.plan.itineraries.forEach((itinerary) => { + // hashing takes a while on itineraries + const itineraryHash = hashItinerary(itinerary) + if (!seenItineraryHashes[itineraryHash]) { + itineraries.push(itinerary) + seenItineraryHashes[itineraryHash] = true + } + }) + } + }) + } + const { sort } = itinerarySortSettings + const { direction, type } = sort + // If no sort type is provided (e.g., because batch routing is not enabled), + // do not sort itineraries (default sort from API response is used). + // Also, do not sort itineraries if a field trip request is active + return !type || Boolean(activeFieldTripRequest) + ? itineraries + : itineraries.sort((a, b) => + sortItineraries(type, direction, a, b, config) + ) + } +) + /** * Get the active itinerary/profile for the active search object * @param {Object} state the redux state object @@ -660,8 +664,8 @@ export const getTransitiveData = createSelector( if (hasItineraryResponse) { return itineraryToRender ? itineraryToTransitive(itineraryToRender, { - companies, - disableFlexArc, + companies, + disableFlexArc, getTransitiveRouteLabel, intl }) @@ -821,6 +825,7 @@ export function getTitle(state, intl) { ) break default: + // eslint-disable-next-line no-case-declarations const activeSearch = getActiveSearch(state) if (activeSearch) { status = summarizeQuery(activeSearch.query, intl, user.savedLocations) From 004c296bec7b48dcd8beab729fb6ee1cb8cd6639 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 8 Jul 2022 11:29:44 -0700 Subject: [PATCH 0328/1425] test: fix tests --- .../viewers/__snapshots__/stop-viewer.js.snap | 86 +++++++++---------- .../map/connected-transitive-overlay.tsx | 1 + 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 8c838a55e..273e9b469 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -566,13 +566,13 @@ exports[`components > viewers > stop viewer should render countdown times after name="calendar" > @@ -779,7 +779,7 @@ exports[`components > viewers > stop viewer should render countdown times after
  • viewers > stop viewer should render countdown times after color="333333" >
    20 @@ -1514,7 +1514,7 @@ exports[`components > viewers > stop viewer should render countdown times after margin={0.25} > viewers > stop viewer should render countdown times after name="refresh" > @@ -2369,7 +2369,7 @@ exports[`components > viewers > stop viewer should render countdown times after >
    P
    @@ -2768,13 +2768,13 @@ exports[`components > viewers > stop viewer should render countdown times for st name="calendar" > @@ -2981,7 +2981,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
    viewers > stop viewer should render countdown times for st color="333333" >
    20 @@ -3437,7 +3437,7 @@ exports[`components > viewers > stop viewer should render countdown times for st margin={0.25} > viewers > stop viewer should render countdown times for st name="refresh" > @@ -3896,7 +3896,7 @@ exports[`components > viewers > stop viewer should render countdown times for st >
    P
    @@ -4493,13 +4493,13 @@ exports[`components > viewers > stop viewer should render times after midnight w name="calendar" > @@ -4706,7 +4706,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
    viewers > stop viewer should render times after midnight w color="333333" >
    20 @@ -5474,7 +5474,7 @@ exports[`components > viewers > stop viewer should render times after midnight w margin={0.25} > viewers > stop viewer should render times after midnight w name="refresh" > @@ -6329,7 +6329,7 @@ exports[`components > viewers > stop viewer should render times after midnight w >
    P
    @@ -7442,13 +7442,13 @@ exports[`components > viewers > stop viewer should render with OTP transit index name="calendar" > @@ -7655,7 +7655,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
    viewers > stop viewer should render with OTP transit index color="333333" >
    20 @@ -8894,7 +8894,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    36 @@ -9341,7 +9341,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -9896,7 +9896,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -10126,7 +10126,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index margin={0.25} > viewers > stop viewer should render with OTP transit index name="refresh" > @@ -12013,7 +12013,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index >
    P
    @@ -13116,13 +13116,13 @@ exports[`components > viewers > stop viewer should render with TriMet transit in name="calendar" > @@ -13329,7 +13329,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
    viewers > stop viewer should render with TriMet transit in color="333333" >
    20 @@ -14350,7 +14350,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in margin={0.25} > viewers > stop viewer should render with TriMet transit in name="refresh" > @@ -16217,7 +16217,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in >
    P
    diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index a02975041..45ff31a86 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -2,6 +2,7 @@ import { Company, Itinerary, Leg } from '@opentripplanner/types' import { connect } from 'react-redux' import { injectIntl, IntlShape, useIntl } from 'react-intl' import React from 'react' +// @ts-expect-error state.js is not typescripted import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { getTransitiveData } from '../../util/state' From 30fad82832589981551b3cffd4dca8825a4d79cb Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 8 Jul 2022 11:38:40 -0700 Subject: [PATCH 0329/1425] refactor(transitive): remove unused imports --- lib/components/map/connected-transitive-overlay.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 45ff31a86..b342e9bb4 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -1,7 +1,5 @@ -import { Company, Itinerary, Leg } from '@opentripplanner/types' import { connect } from 'react-redux' -import { injectIntl, IntlShape, useIntl } from 'react-intl' -import React from 'react' +import { injectIntl, IntlShape } from 'react-intl' // @ts-expect-error state.js is not typescripted import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' From 743e97bcbc73e9b6e66ca1b7459c95f5549cf0ed Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 11 Jul 2022 12:42:17 +0200 Subject: [PATCH 0330/1425] refactor(vector tiles): hack to make printable itinerary not display the entirely incorrect itinerary --- lib/components/app/print-layout.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/components/app/print-layout.tsx b/lib/components/app/print-layout.tsx index 1120872c4..aa208fca3 100644 --- a/lib/components/app/print-layout.tsx +++ b/lib/components/app/print-layout.tsx @@ -1,7 +1,6 @@ import { Button } from 'react-bootstrap' import { connect } from 'react-redux' import { FormattedMessage } from 'react-intl' -import { Itinerary } from '@opentripplanner/types' // @ts-expect-error not typescripted yet import PrintableItinerary from '@opentripplanner/printable-itinerary' import React, { Component } from 'react' @@ -14,6 +13,7 @@ import { ComponentContext } from '../../util/contexts' import { getActiveItinerary } from '../../util/state' import { parseUrlQueryString } from '../../actions/form' import { routingQuery } from '../../actions/api' +import { setMapCenter } from '../../actions/config' import DefaultMap from '../map/default-map' import Icon from '../util/icon' import SpanWithSpace from '../util/span-with-space' @@ -22,10 +22,12 @@ import TripDetails from '../narrative/connected-trip-details' type Props = { // TODO: Typescript config type config: any + currentQuery: any // TODO: typescript state.js itinerary: any location?: { search?: string } parseUrlQueryString: (params?: any, source?: string) => any + setMapCenter: ({ lat, lon }: { lat: number; lon: number }) => void } type State = { mapVisible?: boolean @@ -54,7 +56,17 @@ class PrintLayout extends Component { } componentDidMount() { - const { itinerary, location, parseUrlQueryString } = this.props + const { + currentQuery, + itinerary, + location, + parseUrlQueryString, + setMapCenter + } = this.props + // TODO: this is an annoying hack. Ideally we wouldn't wipe out initLat and initLon + // TODO: is there a way to adjust transitiveData to force the transitive overlay to re-adjust bounds? + const { lat, lon } = currentQuery.from + // Add print-view class to html tag to ensure that iOS scroll fix only applies // to non-print views. addPrintViewClassToRootHtml() @@ -62,6 +74,8 @@ class PrintLayout extends Component { if (!itinerary && location && location.search) { parseUrlQueryString() } + + setMapCenter({ lat, lon }) } componentWillUnmount() { @@ -126,13 +140,15 @@ class PrintLayout extends Component { const mapStateToProps = (state: any) => { return { config: state.otp.config, + currentQuery: state.otp.currentQuery, itinerary: getActiveItinerary(state) } } const mapDispatchToProps = { parseUrlQueryString, - routingQuery + routingQuery, + setMapCenter } export default connect(mapStateToProps, mapDispatchToProps)(PrintLayout) From 88d2d5eb61aa8b8ae6c948b0d27187b0d88398b5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 11 Jul 2022 16:44:36 -0400 Subject: [PATCH 0331/1425] refactor(actions/plan): Replace moment with date-fns and fix zoned dates/times. --- lib/actions/plan.js | 164 +++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 70 deletions(-) diff --git a/lib/actions/plan.js b/lib/actions/plan.js index 728bf5ed4..978ed2b8d 100644 --- a/lib/actions/plan.js +++ b/lib/actions/plan.js @@ -1,11 +1,11 @@ -import { getTimeZoneOffset } from '@opentripplanner/core-utils/lib/itinerary' +import { addDays, format, isBefore, subDays } from 'date-fns' import { - OTP_API_DATE_FORMAT, + OTP_API_DATE_FORMAT_DATE_FNS, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time' -import moment from 'moment' +import { toDate, utcToZonedTime } from 'date-fns-tz' -import { getActiveItinerary } from '../util/state' +import { getActiveItineraries, getActiveItinerary } from '../util/state' import { getFirstStopId } from '../util/itinerary' import { routingQuery } from './api' @@ -15,18 +15,12 @@ const SERVICE_BREAK = '03:00' const NINETY_SECONDS = 90000 function updateParamsAndPlan(params) { - return function (dispatch, getState) { + return function (dispatch) { dispatch(setQueryParam(params)) dispatch(routingQuery()) } } -function offsetTime(itinerary, unixTime) { - let offset = 0 - if (itinerary) offset = getTimeZoneOffset(itinerary) - return moment(unixTime + offset) -} - /** * Effectively checks whether #planFirst has already been clicked, i.e., the * query is planning a depart at the service break time. @@ -36,25 +30,28 @@ function isPlanningFirst(query) { return departArrive === 'DEPART' && time === SERVICE_BREAK } +function getActiveItineraryOrFirstFound(state) { + const itineraries = getActiveItineraries(state) + // (Use first entry from from itineraries if no active itinerary is selected.) + return getActiveItinerary(state) || itineraries[0] +} + /** - * Plan the first trip of the day, or if the first trip has already been planned, - * plan the first trip of the previous day. + * Shifts the planning start/end date/time to the specified values. */ -export function planFirst() { +function shiftPlan(departArrive, zonedDate, time, itinerary) { return function (dispatch, getState) { const state = getState() - const itinerary = getActiveItinerary(state) - const { currentQuery } = state.otp - const date = moment(currentQuery.date) - // If already planning for the "first" trip, subtract a day to mirror the - // behavior of planLast. - if (isPlanningFirst(currentQuery)) date.subtract('days', 1) + const { api } = state.otp.config const params = { - date: date.format(OTP_API_DATE_FORMAT), - departArrive: 'DEPART', - time: SERVICE_BREAK + date: format(zonedDate, OTP_API_DATE_FORMAT_DATE_FNS), + departArrive, + time } - if (!state.otp?.config?.api?.v2) { + if (!api?.v2) { + if (!itinerary) { + itinerary = getActiveItineraryOrFirstFound(state) + } params.startTransitStopId = getFirstStopId(itinerary) } dispatch(updateParamsAndPlan(params)) @@ -62,69 +59,96 @@ export function planFirst() { } /** - * Plan the previous trip, setting the arrive by time to the current itinerary's - * end time (minus a small amount). + * Plan the first or last trip of the day, or if the first/last trip has already been planned, + * plan the first/last trip of the previous/next day, respectively. */ -export function planPrevious() { +function shiftDay(dayDirection) { return function (dispatch, getState) { - const itinerary = getActiveItinerary(getState()) - const newEndTime = offsetTime(itinerary, itinerary.endTime - NINETY_SECONDS) - const params = { - date: newEndTime.format(OTP_API_DATE_FORMAT), - departArrive: 'ARRIVE', - time: newEndTime.format(OTP_API_TIME_FORMAT) - } - if (!getState().otp?.config?.api?.v2) { - params.startTransitStopId = getFirstStopId(itinerary) + const { config, currentQuery } = getState().otp + const { homeTimezone: timeZone } = config + const { date, time } = currentQuery + + let newDate = toDate(date, { timeZone }) + + const isBackward = dayDirection === -1 + if (isBackward && isPlanningFirst(currentQuery)) { + // Initially go to the beginning of the service day. + // If already planning for the "first" trip, shift one full day back. + newDate = subDays(newDate, 1) + } else if ( + !isBackward && + !isBefore( + toDate(`${date} ${time}`, { timeZone }), + toDate(`${date} ${SERVICE_BREAK}`, { timeZone }) + ) + ) { + // If searching for the "last" trip, shift to the next calendar day + // at the time when the next service day starts (e.g. 3 am), + // unless the time is between midnight and the service break time. + newDate = addDays(newDate, 1) } - dispatch(updateParamsAndPlan(params)) + + dispatch( + shiftPlan(isBackward ? 'DEPART' : 'ARRIVE', newDate, SERVICE_BREAK) + ) } } /** - * Plan the next trip, setting the depart at time to the current itinerary's - * start time (plus a small amount). + * Plan the previous/next trip, setting the arrive by/departure time to the current itinerary's + * end time (minus/plus a small amount). */ -export function planNext() { +function shiftTime(offset) { return function (dispatch, getState) { - const itinerary = getActiveItinerary(getState()) - const newStartTime = offsetTime( - itinerary, - itinerary.startTime + NINETY_SECONDS + const state = getState() + const { homeTimezone } = state.otp.config + const itinerary = getActiveItineraryOrFirstFound(state) + const isBackward = offset < 0 + // newEndTime is expressed in the configured homeTimezone. + const newEndTime = utcToZonedTime( + new Date((isBackward ? itinerary.startTime : itinerary.endTime) + offset), + homeTimezone ) - const params = { - date: newStartTime.format(OTP_API_DATE_FORMAT), - departArrive: 'DEPART', - time: newStartTime.format(OTP_API_TIME_FORMAT) - } - if (!getState().otp?.config?.api?.v2) { - params.startTransitStopId = getFirstStopId(itinerary) - } - dispatch(updateParamsAndPlan(params)) + dispatch( + shiftPlan( + isBackward ? 'ARRIVE' : 'DEPART', + newEndTime, + format(newEndTime, OTP_API_TIME_FORMAT), + itinerary + ) + ) } } +/** + * Plan the first trip of the day, or if the first trip has already been planned, + * plan the first trip of the previous day. + */ +export function planFirst() { + return shiftDay(-1) +} + +/** + * Plan the previous trip, setting the arrive by time to the current itinerary's + * end time (minus a small amount). + */ +export function planPrevious() { + return shiftTime(-NINETY_SECONDS) +} + +/** + * Plan the next trip, setting the depart at time to the current itinerary's + * start time (plus a small amount). + */ +export function planNext() { + return shiftTime(NINETY_SECONDS) +} + /** * Plan the last trip of the day, or if the last trip has already been planned, * plan the last trip of the next day. */ export function planLast() { - return function (dispatch, getState) { - const state = getState() - const itinerary = getActiveItinerary(state) - const { currentQuery } = state.otp - const params = { - date: moment(currentQuery.date) - .add('days', 1) - .format(OTP_API_DATE_FORMAT), - departArrive: 'ARRIVE', - time: SERVICE_BREAK - } - if (!state.otp?.config?.api?.v2) { - params.startTransitStopId = getFirstStopId(itinerary) - } - - dispatch(updateParamsAndPlan(params)) - } + return shiftDay(1) } From fcf45607218c444afb73738a2f862ce116fe3871 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:04:47 -0400 Subject: [PATCH 0332/1425] refactor(actions/user): Replace moment() with getCurrentDate (otp-ui). --- lib/actions/user.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 335cbd0f3..412193124 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -2,7 +2,6 @@ import { createAction } from 'redux-actions' import clone from 'clone' import coreUtils from '@opentripplanner/core-utils' import isEmpty from 'lodash.isempty' -import moment from 'moment' import qs from 'qs' import { @@ -22,7 +21,7 @@ import { routingQuery } from './api' import { setQueryParam } from './form' const { planParamsToQuery } = coreUtils.query -const { OTP_API_DATE_FORMAT } = coreUtils.time +const { getCurrentDate } = coreUtils.time // Middleware API paths. const API_MONITORED_TRIP_PATH = '/api/secure/monitoredtrip' @@ -672,7 +671,7 @@ export function planNewTripFromMonitoredTrip(monitoredTrip) { return function (dispatch, getState) { // update query params in store const newQuery = planParamsToQuery(qs.parse(monitoredTrip.queryParams)) - newQuery.date = moment().format(OTP_API_DATE_FORMAT) + newQuery.date = getCurrentDate(getState().otp.config.homeTimezone) dispatch(setQueryParam(newQuery)) From 74dd57d2b05ff5d3c10c39f616025e8d22870ab9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 12 Jul 2022 11:17:39 +0200 Subject: [PATCH 0333/1425] fix(metro ui): don't render e-scooter as bikeshare --- lib/components/narrative/default/itinerary-description.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/narrative/default/itinerary-description.tsx b/lib/components/narrative/default/itinerary-description.tsx index 95332565a..dc4fcd44f 100644 --- a/lib/components/narrative/default/itinerary-description.tsx +++ b/lib/components/narrative/default/itinerary-description.tsx @@ -75,9 +75,9 @@ export function ItineraryDescription({ itinerary }: Props): JSX.Element { } if (isBicycle(mode)) accessModeId = 'bicycle' + if (rentedBike) accessModeId = 'bicycle_rent' if (isMicromobility(mode)) accessModeId = 'micromobility' if (rentedVehicle) accessModeId = 'micromobility_rent' - if (rentedBike) accessModeId = 'bicycle_rent' if (mode === 'CAR') accessModeId = 'drive' }) From da4691477b7dd6f516455eb2723fe3a90cefb454 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 12 Jul 2022 12:54:11 +0200 Subject: [PATCH 0334/1425] chore: update lockfile --- yarn.lock | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8079bf3e9..dc173894d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2486,7 +2486,7 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.2.0.tgz#71cf5d5d1b756adef15300edbba0995ccd4b35ee" integrity sha512-x0QRXMDhypFeazZ6r6vzrdU8vhiV56nZ/WX6zUbxpgp6T9Oclw0gwR2Zdw6DZiiFpSYVNeVNxVzZwsu6NRGjcA== -"@opentripplanner/icons@^1.2.2": +"@opentripplanner/icons@1.2.2", "@opentripplanner/icons@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-1.2.2.tgz#18c93ce34afe2d83a2f0fc6ecea85aa587d14366" integrity sha512-SwHAGyayHAPSVKIVbY1mNXq+ZLA1If61onE0mATKh+sifKnKpjRWgHeEHDpeZu6Dz0mIXnY82tcNUvArjeSxig== @@ -2526,6 +2526,22 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" +"@opentripplanner/itinerary-body@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.1.1.tgz#854e83022de09cb023e7f96ae38bb6e625db82b5" + integrity sha512-fGKu7xSHok5FcKK5lEHtintHiEpcsUm1NE35ZZHRW7b5ejqBedq8/Be4Fo5WocEWZz9MWb1yyG2YqctceBYofw== + dependencies: + "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/humanize-distance" "^1.2.0" + "@opentripplanner/icons" "^1.2.2" + "@opentripplanner/location-icon" "^1.4.0" + "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" + flat "^5.0.2" + moment "^2.24.0" + react-resize-detector "^4.2.1" + velocity-react "^1.4.3" + "@opentripplanner/location-field@1.12.3": version "1.12.3" resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.12.3.tgz#0ff027f941e45880e01470ed88a044e4fda19500" @@ -2586,6 +2602,7 @@ dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/types" "^2.0.0" "@opentripplanner/stops-overlay@../otp-ui/packages/stops-overlay": version "4.0.0" @@ -2598,14 +2615,17 @@ version "2.4.0" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/icons" "1.2.2" flat "^5.0.2" "@opentripplanner/transitive-overlay@../otp-ui/packages/transitive-overlay": - version "1.1.5" + version "2.0.0" dependencies: "@mapbox/polyline" "^1.1.1" "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^5.0.1" + "@opentripplanner/itinerary-body" "^3.1.1" "@turf/bbox" "^6.5.0" lodash.isequal "^4.5.0" transitive-js "^0.14.1" From e43307f8107c6201abae138ea0a472812470b9c6 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 12 Jul 2022 13:41:37 +0200 Subject: [PATCH 0335/1425] refactor: better handle poor data --- lib/actions/apiV2.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 149d3e597..5c07ac5ff 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -147,7 +147,7 @@ export const vehicleRentalQuery = ( // we should re-write the rest of the UI to match OTP's behavior instead rewritePayload: (payload) => { return { - stations: payload?.data?.rentalVehicles.map((vehicle) => { + stations: payload?.data?.rentalVehicles?.map((vehicle) => { return { allowPickup: vehicle.allowPickupNow, id: vehicle.vehicleId, @@ -297,27 +297,27 @@ const findNearbyAmenities = ({ lat, lon, radius = 300, stopId }) => { { noThrottle: true, rewritePayload: (payload) => { - if (!payload.data) + if (!payload.data?.nearest) return { bikeRental: { stations: [] }, vehicleRentalQuery: { stations: [] } } const { edges } = payload.data.nearest || [] - const bikeStations = edges.filter( + const bikeStations = edges?.filter( (edge) => edge?.node?.place?.bikesAvailable !== undefined || !!edge?.node?.place?.bicyclePlaces || edge?.node?.place?.vehicleType?.formFactor === 'BICYCLE' ) - const parkAndRides = edges.filter( + const parkAndRides = edges?.filter( (edge) => edge?.node?.place?.carPlaces ) - const vehicleRentals = edges.filter( + const vehicleRentals = edges?.filter( (edge) => edge?.node?.place?.vehicleType?.formFactor === 'SCOOTER' ) return { bikeRental: { - stations: bikeStations.map((edge) => { + stations: bikeStations?.map((edge) => { const { __typename, bikesAvailable, @@ -344,7 +344,7 @@ const findNearbyAmenities = ({ lat, lon, radius = 300, stopId }) => { } }) }, - parkAndRideLocations: parkAndRides.map((edge) => { + parkAndRideLocations: parkAndRides?.map((edge) => { const { id, lat, lon, name } = edge?.node?.place return { distance: edge.node.distance, @@ -358,7 +358,7 @@ const findNearbyAmenities = ({ lat, lon, radius = 300, stopId }) => { }), stopId, vehicleRental: { - stations: vehicleRentals.map((edge) => { + stations: vehicleRentals?.map((edge) => { const { id, lat, lon, name, network, networks } = edge?.node?.place return { From 0cbb42204e56249bf8b6b6ec8037bd099bb93b94 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 12 Jul 2022 12:39:32 -0700 Subject: [PATCH 0336/1425] refactor: fix indent --- lib/util/state.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index ac1c01a33..d845d2e17 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -660,8 +660,8 @@ export const getTransitiveData = createSelector( if (hasItineraryResponse) { return itineraryToRender ? itineraryToTransitive(itineraryToRender, { - companies, - disableFlexArc, + companies, + disableFlexArc, getTransitiveRouteLabel, intl }) From 6a3123807c517f8c6c3bf99e0779d1702ca0daa0 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 12 Jul 2022 12:45:35 -0700 Subject: [PATCH 0337/1425] refactor(transitive-overlay): make props optional --- lib/components/map/connected-transitive-overlay.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index b342e9bb4..c0910f651 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -6,9 +6,9 @@ import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { getTransitiveData } from '../../util/state' type Props = { - intl: IntlShape - labeledModes: string[] - styles: { + intl?: IntlShape + labeledModes?: string[] + styles?: { labels: Record segmentLabels: Record } From 46f74ed40949de346b0f4df21531a9d6605e8521 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:21:38 -0400 Subject: [PATCH 0338/1425] fix(FieldTripDetails): Allow entering unchecked values into ft date input before committing valid on --- lib/components/admin/field-trip-details.js | 31 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 8b1f30ac1..2aa964c8d 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -45,6 +45,15 @@ const WindowHeader = styled(DefaultWindowHeader)` * Shows the details for the active Field Trip Request. */ class FieldTripDetails extends Component { + constructor(props) { + super(props) + this.state = { + // Changes every time a field trip travel date is persisted + // (used to reset the field trip date input). + travelDateUpdateCount: 0 + } + } + _editSubmitterNotes = (val) => { const { editSubmitterNotes, intl, request } = this.props editSubmitterNotes(request, val, intl) @@ -73,9 +82,16 @@ class FieldTripDetails extends Component { } _setRequestDate = (evt) => { - const { dateFormat, intl, request, setRequestDate } = this.props - const convertedRequestDate = moment(evt.target.value).format(dateFormat) - setRequestDate(request, convertedRequestDate, intl) + const newDate = evt.target.value + // Only persist the date if it is valid (not an empty string) + if (newDate !== '') { + const { dateFormat, intl, request, setRequestDate } = this.props + const convertedRequestDate = moment(newDate).format(dateFormat) + setRequestDate(request, convertedRequestDate, intl) + this.setState({ + travelDateUpdateCount: this.state.travelDateUpdateCount + 1 + }) + } } _renderFooter = () => { @@ -118,8 +134,10 @@ class FieldTripDetails extends Component { _renderHeader = () => { const { request } = this.props + const { travelDateUpdateCount } = this.state const { id, schoolName, travelDate } = request const travelDateAsMoment = moment(travelDate) + const travelDateFormatted = travelDateAsMoment.format('YYYY-MM-DD') return ( {schoolName} Trip (#{id}) @@ -128,14 +146,17 @@ class FieldTripDetails extends Component { Travel date: ({travelDateAsMoment.fromNow()}) From 8ab5bdcb8a7d98f9f1d5555127cd722d89dcb21a Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 13 Jul 2022 17:57:56 +0200 Subject: [PATCH 0339/1425] fix: move merging itinerary times behind config option --- .../narrative/narrative-itineraries.js | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 88445abd5..fa2f3b4e6 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -47,6 +47,7 @@ class NarrativeItineraries extends Component { errors: PropTypes.object, itineraries: PropTypes.array, itineraryIsExpanded: PropTypes.bool, + mergeItineraries: PropTypes.bool, modes: PropTypes.object, pending: PropTypes.bool, realtimeEffects: PropTypes.object, @@ -209,6 +210,7 @@ class NarrativeItineraries extends Component { intl, itineraries, itineraryIsExpanded, + mergeItineraries, pending, showHeaderText, sort @@ -218,65 +220,67 @@ class NarrativeItineraries extends Component { if (!activeSearch) return null // Merge duplicate itineraries together and save multiple departure times - const mergedItineraries = itineraries.reduce((prev, cur, curIndex) => { - const updatedItineraries = clone(prev) - const updatedItinerary = clone(cur) - updatedItinerary.index = curIndex - - const duplicateIndex = updatedItineraries.findIndex((itin) => - itinerariesAreEqual(itin, cur) - ) - // If no duplicate, push full itinerary to output - if (duplicateIndex === -1) { - updatedItineraries.push(updatedItinerary) - } else { - const duplicateItin = updatedItineraries[duplicateIndex] - // TODO: MERGE ROUTE NAMES - - // Add only new start time to existing itinerary - if (!duplicateItin.allStartTimes) { - duplicateItin.allStartTimes = [ - { - arrival: duplicateItin.endTime, - realtime: firstTransitLegIsRealtime(duplicateItin), - time: duplicateItin.startTime - } - ] - } - // Only add new time if it doesn't already exist. It would be better to use - // the uniqueness feature of Set, but unfortunately objects are never equal - if ( - !duplicateItin.allStartTimes.find( - (time) => time.time === cur.startTime + const mergedItineraries = mergeItineraries + ? itineraries.reduce((prev, cur, curIndex) => { + const updatedItineraries = clone(prev) + const updatedItinerary = clone(cur) + updatedItinerary.index = curIndex + + const duplicateIndex = updatedItineraries.findIndex((itin) => + itinerariesAreEqual(itin, cur) ) - ) { - duplicateItin.allStartTimes.push({ - arrival: cur.endTime, - realtime: firstTransitLegIsRealtime(cur), - time: cur.startTime - }) - } - - // Some legs will be the same, but have a different route - // This map catches those and stores the alternate routes so they can be displayed - duplicateItin.legs = duplicateItin.legs.map((leg, index) => { - const newLeg = clone(leg) - if (leg?.routeId !== cur.legs[index]?.routeId) { - if (!newLeg.alternateRoutes) { - newLeg.alternateRoutes = {} + // If no duplicate, push full itinerary to output + if (duplicateIndex === -1) { + updatedItineraries.push(updatedItinerary) + } else { + const duplicateItin = updatedItineraries[duplicateIndex] + // TODO: MERGE ROUTE NAMES + + // Add only new start time to existing itinerary + if (!duplicateItin.allStartTimes) { + duplicateItin.allStartTimes = [ + { + arrival: duplicateItin.endTime, + realtime: firstTransitLegIsRealtime(duplicateItin), + time: duplicateItin.startTime + } + ] } - const { routeId } = cur.legs?.[index] - newLeg.alternateRoutes[routeId] = { - // We save the entire leg to the alternateRoutes object so in - // the future, we can draw the leg on the map as an alternate route - ...cur.legs?.[index] + // Only add new time if it doesn't already exist. It would be better to use + // the uniqueness feature of Set, but unfortunately objects are never equal + if ( + !duplicateItin.allStartTimes.find( + (time) => time.time === cur.startTime + ) + ) { + duplicateItin.allStartTimes.push({ + arrival: cur.endTime, + realtime: firstTransitLegIsRealtime(cur), + time: cur.startTime + }) } + + // Some legs will be the same, but have a different route + // This map catches those and stores the alternate routes so they can be displayed + duplicateItin.legs = duplicateItin.legs.map((leg, index) => { + const newLeg = clone(leg) + if (leg?.routeId !== cur.legs[index]?.routeId) { + if (!newLeg.alternateRoutes) { + newLeg.alternateRoutes = {} + } + const { routeId } = cur.legs?.[index] + newLeg.alternateRoutes[routeId] = { + // We save the entire leg to the alternateRoutes object so in + // the future, we can draw the leg on the map as an alternate route + ...cur.legs?.[index] + } + } + return newLeg + }) } - return newLeg - }) - } - return updatedItineraries - }, []) + return updatedItineraries + }, []) + : itineraries // This loop determines if an itinerary uses a single or multiple modes const groupedMergedItineraries = mergedItineraries.reduce( @@ -394,6 +398,7 @@ const mapStateToProps = (state) => { customBatchUiBackground, groupByMode: groupItineraries, groupTransitModes, + mergeItineraries, showHeaderText } = state.otp.config?.itinerary || false // Default to true for backwards compatibility @@ -421,6 +426,7 @@ const mapStateToProps = (state) => { // use a key so that the NarrativeItineraries component and its state is // reset each time a new search is shown key: state.otp.activeSearchId, + mergeItineraries, modes, pending, realtimeEffects, From 556244704d6c028fcbce376128d45b2806eaf5b3 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 13 Jul 2022 18:09:18 +0200 Subject: [PATCH 0340/1425] chore(docs): update example config to reflect latest config options --- example-config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example-config.yml b/example-config.yml index daae8a9f5..722d57fcb 100644 --- a/example-config.yml +++ b/example-config.yml @@ -287,8 +287,10 @@ itinerary: customBatchUiBackground: true # Whether to render itineraries below a mode description header groupByMode: true - # Whether to merge itineraries with the same origin, destination, and transit modes + # Whether to merge groups of itineraries with the same primary mode (Bike, Walk, etc) groupTransitModes: true + # Whether to merge itineraries with the same origin, destination, and transit modes + mergeItineraries: true # Whether to show the first itinerary on the map. If this is set to false, routes # will only show on the map on hover or click showFirstResultByDefault: false From f522e55bca6d3e8842469cc0f36a9f8666efe442 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Wed, 13 Jul 2022 18:21:11 +0200 Subject: [PATCH 0341/1425] chore(percy): update percy config for new config variable --- percy/har-mock-config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/percy/har-mock-config.yml b/percy/har-mock-config.yml index fade442e3..c9ab70f5a 100644 --- a/percy/har-mock-config.yml +++ b/percy/har-mock-config.yml @@ -114,3 +114,6 @@ dateTime: timeFormat: h:mm a dateFormat: MM/DD/YYYY longDateFormat: MMMM D, YYYY + +itinerary: + mergeItineraries: true \ No newline at end of file From 92712e6561fa479bed104cbd12e06fd340477021 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 13 Jul 2022 15:24:20 -0700 Subject: [PATCH 0342/1425] refactor(state): remove eslint disable --- .../viewers/__snapshots__/stop-viewer.js.snap | 86 +++++++++---------- .../map/connected-transitive-overlay.tsx | 1 + lib/util/state.js | 3 +- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 8c838a55e..273e9b469 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -566,13 +566,13 @@ exports[`components > viewers > stop viewer should render countdown times after name="calendar" > @@ -779,7 +779,7 @@ exports[`components > viewers > stop viewer should render countdown times after
    viewers > stop viewer should render countdown times after color="333333" >
    20 @@ -1514,7 +1514,7 @@ exports[`components > viewers > stop viewer should render countdown times after margin={0.25} > viewers > stop viewer should render countdown times after name="refresh" > @@ -2369,7 +2369,7 @@ exports[`components > viewers > stop viewer should render countdown times after >
    P
    @@ -2768,13 +2768,13 @@ exports[`components > viewers > stop viewer should render countdown times for st name="calendar" > @@ -2981,7 +2981,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
    viewers > stop viewer should render countdown times for st color="333333" >
    20 @@ -3437,7 +3437,7 @@ exports[`components > viewers > stop viewer should render countdown times for st margin={0.25} > viewers > stop viewer should render countdown times for st name="refresh" > @@ -3896,7 +3896,7 @@ exports[`components > viewers > stop viewer should render countdown times for st >
    P
    @@ -4493,13 +4493,13 @@ exports[`components > viewers > stop viewer should render times after midnight w name="calendar" > @@ -4706,7 +4706,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
    viewers > stop viewer should render times after midnight w color="333333" >
    20 @@ -5474,7 +5474,7 @@ exports[`components > viewers > stop viewer should render times after midnight w margin={0.25} > viewers > stop viewer should render times after midnight w name="refresh" > @@ -6329,7 +6329,7 @@ exports[`components > viewers > stop viewer should render times after midnight w >
    P
    @@ -7442,13 +7442,13 @@ exports[`components > viewers > stop viewer should render with OTP transit index name="calendar" > @@ -7655,7 +7655,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
    viewers > stop viewer should render with OTP transit index color="333333" >
    20 @@ -8894,7 +8894,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    36 @@ -9341,7 +9341,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -9896,7 +9896,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -10126,7 +10126,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index margin={0.25} > viewers > stop viewer should render with OTP transit index name="refresh" > @@ -12013,7 +12013,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index >
    P
    @@ -13116,13 +13116,13 @@ exports[`components > viewers > stop viewer should render with TriMet transit in name="calendar" > @@ -13329,7 +13329,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
    viewers > stop viewer should render with TriMet transit in color="333333" >
    20 @@ -14350,7 +14350,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in margin={0.25} > viewers > stop viewer should render with TriMet transit in name="refresh" > @@ -16217,7 +16217,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in >
    P
    diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index a02975041..a8b8c1932 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -2,6 +2,7 @@ import { Company, Itinerary, Leg } from '@opentripplanner/types' import { connect } from 'react-redux' import { injectIntl, IntlShape, useIntl } from 'react-intl' import React from 'react' +// @ts-expect-error transitive-overlay not typescripted import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { getTransitiveData } from '../../util/state' diff --git a/lib/util/state.js b/lib/util/state.js index 15e03fb11..842c3fbc0 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -1,4 +1,3 @@ -// REMOVE THIS LINE BEFORE EDITING THIS FILE import { createSelector } from 'reselect' import { FormattedList, FormattedMessage } from 'react-intl' import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' @@ -75,7 +74,7 @@ export function getFeedWideRentalErrors(response) { * Generates a list of issues/errors from a list of OTP responses. * * @param {Object} state the redux state object - * @return {Array} An array of objects describing the errors seen. These could also + * @return {Array} An array of objects describing errors seen. These could also * include an `id` and `modes` key for trip planning errors or `network` for * errors with vehicle rentals. */ From 7944b7372015b6ef17b8c39a43a2b1cc85325565 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 13 Jul 2022 15:32:26 -0700 Subject: [PATCH 0343/1425] refactor(api): remove eslint disable --- lib/actions/api.js | 475 ++++++++++++++++++++++----------------------- 1 file changed, 236 insertions(+), 239 deletions(-) diff --git a/lib/actions/api.js b/lib/actions/api.js index 10eadb852..2ea159092 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ -// FIXME: This file has many methods and variables used before they are defined. - /* globals fetch */ import { push, replace } from 'connected-react-router' import hash from 'object-hash' @@ -106,6 +103,115 @@ function executeOTPAction(methodName, ...params) { } } +function constructRoutingQuery( + state, + ignoreRealtimeUpdates, + injectedParams = {} +) { + const { config, currentQuery } = state.otp + const routingType = currentQuery.routingType + // Check for routingType-specific API config; if none, use default API + const rt = + config.routingTypes && + config.routingTypes.find((rt) => rt.key === routingType) + const api = (rt && rt.api) || config.api + const planEndpoint = `${api.host}${api.port ? ':' + api.port : ''}${ + api.path + }/plan` + const params = { + ...getRoutingParams(config, currentQuery, ignoreRealtimeUpdates), + // Apply mode override, if specified (for batch routing). + ...injectedParams + } + return `${planEndpoint}?${qs.stringify(params, { arrayFormat: 'repeat' })}` +} + +/** + * This method determines the fetch options (including API key and Authorization headers) for the OTP API. + * - If the OTP server is not the middleware server (standalone OTP server), + * an empty object is returned. + * - If the OTP server is the same as the middleware server, + * then an object is returned with the following: + * - A middleware API key, if it has been set in the configuration (it is most likely required), + * - An Auth0 accessToken, when includeToken is true and a user is logged in (userState.loggedInUser is not null). + * This method assumes JSON request bodies.) + */ +function getOtpFetchOptions(state, includeToken = false) { + let apiBaseUrl, apiKey, token + + const { api, persistence } = state.otp.config + if (persistence && persistence.otp_middleware) { + // Prettier does not understand the syntax on this line + // eslint-disable-next-line prettier/prettier + ({ apiBaseUrl, apiKey } = persistence.otp_middleware) + } + + const isOtpServerSameAsMiddleware = apiBaseUrl === api.host + if (isOtpServerSameAsMiddleware) { + if (includeToken && state.user) { + const { accessToken, loggedInUser } = state.user + if (accessToken && loggedInUser) { + token = accessToken + } + } + + return getSecureFetchOptions(token, apiKey) + } else { + return {} + } +} + +function getJsonAndCheckResponse(res) { + if (res.status >= 400) { + const error = new Error('Received error from server') + error.response = res + throw error + } + return res.json() +} + +/** + * Update the browser/URL history with new parameters + * NOTE: This has not been tested for profile-based journeys. + */ +export function setUrlSearch(params, replaceCurrent = false) { + return function (dispatch, getState) { + const base = getState().router.location.pathname + const path = `${base}?${qs.stringify(params, { arrayFormat: 'repeat' })}` + if (replaceCurrent) dispatch(replace(path)) + else dispatch(push(path)) + } +} + +/** + * Update the OTP Query parameters in the URL and ensure that the active search + * is set correctly. Leaves any other existing URL parameters (e.g., UI) unchanged. + */ +export function updateOtpUrlParams(state, searchId) { + const { config, currentQuery } = state.otp + // Get updated OTP params from current query. + const otpParams = getRoutingParams(config, currentQuery, true) + return function (dispatch, getState) { + const params = {} + // Get all URL params and ensure non-routing params (UI, sessionId) remain + // unchanged. + const urlParams = getUrlParams() + Object.keys(urlParams) + // If param is non-routing, add to params to keep the same after update. + .filter((key) => key.indexOf('_') !== -1 || key === 'sessionId') + .forEach((key) => { + params[key] = urlParams[key] + }) + params.ui_activeSearch = searchId + // Assumes this is a new search and the active itinerary should be reset. + params.ui_activeItinerary = config.itinerary?.showFirstResultByDefault + ? 0 + : -1 + // Merge in the provided OTP params and update the URL. + dispatch(setUrlSearch(Object.assign(params, otpParams))) + } +} + /** * Send a routing query to the OTP backend. * @@ -240,71 +346,145 @@ export function routingQuery(searchId = null, updateSearchInReducer = false) { } } -function getJsonAndCheckResponse(res) { - if (res.status >= 400) { - const error = new Error('Received error from server') - error.response = res - throw error - } - return res.json() -} - /** - * This method determines the fetch options (including API key and Authorization headers) for the OTP API. - * - If the OTP server is not the middleware server (standalone OTP server), - * an empty object is returned. - * - If the OTP server is the same as the middleware server, - * then an object is returned with the following: - * - A middleware API key, if it has been set in the configuration (it is most likely required), - * - An Auth0 accessToken, when includeToken is true and a user is logged in (userState.loggedInUser is not null). - * This method assumes JSON request bodies.) + * Creates the URL to use for making an API request. + * + * @param {Object} config The app-wide config + * @param {string} endpoint The API endpoint path + * @param {Object} options The options object for the API request + * @return {string} The URL to use for making the http request */ -function getOtpFetchOptions(state, includeToken = false) { - let apiBaseUrl, apiKey, token +function makeApiUrl(config, endpoint, options) { + let url + if ( + options.serviceId && + config.alternateTransitIndex && + config.alternateTransitIndex.services.includes(options.serviceId) + ) { + console.log('Using alt service for ' + options.serviceId) + url = config.alternateTransitIndex.apiRoot + endpoint + } else { + const api = config.api - const { api, persistence } = state.otp.config - if (persistence && persistence.otp_middleware) { - // Prettier does not understand the syntax on this line - // eslint-disable-next-line prettier/prettier - ({ apiBaseUrl, apiKey } = persistence.otp_middleware) + // Don't crash if no api is defined (such as in the unit test env) + if (!api?.host) return null + + url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}` } + return url +} - const isOtpServerSameAsMiddleware = apiBaseUrl === api.host - if (isOtpServerSameAsMiddleware) { - if (includeToken && state.user) { - const { accessToken, loggedInUser } = state.user - if (accessToken && loggedInUser) { - token = accessToken - } +const throttledUrls = {} + +function now() { + return new Date().getTime() +} + +const TEN_SECONDS = 10000 + +// automatically clear throttled urls older than 10 seconds +window.setInterval(() => { + Object.keys(throttledUrls).forEach((key) => { + if (throttledUrls[key] < now() - TEN_SECONDS) { + delete throttledUrls[key] } + }) +}, 1000) - return getSecureFetchOptions(token, apiKey) - } else { - return {} +/** + * Handle throttling URL. + * @param {[type]} url - API endpoint path + * @param {FetchOptions} fetchOptions - fetch options (e.g., method, body, headers). + * @return {?number} - null if the URL has already been requested in the last + * ten seconds, otherwise the UNIX epoch millis of the request time + */ +function handleThrottlingUrl(url, fetchOptions) { + const throttleKey = fetchOptions ? `${url}-${hash(fetchOptions)}` : url + if ( + throttledUrls[throttleKey] && + throttledUrls[throttleKey] > now() - TEN_SECONDS + ) { + // URL already had a request within last 10 seconds, warn and exit + console.warn(`Request throttled for url: ${url}`) + return null } + throttledUrls[throttleKey] = now() + return throttledUrls[throttleKey] } -function constructRoutingQuery( - state, - ignoreRealtimeUpdates, - injectedParams = {} +/** + * Generic helper for constructing API queries. Automatically throttles queries + * to url to no more than once per 10 seconds. + * + * @param {string} endpoint - The API endpoint path (does not include + * '../otp/routers/router_id/') + * @param {Function} responseAction - Action to dispatch on a successful API + * response. Accepts payload object parameter. + * @param {Function} errorAction - Function to invoke on API error response. + * Accepts error object parameter. + * @param {Options} options - Any of the following optional settings: + * - rewritePayload: Function to be invoked to modify payload before being + * passed to responseAction. Accepts and returns payload object. + * - postprocess: Function to be invoked after responseAction is invoked. + * Accepts payload, dispatch, getState parameters. + * - serviceId: identifier for TransitIndex service used in + * alternateTransitIndex configuration. + * - fetchOptions: fetch options (e.g., method, body, headers). + */ +export function createQueryAction( + endpoint, + responseAction, + errorAction, + options = {} ) { - const { config, currentQuery } = state.otp - const routingType = currentQuery.routingType - // Check for routingType-specific API config; if none, use default API - const rt = - config.routingTypes && - config.routingTypes.find((rt) => rt.key === routingType) - const api = (rt && rt.api) || config.api - const planEndpoint = `${api.host}${api.port ? ':' + api.port : ''}${ - api.path - }/plan` - const params = { - ...getRoutingParams(config, currentQuery, ignoreRealtimeUpdates), - // Apply mode override, if specified (for batch routing). - ...injectedParams + /* eslint-disable-next-line complexity */ + return async function (dispatch, getState) { + const state = getState() + const { config } = state.otp + const url = makeApiUrl(config, endpoint, options) + + if (!options.noThrottle) { + // Don't make a request to a URL that has already seen the same request + // within the last 10 seconds + if (!handleThrottlingUrl(url, options.fetchOptions)) return + } + + let payload + try { + // Need to merge headers to support graphQL POST request with an api key + const mergedHeaders = { + ...getOtpFetchOptions(state)?.headers, + ...options.fetchOptions?.headers + } + + const response = await fetch(url, { + ...getOtpFetchOptions(state), + ...options.fetchOptions, + headers: mergedHeaders + }) + + if (response.status >= 400) { + const error = new Error('Received error from server') + error.response = response + throw error + } + payload = await response.json() + } catch (err) { + return dispatch(errorAction(err)) + } + + if (typeof options.rewritePayload === 'function') { + dispatch( + responseAction(options.rewritePayload(payload, dispatch, getState)) + ) + } else { + dispatch(responseAction(payload)) + } + + if (typeof options.postprocess === 'function') { + options.postprocess(payload, dispatch, getState) + } } - return `${planEndpoint}?${qs.stringify(params, { arrayFormat: 'repeat' })}` } // Park and Ride location query @@ -776,186 +956,3 @@ export const receivedVehiclePositionsError = createAction( export function getVehiclePositionsForRoute(routeId) { return executeOTPAction('getVehiclePositionsForRoute', routeId) } - -const throttledUrls = {} - -function now() { - return new Date().getTime() -} - -const TEN_SECONDS = 10000 - -// automatically clear throttled urls older than 10 seconds -window.setInterval(() => { - Object.keys(throttledUrls).forEach((key) => { - if (throttledUrls[key] < now() - TEN_SECONDS) { - delete throttledUrls[key] - } - }) -}, 1000) - -/** - * Handle throttling URL. - * @param {[type]} url - API endpoint path - * @param {FetchOptions} fetchOptions - fetch options (e.g., method, body, headers). - * @return {?number} - null if the URL has already been requested in the last - * ten seconds, otherwise the UNIX epoch millis of the request time - */ -function handleThrottlingUrl(url, fetchOptions) { - const throttleKey = fetchOptions ? `${url}-${hash(fetchOptions)}` : url - if ( - throttledUrls[throttleKey] && - throttledUrls[throttleKey] > now() - TEN_SECONDS - ) { - // URL already had a request within last 10 seconds, warn and exit - console.warn(`Request throttled for url: ${url}`) - return null - } - throttledUrls[throttleKey] = now() - return throttledUrls[throttleKey] -} - -/** - * Generic helper for constructing API queries. Automatically throttles queries - * to url to no more than once per 10 seconds. - * - * @param {string} endpoint - The API endpoint path (does not include - * '../otp/routers/router_id/') - * @param {Function} responseAction - Action to dispatch on a successful API - * response. Accepts payload object parameter. - * @param {Function} errorAction - Function to invoke on API error response. - * Accepts error object parameter. - * @param {Options} options - Any of the following optional settings: - * - rewritePayload: Function to be invoked to modify payload before being - * passed to responseAction. Accepts and returns payload object. - * - postprocess: Function to be invoked after responseAction is invoked. - * Accepts payload, dispatch, getState parameters. - * - serviceId: identifier for TransitIndex service used in - * alternateTransitIndex configuration. - * - fetchOptions: fetch options (e.g., method, body, headers). - */ -export function createQueryAction( - endpoint, - responseAction, - errorAction, - options = {} -) { - /* eslint-disable-next-line complexity */ - return async function (dispatch, getState) { - const state = getState() - const { config } = state.otp - const url = makeApiUrl(config, endpoint, options) - - if (!options.noThrottle) { - // Don't make a request to a URL that has already seen the same request - // within the last 10 seconds - if (!handleThrottlingUrl(url, options.fetchOptions)) return - } - - let payload - try { - // Need to merge headers to support graphQL POST request with an api key - const mergedHeaders = { - ...getOtpFetchOptions(state)?.headers, - ...options.fetchOptions?.headers - } - - const response = await fetch(url, { - ...getOtpFetchOptions(state), - ...options.fetchOptions, - headers: mergedHeaders - }) - - if (response.status >= 400) { - const error = new Error('Received error from server') - error.response = response - throw error - } - payload = await response.json() - } catch (err) { - return dispatch(errorAction(err)) - } - - if (typeof options.rewritePayload === 'function') { - dispatch( - responseAction(options.rewritePayload(payload, dispatch, getState)) - ) - } else { - dispatch(responseAction(payload)) - } - - if (typeof options.postprocess === 'function') { - options.postprocess(payload, dispatch, getState) - } - } -} - -/** - * Creates the URL to use for making an API request. - * - * @param {Object} config The app-wide config - * @param {string} endpoint The API endpoint path - * @param {Object} options The options object for the API request - * @return {string} The URL to use for making the http request - */ -function makeApiUrl(config, endpoint, options) { - let url - if ( - options.serviceId && - config.alternateTransitIndex && - config.alternateTransitIndex.services.includes(options.serviceId) - ) { - console.log('Using alt service for ' + options.serviceId) - url = config.alternateTransitIndex.apiRoot + endpoint - } else { - const api = config.api - - // Don't crash if no api is defined (such as in the unit test env) - if (!api?.host) return null - - url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}` - } - return url -} - -/** - * Update the browser/URL history with new parameters - * NOTE: This has not been tested for profile-based journeys. - */ -export function setUrlSearch(params, replaceCurrent = false) { - return function (dispatch, getState) { - const base = getState().router.location.pathname - const path = `${base}?${qs.stringify(params, { arrayFormat: 'repeat' })}` - if (replaceCurrent) dispatch(replace(path)) - else dispatch(push(path)) - } -} - -/** - * Update the OTP Query parameters in the URL and ensure that the active search - * is set correctly. Leaves any other existing URL parameters (e.g., UI) unchanged. - */ -export function updateOtpUrlParams(state, searchId) { - const { config, currentQuery } = state.otp - // Get updated OTP params from current query. - const otpParams = getRoutingParams(config, currentQuery, true) - return function (dispatch, getState) { - const params = {} - // Get all URL params and ensure non-routing params (UI, sessionId) remain - // unchanged. - const urlParams = getUrlParams() - Object.keys(urlParams) - // If param is non-routing, add to params to keep the same after update. - .filter((key) => key.indexOf('_') !== -1 || key === 'sessionId') - .forEach((key) => { - params[key] = urlParams[key] - }) - params.ui_activeSearch = searchId - // Assumes this is a new search and the active itinerary should be reset. - params.ui_activeItinerary = config.itinerary?.showFirstResultByDefault - ? 0 - : -1 - // Merge in the provided OTP params and update the URL. - dispatch(setUrlSearch(Object.assign(params, otpParams))) - } -} From 7b243d0a57e5f5b6767513f450962e17df9cd053 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 13 Jul 2022 16:44:31 -0700 Subject: [PATCH 0344/1425] refactor(actions/form): remove eslint disable --- lib/actions/form.js | 110 ++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/lib/actions/form.js b/lib/actions/form.js index 94e011e5a..6b96c0c20 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -1,5 +1,3 @@ -// TODO: Typescript -/* eslint-disable @typescript-eslint/no-use-before-define */ import { createAction } from 'redux-actions' import coreUtils from '@opentripplanner/core-utils' import debounce from 'lodash.debounce' @@ -117,6 +115,60 @@ export function parseUrlQueryString(params = getUrlParams(), source = null) { let debouncedPlanTrip // store as variable here, so it can be reused. let lastDebouncePlanTimeMs +/** + * Shorthand method to evaluate auto plan strategy. It is assumed that this is + * being called within the context of the `formChanged` action, so presumably + * some query param has already changed. If further checking of query params is + * needed, additional strategies should be added. + */ +const evaluateAutoPlanStrategy = ( + strategy, + fromChanged, + toChanged, + oneLocationChanged +) => { + switch (strategy) { + case 'ONE_LOCATION_CHANGED': + if (oneLocationChanged) return true + break + case 'BOTH_LOCATIONS_CHANGED': + if (fromChanged && toChanged) return true + break + case 'ANY': + return true + default: + return false + } +} + +/** + * Check if the trip should be replanned based on the auto plan strategy, + * whether the mobile view is active, and the old/new queries. Response type is + * an object containing various booleans. + */ +export function checkShouldReplanTrip(autoPlan, isMobile, oldQuery, newQuery) { + // Determine if either from/to location has changed + const fromChanged = !isEqual(oldQuery.from, newQuery.from) + const toChanged = !isEqual(oldQuery.to, newQuery.to) + const oneLocationChanged = + (fromChanged && !toChanged) || (!fromChanged && toChanged) + // Check whether a trip should be auto-replanned + const strategy = + isMobile && autoPlan?.mobile ? autoPlan?.mobile : autoPlan?.default + const shouldReplanTrip = evaluateAutoPlanStrategy( + strategy, + fromChanged, + toChanged, + oneLocationChanged + ) + return { + fromChanged, + oneLocationChanged, + shouldReplanTrip, + toChanged + } +} + /** * This action is dispatched when a change between the old query and new query * is detected. It handles checks for whether the trip should be replanned @@ -168,57 +220,3 @@ export function formChanged(oldQuery, newQuery) { } } } - -/** - * Check if the trip should be replanned based on the auto plan strategy, - * whether the mobile view is active, and the old/new queries. Response type is - * an object containing various booleans. - */ -export function checkShouldReplanTrip(autoPlan, isMobile, oldQuery, newQuery) { - // Determine if either from/to location has changed - const fromChanged = !isEqual(oldQuery.from, newQuery.from) - const toChanged = !isEqual(oldQuery.to, newQuery.to) - const oneLocationChanged = - (fromChanged && !toChanged) || (!fromChanged && toChanged) - // Check whether a trip should be auto-replanned - const strategy = - isMobile && autoPlan?.mobile ? autoPlan?.mobile : autoPlan?.default - const shouldReplanTrip = evaluateAutoPlanStrategy( - strategy, - fromChanged, - toChanged, - oneLocationChanged - ) - return { - fromChanged, - oneLocationChanged, - shouldReplanTrip, - toChanged - } -} - -/** - * Shorthand method to evaluate auto plan strategy. It is assumed that this is - * being called within the context of the `formChanged` action, so presumably - * some query param has already changed. If further checking of query params is - * needed, additional strategies should be added. - */ -const evaluateAutoPlanStrategy = ( - strategy, - fromChanged, - toChanged, - oneLocationChanged -) => { - switch (strategy) { - case 'ONE_LOCATION_CHANGED': - if (oneLocationChanged) return true - break - case 'BOTH_LOCATIONS_CHANGED': - if (fromChanged && toChanged) return true - break - case 'ANY': - return true - default: - return false - } -} From be5413e412eab008aa0e522db3dd5f97e63226f4 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 13 Jul 2022 16:45:19 -0700 Subject: [PATCH 0345/1425] refactor(actions/narrative): remove eslint disable --- lib/actions/narrative.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/actions/narrative.js b/lib/actions/narrative.js index 2a30e880d..bc0057ae9 100644 --- a/lib/actions/narrative.js +++ b/lib/actions/narrative.js @@ -1,9 +1,10 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ import { createAction } from 'redux-actions' import coreUtils from '@opentripplanner/core-utils' import { setUrlSearch } from './api' +const settingActiveitinerary = createAction('SET_ACTIVE_ITINERARY') + export function setActiveItinerary(payload) { return function (dispatch, getState) { // Trigger change in store. @@ -14,7 +15,7 @@ export function setActiveItinerary(payload) { dispatch(setUrlSearch(urlParams)) } } -const settingActiveitinerary = createAction('SET_ACTIVE_ITINERARY') + export const setActiveLeg = createAction('SET_ACTIVE_LEG') export const setActiveStep = createAction('SET_ACTIVE_STEP') // Set itinerary visible on map. This is used for mouse over effects with From aa7702c02d9f81e986f653a69dd896fc789f8653 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 13 Jul 2022 16:48:24 -0700 Subject: [PATCH 0346/1425] refactor(components/app-menu): remove eslint ignore --- lib/components/app/app-menu.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/components/app/app-menu.tsx b/lib/components/app/app-menu.tsx index c87eae577..f1f8ae671 100644 --- a/lib/components/app/app-menu.tsx +++ b/lib/components/app/app-menu.tsx @@ -1,18 +1,13 @@ -/* eslint-disable react/jsx-handler-names */ import { connect } from 'react-redux' import { FormattedMessage, injectIntl, useIntl } from 'react-intl' -import React, { Component, Fragment } from 'react' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import { MenuItem } from 'react-bootstrap' import { withRouter } from 'react-router' import qs from 'qs' +import React, { Component, Fragment } from 'react' import SlidingPane from 'react-sliding-pane' import type { RouteComponentProps } from 'react-router' import type { WrappedComponentProps } from 'react-intl' -// No types available, old package -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore +// @ts-expect-error Velocity-React not typescripted import VelocityTransitionGroup from 'velocity-react/velocity-transition-group' import * as callTakerActions from '../../actions/call-taker' From 6e7fcc612a16089546e6173754961d3b103f69e3 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 14 Jul 2022 11:32:26 +0200 Subject: [PATCH 0347/1425] fix(field trip): correctly render class size badge --- .../viewers/__snapshots__/stop-viewer.js.snap | 86 +++++++++---------- .../admin/field-trip-itinerary-group-size.js | 13 --- .../admin/field-trip-itinerary-group-size.tsx | 22 +++++ .../narrative/default/default-itinerary.js | 2 +- 4 files changed, 66 insertions(+), 57 deletions(-) delete mode 100644 lib/components/admin/field-trip-itinerary-group-size.js create mode 100644 lib/components/admin/field-trip-itinerary-group-size.tsx diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 273e9b469..8c838a55e 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -566,13 +566,13 @@ exports[`components > viewers > stop viewer should render countdown times after name="calendar" > @@ -779,7 +779,7 @@ exports[`components > viewers > stop viewer should render countdown times after
    viewers > stop viewer should render countdown times after color="333333" >
    20 @@ -1514,7 +1514,7 @@ exports[`components > viewers > stop viewer should render countdown times after margin={0.25} > viewers > stop viewer should render countdown times after name="refresh" > @@ -2369,7 +2369,7 @@ exports[`components > viewers > stop viewer should render countdown times after >
    P
    @@ -2768,13 +2768,13 @@ exports[`components > viewers > stop viewer should render countdown times for st name="calendar" > @@ -2981,7 +2981,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
    viewers > stop viewer should render countdown times for st color="333333" >
    20 @@ -3437,7 +3437,7 @@ exports[`components > viewers > stop viewer should render countdown times for st margin={0.25} > viewers > stop viewer should render countdown times for st name="refresh" > @@ -3896,7 +3896,7 @@ exports[`components > viewers > stop viewer should render countdown times for st >
    P
    @@ -4493,13 +4493,13 @@ exports[`components > viewers > stop viewer should render times after midnight w name="calendar" > @@ -4706,7 +4706,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
    viewers > stop viewer should render times after midnight w color="333333" >
    20 @@ -5474,7 +5474,7 @@ exports[`components > viewers > stop viewer should render times after midnight w margin={0.25} > viewers > stop viewer should render times after midnight w name="refresh" > @@ -6329,7 +6329,7 @@ exports[`components > viewers > stop viewer should render times after midnight w >
    P
    @@ -7442,13 +7442,13 @@ exports[`components > viewers > stop viewer should render with OTP transit index name="calendar" > @@ -7655,7 +7655,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
    viewers > stop viewer should render with OTP transit index color="333333" >
    20 @@ -8894,7 +8894,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    36 @@ -9341,7 +9341,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -9896,7 +9896,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -10126,7 +10126,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index margin={0.25} > viewers > stop viewer should render with OTP transit index name="refresh" > @@ -12013,7 +12013,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index >
    P
    @@ -13116,13 +13116,13 @@ exports[`components > viewers > stop viewer should render with TriMet transit in name="calendar" > @@ -13329,7 +13329,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
    viewers > stop viewer should render with TriMet transit in color="333333" >
    20 @@ -14350,7 +14350,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in margin={0.25} > viewers > stop viewer should render with TriMet transit in name="refresh" > @@ -16217,7 +16217,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in >
    P
    diff --git a/lib/components/admin/field-trip-itinerary-group-size.js b/lib/components/admin/field-trip-itinerary-group-size.js deleted file mode 100644 index 40d7bfd18..000000000 --- a/lib/components/admin/field-trip-itinerary-group-size.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import { Badge } from 'react-bootstrap' - -import Icon from '../util/icon' - -export default function FieldTripGroupSize ({ itinerary }) { - return itinerary.fieldTripGroupSize > 0 && ( - - - {itinerary.fieldTripGroupSize} - - ) -} diff --git a/lib/components/admin/field-trip-itinerary-group-size.tsx b/lib/components/admin/field-trip-itinerary-group-size.tsx new file mode 100644 index 000000000..bfc90562d --- /dev/null +++ b/lib/components/admin/field-trip-itinerary-group-size.tsx @@ -0,0 +1,22 @@ +import { Badge } from 'react-bootstrap' +import { Itinerary } from '@opentripplanner/types' +import React from 'react' + +import Icon from '../util/icon' + +type Props = { + itinerary: Itinerary & { fieldTripGroupSize: number } +} + +export default function FieldTripGroupSize({ + itinerary +}: Props): React.ReactNode { + if (!itinerary.fieldTripGroupSize || itinerary.fieldTripGroupSize <= 0) + return null + return ( + + + {itinerary.fieldTripGroupSize} + + ) +} diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 68711b6ac..27536ed74 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -243,6 +243,7 @@ class DefaultItinerary extends NarrativeItinerary { /> )}
      + {ITINERARY_ATTRIBUTES.sort((a, b) => { const aSelected = this._isSortingOnAttribute(a) const bSelected = this._isSortingOnAttribute(b) @@ -270,7 +271,6 @@ class DefaultItinerary extends NarrativeItinerary { ) })}
    - {active && !expanded && ( From 835776e2adbe3f469fd77b626bb420b939266b4f Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 14 Jul 2022 11:39:40 +0200 Subject: [PATCH 0348/1425] chore: revert unneeded snapshot changes --- .../viewers/__snapshots__/stop-viewer.js.snap | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 8c838a55e..273e9b469 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -566,13 +566,13 @@ exports[`components > viewers > stop viewer should render countdown times after name="calendar" > @@ -779,7 +779,7 @@ exports[`components > viewers > stop viewer should render countdown times after
    viewers > stop viewer should render countdown times after color="333333" >
    20 @@ -1514,7 +1514,7 @@ exports[`components > viewers > stop viewer should render countdown times after margin={0.25} > viewers > stop viewer should render countdown times after name="refresh" > @@ -2369,7 +2369,7 @@ exports[`components > viewers > stop viewer should render countdown times after >
    P
    @@ -2768,13 +2768,13 @@ exports[`components > viewers > stop viewer should render countdown times for st name="calendar" > @@ -2981,7 +2981,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
    viewers > stop viewer should render countdown times for st color="333333" >
    20 @@ -3437,7 +3437,7 @@ exports[`components > viewers > stop viewer should render countdown times for st margin={0.25} > viewers > stop viewer should render countdown times for st name="refresh" > @@ -3896,7 +3896,7 @@ exports[`components > viewers > stop viewer should render countdown times for st >
    P
    @@ -4493,13 +4493,13 @@ exports[`components > viewers > stop viewer should render times after midnight w name="calendar" > @@ -4706,7 +4706,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
    viewers > stop viewer should render times after midnight w color="333333" >
    20 @@ -5474,7 +5474,7 @@ exports[`components > viewers > stop viewer should render times after midnight w margin={0.25} > viewers > stop viewer should render times after midnight w name="refresh" > @@ -6329,7 +6329,7 @@ exports[`components > viewers > stop viewer should render times after midnight w >
    P
    @@ -7442,13 +7442,13 @@ exports[`components > viewers > stop viewer should render with OTP transit index name="calendar" > @@ -7655,7 +7655,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
    viewers > stop viewer should render with OTP transit index color="333333" >
    20 @@ -8894,7 +8894,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    36 @@ -9341,7 +9341,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -9896,7 +9896,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index color="333333" >
    94 @@ -10126,7 +10126,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index margin={0.25} > viewers > stop viewer should render with OTP transit index name="refresh" > @@ -12013,7 +12013,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index >
    P
    @@ -13116,13 +13116,13 @@ exports[`components > viewers > stop viewer should render with TriMet transit in name="calendar" > @@ -13329,7 +13329,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
    viewers > stop viewer should render with TriMet transit in color="333333" >
    20 @@ -14350,7 +14350,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in margin={0.25} > viewers > stop viewer should render with TriMet transit in name="refresh" > @@ -16217,7 +16217,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in >
    P
    From d925b37a8b90fb8fa9f01b4ed4a45a4e59c22959 Mon Sep 17 00:00:00 2001 From: caleb-diehl-ibigroup Date: Thu, 14 Jul 2022 08:13:05 -0700 Subject: [PATCH 0349/1425] refactor park and ride overlay to functional component --- .../map/connected-park-and-ride-overlay.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/components/map/connected-park-and-ride-overlay.tsx b/lib/components/map/connected-park-and-ride-overlay.tsx index 5b8d80619..ad5441e8e 100644 --- a/lib/components/map/connected-park-and-ride-overlay.tsx +++ b/lib/components/map/connected-park-and-ride-overlay.tsx @@ -1,6 +1,6 @@ import { connect } from 'react-redux' import ParkAndRideOverlay from '@opentripplanner/park-and-ride-overlay' -import React, { Component } from 'react' +import React, { Component, useEffect } from 'react' import { parkAndRideQuery } from '../../actions/api' import { setLocation } from '../../actions/map' @@ -10,23 +10,18 @@ type ParkAndRideParams = { // FIXME: properly type } -class ConnectedParkAndRideOverlay extends Component< - { parkAndRideQuery: (params: ParkAndRideParams) => void } & ParkAndRideParams -> { - componentDidMount() { +// rewrote this as a functional component, still need to add more Typescript +function ConnectedParkAndRideOverlay(props: any): JSX.Element { + useEffect(() => { const params: ParkAndRideParams = {} - if (this.props.maxTransitDistance) { - params.maxTransitDistance = this.props.maxTransitDistance + if (props.maxTransitDistance) { + params.maxTransitDistance = props.maxTransitDistance } - // TODO: support config-defined bounding envelope - this.props.parkAndRideQuery(params) - } + props.parkAndRideQuery(params) + }, []) - render() { - // @ts-expect-error TODO: re-write this component as a functional component and properly type - return - } + return } // connect to the redux store From b9c3ce1a27a4c1e546a2f3a1063a4aad775aeadf Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 14 Jul 2022 11:05:21 -0700 Subject: [PATCH 0350/1425] refactor(components): remove eslint disables --- lib/components/admin/field-trip-details.js | 1 - lib/components/form/mode-buttons.tsx | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 0d55ec26d..24d7fcff8 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-handler-names */ /* eslint-disable react/prop-types */ import { connect } from 'react-redux' import { DropdownButton, MenuItem } from 'react-bootstrap' diff --git a/lib/components/form/mode-buttons.tsx b/lib/components/form/mode-buttons.tsx index 1d987c4e9..49049b9db 100644 --- a/lib/components/form/mode-buttons.tsx +++ b/lib/components/form/mode-buttons.tsx @@ -38,9 +38,8 @@ const ModeButton = ({ onClick: (mode: string) => void selected: boolean }): JSX.Element => { - // FIXME: type context - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // FIXME: add types to context + // @ts-expect-error No type on ComponentContext const { ModeIcon } = useContext(ComponentContext) const { icon, label, mode } = item const overlayTooltip = ( From dc30a87dc5e6e9742fb021666a96e6a38d261b7f Mon Sep 17 00:00:00 2001 From: caleb-diehl-ibigroup Date: Thu, 14 Jul 2022 11:22:59 -0700 Subject: [PATCH 0351/1425] stop viewer icon enhancements --- .../map/connected-stop-viewer-overlay.js | 15 +++++-- lib/components/map/enhanced-stop-marker.js | 43 +++++++++---------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/lib/components/map/connected-stop-viewer-overlay.js b/lib/components/map/connected-stop-viewer-overlay.js index f2ab83834..eb6e6c348 100644 --- a/lib/components/map/connected-stop-viewer-overlay.js +++ b/lib/components/map/connected-stop-viewer-overlay.js @@ -13,8 +13,13 @@ import EnhancedStopMarker from './enhanced-stop-marker' /** * An overlay to view a collection of stops. */ -const StopViewerOverlay = (props) => { - const { configCompanies, mapConfig, setLocation, stopData, stops } = props +const StopViewerOverlay = ({ + configCompanies, + mapConfig, + setLocation, + stopData, + stops +}) => { const { mainMap } = useMap() useEffect(() => { @@ -84,8 +89,10 @@ const mapStateToProps = (state) => { const stopData = stopLookup[state.otp.ui.viewedStop?.stopId] const nearbyStops = stopData?.nearbyStops?.map((stopId) => stopLookup[stopId]) const stops = [] - if (stopData) stops.push(stopData) - if (nearbyStops && nearbyStops.length > 0) stops.push(...nearbyStops) + if (nearbyStops && nearbyStops.length > 0 && stopData) { + stops.push(...nearbyStops) + stops.push(stopData) + } return { configCompanies: state.otp.config.companies, mapConfig: state.otp.config.map, diff --git a/lib/components/map/enhanced-stop-marker.js b/lib/components/map/enhanced-stop-marker.js index 60cdfb14f..1b9699971 100644 --- a/lib/components/map/enhanced-stop-marker.js +++ b/lib/components/map/enhanced-stop-marker.js @@ -1,5 +1,6 @@ // TYPESCRIPT TODO: all props here are missing types /* eslint-disable react/prop-types */ +// Modecolors is never getting passed to component, so it's always the same color import { Styled as BaseMapStyled, MarkerWithPopup @@ -23,22 +24,22 @@ const { ViewStopButton } = StopsOverlayStyled const getComplementaryColor = (color) => color.isLight() ? color.darken(30) : color.lighten(40) -const caretPixels = 24 +const caretPixels = 18 const iconPixels = 32 -const iconPadding = 8 -const borderPixels = (props) => (props?.thick ? 3 : 1) -const caretVisibleHeight = caretPixels / 1.4142 // Math.sqrt(2) +const iconPadding = 5 +const borderPixels = (props) => (props?.active ? 3 : 1) const caretMarginPixels = (props) => (iconPixels - caretPixels - borderPixels(props)) / 2 -const bubblePixels = (props) => - iconPixels + iconPadding + 2 * borderPixels(props) + +const defaultColor = '#a6a6a6' const BaseStopIcon = styled.div` - background: ${(props) => props.mainColor}; - border-radius: 5px; - border: ${borderPixels}px solid ${(props) => props.secondaryColor}; - color: ${(props) => props.secondaryColor}; - fill: ${(props) => props.secondaryColor}; + background: #fff; + border-radius: 50%; + border: ${borderPixels}px solid + ${(props) => (props?.active ? props.mainColor : defaultColor)}; + color: ${(props) => (props?.active ? props.mainColor : defaultColor)}; + fill: ${(props) => (props?.active ? props.mainColor : defaultColor)}; font-size: ${iconPixels}px; height: ${iconPixels}px; line-height: 1px; @@ -51,15 +52,11 @@ const BaseStopIcon = styled.div` } &::after { - background: linear-gradient( - to bottom right, - transparent 0%, - transparent 40%, - ${(props) => props.mainColor} 40%, - ${(props) => props.mainColor} 100% - ); - border-bottom: ${borderPixels}px solid ${(props) => props.secondaryColor}; - border-right: ${borderPixels}px solid ${(props) => props.secondaryColor}; + background: #fff; + border-bottom: ${borderPixels}px solid + ${(props) => (props?.active ? props.mainColor : defaultColor)}; + border-right: ${borderPixels}px solid + ${(props) => (props?.active ? props.mainColor : defaultColor)}; content: ''; display: block; height: ${caretPixels}px; @@ -107,10 +104,11 @@ class EnhancedStopMarker extends Component { if (!stopCode) return null const mode = getModeFromStop(stop) - let color = modeColors && modeColors[mode] ? modeColors[mode] : 'pink' + let color = modeColors && modeColors[mode] ? modeColors[mode] : '#121212' if (highlight) { // Generates a pretty variant of the color color = tinycolor(color).monochromatic()[3].toHexString() + console.log(color) } const { ModeIcon } = this.context @@ -125,7 +123,6 @@ class EnhancedStopMarker extends Component { active={id === activeStopId} mainColor={color} secondaryColor={getComplementaryColor(tinycolor(color))} - thick={activeStopId === id} // Show actual stop name on hover for easy identification. title={getStopName(stop)} > @@ -135,7 +132,7 @@ class EnhancedStopMarker extends Component { return ( From f4cffb4acc9823ae2a5598d112dfd6c59bb8717b Mon Sep 17 00:00:00 2001 From: caleb-diehl-ibigroup Date: Thu, 14 Jul 2022 11:31:32 -0700 Subject: [PATCH 0352/1425] stop viewer icon enhancements --- lib/components/app/print-layout.tsx | 1 + .../map/connected-route-viewer-overlay.js | 5 ++-- lib/components/map/default-map.js | 1 - yarn.lock | 24 +++++++++++++++++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/components/app/print-layout.tsx b/lib/components/app/print-layout.tsx index aa208fca3..6d2d2ec48 100644 --- a/lib/components/app/print-layout.tsx +++ b/lib/components/app/print-layout.tsx @@ -29,6 +29,7 @@ type Props = { parseUrlQueryString: (params?: any, source?: string) => any setMapCenter: ({ lat, lon }: { lat: number; lon: number }) => void } + type State = { mapVisible?: boolean } diff --git a/lib/components/map/connected-route-viewer-overlay.js b/lib/components/map/connected-route-viewer-overlay.js index a11101a22..93ce7ca4d 100644 --- a/lib/components/map/connected-route-viewer-overlay.js +++ b/lib/components/map/connected-route-viewer-overlay.js @@ -26,6 +26,7 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = {} +// Taking this out doesn't break anything +// const mapDispatchToProps = {} -export default connect(mapStateToProps, mapDispatchToProps)(RouteViewerOverlay) +export default connect(mapStateToProps)(RouteViewerOverlay) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 8fc0e1057..0b577caa6 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -226,7 +226,6 @@ class DefaultMap extends Component { mapConfig, mapPopupLocation, setMapCenter, - setMapZoom, vehicleRentalQuery, vehicleRentalStations } = this.props diff --git a/yarn.lock b/yarn.lock index 8079bf3e9..dc173894d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2486,7 +2486,7 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/humanize-distance/-/humanize-distance-1.2.0.tgz#71cf5d5d1b756adef15300edbba0995ccd4b35ee" integrity sha512-x0QRXMDhypFeazZ6r6vzrdU8vhiV56nZ/WX6zUbxpgp6T9Oclw0gwR2Zdw6DZiiFpSYVNeVNxVzZwsu6NRGjcA== -"@opentripplanner/icons@^1.2.2": +"@opentripplanner/icons@1.2.2", "@opentripplanner/icons@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@opentripplanner/icons/-/icons-1.2.2.tgz#18c93ce34afe2d83a2f0fc6ecea85aa587d14366" integrity sha512-SwHAGyayHAPSVKIVbY1mNXq+ZLA1If61onE0mATKh+sifKnKpjRWgHeEHDpeZu6Dz0mIXnY82tcNUvArjeSxig== @@ -2526,6 +2526,22 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" +"@opentripplanner/itinerary-body@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.1.1.tgz#854e83022de09cb023e7f96ae38bb6e625db82b5" + integrity sha512-fGKu7xSHok5FcKK5lEHtintHiEpcsUm1NE35ZZHRW7b5ejqBedq8/Be4Fo5WocEWZz9MWb1yyG2YqctceBYofw== + dependencies: + "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/humanize-distance" "^1.2.0" + "@opentripplanner/icons" "^1.2.2" + "@opentripplanner/location-icon" "^1.4.0" + "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" + flat "^5.0.2" + moment "^2.24.0" + react-resize-detector "^4.2.1" + velocity-react "^1.4.3" + "@opentripplanner/location-field@1.12.3": version "1.12.3" resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.12.3.tgz#0ff027f941e45880e01470ed88a044e4fda19500" @@ -2586,6 +2602,7 @@ dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/types" "^2.0.0" "@opentripplanner/stops-overlay@../otp-ui/packages/stops-overlay": version "4.0.0" @@ -2598,14 +2615,17 @@ version "2.4.0" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" + "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/icons" "1.2.2" flat "^5.0.2" "@opentripplanner/transitive-overlay@../otp-ui/packages/transitive-overlay": - version "1.1.5" + version "2.0.0" dependencies: "@mapbox/polyline" "^1.1.1" "@opentripplanner/base-map" "^3.0.0-alpha.1" "@opentripplanner/core-utils" "^5.0.1" + "@opentripplanner/itinerary-body" "^3.1.1" "@turf/bbox" "^6.5.0" lodash.isequal "^4.5.0" transitive-js "^0.14.1" From f3caf710f13b6c95deb33d2d0790796b5ddd449b Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 14 Jul 2022 15:21:23 -0700 Subject: [PATCH 0353/1425] refactor(narrative): finish fixing co2 from merge --- .../narrative/narrative-itineraries.js | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 4d367691d..069d22b86 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -306,8 +306,22 @@ class NarrativeItineraries extends Component { return updatedItineraries }, []) + const baselineCo2 = this._getBaselineCo2() + const itinerariesWithCo2 = + mergedItineraries?.map((itin) => { + const emissions = coreUtils.itinerary.calculateEmissions( + itin, + co2Config?.carbonIntensity + ) + return { + ...itin, + co2: emissions, + co2VsBaseline: (emissions - baselineCo2) / baselineCo2 + } + }) || [] + // This loop determines if an itinerary uses a single or multiple modes - const groupedMergedItineraries = mergedItineraries.reduce( + const groupedMergedItineraries = itinerariesWithCo2.reduce( (prev, cur) => { // Create a clone of our buckets const modeItinMap = clone(prev) @@ -335,21 +349,6 @@ class NarrativeItineraries extends Component { { multi: {}, single: {} } ) - const baselineCo2 = this._getBaselineCo2() - - const itinerariesWithCo2 = - mergedItineraries?.map((itin) => { - const emissions = coreUtils.itinerary.calculateEmissions( - itin, - co2Config?.carbonIntensity - ) - return { - ...itin, - co2: emissions, - co2VsBaseline: (emissions - baselineCo2) / baselineCo2 - } - }) || [] - return ( ) }) - : mergedItineraries.map((itin) => + : itinerariesWithCo2.map((itin) => this._renderItineraryRow(itin) )} {this._renderLoadingDivs()} From 7c5bec68bf5fc6a0a8cd687e2ffff44a62cf1a78 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Thu, 14 Jul 2022 15:50:32 -0700 Subject: [PATCH 0354/1425] feat(metro ui): add co2 note --- .../narrative/metro/metro-itinerary.tsx | 33 +++++++++++++++++++ .../narrative/metro/route-block.tsx | 1 + 2 files changed, 34 insertions(+) diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx index 68d549160..5cdb0829b 100644 --- a/lib/components/narrative/metro/metro-itinerary.tsx +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -77,6 +77,17 @@ const SecondaryInfo = styled.span` const Spacer = styled.span`` +const ItineraryNote = styled.div` + background: mediumseagreen; + color: white; + padding: 4px 8px; + text-align: right; +` + +const ItineraryNoteIcon = styled(Icon)` + margin: 0 4px; +` + const Routes = styled.section<{ enableDot?: boolean }>` display: flex; flex-wrap: wrap; @@ -232,10 +243,12 @@ class MetroItinerary extends NarrativeItinerary { showRealtimeAnnotation, timeFormat } = this.props + const timeOptions = { format: timeFormat, offset: coreUtils.itinerary.getTimeZoneOffset(itinerary) } + const { isCallAhead, isContinuousDropoff, isFlexItinerary, phone } = getFlexAttirbutes(itinerary) @@ -245,6 +258,25 @@ class MetroItinerary extends NarrativeItinerary { currency ) + const co2VsBaseline = Math.round(itinerary.co2VsBaseline * 100) + const emissionsNote = !mini && co2VsBaseline < -20 && ( + <> + + {' '} + 0 + }} + /> + + ) + const firstTransitStop = getFirstTransitLegStop(itinerary) const renderRouteBlocks = (legs: Leg[], firstOnly = false) => { @@ -313,6 +345,7 @@ class MetroItinerary extends NarrativeItinerary { )} className={`itin-wrapper${mini ? '-small' : ''}`} > + {emissionsNote && {emissionsNote}} {itineraryHasAccessibilityScores(itinerary) && ( Date: Thu, 14 Jul 2022 16:10:28 -0700 Subject: [PATCH 0355/1425] route viewer styling changes --- lib/components/viewers/RouteRow.js | 28 ++++++++++++++++++---------- lib/components/viewers/viewers.css | 5 ++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/components/viewers/RouteRow.js b/lib/components/viewers/RouteRow.js index bf2cc8cdf..20d0d3a35 100644 --- a/lib/components/viewers/RouteRow.js +++ b/lib/components/viewers/RouteRow.js @@ -2,7 +2,7 @@ /* eslint-disable react/prop-types */ import { Button, Label } from 'react-bootstrap' import { VelocityTransitionGroup } from 'velocity-react' -import React, { PureComponent } from 'react' +import React, { Fragment, PureComponent } from 'react' import styled from 'styled-components' import { ComponentContext } from '../../util/contexts' @@ -13,7 +13,7 @@ import RouteDetails from './route-details' export const StyledRouteRow = styled.div` background-color: white; - border-bottom: 1px solid gray; + margin-bottom: 10px; ` export const RouteRowButton = styled(Button)` @@ -44,7 +44,7 @@ const RouteNameElement = styled(Label)` : props.backgroundColor}; color: ${(props) => props.color}; flex: 0 1 auto; - font-size: medium; + font-size: 16px; font-weight: 400; margin-left: ${(props) => props.backgroundColor === '#ffffff' || props.backgroundColor === 'white' @@ -54,6 +54,11 @@ const RouteNameElement = styled(Label)` overflow: hidden; text-overflow: ellipsis; ` +const RouteLongNameElement = styled.span` + font-size: 16px; + margin-left: 5px; + font-weight: 500; +` export const RouteName = ({ operator, route }) => { const { backgroundColor, color, longName } = getColorAndNameFromRoute( @@ -61,13 +66,16 @@ export const RouteName = ({ operator, route }) => { route ) return ( - - {route.shortName} {longName} - + <> + + {route.shortName} + + {longName} + ) } diff --git a/lib/components/viewers/viewers.css b/lib/components/viewers/viewers.css index 0969a541a..01f66d369 100644 --- a/lib/components/viewers/viewers.css +++ b/lib/components/viewers/viewers.css @@ -3,8 +3,11 @@ .otp .route-viewer-header, .otp .stop-viewer-header, .otp .trip-viewer-header { - background-color: #ddd; + background-color: #FFF; padding: 12px; + border: 1px solid #a6a6a6; + margin: 10px; + border-radius: 10px; } .otp .route-viewer, From 72b2039182e2482911df7fc79ac8a8e610e6e7fd Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 15 Jul 2022 11:09:09 -0700 Subject: [PATCH 0356/1425] refactor(itinerary): add fr, co2 subscript --- i18n/en-US.yml | 4 ++-- i18n/fr.yml | 6 ++++++ lib/components/narrative/default/default-itinerary.js | 4 +++- lib/components/narrative/metro/metro-itinerary.tsx | 4 +++- lib/components/util/sub-text.js | 3 +++ 5 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 lib/components/util/sub-text.js diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 8ff6105c9..ffa06b66e 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -677,10 +677,10 @@ common: transfers: "{transfers, plural, =0 {} one {# transfer} other {# transfers}}" # This string will have a formatted number before it, including units. relativeCo2: > - {isMore, select, + {isMore, select, true {more} other {less} - } CO2 than driving alone. + } CO2 than driving alone. # Note to translator: the strings below are used in sentences such as: # "No trip found for bike, walk, and transit." diff --git a/i18n/fr.yml b/i18n/fr.yml index c80cde446..a55b8d5d4 100644 --- a/i18n/fr.yml +++ b/i18n/fr.yml @@ -653,6 +653,12 @@ common: # Note to translator: noTransitFareProvided is width-constrained. noTransitFareProvided: Tarif inconnu transfers: "{transfers, plural, =0 {} one {# correspondance} other {# correspondances}}" + relativeCo2: > + de CO2 en + {isMore, select, + true {plus} + other {moins} + } qu'en voiture. # Note to translator: some of the strings below are used in sentences such as: # "No trip found for bike, walk, and transit." diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index 3e025ad42..2f69a2712 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -28,6 +28,7 @@ import SimpleRealtimeAnnotation from '../simple-realtime-annotation' import { FlexIndicator } from './flex-indicator' import { ItineraryDescription } from './itinerary-description' import ItinerarySummary from './itinerary-summary' +import Sub from '../../util/sub-text' const { isContinuousDropoff, isFlex, isReservationRequired } = coreUtils.itinerary @@ -154,7 +155,8 @@ const ITINERARY_ATTRIBUTES = [ 0 + isMore: co2VsBaseline > 0, + sub: Sub }} /> diff --git a/lib/components/narrative/metro/metro-itinerary.tsx b/lib/components/narrative/metro/metro-itinerary.tsx index 5cdb0829b..1cee03d36 100644 --- a/lib/components/narrative/metro/metro-itinerary.tsx +++ b/lib/components/narrative/metro/metro-itinerary.tsx @@ -35,6 +35,7 @@ import { removeInsignifigantWalkLegs } from './attribute-utils' import RouteBlock from './route-block' +import Sub from '../../util/sub-text' const { ItineraryView } = uiActions @@ -271,7 +272,8 @@ class MetroItinerary extends NarrativeItinerary { 0 + isMore: co2VsBaseline > 0, + sub: Sub }} /> diff --git a/lib/components/util/sub-text.js b/lib/components/util/sub-text.js new file mode 100644 index 000000000..09829bf4f --- /dev/null +++ b/lib/components/util/sub-text.js @@ -0,0 +1,3 @@ +import React from 'react' +const Sub = (contents) => {contents} +export default Sub From 9ade24ae702bcb9fb6ebf586261f154147e36663 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:13:48 -0400 Subject: [PATCH 0357/1425] refactor(FormattedCalendarString): Remove momentjs reference --- lib/components/form/date-time-preview.js | 2 +- ...tring.js => formatted-calendar-string.tsx} | 33 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) rename lib/components/util/{formatted-calendar-string.js => formatted-calendar-string.tsx} (58%) diff --git a/lib/components/form/date-time-preview.js b/lib/components/form/date-time-preview.js index cb0cf86ab..c2d87fbb8 100644 --- a/lib/components/form/date-time-preview.js +++ b/lib/components/form/date-time-preview.js @@ -63,7 +63,7 @@ class DateTimePreview extends Component { const summary = (
    - +
    { - const previewDate = moment(date, OTP_API_DATE_FORMAT) - const today = moment().startOf('day') - const compareDate = previewDate.clone().startOf('day') - const days = compareDate.diff(today, 'days') +const FormattedCalendarString = ({ date, timeZone }: Props): ReactElement => { + // Dates are expressed in the agency's timezone. + const today = toDate(getCurrentDate(timeZone), { timeZone }) + const compareDate = toDate(date, { timeZone }) + const days = differenceInDays(compareDate, today) + const dayId = format(compareDate, 'eeee').toLowerCase() - // Mimic moment.calendar logic (translation strings must come from language files) if (days > -7 && days < -1) { - const dayId = previewDate.format('dddd').toLowerCase() const formattedDayOfWeek = return ( { } else if (days === 1) { return } else if (days > 1 && days < 7) { - const dayId = previewDate.format('dddd').toLowerCase() return } else { - return + return } } -FormattedCalendarString.propTypes = { - date: PropTypes.string -} - export default FormattedCalendarString From 04b7610778fceb76efb38a438ce96fb8b3e9b121 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:18:17 -0400 Subject: [PATCH 0358/1425] refactor(FormattedTimeRange): Delete unused file. --- lib/components/util/formatted-time-range.tsx | 25 -------------------- 1 file changed, 25 deletions(-) delete mode 100644 lib/components/util/formatted-time-range.tsx diff --git a/lib/components/util/formatted-time-range.tsx b/lib/components/util/formatted-time-range.tsx deleted file mode 100644 index 28c3a547a..000000000 --- a/lib/components/util/formatted-time-range.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { FormattedMessage } from 'react-intl' -import moment from 'moment-timezone' -import React from 'react' - -/** - * Renders a time range e.g. 3:45pm-4:15pm according to the - * react-intl default time format for the ambient locale. - */ -export default function FormattedTimeRange({ - endTime, - startTime -}: { - endTime: number - startTime: number -}): JSX.Element { - return ( - - ) -} From c0d8c130ae008e37438816e16010a9298f57f0dc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:35:09 -0400 Subject: [PATCH 0359/1425] refactor(FormattedDuration): Remove reference to moment. --- lib/components/util/formatted-duration.tsx | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/components/util/formatted-duration.tsx b/lib/components/util/formatted-duration.tsx index ece82639b..21f445252 100644 --- a/lib/components/util/formatted-duration.tsx +++ b/lib/components/util/formatted-duration.tsx @@ -1,17 +1,8 @@ import { FormattedMessage, IntlShape } from 'react-intl' -import moment from 'moment-timezone' -import PropTypes from 'prop-types' +import coreUtils from '@opentripplanner/core-utils' import React from 'react' -/** - * Helper function to split out hours and minutes - */ -const splitDuration = ( - duration: number -): { hours: number; minutes: number } => { - const dur = moment.duration(duration, 'seconds') - return { hours: dur.hours(), minutes: dur.minutes() } -} +const { toHoursMinutesSeconds } = coreUtils.time /** * Formats the given duration according to the selected locale. @@ -21,7 +12,7 @@ export default function FormattedDuration({ }: { duration: number }): JSX.Element { - const { hours, minutes } = splitDuration(duration) + const { hours, minutes } = toHoursMinutesSeconds(duration) return ( { - const { hours, minutes } = splitDuration(duration) + const { hours, minutes } = toHoursMinutesSeconds(duration) return intl.formatMessage( { id: 'common.time.tripDurationFormat' }, { @@ -45,7 +36,3 @@ const formatDuration = (duration: number, intl: IntlShape): string => { ) } export { formatDuration } - -FormattedDuration.propTypes = { - duration: PropTypes.number.isRequired -} From 3aec73f196276f6156f0da579fbc033c20123014 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 16:00:20 -0400 Subject: [PATCH 0360/1425] refactor(monitored-trip): Fix FormattedDuration markup. --- .../active-trip-renderer.js | 66 +++++++++++-------- .../base-renderer.js | 10 +-- .../upcoming-trip-renderer.js | 4 +- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-status-rendering-strategies/active-trip-renderer.js b/lib/components/user/monitored-trip/trip-status-rendering-strategies/active-trip-renderer.js index 7745c9bb9..7d4fb39ee 100644 --- a/lib/components/user/monitored-trip/trip-status-rendering-strategies/active-trip-renderer.js +++ b/lib/components/user/monitored-trip/trip-status-rendering-strategies/active-trip-renderer.js @@ -1,5 +1,5 @@ -import React from 'react' import { FormattedMessage } from 'react-intl' +import React from 'react' import { getTripStatus, REALTIME_STATUS } from '../../../../util/viewer' import FormattedDuration from '../../../util/formatted-duration' @@ -9,7 +9,7 @@ import baseRenderer from './base-renderer' /** * Calculates various data for monitored trips that are currently active. */ -export default function activeTripRenderer ({ +export default function activeTripRenderer({ monitoredTrip, onTimeThresholdSeconds }) { @@ -22,10 +22,10 @@ export default function activeTripRenderer ({ if (data.journeyState.hasRealtimeData) { // calculate the deviation from the scheduled arrival time (positive // value indicates delay) - const arrivalDeviationSeconds = ( - data.matchingItinerary.endTime - - data.journeyState.scheduledArrivalTimeEpochMillis - ) / 1000 + const arrivalDeviationSeconds = + (data.matchingItinerary.endTime - + data.journeyState.scheduledArrivalTimeEpochMillis) / + 1000 const tripStatus = getTripStatus( true, arrivalDeviationSeconds, @@ -34,39 +34,49 @@ export default function activeTripRenderer ({ if (tripStatus === REALTIME_STATUS.ON_TIME) { // about on time data.panelBsStyle = 'success' - data.headingText = + data.headingText = ( + + ) } else if (tripStatus === REALTIME_STATUS.LATE) { // delayed data.panelBsStyle = 'warning' - data.headingText = - }} - /> + data.headingText = ( + + ) + }} + /> + ) } else { // early data.panelBsStyle = 'warning' - data.headingText = - }} - /> + data.headingText = ( + + ) + }} + /> + ) } } else { data.panelBsStyle = 'info' - data.headingText = + data.headingText = ( + + ) } - data.bodyText = + data.bodyText = ( + + ) data.shouldRenderTogglePauseTripButton = true data.shouldRenderToggleSnoozeTripButton = true diff --git a/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js b/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js index 6798f8a0b..6d3735697 100644 --- a/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js +++ b/lib/components/user/monitored-trip/trip-status-rendering-strategies/base-renderer.js @@ -1,5 +1,5 @@ +import { differenceInSeconds } from 'date-fns' import { FormattedMessage } from 'react-intl' -import moment from 'moment' import React from 'react' import FormattedDuration from '../../../util/formatted-duration' @@ -31,16 +31,16 @@ export default function baseRenderer(monitoredTrip) { // set the last checked text if the journey state exists if (data.journeyState) { - const secondsSinceLastCheck = moment().diff( - moment(data.journeyState.lastCheckedEpochMillis), - 'seconds' + const secondsSinceLastCheck = differenceInSeconds( + new Date(), + new Date(data.journeyState.lastCheckedEpochMillis) ) data.lastCheckedText = ( + ) }} /> diff --git a/lib/components/user/monitored-trip/trip-status-rendering-strategies/upcoming-trip-renderer.js b/lib/components/user/monitored-trip/trip-status-rendering-strategies/upcoming-trip-renderer.js index 8548cc573..43e654fcf 100644 --- a/lib/components/user/monitored-trip/trip-status-rendering-strategies/upcoming-trip-renderer.js +++ b/lib/components/user/monitored-trip/trip-status-rendering-strategies/upcoming-trip-renderer.js @@ -89,7 +89,7 @@ export default function upcomingTripRenderer({ + duration: }} /> ) @@ -100,7 +100,7 @@ export default function upcomingTripRenderer({ + duration: }} /> ) From df282f658c5048eddfd9c46a9da95023cf034c2a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 16:17:03 -0400 Subject: [PATCH 0361/1425] refactor(UserSettings): Remove reference to moment --- lib/components/form/user-settings.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/components/form/user-settings.js b/lib/components/form/user-settings.js index 723dd3ea9..0d4a89449 100644 --- a/lib/components/form/user-settings.js +++ b/lib/components/form/user-settings.js @@ -2,9 +2,9 @@ /* eslint-disable react/prop-types */ import { Button } from 'react-bootstrap' import { connect } from 'react-redux' +import { differenceInSeconds } from 'date-fns' import { FormattedMessage, injectIntl, useIntl } from 'react-intl' import coreUtils from '@opentripplanner/core-utils' -import moment from 'moment' import React, { Component, useCallback } from 'react' import * as apiActions from '../../actions/api' @@ -27,6 +27,7 @@ import { UnpaddedList } from './styled' const { matchLatLon } = coreUtils.map const { hasTransit, toSentenceCase } = coreUtils.itinerary +const { toHoursMinutesSeconds } = coreUtils.time /** * Reformats a {lat, lon} object to be internationalized. @@ -107,17 +108,18 @@ export function summarizeQuery(query, intl, locations = []) { * as in "5 hours ago" or the localized equivalent. */ function formatElapsedTime(timestamp, intl) { + const ellapsedSeconds = differenceInSeconds(new Date(), new Date(timestamp)) + const { hours: allHours, minutes } = toHoursMinutesSeconds(ellapsedSeconds) + // TODO: add a day component to OTP-UI's toHoursMinutesSeconds? + const days = Math.floor(allHours / 24) + const hours = allHours - days * 24 + // This text will be shown in a tooltip, therefore - // it is obtained using formaMessage rather than . - const fromNowDur = moment.duration(moment().diff(moment(timestamp))) + // it is obtained using formatMessage rather than . return intl .formatMessage( { id: 'common.time.fromNowUpdate' }, - { - days: fromNowDur.days(), - hours: fromNowDur.hours(), - minutes: fromNowDur.minutes() - } + { days, hours, minutes } ) .trim() } From 67eeebb88a09400732a475be5685ee1614cd48f1 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 17:02:15 -0400 Subject: [PATCH 0362/1425] refactor(StopViewer): Remove moment reference --- lib/components/viewers/stop-viewer.js | 32 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index b88ee9e5a..4b5e60825 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -2,10 +2,11 @@ import 'moment-timezone' import { Alert, Button } from 'react-bootstrap' import { connect } from 'react-redux' +import { format } from 'date-fns-tz' import { FormattedMessage, injectIntl } from 'react-intl' import coreUtils from '@opentripplanner/core-utils' +import dateFnsUSLocale from 'date-fns/locale/en-US' import FromToLocationPicker from '@opentripplanner/from-to-location-picker' -import moment from 'moment' import PropTypes from 'prop-types' import React, { Component } from 'react' import styled from 'styled-components' @@ -24,11 +25,14 @@ import Strong from '../util/strong-text' import LiveStopTimes from './live-stop-times' import StopScheduleTable from './stop-schedule-table' -const { getUserTimezone, OTP_API_DATE_FORMAT } = coreUtils.time +const { getCurrentDate, getUserTimezone } = coreUtils.time -const defaultState = { - date: moment().format(OTP_API_DATE_FORMAT), - isShowingSchedule: false +function getDefaultState(timeZone) { + return { + // Compare dates/times in the stop viewer based on the agency's timezone. + date: getCurrentDate(timeZone), + isShowingSchedule: false + } } // A scrollable container for the contents of the stop viewer body. @@ -47,7 +51,10 @@ const StyledAlert = styled(Alert)` ` class StopViewer extends Component { - state = defaultState + constructor(props) { + super(props) + this.state = getDefaultState(props.homeTimezone) + } static propTypes = { hideBackButton: PropTypes.bool, @@ -105,12 +112,12 @@ class StopViewer extends Component { ) !== -1 componentDidUpdate(prevProps, prevState) { - const { fetchStopInfo, viewedStop } = this.props + const { fetchStopInfo, homeTimezone, viewedStop } = this.props const { date, isShowingSchedule } = this.state if (prevProps.viewedStop?.stopId !== viewedStop?.stopId) { // Reset state to default if stop changes (i.e., show next arrivals). - this.setState(defaultState) + this.setState(getDefaultState(homeTimezone)) fetchStopInfo(viewedStop) } else if (!!isShowingSchedule && !prevState.isShowingSchedule) { // If the viewing mode has changed to schedule view, @@ -181,8 +188,7 @@ class StopViewer extends Component { _renderControls = () => { const { homeTimezone, intl, stopData } = this.props const { isShowingSchedule } = this.state - const userTimeZone = getUserTimezone() - const inHomeTimezone = homeTimezone && homeTimezone === userTimeZone + const inHomeTimezone = homeTimezone && homeTimezone === getUserTimezone() // Rewrite stop ID to not include Agency prefix, if present // TODO: make this functionality configurable? @@ -197,7 +203,11 @@ class StopViewer extends Component { let timezoneWarning if (!inHomeTimezone) { - const timezoneCode = moment().tz(homeTimezone).format('z') + const timezoneCode = format(Date.now(), 'z', { + // To avoid ambiguities for now, use the English-US timezone abbreviations ("EST", "PDT", etc.) + locale: dateFnsUSLocale, + timeZone: homeTimezone + }) // Display a banner about the departure timezone if user's timezone is not the configured 'homeTimezone' // (e.g. cases where a user in New York looks at a schedule in Los Angeles). From 32e95b776bf72be4ca66177dfcaf0651bbbbfd6b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:04:22 -0400 Subject: [PATCH 0363/1425] style(SavedTripScreen): Apply lint/prettier rules --- .../user/monitored-trip/saved-trip-screen.js | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index fc79f0e1d..b2ff5d05b 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -1,46 +1,48 @@ +/* eslint-disable react/prop-types */ +import * as yup from 'yup' +import { connect } from 'react-redux' +import { Form, Formik } from 'formik' +import { injectIntl } from 'react-intl' import { withAuthenticationRequired } from '@auth0/auth0-react' import clone from 'clone' -import { Form, Formik } from 'formik' import React, { Component } from 'react' -import { injectIntl } from 'react-intl' -import { connect } from 'react-redux' -import * as yup from 'yup' -import AccountPage from '../account-page' import * as uiActions from '../../../actions/ui' import * as userActions from '../../../actions/user' -import AwaitingScreen from '../awaiting-screen' -import { TRIPS_PATH } from '../../../util/constants' -import { getItineraryDefaultMonitoredDays } from '../../../util/itinerary' import { ALL_DAYS, arrayToDayFields } from '../../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../../util/state' +import { getItineraryDefaultMonitoredDays } from '../../../util/itinerary' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' +import { TRIPS_PATH } from '../../../util/constants' +import AccountPage from '../account-page' +import AwaitingScreen from '../awaiting-screen' import withLoggedInUserSupport from '../with-logged-in-user-support' -import TripSummaryPane from './trip-summary-pane' -import TripNotificationsPane from './trip-notifications-pane' -import TripBasicsPane from './trip-basics-pane' import SavedTripEditor from './saved-trip-editor' +import TripBasicsPane from './trip-basics-pane' +import TripNotificationsPane from './trip-notifications-pane' +import TripSummaryPane from './trip-summary-pane' // The validation schema shape for the form fields. // TODO: add fields here as they are implemented. const validationSchemaShape = { isActive: yup.boolean(), leadTimeInMinutes: yup.number().positive().integer(), - tripName: yup.string() + tripName: yup + .string() // Text constant to display components.SavedTripScreen.tripNameRequired // It is used to allow format.js command line tool to keep track of // which IDs are in the code. .required('trip-name-required') } // Add the checks on each day that at least one day is monitored. -ALL_DAYS.forEach(day => { +ALL_DAYS.forEach((day) => { validationSchemaShape[day] = yup.boolean().test( 'one-day-selected', 'selectAtLeastOneDay', // not directly shown. function () { let selectedDays = 0 - ALL_DAYS.forEach(day => { + ALL_DAYS.forEach((day) => { if (this.parent[day]) selectedDays++ }) return selectedDays !== 0 @@ -51,7 +53,7 @@ ALL_DAYS.forEach(day => { /** * Checks that the maximum allowed number of saved trips has not been reached. */ -function hasMaxTripCount (trips) { +function hasMaxTripCount(trips) { // TODO: Obtain the maximum number from a query to middleware (it is currently hard coded there too). return trips && trips.length >= 5 } @@ -85,13 +87,14 @@ class SavedTripScreen extends Component { userId: loggedInUser.id } } + /** * Persists changes to the edited trip. * On success, this operation will also make the browser * navigate to the Saved trips page. * @param {*} monitoredTrip The trip edited state to be saved, provided by Formik. */ - _updateMonitoredTrip = monitoredTrip => { + _updateMonitoredTrip = (monitoredTrip) => { const { createOrUpdateUserMonitoredTrip, intl, isCreating } = this.props createOrUpdateUserMonitoredTrip( monitoredTrip, @@ -119,12 +122,14 @@ class SavedTripScreen extends Component { summary: TripSummaryPane } - componentDidMount () { + componentDidMount() { const { intl, isCreating, monitoredTrips } = this.props if (isCreating && hasMaxTripCount(monitoredTrips)) { // There is a middleware limit of 5 saved trips, // so if that limit is already reached, alert, then show editing mode. - alert(intl.formatMessage({id: 'components.SavedTripScreen.tooManyTrips'})) + alert( + intl.formatMessage({ id: 'components.SavedTripScreen.tooManyTrips' }) + ) this._goToSavedTrips() } @@ -138,10 +143,10 @@ class SavedTripScreen extends Component { const { isCreating, monitoredTrips, tripId } = this.props return isCreating ? this._createMonitoredTrip() - : monitoredTrips.find(trip => trip.id === tripId) + : monitoredTrips.find((trip) => trip.id === tripId) } - render () { + render() { const { isCreating, loggedInUser, monitoredTrips } = this.props let screenContents @@ -151,11 +156,12 @@ class SavedTripScreen extends Component { } else { const monitoredTrip = this._getTripToEdit() const otherTripNames = monitoredTrips - .filter(trip => trip !== monitoredTrip) - .map(trip => trip.tripName) + .filter((trip) => trip !== monitoredTrip) + .map((trip) => trip.tripName) const clonedSchemaShape = clone(validationSchemaShape) - clonedSchemaShape.tripName = yup.string() + clonedSchemaShape.tripName = yup + .string() // Text constant to display components.SavedTripScreen.tripNameRequired // Text constant is used to allow format.js command line tool to keep track of // which IDs are in the code. @@ -182,14 +188,16 @@ class SavedTripScreen extends Component { // and to its own blur/change/submit event handlers that automate the state. // We pass the Formik props below to the components rendered so that individual controls // can be wired to be managed by Formik. - props => { + (props) => { return (
    @@ -200,11 +208,7 @@ class SavedTripScreen extends Component { ) } - return ( - - {screenContents} - - ) + return {screenContents} } } @@ -233,9 +237,7 @@ const mapDispatchToProps = { export default withLoggedInUserSupport( withAuthenticationRequired( - connect(mapStateToProps, mapDispatchToProps)( - injectIntl(SavedTripScreen) - ), + connect(mapStateToProps, mapDispatchToProps)(injectIntl(SavedTripScreen)), RETURN_TO_CURRENT_ROUTE ), true From 6c1d10fc5a9c9aab3c0991f0e367811591e7c190 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:07:05 -0400 Subject: [PATCH 0364/1425] refactor(util/itinerary): Remove reference to moment --- .../user/monitored-trip/saved-trip-screen.js | 8 +++++-- lib/util/itinerary.js | 22 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index b2ff5d05b..ab88a0c70 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -67,8 +67,11 @@ class SavedTripScreen extends Component { * Initializes a monitored trip object from the props. */ _createMonitoredTrip = () => { - const { itinerary, loggedInUser, queryParams } = this.props - const monitoredDays = getItineraryDefaultMonitoredDays(itinerary) + const { homeTimezone, itinerary, loggedInUser, queryParams } = this.props + const monitoredDays = getItineraryDefaultMonitoredDays( + itinerary, + homeTimezone + ) return { ...arrayToDayFields(monitoredDays), arrivalVarianceMinutesThreshold: 5, @@ -221,6 +224,7 @@ const mapStateToProps = (state, ownProps) => { const tripId = ownProps.match.params.id return { activeSearchId: state.otp.activeSearchId, + homeTimezone: state.otp.config.homeTimezone, isCreating: tripId === 'new', itinerary: itineraries[activeItinerary], loggedInUser: state.user.loggedInUser, diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index 84eeee707..099731508 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -1,6 +1,7 @@ +import { differenceInMinutes } from 'date-fns' import { latLngBounds } from 'leaflet' +import { toDate, utcToZonedTime } from 'date-fns-tz' import coreUtils from '@opentripplanner/core-utils' -import moment from 'moment' import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' @@ -49,7 +50,7 @@ export function itineraryCanBeMonitored(itinerary) { } export function getMinutesUntilItineraryStart(itinerary) { - return moment(itinerary.startTime).diff(moment(), 'minutes') + return differenceInMinutes(new Date(itinerary.startTime), new Date()) } /** @@ -76,13 +77,20 @@ export function getFirstStopId(itinerary) { * (*) For transit itineraries, the first transit leg is used to make * the determination. Otherwise, the itinerary startTime is used. */ -export function getItineraryDefaultMonitoredDays(itinerary) { +export function getItineraryDefaultMonitoredDays( + itinerary, + timeZone = coreUtils.time.getUserTimezone() +) { const firstTransitLeg = getFirstTransitLeg(itinerary) - const startMoment = firstTransitLeg - ? moment(firstTransitLeg.serviceDate, 'YYYYMMDD') - : moment(itinerary.startTime) - const dayOfWeek = startMoment.day() + // firstTransitLeg should be non-null because only transit trips can be monitored at this time. + // - using serviceDate covers legs that start past midnight. + // - The format of serviceDate can either be 'yyyyMMdd' (OTP v1) or 'yyyy-MM-dd' (OTP v2) + // and both formats are correctly handled by toDate from date-fns-tz. + const startDate = firstTransitLeg + ? toDate(firstTransitLeg.serviceDate, { timeZone }) + : utcToZonedTime(new Date(itinerary.startTime), timeZone) + const dayOfWeek = startDate.getDay() return dayOfWeek === 0 || dayOfWeek === 6 ? WEEKEND_DAYS : WEEKDAYS } From 6c56e6bebcc2fa49ded914626746dd6b58205af4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:04:48 -0400 Subject: [PATCH 0365/1425] refactor(DefaultMap): Handle null mapConfig --- lib/components/map/default-map.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index 39cfb5b34..d20e7b3ae 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -142,11 +142,10 @@ class DefaultMap extends Component { vehicleRentalStations } = this.props const { getCustomMapOverlays, getTransitiveRouteLabel } = this.context + const { baseLayers, initLat, initLon, initZoom, maxZoom, overlays } = + mapConfig || {} - const center = - mapConfig && mapConfig.initLat && mapConfig.initLon - ? [mapConfig.initLat, mapConfig.initLon] - : null + const center = initLat && initLon ? [initLat, initLon] : null const popup = mapPopupLocation && { contents: ( @@ -161,13 +160,13 @@ class DefaultMap extends Component { return ( {/* The default overlays */} @@ -182,7 +181,7 @@ class DefaultMap extends Component { {/* The configurable overlays */} - {mapConfig.overlays?.map((overlayConfig, k) => { + {overlays?.map((overlayConfig, k) => { switch (overlayConfig.type) { case 'bike-rental': return ( From def918ef669aeb98212c5084f7f6f6f4efce316f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:28:19 -0400 Subject: [PATCH 0366/1425] refactor(DefaultMap): Remove redundant props --- lib/components/map/default-map.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index 1c6939e14..1e34cb793 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -126,8 +126,8 @@ class DefaultMap extends Component { * TODO: Implement for the batch interface. */ _handleQueryChange = (oldQuery, newQuery) => { - const { overlays } = this.props - if (overlays && oldQuery.mode) { + const { overlays = [] } = this.props.mapConfig || {} + if (oldQuery.mode) { // Determine any added/removed modes const oldModes = oldQuery.mode.split(',') const newModes = newQuery.mode.split(',') @@ -362,7 +362,6 @@ class DefaultMap extends Component { const mapStateToProps = (state) => { const activeSearch = getActiveSearch(state) - const overlays = state.otp.config.map?.overlays || [] return { bikeRentalStations: state.otp.overlay.bikeRental.stations, @@ -371,7 +370,6 @@ const mapStateToProps = (state) => { itinerary: getActiveItinerary(state), mapConfig: state.otp.config.map, mapPopupLocation: state.otp.ui.mapPopupLocation, - overlays, pending: activeSearch ? Boolean(activeSearch.pending) : false, query: state.otp.currentQuery, vehicleRentalStations: state.otp.overlay.vehicleRental.stations From a55df70c38bfa7f1cf4ac89ebbfe70d0e642cc18 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:38:12 -0400 Subject: [PATCH 0367/1425] test(DefaultMap): Disable type checks for now --- lib/components/map/default-map.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/components/map/default-map.tsx b/lib/components/map/default-map.tsx index 1e34cb793..b5a40fc44 100644 --- a/lib/components/map/default-map.tsx +++ b/lib/components/map/default-map.tsx @@ -1,4 +1,6 @@ /* eslint-disable react/prop-types */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck import { connect } from 'react-redux' import { injectIntl } from 'react-intl' import BaseMap from '@opentripplanner/base-map' From 090c0aa72dd1b5c23858c77fa101497e47b509c9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 12:19:23 -0400 Subject: [PATCH 0368/1425] refactor(SessionTimeout): Address PR comments --- example-config.yml | 5 +++-- lib/actions/ui.js | 3 +-- lib/components/app/session-timeout.tsx | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/example-config.yml b/example-config.yml index 82bbd1739..59b1dabfa 100644 --- a/example-config.yml +++ b/example-config.yml @@ -449,6 +449,7 @@ dateTime: ### e.g. on touch-screen kiosks that run a desktop OS. # isTouchScreenOnDesktop: true -### Approximate duration in seconds after which, if there is no user activity, -### the UI is reset to an initial URL. +### Approximate duration in seconds after which, if there is no user activity, the UI is reset to an initial URL. +### The value should be at least over a minute, so that users have enough time to extend their session if needed. +### A warning is shown when a minute or a third of the session time remains, whichever is shorter. # sessionTimeoutSeconds: 180 diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 5342ab26b..510464a1f 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -394,13 +394,12 @@ export function setRouteViewerFilter(filter) { } /** - * Start over from the initial URL + * Start over by setting the initial URL and resetting (reloading) the page. */ export function startOverFromInitialUrl() { return function (dispatch, getState) { const { initialUrl } = getState().otp window.location.replace(initialUrl) - // FIXME: Trigger a reload to reset page, otherwise the UI keeps some previous states. window.location.reload() } } diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index 178415d8e..88398c47e 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -53,9 +53,15 @@ class SessionTimeout extends Component { // Reload initial URL (page state is lost after this point) startOverFromInitialUrl() } else { + // If session is going to expire, display warning dialog, don't otherwise. + // For session timeouts of more than 180 seconds, display warning within one minute. + // For timeouts shorter than that, set the warning to 1/3 of the session timeout. + const timeoutWarningSeconds = + sessionTimeoutSeconds >= 180 ? 60 : sessionTimeoutSeconds / 3 + this.setState({ - // If within a minute of timeout, display dialog, don't otherwise. - showTimeoutWarning: secondsToTimeout >= 0 && secondsToTimeout <= 60 + showTimeoutWarning: + secondsToTimeout >= 0 && secondsToTimeout <= timeoutWarningSeconds }) } } From 71a99f01d47edd8722dd0bb521fcbcadecbd5888 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:11:07 -0400 Subject: [PATCH 0369/1425] refactor(QueryRecord): Remove momentjs --- lib/components/admin/query-record.js | 47 ++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index 9d553f749..7781f37f5 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -1,8 +1,9 @@ +/* eslint-disable react/prop-types */ +import { connect } from 'react-redux' +import { format, parse } from 'date-fns' +import { getTimeFormat } from '@opentripplanner/core-utils/lib/time' import { planParamsToQuery } from '@opentripplanner/core-utils/lib/query' -import { getTimeFormat, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time' -import moment from 'moment' import React, { Component } from 'react' -import { connect } from 'react-redux' import * as formActions from '../../actions/form' import { parseQueryParams } from '../../util/call-taker' @@ -13,25 +14,36 @@ import { CallRecordButton, CallRecordIcon } from './styled' * Displays information for a query stored for the Call Taker module. */ class QueryRecordLayout extends Component { - _getParams = () => planParamsToQuery(parseQueryParams(this.props.query.queryParams)) + _getParams = () => + planParamsToQuery(parseQueryParams(this.props.query.queryParams)) _viewQuery = () => { - const {parseUrlQueryString} = this.props + const { parseUrlQueryString } = this.props const params = this._getParams() parseUrlQueryString(params, '_CALL') } - render () { - const {query, timeFormat} = this.props + render() { + const { query, timeFormat } = this.props const params = this._getParams() - const time = query.timeStamp - ? moment(query.timeStamp).format(timeFormat) - : moment(params.time, OTP_API_TIME_FORMAT).format(timeFormat) + // Express time in the agency's homeTimezone. + // + const now = Date.now() + const time = format( + query.timeStamp + ? parse(query.timeStamp, 'MMM d, yyyy h:mm:ss a', now) + : parse(params.time, 'H:mm', now), + timeFormat + ) return (
  • - - - {time}
    + + + {time} +
    {params.from.name} to {params.to.name}
  • @@ -39,15 +51,18 @@ class QueryRecordLayout extends Component { } } -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state) => { return { timeFormat: getTimeFormat(state.otp.config) } } -const {parseUrlQueryString} = formActions +const { parseUrlQueryString } = formActions const mapDispatchToProps = { parseUrlQueryString } -const QueryRecord = connect(mapStateToProps, mapDispatchToProps)(QueryRecordLayout) +const QueryRecord = connect( + mapStateToProps, + mapDispatchToProps +)(QueryRecordLayout) export default QueryRecord From 0c5291ddc365f9a95f104eb1937916618d47d46f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 17:28:54 -0400 Subject: [PATCH 0370/1425] refactor(CallRecord): Remove momentjs --- lib/components/admin/call-record.js | 100 ++++++++++++++------------- lib/components/admin/query-record.js | 6 +- lib/util/call-taker.js | 25 ++++--- 3 files changed, 68 insertions(+), 63 deletions(-) diff --git a/lib/components/admin/call-record.js b/lib/components/admin/call-record.js index 80cbd41ca..4f67181dd 100644 --- a/lib/components/admin/call-record.js +++ b/lib/components/admin/call-record.js @@ -1,13 +1,14 @@ +/* eslint-disable react/prop-types */ +import { differenceInMilliseconds, format } from 'date-fns' import humanizeDuration from 'humanize-duration' -import moment from 'moment' import React, { Component } from 'react' -import {searchToQuery} from '../../util/call-taker' +import { parseDate, searchToQuery, toTimeZone } from '../../util/call-taker' import Icon from '../util/icon' +import { CallRecordButton, CallRecordIcon, QueryList } from './styled' import CallTimeCounter from './call-time-counter' import QueryRecord from './query-record' -import {CallRecordButton, CallRecordIcon, QueryList} from './styled' /** * Displays information for a particular call record in the Call Taker window. @@ -18,90 +19,93 @@ export default class CallRecord extends Component { } _getCallDuration = () => { - const {call} = this.props - const start = moment(call.startTime) - const end = moment(call.endTime) - const millis = moment.duration(end.diff(start)).asMilliseconds() + const { call } = this.props + const start = parseDate(call.startTime) + const end = parseDate(call.endTime) + const millis = differenceInMilliseconds(end, start) return humanizeDuration(millis) } _toggleExpanded = () => { - const {call, fetchQueries, intl} = this.props - const {expanded} = this.state + const { call, fetchQueries, intl } = this.props + const { expanded } = this.state if (!expanded) { fetchQueries(call.id, intl) } - this.setState({expanded: !expanded}) + this.setState({ expanded: !expanded }) } - render () { + render() { // FIXME: consolidate red color with call taker controls const RED = '#C35134' - const {call, inProgress, searches} = this.props - const {expanded} = this.state + const { call, inProgress, searches } = this.props + const { expanded } = this.state if (!call) return null if (inProgress) { // Map search IDs made during active call to queries. - const activeQueries = call.searches - .map(searchId => searchToQuery(searches[searchId], call, {})) + const activeQueries = call.searches.map((searchId) => + searchToQuery(searches[searchId], call, {}) + ) return (
    -
    +
    + className="animate-flicker" + style={{ color: RED, fontSize: '8px', verticalAlign: '2px' }} + type="circle" + />
    - {' '} - [Active call] + [Active call]
    - - In progress... click to save{' '} - ({call.searches.length} searches) + + In progress... click to save ( + {call.searches.length} searches) {activeQueries.length > 0 - ? activeQueries.map((query, i) => ( - - )) - : 'No queries recorded.' - } + ? activeQueries.map((query, i) => { + return + }) + : 'No queries recorded.'}
    ) } // Default (no active call) view - const startTimeMoment = moment(call.startTime) + const startTime = parseDate(call.startTime) return ( -
    +
    - - - {startTimeMoment.format('h:mm a, MMM D')} + + + {format(startTime, 'h:mm a, MMM d')} - {' '} - {this._getCallDuration()} + {this._getCallDuration()} - {expanded - ? + {expanded ? ( + {call.queries && call.queries.length > 0 - ? call.queries.map((query, i) => ( - - )) - : 'No queries recorded.' - } + ? call.queries.map((query, i) => { + return + }) + : 'No queries recorded.'} - : null - } + ) : null}
    ) } diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index 7781f37f5..2122bb0c4 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -6,7 +6,7 @@ import { planParamsToQuery } from '@opentripplanner/core-utils/lib/query' import React, { Component } from 'react' import * as formActions from '../../actions/form' -import { parseQueryParams } from '../../util/call-taker' +import { parseDate, parseQueryParams } from '../../util/call-taker' import { CallRecordButton, CallRecordIcon } from './styled' @@ -26,12 +26,10 @@ class QueryRecordLayout extends Component { render() { const { query, timeFormat } = this.props const params = this._getParams() - // Express time in the agency's homeTimezone. - // const now = Date.now() const time = format( query.timeStamp - ? parse(query.timeStamp, 'MMM d, yyyy h:mm:ss a', now) + ? parseDate(query.timeStamp) : parse(params.time, 'H:mm', now), timeFormat ) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index 5747c3336..b21eba293 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,9 +1,8 @@ import { compareAsc, differenceInCalendarDays, parse } from 'date-fns' import { getRoutingParams } from '@opentripplanner/core-utils/lib/query' import { getUserTimezone } from '@opentripplanner/core-utils/lib/time' -import { toDate } from 'date-fns-tz' +import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz' -import { getISOLikeTimestamp } from './state' const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' /** @@ -13,8 +12,16 @@ const referenceDateForDateFns = Date.now() /** * Date parsing helper, specific to calltaker/field trip entities. */ -function parseDate(date) { - parse(date, ENTITY_DATE_TIME_FORMAT, referenceDateForDateFns) +export function parseDate(date) { + return parse(date, ENTITY_DATE_TIME_FORMAT, referenceDateForDateFns) +} + +/** + * Helper function to convert a date from the browser's timezone to the specified one. + */ +export function toTimeZone(date, timeZone) { + const dateUtc = zonedTimeToUtc(date, getUserTimezone()) + return utcToZonedTime(dateUtc, timeZone) } export const TICKET_TYPES = { @@ -167,14 +174,10 @@ export function getVisibleRequests(state) { if (!isNaN(month) && !isNaN(day)) { // If month and day seem to be numbers, check against request date // in the the configured homeTimezone. - const dateInBrowserTz = parseDate(request.travelDate) - const isoDateWithoutTz = getISOLikeTimestamp( - getUserTimezone(), - dateInBrowserTz + const date = toTimeZone( + parseDate(request.travelDate), + otp.config.homeTimezone ) - const date = toDate(isoDateWithoutTz, { - timeZone: otp.config.homeTimezone - }) return date.getMonth() + 1 === +month && date.getDate() === +day } } From cd96808f26f64dd1b55cb37989b00d54b64282b7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 17:40:02 -0400 Subject: [PATCH 0371/1425] refactor(TripStatus): Remove momentjs --- lib/components/admin/trip-status.js | 81 ++++++++++++++--------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/lib/components/admin/trip-status.js b/lib/components/admin/trip-status.js index 833a82a1a..0770f7863 100644 --- a/lib/components/admin/trip-status.js +++ b/lib/components/admin/trip-status.js @@ -1,28 +1,23 @@ +/* eslint-disable react/prop-types */ +import { connect } from 'react-redux' +import { format } from 'date-fns' import { getTimeFormat } from '@opentripplanner/core-utils/lib/time' -import moment from 'moment' -import React, {Component} from 'react' import { injectIntl } from 'react-intl' -import { connect } from 'react-redux' +import React, { Component } from 'react' import * as fieldTripActions from '../../actions/field-trip' import * as formActions from '../../actions/form' -import { getTripFromRequest } from '../../util/call-taker' +import { getTripFromRequest, parseDate } from '../../util/call-taker' +import { Bold, Button, Full, Header, Para } from './styled' import FieldTripStatusIcon from './field-trip-status-icon' -import { - Bold, - Button, - Full, - Header, - Para -} from './styled' class TripStatus extends Component { - _formatTime = (time) => moment(time).format(this.props.timeFormat) + _formatTime = (time) => format(parseDate(time), this.props.timeFormat) _onDeleteTrip = () => { const { deleteRequestTripItineraries, intl, request, trip } = this.props - if (!confirm('Are you sure you want to delete the planned trip?')) { + if (!window.confirm('Are you sure you want to delete the planned trip?')) { return } deleteRequestTripItineraries(request, trip.id, intl) @@ -31,7 +26,11 @@ class TripStatus extends Component { _onPlanTrip = () => { const { intl, outbound, planTrip, request, status, trip } = this.props if (status && trip) { - if (!confirm('Re-planning this trip will cause the trip planner to avoid the currently saved trip. Are you sure you want to continue?')) { + if ( + !window.confirm( + 'Re-planning this trip will cause the trip planner to avoid the currently saved trip. Are you sure you want to continue?' + ) + ) { return } } @@ -53,11 +52,7 @@ class TripStatus extends Component { _renderTripStatus = () => { const { trip } = this.props if (!this._tripIsPlanned()) { - return ( - - No itineraries planned! Click Plan to plan trip. - - ) + return No itineraries planned! Click Plan to plan trip. } return ( <> @@ -66,15 +61,19 @@ class TripStatus extends Component { {trip.createdBy} at {trip.timeStamp} - - + + ) } - render () { - const {outbound, request, saveable} = this.props + render() { + const { outbound, request, saveable } = this.props const { arriveDestinationTime, arriveSchoolTime, @@ -93,27 +92,26 @@ class TripStatus extends Component {
    {outbound ? 'Outbound' : 'Inbound'} trip - - +
    - From {start} to {end} - {outbound - ? - Arriving at {this._formatTime(arriveDestinationTime)} - - : <> + + From {start} to {end} + + {outbound ? ( + Arriving at {this._formatTime(arriveDestinationTime)} + ) : ( + <> - Leave at {this._formatTime(leaveDestinationTime)},{' '} - due back at {this._formatTime(arriveSchoolTime)} + Leave at {this._formatTime(leaveDestinationTime)}, due back at{' '} + {this._formatTime(arriveSchoolTime)} - } + )} {this._renderTripStatus()} ) @@ -140,6 +138,7 @@ const mapDispatchToProps = { viewRequestTripItineraries: fieldTripActions.viewRequestTripItineraries } -export default connect(mapStateToProps, mapDispatchToProps)( - injectIntl(TripStatus) -) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(TripStatus)) From eabc38bdcfc64a6169ee373fb7a05542b6738bf7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 17:47:20 -0400 Subject: [PATCH 0372/1425] refactor(FieldTripList): Remove momentjs --- lib/components/admin/field-trip-list.js | 286 ++++++++++++------------ 1 file changed, 141 insertions(+), 145 deletions(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index e2b96c0da..2d94a68c4 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -1,37 +1,113 @@ -import moment from 'moment' -import qs from 'qs' -import React, { Component } from 'react' +/* eslint-disable react/prop-types */ import { Badge, Button } from 'react-bootstrap' -import { injectIntl } from 'react-intl' import { connect } from 'react-redux' +import { injectIntl } from 'react-intl' +import coreUtils from '@opentripplanner/core-utils' +import qs from 'qs' +import React, { Component } from 'react' import * as fieldTripActions from '../../actions/field-trip' -import Loading from '../narrative/loading' -import {getVisibleRequests, TABS} from '../../util/call-taker' -import {FETCH_STATUS} from '../../util/constants' +import { FETCH_STATUS } from '../../util/constants' +import { getVisibleRequests, TABS } from '../../util/call-taker' import Icon from '../util/icon' +import Loading from '../narrative/loading' -import FieldTripStatusIcon from './field-trip-status-icon' -import {FieldTripRecordButton, WindowHeader} from './styled' +import { FieldTripRecordButton, WindowHeader } from './styled' import DraggableWindow from './draggable-window' +import FieldTripStatusIcon from './field-trip-status-icon' + +function Tab({ data, filter, onClick, tab }) { + const count = React.useMemo(() => data.filter(tab.filter).length, [data, tab]) + const active = tab.id === filter.tab + const style = { + backgroundColor: active ? 'navy' : undefined, + borderRadius: 5, + color: active ? 'white' : undefined, + padding: '2px 3px' + } + return ( + + ) +} + +class FieldTripRequestRecord extends Component { + _onClick = () => { + const { onClick, request } = this.props + onClick(request) + } + + render() { + const { active, request } = this.props + const style = { + backgroundColor: active ? 'lightgrey' : undefined, + borderBottom: '1px solid grey' + } + const { + endLocation, + id, + inboundTripStatus, + outboundTripStatus, + schoolName, + startLocation, + teacherName, + timeStamp, + travelDate + } = request + const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') + return ( +
  • + + + {schoolName} Trip (#{id}) + + + + Outbound + + + Inbound + + + + Submitted by {teacherName} on {timeStamp} + + + {startLocation} to {endLocation} on {formattedDate} + + +
  • + ) + } +} /** * Displays a searchable list of field trip requests in a draggable window. */ class FieldTripList extends Component { - constructor (props) { + constructor(props) { super(props) this.state = { - date: moment().startOf('day').format('YYYY-MM-DD') + date: coreUtils.time.getCurrentDate() } } + _onClickFieldTrip = (request) => { - const { - callTaker, - fetchFieldTripDetails, - intl, - setActiveFieldTrip - } = this.props + const { callTaker, fetchFieldTripDetails, intl, setActiveFieldTrip } = + this.props if (request.id === callTaker.fieldTrip.activeId) { this._onCloseActiveFieldTrip() } else { @@ -50,10 +126,10 @@ class FieldTripList extends Component { } _getReportUrl = () => { - const {datastoreUrl} = this.props - const {date} = this.state + const { datastoreUrl } = this.props + const { date } = this.state const [year, month, day] = date.split('-') - const params = {day, month, year} + const params = { day, month, year } return `${datastoreUrl}/fieldtrip/opsReport?${qs.stringify(params)}` } @@ -62,36 +138,36 @@ class FieldTripList extends Component { * each time the search input changes (on TriMet's production instance there * are thousands of field trip requests). */ - _handleSearchKeyUp = e => { - const {callTaker, setFieldTripFilter} = this.props - const {search} = callTaker.fieldTrip.filter + _handleSearchKeyUp = (e) => { + const { callTaker, setFieldTripFilter } = this.props + const { search } = callTaker.fieldTrip.filter const newSearch = e.target.value // Update filter if Enter is pressed or search value is entirely cleared. const newSearchEntered = e.keyCode === 13 && newSearch !== search const searchCleared = search && !newSearch if (newSearchEntered || searchCleared) { - setFieldTripFilter({search: newSearch}) + setFieldTripFilter({ search: newSearch }) } } - _onTabChange = e => { - this.props.setFieldTripFilter({tab: e.currentTarget.name}) + _onTabChange = (e) => { + this.props.setFieldTripFilter({ tab: e.currentTarget.name }) } - _updateReportDate = evt => this.setState({ date: evt.target.value }) + _updateReportDate = (evt) => this.setState({ date: evt.target.value }) - render () { - const {callTaker, style, toggleFieldTrips, visibleRequests} = this.props - const {fieldTrip} = callTaker - const {activeId, filter} = fieldTrip - const {search} = filter - const {date} = this.state + render() { + const { callTaker, style, toggleFieldTrips, visibleRequests } = this.props + const { fieldTrip } = callTaker + const { activeId, filter } = fieldTrip + const { search } = filter + const { date } = this.state return ( - @@ -115,19 +187,19 @@ class FieldTripList extends Component { header={ <> - Field Trip Requests{' '} - + Field Trip Requests{' '} + - {TABS.map(tab => + {TABS.map((tab) => ( - )} + tab={tab} + /> + ))} } onClickClose={toggleFieldTrips} style={style} > - {fieldTrip.requests.status === FETCH_STATUS.FETCHING - ? - : visibleRequests.length > 0 - ? visibleRequests.map((request, i) => ( - - )) - :
    No field trips found.
    - } + {fieldTrip.requests.status === FETCH_STATUS.FETCHING ? ( + + ) : visibleRequests.length > 0 ? ( + visibleRequests.map((request, i) => ( + + )) + ) : ( +
    No field trips found.
    + )}
    ) } } -function Tab ({data, filter, onClick, tab}) { - const count = React.useMemo(() => data.filter(tab.filter).length, [data, tab]) - const active = tab.id === filter.tab - const style = { - backgroundColor: active ? 'navy' : undefined, - borderRadius: 5, - color: active ? 'white' : undefined, - padding: '2px 3px' - } - return ( - - ) -} - -class FieldTripRequestRecord extends Component { - _onClick = () => { - const {onClick, request} = this.props - onClick(request) - } - - render () { - const {active, request} = this.props - const style = { - backgroundColor: active ? 'lightgrey' : undefined, - borderBottom: '1px solid grey' - } - const { - endLocation, - id, - inboundTripStatus, - outboundTripStatus, - schoolName, - startLocation, - teacherName, - timeStamp, - travelDate - } = request - const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') - return ( -
  • - - - {schoolName} Trip (#{id}) - - - - Outbound - - - Inbound - - - - Submitted by {teacherName} on {timeStamp} - - - {startLocation} to {endLocation} on {formattedDate} - - -
  • - ) - } -} - -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state) => { return { callTaker: state.callTaker, currentQuery: state.otp.currentQuery, @@ -265,6 +260,7 @@ const mapDispatchToProps = { toggleFieldTrips: fieldTripActions.toggleFieldTrips } -export default connect(mapStateToProps, mapDispatchToProps)( - injectIntl(FieldTripList) -) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(FieldTripList)) From e69758be318aa944081a01c2573f3c9454f535ad Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 18 Jul 2022 18:11:09 -0400 Subject: [PATCH 0373/1425] refactor(FieldTripDetails): Remove momentjs --- lib/components/admin/field-trip-details.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 0d55ec26d..788417c69 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -1,10 +1,9 @@ -/* eslint-disable react/jsx-handler-names */ /* eslint-disable react/prop-types */ import { connect } from 'react-redux' import { DropdownButton, MenuItem } from 'react-bootstrap' +import { format, formatDistanceToNow } from 'date-fns' import { getDateFormat } from '@opentripplanner/core-utils/lib/time' import { injectIntl } from 'react-intl' -import moment from 'moment' import React, { Component } from 'react' import styled from 'styled-components' @@ -13,6 +12,7 @@ import { getActiveFieldTripRequest } from '../../util/state' import { getGroupSize, GROUP_FIELDS, + parseDate, PAYMENT_FIELDS, TICKET_TYPES } from '../../util/call-taker' @@ -113,14 +113,14 @@ class FieldTripDetails extends Component { _renderHeader = () => { const { dateFormat, request } = this.props const { id, schoolName, travelDate } = request - const travelDateAsMoment = moment(travelDate) + const travelDateObject = parseDate(travelDate) return ( {schoolName} Trip (#{id})
    - Travel date: {travelDateAsMoment.format(dateFormat)} ( - {travelDateAsMoment.fromNow()}) + Travel date: {format(travelDateObject, dateFormat)} ( + {formatDistanceToNow(travelDateObject, { addSuffix: true })})
    From 2b28447b683691e1f92fafe95e8cccf6ff60a237 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Mon, 18 Jul 2022 23:11:42 -0700 Subject: [PATCH 0374/1425] refactor: remove velocity-react --- lib/components/app/app-menu.tsx | 17 ++-- lib/components/viewers/RouteRow.js | 22 +++-- lib/components/viewers/pattern-row.tsx | 108 ++++++++++++------------- package.json | 4 +- yarn.lock | 11 ++- 5 files changed, 87 insertions(+), 75 deletions(-) diff --git a/lib/components/app/app-menu.tsx b/lib/components/app/app-menu.tsx index c87eae577..7c0fd98ad 100644 --- a/lib/components/app/app-menu.tsx +++ b/lib/components/app/app-menu.tsx @@ -6,6 +6,7 @@ import React, { Component, Fragment } from 'react' // @ts-ignore import { MenuItem } from 'react-bootstrap' import { withRouter } from 'react-router' +import AnimateHeight from 'react-animate-height' import qs from 'qs' import SlidingPane from 'react-sliding-pane' import type { RouteComponentProps } from 'react-router' @@ -135,16 +136,14 @@ class AppMenu extends Component< /> - - {isSubmenuExpanded && ( -
    - {this._addExtraMenuItems(children)} -
    - )} -
    +
    + {this._addExtraMenuItems(children)} +
    + ) } diff --git a/lib/components/viewers/RouteRow.js b/lib/components/viewers/RouteRow.js index db9136fc2..a818ddf47 100644 --- a/lib/components/viewers/RouteRow.js +++ b/lib/components/viewers/RouteRow.js @@ -1,7 +1,7 @@ // TODO: Typescript, which doesn't make sense to do in this file until common types are established /* eslint-disable react/prop-types */ import { Button, Label } from 'react-bootstrap' -import { VelocityTransitionGroup } from 'velocity-react' +import AnimateHeight from 'react-animate-height' import React, { PureComponent } from 'react' import styled from 'styled-components' @@ -78,6 +78,7 @@ export class RouteRow extends PureComponent { super(props) // Create a ref used to scroll to this.activeRef = React.createRef() + this.state = { newHeight: 0 } } componentDidMount = () => { @@ -122,8 +123,14 @@ export class RouteRow extends PureComponent { render() { const { intl, isActive, operator, route } = this.props + const { newHeight } = this.state const { ModeIcon } = this.context + // Used to ensure details are visible until animation completes + const setNewHeight = (newHeight) => { + this.setState({ newHeight }) + } + return ( - - {isActive && } - + {(newHeight > 0 || isActive) && ( + + )} + ) } diff --git a/lib/components/viewers/pattern-row.tsx b/lib/components/viewers/pattern-row.tsx index d37442687..d6eefa4e6 100644 --- a/lib/components/viewers/pattern-row.tsx +++ b/lib/components/viewers/pattern-row.tsx @@ -1,6 +1,7 @@ import { FormattedMessage, injectIntl, IntlShape } from 'react-intl' // @ts-expect-error no types available import { VelocityTransitionGroup } from 'velocity-react' +import AnimateHeight from 'react-animate-height' import React, { Component } from 'react' import type { Route } from '@opentripplanner/types' @@ -117,66 +118,61 @@ class PatternRow extends Component {
    {/* expanded view */} - - {this.state.expanded && ( -
    -
    - {/* trips table header row */} -
    -
    -
    - -
    -
    - -
    + +
    +
    + {/* trips table header row */} +
    +
    +
    +
    +
    + +
    +
    - {/* list of upcoming trips */} - {hasStopTimes && - sortedStopTimes.map((stopTime, i) => { - const { departureDelay: delay, realtimeState } = stopTime - return ( -
    -
    - -
    -
    - -
    -
    - -
    + {/* list of upcoming trips */} + {hasStopTimes && + sortedStopTimes.map((stopTime, i) => { + const { departureDelay: delay, realtimeState } = stopTime + return ( +
    +
    +
    - ) - })} -
    +
    + +
    +
    + +
    +
    + ) + })}
    - )} - +
    +
    ) } diff --git a/package.json b/package.json index 0956e1880..a8a6fc77d 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "prop-types": "^15.6.0", "qs": "^6.3.0", "react": "<17.0.0", + "react-animate-height": "^3.0.4", "react-bootstrap": "^0.32.1", "react-dom": "<17.0.0", "react-draggable": "^4.4.3", @@ -118,7 +119,6 @@ "styled-components": "^5.0.0", "tinycolor2": "^1.4.2", "transitive-js": "^0.14.1", - "velocity-react": "^1.3.3", "yup": "^0.29.3" }, "devDependencies": { @@ -127,8 +127,8 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", - "@opentripplanner/types": "^2.0.0", "@opentripplanner/scripts": "^1.0.1", + "@opentripplanner/types": "^2.0.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", diff --git a/yarn.lock b/yarn.lock index c5ce0a01e..524d23eab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6615,7 +6615,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.2.6: +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== @@ -16840,6 +16840,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-animate-height@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-animate-height/-/react-animate-height-3.0.4.tgz#80c9cc25e8569709ad1c626b968dbe5108d0ce46" + integrity sha512-k+mBS8yCzpFp+7BdrHsL5bXd6CO/2bYO2SvRGKfxK+Ss3nzplAJLlgnd6Zhcxe/avdpy/CgcziicFj7pIHgG5g== + dependencies: + classnames "^2.3.1" + react-app-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" @@ -20328,7 +20335,7 @@ velocity-animate@^1.4.0: resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-1.5.2.tgz#5a351d75fca2a92756f5c3867548b873f6c32105" integrity sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg== -velocity-react@^1.3.3, velocity-react@^1.4.3: +velocity-react@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/velocity-react/-/velocity-react-1.4.3.tgz#63e41d92e334d5a3bea8b2fa02ee170f62ef4d36" integrity sha512-zvefGm85A88S3KdF9/dz5vqyFLAiwKYlXGYkHH2EbXl+CZUD1OT0a0aS1tkX/WXWTa/FUYqjBaAzAEFYuSobBQ== From f453468c35384c1d20815e89b4c0cc38a0cf4479 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Mon, 18 Jul 2022 23:11:52 -0700 Subject: [PATCH 0375/1425] test: update snapshots --- .../viewers/__snapshots__/stop-viewer.js.snap | 5400 +++++++++++++++-- 1 file changed, 5045 insertions(+), 355 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 273e9b469..9809ef808 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -1461,36 +1461,649 @@ exports[`components > viewers > stop viewer should render countdown times after
    - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.time.tripDurationFormat + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.thursday + + +
    +
    + + + 1:52 AM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.thursday + + +
    +
    + + + 2:53 AM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -3384,36 +3997,237 @@ exports[`components > viewers > stop viewer should render countdown times for st
    - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.time.tripDurationFormat + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -5421,80 +6235,726 @@ exports[`components > viewers > stop viewer should render times after midnight w
    - - -
    - - -
    - - -
    -
    - -
    + +
    + + + +
    + +
    - - -
    - - -
    - - - +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 6:00 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 6:15 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 6:31 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + +
    + @@ -9071,36 +11177,682 @@ exports[`components > viewers > stop viewer should render with OTP transit index - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 4:11 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 5:09 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.wednesday + + +
    +
    + + + 4:11 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -9518,36 +12270,682 @@ exports[`components > viewers > stop viewer should render with OTP transit index - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 3:22 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.wednesday + + +
    +
    + + + 3:22 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.thursday + + +
    +
    + + + 3:22 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -10073,36 +13471,682 @@ exports[`components > viewers > stop viewer should render with OTP transit index - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 2:28 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 3:02 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.tuesday + + +
    +
    + + + 3:12 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -14297,36 +18341,682 @@ exports[`components > viewers > stop viewer should render with TriMet transit in - - -
    - - +
    +
    +
    +
    +
    +
    + + components.PatternRow.departure + +
    +
    + + components.PatternRow.status + +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 5:45 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 6:00 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + components.PatternRow.routeShort + +
    +
    + +
    +
    + + + + + +
    +
    +
    + + + common.daysOfWeek.monday + + +
    +
    + + + 6:15 PM + + +
    +
    +
    +
    +
    +
    + + + +
    + +
    + } + status="scheduled" + > + + components.RealtimeStatusLabel.scheduled + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    From a8f75f3306d2f0db17a9eb384863323cab93e8a9 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Tue, 19 Jul 2022 14:37:59 +0200 Subject: [PATCH 0376/1425] refactor: don't use named map --- lib/components/map/connected-stop-viewer-overlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/map/connected-stop-viewer-overlay.js b/lib/components/map/connected-stop-viewer-overlay.js index eb6e6c348..38b58d09a 100644 --- a/lib/components/map/connected-stop-viewer-overlay.js +++ b/lib/components/map/connected-stop-viewer-overlay.js @@ -20,7 +20,7 @@ const StopViewerOverlay = ({ stopData, stops }) => { - const { mainMap } = useMap() + const { current: mainMap } = useMap() useEffect(() => { if (stopData?.lat && stopData?.lon) { From d87b01c37965c3c4382b98482444ebe2456d1fd7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 09:24:58 -0400 Subject: [PATCH 0377/1425] style(actions/field-trip): Apply lint/prettier rules --- lib/actions/field-trip.js | 891 ++++++++++++++++++++------------------ 1 file changed, 460 insertions(+), 431 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 4a8534582..561d74924 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -1,18 +1,19 @@ -import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' +import { createAction } from 'redux-actions' import { getRoutingParams, planParamsToQueryAsync } from '@opentripplanner/core-utils/lib/query' +import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' import { OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time' import { randId } from '@opentripplanner/core-utils/lib/storage' -import moment from 'moment' import { serialize } from 'object-to-formdata' +import moment from 'moment' import qs from 'qs' -import { createAction } from 'redux-actions' +import { getActiveItineraries, getActiveSearch } from '../util/state' import { getGroupSize, getTripFromRequest, @@ -21,18 +22,17 @@ import { sessionIsInvalid } from '../util/call-taker' import { getModuleConfig, Modules } from '../util/config' -import { getActiveItineraries, getActiveSearch } from '../util/state' +import { clearActiveSearch, resetForm, setQueryParam } from './form' import { routingQuery, setActiveItineraries, setPendingRequests, updateOtpUrlParams } from './api' -import {toggleCallHistory} from './call-taker' -import {clearActiveSearch, resetForm, setQueryParam} from './form' +import { toggleCallHistory } from './call-taker' -if (typeof (fetch) === 'undefined') require('isomorphic-fetch') +if (typeof fetch === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -41,8 +41,9 @@ const receiveTripHash = createAction('RECEIVE_TRIP_HASH') const requestingFieldTrips = createAction('REQUESTING_FIELD_TRIPS') const receivedFieldTripDetails = createAction('RECEIVED_FIELD_TRIP_DETAILS') const requestingFieldTripDetails = createAction('REQUESTING_FIELD_TRIP_DETAILS') -const setActiveItinerariesFromFieldTrip = - createAction('SET_ACTIVE_ITINERARIES_FROM_FIELD_TRIP') +const setActiveItinerariesFromFieldTrip = createAction( + 'SET_ACTIVE_ITINERARIES_FROM_FIELD_TRIP' +) // PUBLIC ACTIONS export const receivedFieldTrips = createAction('RECEIVED_FIELD_TRIPS') @@ -61,7 +62,7 @@ const FIELD_TRIP_TIME_FORMAT = 'HH:mm:ss' /** * Fully reset form and toggle field trips (and close call history if open). */ -export function resetAndToggleFieldTrips () { +export function resetAndToggleFieldTrips() { return async function (dispatch, getState) { dispatch(resetForm(true)) dispatch(toggleFieldTrips()) @@ -72,16 +73,20 @@ export function resetAndToggleFieldTrips () { /** * Fetch all field trip requests (as summaries). */ -export function fetchFieldTrips (intl) { +export function fetchFieldTrips(intl) { return async function (dispatch, getState) { dispatch(requestingFieldTrips()) - const {callTaker, otp} = getState() + const { callTaker, otp } = getState() if (sessionIsInvalid(callTaker.session)) return - const {datastoreUrl} = otp.config - const {sessionId} = callTaker.session + const { datastoreUrl } = otp.config + const { sessionId } = callTaker.session let fieldTrips = [] try { - const res = await fetch(`${datastoreUrl}/fieldtrip/getRequestsSummary?${qs.stringify({sessionId})}`) + const res = await fetch( + `${datastoreUrl}/fieldtrip/getRequestsSummary?${qs.stringify({ + sessionId + })}` + ) fieldTrips = await res.json() } catch (err) { alert( @@ -91,24 +96,29 @@ export function fetchFieldTrips (intl) { ) ) } - dispatch(receivedFieldTrips({fieldTrips})) + dispatch(receivedFieldTrips({ fieldTrips })) } } /** * Fetch details for a particular field trip request. */ -export function fetchFieldTripDetails (requestId, intl) { +export function fetchFieldTripDetails(requestId, intl) { return function (dispatch, getState) { dispatch(requestingFieldTripDetails()) - const {callTaker, otp} = getState() + const { callTaker, otp } = getState() if (sessionIsInvalid(callTaker.session)) return - const {datastoreUrl} = otp.config - const {sessionId} = callTaker.session - fetch(`${datastoreUrl}/fieldtrip/getRequest?${qs.stringify({requestId, sessionId})}`) - .then(res => res.json()) - .then(fieldTrip => dispatch(receivedFieldTripDetails({fieldTrip}))) - .catch(err => { + const { datastoreUrl } = otp.config + const { sessionId } = callTaker.session + fetch( + `${datastoreUrl}/fieldtrip/getRequest?${qs.stringify({ + requestId, + sessionId + })}` + ) + .then((res) => res.json()) + .then((fieldTrip) => dispatch(receivedFieldTripDetails({ fieldTrip }))) + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.fetchFieldTripError' }, @@ -122,22 +132,23 @@ export function fetchFieldTripDetails (requestId, intl) { /** * Add note for field trip request. */ -export function addFieldTripNote (request, note, intl) { +export function addFieldTripNote(request, note, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId, username} = callTaker.session + const { sessionId, username } = callTaker.session const noteData = serialize({ - note: {...note, userName: username}, + note: { ...note, userName: username }, requestId: request.id, sessionId }) - return fetch(`${datastoreUrl}/fieldtrip/addNote`, - {body: noteData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/addNote`, { + body: noteData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.addNoteError' }, @@ -151,17 +162,18 @@ export function addFieldTripNote (request, note, intl) { /** * Delete a specific note for a field trip request. */ -export function deleteFieldTripNote (request, noteId, intl) { +export function deleteFieldTripNote(request, noteId, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteNote`, - {body: serialize({ noteId, sessionId }), method: 'POST'} - ) + const { sessionId } = callTaker.session + return fetch(`${datastoreUrl}/fieldtrip/deleteNote`, { + body: serialize({ noteId, sessionId }), + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.deleteNoteError' }, @@ -175,22 +187,23 @@ export function deleteFieldTripNote (request, noteId, intl) { /** * Edit teacher (AKA submitter) notes for a field trip request. */ -export function editSubmitterNotes (request, submitterNotes, intl) { +export function editSubmitterNotes(request, submitterNotes, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session + const { sessionId } = callTaker.session const noteData = serialize({ notes: submitterNotes, requestId: request.id, sessionId }) - return fetch(`${datastoreUrl}/fieldtrip/editSubmitterNotes`, - {body: noteData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/editSubmitterNotes`, { + body: noteData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.editSubmitterNotesError' }, @@ -202,58 +215,54 @@ export function editSubmitterNotes (request, submitterNotes, intl) { } /** - * Validates and saves the currently active field trip itineraries to the - * appropriate inbound or outbound trip of a field trip request. - * - * @param {Object} request The field trip request - * @param {boolean} outbound If true, save the current itineraries to the - * outbound field trip journey. - * @param {Object} intl A format.js intl object + * Returns the earliest start time (in unix epoch milliseconds) in the given + * itineraries. */ -export function saveRequestTripItineraries (request, outbound, intl) { - return async function (dispatch, getState) { - const state = getState() - const { session } = state.callTaker - if (sessionIsInvalid(session)) return - - const itineraries = getActiveItineraries(state) - - // If plan is not valid, return before persisting trip. - if (fieldTripPlanIsInvalid(request, itineraries, intl)) return +function getEarliestStartTime(itineraries) { + return itineraries.reduce( + (earliestStartTime, itinerary) => + Math.min(earliestStartTime, itinerary.startTime), + Number.POSITIVE_INFINITY + ) +} - // Show a confirmation dialog before overwriting existing plan - if (!overwriteExistingRequestTripsConfirmed(request, outbound, intl)) return +/** + * Creates an OTP-style location string based on the location name, lat and lon. + */ +function getOtpLocationString(location) { + const latLonString = `${location.lat},${location.lon}` + return location.name ? `${location.name}::${latLonString}` : latLonString +} - // Send data to server for saving. - let text - try { - const res = await fetch( - `${state.otp.config.datastoreUrl}/fieldtrip/newTrip`, - { - body: makeSaveFieldTripItinerariesData(request, outbound, state), - method: 'POST' - } - ) - text = await res.text() - } catch (err) { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.saveItinerariesError' }, - { err: JSON.stringify(err) } - ) - ) - return - } +// These can be overridden in the field trip module config. +const defaultFieldTripModeCapacities = { + BUS: 40, + CABLE_CAR: 20, + FERRY: 100, + FUNICULAR: 20, + GONDOLA: 15, + RAIL: 80, + SUBWAY: 120, + TRAM: 80 +} - if (text === '-1') { - alert( - intl.formatMessage({ id: 'actions.fieldTrip.itineraryCapacityError' }) - ) - return - } +const unknownModeCapacity = 15 - dispatch(fetchFieldTripDetails(request.id, intl)) - } +/** + * Calculates the mode capacity based on the field trip module mode capacities + * (if it exists) or from the above default lookup of mode capacities or if + * given an unknown mode, then the unknownModeCapacity is returned. + * + * @param {Object} fieldTripModuleConfig the field trip module config + * @param {string} mode the OTP mode + */ +function getFieldTripGroupCapacityForMode(fieldTripModuleConfig, mode) { + const configModeCapacities = fieldTripModuleConfig?.modeCapacities + return ( + (configModeCapacities && configModeCapacities[mode]) || + defaultFieldTripModeCapacities[mode] || + unknownModeCapacity + ) } /** @@ -265,7 +274,7 @@ export function saveRequestTripItineraries (request, outbound, intl) { * @param {Object} intl A format.js intl object * @return true if invalid */ -function fieldTripPlanIsInvalid (request, itineraries, intl) { +function fieldTripPlanIsInvalid(request, itineraries, intl) { if (!itineraries || itineraries.length === 0) { return true } @@ -296,18 +305,6 @@ function fieldTripPlanIsInvalid (request, itineraries, intl) { return false } -/** - * Returns the earliest start time (in unix epoch milliseconds) in the given - * itineraries. - */ -function getEarliestStartTime (itineraries) { - return itineraries.reduce( - (earliestStartTime, itinerary) => - Math.min(earliestStartTime, itinerary.startTime), - Number.POSITIVE_INFINITY - ) -} - /** * Returns true if the user confirms the overwrite of an existing set of * itineraries planned for the inbound or outbound part of the field trip. @@ -317,10 +314,10 @@ function getEarliestStartTime (itineraries) { * outbound field trip journey. * @param {Object} intl A format.js intl object */ -function overwriteExistingRequestTripsConfirmed (request, outbound, intl) { +function overwriteExistingRequestTripsConfirmed(request, outbound, intl) { const preExistingTrip = getTripFromRequest(request, outbound) if (preExistingTrip) { - return confirm( + return window.confirm( intl.formatMessage( { id: 'actions.fieldTrip.confirmOverwriteItineraries' }, { outbound } @@ -334,7 +331,7 @@ function overwriteExistingRequestTripsConfirmed (request, outbound, intl) { * Constructs data used to save a set of itineraries for the inbound or outbound * part of the field trip. */ -function makeSaveFieldTripItinerariesData (request, outbound, state) { +function makeSaveFieldTripItinerariesData(request, outbound, state) { const { fieldTrip, session } = state.callTaker const { tripHashLookup } = fieldTrip const { config, currentQuery } = state.otp @@ -349,8 +346,9 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { sessionId: session.sessionId, trip: { createdBy: session.username, - departure: moment(getEarliestStartTime(itineraries)) - .format(FIELD_TRIP_DATE_TIME_FORMAT), + departure: moment(getEarliestStartTime(itineraries)).format( + FIELD_TRIP_DATE_TIME_FORMAT + ), destination: getOtpLocationString(currentQuery.from), origin: getOtpLocationString(currentQuery.to), passengers: getGroupSize(request), @@ -361,7 +359,7 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { // iterate through itineraries to construct itinerary and gtfsTrip data to // save the itineraries to otp-datastore. - itineraries.forEach((itinerary, itinIdx) => { + itineraries.forEach((itinerary) => { const itineraryDataToSave = { itinData: lzwEncode(JSON.stringify(itinerary)), passengers: itinerary.fieldTripGroupSize, @@ -369,11 +367,10 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { } const gtfsTripsForItinerary = [] - itinerary.legs.filter(leg => isTransit(leg.mode)) - .forEach(leg => { - let routeName = leg.routeShortName - ? `(${leg.routeShortName}) ` - : '' + itinerary.legs + .filter((leg) => isTransit(leg.mode)) + .forEach((leg) => { + let routeName = leg.routeShortName ? `(${leg.routeShortName}) ` : '' routeName = `${routeName}${leg.routeLongName}` const gtfsTrip = { agencyAndId: leg.tripId, @@ -402,25 +399,57 @@ function makeSaveFieldTripItinerariesData (request, outbound, state) { } /** - * Creates an OTP-style location string based on the location name, lat and lon. - */ -function getOtpLocationString (location) { - const latLonString = `${location.lat},${location.lon}` - return location.name - ? `${location.name}::${latLonString}` - : latLonString -} - -/** - * Begins the process of making trip requests to find suitable itineraries for - * either an inbound or outbound journey of a field trip. + * Validates and saves the currently active field trip itineraries to the + * appropriate inbound or outbound trip of a field trip request. + * + * @param {Object} request The field trip request + * @param {boolean} outbound If true, save the current itineraries to the + * outbound field trip journey. + * @param {Object} intl A format.js intl object */ -export function planTrip (request, outbound, intl) { +export function saveRequestTripItineraries(request, outbound, intl) { return async function (dispatch, getState) { - dispatch(clearSaveable()) - dispatch(setGroupSize(getGroupSize(request))) - await dispatch(prepareQueryParams(request, outbound)) - dispatch(makeFieldTripPlanRequests(request, outbound, intl)) + const state = getState() + const { session } = state.callTaker + if (sessionIsInvalid(session)) return + + const itineraries = getActiveItineraries(state) + + // If plan is not valid, return before persisting trip. + if (fieldTripPlanIsInvalid(request, itineraries, intl)) return + + // Show a confirmation dialog before overwriting existing plan + if (!overwriteExistingRequestTripsConfirmed(request, outbound, intl)) return + + // Send data to server for saving. + let text + try { + const res = await fetch( + `${state.otp.config.datastoreUrl}/fieldtrip/newTrip`, + { + body: makeSaveFieldTripItinerariesData(request, outbound, state), + method: 'POST' + } + ) + text = await res.text() + } catch (err) { + alert( + intl.formatMessage( + { id: 'actions.fieldTrip.saveItinerariesError' }, + { err: JSON.stringify(err) } + ) + ) + return + } + + if (text === '-1') { + alert( + intl.formatMessage({ id: 'actions.fieldTrip.itineraryCapacityError' }) + ) + return + } + + dispatch(fetchFieldTripDetails(request.id, intl)) } } @@ -428,9 +457,9 @@ export function planTrip (request, outbound, intl) { * Sets the appropriate request parameters for either the outbound or inbound * journey of a field trip. */ -function prepareQueryParams (request, outbound) { +function prepareQueryParams(request, outbound) { return async function (dispatch, getState) { - const {config} = getState().otp + const { config } = getState().otp const queryParams = { date: moment(request.travelDate).format(OTP_API_DATE_FORMAT) } @@ -441,148 +470,36 @@ function prepareQueryParams (request, outbound) { toPlace: request.endLocation } queryParams.departArrive = 'ARRIVE' - queryParams.time = moment(request.arriveDestinationTime) - .format(OTP_API_TIME_FORMAT) + queryParams.time = moment(request.arriveDestinationTime).format( + OTP_API_TIME_FORMAT + ) } else { locationsToGeocode = { fromPlace: request.endLocation, toPlace: request.startLocation } queryParams.departArrive = 'DEPART' - queryParams.time = moment(request.leaveDestinationTime) - .format(OTP_API_TIME_FORMAT) + queryParams.time = moment(request.leaveDestinationTime).format( + OTP_API_TIME_FORMAT + ) } const locations = await planParamsToQueryAsync(locationsToGeocode, config) return dispatch(setQueryParam({ ...locations, ...queryParams })) } } -/** - * Makes appropriate OTP requests until enough itineraries have been found to - * accommodate the field trip group. This is done as follows: - * - * 1. Fetch a list of all the existing transit trips that already have a field - * trip assigned to the trip. - * 2. In a loop of up to 10 times: - * i. Make a trip plan query to OTP for one additional itinerary - * ii. Calculate the trip hashes in the resulting itinerary by making requests - * to the OTP index API - * iii. Assign as many field trip travelers to the resulting itinerary as - * possible. - * iv. Check if there are still unassigned field trip travelers - * a. If there are still more to assign, ban each trip used in this - * itinerary in subsequent OTP plan requests. - * b. If all travelers have been assigned, exit the loop and cleanup - */ -function makeFieldTripPlanRequests (request, outbound, intl) { - return async function (dispatch, getState) { - const fieldTripModuleConfig = getModuleConfig( - getState(), - Modules.FIELD_TRIP - ) - - // request other known trip IDs that other field trips are using on the - // field trip request date - try { - await dispatch(getTripIdsForTravelDate(request)) - } catch (err) { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.fetchTripsForDateError' }, - { err: JSON.stringify(err) } - ) - ) - return - } - - // create a new searchId to use for making all requests - const searchId = randId() - - // set numItineraries param for field trip requests - dispatch(setQueryParam({ numItineraries: 1 })) - - // create a new set to keep track of trips that should be banned - const bannedTrips = new Set() - - // track number of requests made such that endless requesting doesn't occur - const maxRequests = fieldTripModuleConfig?.maxRequests || 10 - let numRequests = 0 - - let shouldContinueSearching = true - - // make requests until enough itineraries have been found to accommodate - // group - while (shouldContinueSearching) { - numRequests++ - if (numRequests > maxRequests) { - // max number of requests exceeded. Show error. - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.maxTripRequestsExceeded' } - ) - ) - return dispatch(doFieldTripPlanRequestCleanup(searchId)) - } - - // make next query. The second param instructs the action/reducer whether - // or not to override previous search results in the state. - await dispatch(routingQuery(searchId, numRequests > 1)) - - // set the pending amount of requests to be 2 so that there will always - // seem to be potentially additional requests that have to be made. If - // there aren't after making this next request, the pending amount will - // be set to 0. This needs to happen after the routingQuery so the search - // is defined. - dispatch(setPendingRequests({ pending: 2, searchId })) - - // obtain trip hashes from OTP Index API - await getMissingTripHashesForActiveItineraries() - - // check trip validity and calculate itinerary capacity - const { - assignedItinerariesByResponse, - remainingGroupSize, - tripsToBanInSubsequentSearches - } = checkValidityAndCapacity( - getState(), - request - ) - - // always update itineraries on each itineration - dispatch(setActiveItineraries({ - assignedItinerariesByResponse, - searchId - })) - - if (remainingGroupSize <= 0) { - // All members of the field trip group have been assigned! - shouldContinueSearching = false - dispatch(setSaveable(outbound)) - dispatch(doFieldTripPlanRequestCleanup(searchId)) - } else { - // Not enough acceptable itineraries have been generated. Request more. - - // Update banned trips - tripsToBanInSubsequentSearches.forEach(tripId => { - bannedTrips.add(tripId) - }) - dispatch(setQueryParam({ bannedTrips: [...bannedTrips].join(',') })) - } - } - } -} - /** * Makes a request to get data about other planned field trips happening on a * particular date. */ -function getTripIdsForTravelDate (request) { +function getTripIdsForTravelDate(request) { return async function (dispatch, getState) { const state = getState() - const {datastoreUrl} = state.otp.config - const {sessionId} = state.callTaker.session - const formattedTravelDate = moment(request.travelDate) - .format(FIELD_TRIP_DATE_FORMAT) + const { datastoreUrl } = state.otp.config + const { sessionId } = state.callTaker.session + const formattedTravelDate = moment(request.travelDate).format( + FIELD_TRIP_DATE_FORMAT + ) const params = { date: formattedTravelDate, sessionId @@ -595,9 +512,9 @@ function getTripIdsForTravelDate (request) { // add passengers and converted tripId to trips const trips = [] - fieldTrips.forEach(fieldTrip => { - fieldTrip.groupItineraries.forEach(groupItinerary => { - groupItinerary.trips.forEach(gtfsTrip => { + fieldTrips.forEach((fieldTrip) => { + fieldTrip.groupItineraries.forEach((groupItinerary) => { + groupItinerary.trips.forEach((gtfsTrip) => { // tripIds still stored as 'agencyAndId' in DB, so convert them to // be compatible with OTP responses gtfsTrip.tripId = gtfsTrip.agencyAndId.replace('_', ':') @@ -616,14 +533,14 @@ function getTripIdsForTravelDate (request) { * for any tripIds in the active itineraries that haven't already been obtained * from the OTP index API. */ -function getMissingTripHashesForActiveItineraries () { +function getMissingTripHashesForActiveItineraries() { return function (dispatch, getState) { const state = getState() const activeItineraries = getActiveItineraries(state) const { tripHashLookup } = state.otp.callTaker.fieldTrip const tripHashesToRequest = [] - activeItineraries.forEach(itinerary => { - itinerary.legs.forEach(leg => { + activeItineraries.forEach((itinerary) => { + itinerary.legs.forEach((leg) => { if (leg.tripId && !tripHashLookup[leg.tripId]) { tripHashesToRequest.push(leg.tripId) } @@ -633,12 +550,51 @@ function getMissingTripHashesForActiveItineraries () { const api = state.config.api const baseUrl = `${api.host}${api.port ? ':' + api.port : ''}${api.path}` - return Promise.all(tripHashesToRequest.map(tripId => { - return fetch(`${baseUrl}/index/trips/${tripId}/semanticHash`) - .then(res => res.text()) - .then(hash => dispatch(receiveTripHash({ hash, tripId }))) - })) + return Promise.all( + tripHashesToRequest.map((tripId) => { + return fetch(`${baseUrl}/index/trips/${tripId}/semanticHash`) + .then((res) => res.text()) + .then((hash) => dispatch(receiveTripHash({ hash, tripId }))) + }) + ) + } +} + +/** + * Checks whether an existing trip in use by another field trip overlaps with a + * a trip identified by tripId. + * + * @param leg The leg information of the trip in the associated + * itinerary + * @param tripHashLookup The lookup of trip hashes + * @param tripId The tripId to analyze with respect to a trip in use + * @param tripInUse The trip in use by an existing field trip. This is an + * otp-datastore object. + * @return true if the trips overlap + */ +function tripsOverlap(leg, tripHashLookup, tripId, tripInUse) { + // check if the trip is being used by another field trip + let sameVehicleTrip = false + if (tripId in tripHashLookup && tripInUse.tripHash) { + // use the trip hashes if available + sameVehicleTrip = tripHashLookup[tripId] === tripInUse.tripHash + } else { + // as fallback, compare the tripId strings + sameVehicleTrip = tripId === tripInUse.tripId + } + // not used by another vehicle, so this trip/vehicle is free to use + if (!sameVehicleTrip) return false + + // check if the stop ranges overlap. It is OK if one field trip begins + // where the other ends. + if ( + leg.from.stopIndex >= tripInUse.toStopIndex || + leg.to.stopIndex <= tripInUse.fromStopIndex + ) { + // legs do not overlap, so this trip/vehicle is free to use + return false } + return true } /** @@ -657,7 +613,7 @@ function getMissingTripHashesForActiveItineraries () { * representing tripIds that should be added to the list of tripIds to ban when * making requests for additional itineraries from OTP. */ -function checkValidityAndCapacity (state, request) { +function checkValidityAndCapacity(state, request) { const fieldTripModuleConfig = getModuleConfig(state, Modules.FIELD_TRIP) const minimumAllowableRemainingCapacity = fieldTripModuleConfig?.minimumAllowableRemainingCapacity || 10 @@ -679,65 +635,69 @@ function checkValidityAndCapacity (state, request) { // iterate through itineraries to check validity and assign field trip // groups - response.plan.itineraries.forEach((itinerary, idx) => { + response.plan.itineraries.forEach((itinerary) => { let itineraryCapacity = Number.POSITIVE_INFINITY // check each individual trip to see if there aren't any trips in this // itinerary that are already in use by another field trip - itinerary.legs.filter(leg => isTransit(leg.mode)).forEach(leg => { - const { tripId } = leg - - // this variable is used to track how many other field trips are using a - // particular trip - let capacityInUse = 0 - - // iterate over trips that are already being used by other field trips - // NOTE: In the use case of re-planning trips, there is currently no way - // to discern whether a tripInUse belongs to the current direction of - // the field trip being planned. Therefore, this will result in the - // re-planning of trips avoiding it's own previously planned trips - // that it currently has saved - travelDateTripsInUse.forEach(tripInUse => { - if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return - - // ranges overlap! Add number of passengers on this other field trip - // to total capacity in use - capacityInUse += tripInUse.passengers + itinerary.legs + .filter((leg) => isTransit(leg.mode)) + .forEach((leg) => { + const { tripId } = leg + + // this variable is used to track how many other field trips are using a + // particular trip + let capacityInUse = 0 + + // iterate over trips that are already being used by other field trips + // NOTE: In the use case of re-planning trips, there is currently no way + // to discern whether a tripInUse belongs to the current direction of + // the field trip being planned. Therefore, this will result in the + // re-planning of trips avoiding it's own previously planned trips + // that it currently has saved + travelDateTripsInUse.forEach((tripInUse) => { + if (!tripsOverlap(leg, tripHashLookup, tripId, tripInUse)) return + + // ranges overlap! Add number of passengers on this other field trip + // to total capacity in use + capacityInUse += tripInUse.passengers + }) + + // check if the remaining capacity on this trip is enough to allow more + // field trip passengers on board + const legModeCapacity = getFieldTripGroupCapacityForMode( + fieldTripModuleConfig, + leg.mode + ) + let remainingTripCapacity = legModeCapacity - capacityInUse + if (remainingTripCapacity < minimumAllowableRemainingCapacity) { + // This trip is already too "full" to allow any addition field trips + // on board. Ban this trip in future searches and don't use this + // itinerary in final results (set trip and itinerary capacity to 0). + remainingTripCapacity = 0 + } + + // always ban trips found in itineraries so that subsequent searches + // don't encounter them. + // TODO: a more advanced way of doing things might be to ban trip + // sequences to not find the same exact sequence, but also + // individual trips that are too full. + tripsToBanInSubsequentSearches.push(tripId) + + itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity) }) - // check if the remaining capacity on this trip is enough to allow more - // field trip passengers on board - const legModeCapacity = getFieldTripGroupCapacityForMode( - fieldTripModuleConfig, - leg.mode - ) - let remainingTripCapacity = legModeCapacity - capacityInUse - if (remainingTripCapacity < minimumAllowableRemainingCapacity) { - // This trip is already too "full" to allow any addition field trips - // on board. Ban this trip in future searches and don't use this - // itinerary in final results (set trip and itinerary capacity to 0). - remainingTripCapacity = 0 - } - - // always ban trips found in itineraries so that subsequent searches - // don't encounter them. - // TODO: a more advanced way of doing things might be to ban trip - // sequences to not find the same exact sequence, but also - // individual trips that are too full. - tripsToBanInSubsequentSearches.push(tripId) - - itineraryCapacity = Math.min(itineraryCapacity, remainingTripCapacity) - }) - if (itineraryCapacity > 0) { // itinerary has capacity, add to list and update remaining group size. // A field trip response is guaranteed to have only one itinerary, so it // ok to set the itinerary by response as an array with a single // itinerary. - assignedItinerariesByResponse[responseIdx] = [{ - ...itinerary, - fieldTripGroupSize: Math.min(itineraryCapacity, remainingGroupSize) - }] + assignedItinerariesByResponse[responseIdx] = [ + { + ...itinerary, + fieldTripGroupSize: Math.min(itineraryCapacity, remainingGroupSize) + } + ] remainingGroupSize -= itineraryCapacity } }) @@ -750,77 +710,12 @@ function checkValidityAndCapacity (state, request) { } } -/** - * Checks whether an existing trip in use by another field trip overlaps with a - * a trip identified by tripId. - * - * @param leg The leg information of the trip in the associated - * itinerary - * @param tripHashLookup The lookup of trip hashes - * @param tripId The tripId to analyze with respect to a trip in use - * @param tripInUse The trip in use by an existing field trip. This is an - * otp-datastore object. - * @return true if the trips overlap - */ -function tripsOverlap (leg, tripHashLookup, tripId, tripInUse) { - // check if the trip is being used by another field trip - let sameVehicleTrip = false - if (tripId in tripHashLookup && tripInUse.tripHash) { - // use the trip hashes if available - sameVehicleTrip = (tripHashLookup[tripId] === tripInUse.tripHash) - } else { - // as fallback, compare the tripId strings - sameVehicleTrip = (tripId === tripInUse.tripId) - } - // not used by another vehicle, so this trip/vehicle is free to use - if (!sameVehicleTrip) return false - - // check if the stop ranges overlap. It is OK if one field trip begins - // where the other ends. - if ( - leg.from.stopIndex >= tripInUse.toStopIndex || - leg.to.stopIndex <= tripInUse.fromStopIndex - ) { - // legs do not overlap, so this trip/vehicle is free to use - return false - } - return true -} - -// These can be overridden in the field trip module config. -const defaultFieldTripModeCapacities = { - 'BUS': 40, - 'CABLE_CAR': 20, - 'FERRY': 100, - 'FUNICULAR': 20, - 'GONDOLA': 15, - 'RAIL': 80, - 'SUBWAY': 120, - 'TRAM': 80 -} -const unknownModeCapacity = 15 - -/** - * Calculates the mode capacity based on the field trip module mode capacities - * (if it exists) or from the above default lookup of mode capacities or if - * given an unknown mode, then the unknownModeCapacity is returned. - * - * @param {Object} fieldTripModuleConfig the field trip module config - * @param {string} mode the OTP mode - */ -function getFieldTripGroupCapacityForMode (fieldTripModuleConfig, mode) { - const configModeCapacities = fieldTripModuleConfig?.modeCapacities - return (configModeCapacities && configModeCapacities[mode]) || - defaultFieldTripModeCapacities[mode] || - unknownModeCapacity -} - /** * Dispatches the appropriate cleanup actions after making requests to find the * itineraries for an inbound or outbound field trip journey. */ -function doFieldTripPlanRequestCleanup (searchId) { - return function (dispatch, getState) { +function doFieldTripPlanRequestCleanup(searchId) { + return function (dispatch) { // set pending searches to 0 to indicate searching is finished dispatch(setPendingRequests({ pending: 0, searchId })) // clear banned trips query param @@ -831,17 +726,18 @@ function doFieldTripPlanRequestCleanup (searchId) { /** * Removes the planned journey associated with the given id. */ -export function deleteRequestTripItineraries (request, tripId, intl) { +export function deleteRequestTripItineraries(request, tripId, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session - return fetch(`${datastoreUrl}/fieldtrip/deleteTrip`, - {body: serialize({ id: tripId, sessionId }), method: 'POST'} - ) + const { sessionId } = callTaker.session + return fetch(`${datastoreUrl}/fieldtrip/deleteTrip`, { + body: serialize({ id: tripId, sessionId }), + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.deleteItinerariesError' }, @@ -852,12 +748,139 @@ export function deleteRequestTripItineraries (request, tripId, intl) { } } +/** + * Makes appropriate OTP requests until enough itineraries have been found to + * accommodate the field trip group. This is done as follows: + * + * 1. Fetch a list of all the existing transit trips that already have a field + * trip assigned to the trip. + * 2. In a loop of up to 10 times: + * i. Make a trip plan query to OTP for one additional itinerary + * ii. Calculate the trip hashes in the resulting itinerary by making requests + * to the OTP index API + * iii. Assign as many field trip travelers to the resulting itinerary as + * possible. + * iv. Check if there are still unassigned field trip travelers + * a. If there are still more to assign, ban each trip used in this + * itinerary in subsequent OTP plan requests. + * b. If all travelers have been assigned, exit the loop and cleanup + */ +function makeFieldTripPlanRequests(request, outbound, intl) { + return async function (dispatch, getState) { + const fieldTripModuleConfig = getModuleConfig( + getState(), + Modules.FIELD_TRIP + ) + + // request other known trip IDs that other field trips are using on the + // field trip request date + try { + await dispatch(getTripIdsForTravelDate(request)) + } catch (err) { + alert( + intl.formatMessage( + { id: 'actions.fieldTrip.fetchTripsForDateError' }, + { err: JSON.stringify(err) } + ) + ) + return + } + + // create a new searchId to use for making all requests + const searchId = randId() + + // set numItineraries param for field trip requests + dispatch(setQueryParam({ numItineraries: 1 })) + + // create a new set to keep track of trips that should be banned + const bannedTrips = new Set() + + // track number of requests made such that endless requesting doesn't occur + const maxRequests = fieldTripModuleConfig?.maxRequests || 10 + let numRequests = 0 + + let shouldContinueSearching = true + + // make requests until enough itineraries have been found to accommodate + // group + while (shouldContinueSearching) { + numRequests++ + if (numRequests > maxRequests) { + // max number of requests exceeded. Show error. + alert( + intl.formatMessage({ + id: 'actions.fieldTrip.maxTripRequestsExceeded' + }) + ) + return dispatch(doFieldTripPlanRequestCleanup(searchId)) + } + + // make next query. The second param instructs the action/reducer whether + // or not to override previous search results in the state. + await dispatch(routingQuery(searchId, numRequests > 1)) + + // set the pending amount of requests to be 2 so that there will always + // seem to be potentially additional requests that have to be made. If + // there aren't after making this next request, the pending amount will + // be set to 0. This needs to happen after the routingQuery so the search + // is defined. + dispatch(setPendingRequests({ pending: 2, searchId })) + + // obtain trip hashes from OTP Index API + await getMissingTripHashesForActiveItineraries() + + // check trip validity and calculate itinerary capacity + const { + assignedItinerariesByResponse, + remainingGroupSize, + tripsToBanInSubsequentSearches + } = checkValidityAndCapacity(getState(), request) + + // always update itineraries on each itineration + dispatch( + setActiveItineraries({ + assignedItinerariesByResponse, + searchId + }) + ) + + if (remainingGroupSize <= 0) { + // All members of the field trip group have been assigned! + shouldContinueSearching = false + dispatch(setSaveable(outbound)) + dispatch(doFieldTripPlanRequestCleanup(searchId)) + } else { + // Not enough acceptable itineraries have been generated. Request more. + + // Update banned trips + tripsToBanInSubsequentSearches.forEach((tripId) => { + bannedTrips.add(tripId) + }) + dispatch(setQueryParam({ bannedTrips: [...bannedTrips].join(',') })) + } + } + } +} + +/** + * Begins the process of making trip requests to find suitable itineraries for + * either an inbound or outbound journey of a field trip. + */ +export function planTrip(request, outbound, intl) { + return async function (dispatch, getState) { + dispatch(clearSaveable()) + dispatch(setGroupSize(getGroupSize(request))) + await dispatch(prepareQueryParams(request, outbound)) + dispatch(makeFieldTripPlanRequests(request, outbound, intl)) + } +} + /** * Sets the appropriate query parameters for the saved field trip and loads the * saved itineraries from the field trip request and sets these loaded * itineraries as if they appeared from a new OTP trip plan request. */ -export function viewRequestTripItineraries (request, outbound) { +export function viewRequestTripItineraries(request, outbound) { return async function (dispatch, getState) { // set the appropriate query parameters as if the trip were being planned await dispatch(prepareQueryParams(request, outbound)) @@ -866,17 +889,20 @@ export function viewRequestTripItineraries (request, outbound) { const trip = getTripFromRequest(request, outbound) // decode the saved itineraries - const itineraries = trip.groupItineraries?.map(groupItin => - JSON.parse(lzwDecode(groupItin.itinData)) - ) || [] + const itineraries = + trip.groupItineraries?.map((groupItin) => + JSON.parse(lzwDecode(groupItin.itinData)) + ) || [] const searchId = randId() // set the itineraries in a new OTP response - dispatch(setActiveItinerariesFromFieldTrip({ - response: [{ plan: { itineraries } }], - searchId - })) + dispatch( + setActiveItinerariesFromFieldTrip({ + response: [{ plan: { itineraries } }], + searchId + }) + ) // appropriately initialize the URL params. If this doesn't happen, it won't // be possible to set an active itinerary properly due to funkiness with the @@ -889,22 +915,23 @@ export function viewRequestTripItineraries (request, outbound) { * Set group size for a field trip request. Group size consists of numStudents, * numFreeStudents, and numChaperones. */ -export function setRequestGroupSize (request, groupSize, intl) { +export function setRequestGroupSize(request, groupSize, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session + const { sessionId } = callTaker.session const groupSizeData = serialize({ ...groupSize, requestId: request.id, sessionId }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestGroupSize`, - {body: groupSizeData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/setRequestGroupSize`, { + body: groupSizeData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.setGroupSizeError' }, @@ -918,22 +945,23 @@ export function setRequestGroupSize (request, groupSize, intl) { /** * Set payment info for a field trip request. */ -export function setRequestPaymentInfo (request, paymentInfo, intl) { +export function setRequestPaymentInfo(request, paymentInfo, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session + const { sessionId } = callTaker.session const paymentData = serialize({ ...paymentInfo, requestId: request.id, sessionId }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestPaymentInfo`, - {body: paymentData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/setRequestPaymentInfo`, { + body: paymentData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.setPaymentError' }, @@ -947,22 +975,23 @@ export function setRequestPaymentInfo (request, paymentInfo, intl) { /** * Set field trip request status (e.g., cancelled). */ -export function setRequestStatus (request, status, intl) { +export function setRequestStatus(request, status, intl) { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const {datastoreUrl} = otp.config + const { callTaker, otp } = getState() + const { datastoreUrl } = otp.config if (sessionIsInvalid(callTaker.session)) return - const {sessionId} = callTaker.session + const { sessionId } = callTaker.session const statusData = serialize({ requestId: request.id, sessionId, status }) - return fetch(`${datastoreUrl}/fieldtrip/setRequestStatus`, - {body: statusData, method: 'POST'} - ) + return fetch(`${datastoreUrl}/fieldtrip/setRequestStatus`, { + body: statusData, + method: 'POST' + }) .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch(err => { + .catch((err) => { alert( intl.formatMessage( { id: 'actions.fieldTrip.setRequestStatusError' }, @@ -977,7 +1006,7 @@ export function setRequestStatus (request, status, intl) { * Clears and resets all relevant data after a field trip loses focus (upon * closing the field trip details window) */ -export function clearActiveFieldTrip () { +export function clearActiveFieldTrip() { return function (dispatch, getState) { dispatch(setActiveFieldTrip(null)) dispatch(clearActiveSearch()) From 77cbe7000c663823e56c6925494c6eba10400ab9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:22:29 -0400 Subject: [PATCH 0378/1425] refactor(actions/field-trip): Remove momentjs --- lib/actions/field-trip.js | 67 +++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 561d74924..55a0cf0e0 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -1,16 +1,17 @@ import { createAction } from 'redux-actions' +import { format } from 'date-fns' +import { format as formatInTz } from 'date-fns-tz' import { getRoutingParams, planParamsToQueryAsync } from '@opentripplanner/core-utils/lib/query' import { isTransit } from '@opentripplanner/core-utils/lib/itinerary' import { - OTP_API_DATE_FORMAT, + OTP_API_DATE_FORMAT_DATE_FNS, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time' import { randId } from '@opentripplanner/core-utils/lib/storage' import { serialize } from 'object-to-formdata' -import moment from 'moment' import qs from 'qs' import { getActiveItineraries, getActiveSearch } from '../util/state' @@ -19,6 +20,7 @@ import { getTripFromRequest, lzwDecode, lzwEncode, + parseDate, sessionIsInvalid } from '../util/call-taker' import { getModuleConfig, Modules } from '../util/config' @@ -55,8 +57,8 @@ export const clearSaveable = createAction('CLEAR_SAVEABLE') export const setSaveable = createAction('SET_SAVEABLE') // these are date/time formats specifically used by otp-datastore -const FIELD_TRIP_DATE_FORMAT = 'MM/DD/YYYY' -const FIELD_TRIP_DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss' +const FIELD_TRIP_DATE_FORMAT = 'MM/dd/yyyy' +const FIELD_TRIP_DATE_TIME_FORMAT = 'yyyy-MM-ddTHH:mm:ss' const FIELD_TRIP_TIME_FORMAT = 'HH:mm:ss' /** @@ -274,28 +276,32 @@ function getFieldTripGroupCapacityForMode(fieldTripModuleConfig, mode) { * @param {Object} intl A format.js intl object * @return true if invalid */ -function fieldTripPlanIsInvalid(request, itineraries, intl) { +function fieldTripPlanIsInvalid(request, itineraries, intl, homeTimezone) { if (!itineraries || itineraries.length === 0) { return true } const earliestStartTime = getEarliestStartTime(itineraries) - // FIXME: add back in offset? - const planDeparture = moment(earliestStartTime) // .add('hours', otp.config.timeOffset) - const requestDate = moment(request.travelDate) + // Timestamp, which translates to the correct date when formatting in the agency's homeTimezone. + const planDeparture = new Date(earliestStartTime) + // Midnight in the user's time zone, which does not need to be shifted when formatting. + const requestDate = parseDate(request.travelDate) - if ( - planDeparture.date() !== requestDate.date() || - planDeparture.month() !== requestDate.month() || - planDeparture.year() !== requestDate.year() - ) { + const formattedDepartureDate = formatInTz( + planDeparture, + FIELD_TRIP_DATE_FORMAT, + { timeZone: homeTimezone } + ) + const formattedRequestDate = format(requestDate, FIELD_TRIP_DATE_FORMAT) + + if (formattedDepartureDate !== formattedRequestDate) { alert( intl.formatMessage( { id: 'actions.fieldTrip.incompatibleTripDateError' }, { - requestDate: requestDate.format(FIELD_TRIP_DATE_FORMAT), - tripDate: planDeparture.format(FIELD_TRIP_DATE_FORMAT) + requestDate: formattedRequestDate, + tripDate: formattedDepartureDate } ) ) @@ -337,6 +343,7 @@ function makeSaveFieldTripItinerariesData(request, outbound, state) { const { config, currentQuery } = state.otp const fieldTripModuleConfig = getModuleConfig(state, Modules.FIELD_TRIP) const itineraries = getActiveItineraries(state) + const { homeTimezone } = config // initialize base object const data = { @@ -346,8 +353,10 @@ function makeSaveFieldTripItinerariesData(request, outbound, state) { sessionId: session.sessionId, trip: { createdBy: session.username, - departure: moment(getEarliestStartTime(itineraries)).format( - FIELD_TRIP_DATE_TIME_FORMAT + departure: formatInTz( + new Date(getEarliestStartTime(itineraries)), + FIELD_TRIP_DATE_TIME_FORMAT, + { timeZone: homeTimezone } ), destination: getOtpLocationString(currentQuery.from), origin: getOtpLocationString(currentQuery.to), @@ -374,12 +383,18 @@ function makeSaveFieldTripItinerariesData(request, outbound, state) { routeName = `${routeName}${leg.routeLongName}` const gtfsTrip = { agencyAndId: leg.tripId, - arrive: moment(leg.endTime).format(FIELD_TRIP_TIME_FORMAT), + // 'arrive' must be expressed in the agency's timezone + arrive: formatInTz(new Date(leg.endTime), FIELD_TRIP_TIME_FORMAT, { + timeZone: homeTimezone + }), capacity: getFieldTripGroupCapacityForMode( fieldTripModuleConfig, leg.mode ), - depart: moment(leg.startTime).format(FIELD_TRIP_TIME_FORMAT), + // 'depart' must be expressed in the agency's timezone + depart: formatInTz(new Date(leg.startTime), FIELD_TRIP_TIME_FORMAT, { + timeZone: homeTimezone + }), fromStopIndex: leg.from.stopIndex, fromStopName: leg.from.name, headsign: leg.headsign, @@ -411,12 +426,13 @@ export function saveRequestTripItineraries(request, outbound, intl) { return async function (dispatch, getState) { const state = getState() const { session } = state.callTaker + const { homeTimezone } = state.otp.config if (sessionIsInvalid(session)) return const itineraries = getActiveItineraries(state) // If plan is not valid, return before persisting trip. - if (fieldTripPlanIsInvalid(request, itineraries, intl)) return + if (fieldTripPlanIsInvalid(request, itineraries, intl, homeTimezone)) return // Show a confirmation dialog before overwriting existing plan if (!overwriteExistingRequestTripsConfirmed(request, outbound, intl)) return @@ -461,7 +477,7 @@ function prepareQueryParams(request, outbound) { return async function (dispatch, getState) { const { config } = getState().otp const queryParams = { - date: moment(request.travelDate).format(OTP_API_DATE_FORMAT) + date: format(parseDate(request.travelDate), OTP_API_DATE_FORMAT_DATE_FNS) } let locationsToGeocode if (outbound) { @@ -470,7 +486,8 @@ function prepareQueryParams(request, outbound) { toPlace: request.endLocation } queryParams.departArrive = 'ARRIVE' - queryParams.time = moment(request.arriveDestinationTime).format( + queryParams.time = format( + parseDate(request.arriveDestinationTime), OTP_API_TIME_FORMAT ) } else { @@ -479,7 +496,8 @@ function prepareQueryParams(request, outbound) { toPlace: request.startLocation } queryParams.departArrive = 'DEPART' - queryParams.time = moment(request.leaveDestinationTime).format( + queryParams.time = format( + parseDate(request.leaveDestinationTime), OTP_API_TIME_FORMAT ) } @@ -497,7 +515,8 @@ function getTripIdsForTravelDate(request) { const state = getState() const { datastoreUrl } = state.otp.config const { sessionId } = state.callTaker.session - const formattedTravelDate = moment(request.travelDate).format( + const formattedTravelDate = format( + parseDate(request.travelDate), FIELD_TRIP_DATE_FORMAT ) const params = { From e8b6ee7a19211f6fd0b5c25b78cb50375be67fb9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 11:28:39 -0400 Subject: [PATCH 0379/1425] fix(util/call-taker): Remove unnecessary tz conversions --- lib/util/call-taker.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index b21eba293..eb1673397 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,7 +1,5 @@ import { compareAsc, differenceInCalendarDays, parse } from 'date-fns' import { getRoutingParams } from '@opentripplanner/core-utils/lib/query' -import { getUserTimezone } from '@opentripplanner/core-utils/lib/time' -import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz' const ENTITY_DATE_TIME_FORMAT = 'MMM d, yyyy h:mm:ss a' @@ -16,14 +14,6 @@ export function parseDate(date) { return parse(date, ENTITY_DATE_TIME_FORMAT, referenceDateForDateFns) } -/** - * Helper function to convert a date from the browser's timezone to the specified one. - */ -export function toTimeZone(date, timeZone) { - const dateUtc = zonedTimeToUtc(date, getUserTimezone()) - return utcToZonedTime(dateUtc, timeZone) -} - export const TICKET_TYPES = { hop_new: 'Will purchase new Hop Card', hop_reload: 'Will reload existing Hop Card', @@ -172,12 +162,10 @@ export function getVisibleRequests(state) { // Potential date format detected in search term. const [month, day] = splitBySlash if (!isNaN(month) && !isNaN(day)) { - // If month and day seem to be numbers, check against request date - // in the the configured homeTimezone. - const date = toTimeZone( - parseDate(request.travelDate), - otp.config.homeTimezone - ) + // If month and day seem to be numbers, check against request date. + // (No timezone conversion is needed because parseDate and date.getMonth/getDate + // operate in the browser's time zone). + const date = parseDate(request.travelDate) return date.getMonth() + 1 === +month && date.getDate() === +day } } From 238f2d4b7b66a9732719f182537725bb25a00c16 Mon Sep 17 00:00:00 2001 From: caleb-diehl-ibigroup Date: Tue, 19 Jul 2022 11:56:08 -0700 Subject: [PATCH 0380/1425] elevation styling --- lib/components/map/elevation-point-marker.tsx | 17 +- lib/components/map/leg-diagram.js | 199 +++++++++++------- lib/components/map/map.css | 4 +- 3 files changed, 131 insertions(+), 89 deletions(-) diff --git a/lib/components/map/elevation-point-marker.tsx b/lib/components/map/elevation-point-marker.tsx index 6ef24e010..ae5674eaa 100644 --- a/lib/components/map/elevation-point-marker.tsx +++ b/lib/components/map/elevation-point-marker.tsx @@ -1,5 +1,4 @@ import { connect } from 'react-redux' -import { LeafletStyleMarker } from '@opentripplanner/base-map/lib/styled' import { Leg } from '@opentripplanner/types' import { Marker } from 'react-map-gl' import coreUtils from '@opentripplanner/core-utils' @@ -20,6 +19,15 @@ type Props = { class ElevationPointMarker extends Component { render() { const { diagramLeg, elevationPoint, showElevationProfile } = this.props + + const markerStyle = { + backgroundColor: '#87CEFA', + border: '2px solid #FFF', + borderRadius: '50%', + height: '15px', + width: '15px' + } + // Compute the elevation point marker, if activeLeg and elevation profile is enabled. let elevationPointMarker = null if (showElevationProfile && diagramLeg && elevationPoint) { @@ -30,12 +38,7 @@ class ElevationPointMarker extends Component { if (pos) { elevationPointMarker = ( - +
    ) } diff --git a/lib/components/map/leg-diagram.js b/lib/components/map/leg-diagram.js index 1ad10ad5c..1bef7fa7e 100644 --- a/lib/components/map/leg-diagram.js +++ b/lib/components/map/leg-diagram.js @@ -1,16 +1,17 @@ // FIXME: Remove this eslint rule exception. /* eslint-disable jsx-a11y/no-static-element-interactions */ -import memoize from 'lodash.memoize' -import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' -import React, {Component} from 'react' import { Button } from 'react-bootstrap' import { connect } from 'react-redux' +import coreUtils from '@opentripplanner/core-utils' +import memoize from 'lodash.memoize' +import PropTypes from 'prop-types' +import React, { Component } from 'react' import ReactResizeDetector from 'react-resize-detector' import { setElevationPoint, setLegDiagram } from '../../actions/map' -const { getElevationProfile, getTextWidth, legElevationAtDistance } = coreUtils.itinerary +const { getElevationProfile, getTextWidth, legElevationAtDistance } = + coreUtils.itinerary // Fixed dimensions for chart const height = 160 @@ -25,11 +26,12 @@ const METERS_TO_FEET = 3.28084 class LegDiagram extends Component { static propTypes = { elevationPoint: PropTypes.number, + leg: PropTypes.string, setElevationPoint: PropTypes.func, setLegDiagram: PropTypes.func } - constructor (props) { + constructor(props) { super(props) this.state = { useImperialUnits: true, @@ -37,7 +39,7 @@ class LegDiagram extends Component { } } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { const { leg } = this.props if (leg && prevProps.leg && leg.startTime !== prevProps.leg.startTime) { this._determineCompressionFactor(this.state.width, leg) @@ -60,7 +62,10 @@ class LegDiagram extends Component { /** Set mouse hover location for drawing elevation point. */ _onMouseMove = (evt) => { - const m = evt.clientX - this.container.getBoundingClientRect().left + this.container.scrollLeft + const m = + evt.clientX - + this.container.getBoundingClientRect().left + + this.container.scrollLeft this.props.setElevationPoint(m / this.state.xAxisCompression) } @@ -73,11 +78,11 @@ class LegDiagram extends Component { this.props.setElevationPoint(null) } - _unitConversion = () => this.state.useImperialUnits ? METERS_TO_FEET : 1 + _unitConversion = () => (this.state.useImperialUnits ? METERS_TO_FEET : 1) /** Round elevation to whole number and add symbol. */ - _formatElevation (elev) { - return Math.round(elev) + (this.state.useImperialUnits ? `'` : 'm') + _formatElevation(elev) { + return Math.round(elev) + (this.state.useImperialUnits ? "'" : 'm') } _getElevationProfile = memoize((leg) => { @@ -85,7 +90,7 @@ class LegDiagram extends Component { return getElevationProfile(leg.steps, this._unitConversion()) }) - render () { + render() { const { elevationPoint } = this.props const { xAxisCompression } = this.state @@ -98,7 +103,8 @@ class LegDiagram extends Component { const mainSvgContent = [] const foregroundSvgContent = [] - const { maxElev, minElev, points, traversed } = this._getElevationProfile(leg) + const { maxElev, minElev, points, traversed } = + this._getElevationProfile(leg) const SVG_WIDTH = traversed * xAxisCompression const range = maxElev - minElev @@ -122,12 +128,15 @@ class LegDiagram extends Component { // Draw the y-axis labels & guidelines for (let elev = minDisplayed; elev <= maxDisplayed; elev += rangeUnit) { - const y = topElevYPx + elevHeight - elevHeight * (elev - minDisplayed) / displayedRange + const y = + topElevYPx + + elevHeight - + (elevHeight * (elev - minDisplayed)) / displayedRange yAxisPanelSvgContent.push( @@ -137,8 +146,8 @@ class LegDiagram extends Component { backgroundSvgContent.push( { + if (str === 'Northwest') return 'NW' + if (str === 'Northeast') return 'NE' + if (str === 'Southwest') return 'SW' + if (str === 'Southeast') return 'SE' + if (str === 'North') return 'N' + if (str === 'East') return 'E' + if (str === 'South') return 'S' + if (str === 'West') return 'W' + if (str === 'Street') return 'St' + if (str === 'Avenue') return 'Ave' + if (str === 'Road') return 'Rd' + if (str === 'Drive') return 'Dr' + if (str === 'Boulevard') return 'Blvd' + return str + }) + .join(' ') + } + let currentX = 0 const ptArr = [] const stepArr = [currentX] @@ -163,12 +194,18 @@ class LegDiagram extends Component { for (let i = 0; i < step.elevation.length; i++) { const elevPair = step.elevation[i] if (previousPair) { - const diff = (elevPair.second - previousPair.second) * this._unitConversion() + const diff = + (elevPair.second - previousPair.second) * this._unitConversion() if (diff > 0) gain += diff else loss += diff } const x = currentX + elevPair.first * xAxisCompression // - firstX - const y = topElevYPx + elevHeight - elevHeight * (elevPair.second * this._unitConversion() - minDisplayed) / displayedRange + const y = + topElevYPx + + elevHeight - + (elevHeight * + (elevPair.second * this._unitConversion() - minDisplayed)) / + displayedRange ptArr.push([x, y]) previousPair = elevPair } @@ -178,8 +215,8 @@ class LegDiagram extends Component { mainSvgContent.push( 30) { mainSvgContent.push( - - - - {// FIXME: bug where gain is shown for a single step even though + { + // FIXME: bug where gain is shown for a single step even though // the elevation gain actually begins accumulating with a different step } - {gain >= 10 && ↑{this._formatElevation(gain)}{' '}} - {loss <= -10 && ↓{this._formatElevation(-loss)}} + {gain >= 10 && ( + + ↑{this._formatElevation(gain)} + {' '} + + )} + {loss <= -10 && ( + ↓{this._formatElevation(-loss)} + )} + + + ) } @@ -213,7 +263,7 @@ class LegDiagram extends Component { stepDetails.push({ gain, loss }) }) if (ptArr.length === 0) { - console.warn(`There is no elevation data to render for leg`, leg) + console.warn('There is no elevation data to render for leg', leg) return null } // Add initial point if the first elevation entry does not start at zero @@ -224,13 +274,15 @@ class LegDiagram extends Component { ptArr.push([ptArr[ptArr.length - 1][0], BASELINE_Y]) ptArr.push([0, BASELINE_Y]) // Construct and add the main elevation contour area - const pts = ptArr.map((pt, i) => i === 0 ? `M${pt[0]} ${pt[1]}` : `L${pt[0]} ${pt[1]}`).join(' ') + const pts = ptArr + .map((pt, i) => (i === 0 ? `M${pt[0]} ${pt[1]}` : `L${pt[0]} ${pt[1]}`)) + .join(' ') mainSvgContent.unshift( ) @@ -240,7 +292,7 @@ class LegDiagram extends Component { const elev = legElevationAtDistance(points, elevationPoint) const elevConverted = elev * this._unitConversion() const x = elevationPoint * xAxisCompression - for (var i = 0; i < stepArr.length; i++) { + for (let i = 0; i < stepArr.length; i++) { if (x >= stepArr[i] && x <= stepArr[i + 1]) { const beginStep = stepArr[i] // Mouse hover is at step i, add hover fill for street step and draw @@ -248,13 +300,14 @@ class LegDiagram extends Component { const stepWidth = stepArr[i + 1] - beginStep backgroundSvgContent.push( + y={0} + /> ) const name = compressStreetName(leg.steps[i].streetName) const fontSize = 22 @@ -275,7 +328,7 @@ class LegDiagram extends Component { } backgroundSvgContent.push( ) // Add the current elevation text label foregroundSvgContent.push( + y={y - 15} + > {this._formatElevation(elevConverted)} ) } } return ( -
    +
    {/* The y-axis labels, which are fixed to the left side */} -
    - - {yAxisPanelSvgContent} - +
    + {yAxisPanelSvgContent}
    {/* The main, scrollable diagram */}
    { this.container = container }} + ref={(container) => { + this.container = container + }} style={{ left: 40 }} > @@ -356,35 +414,16 @@ class LegDiagram extends Component { {/* The close button */} +
    ) } } -function compressStreetName (name) { - return name.split(' ').map(str => { - if (str === 'Northwest') return 'NW' - if (str === 'Northeast') return 'NE' - if (str === 'Southwest') return 'SW' - if (str === 'Southeast') return 'SE' - if (str === 'North') return 'N' - if (str === 'East') return 'E' - if (str === 'South') return 'S' - if (str === 'West') return 'W' - if (str === 'Street') return 'St' - if (str === 'Avenue') return 'Ave' - if (str === 'Road') return 'Rd' - if (str === 'Drive') return 'Dr' - if (str === 'Boulevard') return 'Blvd' - return str - }).join(' ') -} - // Connect to Redux store const mapStateToProps = (state, ownProps) => { diff --git a/lib/components/map/map.css b/lib/components/map/map.css index 2cb3349b7..5afdd6c3c 100644 --- a/lib/components/map/map.css +++ b/lib/components/map/map.css @@ -29,8 +29,8 @@ z-index: 1000; background-color: white; background-clip: padding-box; - border: 2px solid rgba(127, 127, 127, 0.5); - border-radius: 4px; + border: 1px solid rgba(170, 170, 170, 0.5); + border-radius: 10px; cursor: crosshair; } From 1645098b2f85b4716efcdc5a2711b3059968e4dd Mon Sep 17 00:00:00 2001 From: caleb-diehl-ibigroup Date: Tue, 19 Jul 2022 13:06:14 -0700 Subject: [PATCH 0381/1425] add navigation control --- lib/components/map/default-map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 0b577caa6..ea7325518 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import { connect } from 'react-redux' import { injectIntl } from 'react-intl' -import { Popup } from 'react-map-gl' +import { NavigationControl, Popup } from 'react-map-gl' import BaseMap from '@opentripplanner/base-map' import React, { Component } from 'react' import styled from 'styled-components' @@ -369,6 +369,7 @@ class DefaultMap extends Component { })} {/* Render custom overlays, if set. */} {typeof getCustomMapOverlays === 'function' && getCustomMapOverlays()} + ) From 33cfb102dc50acd51c5d71e1eec4ebf7e00ff241 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:23:04 -0400 Subject: [PATCH 0382/1425] refactor(DateTimeOptions): Remove momentjs Multiformat parsing still needs to be implemented. --- .../form/call-taker/date-time-options.js | 114 +++++++++++------- 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/lib/components/form/call-taker/date-time-options.js b/lib/components/form/call-taker/date-time-options.js index c4dba40ef..98c26c055 100644 --- a/lib/components/form/call-taker/date-time-options.js +++ b/lib/components/form/call-taker/date-time-options.js @@ -1,13 +1,15 @@ /* eslint-disable react/prop-types */ +import { connect } from 'react-redux' +import { format, toDate } from 'date-fns-tz' import { injectIntl } from 'react-intl' -import { - OTP_API_DATE_FORMAT, - OTP_API_TIME_FORMAT -} from '@opentripplanner/core-utils/lib/time' +import { isSameMinute } from 'date-fns' import { OverlayTrigger, Tooltip } from 'react-bootstrap' -import moment from 'moment' +import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' +const { getCurrentDate, OTP_API_DATE_FORMAT_DATE_FNS, OTP_API_TIME_FORMAT } = + coreUtils.time + function getDepartureOptions(intl) { return [ { @@ -27,7 +29,7 @@ function getDepartureOptions(intl) { } /** - * Time formats passed to moment.js used to parse the user's time input. + * Time formats passed to date-fns to parse the user's time input. */ const SUPPORTED_TIME_FORMATS = [ 'HH:mm:ss', @@ -41,17 +43,16 @@ const SUPPORTED_TIME_FORMATS = [ 'ha', 'h', 'HH:mm' -].map((format) => `YYYY-MM-DDT${format}`) +].map((format) => `yyyy-MM-dd'T'${format}`) /** - * Convert input moment object to date/time query params in OTP API format. - * @param {[type]} [time=moment(] [description] - * @return {[type]} [description] + * Convert input date object to date/time query params in OTP API format, + * expressed in the specified time zone. */ -function momentToQueryParams(time = moment()) { +function dateToQueryParams(date, timeZone) { return { - date: time.format(OTP_API_DATE_FORMAT), - time: time.format(OTP_API_TIME_FORMAT) + date: format(date, OTP_API_DATE_FORMAT_DATE_FNS, { timeZone }), + time: format(date, OTP_API_TIME_FORMAT, { timeZone }) } } @@ -60,7 +61,7 @@ function momentToQueryParams(time = moment()) { * Call Taker form. A few unique features/behaviors to note: * - when "leave now" is selected the time/date will now stay up to date in the * form and query params - * - the time input will interpret various time formats using moment.js so that + * - the time input will interpret various time formats so that * users can quickly type a shorthand value (5p) and have that be parsed into * the correct OTP format. * - when a user changes the date or time, "leave now" (if selected) will @@ -85,29 +86,39 @@ class DateTimeOptions extends Component { componentDidUpdate(prevProps) { const { date, departArrive, time } = this.props - const dateTime = this.dateTimeAsMoment() + const dateTime = this.dateTimeWithTz() const parsedTime = this.parseInputAsTime(this.state.timeInput, date) // Update time input if time changes and the parsed time does not match what // the user originally input (this helps avoid changing the time input while // the user is simultaneously updating it). - if (prevProps.time !== time && !parsedTime.isSame(dateTime)) { + if (prevProps.time !== time && !isSameMinute(parsedTime, dateTime)) { this._updateTimeInput(dateTime) } // If departArrive has been changed to leave now, begin auto refresh. if (departArrive !== prevProps.departArrive) { - if (departArrive === 'NOW') this._startAutoRefresh() - else this._stopAutoRefresh() + if (departArrive === 'NOW') { + this._startAutoRefresh() + this.setState({ inputTime: null }) + } else this._stopAutoRefresh() } } - _updateTimeInput = (time = moment()) => + _updateTimeInput = (time) => { // If auto-updating time input (for leave now), use short 24-hr format to // avoid writing a value that is too long for the time input's width. - this.setState({ timeInput: time.format('H:mm') }) + // The time is expressed in the agency's home time zone. + // console.log(time, format(time, 'H:mm', { timeZone: this.props.homeTimezone })) + this.setState({ + timeInput: format(time, 'H:mm', { timeZone: this.props.homeTimezone }) + }) + } _startAutoRefresh = () => { - const timer = window.setInterval(this._refreshDateTime, 1000) - this.setState({ timer }) + // Stop any timer that was there before creating a new timer. + const { timer } = this.state + if (timer) window.clearInterval(timer) + const newTimer = window.setInterval(this._refreshDateTime, 1000) + this.setState({ timer: newTimer }) } _stopAutoRefresh = () => { @@ -116,9 +127,8 @@ class DateTimeOptions extends Component { } _refreshDateTime = () => { - const now = moment() - this._updateTimeInput(now) - const dateTimeParams = momentToQueryParams(now) + const now = new Date() + const dateTimeParams = dateToQueryParams(now, this.props.homeTimezone) // Update query param if the current time has changed (i.e., on minute ticks). if (dateTimeParams.time !== this.props.time) { this.props.setQueryParam(dateTimeParams) @@ -128,12 +138,13 @@ class DateTimeOptions extends Component { _setDepartArrive = (evt) => { const { value: departArrive } = evt.target if (departArrive === 'NOW') { - const now = moment() - // If setting to leave now, update date/time and start auto refresh to keep - // form input in sync. + const now = new Date() + // If setting to leave now, update date/time to "now" in the agency's times zone + // and start auto refresh to keep form input in sync. + this.setState({ inputTime: null }) this.props.setQueryParam({ departArrive, - ...momentToQueryParams(now) + ...dateToQueryParams(now, this.props.homeTimezone) }) if (!this.state.timer) { this._startAutoRefresh() @@ -164,27 +175,37 @@ class DateTimeOptions extends Component { */ handleTimeFocus = (evt) => evt.target.select() + /** + * Parse a time input expressed in the agency time zone. + */ parseInputAsTime = ( timeInput, - date = moment().startOf('day').format('YYYY-MM-DD') - ) => { - return moment(date + 'T' + timeInput, SUPPORTED_TIME_FORMATS) - } + date = getCurrentDate(this.props.homeTimezone) + ) => toDate(`${date}T${timeInput}`, { timeZone: this.props.homeTimeZone }) - dateTimeAsMoment = () => moment(`${this.props.date}T${this.props.time}`) + dateTimeWithTz = () => this.parseInputAsTime(this.props.time, this.props.date) handleTimeChange = (evt) => { if (this.state.timer) this._stopAutoRefresh() const timeInput = evt.target.value const parsedTime = this.parseInputAsTime(timeInput) - this.handleDateTimeChange({ time: parsedTime.format(OTP_API_TIME_FORMAT) }) + this.handleDateTimeChange({ + time: format(parsedTime, OTP_API_TIME_FORMAT, { + timeZone: this.props.homeTimeZone + }) + }) this.setState({ timeInput }) } render() { - const { departArrive, intl, onKeyDown } = this.props + const { departArrive, homeTimezone, intl, onKeyDown, timeFormat } = + this.props const { timeInput } = this.state - const dateTime = this.dateTimeAsMoment() + const dateTime = this.dateTimeWithTz() + const nowFormatted = format(dateTime, 'H:mm', { + timeZone: homeTimezone + }) + return ( <> ) } } -export default injectIntl(DateTimeOptions) +// connect to the redux store +const mapStateToProps = (state) => { + const { dateTime, homeTimezone } = state.otp.config + return { + homeTimezone, + timeFormat: dateTime.timeFormat + } +} + +export default connect(mapStateToProps)(injectIntl(DateTimeOptions)) From 0711c6bc8a9402d8bb06ba099de65dc0543af676 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:41:17 -0400 Subject: [PATCH 0383/1425] refactor(DateTimeOptions): Implement multiformat parsing --- .../form/call-taker/date-time-options.js | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/components/form/call-taker/date-time-options.js b/lib/components/form/call-taker/date-time-options.js index 98c26c055..0606b86a2 100644 --- a/lib/components/form/call-taker/date-time-options.js +++ b/lib/components/form/call-taker/date-time-options.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux' import { format, toDate } from 'date-fns-tz' import { injectIntl } from 'react-intl' -import { isSameMinute } from 'date-fns' +import { isMatch, isSameMinute, parse } from 'date-fns' import { OverlayTrigger, Tooltip } from 'react-bootstrap' import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' @@ -43,7 +43,7 @@ const SUPPORTED_TIME_FORMATS = [ 'ha', 'h', 'HH:mm' -].map((format) => `yyyy-MM-dd'T'${format}`) +] /** * Convert input date object to date/time query params in OTP API format, @@ -88,11 +88,13 @@ class DateTimeOptions extends Component { const { date, departArrive, time } = this.props const dateTime = this.dateTimeWithTz() const parsedTime = this.parseInputAsTime(this.state.timeInput, date) - // Update time input if time changes and the parsed time does not match what - // the user originally input (this helps avoid changing the time input while - // the user is simultaneously updating it). - if (prevProps.time !== time && !isSameMinute(parsedTime, dateTime)) { - this._updateTimeInput(dateTime) + if (parsedTime) { + // Update time input if time changes and the parsed time does not match what + // the user originally input (this helps avoid changing the time input while + // the user is simultaneously updating it). + if (prevProps.time !== time && !isSameMinute(parsedTime, dateTime)) { + this._updateTimeInput(dateTime) + } } // If departArrive has been changed to leave now, begin auto refresh. if (departArrive !== prevProps.departArrive) { @@ -177,11 +179,27 @@ class DateTimeOptions extends Component { /** * Parse a time input expressed in the agency time zone. + * @returns A date if the parsing succeeded, or null. */ parseInputAsTime = ( timeInput, date = getCurrentDate(this.props.homeTimezone) - ) => toDate(`${date}T${timeInput}`, { timeZone: this.props.homeTimeZone }) + ) => { + // Match one of the supported time formats + const matchedTimeFormat = SUPPORTED_TIME_FORMATS.find((timeFormat) => + isMatch(timeInput, timeFormat) + ) + if (matchedTimeFormat) { + const resolvedDateTime = format( + parse(timeInput, matchedTimeFormat, new Date()), + 'HH:mm:ss' + ) + return toDate(`${date}T${resolvedDateTime}`, { + timeZone: this.props.homeTimeZone + }) + } + return null + } dateTimeWithTz = () => this.parseInputAsTime(this.props.time, this.props.date) @@ -189,11 +207,13 @@ class DateTimeOptions extends Component { if (this.state.timer) this._stopAutoRefresh() const timeInput = evt.target.value const parsedTime = this.parseInputAsTime(timeInput) - this.handleDateTimeChange({ - time: format(parsedTime, OTP_API_TIME_FORMAT, { - timeZone: this.props.homeTimeZone + if (parsedTime) { + this.handleDateTimeChange({ + time: format(parsedTime, OTP_API_TIME_FORMAT, { + timeZone: this.props.homeTimeZone + }) }) - }) + } this.setState({ timeInput }) } From d74990d8e6448220969f7526dfb1d76c96fe4e92 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:42:03 -0400 Subject: [PATCH 0384/1425] refactor: Remove moment-timezone --- lib/components/viewers/live-stop-times.tsx | 1 - lib/components/viewers/stop-viewer.js | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/components/viewers/live-stop-times.tsx b/lib/components/viewers/live-stop-times.tsx index 854ccb74f..94523b055 100644 --- a/lib/components/viewers/live-stop-times.tsx +++ b/lib/components/viewers/live-stop-times.tsx @@ -1,4 +1,3 @@ -import 'moment-timezone' import { FormattedMessage, FormattedTime } from 'react-intl' import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 4b5e60825..274d93893 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -1,5 +1,4 @@ /* eslint-disable react/prop-types */ -import 'moment-timezone' import { Alert, Button } from 'react-bootstrap' import { connect } from 'react-redux' import { format } from 'date-fns-tz' From a8c6f0d304578d082e788f2dc9926b65ebd40b18 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:42:41 -0400 Subject: [PATCH 0385/1425] chore(deps): Remove moment package dependencies --- package.json | 2 -- yarn.lock | 9 +-------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/package.json b/package.json index 6788d5227..97597462c 100644 --- a/package.json +++ b/package.json @@ -84,8 +84,6 @@ "lodash.isempty": "^4.4.0", "lodash.isequal": "^4.5.0", "lodash.memoize": "^4.1.2", - "moment": "^2.17.1", - "moment-timezone": "^0.5.33", "object-hash": "^1.3.1", "object-path": "^0.11.5", "object-to-formdata": "^4.1.0", diff --git a/yarn.lock b/yarn.lock index 1e1c2f69c..ec5a162bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14007,14 +14007,7 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@^0.5.33: - version "0.5.33" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" - integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0", moment@^2.17.1, moment@^2.24.0: +moment@^2.17.1, moment@^2.24.0: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== From e99f7e04ad557d46130d092b7f0a8986d5815df6 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Tue, 19 Jul 2022 15:43:41 -0700 Subject: [PATCH 0386/1425] refactor(mailables): code split PDFKit dependency --- lib/util/mailables.js | 156 +++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 79 deletions(-) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 8bb53ee1f..4d74b1154 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -1,64 +1,66 @@ import blobStream from 'blob-stream' import moment from 'moment' -import PDFDocument from './PDFDocumentWithTables' - /** * Fields use to construct PDF letter. The nested arrays * correspond to rows of inputs in the form layout. */ export const LETTER_FIELDS = [ [ - {fieldName: 'firstname', placeholder: 'First name'}, - {fieldName: 'lastname', placeholder: 'Last name'} + { fieldName: 'firstname', placeholder: 'First name' }, + { fieldName: 'lastname', placeholder: 'Last name' } ], [ - {fieldName: 'address1', placeholder: 'Address 1'}, - {fieldName: 'address2', placeholder: 'Address 2'} + { fieldName: 'address1', placeholder: 'Address 1' }, + { fieldName: 'address2', placeholder: 'Address 2' } ], [ - {fieldName: 'city', placeholder: 'City'}, - {fieldName: 'state', placeholder: 'State'}, - {fieldName: 'zip', placeholder: 'Zip'} + { fieldName: 'city', placeholder: 'City' }, + { fieldName: 'state', placeholder: 'State' }, + { fieldName: 'zip', placeholder: 'Zip' } ] ] /** - * Generate a PDF letter for the provided form data/config. - * This depends on the headerGraphic config field being provided and a valid URL - * to a .png image file. + * Handle preparing a new page for the PDF doc, adding a footer, header image, + * and resetting the currsor to the correct position. */ -export function createLetter (formData, mailablesConfig) { - const imageUrl = mailablesConfig.headerGraphic - // A valid URL must be provided in the config in order for the create letter - // approach to function properly. - if (!imageUrl) { - return alert(`Error constructing letter. Mailables headerGraphic config property is missing.`) +function preparePage(doc, imageData, otpConfig) { + const { footer, headerGraphic, headerGraphicHeight, headerGraphicWidth } = + otpConfig + // Store true bottom of page while bottom is temporarily moved to 0. + const bottom = doc.page.margins.bottom + doc.page.margins.bottom = 0 + // Add footer to page. + if (footer) { + doc + .fontSize(9) + .text(footer, 0.5 * (doc.page.width - 500), doc.page.height - 50, { + align: 'center', + lineBreak: false, + width: 500 + }) } - // This is a very goofy approach to convert an image URL to its image data for - // writing to the PDF, but it seems to be a solid approach. - const img = new Image() - img.crossOrigin = 'anonymous' - img.onload = () => { - var canvas = document.createElement('canvas') - canvas.width = img.width - canvas.height = img.height - - const ctx = canvas.getContext('2d') - ctx.drawImage(img, 0, 0) - - const data = canvas.toDataURL('image/png') - const imageData = {data, height: img.height, width: img.width} - writePDF(formData, imageData, mailablesConfig) + // Add header image. + if (headerGraphic) { + const width = headerGraphicWidth || (72 * imageData.width) / 300 + const height = headerGraphicHeight || (72 * imageData.height) / 300 + doc.image(imageData.data, 0.5 * (doc.page.width - 300), 40, { + align: 'center', + height, + width + }) } - img.src = imageUrl + // Reset font size, bottom margin, and text writer position. + doc.fontSize(12).text('', doc.page.margins.left, doc.page.margins.top) + doc.page.margins.bottom = bottom } /** * Write the provided formData and imageData (header image) to a PDF file and * open as a new tab. */ -function writePDF (formData, imageData, otpConfig) { +async function writePDF(formData, imageData, otpConfig) { const { address1 = '', address2, @@ -69,14 +71,16 @@ function writePDF (formData, imageData, otpConfig) { state = '', zip = '' } = formData - const {horizontalMargin, verticalMargin} = otpConfig + const { horizontalMargin, verticalMargin } = otpConfig const margins = { bottom: verticalMargin, left: horizontalMargin, right: horizontalMargin, top: verticalMargin } - const doc = new PDFDocument({margins}) + const { default: PDFDocument } = await import('./PDFDocumentWithTables') + console.log(PDFDocument) + const doc = new PDFDocument({ margins }) const stream = doc.pipe(blobStream()) preparePage(doc, imageData, otpConfig) doc.on('pageAdded', () => preparePage(doc, imageData, otpConfig)) @@ -85,7 +89,8 @@ function writePDF (formData, imageData, otpConfig) { doc.text(moment().format('MMMM D, YYYY')) // recipient address - doc.moveDown() + doc + .moveDown() .moveDown() .moveDown() .text(firstname.toUpperCase() + ' ' + lastname.toUpperCase()) @@ -96,22 +101,21 @@ function writePDF (formData, imageData, otpConfig) { doc.text(city.toUpperCase() + ', ' + state.toUpperCase() + ' ' + zip) // introduction block - doc.moveDown() - .moveDown() - .text(otpConfig.introduction) + doc.moveDown().moveDown().text(otpConfig.introduction) // table header - doc.font('Helvetica-Bold') + doc + .font('Helvetica-Bold') .moveDown() .moveDown() - .text('SUMMARY BY ITEM', {align: 'center'}) + .text('SUMMARY BY ITEM', { align: 'center' }) .moveDown() .moveDown() const tableData = { headers: ['Item', 'Quantity'], - rows: mailables.map(mailable => { - let {largeFormat, largePrint, name, quantity} = mailable + rows: mailables.map((mailable) => { + let { largeFormat, largePrint, name, quantity } = mailable if (largePrint && largeFormat) { name += ' (LARGE PRINT)' } @@ -125,10 +129,7 @@ function writePDF (formData, imageData, otpConfig) { }) // conclusion block - doc.moveDown() - .moveDown() - .font('Helvetica') - .text(otpConfig.conclusion) + doc.moveDown().moveDown().font('Helvetica').text(otpConfig.conclusion) doc.end() stream.on('finish', () => { @@ -138,37 +139,34 @@ function writePDF (formData, imageData, otpConfig) { } /** - * Handle preparing a new page for the PDF doc, adding a footer, header image, - * and resetting the currsor to the correct position. + * Generate a PDF letter for the provided form data/config. + * This depends on the headerGraphic config field being provided and a valid URL + * to a .png image file. */ -function preparePage (doc, imageData, otpConfig) { - const {footer, headerGraphic, headerGraphicHeight, headerGraphicWidth} = otpConfig - // Store true bottom of page while bottom is temporarily moved to 0. - const bottom = doc.page.margins.bottom - doc.page.margins.bottom = 0 - // Add footer to page. - if (footer) { - doc.fontSize(9) - .text( - footer, - 0.5 * (doc.page.width - 500), - doc.page.height - 50, - {align: 'center', lineBreak: false, width: 500} - ) - } - // Add header image. - if (headerGraphic) { - const width = headerGraphicWidth || 72 * imageData.width / 300 - const height = headerGraphicHeight || 72 * imageData.height / 300 - doc.image( - imageData.data, - 0.5 * (doc.page.width - 300), - 40, - {align: 'center', height, width} +export function createLetter(formData, mailablesConfig) { + const imageUrl = mailablesConfig.headerGraphic + // A valid URL must be provided in the config in order for the create letter + // approach to function properly. + if (!imageUrl) { + return alert( + 'Error constructing letter. Mailables headerGraphic config property is missing.' ) } - // Reset font size, bottom margin, and text writer position. - doc.fontSize(12) - .text('', doc.page.margins.left, doc.page.margins.top) - doc.page.margins.bottom = bottom + // This is a very goofy approach to convert an image URL to its image data for + // writing to the PDF, but it seems to be a solid approach. + const img = new Image() + img.crossOrigin = 'anonymous' + img.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = img.width + canvas.height = img.height + + const ctx = canvas.getContext('2d') + ctx.drawImage(img, 0, 0) + + const data = canvas.toDataURL('image/png') + const imageData = { data, height: img.height, width: img.width } + writePDF(formData, imageData, mailablesConfig) + } + img.src = imageUrl } From c711d7beef90abd5ae4ec647b35cd16c3a569e0a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 19 Jul 2022 19:07:14 -0400 Subject: [PATCH 0387/1425] refactor(CallTimeCounter): Fix types. --- lib/components/admin/call-time-counter.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/components/admin/call-time-counter.tsx b/lib/components/admin/call-time-counter.tsx index f42b16e5c..5a28e9844 100644 --- a/lib/components/admin/call-time-counter.tsx +++ b/lib/components/admin/call-time-counter.tsx @@ -2,6 +2,7 @@ import React, { Component } from 'react' interface State { counterString: number + timer?: number } /** @@ -10,42 +11,43 @@ interface State { */ export default class CallTimeCounter extends Component { state = { - counterString: 0 + counterString: 0, + timer: undefined } - componentDidMount() { + componentDidMount(): void { this._startRefresh() } - componentWillUnmount() { + componentWillUnmount(): void { this._stopRefresh() } /** * Formats seconds as hh:mm:ss string. */ - _formatSeconds = (seconds) => { + _formatSeconds = (seconds: number): string => { const date = new Date(0) date.setSeconds(seconds) return date.toISOString().substr(11, 8) } - _refreshCounter = () => { + _refreshCounter = (): void => { const counterString = this.state.counterString + 1 this.setState({ counterString }) } - _startRefresh = () => { + _startRefresh = (): void => { // Set refresh to every second. const timer = window.setInterval(this._refreshCounter, 1000) this.setState({ timer }) } - _stopRefresh = () => { + _stopRefresh = (): void => { window.clearInterval(this.state.timer) } - render() { + render(): JSX.Element { const { className } = this.props return (
    Date: Wed, 20 Jul 2022 10:59:31 +0200 Subject: [PATCH 0388/1425] refactor(narriative-itineraries): ensure itinerary rows always have an index --- lib/components/narrative/narrative-itineraries.js | 2 +- percy/percy.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index fa2f3b4e6..49148193f 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -280,7 +280,7 @@ class NarrativeItineraries extends Component { } return updatedItineraries }, []) - : itineraries + : itineraries.map((itin, index) => ({ ...itin, index })) // This loop determines if an itinerary uses a single or multiple modes const groupedMergedItineraries = mergedItineraries.reduce( diff --git a/percy/percy.test.js b/percy/percy.test.js index a2a26c2d0..112720506 100644 --- a/percy/percy.test.js +++ b/percy/percy.test.js @@ -160,6 +160,13 @@ test('OTP-RR', async () => { const [tripViewerButton] = await page.$x( "//button[contains(., 'Trip Viewer')]" ) + + // If the trip viewer button didn't appear, perhaps we need to click the itinerary again + if (!tripViewerButton) { + await page.click('.title:nth-of-type(1)') + await page.waitForTimeout(2000) + } + await tripViewerButton.click() await page.waitForSelector('div.trip-viewer-body') await page.waitForTimeout(1000) From 67dd022b082f3a14253ade87eb84da5ff50dd279 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 20 Jul 2022 09:13:36 -0400 Subject: [PATCH 0389/1425] test(a11y,percy): Update dateTime format for date-fns --- a11y/test-config.yml | 2 +- percy/har-mock-config.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/a11y/test-config.yml b/a11y/test-config.yml index ca32472cf..3c1e6045b 100644 --- a/a11y/test-config.yml +++ b/a11y/test-config.yml @@ -112,5 +112,5 @@ modes: dateTime: timeFormat: h:mm a - dateFormat: MM/DD/YYYY + dateFormat: MM/dd/yyyy longDateFormat: MMMM D, YYYY diff --git a/percy/har-mock-config.yml b/percy/har-mock-config.yml index fade442e3..a2105537b 100644 --- a/percy/har-mock-config.yml +++ b/percy/har-mock-config.yml @@ -112,5 +112,5 @@ modes: dateTime: timeFormat: h:mm a - dateFormat: MM/DD/YYYY + dateFormat: MM/dd/yyyy longDateFormat: MMMM D, YYYY From b982f8239352e8082bd6ee5632d5cd9bb7da275f Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Wed, 20 Jul 2022 09:08:07 -0700 Subject: [PATCH 0390/1425] chore(mailables): remove console.log --- lib/util/mailables.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 4d74b1154..57f715e25 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -79,7 +79,6 @@ async function writePDF(formData, imageData, otpConfig) { top: verticalMargin } const { default: PDFDocument } = await import('./PDFDocumentWithTables') - console.log(PDFDocument) const doc = new PDFDocument({ margins }) const stream = doc.pipe(blobStream()) preparePage(doc, imageData, otpConfig) From 7555c462a0af42ddea2c151f856880c4e940a017 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Thu, 21 Jul 2022 16:14:59 +0200 Subject: [PATCH 0391/1425] chore(deps): upgrade otp-ui packages --- package.json | 8 ++++---- yarn.lock | 46 +++++++++++++++------------------------------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 0956e1880..885ed89bd 100644 --- a/package.json +++ b/package.json @@ -42,17 +42,17 @@ "@opentripplanner/geocoder": "^1.2.1", "@opentripplanner/humanize-distance": "^1.2.0", "@opentripplanner/icons": "^1.2.2", - "@opentripplanner/itinerary-body": "^4.0.0", + "@opentripplanner/itinerary-body": "^4.0.1", "@opentripplanner/location-field": "1.12.3", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.4", - "@opentripplanner/printable-itinerary": "^2.0.1", + "@opentripplanner/printable-itinerary": "^2.0.2", "@opentripplanner/route-viewer-overlay": "^1.4.0", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^4.0.0", "@opentripplanner/transit-vehicle-overlay": "^2.4.0", "@opentripplanner/transitive-overlay": "^2.0.0", - "@opentripplanner/trip-details": "^2.0.0", + "@opentripplanner/trip-details": "^2.1.2", "@opentripplanner/trip-form": "^1.11.4", "@opentripplanner/trip-viewer-overlay": "^1.1.1", "@opentripplanner/vehicle-rental-overlay": "^1.4.3", @@ -127,8 +127,8 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", - "@opentripplanner/types": "^2.0.0", "@opentripplanner/scripts": "^1.0.1", + "@opentripplanner/types": "^2.0.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", diff --git a/yarn.lock b/yarn.lock index c5ce0a01e..ceac7cf19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2440,23 +2440,7 @@ "@opentripplanner/core-utils" "^4.5.0" prop-types "^15.7.2" -"@opentripplanner/itinerary-body@^3.0.1": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.0.4.tgz#ca9f1f575d6ad1ffe355d805a7ac0b9f9f126d81" - integrity sha512-OKnjcmTZJ8wLbjyQf76GGBS1reojh5E3iLVagJPcd0+cjy5QciqVwOahnoT+j7IQtyA5PCtAyqkC//bJvu24/Q== - dependencies: - "@opentripplanner/core-utils" "^4.11.2" - "@opentripplanner/humanize-distance" "^1.2.0" - "@opentripplanner/icons" "^1.2.2" - "@opentripplanner/location-icon" "^1.4.0" - "@styled-icons/fa-solid" "^10.34.0" - "@styled-icons/foundation" "^10.34.0" - flat "^5.0.2" - moment "^2.24.0" - react-resize-detector "^4.2.1" - velocity-react "^1.4.3" - -"@opentripplanner/itinerary-body@^3.1.1": +"@opentripplanner/itinerary-body@^3.0.5", "@opentripplanner/itinerary-body@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.1.1.tgz#854e83022de09cb023e7f96ae38bb6e625db82b5" integrity sha512-fGKu7xSHok5FcKK5lEHtintHiEpcsUm1NE35ZZHRW7b5ejqBedq8/Be4Fo5WocEWZz9MWb1yyG2YqctceBYofw== @@ -2472,10 +2456,10 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/itinerary-body@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-4.0.0.tgz#ddddb9279c62d38613fbfc6c8e51a4a25975479b" - integrity sha512-rGv8ZRiQxQ9lzXJsGZd67DMl7pc2byqJ7wUUYgSjUZ53uTvcXmdg16kHRLxEOdW09fjpMQ1Dv3ZYHrgvIhwhpw== +"@opentripplanner/itinerary-body@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-4.0.1.tgz#18142449b8e7593e7d65e51824203b0d717de362" + integrity sha512-qkV95MdH7/E7ETr/Sze55bjFrHCoXDZvstIX9sNkys7Y+TlTYa5LUag6L2wh5iyVIsqIGurhNgCeF/QxUz/ADw== dependencies: "@opentripplanner/core-utils" "^5.0.0" "@opentripplanner/humanize-distance" "^1.2.0" @@ -2518,12 +2502,12 @@ "@opentripplanner/from-to-location-picker" "^2.1.0" prop-types "^15.7.2" -"@opentripplanner/printable-itinerary@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-2.0.1.tgz#d1606dafd53350737f9a97918532687c76fdab68" - integrity sha512-bkb1s2ptzAENOmkdemCy70OrelwmNHNu3q/2C1OBqzOXyiBPO1qc3FW9BR6Io2Hw9wMSaoD03McrL/1SEGZlhQ== +"@opentripplanner/printable-itinerary@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-2.0.2.tgz#71d4bad158a1adfbfe9808dac5a2a920a397609f" + integrity sha512-TN21p52Y2a9JTwOSlC6KUMaxjYbIXqQN6L7tkW/A6RFE1I1guFTttZlEQaBdkvnJiKZk04V/UaX1golNq+eVGA== dependencies: - "@opentripplanner/itinerary-body" "^3.0.1" + "@opentripplanner/itinerary-body" "^3.0.5" "@opentripplanner/route-viewer-overlay@^1.4.0": version "1.4.0" @@ -2590,12 +2574,12 @@ lodash.isequal "^4.5.0" transitive-js "^0.14.1" -"@opentripplanner/trip-details@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-details/-/trip-details-2.0.0.tgz#ab168e4acbf168e31e9b6f36c83c9706db49f78b" - integrity sha512-njnwHr6nF1xmcnwh5tx1XVym6rCgIWSPadVy8LeOyZsHA5NlTeBHRVKwew0H+Dt5HuJtj/PjZpzArJMBZGMitg== +"@opentripplanner/trip-details@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-details/-/trip-details-2.1.2.tgz#911feed76d6addde6a386d8ded19b37c0bf27b08" + integrity sha512-Qw/qjO3r0P9fK0cnccic0Ro6mDZsNWidC/133IroZon34SFr3qHu7fw3u4B1ylRI86CX5tzUzZNeef8mq15PYg== dependencies: - "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/core-utils" "^5.0.1" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" velocity-react "^1.4.3" From 109b75b37c8ee528e77cdf43c2094ac4fbd4dba5 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 22 Jul 2022 15:20:43 +0200 Subject: [PATCH 0392/1425] refactor: support zoom to leg --- .../map/connected-transitive-overlay.tsx | 3 +- lib/util/state.js | 13 +++++++ yarn.lock | 39 +++++-------------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.tsx b/lib/components/map/connected-transitive-overlay.tsx index 25ee22cf8..d1b154b0d 100644 --- a/lib/components/map/connected-transitive-overlay.tsx +++ b/lib/components/map/connected-transitive-overlay.tsx @@ -2,7 +2,7 @@ import { connect } from 'react-redux' import { injectIntl, IntlShape } from 'react-intl' import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' -import { getTransitiveData } from '../../util/state' +import { getActiveLeg, getTransitiveData } from '../../util/state' type Props = { intl?: IntlShape @@ -28,6 +28,7 @@ const mapStateToProps = (state: Record, ownProps: Props) => { } const obj = { + activeLeg: getActiveLeg(state), labeledModes, styles, // @ts-expect-error state.js is not typescripted diff --git a/lib/util/state.js b/lib/util/state.js index d845d2e17..1623ab57d 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -32,6 +32,19 @@ export function getActiveSearch(state) { return state.otp.searches[state.otp.activeSearchId] } +/** + * Get the active leg object + * @param {Object} state the redux state object + * @returns {Object} an search object, or null if there is no active search + */ + export function getActiveLeg(state) { + const {activeLeg} = getActiveSearch(state) || {} + if (!activeLeg) return undefined + + const activeItinerary = getActiveItinerary(state) + return activeItinerary?.legs?.[activeLeg] +} + /** * Get timestamp in the expected format used by otp-datastore (call taker * back end). Defaults to now. diff --git a/yarn.lock b/yarn.lock index 1b1a1a19d..9ead8a017 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2400,7 +2400,7 @@ maplibre-gl "^2.1.9" react-map-gl "^7.0.15" -"@opentripplanner/core-utils@^4.11.0", "@opentripplanner/core-utils@^4.11.2", "@opentripplanner/core-utils@^4.11.5", "@opentripplanner/core-utils@^4.5.0": +"@opentripplanner/core-utils@^4.11.0", "@opentripplanner/core-utils@^4.11.2", "@opentripplanner/core-utils@^4.5.0": version "4.11.5" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-4.11.5.tgz#ac90d443009fab02aae971d4ed6c1e0fc7da134f" integrity sha512-oVVfbQqzHaY82cYLRm/GpybCo5LsDa8PTJQDU/w/DJLqSqyYm2I2sitQxk6jvmP6Zq80asdxMRvM8Si8yI77aQ== @@ -2418,7 +2418,7 @@ prop-types "^15.7.2" qs "^6.9.1" -"@opentripplanner/core-utils@^5.0.0", "@opentripplanner/core-utils@^5.0.1": +"@opentripplanner/core-utils@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-5.0.2.tgz#5340897aefeebc1cf3ff8b8fdd588659a524d370" integrity sha512-gqNCLYhgeZmABMAbmiHoK2JPKhp+UwPLSnl3tyaxpsHt40qz51FFa27XkEFMR8+NZdWKSfjSlTO2njuY2WsTEA== @@ -2458,7 +2458,7 @@ version "1.4.1" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^4.11.5" + "@opentripplanner/core-utils" "^6.0.0" "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" @@ -2510,22 +2510,6 @@ react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/itinerary-body@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-3.1.1.tgz#854e83022de09cb023e7f96ae38bb6e625db82b5" - integrity sha512-fGKu7xSHok5FcKK5lEHtintHiEpcsUm1NE35ZZHRW7b5ejqBedq8/Be4Fo5WocEWZz9MWb1yyG2YqctceBYofw== - dependencies: - "@opentripplanner/core-utils" "^5.0.0" - "@opentripplanner/humanize-distance" "^1.2.0" - "@opentripplanner/icons" "^1.2.2" - "@opentripplanner/location-icon" "^1.4.0" - "@styled-icons/fa-solid" "^10.34.0" - "@styled-icons/foundation" "^10.34.0" - flat "^5.0.2" - moment "^2.24.0" - react-resize-detector "^4.2.1" - velocity-react "^1.4.3" - "@opentripplanner/itinerary-body@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-4.0.0.tgz#ddddb9279c62d38613fbfc6c8e51a4a25975479b" @@ -2581,8 +2565,7 @@ dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^4.5.0" - maplibre-gl "^2.1.9" + "@opentripplanner/core-utils" "^6.0.0" point-in-polygon "^1.1.0" react-map-gl "^7.0.15" @@ -2601,8 +2584,7 @@ version "1.1.1" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^4.5.0" - "@opentripplanner/types" "^2.0.0" + "@opentripplanner/core-utils" "^6.0.0" "@opentripplanner/stops-overlay@../otp-ui/packages/stops-overlay": version "4.0.0" @@ -2615,7 +2597,7 @@ version "2.4.0" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^5.0.0" + "@opentripplanner/core-utils" "^6.0.0" "@opentripplanner/icons" "1.2.2" flat "^5.0.2" @@ -2624,8 +2606,8 @@ dependencies: "@mapbox/polyline" "^1.1.1" "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^5.0.1" - "@opentripplanner/itinerary-body" "^3.1.1" + "@opentripplanner/core-utils" "^6.0.0" + "@opentripplanner/itinerary-body" "^4.0.0" "@turf/bbox" "^6.5.0" lodash.isequal "^4.5.0" transitive-js "^0.14.1" @@ -2661,7 +2643,7 @@ dependencies: "@mapbox/polyline" "^1.1.0" "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/core-utils" "^6.0.0" "@opentripplanner/types@^2.0.0": version "2.0.0" @@ -2672,12 +2654,11 @@ version "1.4.3" dependencies: "@opentripplanner/base-map" "^3.0.0-alpha.1" - "@opentripplanner/core-utils" "^4.5.0" + "@opentripplanner/core-utils" "^6.0.0" "@opentripplanner/from-to-location-picker" "^2.1.2" "@styled-icons/fa-solid" "^10.34.0" flat "^5.0.2" lodash.memoize "^4.1.2" - react-map-gl "^7.0.15" "@percy/cli-build@1.0.0-beta.76": version "1.0.0-beta.76" From ba23fbe0ef40d7336c5881997d64ea2da80f833a Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Fri, 22 Jul 2022 15:21:00 +0200 Subject: [PATCH 0393/1425] refactor(route-viewer): make more use of route color --- lib/components/viewers/route-details.js | 9 +++++++-- lib/components/viewers/styled.js | 15 ++++++++------- lib/util/viewer.js | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/components/viewers/route-details.js b/lib/components/viewers/route-details.js index 7281738b8..d0d9ebb96 100644 --- a/lib/components/viewers/route-details.js +++ b/lib/components/viewers/route-details.js @@ -7,7 +7,8 @@ import React, { Component } from 'react' import { extractHeadsignFromPattern, - getColorAndNameFromRoute + getColorAndNameFromRoute, + getContrastYIQ } from '../../util/viewer' import { findStopsForPattern, @@ -135,7 +136,11 @@ class RouteDetails extends Component { // if no pattern is set, we are in the routeRow return ( - + {agency && ( diff --git a/lib/components/viewers/styled.js b/lib/components/viewers/styled.js index f402cb4c9..1cbe33ff5 100644 --- a/lib/components/viewers/styled.js +++ b/lib/components/viewers/styled.js @@ -2,9 +2,11 @@ import styled from 'styled-components' /** Route Details */ export const Container = styled.div` - overflow-y: hidden; + background-color: ${(props) => + props.full ? props.backgroundColor || '#ddd' : 'inherit'}; + color: ${(props) => (props.full ? props.textColor : 'inherit')}; height: 100%; - background-color: ${(props) => (props.full ? '#ddd' : 'inherit')}; + overflow-y: hidden; ` export const RouteNameContainer = styled.div` @@ -32,6 +34,10 @@ export const PatternContainer = styled.div` margin-bottom: 0px; white-space: nowrap; } + + select { + color: #333; + } } ` @@ -84,9 +90,4 @@ export const Stop = styled.a` stop's bar connects the previous bar with the current one */ top: -3.5rem; /* adjust position in a way that is agnostic to line-height */ } - - /* hide the first line between blobs */ - &:first-of-type::after { - background: transparent; - } ` diff --git a/lib/util/viewer.js b/lib/util/viewer.js index 2aa2e7741..60ee5804f 100644 --- a/lib/util/viewer.js +++ b/lib/util/viewer.js @@ -333,7 +333,7 @@ export function firstTransitLegIsRealtime(itinerary) { * * TODO: Move to @opentripplanner/core-utils once otp-rr uses otp-ui library. */ -function getContrastYIQ(routeColor) { +export function getContrastYIQ(routeColor) { const textColorOptions = [ tinycolor(routeColor).darken(80).toHexString(), tinycolor(routeColor).lighten(80).toHexString() From abfd401b939dd56467f338d1093b7b6ebc7481c3 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 22 Jul 2022 10:51:32 -0700 Subject: [PATCH 0394/1425] refactor(utils): add styled icons --- lib/components/util/styledIcon.tsx | 23 +++++++++++++++++++++++ package.json | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 lib/components/util/styledIcon.tsx diff --git a/lib/components/util/styledIcon.tsx b/lib/components/util/styledIcon.tsx new file mode 100644 index 000000000..0639e91b3 --- /dev/null +++ b/lib/components/util/styledIcon.tsx @@ -0,0 +1,23 @@ +import { StyledIconBase } from '@styled-icons/styled-icon' +import styled from 'styled-components' + +interface Props { + spaceRight: boolean +} + +const StyledIconWrapper = styled.span` + top: 0.125em; + position: relative; + display: inline-flex; + align-self: center; + ${StyledIconBase} { + width: 1em; + height: 1em; + top: 0.0125em; + position: relative; + margin: 0 0.125em; + } + margin: ${(props) => (props.spaceRight ? '0 0.125em' : '0 0')}; +` + +export default StyledIconWrapper diff --git a/package.json b/package.json index 0956e1880..55b7ca845 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,8 @@ "@opentripplanner/trip-form": "^1.11.4", "@opentripplanner/trip-viewer-overlay": "^1.1.1", "@opentripplanner/vehicle-rental-overlay": "^1.4.3", + "@styled-icons/fa-regular": "^10.34.0", + "@styled-icons/fa-solid": "^10.34.0", "blob-stream": "^0.1.3", "bootstrap": "^3.3.7", "bowser": "^1.9.3", @@ -127,8 +129,8 @@ "@babel/preset-typescript": "^7.15.0", "@craco/craco": "^6.3.0", "@jackwilsdon/craco-use-babelrc": "^1.0.0", - "@opentripplanner/types": "^2.0.0", "@opentripplanner/scripts": "^1.0.1", + "@opentripplanner/types": "^2.0.0", "@percy/cli": "^1.0.0-beta.76", "@percy/puppeteer": "^2.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", From 25a37129b12b4d3e919f963f58754e58d9e8c90e Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 22 Jul 2022 12:54:02 -0700 Subject: [PATCH 0395/1425] refactor(call-taker): replace icons with styledicons --- lib/components/admin/call-history-window.js | 62 -------- lib/components/admin/call-history-window.tsx | 87 +++++++++++ lib/components/admin/call-record.js | 108 -------------- lib/components/admin/call-record.tsx | 141 ++++++++++++++++++ ...er-controls.js => call-taker-controls.tsx} | 111 ++++++++------ lib/components/admin/draggable-window.js | 84 ----------- lib/components/admin/draggable-window.tsx | 97 ++++++++++++ lib/components/admin/{styled.js => styled.ts} | 29 ++-- lib/components/util/styledIcon.tsx | 19 ++- 9 files changed, 425 insertions(+), 313 deletions(-) delete mode 100644 lib/components/admin/call-history-window.js create mode 100644 lib/components/admin/call-history-window.tsx delete mode 100644 lib/components/admin/call-record.js create mode 100644 lib/components/admin/call-record.tsx rename lib/components/admin/{call-taker-controls.js => call-taker-controls.tsx} (61%) delete mode 100644 lib/components/admin/draggable-window.js create mode 100644 lib/components/admin/draggable-window.tsx rename lib/components/admin/{styled.js => styled.ts} (88%) diff --git a/lib/components/admin/call-history-window.js b/lib/components/admin/call-history-window.js deleted file mode 100644 index d623fdf58..000000000 --- a/lib/components/admin/call-history-window.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react' -import { injectIntl } from 'react-intl' -import { connect } from 'react-redux' - -import * as callTakerActions from '../../actions/call-taker' -import Icon from '../util/icon' - -import CallRecord from './call-record' -import DraggableWindow from './draggable-window' -import {WindowHeader} from './styled' - -function CallHistoryWindow (props) { - const {callTaker, fetchQueries, intl, searches, toggleCallHistory} = props - const {activeCall, callHistory} = callTaker - if (!callHistory.visible) return null - return ( - Call history} - onClickClose={toggleCallHistory} - style={{right: '15px', top: '50px', width: '450px'}} - > - {activeCall - ? - : null - } - {callHistory.calls.data.length > 0 - ? callHistory.calls.data.map((call, i) => ( - - )) - :
    No calls in history
    - } -
    - ) -} - -const mapStateToProps = (state, ownProps) => { - return { - callTaker: state.callTaker, - currentQuery: state.otp.currentQuery, - searches: state.otp.searches - } -} - -const mapDispatchToProps = { - fetchQueries: callTakerActions.fetchQueries, - toggleCallHistory: callTakerActions.toggleCallHistory -} - -export default connect(mapStateToProps, mapDispatchToProps)( - injectIntl(CallHistoryWindow) -) diff --git a/lib/components/admin/call-history-window.tsx b/lib/components/admin/call-history-window.tsx new file mode 100644 index 000000000..7bf4eec54 --- /dev/null +++ b/lib/components/admin/call-history-window.tsx @@ -0,0 +1,87 @@ +import { connect } from 'react-redux' +import { History } from '@styled-icons/fa-solid/History' +import { injectIntl, IntlShape, WrappedComponentProps } from 'react-intl' +import React from 'react' + +import * as callTakerActions from '../../actions/call-taker' + +import { WindowHeader } from './styled' +import CallRecord from './call-record' +import DraggableWindow from './draggable-window' +import StyledIconWrapper from '../util/styledIcon' + +type Props = { + callTaker: { + activeCall: any + callHistory: { + calls: { + data: Array + } + visible: boolean + } + } + fetchQueries: (callId: string, intl: IntlShape) => void + searches: Array + toggleCallHistory: () => null +} & WrappedComponentProps + +function CallHistoryWindow(props: Props) { + const { callTaker, fetchQueries, intl, searches, toggleCallHistory } = props + const { activeCall, callHistory } = callTaker + if (!callHistory.visible) return null + return ( + + + + + Call history + + } + onClickClose={toggleCallHistory} + style={{ right: '15px', top: '50px', width: '450px' }} + > + {activeCall ? ( + + ) : null} + {callHistory.calls.data.length > 0 ? ( + callHistory.calls.data.map((call, i) => ( + + )) + ) : ( +
    No calls in history
    + )} +
    + ) +} + +const mapStateToProps = (state: Record) => { + return { + callTaker: state.callTaker, + currentQuery: state.otp.currentQuery, + searches: state.otp.searches + } +} + +const mapDispatchToProps = { + fetchQueries: callTakerActions.fetchQueries, + toggleCallHistory: callTakerActions.toggleCallHistory +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(CallHistoryWindow)) diff --git a/lib/components/admin/call-record.js b/lib/components/admin/call-record.js deleted file mode 100644 index 80cbd41ca..000000000 --- a/lib/components/admin/call-record.js +++ /dev/null @@ -1,108 +0,0 @@ -import humanizeDuration from 'humanize-duration' -import moment from 'moment' -import React, { Component } from 'react' - -import {searchToQuery} from '../../util/call-taker' -import Icon from '../util/icon' - -import CallTimeCounter from './call-time-counter' -import QueryRecord from './query-record' -import {CallRecordButton, CallRecordIcon, QueryList} from './styled' - -/** - * Displays information for a particular call record in the Call Taker window. - */ -export default class CallRecord extends Component { - state = { - expanded: false - } - - _getCallDuration = () => { - const {call} = this.props - const start = moment(call.startTime) - const end = moment(call.endTime) - const millis = moment.duration(end.diff(start)).asMilliseconds() - return humanizeDuration(millis) - } - - _toggleExpanded = () => { - const {call, fetchQueries, intl} = this.props - const {expanded} = this.state - if (!expanded) { - fetchQueries(call.id, intl) - } - this.setState({expanded: !expanded}) - } - - render () { - // FIXME: consolidate red color with call taker controls - const RED = '#C35134' - const {call, inProgress, searches} = this.props - const {expanded} = this.state - if (!call) return null - if (inProgress) { - // Map search IDs made during active call to queries. - const activeQueries = call.searches - .map(searchId => searchToQuery(searches[searchId], call, {})) - return ( -
    -
    - - -
    - {' '} - [Active call] -
    - - In progress... click to save{' '} - ({call.searches.length} searches) - - - {activeQueries.length > 0 - ? activeQueries.map((query, i) => ( - - )) - : 'No queries recorded.' - } - -
    - ) - } - // Default (no active call) view - const startTimeMoment = moment(call.startTime) - return ( -
    - - - - {startTimeMoment.format('h:mm a, MMM D')} - - - {' '} - {this._getCallDuration()} - - - {expanded - ? - {call.queries && call.queries.length > 0 - ? call.queries.map((query, i) => ( - - )) - : 'No queries recorded.' - } - - : null - } -
    - ) - } -} diff --git a/lib/components/admin/call-record.tsx b/lib/components/admin/call-record.tsx new file mode 100644 index 000000000..710d8f87a --- /dev/null +++ b/lib/components/admin/call-record.tsx @@ -0,0 +1,141 @@ +import { Circle, Phone, Stop } from '@styled-icons/fa-solid' +import { Clock } from '@styled-icons/fa-regular' +import { IntlShape, WrappedComponentProps } from 'react-intl' +// @ts-expect-error Not typescripted +import humanizeDuration from 'humanize-duration' +import moment from 'moment' +import React, { Component } from 'react' + +import { searchToQuery } from '../../util/call-taker' +import StyledIconWrapper from '../util/styledIcon' + +import { CallRecordButton, CallRecordIcon, QueryList } from './styled' +import CallTimeCounter from './call-time-counter' +import QueryRecord from './query-record' + +type Props = { + call: { + endTime: string + id: string + queries: Array + searches: Array + startTime: string + } + fetchQueries?: (callId: string, intl: IntlShape) => void + inProgress?: boolean + searches?: Array +} & WrappedComponentProps + +/** + * Displays information for a particular call record in the Call Taker window. + */ +export default class CallRecord extends Component { + state = { + expanded: false + } + + _getCallDuration = () => { + const { call } = this.props + const start = moment(call.startTime) + const end = moment(call.endTime) + const millis = moment.duration(end.diff(start)).asMilliseconds() + return humanizeDuration(millis) + } + + _toggleExpanded = () => { + const { call, fetchQueries, intl } = this.props + const { expanded } = this.state + if (!expanded) { + fetchQueries(call.id, intl) + } + this.setState({ expanded: !expanded }) + } + + render() { + // FIXME: consolidate red color with call taker controls + const RED = '#C35134' + const { call, inProgress, searches } = this.props + const { expanded } = this.state + if (!call) return null + if (inProgress) { + // Map search IDs made during active call to queries. + const activeQueries = call.searches.map((searchId) => + searchToQuery(searches?.[searchId], call, {}) + ) + return ( +
    +
    + + + + +
    + + + + [Active call] +
    + + In progress... click{' '} + + + {' '} + to save ({call.searches.length} searches) + + + {activeQueries.length > 0 + ? activeQueries.map((query, i) => ( + // eslint-disable-next-line react/jsx-indent + + )) + : 'No queries recorded.'} + +
    + ) + } + // Default (no active call) view + const startTimeMoment = moment(call.startTime) + return ( +
    + + + + {startTimeMoment.format('h:mm a, MMM D')} + + + + + + {this._getCallDuration()} + + + {expanded ? ( + + {call.queries && call.queries.length > 0 + ? call.queries.map((query, i) => ( + // eslint-disable-next-line react/jsx-indent + + )) + : 'No queries recorded.'} + + ) : null} +
    + ) + } +} diff --git a/lib/components/admin/call-taker-controls.js b/lib/components/admin/call-taker-controls.tsx similarity index 61% rename from lib/components/admin/call-taker-controls.js rename to lib/components/admin/call-taker-controls.tsx index 263749089..7a4b4b7a1 100644 --- a/lib/components/admin/call-taker-controls.js +++ b/lib/components/admin/call-taker-controls.tsx @@ -1,13 +1,19 @@ -import React, { Component } from 'react' -import { injectIntl } from 'react-intl' import { connect } from 'react-redux' +import { injectIntl, IntlShape, WrappedComponentProps } from 'react-intl' +import React, { Component } from 'react' import * as apiActions from '../../actions/api' import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' import * as uiActions from '../../actions/ui' +import { + GraduationCap, + History, + Phone, + Plus, + Stop +} from '@styled-icons/fa-solid' import { isModuleEnabled, Modules } from '../../util/config' -import Icon from '../util/icon' import { CallHistoryButton, @@ -16,6 +22,28 @@ import { FieldTripsButton, ToggleCallButton } from './styled' +import StyledIconWrapper from '../util/styledIcon' + +type Props = { + beginCall: () => void + callTaker: { + activeCall: any + callHistory: { + calls: { + data: Array + } + visible: boolean + } + } + callTakerEnabled: boolean + endCall: (intl: IntlShape) => void + fetchCalls: (intl: IntlShape) => void + fetchFieldTrips: (intl: IntlShape) => void + fieldTripEnabled: boolean + resetAndToggleCallHistory: () => void + resetAndToggleFieldTrips: () => void + session: string +} & WrappedComponentProps /** * This component displays the controls for the Call Taker/Field Trip modules, @@ -24,8 +52,8 @@ import { * - view call list * - view field trip list */ -class CallTakerControls extends Component { - componentDidUpdate (prevProps) { +class CallTakerControls extends Component { + componentDidUpdate(prevProps: Props) { const { callTakerEnabled, fetchCalls, @@ -54,84 +82,82 @@ class CallTakerControls extends Component { // Show stop button if call not in progress. if (this._callInProgress()) { return ( - + + + ) } // No call is in progress. return ( <> - - + > + + + + + ) } _callInProgress = () => Boolean(this.props.callTaker.activeCall) - render () { + render() { const { callTakerEnabled, fieldTripEnabled, resetAndToggleCallHistory, - resetAndToggleFieldTrips, - session + resetAndToggleFieldTrips } = this.props // If no valid session is found, do not show calltaker controls. - if (!session) return null + // if (!session) return null return ( {/* Start/End Call button */} - {callTakerEnabled && + {callTakerEnabled && ( {this._renderCallButtonIcon()} - } - {this._callInProgress() - ? - : null - } + )} + {this._callInProgress() ? : null} {/* Call History toggle button */} - {callTakerEnabled && + {callTakerEnabled && ( - + + + - } + )} {/* Field Trip toggle button */} - {fieldTripEnabled && + {fieldTripEnabled && ( - + + + - } + )} ) } } -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state: Record) => { return { callTaker: state.callTaker, callTakerEnabled: isModuleEnabled(state, Modules.CALL_TAKER), @@ -151,6 +177,7 @@ const mapDispatchToProps = { setMainPanelContent: uiActions.setMainPanelContent } -export default connect(mapStateToProps, mapDispatchToProps)( - injectIntl(CallTakerControls) -) +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(CallTakerControls)) diff --git a/lib/components/admin/draggable-window.js b/lib/components/admin/draggable-window.js deleted file mode 100644 index 75532fd2f..000000000 --- a/lib/components/admin/draggable-window.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { Component } from 'react' -import Draggable from 'react-draggable' - -import Icon from '../util/icon' - -const noop = () => {} - -export default class DraggableWindow extends Component { - render () { - const { - children, - draggableProps, - footer, - header, - height = '245px', - onClickClose, - scroll = true, - style - } = this.props - const GREY_BORDER = '#777 1.3px solid' - return ( - -
    -
    - - {header} -
    -
    - {children} -
    - {footer && -
    - {footer} -
    - } -
    -
    - ) - } -} - -DraggableWindow.defaultProps = { - onClickClose: noop -} diff --git a/lib/components/admin/draggable-window.tsx b/lib/components/admin/draggable-window.tsx new file mode 100644 index 000000000..cbe628671 --- /dev/null +++ b/lib/components/admin/draggable-window.tsx @@ -0,0 +1,97 @@ +import { Times } from '@styled-icons/fa-solid' +import Draggable, { DraggableProps } from 'react-draggable' +import React, { Component, CSSProperties, ReactNode } from 'react' + +import StyledIconWrapper from '../util/styledIcon' + +interface Props { + children: ReactNode[] + draggableProps?: DraggableProps + footer?: ReactNode + header?: ReactNode + height?: string + onClickClose?: () => null + scroll?: boolean + style?: CSSProperties +} + +export default class DraggableWindow extends Component { + render(): ReactNode { + const { + children, + draggableProps, + footer, + header, + height = '245px', + onClickClose = () => null, + scroll = true, + style + } = this.props + const GREY_BORDER = '#777 1.3px solid' + const overwrittenDraggableProps = { + ...draggableProps, + cancel: 'input', + handle: '.handle' + } + return ( + +
    +
    + + {header} +
    +
    + {children} +
    + {footer && ( +
    + {footer} +
    + )} +
    +
    + ) + } +} diff --git a/lib/components/admin/styled.js b/lib/components/admin/styled.ts similarity index 88% rename from lib/components/admin/styled.js rename to lib/components/admin/styled.ts index 7ee01dbe4..680587461 100644 --- a/lib/components/admin/styled.js +++ b/lib/components/admin/styled.ts @@ -1,5 +1,5 @@ import { Button as BsButton } from 'react-bootstrap' -import styled, {css} from 'styled-components' +import styled, { css } from 'styled-components' import Icon from '../util/icon' @@ -19,15 +19,15 @@ const circleButtonStyle = css` color: white; position: absolute; z-index: 999999; + aspect-ratio: 1/1; ` export const CallHistoryButton = styled.button` ${circleButtonStyle} background-color: ${GREEN}; - height: 40px; margin-left: 69px; top: 140px; - width: 40px; + aspect-ratio: 1/1; ` export const CallTimeCounter = styled(DefaultCounter)` @@ -51,19 +51,18 @@ export const ControlsContainer = styled.div` export const FieldTripsButton = styled.button` ${circleButtonStyle} background-color: ${PURPLE}; - height: 50px; margin-left: 80px; top: 190px; - width: 50px; ` +type ToggleCallButtonProps = { + callInProgress?: boolean +} -export const ToggleCallButton = styled.button` +export const ToggleCallButton = styled.button` ${circleButtonStyle} - background-color: ${props => props.callInProgress ? RED : BLUE}; - height: 80px; + background-color: ${(props) => (props.callInProgress ? RED : BLUE)}; margin-left: -8px; top: 154px; - width: 80px; ` // Field Trip Windows Components @@ -80,16 +79,14 @@ export const Container = styled.div` ` export const Half = styled.div` - width: 50% + width: 50%; ` -export const CallRecordRow = styled.div` - -` +export const CallRecordRow = styled.div`` export const CallRecordButton = styled.button` display: flex; - flexDirection: row; + flexdirection: row; width: 100%; ` @@ -108,7 +105,7 @@ export const FieldTripRecordButton = styled.button` ` export const Full = styled.div` - width: 100% + width: 100%; ` export const FullWithMargin = styled(Full)` @@ -145,7 +142,7 @@ export const Text = styled.span` export const Val = styled.span` :empty:before { - color: #685C5C; + color: #685c5c; content: 'N/A'; } ` diff --git a/lib/components/util/styledIcon.tsx b/lib/components/util/styledIcon.tsx index 0639e91b3..b08851597 100644 --- a/lib/components/util/styledIcon.tsx +++ b/lib/components/util/styledIcon.tsx @@ -2,7 +2,22 @@ import { StyledIconBase } from '@styled-icons/styled-icon' import styled from 'styled-components' interface Props { - spaceRight: boolean + flipHorizontal?: boolean + size?: string + spaceRight?: boolean +} + +const getFontSize = (size?: string) => { + switch (size) { + case '2x': + return '2em' + case '3x': + return '3em' + case '4x': + return '4em' + default: + return 'inherit' + } } const StyledIconWrapper = styled.span` @@ -16,6 +31,8 @@ const StyledIconWrapper = styled.span` top: 0.0125em; position: relative; margin: 0 0.125em; + font-size: ${(props) => getFontSize(props.size)}; + transform: ${(props) => (props.flipHorizontal ? 'scale(-1,1)' : '')}; } margin: ${(props) => (props.spaceRight ? '0 0.125em' : '0 0')}; ` From 4dc497694f846106f3bcdd59eb9c83918da6db86 Mon Sep 17 00:00:00 2001 From: Daniel Heppner Date: Fri, 22 Jul 2022 12:55:59 -0700 Subject: [PATCH 0396/1425] refactor(call-taker-controls): remove debug code --- lib/components/admin/call-taker-controls.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/components/admin/call-taker-controls.tsx b/lib/components/admin/call-taker-controls.tsx index 7a4b4b7a1..e1d068647 100644 --- a/lib/components/admin/call-taker-controls.tsx +++ b/lib/components/admin/call-taker-controls.tsx @@ -113,10 +113,11 @@ class CallTakerControls extends Component { callTakerEnabled, fieldTripEnabled, resetAndToggleCallHistory, - resetAndToggleFieldTrips + resetAndToggleFieldTrips, + session } = this.props // If no valid session is found, do not show calltaker controls. - // if (!session) return null + if (!session) return null return ( {/* Start/End Call button */} From 292181c4439ad16d50a1e1860fd7504c3b1f2721 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:23:42 -0400 Subject: [PATCH 0397/1425] refactor(SessionTimeout): Add TS types. --- lib/components/app/session-timeout.tsx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index 88398c47e..bbe29c485 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* eslint-disable react/prop-types */ import { Button, Modal } from 'react-bootstrap' import { connect } from 'react-redux' import { FormattedMessage } from 'react-intl' @@ -7,13 +5,26 @@ import React, { Component } from 'react' import * as uiActions from '../../actions/ui' +interface Props { + lastActionMillis: number + resetSessionTimeout: () => void + sessionTimeoutSeconds: number + startOverFromInitialUrl: () => void +} + +interface State { + showTimeoutWarning: boolean + timeoutObject: NodeJS.Timer + timeoutStartMillis: number +} + /** * This component makes the current session timeout * by displaying a timeout warning one minute before the timeout, * and by reloading the initial URL if there is no user-initiated * actions within the timeout window. */ -class SessionTimeout extends Component { +class SessionTimeout extends Component { state = { showTimeoutWarning: false, timeoutObject: null, @@ -27,7 +38,6 @@ class SessionTimeout extends Component { } componentWillUnmount() { - // @ts-ignore SessionTimeout is not typed yet clearInterval(this.state.timeoutObject) } @@ -39,7 +49,6 @@ class SessionTimeout extends Component { } handleTimeoutWatch = () => { - // @ts-ignore SessionTimeout is not typed yet const { lastActionMillis, sessionTimeoutSeconds, startOverFromInitialUrl } = this.props if (lastActionMillis > this.state.timeoutStartMillis) { @@ -68,18 +77,14 @@ class SessionTimeout extends Component { } handleKeepSession = () => { - // @ts-ignore SessionTimeout is not typed yet this.setState({ showTimeoutWarning: false }) - // @ts-ignore SessionTimeout is not typed yet this.props.resetSessionTimeout() } render() { - // @ts-ignore SessionTimeout is not typed yet const { startOverFromInitialUrl } = this.props - // @ts-ignore SessionTimeout is not typed yet const { showTimeoutWarning } = this.state return showTimeoutWarning ? ( @@ -106,7 +111,6 @@ class SessionTimeout extends Component { } } -// @ts-ignore SessionTimeout is not typed yet const mapStateToProps = (state) => { const { config, lastActionMillis } = state.otp const { sessionTimeoutSeconds } = config From d3f9bf464fa8aa7ad41b9c356f38134e98cc251a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:32:40 -0400 Subject: [PATCH 0398/1425] refactor(SessionTimeout): Fix types --- lib/components/app/session-timeout.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index bbe29c485..e7c960adf 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -14,7 +14,7 @@ interface Props { interface State { showTimeoutWarning: boolean - timeoutObject: NodeJS.Timer + timeoutObject?: NodeJS.Timer timeoutStartMillis: number } @@ -27,7 +27,7 @@ interface State { class SessionTimeout extends Component { state = { showTimeoutWarning: false, - timeoutObject: null, + timeoutObject: undefined, timeoutStartMillis: 0 } @@ -111,7 +111,7 @@ class SessionTimeout extends Component { } } -const mapStateToProps = (state) => { +const mapStateToProps = (state: any) => { const { config, lastActionMillis } = state.otp const { sessionTimeoutSeconds } = config return { From 7e586aca03155f00c3790a4058c755541f7dcdc2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:36:07 -0400 Subject: [PATCH 0399/1425] chore(en-US.yml): Tweak text per PR comment --- i18n/en-US.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/en-US.yml b/i18n/en-US.yml index 0c1cd0492..cfe2845d0 100644 --- a/i18n/en-US.yml +++ b/i18n/en-US.yml @@ -402,9 +402,9 @@ components: SearchScreen: header: Plan Your Trip SessionTimeout: - body: Your session will expire within a minute. Press 'Keep Session' to keep your search. + body: Your session will expire within a minute. Press 'Continue Session' to keep your search. header: Session about to timeout! - keepSession: Keep Session + keepSession: Continue Session SettingsPreview: defaultPreviewText: "Transit Options\n& Preferences" SimpleRealtimeAnnotation: From 08564816c5df4ff839f6af5a6fe6b42e28d7f0bc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 22 Jul 2022 18:16:54 -0400 Subject: [PATCH 0400/1425] refactor(SessionTimeout): Add clarifying comments per PR review. --- lib/actions/ui.js | 3 +++ lib/components/app/session-timeout.tsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 93e7cbe04..f9d47989a 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -402,6 +402,9 @@ export function setRouteViewerFilter(filter) { /** * Start over by setting the initial URL and resetting (reloading) the page. + * Note: This code is slightly different from the code behind the "Start Over" menu/button + * in the sense that it preserves the initial URL with original query parameters. + * TODO: Make the code from "Start Over" reuse this function. */ export function startOverFromInitialUrl() { return function (dispatch, getState) { diff --git a/lib/components/app/session-timeout.tsx b/lib/components/app/session-timeout.tsx index e7c960adf..92c7e2bbb 100644 --- a/lib/components/app/session-timeout.tsx +++ b/lib/components/app/session-timeout.tsx @@ -34,6 +34,8 @@ class SessionTimeout extends Component { componentDidMount() { // Wait one second or so after loading before probing for changes // so that initialization actions can complete. + // This just delays the start of the session timer, so that checks under `this.handleTimeoutWatch` + // don't get performed unnecessarily with all the stuff that normally occurs during a page load. setTimeout(this.handleAfterInitialActions, 1500) } @@ -51,6 +53,7 @@ class SessionTimeout extends Component { handleTimeoutWatch = () => { const { lastActionMillis, sessionTimeoutSeconds, startOverFromInitialUrl } = this.props + // Ignore actions happening before the session timer is started. if (lastActionMillis > this.state.timeoutStartMillis) { const idleMillis = new Date().valueOf() - lastActionMillis const secondsToTimeout = sessionTimeoutSeconds - idleMillis / 1000 From c2b159239f3b0dafe072ac2b95f2717669494b30 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Jul 2022 11:23:02 -0400 Subject: [PATCH 0401/1425] refactor(FieldTripDetails): Remove unnecessary state. --- lib/components/admin/field-trip-details.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 2aa964c8d..0b9c65a57 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -45,15 +45,6 @@ const WindowHeader = styled(DefaultWindowHeader)` * Shows the details for the active Field Trip Request. */ class FieldTripDetails extends Component { - constructor(props) { - super(props) - this.state = { - // Changes every time a field trip travel date is persisted - // (used to reset the field trip date input). - travelDateUpdateCount: 0 - } - } - _editSubmitterNotes = (val) => { const { editSubmitterNotes, intl, request } = this.props editSubmitterNotes(request, val, intl) @@ -88,9 +79,6 @@ class FieldTripDetails extends Component { const { dateFormat, intl, request, setRequestDate } = this.props const convertedRequestDate = moment(newDate).format(dateFormat) setRequestDate(request, convertedRequestDate, intl) - this.setState({ - travelDateUpdateCount: this.state.travelDateUpdateCount + 1 - }) } } @@ -134,7 +122,6 @@ class FieldTripDetails extends Component { _renderHeader = () => { const { request } = this.props - const { travelDateUpdateCount } = this.state const { id, schoolName, travelDate } = request const travelDateAsMoment = moment(travelDate) const travelDateFormatted = travelDateAsMoment.format('YYYY-MM-DD') @@ -146,10 +133,14 @@ class FieldTripDetails extends Component { Travel date: Date: Mon, 25 Jul 2022 12:53:23 -0400 Subject: [PATCH 0402/1425] refactor(actions/field-trip): Create enum for field trip endpoints. --- lib/actions/field-trip.js | 82 +++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index b18cb92a4..882b9c719 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -59,6 +59,17 @@ const FIELD_TRIP_DATE_FORMAT = 'MM/DD/YYYY' const FIELD_TRIP_DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss' const FIELD_TRIP_TIME_FORMAT = 'HH:mm:ss' +const FieldTripEndPoints = { + ADD_NOTE: 'addNote', + DELETE_NOTE: 'deleteNote', + DELETE_TRIP: 'deleteTrip', + EDIT_SUBMITTER_NOTES: 'editSubmitterNotes', + SET_REQUEST_DATE: 'setRequestDate', + SET_REQUEST_GROUP_SIZE: 'setRequestGroupSize', + SET_REQUEST_PAYMENT_INFO: 'setRequestPaymentInfo', + SET_REQUEST_STATUS: 'setRequestStatus' +} + /** * Returns the earliest start time (in unix epoch milliseconds) in the given * itineraries. @@ -203,36 +214,6 @@ export function fetchFieldTripDetails(requestId, intl) { } } -/** - * Add note for field trip request. - */ -export function addFieldTripNote(request, note, intl) { - return function (dispatch, getState) { - const { callTaker, otp } = getState() - const { datastoreUrl } = otp.config - if (sessionIsInvalid(callTaker.session)) return - const { sessionId, username } = callTaker.session - const noteData = serialize({ - note: { ...note, userName: username }, - requestId: request.id, - sessionId - }) - return fetch(`${datastoreUrl}/fieldtrip/addNote`, { - body: noteData, - method: 'POST' - }) - .then(() => dispatch(fetchFieldTripDetails(request.id, intl))) - .catch((err) => { - alert( - intl.formatMessage( - { id: 'actions.fieldTrip.addNoteError' }, - { err: JSON.stringify(err) } - ) - ) - }) - } -} - /** * Invokes OTP Datatstore to update a field trip request or its related notes or itineraries. */ @@ -265,13 +246,40 @@ function updateFieldTripRequest( } } +/** + * Add note for field trip request. + */ +export function addFieldTripNote(request, note, intl) { + return function (dispatch, getState) { + const { callTaker } = getState() + if (sessionIsInvalid(callTaker.session)) return + const { username } = callTaker.session + const noteData = { + note: { ...note, userName: username } + } + dispatch( + updateFieldTripRequest( + request, + FieldTripEndPoints.ADD_NOTE, + noteData, + intl, + () => + intl.formatMessage({ + id: 'actions.fieldTrip.addNoteError' + }), + true // include requestId when adding note. + ) + ) + } +} + /** * Delete a specific note for a field trip request. */ export function deleteFieldTripNote(request, noteId, intl) { return updateFieldTripRequest( request, - 'deleteNote', + FieldTripEndPoints.DELETE_NOTE, { noteId }, intl, () => @@ -288,7 +296,7 @@ export function deleteFieldTripNote(request, noteId, intl) { export function editSubmitterNotes(request, submitterNotes, intl) { return updateFieldTripRequest( request, - 'editSubmitterNotes', + FieldTripEndPoints.EDIT_SUBMITTER_NOTES, { notes: submitterNotes }, intl, () => @@ -866,7 +874,7 @@ export function planTrip(request, outbound, intl) { export function deleteRequestTripItineraries(request, tripId, intl) { return updateFieldTripRequest( request, - 'deleteTrip', + FieldTripEndPoints.DELETE_TRIP, { id: tripId }, intl, () => @@ -920,7 +928,7 @@ export function viewRequestTripItineraries(request, outbound) { export function setRequestGroupSize(request, groupSize, intl) { return updateFieldTripRequest( request, - 'setRequestGroupSize', + FieldTripEndPoints.SET_REQUEST_GROUP_SIZE, groupSize, intl, () => @@ -937,7 +945,7 @@ export function setRequestGroupSize(request, groupSize, intl) { export function setRequestDate(request, date, intl) { return updateFieldTripRequest( request, - 'setRequestDate', + FieldTripEndPoints.SET_REQUEST_DATE, { date }, intl, () => @@ -954,7 +962,7 @@ export function setRequestDate(request, date, intl) { export function setRequestPaymentInfo(request, paymentInfo, intl) { return updateFieldTripRequest( request, - 'setRequestPaymentInfo', + FieldTripEndPoints.SET_REQUEST_PAYMENT_INFO, paymentInfo, intl, () => @@ -971,7 +979,7 @@ export function setRequestPaymentInfo(request, paymentInfo, intl) { export function setRequestStatus(request, status, intl) { return updateFieldTripRequest( request, - 'setRequestStatus', + FieldTripEndPoints.SET_REQUEST_STATUS, { status }, intl, () => From cf6df2005fe5aebcc8324738f7e5c56ddde4976f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Jul 2022 13:18:32 -0400 Subject: [PATCH 0403/1425] refactor(FieldTripDetails): Reset date control when selecting a different field trip. --- lib/components/admin/field-trip-details.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 0b9c65a57..a971188de 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -138,9 +138,10 @@ class FieldTripDetails extends Component { // That means this control needs to be reset when the travel date or the edited trip changes // (see comment on the key prop below). defaultValue={travelDateFormatted} - // Allows the input to be reset when a field trip value gets edited/reloaded with a different date, - // or a different trip (with a different date) is selected in the field trip list. - key={travelDateFormatted} + // Reset the input control to the defaultValue above and discard any edits when + // a field trip value gets edited/reloaded with a different date, + // or a different trip (with the same or a different date) is selected in the field trip list. + key={`${id}-${travelDateFormatted}`} onBlur={this._setRequestDate} style={{ border: 'none', From 04d2676994db01e5d7f2f4289ef80a6ad1933877 Mon Sep 17 00:00:00 2001 From: miles-grant-ibigroup Date: Mon, 25 Jul 2022 12:47:01 -0400 Subject: [PATCH 0404/1425] feat: add toggleable full screen modal support --- .../__snapshots__/create-otp-reducer.js.snap | 3 ++ lib/actions/ui.js | 1 + lib/components/app/app.css | 9 ++++ lib/components/app/popup.tsx | 51 +++++++++++++++++++ lib/components/app/responsive-webapp.js | 12 ++++- lib/reducers/create-otp-reducer.js | 9 ++++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 lib/components/app/popup.tsx diff --git a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap index 1960b703e..ed103fa70 100644 --- a/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap +++ b/__tests__/reducers/__snapshots__/create-otp-reducer.js.snap @@ -105,6 +105,9 @@ Object { "localizedMessages": null, "mainPanelContent": null, "mobileScreen": 1, + "popup": Object { + "content": null, + }, "printView": false, "routeViewer": Object { "filter": Object { diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 021eb7d4b..37c456d81 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -65,6 +65,7 @@ const viewRoute = createAction('SET_VIEWED_ROUTE') export const unfocusRoute = createAction('UNFOCUS_ROUTE') export const toggleAutoRefresh = createAction('TOGGLE_AUTO_REFRESH') const setPreviousItineraryView = createAction('SET_PREVIOUS_ITINERARY_VIEW') +export const setPopupContent = createAction('SET_POPUP_CONTENT') /** * Wrapper function for history#push (or, if specified, replace, etc.) diff --git a/lib/components/app/app.css b/lib/components/app/app.css index 9e9e642a8..1a8c5f94e 100644 --- a/lib/components/app/app.css +++ b/lib/components/app/app.css @@ -84,3 +84,12 @@ .view-switcher button.btn-link:hover { color: #fff; } + +/* Full screen modal styling */ +.fullscreen-modal { + width: 90vw; + height: 100vh; +} +.fullscreen-modal .modal-content { + height: 90vh; +} diff --git a/lib/components/app/popup.tsx b/lib/components/app/popup.tsx new file mode 100644 index 000000000..d48cb3ab1 --- /dev/null +++ b/lib/components/app/popup.tsx @@ -0,0 +1,51 @@ +import { Modal } from 'react-bootstrap' +import { useIntl } from 'react-intl' +import coreUtils from '@opentripplanner/core-utils' +import React, { useEffect } from 'react' + +type Props = { + appendLocale?: boolean + content: string // TODO: URL TYPE? + hideModal: () => void +} + +/** + * TODO JSDOC + */ +const PopupWrapper = ({ + appendLocale = true, + content, + hideModal +}: Props): JSX.Element | null => { + const intl = useIntl() + const shown = !!content + + const isMobile = coreUtils.ui.isMobile() + const compiledUrl = `${content}${appendLocale && intl.defaultLocale}` + + useEffect(() => { + if (isMobile) { + window.open(compiledUrl) + } + }, [isMobile, compiledUrl]) + + if (isMobile) return null + + return ( + { + hideModal() + }} + show={shown} + > +