From 7389e85e5a0ee17b6ebb23b336f5b1a034d5e911 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 6 May 2021 17:07:33 -0400 Subject: [PATCH 01/89] fix(ConnectedTransitiveOverlay): Expose new props from OTP-UI TransitiveOverlay via config. --- example-config.yml | 23 +++++++++++++ .../map/connected-transitive-overlay.js | 8 +++-- package.json | 4 +-- yarn.lock | 32 +++++++------------ 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/example-config.yml b/example-config.yml index 921e180ac..ef83c473e 100644 --- a/example-config.yml +++ b/example-config.yml @@ -69,6 +69,29 @@ map: - name: Stamen Toner Lite url: http://tile.stamen.com/toner-lite/{z}/{x}/{y}.png attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.' + ### Optional transitive.js (route rendering) properties: + ### - labeledModes: an array of OTP modes for which the route label should be + ### rendered on the map, under the condition that a route_short_name is provided + ### in the GTFS feed for those routes. Example of OTP modes: BUS, RAIL, ... + ### - styles.labels, + ### styles.segment_labels: styles attributes recognized by transitive.js. + ### For examples of applicable style attributes, see + ### https://github.com/conveyal/transitive.js/blob/master/stories/Transitive.stories.js#L47. + # transitive: + # labeledModes: + # - BUS + # - RAIL + # styles: + # labels: + # font-size: 14px + # font-family: Hind, sans-serif + # segment_labels: + # border-color: "#FFFFFF" + # border-radius: 6 + # border-width: 2 + # color: "#FFE0D0" + # font-family: Hind, sans-serif + # font-size: 18px # it is possible to leave out a geocoder config entirely. In that case only # GPS coordinates will be used when finding the origin/destination. diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 9b2eed104..86cef81e2 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -31,14 +31,16 @@ const mapStateToProps = (state, ownProps) => { transitiveData = activeSearch.response.otp } + const { labeledModes, styles } = state.otp.config.map.transitive || {} + return { activeItinerary: activeSearch && activeSearch.activeItinerary, + labeledModes, routingType: activeSearch && activeSearch.query && activeSearch.query.routingType, + styles, transitiveData, visible: true } } -const mapDispatchToProps = {} - -export default connect(mapStateToProps, mapDispatchToProps)(TransitiveCanvasOverlay) +export default connect(mapStateToProps)(TransitiveCanvasOverlay) diff --git a/package.json b/package.json index fc883942c..7276e9b67 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@opentripplanner/route-viewer-overlay": "^1.0.4", "@opentripplanner/stop-viewer-overlay": "^1.0.4", "@opentripplanner/stops-overlay": "^3.0.2", - "@opentripplanner/transitive-overlay": "^1.0.5", + "@opentripplanner/transitive-overlay": "^1.0.6", "@opentripplanner/trip-details": "^1.1.4", "@opentripplanner/trip-form": "^1.0.5", "@opentripplanner/trip-viewer-overlay": "^1.0.4", @@ -96,7 +96,7 @@ "reselect": "^4.0.0", "seamless-immutable": "^7.1.3", "styled-components": "^5.0.0", - "transitive-js": "^0.13.4", + "transitive-js": "^0.13.7", "velocity-react": "^1.3.3", "yup": "^0.29.3" }, diff --git a/yarn.lock b/yarn.lock index 5e1164b41..445de8067 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,7 +60,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.11.5", "@babel/generator@^7.11.6", "@babel/generator@^7.12.11", "@babel/generator@^7.4.0", "@babel/generator@^7.9.4": +"@babel/generator@^7.11.6", "@babel/generator@^7.12.11", "@babel/generator@^7.4.0", "@babel/generator@^7.9.4": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.11.tgz#98a7df7b8c358c9a37ab07a24056853016aba3af" integrity sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA== @@ -298,7 +298,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.4.3": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.4.3": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== @@ -1631,14 +1631,14 @@ "@opentripplanner/from-to-location-picker" "^1.0.3" "@opentripplanner/zoom-based-markers" "^1.0.1" -"@opentripplanner/transitive-overlay@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.0.5.tgz#5dd99f6115acbd4a4e89f05c455a9980a007e973" - integrity sha512-2jDli1TFz1MBUx/em934R26OV9bA0wAlgJUL3MZIsLft+BsMf5oY/kVPqbfEMgEVZkPi8cFKJdrkOrLimoKJuA== +"@opentripplanner/transitive-overlay@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.0.6.tgz#73a1cedbc1df897b950f172e4145924462e88217" + integrity sha512-JXRJWEhhwl3cYFJVO4EULaXQywHaAr2TtHnbCulCgc+jmKOHQTnyLx2son5OKo3NR8v5FIo2YaleoHczhLhnWA== dependencies: "@opentripplanner/core-utils" "^3.0.4" lodash.isequal "^4.5.0" - transitive-js "^0.13.3" + transitive-js "^0.13.7" "@opentripplanner/trip-details@^1.1.4": version "1.1.4" @@ -14532,11 +14532,6 @@ resolve-options@^1.1.0: dependencies: value-or-function "^3.0.0" -resolve-pathname@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" - integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== - resolve-pathname@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" @@ -16092,10 +16087,10 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -transitive-js@^0.13.3, transitive-js@^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.13.4.tgz#2ef9b57f4c0f4ec594f84664300d91a46dfde820" - integrity sha512-26lcurtKYJAZJY0kWo3DelTO2ADIZz6uNxrCRUT0hvAcq/lnTLV3WR/vSmR8vsIWRQ10dokJ80VXK0Y3461Jag== +transitive-js@^0.13.7: + version "0.13.7" + resolved "https://registry.yarnpkg.com/transitive-js/-/transitive-js-0.13.7.tgz#d95f1ffa0dfac5c0daed5061db7b62c69062f1c0" + integrity sha512-eaHeP1SppQzhZHMYNd3wKf+PFBSYtUE7T89zNTvtbENOWzcIXvvoNBDs0FIs38av9qiv8OmmA/7nqVgNX1cy9A== dependencies: augment "4.3.0" component-each "0.2.6" @@ -16710,11 +16705,6 @@ validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: dependencies: builtins "^1.0.3" -value-equal@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" - integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== - value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" From 28428772cdd2f2a7a399b3fc83bb2810912a1ccf Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 19 May 2021 15:24:27 -0400 Subject: [PATCH 02/89] fix(transitive): Upgrade to core-utils/transitive-overlay 1.0.7. --- example.js | 12 ++++ .../map/connected-transitive-overlay.js | 72 +++++++++++-------- package.json | 4 +- yarn.lock | 27 +++++-- 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/example.js b/example.js index 33852b548..f0f6b8d20 100644 --- a/example.js +++ b/example.js @@ -72,6 +72,7 @@ const TermsOfStorage = () => ( // define some application-wide components that should be used in // various places. The following components can be provided here: // - defaultMobileTitle (required) +// - getTransitiveRouteLabel (optional, with signature itineraryLeg => string) // - ItineraryBody (required) // - ItineraryFooter (optional) // - LegIcon (required) @@ -84,7 +85,18 @@ const TermsOfStorage = () => ( // - TermsOfService (required if otpConfig.persistence.strategy === 'otp_middleware') // - TermsOfStorage (required if otpConfig.persistence.strategy === 'otp_middleware') const components = { + defaultMobileTitle: () =>
OpenTripPlanner
, + /** + * Example of a custom route label provider to pass to @opentripplanner/core-utils/map#itineraryToTransitive. + * @param {*} itineraryLeg The OTP itinerary leg for which to obtain a custom route label. + * @returns A string with the custom label to display for the given leg, or null to render no label. + */ + getTransitiveRouteLabel: itineraryLeg => { + if (itineraryLeg.mode === 'RAIL') return 'Train' + if (itineraryLeg.mode === 'BUS') return itineraryLeg.routeShortName + return null // null or undefined or empty string will tell transitive-js to not render a route label. + }, ItineraryBody: DefaultItinerary, LegIcon: MyLegIcon, MainControls: isCallTakerModuleEnabled ? CallTakerControls : null, diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 86cef81e2..f14bd4e31 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -1,46 +1,62 @@ import coreUtils from '@opentripplanner/core-utils' import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' +import React, { Component } from 'react' import { connect } from 'react-redux' -import { getActiveSearch, getActiveItinerary, getActiveItineraries } from '../../util/state' +import { ComponentContext } from '../../util/contexts' +import { getActiveItinerary, getActiveItineraries, getActiveSearch } from '../../util/state' + +/** + * Wrapper for TransitiveCanvasOverlay that passes getTransitiveRouteLabel defined in ComponentContext. + */ +class TransitiveCanvasOverlayWithContext extends Component { + static contextType = ComponentContext + + render () { + const { activeSearchResponseOtp, activeSearchVisibleItinerary, ...otherProps } = this.props + let transitiveData = null + if (activeSearchVisibleItinerary) { + transitiveData = coreUtils.map.itineraryToTransitive(activeSearchVisibleItinerary, null, this.context.getTransitiveRouteLabel) + } else if (activeSearchResponseOtp) { + transitiveData = activeSearchResponseOtp + } + + return + } +} // connect to the redux store const mapStateToProps = (state, ownProps) => { const activeSearch = getActiveSearch(state.otp) - let transitiveData = null - if ( - activeSearch && - activeSearch.query.routingType === 'ITINERARY' && - activeSearch.response && - activeSearch.response.length > 0 - ) { - // FIXME: This may need some simplification. - const itins = getActiveItineraries(state.otp) - const visibleIndex = activeSearch.visibleItinerary !== undefined && activeSearch.visibleItinerary !== null - ? activeSearch.visibleItinerary - : activeSearch.activeItinerary - // TODO: prevent itineraryToTransitive() from being called more than needed - const visibleItinerary = itins[visibleIndex] ? itins[visibleIndex] : getActiveItinerary(state.otp) - if (visibleItinerary) transitiveData = coreUtils.map.itineraryToTransitive(visibleItinerary) - } else if ( - activeSearch && - activeSearch.response && - activeSearch.response.otp - ) { - transitiveData = activeSearch.response.otp - } - + const { activeItinerary, query, response, visibleItinerary } = activeSearch || {} const { labeledModes, styles } = state.otp.config.map.transitive || {} + let activeSearchVisibleItinerary = null + let activeSearchResponseOtp = null + + if (response) { + if (query.routingType === 'ITINERARY' && response.length > 0) { + // FIXME: This may need some simplification. + const itins = getActiveItineraries(state.otp) + const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null + ? visibleItinerary + : activeItinerary + // TODO: prevent itineraryToTransitive() from being called more than needed + activeSearchVisibleItinerary = itins[visibleIndex] || getActiveItinerary(state.otp) + } else if (response.otp) { + activeSearchResponseOtp = response.otp + } + } return { - activeItinerary: activeSearch && activeSearch.activeItinerary, + activeItinerary, + activeSearchResponseOtp, + activeSearchVisibleItinerary, labeledModes, - routingType: activeSearch && activeSearch.query && activeSearch.query.routingType, + routingType: query && query.routingType, styles, - transitiveData, visible: true } } -export default connect(mapStateToProps)(TransitiveCanvasOverlay) +export default connect(mapStateToProps)(TransitiveCanvasOverlayWithContext) diff --git a/package.json b/package.json index 7276e9b67..45b5ab793 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "dependencies": { "@auth0/auth0-react": "^1.1.0", "@opentripplanner/base-map": "^1.0.5", - "@opentripplanner/core-utils": "^3.1.0", + "@opentripplanner/core-utils": "^3.1.1", "@opentripplanner/endpoints-overlay": "^1.0.6", "@opentripplanner/from-to-location-picker": "^1.0.4", "@opentripplanner/geocoder": "^1.0.2", @@ -44,7 +44,7 @@ "@opentripplanner/route-viewer-overlay": "^1.0.4", "@opentripplanner/stop-viewer-overlay": "^1.0.4", "@opentripplanner/stops-overlay": "^3.0.2", - "@opentripplanner/transitive-overlay": "^1.0.6", + "@opentripplanner/transitive-overlay": "^1.0.7", "@opentripplanner/trip-details": "^1.1.4", "@opentripplanner/trip-form": "^1.0.5", "@opentripplanner/trip-viewer-overlay": "^1.0.4", diff --git a/yarn.lock b/yarn.lock index 445de8067..5745cbb52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1495,7 +1495,7 @@ "@opentripplanner/core-utils" "^3.0.4" prop-types "^15.7.2" -"@opentripplanner/core-utils@^3.0.0", "@opentripplanner/core-utils@^3.0.4", "@opentripplanner/core-utils@^3.1.0": +"@opentripplanner/core-utils@^3.0.0", "@opentripplanner/core-utils@^3.0.4": version "3.1.0" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-3.1.0.tgz#4626807893874503c5d365b05e5db4a1b3bf163c" integrity sha512-EYhIv6nQdmadmkQumuXGrEwvRrhUl8xDPckSv24y3o9FX7uk8h5Gqe4FPdjnBT1eOj/XUj0/fZzNcETHUvZvUg== @@ -1510,6 +1510,21 @@ prop-types "^15.7.2" qs "^6.9.1" +"@opentripplanner/core-utils@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-3.1.1.tgz#d3582cbfe7a84cd5370d63a857dd26dff17591e5" + integrity sha512-20uY3uh2TawPG9PWLLWi5TVJ8F1/bws6cRaRfRNwk11qtdeFQNnVqxZGEd8q1OQSW7UI15cM45SCrK7V7Kxn6A== + dependencies: + "@mapbox/polyline" "^1.1.0" + "@opentripplanner/geocoder" "^1.0.2" + "@turf/along" "^6.0.1" + bowser "^2.7.0" + lodash.isequal "^4.5.0" + moment "^2.24.0" + moment-timezone "^0.5.27" + prop-types "^15.7.2" + qs "^6.9.1" + "@opentripplanner/endpoints-overlay@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-1.0.6.tgz#db636e61a058475fbcafdc6fba9443e20455da2f" @@ -1631,12 +1646,12 @@ "@opentripplanner/from-to-location-picker" "^1.0.3" "@opentripplanner/zoom-based-markers" "^1.0.1" -"@opentripplanner/transitive-overlay@^1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.0.6.tgz#73a1cedbc1df897b950f172e4145924462e88217" - integrity sha512-JXRJWEhhwl3cYFJVO4EULaXQywHaAr2TtHnbCulCgc+jmKOHQTnyLx2son5OKo3NR8v5FIo2YaleoHczhLhnWA== +"@opentripplanner/transitive-overlay@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@opentripplanner/transitive-overlay/-/transitive-overlay-1.0.7.tgz#42706fea6ce3fd3c93b75d272c6da1c00e9044e4" + integrity sha512-2QgvfGgq/8B2EpzFAtaxrmexP/k97WHZwKMVho4ZNPiAYfcaE5kRsa1ifWy5A5hHXb36ixAgzfJYiVLeadWbQQ== dependencies: - "@opentripplanner/core-utils" "^3.0.4" + "@opentripplanner/core-utils" "^3.1.1" lodash.isequal "^4.5.0" transitive-js "^0.13.7" From 79b042e573a3cc42bb3a06f03be7ed2dda264624 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 19 May 2021 16:46:21 -0400 Subject: [PATCH 03/89] refactor(transitive): Prevent some unnecessary rerenders, tweak comments. --- example-config.yml | 6 ++++-- lib/components/map/connected-transitive-overlay.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/example-config.yml b/example-config.yml index ef83c473e..6aadbe494 100644 --- a/example-config.yml +++ b/example-config.yml @@ -71,8 +71,10 @@ map: attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.' ### Optional transitive.js (route rendering) properties: ### - labeledModes: an array of OTP modes for which the route label should be - ### rendered on the map, under the condition that a route_short_name is provided - ### in the GTFS feed for those routes. Example of OTP modes: BUS, RAIL, ... + ### rendered on the map. Example of OTP modes: BUS, RAIL, ... + ### The label is rendered under the condition that a route_short_name is provided + ### in the GTFS feed for those routes, or that a getTransitiveRouteLabel function is defined + ### in the ComponentContext (see example.js for more). ### - styles.labels, ### styles.segment_labels: styles attributes recognized by transitive.js. ### For examples of applicable style attributes, see diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index f14bd4e31..3f7845a4f 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -1,3 +1,4 @@ +import isEqual from 'lodash.isequal' import coreUtils from '@opentripplanner/core-utils' import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import React, { Component } from 'react' @@ -12,9 +13,18 @@ import { getActiveItinerary, getActiveItineraries, getActiveSearch } from '../.. class TransitiveCanvasOverlayWithContext extends Component { static contextType = ComponentContext + shouldComponentUpdate (nextProps) { + return ( + !isEqual(nextProps.activeSearchVisibleItinerary, this.props.activeSearchVisibleItinerary) || + !isEqual(nextProps.activeSearchResponseOtp, this.props.activeSearchResponseOtp) + ) + } + render () { const { activeSearchResponseOtp, activeSearchVisibleItinerary, ...otherProps } = this.props let transitiveData = null + // TODO: prevent itineraryToTransitive() from being called more than needed + // (partially addressed by shouldComponentUpdate) if (activeSearchVisibleItinerary) { transitiveData = coreUtils.map.itineraryToTransitive(activeSearchVisibleItinerary, null, this.context.getTransitiveRouteLabel) } else if (activeSearchResponseOtp) { @@ -41,7 +51,6 @@ const mapStateToProps = (state, ownProps) => { const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null ? visibleItinerary : activeItinerary - // TODO: prevent itineraryToTransitive() from being called more than needed activeSearchVisibleItinerary = itins[visibleIndex] || getActiveItinerary(state.otp) } else if (response.otp) { activeSearchResponseOtp = response.otp From e8d72e76aeae80647d8fed600a3b00cf5e39ae45 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Wed, 19 May 2021 16:16:04 -0700 Subject: [PATCH 04/89] ci: remove codecov --- .github/workflows/node-ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index d141a5130..1024d9631 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -25,15 +25,12 @@ jobs: run: yarn lint - name: Lint docs run: yarn lint-docs - - name: Run tests with coverage - run: yarn cover + - name: Run tests + run: yarn jest - name: Build example project run: yarn build # at this point, the build is successful - - name: Codecov - uses: codecov/codecov-action@v1.2.0 - continue-on-error: true - name: Semantic Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 128e77dd2c24bc1346eadc39338ef8e5d5b854ff Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 20 May 2021 12:06:03 -0400 Subject: [PATCH 05/89] fix(calltaker): Add config to show call hist on load; clear itins on call end. Co-authored with @robertgregg3. --- example.js | 2 +- lib/actions/call-taker.js | 2 ++ lib/reducers/call-taker.js | 7 +++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/example.js b/example.js index 33852b548..03bc3a591 100644 --- a/example.js +++ b/example.js @@ -124,7 +124,7 @@ if (process.env.NODE_ENV === 'development') { // set up the Redux store const store = createStore( combineReducers({ - callTaker: createCallTakerReducer(), + callTaker: createCallTakerReducer(otpConfig), otp: createOtpReducer(otpConfig), user: createUserReducer(), router: connectRouter(history) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 6bf7989bd..6d2107288 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -66,6 +66,8 @@ export function endCall () { console.error(err) alert(`Could not save call: ${JSON.stringify(err)}`) }) + // Clear itineraries shown when ending call. + dispatch(resetForm(true)) dispatch(endingCall()) } } diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 7e3eb81db..6307f67af 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -4,7 +4,8 @@ import moment from 'moment' import {constructNewCall} from '../util/call-taker' import {FETCH_STATUS} from '../util/constants' -function createCallTakerReducer () { +function createCallTakerReducer (config) { + const calltakerConfig = config.modules.find(m => m.id === 'call') const initialState = { activeCall: null, callHistory: { @@ -12,7 +13,9 @@ function createCallTakerReducer () { status: FETCH_STATUS.UNFETCHED, data: [] }, - visible: false + visible: calltakerConfig && + calltakerConfig.options && + calltakerConfig.options.showCallHistoryOnLoad }, fieldTrip: { activeId: null, From 5dfbc61ab4dfe17f6d302a13443c2974e81fa33a Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Thu, 20 May 2021 17:02:09 -0700 Subject: [PATCH 06/89] refactor: use a selector to compute transitive data --- .../map/connected-transitive-overlay.js | 60 +++++------------ lib/components/map/default-map.js | 10 ++- lib/util/state.js | 64 +++++++++++++++++++ 3 files changed, 89 insertions(+), 45 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 3f7845a4f..80b78fd66 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -1,37 +1,26 @@ -import isEqual from 'lodash.isequal' -import coreUtils from '@opentripplanner/core-utils' -import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' +import TransitiveOverlay from '@opentripplanner/transitive-overlay' import React, { Component } from 'react' import { connect } from 'react-redux' -import { ComponentContext } from '../../util/contexts' -import { getActiveItinerary, getActiveItineraries, getActiveSearch } from '../../util/state' +import { getActiveSearch, getTransitiveData } from '../../util/state' /** - * Wrapper for TransitiveCanvasOverlay that passes getTransitiveRouteLabel defined in ComponentContext. + * Wrapper for TransitiveOverlay that avoids rerenders by checking transitive + * data computed from redux store. */ -class TransitiveCanvasOverlayWithContext extends Component { - static contextType = ComponentContext - +class TransitiveCanvasOverlay extends Component { shouldComponentUpdate (nextProps) { - return ( - !isEqual(nextProps.activeSearchVisibleItinerary, this.props.activeSearchVisibleItinerary) || - !isEqual(nextProps.activeSearchResponseOtp, this.props.activeSearchResponseOtp) - ) + return nextProps.transitiveData !== this.props.transitiveData } render () { - const { activeSearchResponseOtp, activeSearchVisibleItinerary, ...otherProps } = this.props - let transitiveData = null - // TODO: prevent itineraryToTransitive() from being called more than needed - // (partially addressed by shouldComponentUpdate) - if (activeSearchVisibleItinerary) { - transitiveData = coreUtils.map.itineraryToTransitive(activeSearchVisibleItinerary, null, this.context.getTransitiveRouteLabel) - } else if (activeSearchResponseOtp) { - transitiveData = activeSearchResponseOtp - } - - return + const { transitiveData, ...otherProps } = this.props + return ( + + ) } } @@ -39,33 +28,16 @@ class TransitiveCanvasOverlayWithContext extends Component { const mapStateToProps = (state, ownProps) => { const activeSearch = getActiveSearch(state.otp) - const { activeItinerary, query, response, visibleItinerary } = activeSearch || {} + const { activeItinerary, query } = activeSearch || {} const { labeledModes, styles } = state.otp.config.map.transitive || {} - let activeSearchVisibleItinerary = null - let activeSearchResponseOtp = null - - if (response) { - if (query.routingType === 'ITINERARY' && response.length > 0) { - // FIXME: This may need some simplification. - const itins = getActiveItineraries(state.otp) - const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null - ? visibleItinerary - : activeItinerary - activeSearchVisibleItinerary = itins[visibleIndex] || getActiveItinerary(state.otp) - } else if (response.otp) { - activeSearchResponseOtp = response.otp - } - } return { activeItinerary, - activeSearchResponseOtp, - activeSearchVisibleItinerary, labeledModes, routingType: query && query.routingType, styles, - visible: true + transitiveData: getTransitiveData(state, ownProps) } } -export default connect(mapStateToProps)(TransitiveCanvasOverlayWithContext) +export default connect(mapStateToProps)(TransitiveCanvasOverlay) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index 9a3157701..d37e4db45 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -28,6 +28,7 @@ import ElevationPointMarker from './elevation-point-marker' import PointPopup from './point-popup' import TileOverlay from './tile-overlay' import ZipcarOverlay from './zipcar-overlay' +import { ComponentContext } from '../../util/contexts' const MapContainer = styled.div` height: 100%; @@ -44,6 +45,8 @@ const MapContainer = styled.div` ` class DefaultMap extends Component { + static contextType = ComponentContext + /** * Checks whether the modes have changed between old and new queries and * whether to update the map overlays accordingly (e.g., to show rental vehicle @@ -164,7 +167,12 @@ class DefaultMap extends Component { Without it, transitive resolution will not match the map, and transitive will appear blurry after e.g. the narrative is expanded. */} - + diff --git a/lib/util/state.js b/lib/util/state.js index f7ddfbd5d..eafe11b1e 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -304,6 +304,70 @@ export function getActiveItinerary (otpState) { : null } +function hasResponse (state) { + const activeSearch = getActiveSearch(state.otp) + return !!(activeSearch && activeSearch.response) +} + +function hasAtLeastOneResponse (state) { + const activeSearch = getActiveSearch(state.otp) + return activeSearch && activeSearch.response && activeSearch.response.length > 0 +} + +function getOtpResponse (state) { + const activeSearch = getActiveSearch(state.otp) + return activeSearch && activeSearch.response && activeSearch.response.otp +} + +function getActiveSearchQuery (state) { + const activeSearch = getActiveSearch(state.otp) + return activeSearch && activeSearch.query +} + +function getVisibleItinerary (state) { + const activeSearch = getActiveSearch(state.otp) + return activeSearch && activeSearch.visibleItinerary +} + +export const getTransitiveData = createSelector( + hasResponse, + hasAtLeastOneResponse, + getOtpResponse, + getActiveSearchQuery, + state => getActiveItinerary(state.otp), + state => getActiveItineraries(state.otp), + getVisibleItinerary, + (state, props) => props.getTransitiveRouteLabel, + ( + hasResponse, + hasAtLeastOneResponse, + otpResponse, + query, + activeItinerary, + itins, + visibleItinerary, + getTransitiveRouteLabel + ) => { + if (hasResponse) { + if (query.routingType === 'ITINERARY' && hasAtLeastOneResponse) { + const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null + ? visibleItinerary + : activeItinerary + const foundItinerary = itins[visibleIndex] || activeItinerary + return foundItinerary + ? coreUtils.map.itineraryToTransitive( + foundItinerary, + null, + getTransitiveRouteLabel + ) + : null + } else if (otpResponse) { + return otpResponse + } + } + } +) + /** * Determine if the current query has a valid location, including lat/lon * @param {Object} query an OTP query object From b05c167e3808bbfc63df0cf49eefa500b233925c Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Thu, 20 May 2021 17:17:29 -0700 Subject: [PATCH 07/89] refactor: make selector even more selective --- lib/util/state.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index eafe11b1e..d0bfc93b2 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -319,9 +319,11 @@ function getOtpResponse (state) { return activeSearch && activeSearch.response && activeSearch.response.otp } -function getActiveSearchQuery (state) { +function queryIsItineraryRouting (state) { const activeSearch = getActiveSearch(state.otp) - return activeSearch && activeSearch.query + return activeSearch && + activeSearch.query && + activeSearch.query.routingType === 'ITINERARY' } function getVisibleItinerary (state) { @@ -333,7 +335,7 @@ export const getTransitiveData = createSelector( hasResponse, hasAtLeastOneResponse, getOtpResponse, - getActiveSearchQuery, + queryIsItineraryRouting, state => getActiveItinerary(state.otp), state => getActiveItineraries(state.otp), getVisibleItinerary, @@ -342,14 +344,14 @@ export const getTransitiveData = createSelector( hasResponse, hasAtLeastOneResponse, otpResponse, - query, + queryIsItineraryRouting, activeItinerary, itins, visibleItinerary, getTransitiveRouteLabel ) => { if (hasResponse) { - if (query.routingType === 'ITINERARY' && hasAtLeastOneResponse) { + if (queryIsItineraryRouting && hasAtLeastOneResponse) { const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null ? visibleItinerary : activeItinerary From cf720f470828a86ee78fe7f8d4bbfd0b87c31a78 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 21 May 2021 11:34:29 -0400 Subject: [PATCH 08/89] feat(mailables): add mailables for calltaker --- lib/actions/call-taker.js | 1 + lib/components/admin/call-history-window.js | 56 +++ lib/components/admin/call-taker-windows.js | 55 +-- lib/components/admin/draggable-window.js | 3 +- lib/components/admin/mailables-window.js | 192 +++++++++ lib/reducers/call-taker.js | 8 + lib/util/PDFDocumentWithTables.js | 117 ++++++ lib/util/mailables.js | 153 +++++++ package.json | 2 + yarn.lock | 424 +++++++++++++++++++- 10 files changed, 955 insertions(+), 56 deletions(-) create mode 100644 lib/components/admin/call-history-window.js create mode 100644 lib/components/admin/mailables-window.js create mode 100644 lib/util/PDFDocumentWithTables.js create mode 100644 lib/util/mailables.js diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 6bf7989bd..3bf71dab7 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -24,6 +24,7 @@ const storeSession = createAction('STORE_SESSION') export const beginCall = createAction('BEGIN_CALL') export const toggleCallHistory = createAction('TOGGLE_CALL_HISTORY') +export const toggleMailables = createAction('TOGGLE_MAILABLES') /** * Fully reset form and toggle call history (and close field trips if open). diff --git a/lib/components/admin/call-history-window.js b/lib/components/admin/call-history-window.js new file mode 100644 index 000000000..a938168b8 --- /dev/null +++ b/lib/components/admin/call-history-window.js @@ -0,0 +1,56 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' + +import * as callTakerActions from '../../actions/call-taker' +import CallRecord from './call-record' +import DraggableWindow from './draggable-window' +import Icon from '../narrative/icon' +import {WindowHeader} from './styled' + +class CallHistoryWindow extends Component { + render () { + const {callTaker, fetchQueries, searches, toggleCallHistory} = this.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)(CallHistoryWindow) diff --git a/lib/components/admin/call-taker-windows.js b/lib/components/admin/call-taker-windows.js index 56e0ed712..9cfe9581e 100644 --- a/lib/components/admin/call-taker-windows.js +++ b/lib/components/admin/call-taker-windows.js @@ -1,60 +1,19 @@ import React, { Component } from 'react' -import { connect } from 'react-redux' -import * as callTakerActions from '../../actions/call-taker' -import CallRecord from './call-record' -import DraggableWindow from './draggable-window' -import Icon from '../narrative/icon' -import {WindowHeader} from './styled' +import CallHistoryWindow from './call-history-window' +import MailablesWindow from './mailables-window' /** * Collects the various draggable windows used in the Call Taker module to * display, for example, the call record list and (TODO) the list of field trips. */ -class CallTakerWindows extends Component { +export default class CallTakerWindows extends Component { render () { - const {callTaker, fetchQueries, searches, toggleCallHistory} = this.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)(CallTakerWindows) diff --git a/lib/components/admin/draggable-window.js b/lib/components/admin/draggable-window.js index a43789139..2d6aba12f 100644 --- a/lib/components/admin/draggable-window.js +++ b/lib/components/admin/draggable-window.js @@ -14,6 +14,7 @@ export default class DraggableWindow extends Component { header, height = '245px', onClickClose, + scroll = true, style } = this.props const GREY_BORDER = '#777 1.3px solid' @@ -57,7 +58,7 @@ export default class DraggableWindow extends Component {
{children}
diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js new file mode 100644 index 000000000..7766c5aa1 --- /dev/null +++ b/lib/components/admin/mailables-window.js @@ -0,0 +1,192 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' + +import * as callTakerActions from '../../actions/call-taker' +import DraggableWindow from './draggable-window' +import Icon from '../narrative/icon' +import {WindowHeader} from './styled' +import {createLetter, MAILABLE_FIELDS} from '../../util/mailables' + +/** + * A window enabled through the Call Taker module that allows Call Taker users + * to generate a PDF with an invoice of items to be mailed to transit customers. + */ +class MailablesWindow extends Component { + constructor (props) { + super(props) + this.state = { + mailables: [] + } + } + + _addMailable = (mailable) => { + const mailables = [...this.state.mailables] + if (!mailables.find(m => m.name === mailable.name)) { + mailables.push({...mailable, quantity: 1}) + } else { + // FIXME: Increase quanity? + } + this.setState({mailables}) + } + + _removeMailable = (mailable) => { + const mailables = [...this.state.mailables] + const removeIndex = mailables.findIndex(m => m.name === mailable.name) + mailables.splice(removeIndex, 1) + this.setState({mailables}) + } + + _updateMailableField = (index, fieldName, value) => { + const mailables = [...this.state.mailables] + mailables[index] = {...mailables[index], [fieldName]: value} + this.setState({mailables}) + } + + _onClickCreateLetter = () => createLetter(this.state, this.props.callConfig.options) + + _updateField = (evt) => { + this.setState({[evt.target.id]: evt.target.value}) + } + + render () { + const {callConfig, callTaker, toggleMailables} = this.props + const {mailables: selectedMailables} = this.state + const {mailables} = callConfig.options + if (!callTaker.mailables.visible) return null + const selectableMailables = mailables.filter(m => !selectedMailables.find(mailable => mailable.name === m.name)) + return ( + + + Mailables{' '} + + + + + + } + onClickClose={toggleMailables} + scroll={false} + style={{width: '600px'}} + > +
+ {MAILABLE_FIELDS.map(f => ( + + ))} +
+
+
+

All Mailables

+
+ {selectableMailables.map((mailable, i) => ( + + ))} +
+
+
+

Selected Mailables

+
+ {selectedMailables.length > 0 + ? selectedMailables.map((mailable, i) => ( + + )) + :
No mailables selected.
+ } +
+
+
+
+ ) + } +} + +class MailableOption extends Component { + _changeLargeFormat = (evt) => { + const {index, updateField} = this.props + updateField(index, 'largeFormat', evt.target.checked) + } + + _changeQuantity = (evt) => { + const {index, updateField} = this.props + updateField(index, 'quantity', evt.target.value) + } + + _onClear = () => this.props.onClear && this.props.onClear(this.props.mailable) + + _onClick = () => this.props.onClick && this.props.onClick(this.props.mailable) + + render () { + const {mailable, onClear} = this.props + const isSelected = Boolean(onClear) + if (isSelected) { + return ( +
+ {mailable.name} + +
+ + {mailable.largePrint && + <> + + + + } +
+
+ ) + } + return ( + + ) + } +} + +const mapStateToProps = (state, ownProps) => { + const callConfig = state.otp.config.modules.find(m => m.id === 'call') + return { + callConfig, + callTaker: state.callTaker + } +} + +const mapDispatchToProps = { + toggleMailables: callTakerActions.toggleMailables +} + +export default connect(mapStateToProps, mapDispatchToProps)(MailablesWindow) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 7e3eb81db..b4f1fc0f5 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -26,6 +26,9 @@ function createCallTakerReducer () { }, visible: false }, + mailables: { + visible: true + }, session: null } return (state = initialState, action) => { @@ -148,6 +151,11 @@ function createCallTakerReducer () { } }) } + case 'TOGGLE_MAILABLES': { + return update(state, { + mailables: { visible: { $set: !state.mailables.visible } } + }) + } case 'END_CALL': { return update(state, { activeCall: { $set: null } diff --git a/lib/util/PDFDocumentWithTables.js b/lib/util/PDFDocumentWithTables.js new file mode 100644 index 000000000..8a9bf9ee6 --- /dev/null +++ b/lib/util/PDFDocumentWithTables.js @@ -0,0 +1,117 @@ +'use strict' + +const PDFDocument = require('pdfkit') + +/** + * This class extends the pdfkit functionality to provide a tables method, which + * will write tableData (2-d array) to the pdf doc. + * + * See https://www.andronio.me/2017/09/02/pdfkit-tables/ for more info. + */ +class PDFDocumentWithTables extends PDFDocument { + table (table, arg0, arg1, arg2) { + let startX = this.page.margins.left; let startY = this.y + let options = {} + + if ((typeof arg0 === 'number') && (typeof arg1 === 'number')) { + startX = arg0 + startY = arg1 + + if (typeof arg2 === 'object') { options = arg2 } + } else if (typeof arg0 === 'object') { + options = arg0 + } + + const columnCount = table.headers.length + const columnSpacing = options.columnSpacing || 15 + const rowSpacing = options.rowSpacing || 5 + const usableWidth = options.width || (this.page.width - this.page.margins.left - this.page.margins.right) + + const prepareHeader = options.prepareHeader || (() => {}) + const prepareRow = options.prepareRow || (() => {}) + const computeRowHeight = (row) => { + let result = 0 + + row.forEach((cell) => { + const cellHeight = this.heightOfString(cell, { + width: columnWidth, + align: 'left' + }) + result = Math.max(result, cellHeight) + }) + + return result + rowSpacing + } + + const columnContainerWidth = usableWidth / columnCount + const columnWidth = columnContainerWidth - columnSpacing + const maxY = this.page.height - this.page.margins.bottom + + let rowBottomY = 0 + + this.on('pageAdded', () => { + startY = this.page.margins.top + rowBottomY = 0 + }) + + // Allow the user to override style for headers + prepareHeader() + + // Check to have enough room for header and first rows + if (startY + 3 * computeRowHeight(table.headers) > maxY) { this.addPage() } + + // Print all headers + table.headers.forEach((header, i) => { + this.text(header, startX + i * columnContainerWidth, startY, { + width: columnWidth, + align: 'left' + }) + }) + + // Refresh the y coordinate of the bottom of the headers row + rowBottomY = Math.max(startY + computeRowHeight(table.headers), rowBottomY) + + // Separation line between headers and rows + this.moveTo(startX, rowBottomY - rowSpacing * 0.5) + .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5) + .lineWidth(2) + .stroke() + + table.rows.forEach((row, i) => { + const rowHeight = computeRowHeight(row) + + // Switch to next page if we cannot go any further because the space is over. + // For safety, consider 3 rows margin instead of just one + if (startY + 3 * rowHeight < maxY) { startY = rowBottomY + rowSpacing } else { this.addPage() } + + // Allow the user to override style for rows + prepareRow(row, i) + + // Print all cells of the current row + row.forEach((cell, i) => { + this.text(cell, startX + i * columnContainerWidth, startY, { + width: columnWidth, + align: 'left' + }) + }) + + // Refresh the y coordinate of the bottom of this row + rowBottomY = Math.max(startY + rowHeight, rowBottomY) + + // Separation line between rows + this.moveTo(startX, rowBottomY - rowSpacing * 0.5) + .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5) + .lineWidth(1) + .opacity(0.7) + .stroke() + .opacity(1) // Reset opacity after drawing the line + }) + + this.x = startX + this.moveDown() + + return this + } +} + +module.exports = PDFDocumentWithTables diff --git a/lib/util/mailables.js b/lib/util/mailables.js new file mode 100644 index 000000000..5094aaefd --- /dev/null +++ b/lib/util/mailables.js @@ -0,0 +1,153 @@ +import blobStream from 'blob-stream' +import moment from 'moment' + +import PDFDocument from './PDFDocumentWithTables' + +export const MAILABLE_FIELDS = [ + {fieldName: 'firstname'}, + {fieldName: 'lastname'}, + {fieldName: 'address1'}, + {fieldName: 'address2'}, + {fieldName: 'city'}, + {fieldName: 'state'}, + {fieldName: 'zip'} +] + +export function createLetter (formData, otpConfig) { + // 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') + writePDF(formData, {data, height: img.height, width: img.width}, otpConfig) + } + img.src = otpConfig.mailables_header_graphic +} + +function writePDF (formData, imageData, otpConfig) { + const { + address1 = '', + address2, + city = '', + firstname = '', + lastname = '', + mailables, + state = '', + zip = '' + } = formData + const { + mailables_horizontal_margin: horizontalMargin, + mailables_vertical_margin: verticalMargin + } = otpConfig + const margins = { + top: verticalMargin, + bottom: verticalMargin, + left: horizontalMargin, + right: horizontalMargin + } + console.log(margins) + const doc = new PDFDocument({margins}) + const stream = doc.pipe(blobStream()) + preparePage(doc, imageData, otpConfig) + doc.on('pageAdded', () => preparePage(doc, otpConfig)) + + // current date + doc.text(moment().format('MMMM D, YYYY')) + + // recipient address + doc.moveDown() + .moveDown() + .moveDown() + .text(firstname.toUpperCase() + ' ' + lastname.toUpperCase()) + .text(address1.toUpperCase()) + if (address2 && address2.length > 0) { + doc.text(address2.toUpperCase()) + } + doc.text(city.toUpperCase() + ', ' + state.toUpperCase() + ' ' + zip) + + // introduction block + doc.moveDown() + .moveDown() + .text(otpConfig.mailables_introduction) + + // table header + doc.font('Helvetica-Bold') + .moveDown() + .moveDown() + .text('SUMMARY BY ITEM', {align: 'center'}) + .moveDown() + .moveDown() + + const tableData = { + headers: ['Item', 'Quantity'], + rows: mailables.map(mailable => { + let {name, largeFormat, largePrint, quantity} = mailable + if (largePrint && largeFormat) { + name += ' (LARGE PRINT)' + } + return [name, quantity] + }) + } + + doc.table(tableData, { + prepareHeader: () => doc.font('Helvetica-Bold'), + prepareRow: (row, i) => doc.font('Helvetica').fontSize(12) + }) + + // conclusion block + doc.moveDown() + .moveDown() + .font('Helvetica') + .text(otpConfig.mailables_conclusion) + + doc.end() + stream.on('finish', () => { + const url = stream.toBlobURL('application/pdf') + window.open(url) + }) +} + +function preparePage (doc, imageData, otpConfig) { + const { + mailables_footer: footer, + mailables_header_graphic: headerGraphic, + mailables_header_graphic_height: headerGraphicHeight, + mailables_header_graphic_width: 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} + ) + } + // 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 +} diff --git a/package.json b/package.json index 231e168a8..10c6f81ca 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@opentripplanner/trip-form": "^1.0.5", "@opentripplanner/trip-viewer-overlay": "^1.0.4", "@opentripplanner/vehicle-rental-overlay": "^1.0.6", + "blob-stream": "^0.1.3", "bootstrap": "^3.3.7", "bowser": "^1.9.3", "clone": "^2.1.0", @@ -74,6 +75,7 @@ "object-hash": "^1.3.1", "object-path": "^0.11.5", "object-to-formdata": "^4.1.0", + "pdfkit": "^0.12.1", "prop-types": "^15.6.0", "qs": "^6.3.0", "react": "^16.9.0", diff --git a/yarn.lock b/yarn.lock index 383ad16bf..0e929fc73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2319,6 +2319,11 @@ alphanum-sort@^1.0.0, alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= + ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -2531,6 +2536,11 @@ array-find-index@^1.0.1: resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= +array-from@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195" + integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU= + array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" @@ -2645,11 +2655,25 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-transform@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/ast-transform/-/ast-transform-0.0.0.tgz#74944058887d8283e189d954600947bc98fe0062" + integrity sha1-dJRAWIh9goPhidlUYAlHvJj+AGI= + dependencies: + escodegen "~1.2.0" + esprima "~1.0.4" + through "~2.3.4" + ast-types-flow@0.0.7, ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= +ast-types@^0.7.0: + version "0.7.8" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.7.8.tgz#902d2e0d60d071bdcd46dc115e1809ed11c138a9" + integrity sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk= + astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -3639,11 +3663,21 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg= + base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== +base64-js@^1.1.2, base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -3701,6 +3735,18 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== +blob-stream@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/blob-stream/-/blob-stream-0.1.3.tgz#98d668af6996e0f32ef666d06e215ccc7d77686c" + integrity sha1-mNZor2mW4PMu9mbQbiFczH13aGw= + dependencies: + blob "0.0.4" + +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= + bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -3813,11 +3859,28 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +brfs@^2.0.0, brfs@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-2.0.2.tgz#44237878fa82aa479ce4f5fe2c1796ec69f07845" + integrity sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ== + dependencies: + quote-stream "^1.0.1" + resolve "^1.1.5" + static-module "^3.0.2" + through2 "^2.0.0" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +brotli@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.2.tgz#525a9cad4fcba96475d7d388f6aecb13eed52f46" + integrity sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y= + dependencies: + base64-js "^1.1.2" + browser-pack@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" @@ -3835,7 +3898,7 @@ browser-process-hrtime@^0.1.2: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== -browser-resolve@^1.11.0, browser-resolve@^1.11.3, browser-resolve@^1.7.0: +browser-resolve@^1.11.0, browser-resolve@^1.11.3, browser-resolve@^1.7.0, browser-resolve@^1.8.1: version "1.11.3" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== @@ -3891,6 +3954,15 @@ browserify-markdown@2.0.1: through "^2.3.7" through2 "2.0.1" +browserify-optional@^1.0.0, browserify-optional@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-optional/-/browserify-optional-1.0.1.tgz#1e13722cfde0d85f121676c2a72ced533a018869" + integrity sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk= + dependencies: + ast-transform "0.0.0" + ast-types "^0.7.0" + browser-resolve "^1.8.1" + browserify-rsa@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" @@ -4034,6 +4106,11 @@ budo@^11.6.1: ws "^1.1.1" xtend "^4.0.0" +buffer-equal@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" + integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -4584,7 +4661,7 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^1.0.2: +clone@^1.0.2, clone@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= @@ -5162,6 +5239,11 @@ crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" + integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -5559,6 +5641,14 @@ d3@^3.5.8: resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" integrity sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g= +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + damerau-levenshtein@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz#780cf7144eb2e8dbd1c3bb83ae31100ccc31a414" @@ -5858,6 +5948,11 @@ dezalgo@^1.0.0, dezalgo@~1.0.3: asap "^2.0.0" wrappy "1" +dfa@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657" + integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== + diff-sequences@^24.3.0: version "24.3.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" @@ -6077,7 +6172,7 @@ dotenv@^5.0.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: +duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2, duplexer2@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= @@ -6333,6 +6428,36 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@~2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-map@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + es6-math@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es6-math/-/es6-math-1.0.0.tgz#5eac891860c2024b728e7122444df388e1d8177a" @@ -6355,6 +6480,33 @@ es6-promisify@^6.0.0: resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.1.tgz#6edaa45f3bd570ffe08febce66f7116be4b1cdb6" integrity sha512-J3ZkwbEnnO+fGAKrjVpeUAnZshAdfZvbhQpqfIH9kSAspReRC4nJnu8ewm55b4y9ElyeuhCTzJD0XiH8Tsbhlw== +es6-set@^0.1.5, es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-symbol@^3.1.1, es6-symbol@~3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e" @@ -6370,6 +6522,18 @@ escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escodegen@^1.11.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escodegen@^1.9.1: version "1.11.1" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" @@ -6382,6 +6546,17 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +escodegen@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.2.0.tgz#09de7967791cc958b7f89a2ddb6d23451af327e1" + integrity sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E= + dependencies: + esprima "~1.0.4" + estraverse "~1.5.0" + esutils "~1.0.0" + optionalDependencies: + source-map "~0.1.30" + eslint-config-standard-jsx@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz#90c9aa16ac2c4f8970c13fc7efc608bacd02da70" @@ -6604,11 +6779,16 @@ esprima@^3.1.3: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= -esprima@^4.0.0, esprima@~4.0.0: +esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esprima@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" + integrity sha1-n1V+CPw7TSbs6d00+Pv0drYlha0= + esquery@^1.0.0, esquery@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" @@ -6628,16 +6808,39 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= +estraverse@~1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.5.1.tgz#867a3e8e58a9f84618afb6c2ddbcd916b7cbaf71" + integrity sha1-hno+jlip+EYYr7bC3bzZFrfLr3E= + +estree-is-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-is-function/-/estree-is-function-1.0.0.tgz#c0adc29806d7f18a74db7df0f3b2666702e37ad2" + integrity sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA== + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= +esutils@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.0.0.tgz#8151d358e20c8acc7fb745e7472c0025fe496570" + integrity sha1-gVHTWOIMisx/t0XnRywAJf5JZXA= + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + events@1.1.1, events@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -6781,6 +6984,13 @@ expect@^24.8.0: jest-message-util "^24.8.0" jest-regex-util "^24.3.0" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -7120,6 +7330,23 @@ font-awesome@^4.7.0: resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= +fontkit@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/fontkit/-/fontkit-1.8.1.tgz#ae77485376f1096b45548bf6ced9a07af62a7846" + integrity sha512-BsNCjDoYRxmNWFdAuK1y9bQt+igIxGtTC9u/jSFjR9MKhmI00rP1fwSvERt+5ddE82544l0XH5mzXozQVUy2Tw== + dependencies: + babel-runtime "^6.26.0" + brfs "^2.0.0" + brotli "^1.2.0" + browserify-optional "^1.0.1" + clone "^1.0.4" + deep-equal "^1.0.0" + dfa "^1.2.0" + restructure "^0.5.3" + tiny-inflate "^1.0.2" + unicode-properties "^1.2.2" + unicode-trie "^0.3.0" + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -7381,7 +7608,7 @@ geocoder-arcgis@^2.0.4: lodash.isobject "^3.0.2" lodash.isstring "^4.0.1" -get-assigned-identifiers@^1.2.0: +get-assigned-identifiers@^1.1.0, get-assigned-identifiers@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== @@ -8444,6 +8671,13 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -9741,6 +9975,15 @@ libphonenumber-js@^1.8.1: minimist "^1.2.5" xml2js "^0.4.17" +linebreak@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.0.2.tgz#4b5781733e9a9eb2849dba2f963e47c887f8aa06" + integrity sha512-bJwSRsJeAmaZYnkcwl5sCQNfSDAhBuXxb6L27tb+qkBRtUQSSTUa5bcgCPD6hFEkRNlpWHfK7nFMmcANU7ZP1w== + dependencies: + base64-js "0.0.8" + brfs "^2.0.2" + unicode-trie "^1.0.0" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -10139,6 +10382,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-string@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e" + integrity sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg== + dependencies: + sourcemap-codec "^1.4.1" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -10536,6 +10786,13 @@ meow@^8.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= + dependencies: + source-map "^0.5.6" + merge-stream@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" @@ -10940,6 +11197,11 @@ nerf-dart@^1.0.0: resolved "https://registry.yarnpkg.com/nerf-dart/-/nerf-dart-1.0.0.tgz#e6dab7febf5ad816ea81cf5c629c5a0ebde72c1a" integrity sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo= +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -11850,6 +12112,11 @@ pad-right@^0.2.2: dependencies: repeat-string "^1.5.2" +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" @@ -12116,6 +12383,16 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pdfkit@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/pdfkit/-/pdfkit-0.12.1.tgz#0df246b46cffd3d8fb99b1ea33dc1854430a9199" + integrity sha512-ruNLx49hVW3ePJziKjHtWdTHN1VZHLCUCcbui/vx4lYwFLEM1d8W0L7ObYPbN8EifK7s281ZMugCLgSbk+KRhg== + dependencies: + crypto-js "^3.3.0" + fontkit "^1.8.1" + linebreak "^1.0.2" + png-js "^1.0.0" + pem@^1.13.2: version "1.14.2" resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.2.tgz#ab29350416bc3a532c30beeee0d541af897fb9ac" @@ -12214,6 +12491,11 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== +png-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d" + integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -13429,6 +13711,15 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quote-stream@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" + integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= + dependencies: + buffer-equal "0.0.1" + minimist "^1.1.3" + through2 "^2.0.0" + qw@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4" @@ -13922,7 +14213,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -14548,6 +14839,14 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, is-core-module "^2.1.0" path-parse "^1.0.6" +resolve@^1.1.5: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + resp-modifier@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/resp-modifier/-/resp-modifier-6.0.2.tgz#b124de5c4fbafcba541f48ffa73970f4aa456b4f" @@ -14564,6 +14863,13 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +restructure@^0.5.3: + version "0.5.4" + resolved "https://registry.yarnpkg.com/restructure/-/restructure-0.5.4.tgz#f54e7dd563590fb34fd6bf55876109aeccb28de8" + integrity sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg= + dependencies: + browserify-optional "^1.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -14752,6 +15058,19 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" +scope-analyzer@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/scope-analyzer/-/scope-analyzer-2.1.1.tgz#5156c27de084d74bf75af9e9506aaf95c6e73dd6" + integrity sha512-azEAihtQ9mEyZGhfgTJy3IbOWEzeOrYbg7NcYEshPKnKd+LZmC3TNd5dmDxbLBsTG/JVWmCp+vDJ03vJjeXMHg== + dependencies: + array-from "^2.1.1" + dash-ast "^1.0.0" + es6-map "^0.1.5" + es6-set "^0.1.5" + es6-symbol "^3.1.1" + estree-is-function "^1.0.0" + get-assigned-identifiers "^1.1.0" + scroll-to-element@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/scroll-to-element/-/scroll-to-element-2.0.3.tgz#99b404fc6a09fe73f3c062cd5b8a14efb6404e4d" @@ -14911,6 +15230,11 @@ sha@^3.0.0: dependencies: graceful-fs "^4.1.2" +shallow-copy@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" + integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= + shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" @@ -15168,6 +15492,18 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@~0.1.30: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + integrity sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y= + dependencies: + amdefine ">=0.0.4" + +sourcemap-codec@^1.4.1: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + space-separated-tokens@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa" @@ -15296,6 +15632,13 @@ state-toggle@^1.0.0: resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.2.tgz#75e93a61944116b4959d665c8db2d243631d6ddc" integrity sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw== +static-eval@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.1.0.tgz#a16dbe54522d7fa5ef1389129d813fd47b148014" + integrity sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw== + dependencies: + escodegen "^1.11.1" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -15304,6 +15647,26 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" +static-module@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-3.0.4.tgz#bfbd1d1c38dd1fbbf0bb4af0c1b3ae18a93a2b68" + integrity sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw== + dependencies: + acorn-node "^1.3.0" + concat-stream "~1.6.0" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "^1.11.1" + has "^1.0.1" + magic-string "0.25.1" + merge-source-map "1.0.4" + object-inspect "^1.6.0" + readable-stream "~2.3.3" + scope-analyzer "^2.0.1" + shallow-copy "~0.0.1" + static-eval "^2.0.5" + through2 "~2.0.3" + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -15878,7 +16241,7 @@ through2@2.0.1: readable-stream "~2.0.0" xtend "~4.0.0" -through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0: +through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -15942,6 +16305,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + tiny-invariant@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" @@ -16211,6 +16579,16 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" + integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== + typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -16341,11 +16719,43 @@ unicode-match-property-value-ecmascript@^1.2.0: resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== +unicode-properties@^1.2.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.3.1.tgz#cc642b6314bde2c691d65dd94cece09ed84f1282" + integrity sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + unicode-property-aliases-ecmascript@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== +unicode-trie@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" + integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +unicode-trie@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-1.0.0.tgz#f649afdca127135edb55ca0ad7c8c60656d92ad1" + integrity sha512-v5raLKsobbFbWLMoX9+bChts/VhPPj3XpkNr/HbqkirXR1DPk8eo9IYKyvk0MQZFkaoRsFj2Rmaqgi2rfAZYtA== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + unified@^6.0.0: version "6.2.0" resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" From cd1bd1966d10fb61a0103df35df48d90d476c43e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 21 May 2021 13:10:21 -0400 Subject: [PATCH 09/89] refactor(calltaker): Start a call on clicking Plan button. This is except when the field trip window is shown. --- lib/reducers/call-taker.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 6307f67af..5f773974e 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -114,8 +114,12 @@ function createCallTakerReducer (config) { return update(state, { activeCall: { searches: { $push: [searchId] } } }) - } else if (state.callHistory.visible && searchId.indexOf('_CALL') === -1) { - // If call not in progress, but history is visible, + } else if (calltakerConfig && !state.fieldTrip.visible && searchId.indexOf('_CALL') === -1) { + // If + // - the call module is enabled, and + // - a call is not in progress, and + // - the field trip window is not visible, and + // - regardless of whether the call history is visible or not, // construct new call and add search. Excepting the case where the // searchId contains _CALL, which indicates that a user is viewing a // past call record (and not intending to start a new call session). From cd054ce26f398284599b88e481c8a27760dd30e8 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 25 May 2021 17:55:38 -0400 Subject: [PATCH 10/89] refactor(util/state.js): Tweak selector to prevent extra itinerary.js re-renders. --- lib/util/state.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index d0bfc93b2..1c78f3b98 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -331,34 +331,37 @@ function getVisibleItinerary (state) { return activeSearch && activeSearch.visibleItinerary } +function getItineraryToRender (state) { + const visibleItinerary = getVisibleItinerary(state) + const activeItinerary = getActiveItinerary(state.otp) + const itins = getActiveItineraries(state.otp) + + const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null + ? visibleItinerary + : activeItinerary + return itins[visibleIndex] || activeItinerary +} + export const getTransitiveData = createSelector( hasResponse, hasAtLeastOneResponse, getOtpResponse, queryIsItineraryRouting, - state => getActiveItinerary(state.otp), - state => getActiveItineraries(state.otp), - getVisibleItinerary, + getItineraryToRender, (state, props) => props.getTransitiveRouteLabel, ( hasResponse, hasAtLeastOneResponse, otpResponse, queryIsItineraryRouting, - activeItinerary, - itins, - visibleItinerary, + itineraryToRender, getTransitiveRouteLabel ) => { if (hasResponse) { if (queryIsItineraryRouting && hasAtLeastOneResponse) { - const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null - ? visibleItinerary - : activeItinerary - const foundItinerary = itins[visibleIndex] || activeItinerary - return foundItinerary + return itineraryToRender ? coreUtils.map.itineraryToTransitive( - foundItinerary, + itineraryToRender, null, getTransitiveRouteLabel ) From 6672b591a76b5b703804dc5298479dc4c0352d2f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 25 May 2021 18:20:47 -0400 Subject: [PATCH 11/89] refactor(util/state.js): Reuse getVisibleItinerary, add JSDoc. --- .../narrative/narrative-itineraries.js | 5 ++-- lib/util/state.js | 30 ++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index b241ac753..301c0d89d 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -19,7 +19,8 @@ import { getActiveItineraries, getActiveSearch, getRealtimeEffects, - getResponsesWithErrors + getResponsesWithErrors, + getVisibleItinerary } from '../../util/state' import SaveTripButton from './save-trip-button' @@ -287,7 +288,7 @@ const mapStateToProps = (state, ownProps) => { sort, timeFormat: coreUtils.time.getTimeFormat(state.otp.config), useRealtime, - visibleItinerary: activeSearch && activeSearch.visibleItinerary + visibleItinerary: getVisibleItinerary(state) } } diff --git a/lib/util/state.js b/lib/util/state.js index 1c78f3b98..0f3d7b40d 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -304,21 +304,37 @@ export function getActiveItinerary (otpState) { : null } +/** + * Returns true if the OTP state contains a response within the current active search. + * @param {*} state The entire redux state used to obtain the response. + */ function hasResponse (state) { const activeSearch = getActiveSearch(state.otp) return !!(activeSearch && activeSearch.response) } +/** + * Returns true if the OTP state contains one or more OTP responses for the current active search. + * @param {*} state The entire redux state used to obtain the responses. + */ function hasAtLeastOneResponse (state) { const activeSearch = getActiveSearch(state.otp) return activeSearch && activeSearch.response && activeSearch.response.length > 0 } +/** + * Returns the raw OTP response for the current active search. + * @param {*} state The entire redux state used to obtain the response. + */ function getOtpResponse (state) { const activeSearch = getActiveSearch(state.otp) return activeSearch && activeSearch.response && activeSearch.response.otp } +/** + * Returns true if the query routing type specifies 'ITINERARY'. + * @param {*} state The entire redux state in which the query is stored. + */ function queryIsItineraryRouting (state) { const activeSearch = getActiveSearch(state.otp) return activeSearch && @@ -326,11 +342,19 @@ function queryIsItineraryRouting (state) { activeSearch.query.routingType === 'ITINERARY' } -function getVisibleItinerary (state) { +/** + * Returns the visible itinerary to render on the map and in narrative components. + * @param {*} state The entire redux state from which to retrieve the itinerary. + */ +export function getVisibleItinerary (state) { const activeSearch = getActiveSearch(state.otp) return activeSearch && activeSearch.visibleItinerary } +/** + * Returns the itinerary to be passed to the transitive overlay for rendering. + * @param {*} state The entire redux state from which to derive the itinerary. + */ function getItineraryToRender (state) { const visibleItinerary = getVisibleItinerary(state) const activeItinerary = getActiveItinerary(state.otp) @@ -342,6 +366,10 @@ function getItineraryToRender (state) { return itins[visibleIndex] || activeItinerary } +/** + * Converts an OTP itinerary to the transitive.js format, + * using a selector to prevent unnecessary re-renderings of the transitive overlay. + */ export const getTransitiveData = createSelector( hasResponse, hasAtLeastOneResponse, From 09cac4df519e62c85f7c42d0279660fceb84cb79 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 26 May 2021 15:46:58 -0400 Subject: [PATCH 12/89] Update lib/reducers/call-taker.js Co-authored-by: Landon Reed --- lib/reducers/call-taker.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 5f773974e..08e0dd1c2 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -13,9 +13,7 @@ function createCallTakerReducer (config) { status: FETCH_STATUS.UNFETCHED, data: [] }, - visible: calltakerConfig && - calltakerConfig.options && - calltakerConfig.options.showCallHistoryOnLoad + visible: calltakerConfig?.options?.showCallHistoryOnLoad }, fieldTrip: { activeId: null, From 4638a55af190eed58b27d4026a1fda508421afc9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 28 May 2021 14:13:25 -0400 Subject: [PATCH 13/89] refactor(call-taker): Autostart call on plan btn click only. --- lib/actions/call-taker.js | 16 ++++++++++++++++ lib/actions/form.js | 8 ++------ lib/components/admin/query-record.js | 2 +- lib/components/app/call-taker-panel.js | 6 +++++- lib/reducers/call-taker.js | 13 ------------- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 6d2107288..61296b466 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -36,6 +36,22 @@ export function resetAndToggleCallHistory () { } } +/** + * Start a new call (and show the call history window) if + * - the call module is enabled, and + * - a call is not in progress, and + * - the field trip window is not visible. + */ +export function beginCallIfNeeded () { + return function (dispatch, getState) { + const {callTaker, otp} = getState() + const calltakerConfig = otp.config.modules.find(m => m.id === 'call') + if (calltakerConfig && !callTaker.activeCall && !callTaker.fieldTrip.visible) { + dispatch(beginCall()) + } + } +} + /** * End the active call and store the queries made during the call. */ diff --git a/lib/actions/form.js b/lib/actions/form.js index 976860c57..077e85ae3 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -87,19 +87,15 @@ export function setQueryParam (payload, searchId) { * performed according to the behavior of setQueryParam (i.e., if the passed * searchId is not null). * @param {Object} params an object containing URL query params - * @param {string} source optionally contains the source of the query params - * (e.g., _CALL indicates that the source is from a past - * query made during a call taker session) */ -export function parseUrlQueryString (params = getUrlParams(), source) { +export function parseUrlQueryString (params = getUrlParams()) { return function (dispatch, getState) { // Filter out the OTP (i.e. non-UI) params and set the initial query const planParams = {} Object.keys(params).forEach(key => { if (!key.startsWith('ui_')) planParams[key] = params[key] }) - let searchId = params.ui_activeSearch || coreUtils.storage.randId() - if (source) searchId += source + const searchId = params.ui_activeSearch || coreUtils.storage.randId() // Convert strings to numbers/objects and dispatch planParamsToQueryAsync(planParams, getState().otp.config) .then(query => dispatch(setQueryParam(query, searchId))) diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index 7b64eb4da..d8fd28c36 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -20,7 +20,7 @@ class QueryRecordLayout extends Component { const {parseUrlQueryString} = this.props const params = this._getParams() params.departArrive = params.arriveBy ? 'ARRIVE' : 'DEPART' - parseUrlQueryString(params, '_CALL') + parseUrlQueryString(params) } render () { diff --git a/lib/components/app/call-taker-panel.js b/lib/components/app/call-taker-panel.js index a15366b49..b731c07da 100644 --- a/lib/components/app/call-taker-panel.js +++ b/lib/components/app/call-taker-panel.js @@ -5,6 +5,7 @@ import { Button } from 'react-bootstrap' import { connect } from 'react-redux' import * as apiActions from '../../actions/api' +import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' import * as formActions from '../../actions/form' import AddPlaceButton from '../form/add-place-button' @@ -35,7 +36,7 @@ class CallTakerPanel extends Component { } _planTrip = () => { - const {currentQuery, routingQuery} = this.props + const {beginCallIfNeeded, currentQuery, routingQuery} = this.props const issues = [] if (!hasValidLocation(currentQuery, 'from')) issues.push('from') if (!hasValidLocation(currentQuery, 'to')) issues.push('to') @@ -45,6 +46,8 @@ class CallTakerPanel extends Component { return } if (this.state.expandAdvanced) this.setState({expandAdvanced: false}) + // Ensure call is started, or start a new one. + beginCallIfNeeded() routingQuery() } @@ -288,6 +291,7 @@ const mapStateToProps = (state, ownProps) => { } const mapDispatchToProps = { + beginCallIfNeeded: callTakerActions.beginCallIfNeeded, findRoutes: apiActions.findRoutes, routingQuery: apiActions.routingQuery, setGroupSize: fieldTripActions.setGroupSize, diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 08e0dd1c2..97709ec5e 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -112,19 +112,6 @@ function createCallTakerReducer (config) { return update(state, { activeCall: { searches: { $push: [searchId] } } }) - } else if (calltakerConfig && !state.fieldTrip.visible && searchId.indexOf('_CALL') === -1) { - // If - // - the call module is enabled, and - // - a call is not in progress, and - // - the field trip window is not visible, and - // - regardless of whether the call history is visible or not, - // construct new call and add search. Excepting the case where the - // searchId contains _CALL, which indicates that a user is viewing a - // past call record (and not intending to start a new call session). - const newCall = constructNewCall() - newCall.searches.push(searchId) - // Initialize new call and show call history window. - return update(state, { activeCall: { $set: newCall } }) } // Otherwise, ignore. return state From b14f52955c55aa31e44b55e7449d65f90e748dad Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Thu, 3 Jun 2021 17:35:05 +0100 Subject: [PATCH 14/89] fix(batch-routing-panel.js): Added the reverse directions button The Batch routing panel was missing the button to reverse the directions so the switchButton #371 --- lib/components/app/batch-routing-panel.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/components/app/batch-routing-panel.js b/lib/components/app/batch-routing-panel.js index 0cc9a36a8..ee6376d6b 100644 --- a/lib/components/app/batch-routing-panel.js +++ b/lib/components/app/batch-routing-panel.js @@ -9,6 +9,7 @@ import LocationField from '../form/connected-location-field' import NarrativeItineraries from '../narrative/narrative-itineraries' import { getActiveSearch, getShowUserSettings } from '../../util/state' import ViewerContainer from '../viewers/viewer-container' +import switchButton from '../form/switch-button' // Style for setting the top of the narrative itineraries based on the width of the window. // If the window width is less than 1200px (Bootstrap's "large" size), the @@ -46,6 +47,9 @@ class BatchRoutingPanel extends Component { locationType='to' showClearButton={!mobile} /> +
+ } /> +
{/* FIXME: Add back user settings (home, work, etc.) once connected to the middleware persistence. From 204878088bea0570fa7dea743fe4c896a3faa1fa Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 3 Jun 2021 12:51:08 -0400 Subject: [PATCH 15/89] chore(lint): Add pre-commit hook to enforce formatting. --- .husky/.gitignore | 1 + .husky/pre-commit | 4 + package.json | 43 ++++ yarn.lock | 582 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 622 insertions(+), 8 deletions(-) create mode 100644 .husky/.gitignore create mode 100755 .husky/pre-commit diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 000000000..31354ec13 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..36af21989 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/package.json b/package.json index 231e168a8..621582195 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "jest": "mastarm test -e test", "lint": "mastarm lint lib __tests__ --quiet", "lint-docs": "documentation lint lib/**/*.js", + "postinstall": "husky install", "prepublish": "mastarm prepublish --config configurations/prepublish", + "prepublishOnly": "pinst --disable", + "postpublish": "pinst --enable", "prestart": "yarn", "test": "yarn run lint && yarn run lint-docs && yarn run jest", "semantic-release": "semantic-release", @@ -107,9 +110,14 @@ "enzyme-adapter-react-16": "^1.4.0", "enzyme-to-json": "^3.4.0", "es6-math": "^1.0.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-sort-destructure-keys": "^1.3.5", + "husky": "^6.0.0", "leaflet": "^1.6.0", + "lint-staged": "^11.0.0", "mastarm": "^5.1.3", "nock": "^9.0.9", + "pinst": "^2.1.6", "react": "^16.9.0", "react-dom": "^16.9.0", "react-leaflet": "^2.6.1", @@ -128,5 +136,40 @@ "/__tests__/test-utils/setup-env.js" ], "testURL": "http://localhost/" + }, + "eslintConfig": { + "extends": [ + "../mastarm/lib/eslintrc.json" + ], + "parser": "babel-eslint", + "rules": { + "react/jsx-sort-props": [ + "error", + { + "ignoreCase": true + } + ], + "sort-destructure-keys/sort-destructure-keys": ["error", { + "caseSensitive": false + }], + "sort-keys": [ + "error", + "asc", + { + "caseSensitive": false + } + ], + "sort-vars": [ + "error", + { + "ignoreCase": true + } + ] + } + }, + "lint-staged": { + "*.{js,jsx}": [ + "eslint --fix" + ] } } diff --git a/yarn.lock b/yarn.lock index 383ad16bf..25bbf3100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2326,11 +2326,23 @@ ansi-align@^2.0.0: dependencies: string-width "^2.0.0" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-escapes@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -2544,6 +2556,17 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" +array-includes@^3.1.2, array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" + is-string "^1.0.5" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -2591,6 +2614,16 @@ array.prototype.flat@^1.2.1: es-abstract "^1.10.0" function-bind "^1.1.1" +array.prototype.flatmap@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -2655,6 +2688,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.0, async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -4142,6 +4180,14 @@ cachedir@2.1.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.1.0.tgz#b448c32b44cd9c0cd6ce4c419fa5b3c112c02191" integrity sha512-xGBpPqoBvn3unBW7oxgb8aJn42K0m9m1/wyjmazah10Fq7bROGG3kRAE6OIyr3U3PIJUqGuebhCEdMk9OKJG0A== +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + call-limit@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.1.tgz#ef15f2670db3f1992557e2d965abc459e6e358d4" @@ -4333,6 +4379,14 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.0, chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + character-entities-html4@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.3.tgz#5ce6e01618e47048ac22f34f7f39db5c6fd679ef" @@ -4507,6 +4561,13 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + cli-table3@^0.5.0, cli-table3@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" @@ -4524,6 +4585,14 @@ cli-table@^0.3.1: dependencies: colors "1.0.3" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -4706,6 +4775,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + colormin@^1.0.5: version "1.1.2" resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" @@ -4765,6 +4839,11 @@ commander@^2.11.0, commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commitizen@^3.0.7: version "3.1.2" resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-3.1.2.tgz#29ddd8b39396923e9058a0e4840cbeef144290be" @@ -5060,6 +5139,17 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + country-flag-icons@^1.0.2: version "1.2.5" resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.2.5.tgz#784185503f3589e650b30402c93ef7cc2a3225a9" @@ -5131,7 +5221,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -5619,7 +5709,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -5656,7 +5746,7 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -dedent@0.7.0: +dedent@0.7.0, dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= @@ -6184,6 +6274,13 @@ enhanced-resolve@^3.3.0: object-assign "^4.0.1" tapable "^0.2.7" +enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -6319,6 +6416,28 @@ es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13 string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: + version "1.18.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + es-cookie@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" @@ -6494,6 +6613,31 @@ eslint-plugin-react@^7.12.4: prop-types "^15.7.2" resolve "^1.10.1" +eslint-plugin-react@^7.24.0: + version "7.24.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz#eadedfa351a6f36b490aa17f4fa9b14e842b9eb4" + integrity sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q== + dependencies: + array-includes "^3.1.3" + array.prototype.flatmap "^1.2.4" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.0.4" + object.entries "^1.1.4" + object.fromentries "^2.0.4" + object.values "^1.1.4" + prop-types "^15.7.2" + resolve "^2.0.0-next.3" + string.prototype.matchall "^4.0.5" + +eslint-plugin-sort-destructure-keys@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-sort-destructure-keys/-/eslint-plugin-sort-destructure-keys-1.3.5.tgz#c6f45c3e58d4435564025a6ca5f4a838010800fd" + integrity sha512-JmVpidhDsLwZsmRDV7Tf/vZgOAOEQGkLtwToSvX5mD8fuWYS/xkgMRBsalW1fGlc8CgJJwnzropt4oMQ7YCHLg== + dependencies: + natural-compare-lite "^1.4.0" + eslint-plugin-standard@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c" @@ -6720,6 +6864,21 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.0.tgz#3ea50ee863d226bfa323528cce1684e7481dfe46" + integrity sha512-CkdUB7s2y6S+d4y+OM/+ZtQcJCiKUCth4cNImGMqrt2zEVtW2rfHGspQBE1GDo6LjeNIQmTPKXqTCKjqFKyu3A== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -7211,6 +7370,11 @@ from2@^2.0.3, from2@^2.1.0, from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" +fromentries@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -7401,6 +7565,20 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + get-port@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" @@ -7437,6 +7615,11 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -7690,6 +7873,11 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -7710,6 +7898,11 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + has-unicode@^2.0.0, has-unicode@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -8006,6 +8199,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + humanize-duration@^3.25.2: version "3.25.2" resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.25.2.tgz#5259585b749ecc5ad5a60fb37121aee0e9ab0c5e" @@ -8018,6 +8216,11 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +husky@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" + integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ== + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8095,6 +8298,14 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" @@ -8270,6 +8481,15 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + interpret@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -8370,6 +8590,11 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -8389,6 +8614,13 @@ is-boolean-object@^1.0.0: resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M= +is-boolean-object@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + dependencies: + call-bind "^1.0.2" + is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -8404,6 +8636,11 @@ is-callable@^1.1.4, is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== +is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + is-ci@^1.0.10: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -8444,6 +8681,13 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" +is-core-module@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8593,6 +8837,11 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -8603,6 +8852,11 @@ is-number-object@^1.0.3: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" integrity sha1-8mWrian0RQNO9q/xWo8AsA9VF5k= +is-number-object@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -8627,7 +8881,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0: +is-obj@^1.0.0, is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= @@ -8693,6 +8947,19 @@ is-regex@^1.0.4, is-regex@^1.1.0: dependencies: has-symbols "^1.0.1" +is-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.2" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + is-relative@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" @@ -8732,6 +8999,11 @@ is-string@^1.0.4: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" integrity sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ= +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== + is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" @@ -8758,6 +9030,13 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" +is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -8777,6 +9056,11 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -9477,6 +9761,14 @@ jsx-ast-utils@^2.1.0, jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== + dependencies: + array-includes "^3.1.2" + object.assign "^4.1.2" + keycode@^2.1.7, keycode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" @@ -9746,6 +10038,40 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +lint-staged@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.0.0.tgz#24d0a95aa316ba28e257f5c4613369a75a10c712" + integrity sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw== + dependencies: + chalk "^4.1.1" + cli-truncate "^2.1.0" + commander "^7.2.0" + cosmiconfig "^7.0.0" + debug "^4.3.1" + dedent "^0.7.0" + enquirer "^2.3.6" + execa "^5.0.0" + listr2 "^3.8.2" + log-symbols "^4.1.0" + micromatch "^4.0.4" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "^3.3.0" + +listr2@^3.8.2: + version "3.9.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.9.0.tgz#27f23c91ba4fdf513b0682bf604bc6b0ab36b6c1" + integrity sha512-+JxQt7Vi4WEWgJsxmOEX9lDbCumrb3mrEYIeE1VI7I4lf2rXE4v9pq3RMVNp+a9s6mCgc/IsF0ppHsLrx2BEAw== + dependencies: + cli-truncate "^2.1.0" + colorette "^1.2.2" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" + livereload-js@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" @@ -10074,6 +10400,24 @@ log-symbols@^2.2.0: dependencies: chalk "^2.0.1" +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loglevel-colored-level-prefix@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" @@ -10623,6 +10967,14 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + middleware-proxy@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/middleware-proxy/-/middleware-proxy-2.0.5.tgz#4038f309a1ee95afd3d017bc701950df82fd1660" @@ -10905,6 +11257,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha1-F7CVgZiJef3a/gIB6TG6kzyWy7Q= + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -11257,7 +11614,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -11446,6 +11803,11 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-inspect@^1.10.3, object-inspect@^1.9.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + object-inspect@^1.6.0, object-inspect@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -11488,6 +11850,16 @@ object.assign@^4.0.4, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.entries@^1.0.4, object.entries@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" @@ -11498,6 +11870,15 @@ object.entries@^1.0.4, object.entries@^1.1.0: function-bind "^1.1.1" has "^1.0.3" +object.entries@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" + integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" + object.fromentries@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab" @@ -11508,6 +11889,16 @@ object.fromentries@^2.0.0: function-bind "^1.1.1" has "^1.0.1" +object.fromentries@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + object.getownpropertydescriptors@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" @@ -11541,6 +11932,15 @@ object.values@^1.0.4, object.values@^1.1.0: function-bind "^1.1.1" has "^1.0.3" +object.values@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" + on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -11574,7 +11974,7 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -12136,6 +12536,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +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== + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -12168,6 +12573,13 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= +pinst@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/pinst/-/pinst-2.1.6.tgz#8d968b8ec1dac5dddcfc976c735592dbec58b42c" + integrity sha512-B4dYmf6nEXg1NpDSB+orYWvKa5Kfmz5KzWC29U59dpVM4S/+xp0ak/JMEsw04UQTNNKps7klu0BUalr343Gt9g== + dependencies: + fromentries "^1.3.2" + pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -12204,6 +12616,13 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + plur@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" @@ -14177,6 +14596,14 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -14548,6 +14975,14 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, is-core-module "^2.1.0" path-parse "^1.0.6" +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + resp-modifier@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/resp-modifier/-/resp-modifier-6.0.2.tgz#b124de5c4fbafcba541f48ffa73970f4aa456b4f" @@ -14564,6 +14999,14 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -14684,6 +15127,13 @@ rxjs@^6.1.0, rxjs@^6.4.0, rxjs@^6.5.2: dependencies: tslib "^1.9.0" +rxjs@^6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + 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, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -14798,6 +15248,11 @@ semantic-release@^17.2.3: signale "^1.2.1" yargs "^15.0.1" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -14972,7 +15427,16 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -signal-exit@^3.0.0, signal-exit@^3.0.2: +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -15041,6 +15505,24 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -15394,6 +15876,11 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= +string-argv@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -15447,6 +15934,20 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.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" + integrity sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + string.prototype.trim@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz#75a729b10cfc1be439543dae442129459ce61e3d" @@ -15464,6 +15965,14 @@ string.prototype.trimend@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string.prototype.trimstart@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" @@ -15472,6 +15981,14 @@ string.prototype.trimstart@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string_decoder@0.10, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -15501,6 +16018,15 @@ stringify-entities@^1.0.1: is-alphanumerical "^1.0.0" is-hexadecimal "^1.0.0" +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + stringify-package@^1.0.0, stringify-package@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" @@ -15908,7 +16434,7 @@ through2@~0.6.1: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.7, through@~2.3.4: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.7, through@^2.3.8, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -16201,6 +16727,11 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -16277,6 +16808,16 @@ umd@^3.0.0: resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -17043,6 +17584,17 @@ whet.extend@~0.9.9: resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -17134,6 +17686,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -17250,6 +17811,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yaml@^1.7.2: version "1.9.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.2.tgz#f0cfa865f003ab707663e4f04b3956957ea564ed" From f92ffdbce4b9763bc69806e4b96d91913f32a3a8 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Thu, 3 Jun 2021 12:43:20 -0700 Subject: [PATCH 16/89] perf(transitive): avoid unnecessary rerenders --- lib/components/map/connected-transitive-overlay.js | 11 ++++------- lib/components/map/default-map.js | 7 ------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 80b78fd66..8a2658fed 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -2,7 +2,7 @@ import TransitiveOverlay from '@opentripplanner/transitive-overlay' import React, { Component } from 'react' import { connect } from 'react-redux' -import { getActiveSearch, getTransitiveData } from '../../util/state' +import { getTransitiveData } from '../../util/state' /** * Wrapper for TransitiveOverlay that avoids rerenders by checking transitive @@ -14,10 +14,11 @@ class TransitiveCanvasOverlay extends Component { } render () { - const { transitiveData, ...otherProps } = this.props + const { labeledModes, styles, transitiveData } = this.props return ( ) @@ -27,14 +28,10 @@ class TransitiveCanvasOverlay extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) - const { activeItinerary, query } = activeSearch || {} const { labeledModes, styles } = state.otp.config.map.transitive || {} return { - activeItinerary, labeledModes, - routingType: query && query.routingType, styles, transitiveData: getTransitiveData(state, ownProps) } diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index d37e4db45..dd1d9b479 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -1,5 +1,4 @@ import BaseMap from '@opentripplanner/base-map' -import coreUtils from '@opentripplanner/core-utils' import React, { Component } from 'react' import { connect } from 'react-redux' import styled from 'styled-components' @@ -123,7 +122,6 @@ class DefaultMap extends Component { bikeRentalStations, carRentalQuery, carRentalStations, - itineraryView, mapConfig, mapPopupLocation, vehicleRentalQuery, @@ -169,9 +167,6 @@ class DefaultMap extends Component { */} @@ -223,12 +218,10 @@ const mapStateToProps = (state, ownProps) => { const overlays = state.otp.config.map && state.otp.config.map.overlays ? state.otp.config.map.overlays : [] - const urlParams = coreUtils.query.getUrlParams() return { bikeRentalStations: state.otp.overlay.bikeRental.stations, carRentalStations: state.otp.overlay.carRental.stations, - itineraryView: urlParams.ui_itineraryView, mapConfig: state.otp.config.map, mapPopupLocation: state.otp.ui.mapPopupLocation, overlays, From fbf45065e5cf175d57c71f52cf63e10a9410cfab Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 3 Jun 2021 15:58:54 -0400 Subject: [PATCH 17/89] style(app-menu): Test styles --- lib/components/app/app-menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index 703c0752d..74201463a 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -6,7 +6,6 @@ import { DropdownButton, MenuItem } from 'react-bootstrap' import { withRouter } from 'react-router' import Icon from '../narrative/icon' - import { MainPanelContent, setMainPanelContent } from '../../actions/ui' // TODO: make menu items configurable via props/config @@ -23,6 +22,7 @@ class AppMenu extends Component { _startOver = () => { const { location, reactRouterConfig } = this.props const { search } = location + let startOverUrl = '/' if (reactRouterConfig && reactRouterConfig.basename) { startOverUrl += reactRouterConfig.basename @@ -46,10 +46,10 @@ class AppMenu extends Component {
)} - noCaret className='app-menu-button' - id='app-menu'> + id='app-menu' + noCaret + title={()}> {languageConfig.routeViewer || 'Route Viewer'} From 398a4bbf50cb2ce1fd4b4ebf39871438d142a25c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 3 Jun 2021 16:01:49 -0400 Subject: [PATCH 18/89] chore(lint): Remove interference with yarn lint. --- eslint-precommit.json | 36 ++++++ package.json | 33 +---- yarn.lock | 278 ------------------------------------------ 3 files changed, 37 insertions(+), 310 deletions(-) create mode 100644 eslint-precommit.json diff --git a/eslint-precommit.json b/eslint-precommit.json new file mode 100644 index 000000000..63d59b0b2 --- /dev/null +++ b/eslint-precommit.json @@ -0,0 +1,36 @@ +{ + "extends": [ + "../mastarm/lib/eslintrc.json" + ], + "parser": "babel-eslint", + "plugins": [ + "sort-destructure-keys" + ], + "rules": { + "react/jsx-sort-props": [ + "error", + { + "ignoreCase": true + } + ], + "sort-destructure-keys/sort-destructure-keys": [ + "error", + { + "caseSensitive": false + } + ], + "sort-keys": [ + "error", + "asc", + { + "caseSensitive": false + } + ], + "sort-vars": [ + "error", + { + "ignoreCase": true + } + ] + } +} diff --git a/package.json b/package.json index 621582195..4504174e3 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,6 @@ "enzyme-adapter-react-16": "^1.4.0", "enzyme-to-json": "^3.4.0", "es6-math": "^1.0.0", - "eslint-plugin-react": "^7.24.0", "eslint-plugin-sort-destructure-keys": "^1.3.5", "husky": "^6.0.0", "leaflet": "^1.6.0", @@ -137,39 +136,9 @@ ], "testURL": "http://localhost/" }, - "eslintConfig": { - "extends": [ - "../mastarm/lib/eslintrc.json" - ], - "parser": "babel-eslint", - "rules": { - "react/jsx-sort-props": [ - "error", - { - "ignoreCase": true - } - ], - "sort-destructure-keys/sort-destructure-keys": ["error", { - "caseSensitive": false - }], - "sort-keys": [ - "error", - "asc", - { - "caseSensitive": false - } - ], - "sort-vars": [ - "error", - { - "ignoreCase": true - } - ] - } - }, "lint-staged": { "*.{js,jsx}": [ - "eslint --fix" + "eslint --config eslint-precommit.json --fix" ] } } diff --git a/yarn.lock b/yarn.lock index 25bbf3100..71e325cd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2556,17 +2556,6 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" -array-includes@^3.1.2, array-includes@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" - integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - get-intrinsic "^1.1.1" - is-string "^1.0.5" - array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -2614,16 +2603,6 @@ array.prototype.flat@^1.2.1: es-abstract "^1.10.0" function-bind "^1.1.1" -array.prototype.flatmap@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" - integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - function-bind "^1.1.1" - arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -4180,14 +4159,6 @@ cachedir@2.1.0: resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.1.0.tgz#b448c32b44cd9c0cd6ce4c419fa5b3c112c02191" integrity sha512-xGBpPqoBvn3unBW7oxgb8aJn42K0m9m1/wyjmazah10Fq7bROGG3kRAE6OIyr3U3PIJUqGuebhCEdMk9OKJG0A== -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - call-limit@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.1.tgz#ef15f2670db3f1992557e2d965abc459e6e358d4" @@ -6416,28 +6387,6 @@ es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.13 string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: - version "1.18.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" - integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.2" - is-callable "^1.2.3" - is-negative-zero "^2.0.1" - is-regex "^1.1.3" - is-string "^1.0.6" - object-inspect "^1.10.3" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" - es-cookie@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" @@ -6613,24 +6562,6 @@ eslint-plugin-react@^7.12.4: prop-types "^15.7.2" resolve "^1.10.1" -eslint-plugin-react@^7.24.0: - version "7.24.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz#eadedfa351a6f36b490aa17f4fa9b14e842b9eb4" - integrity sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q== - dependencies: - array-includes "^3.1.3" - array.prototype.flatmap "^1.2.4" - doctrine "^2.1.0" - has "^1.0.3" - jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.0.4" - object.entries "^1.1.4" - object.fromentries "^2.0.4" - object.values "^1.1.4" - prop-types "^15.7.2" - resolve "^2.0.0-next.3" - string.prototype.matchall "^4.0.5" - eslint-plugin-sort-destructure-keys@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/eslint-plugin-sort-destructure-keys/-/eslint-plugin-sort-destructure-keys-1.3.5.tgz#c6f45c3e58d4435564025a6ca5f4a838010800fd" @@ -7565,15 +7496,6 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7873,11 +7795,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -7898,11 +7815,6 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== -has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - has-unicode@^2.0.0, has-unicode@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -8481,15 +8393,6 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - interpret@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -8590,11 +8493,6 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -8614,13 +8512,6 @@ is-boolean-object@^1.0.0: resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M= -is-boolean-object@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== - dependencies: - call-bind "^1.0.2" - is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -8636,11 +8527,6 @@ is-callable@^1.1.4, is-callable@^1.2.0: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== -is-callable@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== - is-ci@^1.0.10: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -8681,13 +8567,6 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -8837,11 +8716,6 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== - is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -8852,11 +8726,6 @@ is-number-object@^1.0.3: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" integrity sha1-8mWrian0RQNO9q/xWo8AsA9VF5k= -is-number-object@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== - is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -8947,14 +8816,6 @@ is-regex@^1.0.4, is-regex@^1.1.0: dependencies: has-symbols "^1.0.1" -is-regex@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" - integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== - dependencies: - call-bind "^1.0.2" - has-symbols "^1.0.2" - is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" @@ -8999,11 +8860,6 @@ is-string@^1.0.4: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" integrity sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ= -is-string@^1.0.5, is-string@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" - integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== - is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" @@ -9030,13 +8886,6 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -9761,14 +9610,6 @@ jsx-ast-utils@^2.1.0, jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" -"jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" - integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== - dependencies: - array-includes "^3.1.2" - object.assign "^4.1.2" - keycode@^2.1.7, keycode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" @@ -11803,11 +11644,6 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-inspect@^1.10.3, object-inspect@^1.9.0: - version "1.10.3" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" - integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== - object-inspect@^1.6.0, object-inspect@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -11850,16 +11686,6 @@ object.assign@^4.0.4, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - object.entries@^1.0.4, object.entries@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" @@ -11870,15 +11696,6 @@ object.entries@^1.0.4, object.entries@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -object.entries@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" - integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.2" - object.fromentries@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab" @@ -11889,16 +11706,6 @@ object.fromentries@^2.0.0: function-bind "^1.1.1" has "^1.0.1" -object.fromentries@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" - integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" - object.getownpropertydescriptors@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" @@ -11932,15 +11739,6 @@ object.values@^1.0.4, object.values@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -object.values@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" - integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.2" - on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -14596,14 +14394,6 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -14975,14 +14765,6 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, is-core-module "^2.1.0" path-parse "^1.0.6" -resolve@^2.0.0-next.3: - version "2.0.0-next.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" - integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - resp-modifier@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/resp-modifier/-/resp-modifier-6.0.2.tgz#b124de5c4fbafcba541f48ffa73970f4aa456b4f" @@ -15427,15 +15209,6 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -15934,20 +15707,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.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" - integrity sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.2" - get-intrinsic "^1.1.1" - has-symbols "^1.0.2" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.3.1" - side-channel "^1.0.4" - string.prototype.trim@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz#75a729b10cfc1be439543dae442129459ce61e3d" @@ -15965,14 +15724,6 @@ string.prototype.trimend@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - string.prototype.trimstart@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" @@ -15981,14 +15732,6 @@ string.prototype.trimstart@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - string_decoder@0.10, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -16808,16 +16551,6 @@ umd@^3.0.0: resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -17584,17 +17317,6 @@ whet.extend@~0.9.9: resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" From c2c9ee93fba54900be347a6c2b4025d969e81bdd Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 4 Jun 2021 08:39:14 -0400 Subject: [PATCH 19/89] refactor(call-taker): increase call history limit --- lib/actions/call-taker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 5215eb5f4..c46a455ee 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -156,7 +156,7 @@ export function fetchCalls () { if (sessionIsInvalid(callTaker.session)) return const {datastoreUrl} = otp.config const {sessionId} = callTaker.session - const limit = 30 + const limit = 1000 fetch(`${datastoreUrl}/calltaker/call?${qs.stringify({limit, sessionId})}`) .then(res => res.json()) .then(calls => dispatch(receivedCalls({calls}))) From d44fd2fb90f36e1f317a232a2210d60b964ae205 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 4 Jun 2021 09:26:16 -0400 Subject: [PATCH 20/89] Revert "style(app-menu): Test styles" This reverts commit fbf45065e5cf175d57c71f52cf63e10a9410cfab. --- lib/components/app/app-menu.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index 74201463a..703c0752d 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -6,6 +6,7 @@ import { DropdownButton, MenuItem } from 'react-bootstrap' import { withRouter } from 'react-router' import Icon from '../narrative/icon' + import { MainPanelContent, setMainPanelContent } from '../../actions/ui' // TODO: make menu items configurable via props/config @@ -22,7 +23,6 @@ class AppMenu extends Component { _startOver = () => { const { location, reactRouterConfig } = this.props const { search } = location - let startOverUrl = '/' if (reactRouterConfig && reactRouterConfig.basename) { startOverUrl += reactRouterConfig.basename @@ -46,10 +46,10 @@ class AppMenu extends Component {
)} noCaret - title={()}> + className='app-menu-button' + id='app-menu'> {languageConfig.routeViewer || 'Route Viewer'} From e66a45da926abaae948d9460fd49eeee52417e70 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 4 Jun 2021 09:38:45 -0400 Subject: [PATCH 21/89] chore(lint): Use only mastarm eslint settings. --- eslint-precommit.json | 36 ------------------------------------ package.json | 3 +-- yarn.lock | 12 ------------ 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 eslint-precommit.json diff --git a/eslint-precommit.json b/eslint-precommit.json deleted file mode 100644 index 63d59b0b2..000000000 --- a/eslint-precommit.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": [ - "../mastarm/lib/eslintrc.json" - ], - "parser": "babel-eslint", - "plugins": [ - "sort-destructure-keys" - ], - "rules": { - "react/jsx-sort-props": [ - "error", - { - "ignoreCase": true - } - ], - "sort-destructure-keys/sort-destructure-keys": [ - "error", - { - "caseSensitive": false - } - ], - "sort-keys": [ - "error", - "asc", - { - "caseSensitive": false - } - ], - "sort-vars": [ - "error", - { - "ignoreCase": true - } - ] - } -} diff --git a/package.json b/package.json index 4504174e3..4536ce1e0 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,6 @@ "enzyme-adapter-react-16": "^1.4.0", "enzyme-to-json": "^3.4.0", "es6-math": "^1.0.0", - "eslint-plugin-sort-destructure-keys": "^1.3.5", "husky": "^6.0.0", "leaflet": "^1.6.0", "lint-staged": "^11.0.0", @@ -138,7 +137,7 @@ }, "lint-staged": { "*.{js,jsx}": [ - "eslint --config eslint-precommit.json --fix" + "eslint --config node_modules/mastarm/lib/eslintrc.json --fix" ] } } diff --git a/yarn.lock b/yarn.lock index 71e325cd6..7b959e53f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6562,13 +6562,6 @@ eslint-plugin-react@^7.12.4: prop-types "^15.7.2" resolve "^1.10.1" -eslint-plugin-sort-destructure-keys@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/eslint-plugin-sort-destructure-keys/-/eslint-plugin-sort-destructure-keys-1.3.5.tgz#c6f45c3e58d4435564025a6ca5f4a838010800fd" - integrity sha512-JmVpidhDsLwZsmRDV7Tf/vZgOAOEQGkLtwToSvX5mD8fuWYS/xkgMRBsalW1fGlc8CgJJwnzropt4oMQ7YCHLg== - dependencies: - natural-compare-lite "^1.4.0" - eslint-plugin-standard@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c" @@ -11098,11 +11091,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha1-F7CVgZiJef3a/gIB6TG6kzyWy7Q= - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" From 344fc9fb80b4de8724aa55b4bfae5dafd15d0fc9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 4 Jun 2021 15:19:48 -0400 Subject: [PATCH 22/89] refactor(util/state): Address PR comments. --- lib/components/map/default-map.js | 7 --- .../narrative/narrative-itineraries.js | 4 +- lib/util/state.js | 45 ++++++------------- 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/lib/components/map/default-map.js b/lib/components/map/default-map.js index dd1d9b479..f3306cbe9 100644 --- a/lib/components/map/default-map.js +++ b/lib/components/map/default-map.js @@ -158,13 +158,6 @@ class DefaultMap extends Component { - {/* - HACK: Use the key prop to force a remount and full resizing of transitive - if the map container size changes, - per https://linguinecode.com/post/4-methods-to-re-render-react-component - Without it, transitive resolution will not match the map, - and transitive will appear blurry after e.g. the narrative is expanded. - */} diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 301c0d89d..ce7d16a28 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -20,7 +20,7 @@ import { getActiveSearch, getRealtimeEffects, getResponsesWithErrors, - getVisibleItinerary + getVisibleItineraryIndex } from '../../util/state' import SaveTripButton from './save-trip-button' @@ -288,7 +288,7 @@ const mapStateToProps = (state, ownProps) => { sort, timeFormat: coreUtils.time.getTimeFormat(state.otp.config), useRealtime, - visibleItinerary: getVisibleItinerary(state) + visibleItinerary: getVisibleItineraryIndex(state) } } diff --git a/lib/util/state.js b/lib/util/state.js index 0f3d7b40d..bbde22fa6 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -308,45 +308,34 @@ export function getActiveItinerary (otpState) { * Returns true if the OTP state contains a response within the current active search. * @param {*} state The entire redux state used to obtain the response. */ -function hasResponse (state) { +function activeSearchHasResponse (state) { const activeSearch = getActiveSearch(state.otp) return !!(activeSearch && activeSearch.response) } -/** - * Returns true if the OTP state contains one or more OTP responses for the current active search. - * @param {*} state The entire redux state used to obtain the responses. - */ -function hasAtLeastOneResponse (state) { - const activeSearch = getActiveSearch(state.otp) - return activeSearch && activeSearch.response && activeSearch.response.length > 0 -} - /** * Returns the raw OTP response for the current active search. * @param {*} state The entire redux state used to obtain the response. */ function getOtpResponse (state) { - const activeSearch = getActiveSearch(state.otp) - return activeSearch && activeSearch.response && activeSearch.response.otp + return getActiveSearch(state.otp)?.response?.otp } /** - * Returns true if the query routing type specifies 'ITINERARY'. + * Returns true if the query routing type specifies 'ITINERARY' and an OTP response is available. * @param {*} state The entire redux state in which the query is stored. */ -function queryIsItineraryRouting (state) { +function itineraryResponseExists (state) { const activeSearch = getActiveSearch(state.otp) - return activeSearch && - activeSearch.query && - activeSearch.query.routingType === 'ITINERARY' + return activeSearch?.response?.length > 0 && + activeSearch?.query?.routingType === 'ITINERARY' } /** - * Returns the visible itinerary to render on the map and in narrative components. + * Returns the visible itinerary index to render on the map and in narrative components. * @param {*} state The entire redux state from which to retrieve the itinerary. */ -export function getVisibleItinerary (state) { +export function getVisibleItineraryIndex (state) { const activeSearch = getActiveSearch(state.otp) return activeSearch && activeSearch.visibleItinerary } @@ -356,14 +345,10 @@ export function getVisibleItinerary (state) { * @param {*} state The entire redux state from which to derive the itinerary. */ function getItineraryToRender (state) { - const visibleItinerary = getVisibleItinerary(state) + const visibleItineraryIndex = getVisibleItineraryIndex(state) const activeItinerary = getActiveItinerary(state.otp) const itins = getActiveItineraries(state.otp) - - const visibleIndex = visibleItinerary !== undefined && visibleItinerary !== null - ? visibleItinerary - : activeItinerary - return itins[visibleIndex] || activeItinerary + return itins[visibleItineraryIndex] || activeItinerary } /** @@ -371,22 +356,20 @@ function getItineraryToRender (state) { * using a selector to prevent unnecessary re-renderings of the transitive overlay. */ export const getTransitiveData = createSelector( - hasResponse, - hasAtLeastOneResponse, + activeSearchHasResponse, getOtpResponse, - queryIsItineraryRouting, + itineraryResponseExists, getItineraryToRender, (state, props) => props.getTransitiveRouteLabel, ( hasResponse, - hasAtLeastOneResponse, otpResponse, - queryIsItineraryRouting, + hasItineraryResponse, itineraryToRender, getTransitiveRouteLabel ) => { if (hasResponse) { - if (queryIsItineraryRouting && hasAtLeastOneResponse) { + if (hasItineraryResponse) { return itineraryToRender ? coreUtils.map.itineraryToTransitive( itineraryToRender, From 32d67571bf76c73fa94708a62c1402fe6f5e2892 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Mon, 7 Jun 2021 17:15:09 +0100 Subject: [PATCH 23/89] fix(batch-routing-panel.js): Fixed the code so the button shows. For some reason the button was showing on my initial commit but not when I pushed teh changes. I changed some code and cleared my cache. All seems good now. #371 --- lib/components/app/batch-routing-panel.js | 30 ++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/components/app/batch-routing-panel.js b/lib/components/app/batch-routing-panel.js index ee6376d6b..d7505ef79 100644 --- a/lib/components/app/batch-routing-panel.js +++ b/lib/components/app/batch-routing-panel.js @@ -9,7 +9,7 @@ import LocationField from '../form/connected-location-field' import NarrativeItineraries from '../narrative/narrative-itineraries' import { getActiveSearch, getShowUserSettings } from '../../util/state' import ViewerContainer from '../viewers/viewer-container' -import switchButton from '../form/switch-button' +import SwitchButton from '../form/switch-button' // Style for setting the top of the narrative itineraries based on the width of the window. // If the window width is less than 1200px (Bootstrap's "large" size), the @@ -36,19 +36,21 @@ class BatchRoutingPanel extends Component { const {mobile} = this.props const actionText = mobile ? 'tap' : 'click' return ( - - - -
- } /> + +
+ + +
+ } /> +
{/* FIXME: Add back user settings (home, work, etc.) once connected to From 61236f217c33cd0450f64b3210af75f1c3d71c59 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 8 Jun 2021 11:36:57 -0400 Subject: [PATCH 24/89] refactor(transitive-overlay): Remove componentShouldUpdate --- .../map/connected-transitive-overlay.js | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 8a2658fed..5d7c6da75 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -1,35 +1,12 @@ -import TransitiveOverlay from '@opentripplanner/transitive-overlay' -import React, { Component } from 'react' +import TransitiveCanvasOverlay from '@opentripplanner/transitive-overlay' import { connect } from 'react-redux' import { getTransitiveData } from '../../util/state' -/** - * Wrapper for TransitiveOverlay that avoids rerenders by checking transitive - * data computed from redux store. - */ -class TransitiveCanvasOverlay extends Component { - shouldComponentUpdate (nextProps) { - return nextProps.transitiveData !== this.props.transitiveData - } - - render () { - const { labeledModes, styles, transitiveData } = this.props - return ( - - ) - } -} - // connect to the redux store const mapStateToProps = (state, ownProps) => { const { labeledModes, styles } = state.otp.config.map.transitive || {} - return { labeledModes, styles, From da1262751ad1d8e9c18a39b35ddd4a2f1c13b3a3 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 9 Jun 2021 09:47:11 -0400 Subject: [PATCH 25/89] fix(calltaker): Do not require modules in config. Fix #382 --- lib/reducers/call-taker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 97709ec5e..4fa3849b7 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -5,7 +5,7 @@ import {constructNewCall} from '../util/call-taker' import {FETCH_STATUS} from '../util/constants' function createCallTakerReducer (config) { - const calltakerConfig = config.modules.find(m => m.id === 'call') + const calltakerConfig = config.modules?.find(m => m.id === 'call') const initialState = { activeCall: null, callHistory: { From b9e07a113c0ad15355997560179765d70f4c8f6d Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 10 Jun 2021 08:51:07 -0400 Subject: [PATCH 26/89] fix(SavedTripScreen): Initialize trip monitored days using itin transit leg svc date. --- __tests__/util/itinerary.js | 62 +++++++++++++++++-- .../user/monitored-trip/saved-trip-screen.js | 6 +- lib/util/itinerary.js | 27 ++++++++ lib/util/monitored-trip.js | 3 +- 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/__tests__/util/itinerary.js b/__tests__/util/itinerary.js index c2604a303..8bbc95538 100644 --- a/__tests__/util/itinerary.js +++ b/__tests__/util/itinerary.js @@ -1,6 +1,14 @@ /* globals describe, expect, it */ -import { itineraryCanBeMonitored } from '../../lib/util/itinerary' +import { + getTransitItineraryDefaultMonitoredDays, + itineraryCanBeMonitored +} from '../../lib/util/itinerary' +import { WEEKDAYS, WEEKEND_DAYS } from '../../lib/util/monitored-trip' + +const walkLeg = { + mode: 'WALK' +} describe('util > itinerary', () => { describe('itineraryCanBeMonitored', () => { @@ -20,9 +28,6 @@ describe('util > itinerary', () => { mode: 'MICROMOBILITY_RENT', rentedVehicle: true } - const walkLeg = { - mode: 'WALK' - } const rideHailLeg = { mode: 'CAR_HAIL', hailedCar: true @@ -80,4 +85,53 @@ describe('util > itinerary', () => { }) }) }) + describe('getTransitItineraryDefaultMonitoredDays', () => { + const transitLegWeekday = { + mode: 'BUS', + serviceDate: '20210609', // Wendesday + transitLeg: true + } + const transitLegSaturday = { + mode: 'BUS', + serviceDate: '20210612', // Saturday + transitLeg: true + } + const transitLegSunday = { + mode: 'BUS', + serviceDate: '20210613', // Sunday + transitLeg: true + } + + const testCases = [{ + expected: WEEKDAYS, + itinerary: { + legs: [walkLeg, transitLegWeekday] + }, + title: 'should be [\'monday\' thru \'friday\'] for an itinerary starting on a weekday.' + }, { + expected: WEEKEND_DAYS, + itinerary: { + legs: [walkLeg, transitLegSaturday] + }, + title: 'should be [\'saturday\', \'sunday\'] for an itinerary starting on a Saturday.' + }, { + expected: WEEKEND_DAYS, + itinerary: { + legs: [walkLeg, transitLegSunday] + }, + title: 'should be [\'saturday\', \'sunday\'] for an itinerary starting on a Sunday.' + }, { + expected: null, + itinerary: { + legs: [walkLeg] + }, + title: 'should be null for an itinerary without transit.' + }] + + testCases.forEach(({ expected, itinerary, title }) => { + it(title, () => { + expect(getTransitItineraryDefaultMonitoredDays(itinerary)).toBe(expected) + }) + }) + }) }) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 3b02e59d5..8b17d743d 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -14,7 +14,8 @@ import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' import { TRIPS_PATH } from '../../../util/constants' -import { ALL_DAYS, arrayToDayFields, WEEKDAYS } from '../../../util/monitored-trip' +import { getTransitItineraryDefaultMonitoredDays } from '../../../util/itinerary' +import { ALL_DAYS, arrayToDayFields } from '../../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../../util/state' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' import withLoggedInUserSupport from '../with-logged-in-user-support' @@ -60,8 +61,9 @@ class SavedTripScreen extends Component { */ _createMonitoredTrip = () => { const { itinerary, loggedInUser, queryParams } = this.props + const monitoredDays = getTransitItineraryDefaultMonitoredDays(itinerary) return { - ...arrayToDayFields(WEEKDAYS), + ...arrayToDayFields(monitoredDays), arrivalVarianceMinutesThreshold: 5, departureVarianceMinutesThreshold: 5, excludeFederalHolidays: true, diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index e31e70c67..b030afedd 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -1,6 +1,7 @@ import { latLngBounds } from 'leaflet' import moment from 'moment' import coreUtils from '@opentripplanner/core-utils' +import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' export function getLeafletItineraryBounds (itinerary) { return latLngBounds(coreUtils.itinerary.getItineraryBounds(itinerary)) @@ -47,3 +48,29 @@ export function itineraryCanBeMonitored (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) +} + +/** + * Returns the set of monitored days that will be initially shown to the user + * for the given transit itinerary (itinerary with transit leg). + * @param itinerary The itinerary from which the default monitored days are extracted. + * @returns ['monday' thru 'friday'] if itinerary happens on a weekday(*), + * ['saturday', 'sunday'] if itinerary happens on a saturday/sunday(*). + * (*) The first transit leg will be used to make the determination. + */ +export function getTransitItineraryDefaultMonitoredDays (itinerary) { + const firstTransitLeg = getFirstTransitLeg(itinerary) + // Assume a transit leg exists. + if (!firstTransitLeg) return null + + const dayOfWeek = moment(firstTransitLeg.serviceDate, 'YYYYMMDD').day() + return (dayOfWeek === 0 || dayOfWeek === 6) + ? WEEKEND_DAYS + : WEEKDAYS +} diff --git a/lib/util/monitored-trip.js b/lib/util/monitored-trip.js index 4ff62656f..dee229197 100644 --- a/lib/util/monitored-trip.js +++ b/lib/util/monitored-trip.js @@ -1,5 +1,6 @@ export const WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] -export const ALL_DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] +export const WEEKEND_DAYS = ['saturday', 'sunday'] +export const ALL_DAYS = [...WEEKDAYS, ...WEEKEND_DAYS] /** * Extracts the day of week fields of an object (e.g. a monitoredTrip) to an array. From 0e87eb28ec806efb6cf079318a1093e7b66524c5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 10 Jun 2021 09:05:48 -0400 Subject: [PATCH 27/89] docs(tests/itinerary): Fix comment typo. --- __tests__/util/itinerary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/util/itinerary.js b/__tests__/util/itinerary.js index 8bbc95538..44922640a 100644 --- a/__tests__/util/itinerary.js +++ b/__tests__/util/itinerary.js @@ -88,7 +88,7 @@ describe('util > itinerary', () => { describe('getTransitItineraryDefaultMonitoredDays', () => { const transitLegWeekday = { mode: 'BUS', - serviceDate: '20210609', // Wendesday + serviceDate: '20210609', // Wednesday transitLeg: true } const transitLegSaturday = { From 4eaa00046b5dbcda886968309c4452f49cda1237 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 10 Jun 2021 12:43:16 -0400 Subject: [PATCH 28/89] refactor(util/itinerary): Add monitored days calculation for non-transit itineraries. --- __tests__/util/itinerary.js | 27 ++++++++++++++----- .../user/monitored-trip/saved-trip-screen.js | 4 +-- lib/util/itinerary.js | 14 +++++----- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/__tests__/util/itinerary.js b/__tests__/util/itinerary.js index 44922640a..caac6ec17 100644 --- a/__tests__/util/itinerary.js +++ b/__tests__/util/itinerary.js @@ -1,7 +1,7 @@ /* globals describe, expect, it */ import { - getTransitItineraryDefaultMonitoredDays, + getItineraryDefaultMonitoredDays, itineraryCanBeMonitored } from '../../lib/util/itinerary' import { WEEKDAYS, WEEKEND_DAYS } from '../../lib/util/monitored-trip' @@ -85,7 +85,7 @@ describe('util > itinerary', () => { }) }) }) - describe('getTransitItineraryDefaultMonitoredDays', () => { + describe('getItineraryDefaultMonitoredDays', () => { const transitLegWeekday = { mode: 'BUS', serviceDate: '20210609', // Wednesday @@ -121,16 +121,31 @@ describe('util > itinerary', () => { }, title: 'should be [\'saturday\', \'sunday\'] for an itinerary starting on a Sunday.' }, { - expected: null, + expected: WEEKDAYS, + itinerary: { + legs: [walkLeg], + startTime: 1623341891000 // Thursday 2021-06-10 12:18 pm EDT + }, + title: 'should be [\'monday\' thru \'friday\'] for an itinerary without transit starting on a weekday (fallback case).' + }, { + expected: WEEKEND_DAYS, + itinerary: { + legs: [walkLeg], + startTime: 1623514691000 // Saturday 2021-06-12 12:18 pm EDT + }, + title: 'should be [\'saturday\', \'sunday\'] for an itinerary without transit starting on a Saturday (fallback case).' + }, { + expected: WEEKEND_DAYS, itinerary: { - legs: [walkLeg] + legs: [walkLeg], + startTime: 1623601091000 // Sunday 2021-06-13 12:18 pm EDT }, - title: 'should be null for an itinerary without transit.' + title: 'should be [\'saturday\', \'sunday\'] for an itinerary without transit starting on a Sunday (fallback case).' }] testCases.forEach(({ expected, itinerary, title }) => { it(title, () => { - expect(getTransitItineraryDefaultMonitoredDays(itinerary)).toBe(expected) + expect(getItineraryDefaultMonitoredDays(itinerary)).toBe(expected) }) }) }) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 8b17d743d..0a814d9e7 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -14,7 +14,7 @@ import TripBasicsPane from './trip-basics-pane' import TripNotificationsPane from './trip-notifications-pane' import TripSummaryPane from './trip-summary-pane' import { TRIPS_PATH } from '../../../util/constants' -import { getTransitItineraryDefaultMonitoredDays } from '../../../util/itinerary' +import { getItineraryDefaultMonitoredDays } from '../../../util/itinerary' import { ALL_DAYS, arrayToDayFields } from '../../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../../util/state' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' @@ -61,7 +61,7 @@ class SavedTripScreen extends Component { */ _createMonitoredTrip = () => { const { itinerary, loggedInUser, queryParams } = this.props - const monitoredDays = getTransitItineraryDefaultMonitoredDays(itinerary) + const monitoredDays = getItineraryDefaultMonitoredDays(itinerary) return { ...arrayToDayFields(monitoredDays), arrivalVarianceMinutesThreshold: 5, diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index b030afedd..a8eec1c9c 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -58,18 +58,20 @@ function getFirstTransitLeg (itinerary) { /** * Returns the set of monitored days that will be initially shown to the user - * for the given transit itinerary (itinerary with transit leg). + * for the given itinerary. * @param itinerary The itinerary from which the default monitored days are extracted. * @returns ['monday' thru 'friday'] if itinerary happens on a weekday(*), * ['saturday', 'sunday'] if itinerary happens on a saturday/sunday(*). - * (*) The first transit leg will be used to make the determination. + * (*) For transit itineraries, the first transit leg is used to make + * the determination. Otherwise, the itinerary startTime is used. */ -export function getTransitItineraryDefaultMonitoredDays (itinerary) { +export function getItineraryDefaultMonitoredDays (itinerary) { const firstTransitLeg = getFirstTransitLeg(itinerary) - // Assume a transit leg exists. - if (!firstTransitLeg) return null + const startMoment = firstTransitLeg + ? moment(firstTransitLeg.serviceDate, 'YYYYMMDD') + : moment(itinerary.startTime) + const dayOfWeek = startMoment.day() - const dayOfWeek = moment(firstTransitLeg.serviceDate, 'YYYYMMDD').day() return (dayOfWeek === 0 || dayOfWeek === 6) ? WEEKEND_DAYS : WEEKDAYS From 1616cfa01a08d122c67dff4708e0d28c011e3804 Mon Sep 17 00:00:00 2001 From: Rob Gregg Date: Fri, 11 Jun 2021 14:25:57 +0100 Subject: [PATCH 29/89] refactor(batch-routing-panel.js): Added the class back for the ViewerContainer --- lib/components/app/batch-routing-panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/app/batch-routing-panel.js b/lib/components/app/batch-routing-panel.js index d7505ef79..5d7125d67 100644 --- a/lib/components/app/batch-routing-panel.js +++ b/lib/components/app/batch-routing-panel.js @@ -36,7 +36,7 @@ class BatchRoutingPanel extends Component { const {mobile} = this.props const actionText = mobile ? 'tap' : 'click' return ( - +
Date: Mon, 14 Jun 2021 10:46:44 -0400 Subject: [PATCH 30/89] refactor(mailables): update config, add menu item in navbar --- lib/components/admin/mailables-window.js | 44 +++++++++++++----------- lib/components/admin/styled.js | 7 ++++ lib/components/app/app-menu.js | 17 +++++++-- lib/reducers/call-taker.js | 2 +- lib/util/mailables.js | 37 ++++++++++---------- 5 files changed, 64 insertions(+), 43 deletions(-) diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index 7766c5aa1..1d5f7af99 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -1,14 +1,15 @@ import React, { Component } from 'react' +import { Badge } from 'react-bootstrap' import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' import DraggableWindow from './draggable-window' import Icon from '../narrative/icon' -import {WindowHeader} from './styled' +import {MailablesList, WindowHeader} from './styled' import {createLetter, MAILABLE_FIELDS} from '../../util/mailables' /** - * A window enabled through the Call Taker module that allows Call Taker users + * A window enabled through the Mailables module that allows Call Taker users * to generate a PDF with an invoice of items to be mailed to transit customers. */ class MailablesWindow extends Component { @@ -20,13 +21,11 @@ class MailablesWindow extends Component { } _addMailable = (mailable) => { - const mailables = [...this.state.mailables] - if (!mailables.find(m => m.name === mailable.name)) { + if (!this.state.mailables.find(m => m.name === mailable.name)) { + const mailables = [...this.state.mailables] mailables.push({...mailable, quantity: 1}) - } else { - // FIXME: Increase quanity? + this.setState({mailables}) } - this.setState({mailables}) } _removeMailable = (mailable) => { @@ -42,18 +41,20 @@ class MailablesWindow extends Component { this.setState({mailables}) } - _onClickCreateLetter = () => createLetter(this.state, this.props.callConfig.options) + _onClickCreateLetter = () => createLetter(this.state, this.props.mailablesConfig) _updateField = (evt) => { this.setState({[evt.target.id]: evt.target.value}) } render () { - const {callConfig, callTaker, toggleMailables} = this.props + const {mailablesConfig, callTaker, toggleMailables} = this.props const {mailables: selectedMailables} = this.state - const {mailables} = callConfig.options + const {items} = mailablesConfig if (!callTaker.mailables.visible) return null - const selectableMailables = mailables.filter(m => !selectedMailables.find(mailable => mailable.name === m.name)) + const selectableMailables = items.filter(m => + !selectedMailables.find(mailable => mailable.name === m.name) + ) return (
{MAILABLE_FIELDS.map(f => ( @@ -82,25 +83,26 @@ class MailablesWindow extends Component { key={f.fieldName} id={f.fieldName} onChange={this._updateField} - placeholder={f.fieldName} + placeholder={f.placeholder} + style={{margin: '5px'}} value={this.state[f.fieldName]} /> ))}

All Mailables

-
+ {selectableMailables.map((mailable, i) => ( ))} -
+
-

Selected Mailables

-
+

Selected Mailables {selectedMailables.length}

+ {selectedMailables.length > 0 ? selectedMailables.map((mailable, i) => ( No mailables selected.
} -
+
@@ -178,10 +180,10 @@ class MailableOption extends Component { } const mapStateToProps = (state, ownProps) => { - const callConfig = state.otp.config.modules.find(m => m.id === 'call') + const mailablesConfig = state.otp.config.modules?.find(m => m.id === 'mailables') return { - callConfig, - callTaker: state.callTaker + callTaker: state.callTaker, + mailablesConfig } } diff --git a/lib/components/admin/styled.js b/lib/components/admin/styled.js index 888c0cbb6..29d34cec3 100644 --- a/lib/components/admin/styled.js +++ b/lib/components/admin/styled.js @@ -154,3 +154,10 @@ export const WindowHeader = styled.h3` margin-bottom: 10px; margin-top: 10px; ` + +// Mailables components + +export const MailablesList = styled.div` + max-height: 120px; + overflow-y: scroll; +` diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index 703c0752d..fae07c9f1 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -7,6 +7,7 @@ import { withRouter } from 'react-router' import Icon from '../narrative/icon' +import * as callTakerActions from '../../actions/call-taker' import { MainPanelContent, setMainPanelContent } from '../../actions/ui' // TODO: make menu items configurable via props/config @@ -39,8 +40,10 @@ class AppMenu extends Component { window.location.href = startOverUrl } + _toggleMailables = () => this.props.toggleMailables() + render () { - const { languageConfig } = this.props + const { languageConfig, mailablesEnabled } = this.props return (
@@ -53,6 +56,11 @@ class AppMenu extends Component { {languageConfig.routeViewer || 'Route Viewer'} + {mailablesEnabled && + + Mailables + + } Start Over @@ -65,13 +73,16 @@ class AppMenu extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { + const {language, modules} = state.otp.config return { - languageConfig: state.otp.config.language + languageConfig: language, + mailablesEnabled: Boolean(modules?.find(m => m.id === 'mailables')) } } const mapDispatchToProps = { - setMainPanelContent + setMainPanelContent, + toggleMailables: callTakerActions.toggleMailables } export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AppMenu)) diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 6c0e1cad6..2694017dc 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -28,7 +28,7 @@ function createCallTakerReducer (config) { visible: false }, mailables: { - visible: true + visible: false }, session: null } diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 5094aaefd..59ee14394 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -4,16 +4,16 @@ import moment from 'moment' import PDFDocument from './PDFDocumentWithTables' export const MAILABLE_FIELDS = [ - {fieldName: 'firstname'}, - {fieldName: 'lastname'}, - {fieldName: 'address1'}, - {fieldName: 'address2'}, - {fieldName: 'city'}, - {fieldName: 'state'}, - {fieldName: 'zip'} + {fieldName: 'firstname', placeholder: 'First name'}, + {fieldName: 'lastname', placeholder: 'Last name'}, + {fieldName: 'address1', placeholder: 'Address 1'}, + {fieldName: 'address2', placeholder: 'Address 2'}, + {fieldName: 'city', placeholder: 'City'}, + {fieldName: 'state', placeholder: 'State'}, + {fieldName: 'zip', placeholder: 'Zip'} ] -export function createLetter (formData, otpConfig) { +export function createLetter (formData, mailablesConfig) { // 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() @@ -27,9 +27,10 @@ export function createLetter (formData, otpConfig) { ctx.drawImage(img, 0, 0) const data = canvas.toDataURL('image/png') - writePDF(formData, {data, height: img.height, width: img.width}, otpConfig) + const imageData = {data, height: img.height, width: img.width} + writePDF(formData, imageData, mailablesConfig) } - img.src = otpConfig.mailables_header_graphic + img.src = mailablesConfig.header_graphic } function writePDF (formData, imageData, otpConfig) { @@ -44,8 +45,8 @@ function writePDF (formData, imageData, otpConfig) { zip = '' } = formData const { - mailables_horizontal_margin: horizontalMargin, - mailables_vertical_margin: verticalMargin + horizontal_margin: horizontalMargin, + vertical_margin: verticalMargin } = otpConfig const margins = { top: verticalMargin, @@ -76,7 +77,7 @@ function writePDF (formData, imageData, otpConfig) { // introduction block doc.moveDown() .moveDown() - .text(otpConfig.mailables_introduction) + .text(otpConfig.introduction) // table header doc.font('Helvetica-Bold') @@ -106,7 +107,7 @@ function writePDF (formData, imageData, otpConfig) { doc.moveDown() .moveDown() .font('Helvetica') - .text(otpConfig.mailables_conclusion) + .text(otpConfig.conclusion) doc.end() stream.on('finish', () => { @@ -117,10 +118,10 @@ function writePDF (formData, imageData, otpConfig) { function preparePage (doc, imageData, otpConfig) { const { - mailables_footer: footer, - mailables_header_graphic: headerGraphic, - mailables_header_graphic_height: headerGraphicHeight, - mailables_header_graphic_width: headerGraphicWidth + footer: footer, + header_graphic: headerGraphic, + header_graphic_height: headerGraphicHeight, + header_graphic_width: headerGraphicWidth } = otpConfig // Store true bottom of page while bottom is temporarily moved to 0. const bottom = doc.page.margins.bottom From d66db6d84e238eb6d16f1c02944ee3191621e590 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 14 Jun 2021 10:55:14 -0400 Subject: [PATCH 31/89] refactor(mailables): tweak window height --- lib/components/admin/mailables-window.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index 1d5f7af99..73674cf03 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -75,7 +75,7 @@ class MailablesWindow extends Component { } onClickClose={toggleMailables} scroll={false} - style={{height: '400px', width: '600px'}} + style={{height: '340px', width: '600px'}} >
{MAILABLE_FIELDS.map(f => ( From a0e0384c6689c5f3dd2774e50dc7eba255a02d8c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 14 Jun 2021 13:37:21 -0400 Subject: [PATCH 32/89] refactor(mailables): fix lint --- lib/util/mailables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 59ee14394..d4c30221d 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -118,7 +118,7 @@ function writePDF (formData, imageData, otpConfig) { function preparePage (doc, imageData, otpConfig) { const { - footer: footer, + footer, header_graphic: headerGraphic, header_graphic_height: headerGraphicHeight, header_graphic_width: headerGraphicWidth From 5c4d8f693ad6644619c6352b0461eb597a8973c5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 15 Jun 2021 07:28:13 -0400 Subject: [PATCH 33/89] Update lib/util/state.js per PR suggestion. Co-authored-by: Evan Siroky --- lib/util/state.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index bbde22fa6..846e2da3f 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -336,8 +336,7 @@ function itineraryResponseExists (state) { * @param {*} state The entire redux state from which to retrieve the itinerary. */ export function getVisibleItineraryIndex (state) { - const activeSearch = getActiveSearch(state.otp) - return activeSearch && activeSearch.visibleItinerary + return getActiveSearch(state.otp)?.visibleItinerary } /** From fa122b9c3aed283fccef701163e6b41e9be9c723 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Wed, 16 Jun 2021 16:45:16 -0700 Subject: [PATCH 34/89] refactor: remove otpState middleman --- __tests__/util/state.js | 14 ++-- lib/actions/api.js | 68 ++++++++++-------- lib/actions/form.js | 19 ++--- lib/actions/map.js | 4 +- lib/actions/ui.js | 11 +-- lib/components/app/batch-routing-panel.js | 4 +- lib/components/app/call-taker-panel.js | 4 +- lib/components/app/default-main-panel.js | 4 +- lib/components/app/print-layout.js | 2 +- lib/components/app/responsive-webapp.js | 7 +- lib/components/form/batch-preferences.js | 2 +- lib/components/form/batch-settings.js | 9 +-- lib/components/form/connect-location-field.js | 4 +- .../form/connected-settings-selector-panel.js | 2 +- lib/components/form/error-message.js | 4 +- lib/components/map/bounds-updating-overlay.js | 4 +- .../map/connected-endpoints-overlay.js | 4 +- .../map/connected-transitive-overlay.js | 6 +- lib/components/map/stylized-map.js | 4 +- lib/components/mobile/batch-results-screen.js | 6 +- lib/components/mobile/main.js | 10 +-- lib/components/mobile/results-header.js | 6 +- lib/components/mobile/results-screen.js | 8 +-- .../narrative/itinerary-carousel.js | 9 +-- .../narrative/narrative-itineraries.js | 13 ++-- .../narrative/narrative-routing-results.js | 12 ++-- lib/components/narrative/save-trip-button.js | 2 +- .../narrative/tabbed-itineraries.js | 4 +- .../user/monitored-trip/saved-trip-screen.js | 13 ++-- lib/components/viewers/stop-viewer.js | 9 +-- lib/util/state.js | 72 +++++++++---------- 31 files changed, 182 insertions(+), 158 deletions(-) diff --git a/__tests__/util/state.js b/__tests__/util/state.js index 5385038f9..ae982c5d8 100644 --- a/__tests__/util/state.js +++ b/__tests__/util/state.js @@ -15,17 +15,21 @@ describe('util > state', () => { const testCases = [{ expected: false, input: { - currentQuery: { - from: fakeFromLocation + otp: { + currentQuery: { + from: fakeFromLocation + } } }, title: 'should not be valid with only from location' }, { expected: true, input: { - currentQuery: { - from: fakeFromLocation, - to: fakeToLocation + otp: { + currentQuery: { + from: fakeFromLocation, + to: fakeToLocation + } } }, title: 'should be valid with from and to locations' diff --git a/lib/actions/api.js b/lib/actions/api.js index 0bf3a442c..f712a786b 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -8,10 +8,11 @@ import queryParams from '@opentripplanner/core-utils/lib/query-params' import { createAction } from 'redux-actions' import qs from 'qs' -import { rememberPlace } from './map' import { getStopViewerConfig, queryIsValid } from '../util/state' import { getSecureFetchOptions } from '../util/middleware' +import { rememberPlace } from './map' + if (typeof (fetch) === 'undefined') require('isomorphic-fetch') const { hasCar } = coreUtils.itinerary @@ -39,9 +40,9 @@ function formatRecentPlace (place) { } } -function formatRecentSearch (url, otpState) { +function formatRecentSearch (url, state) { return { - query: getTripOptionsFromQuery(otpState.currentQuery, true), + query: getTripOptionsFromQuery(state.otp.currentQuery, true), url, id: randId(), timestamp: new Date().getTime() @@ -57,8 +58,8 @@ function isStoredPlace (place) { * session (i.e. searches lookup is empty/null) AND an activeItinerary ID * is specified in URL parameters, use that ID. Otherwise, use null/0. */ -function getActiveItinerary (otpState) { - const {currentQuery, searches} = otpState +function getActiveItinerary (state) { + const {currentQuery, searches} = state.otp let activeItinerary = currentQuery.routingType === 'ITINERARY' ? 0 : null // We cannot use window.history.state here to check for the active // itinerary param because it is unreliable in some states (e.g., @@ -84,43 +85,47 @@ export function routingQuery (searchId = null) { return async function (dispatch, getState) { // FIXME: batchId is searchId for now. const state = getState() - const otpState = state.otp const isNewSearch = !searchId if (isNewSearch) searchId = randId() // Don't permit a routing query if the query is invalid - if (!queryIsValid(otpState)) { - console.warn('Query is invalid. Aborting routing query', otpState.currentQuery) + if (!queryIsValid(state)) { + console.warn( + 'Query is invalid. Aborting routing query', + state.otp.currentQuery + ) return } - const activeItinerary = getActiveItinerary(otpState) - const routingType = otpState.currentQuery.routingType + const activeItinerary = getActiveItinerary(state) + const routingType = state.otp.currentQuery.routingType // For multiple mode combinations, gather injected params from config/query. // Otherwise, inject nothing (rely on what's in current query) and perform // one iteration. - const iterations = otpState.currentQuery.combinations - ? otpState.currentQuery.combinations.map(({mode, params}) => ({mode, ...params})) + const iterations = state.otp.currentQuery.combinations + ? state.otp.currentQuery.combinations.map( + ({mode, params}) => ({mode, ...params}) + ) : [{}] dispatch(routingRequest({ activeItinerary, routingType, searchId, pending: iterations.length })) iterations.forEach((injectedParams, i) => { const requestId = randId() // fetch a realtime route - const query = constructRoutingQuery(otpState, false, injectedParams) + const query = constructRoutingQuery(state, false, injectedParams) fetch(query, getOtpFetchOptions(state)) .then(getJsonAndCheckResponse) .then(json => { dispatch(routingResponse({ response: json, requestId, searchId })) // If tracking is enabled, store locations and search after successful // search is completed. - if (otpState.user.trackRecent) { - const { from, to } = otpState.currentQuery + if (state.otp.user.trackRecent) { + const { from, to } = state.otp.currentQuery if (!isStoredPlace(from)) { dispatch(rememberPlace({ type: 'recent', location: formatRecentPlace(from) })) } if (!isStoredPlace(to)) { dispatch(rememberPlace({ type: 'recent', location: formatRecentPlace(to) })) } - dispatch(rememberSearch(formatRecentSearch(query, otpState))) + dispatch(rememberSearch(formatRecentSearch(query, state))) } }) .catch(error => { @@ -135,7 +140,7 @@ export function routingQuery (searchId = null) { // that the path absolutely accurately reflects the app state. const params = getUrlParams() if (isNewSearch || params.ui_activeSearch !== searchId) { - dispatch(updateOtpUrlParams(otpState, searchId)) + dispatch(updateOtpUrlParams(state, searchId)) } // Also fetch a non-realtime route. @@ -159,7 +164,10 @@ export function routingQuery (searchId = null) { user.loggedInUser && user.loggedInUser.storeTripHistory - fetch(constructRoutingQuery(otpState, true), getOtpFetchOptions(state, storeTripHistory)) + fetch( + constructRoutingQuery(state, true), + getOtpFetchOptions(state, storeTripHistory) + ) .then(getJsonAndCheckResponse) .then(json => { // FIXME: This is only performed when ignoring realtimeupdates currently, just @@ -219,8 +227,8 @@ function getOtpFetchOptions (state, includeToken = false) { } } -function constructRoutingQuery (otpState, ignoreRealtimeUpdates, injectedParams = {}) { - const { config, currentQuery } = otpState +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) @@ -549,6 +557,7 @@ const findStopTimesForStopError = createAction('FIND_STOP_TIMES_FOR_STOP_ERROR') */ export function findStopTimesForStop (params) { return function (dispatch, getState) { + const state = getState() dispatch(fetchingStopTimesForStop(params)) const { date, stopId, ...otherParams } = params let datePath = '' @@ -559,7 +568,7 @@ export function findStopTimesForStop (params) { // If other params not provided, fall back on defaults from stop viewer config. // Note: query params don't apply with the OTP /date endpoint. - const queryParams = { ...getStopViewerConfig(getState().otp), ...otherParams } + const queryParams = { ...getStopViewerConfig(state), ...otherParams } // If no start time is provided and no date is provided in params, // pass in the current time. Note: this is not @@ -936,15 +945,18 @@ window.setInterval(() => { function createQueryAction (endpoint, responseAction, errorAction, options = {}) { return async function (dispatch, getState) { - const otpState = getState().otp + const state = getState() + const { config } = state.otp let url - if (options.serviceId && otpState.config.alternateTransitIndex && - otpState.config.alternateTransitIndex.services.includes(options.serviceId) + if ( + options.serviceId && + config.alternateTransitIndex && + config.alternateTransitIndex.services.includes(options.serviceId) ) { console.log('Using alt service for ' + options.serviceId) - url = otpState.config.alternateTransitIndex.apiRoot + endpoint + url = config.alternateTransitIndex.apiRoot + endpoint } else { - const api = otpState.config.api + const api = config.api url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}` } @@ -1023,8 +1035,8 @@ export function setUrlSearch (params, replaceCurrent = false) { * 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. */ -function updateOtpUrlParams (otpState, searchId) { - const {config, currentQuery} = otpState +function updateOtpUrlParams (state, searchId) { + const {config, currentQuery} = state.otp // Get updated OTP params from current query. const otpParams = getRoutingParams(currentQuery, config, true) return function (dispatch, getState) { diff --git a/lib/actions/form.js b/lib/actions/form.js index 077e85ae3..1192b2218 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -5,6 +5,8 @@ import moment from 'moment' import qs from 'qs' import { createAction } from 'redux-actions' +import { queryIsValid } from '../util/state' + import { routingQuery } from './api' import { setLocation } from './map' import { @@ -13,7 +15,6 @@ import { setMainPanelContent, setMobileScreen } from './ui' -import { queryIsValid } from '../util/state' const { getDefaultQuery, @@ -35,15 +36,15 @@ export const storeDefaultSettings = createAction('STORE_DEFAULT_SETTINGS') */ export function resetForm (full = false) { return function (dispatch, getState) { - const otpState = getState().otp - const { transitModes } = otpState.config.modes - if (otpState.user.defaults) { - dispatch(settingQueryParam(otpState.user.defaults)) + const state = getState() + const { transitModes } = state.otp.config.modes + if (state.otp.user.defaults) { + dispatch(settingQueryParam(state.otp.user.defaults)) } else { // Get user overrides and apply to default query const userOverrides = coreUtils.storage.getItem('defaultQuery', {}) const defaultQuery = Object.assign( - getDefaultQuery(otpState.config), + getDefaultQuery(state.otp.config), userOverrides ) // Filter out non-options (i.e., date, places). @@ -113,8 +114,8 @@ let lastDebouncePlanTimeMs */ export function formChanged (oldQuery, newQuery) { return function (dispatch, getState) { - const otpState = getState().otp - const { config, currentQuery, ui } = otpState + const state = getState() + const { config, currentQuery, ui } = state.otp const { autoPlan, debouncePlanTimeMs } = config const isMobile = coreUtils.ui.isMobile() const { @@ -146,7 +147,7 @@ export function formChanged (oldQuery, newQuery) { dispatch(setMobileScreen(MobileScreens.SEARCH_FORM)) } } - } else if (queryIsValid(otpState)) { + } else if (queryIsValid(state)) { // If replanning trip and query is valid, // check if debouncing function needs to be (re)created. if (!debouncedPlanTrip || lastDebouncePlanTimeMs !== debouncePlanTimeMs) { diff --git a/lib/actions/map.js b/lib/actions/map.js index 26cbcbcef..c139b9da6 100644 --- a/lib/actions/map.js +++ b/lib/actions/map.js @@ -51,11 +51,11 @@ export function onLocationSelected ({ locationType, location, resultType }) { export function setLocation (payload) { return function (dispatch, getState) { - const otpState = getState().otp + const state = getState() // reverse geocode point location if requested if (payload.reverseGeocode) { - getGeocoder(otpState.config.geocoder) + getGeocoder(state.otp.config.geocoder) .reverse({ point: payload.location }) .then((location) => { dispatch(settingLocation({ diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 955515798..bfcd22f90 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -3,6 +3,8 @@ import coreUtils from '@opentripplanner/core-utils' import { createAction } from 'redux-actions' import { matchPath } from 'react-router' +import { getUiUrlParams } from '../util/state' + import { findRoute, setUrlSearch } from './api' import { setMapCenter, setMapZoom, setRouterId } from './config' import { @@ -12,7 +14,6 @@ import { } from './form' import { clearLocation } from './map' import { setActiveItinerary } from './narrative' -import { getUiUrlParams } from '../util/state' /** * Wrapper function for history#push (or, if specified, replace, etc.) @@ -111,15 +112,15 @@ function idToParams (id, delimiter = ',') { */ export function handleBackButtonPress (e) { return function (dispatch, getState) { - const otpState = getState().otp - const { activeSearchId } = otpState - const uiUrlParams = getUiUrlParams(otpState) + const state = getState() + const { activeSearchId } = state.otp + const uiUrlParams = getUiUrlParams(state) // Get new search ID from URL after back button pressed. // console.log('back button pressed', e) const urlParams = coreUtils.query.getUrlParams() const previousSearchId = urlParams.ui_activeSearch const previousItinIndex = +urlParams.ui_activeItinerary || 0 - const previousSearch = otpState.searches[previousSearchId] + const previousSearch = state.otp.searches[previousSearchId] if (previousSearch) { // If back button pressed and active search has changed, set search to // previous search ID. diff --git a/lib/components/app/batch-routing-panel.js b/lib/components/app/batch-routing-panel.js index 0cc9a36a8..c5b5798c5 100644 --- a/lib/components/app/batch-routing-panel.js +++ b/lib/components/app/batch-routing-panel.js @@ -71,9 +71,9 @@ class BatchRoutingPanel extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const showUserSettings = getShowUserSettings(state.otp) + const showUserSettings = getShowUserSettings(state) return { - activeSearch: getActiveSearch(state.otp), + activeSearch: getActiveSearch(state), config: state.otp.config, currentQuery: state.otp.currentQuery, expandAdvanced: state.otp.user.expandAdvanced, diff --git a/lib/components/app/call-taker-panel.js b/lib/components/app/call-taker-panel.js index b731c07da..f096d27b2 100644 --- a/lib/components/app/call-taker-panel.js +++ b/lib/components/app/call-taker-panel.js @@ -275,9 +275,9 @@ class CallTakerPanel extends Component { const mapStateToProps = (state, ownProps) => { const {activeId, requests} = state.callTaker.fieldTrip const request = requests.data.find(req => req.id === activeId) - const showUserSettings = getShowUserSettings(state.otp) + const showUserSettings = getShowUserSettings(state) return { - activeSearch: getActiveSearch(state.otp), + activeSearch: getActiveSearch(state), currentQuery: state.otp.currentQuery, expandAdvanced: state.otp.user.expandAdvanced, groupSize: state.callTaker.fieldTrip.groupSize, diff --git a/lib/components/app/default-main-panel.js b/lib/components/app/default-main-panel.js index 2f708b43a..a98be3451 100644 --- a/lib/components/app/default-main-panel.js +++ b/lib/components/app/default-main-panel.js @@ -63,9 +63,9 @@ class DefaultMainPanel extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const showUserSettings = getShowUserSettings(state.otp) + const showUserSettings = getShowUserSettings(state) return { - activeSearch: getActiveSearch(state.otp), + activeSearch: getActiveSearch(state), currentQuery: state.otp.currentQuery, mainPanelContent: state.otp.ui.mainPanelContent, showUserSettings diff --git a/lib/components/app/print-layout.js b/lib/components/app/print-layout.js index a67760e13..da1a292f5 100644 --- a/lib/components/app/print-layout.js +++ b/lib/components/app/print-layout.js @@ -102,7 +102,7 @@ class PrintLayout extends Component { const mapStateToProps = (state, ownProps) => { return { config: state.otp.config, - itinerary: getActiveItinerary(state.otp) + itinerary: getActiveItinerary(state) } } diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 8dfd7f934..80d5f698e 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -18,11 +18,9 @@ import * as locationActions from '../../actions/location' import * as mapActions from '../../actions/map' import * as uiActions from '../../actions/ui' import { frame } from '../app/app-frame' -import DesktopNav from './desktop-nav' import { RedirectWithQuery } from '../form/connected-links' import Map from '../map/map' import MobileMain from '../mobile/main' -import PrintLayout from './print-layout' import { getAuth0Config } from '../../util/auth' import { ACCOUNT_PATH, @@ -48,6 +46,9 @@ import SavedTripScreen from '../user/monitored-trip/saved-trip-screen' import UserAccountScreen from '../user/user-account-screen' import withLoggedInUserSupport from '../user/with-logged-in-user-support' +import PrintLayout from './print-layout' +import DesktopNav from './desktop-nav' + const { isMobile } = coreUtils.ui class ResponsiveWebapp extends Component { @@ -228,7 +229,7 @@ class ResponsiveWebapp extends Component { const mapStateToProps = (state, ownProps) => { const title = getTitle(state) return { - activeItinerary: getActiveItinerary(state.otp), + activeItinerary: getActiveItinerary(state), activeSearchId: state.otp.activeSearchId, currentPosition: state.otp.location.currentPosition, initZoomOnLocate: state.otp.config.map && state.otp.config.map.initZoomOnLocate, diff --git a/lib/components/form/batch-preferences.js b/lib/components/form/batch-preferences.js index 29718eac2..fe9bcb470 100644 --- a/lib/components/form/batch-preferences.js +++ b/lib/components/form/batch-preferences.js @@ -100,7 +100,7 @@ const mapStateToProps = (state, ownProps) => { return { query: currentQuery, config, - showUserSettings: getShowUserSettings(state.otp) + showUserSettings: getShowUserSettings(state) } } diff --git a/lib/components/form/batch-settings.js b/lib/components/form/batch-settings.js index 88deaadac..61d124b44 100644 --- a/lib/components/form/batch-settings.js +++ b/lib/components/form/batch-settings.js @@ -5,10 +5,12 @@ import styled from 'styled-components' import * as apiActions from '../../actions/api' import * as formActions from '../../actions/form' +import Icon from '../narrative/icon' +import { hasValidLocation, getActiveSearch, getShowUserSettings } from '../../util/state' + import BatchPreferences from './batch-preferences' import DateTimeModal from './date-time-modal' import ModeButtons, {MODE_OPTIONS, StyledModeButton} from './mode-buttons' -import Icon from '../narrative/icon' import { BatchPreferencesContainer, DateTimeModalContainer, @@ -18,7 +20,6 @@ import { StyledDateTimePreview } from './batch-styled' import { Dot } from './styled' -import { hasValidLocation, getActiveSearch, getShowUserSettings } from '../../util/state' /** * Simple utility to check whether a list of mode strings contains the provided @@ -182,9 +183,9 @@ class BatchSettings extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const showUserSettings = getShowUserSettings(state.otp) + const showUserSettings = getShowUserSettings(state) return { - activeSearch: getActiveSearch(state.otp), + activeSearch: getActiveSearch(state), config: state.otp.config, currentQuery: state.otp.currentQuery, expandAdvanced: state.otp.user.expandAdvanced, diff --git a/lib/components/form/connect-location-field.js b/lib/components/form/connect-location-field.js index b22262f21..ca2c562d1 100644 --- a/lib/components/form/connect-location-field.js +++ b/lib/components/form/connect-location-field.js @@ -20,7 +20,7 @@ export default function connectLocationField (StyledLocationField, options = {}) const mapStateToProps = (state, ownProps) => { const { config, currentQuery, location, transitIndex, user } = state.otp const { currentPosition, nearbyStops, sessionSearches } = location - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const query = activeSearch ? activeSearch.query : currentQuery const stateToProps = { @@ -28,7 +28,7 @@ export default function connectLocationField (StyledLocationField, options = {}) geocoderConfig: config.geocoder, nearbyStops, sessionSearches, - showUserSettings: getShowUserSettings(state.otp), + showUserSettings: getShowUserSettings(state), stopsIndex: transitIndex.stops, userLocationsAndRecentPlaces: [...user.locations, ...user.recentPlaces] } diff --git a/lib/components/form/connected-settings-selector-panel.js b/lib/components/form/connected-settings-selector-panel.js index 7ca2aa5b9..f918f1e69 100644 --- a/lib/components/form/connected-settings-selector-panel.js +++ b/lib/components/form/connected-settings-selector-panel.js @@ -47,7 +47,7 @@ const mapStateToProps = (state, ownProps) => { return { query: currentQuery, config, - showUserSettings: getShowUserSettings(state.otp) + showUserSettings: getShowUserSettings(state) } } diff --git a/lib/components/form/error-message.js b/lib/components/form/error-message.js index 96a1e6df9..f00285626 100644 --- a/lib/components/form/error-message.js +++ b/lib/components/form/error-message.js @@ -1,7 +1,7 @@ import React from 'react' import { connect } from 'react-redux' -import TripTools from '../narrative/trip-tools' +import TripTools from '../narrative/trip-tools' import { getActiveError, getErrorMessage } from '../../util/state' const ErrorMessage = ({ message }) => { @@ -23,7 +23,7 @@ const ErrorMessage = ({ message }) => { const mapStateToProps = (state, ownProps) => { return { message: getErrorMessage( - getActiveError(state.otp), + getActiveError(state), state.otp.config.errorMessages ) } diff --git a/lib/components/map/bounds-updating-overlay.js b/lib/components/map/bounds-updating-overlay.js index e4ce308e6..1a11659c6 100644 --- a/lib/components/map/bounds-updating-overlay.js +++ b/lib/components/map/bounds-updating-overlay.js @@ -169,13 +169,13 @@ class BoundsUpdatingOverlay extends MapLayer { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const urlParams = coreUtils.query.getUrlParams() return { activeLeg: activeSearch && activeSearch.activeLeg, activeStep: activeSearch && activeSearch.activeStep, - itinerary: getActiveItinerary(state.otp), + itinerary: getActiveItinerary(state), itineraryView: urlParams.ui_itineraryView, popupLocation: state.otp.ui.mapPopupLocation, query: state.otp.currentQuery diff --git a/lib/components/map/connected-endpoints-overlay.js b/lib/components/map/connected-endpoints-overlay.js index 0e85d3d80..73016b101 100644 --- a/lib/components/map/connected-endpoints-overlay.js +++ b/lib/components/map/connected-endpoints-overlay.js @@ -14,9 +14,9 @@ import { getActiveSearch, getShowUserSettings } from '../../util/state' const mapStateToProps = (state, ownProps) => { // Use query from active search (if a search has been made) or default to // current query is no search is available. - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const query = activeSearch ? activeSearch.query : state.otp.currentQuery - const showUserSettings = getShowUserSettings(state.otp) + const showUserSettings = getShowUserSettings(state) const { from, to } = query // Intermediate places doesn't trigger a re-plan, so for now default to // current query. FIXME: Determine with TriMet if this is desired behavior. diff --git a/lib/components/map/connected-transitive-overlay.js b/lib/components/map/connected-transitive-overlay.js index 9b2eed104..655e4d146 100644 --- a/lib/components/map/connected-transitive-overlay.js +++ b/lib/components/map/connected-transitive-overlay.js @@ -7,7 +7,7 @@ import { getActiveSearch, getActiveItinerary, getActiveItineraries } from '../.. // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) let transitiveData = null if ( activeSearch && @@ -16,12 +16,12 @@ const mapStateToProps = (state, ownProps) => { activeSearch.response.length > 0 ) { // FIXME: This may need some simplification. - const itins = getActiveItineraries(state.otp) + const itins = getActiveItineraries(state) const visibleIndex = activeSearch.visibleItinerary !== undefined && activeSearch.visibleItinerary !== null ? activeSearch.visibleItinerary : activeSearch.activeItinerary // TODO: prevent itineraryToTransitive() from being called more than needed - const visibleItinerary = itins[visibleIndex] ? itins[visibleIndex] : getActiveItinerary(state.otp) + const visibleItinerary = itins[visibleIndex] ? itins[visibleIndex] : getActiveItinerary(state) if (visibleItinerary) transitiveData = coreUtils.map.itineraryToTransitive(visibleItinerary) } else if ( activeSearch && diff --git a/lib/components/map/stylized-map.js b/lib/components/map/stylized-map.js index 48a8ee794..1d32f33f1 100644 --- a/lib/components/map/stylized-map.js +++ b/lib/components/map/stylized-map.js @@ -107,7 +107,7 @@ class StylizedMap extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) let transitiveData = null if ( activeSearch && @@ -115,7 +115,7 @@ const mapStateToProps = (state, ownProps) => { activeSearch.response && activeSearch.response.plan ) { - const itins = getActiveItineraries(state.otp) + const itins = getActiveItineraries(state) const visibleItinerary = itins[activeSearch.activeItinerary] if (visibleItinerary) transitiveData = coreUtils.map.itineraryToTransitive(visibleItinerary) } else if ( diff --git a/lib/components/mobile/batch-results-screen.js b/lib/components/mobile/batch-results-screen.js index 51370a897..489fb4fa7 100644 --- a/lib/components/mobile/batch-results-screen.js +++ b/lib/components/mobile/batch-results-screen.js @@ -129,12 +129,12 @@ const BatchMobileResultsScreen = ({ // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const urlParams = coreUtils.query.getUrlParams() return { activeLeg: activeSearch ? activeSearch.activeLeg : null, - errors: getResponsesWithErrors(state.otp), - itineraries: getActiveItineraries(state.otp), + errors: getResponsesWithErrors(state), + itineraries: getActiveItineraries(state), itineraryView: urlParams.ui_itineraryView || ItineraryView.DEFAULT } } diff --git a/lib/components/mobile/main.js b/lib/components/mobile/main.js index 4027f295e..58af94275 100644 --- a/lib/components/mobile/main.js +++ b/lib/components/mobile/main.js @@ -2,6 +2,10 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' +import * as uiActions from '../../actions/ui' +import { ComponentContext } from '../../util/contexts' +import { getActiveSearch } from '../../util/state' + import MobileDateTimeScreen from './date-time-screen' import MobileOptionsScreen from './options-screen' import MobileLocationSearch from './location-search' @@ -10,10 +14,6 @@ import MobileStopViewer from './stop-viewer' import MobileTripViewer from './trip-viewer' import MobileRouteViewer from './route-viewer' -import * as uiActions from '../../actions/ui' -import { ComponentContext } from '../../util/contexts' -import { getActiveSearch } from '../../util/state' - const { MainPanelContent, MobileScreens } = uiActions class MobileMain extends Component { @@ -122,7 +122,7 @@ class MobileMain extends Component { const mapStateToProps = (state, ownProps) => { const { config, currentQuery, location, ui: uiState } = state.otp - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) return { activeSearch, config, diff --git a/lib/components/mobile/results-header.js b/lib/components/mobile/results-header.js index d0aa51c4b..39431c201 100644 --- a/lib/components/mobile/results-header.js +++ b/lib/components/mobile/results-header.js @@ -93,15 +93,15 @@ class ResultsHeader extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const {useRealtime} = state.otp const response = !activeSearch ? null : useRealtime ? activeSearch.response : activeSearch.nonRealtimeResponse - const itineraries = getActiveItineraries(state.otp) + const itineraries = getActiveItineraries(state) return { - errors: getResponsesWithErrors(state.otp), + errors: getResponsesWithErrors(state), query: state.otp.currentQuery, resultCount: response ? itineraries.length diff --git a/lib/components/mobile/results-screen.js b/lib/components/mobile/results-screen.js index ece1501bc..170eeeb56 100644 --- a/lib/components/mobile/results-screen.js +++ b/lib/components/mobile/results-screen.js @@ -157,18 +157,18 @@ class MobileResultsScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const {useRealtime} = state.otp const response = !activeSearch ? null : useRealtime ? activeSearch.response : activeSearch.nonRealtimeResponse - const realtimeEffects = getRealtimeEffects(state.otp) - const itineraries = getActiveItineraries(state.otp) + const realtimeEffects = getRealtimeEffects(state) + const itineraries = getActiveItineraries(state) return { activeItineraryIndex: activeSearch ? activeSearch.activeItinerary : null, activeLeg: activeSearch ? activeSearch.activeLeg : null, - error: getActiveError(state.otp), + error: getActiveError(state), query: state.otp.currentQuery, realtimeEffects, resultCount: diff --git a/lib/components/narrative/itinerary-carousel.js b/lib/components/narrative/itinerary-carousel.js index 912126d88..9b08b0cc6 100644 --- a/lib/components/narrative/itinerary-carousel.js +++ b/lib/components/narrative/itinerary-carousel.js @@ -6,11 +6,12 @@ import { connect } from 'react-redux' import SwipeableViews from 'react-swipeable-views' import { setActiveItinerary, setActiveLeg, setActiveStep } from '../../actions/narrative' -import Icon from './icon' -import Loading from './loading' import { ComponentContext } from '../../util/contexts' import { getActiveItineraries, getActiveSearch } from '../../util/state' +import Icon from './icon' +import Loading from './loading' + class ItineraryCarousel extends Component { state = {} static propTypes = { @@ -110,8 +111,8 @@ class ItineraryCarousel extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) - const itineraries = getActiveItineraries(state.otp) + const activeSearch = getActiveSearch(state) + const itineraries = getActiveItineraries(state) return { itineraries, diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 3f0283811..ba4a145fc 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -13,8 +13,6 @@ import { updateItineraryFilter } from '../../actions/narrative' import * as uiActions from '../../actions/ui' -import NarrativeItinerariesErrors from './narrative-itineraries-errors' -import NarrativeItinerariesHeader from './narrative-itineraries-header' import { ComponentContext } from '../../util/contexts' import { getActiveItineraries, @@ -23,6 +21,9 @@ import { getResponsesWithErrors } from '../../util/state' +import NarrativeItinerariesErrors from './narrative-itineraries-errors' +import NarrativeItinerariesHeader from './narrative-itineraries-header' + const { ItineraryView } = uiActions class NarrativeItineraries extends Component { @@ -211,13 +212,13 @@ class NarrativeItineraries extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const activeItinerary = activeSearch && activeSearch.activeItinerary const { errorMessages, modes } = state.otp.config const { sort } = state.otp.filter const pending = activeSearch ? Boolean(activeSearch.pending) : false - const itineraries = getActiveItineraries(state.otp) - const realtimeEffects = getRealtimeEffects(state.otp) + const itineraries = getActiveItineraries(state) + const realtimeEffects = getRealtimeEffects(state) const useRealtime = state.otp.useRealtime const urlParams = coreUtils.query.getUrlParams() const itineraryView = urlParams.ui_itineraryView || ItineraryView.DEFAULT @@ -234,7 +235,7 @@ const mapStateToProps = (state, ownProps) => { activeLeg: activeSearch && activeSearch.activeLeg, activeSearch, activeStep: activeSearch && activeSearch.activeStep, - errors: getResponsesWithErrors(state.otp), + errors: getResponsesWithErrors(state), errorMessages, itineraries, itineraryIsExpanded, diff --git a/lib/components/narrative/narrative-routing-results.js b/lib/components/narrative/narrative-routing-results.js index 5660c7e29..9cd2fdbb9 100644 --- a/lib/components/narrative/narrative-routing-results.js +++ b/lib/components/narrative/narrative-routing-results.js @@ -2,10 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' -import Loading from './loading' -import TabbedItineraries from './tabbed-itineraries' import ErrorMessage from '../form/error-message' - import { getActiveError, getActiveItineraries, @@ -13,6 +10,9 @@ import { } from '../../util/state' import { setMainPanelContent } from '../../actions/ui' +import TabbedItineraries from './tabbed-itineraries' +import Loading from './loading' + class NarrativeRoutingResults extends Component { static propTypes = { routingType: PropTypes.string @@ -46,12 +46,12 @@ class NarrativeRoutingResults extends Component { } const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const pending = activeSearch ? Boolean(activeSearch.pending) : false return { mainPanelContent: state.otp.ui.mainPanelContent, - error: getActiveError(state.otp), - itineraries: getActiveItineraries(state.otp), + error: getActiveError(state), + itineraries: getActiveItineraries(state), pending, routingType: activeSearch && activeSearch.query.routingType } diff --git a/lib/components/narrative/save-trip-button.js b/lib/components/narrative/save-trip-button.js index af80a07fa..d5cabe2e7 100644 --- a/lib/components/narrative/save-trip-button.js +++ b/lib/components/narrative/save-trip-button.js @@ -79,7 +79,7 @@ const SaveTripButton = ({ const mapStateToProps = (state, ownProps) => { const { persistence } = state.otp.config return { - itinerary: getActiveItinerary(state.otp), + itinerary: getActiveItinerary(state), loggedInUser: state.user.loggedInUser, persistence } diff --git a/lib/components/narrative/tabbed-itineraries.js b/lib/components/narrative/tabbed-itineraries.js index cfa2af827..d57ff665b 100644 --- a/lib/components/narrative/tabbed-itineraries.js +++ b/lib/components/narrative/tabbed-itineraries.js @@ -162,9 +162,9 @@ class TabButton extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const pending = activeSearch ? Boolean(activeSearch.pending) : false - const realtimeEffects = getRealtimeEffects(state.otp) + const realtimeEffects = getRealtimeEffects(state) const useRealtime = state.otp.useRealtime return { // swap out realtime itineraries with non-realtime depending on boolean diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 3b02e59d5..d7fe2534c 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -9,16 +9,17 @@ import AccountPage from '../account-page' import * as uiActions from '../../../actions/ui' import * as userActions from '../../../actions/user' import AwaitingScreen from '../awaiting-screen' -import SavedTripEditor from './saved-trip-editor' -import TripBasicsPane from './trip-basics-pane' -import TripNotificationsPane from './trip-notifications-pane' -import TripSummaryPane from './trip-summary-pane' import { TRIPS_PATH } from '../../../util/constants' import { ALL_DAYS, arrayToDayFields, WEEKDAYS } from '../../../util/monitored-trip' import { getActiveItineraries, getActiveSearch } from '../../../util/state' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' 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' + // The validation schema shape for the form fields. // TODO: add fields here as they are implemented. const validationSchemaShape = { @@ -194,9 +195,9 @@ class SavedTripScreen extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) const activeItinerary = activeSearch && activeSearch.activeItinerary - const itineraries = getActiveItineraries(state.otp) || [] + const itineraries = getActiveItineraries(state) || [] const tripId = ownProps.match.params.id return { activeSearchId: state.otp.activeSearchId, diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 2cc4ae95c..b4f338a6a 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -11,11 +11,12 @@ import styled from 'styled-components' import * as apiActions from '../../actions/api' import * as mapActions from '../../actions/map' import * as uiActions from '../../actions/ui' -import LiveStopTimes from './live-stop-times' import Icon from '../narrative/icon' -import StopScheduleTable from './stop-schedule-table' import { getShowUserSettings, getStopViewerConfig } from '../../util/state' +import LiveStopTimes from './live-stop-times' +import StopScheduleTable from './stop-schedule-table' + const { getTimeFormat, getUserTimezone, @@ -290,8 +291,8 @@ class StopViewer extends Component { // connect to redux store const mapStateToProps = (state, ownProps) => { - const showUserSettings = getShowUserSettings(state.otp) - const stopViewerConfig = getStopViewerConfig(state.otp) + const showUserSettings = getShowUserSettings(state) + const stopViewerConfig = getStopViewerConfig(state) return { autoRefreshStopTimes: state.otp.user.autoRefreshStopTimes, favoriteStops: state.otp.user.favoriteStops, diff --git a/lib/util/state.js b/lib/util/state.js index 89dd207af..0e6c0f6ec 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -11,11 +11,11 @@ const { calculateFares } = coreUtils.itinerary /** * Get the active search object - * @param {Object} otpState the OTP state object + * @param {Object} state the redux state object * @returns {Object} an search object, or null if there is no active search */ -export function getActiveSearch (otpState) { - return otpState.searches[otpState.activeSearchId] +export function getActiveSearch (state) { + return state.otp.searches[state.otp.activeSearchId] } /** @@ -74,14 +74,14 @@ function humanReadableMode (modeStr) { /** * Generates a list of issues/errors from a list of OTP responses. * - * @param {Object} otpState the OTP state object + * @param {Object} state the redux state object * @return {Array} An array of objects describing the errors seen. These will at * a minimum contain the `msg` object describing the error, but could also * include an `id` and `modes` key for trip planning errors or `network` for * errors with vehicle rentals */ -export function getResponsesWithErrors (otpState) { - const search = getActiveSearch(otpState) +export function getResponsesWithErrors (state) { + const search = getActiveSearch(state) const rentalFeedErrors = [] const networksWithFeedWideErrors = {} const tripPlanningErrors = [] @@ -126,8 +126,8 @@ export function getResponsesWithErrors (otpState) { * Gets the active error (either a planning error or a rental feed error). This * should only be used with itinerary routing, not batch routing. */ -export function getActiveError (otpState) { - const errors = getResponsesWithErrors(otpState) +export function getActiveError (state) { + const errors = getResponsesWithErrors(state) return errors[errors.length - 1] } @@ -162,16 +162,16 @@ export function getErrorMessage (error, errorMessages) { /** * Helper to get the active search's non-realtime response */ -function getActiveSearchNonRealtimeResponse (otpState) { - const search = getActiveSearch(otpState) +function getActiveSearchNonRealtimeResponse (state) { + const search = getActiveSearch(state) return search && search.nonRealtimeResponse } /** * Helper to get the active search's realtime response */ -function getActiveSearchRealtimeResponse (otpState) { - const search = getActiveSearch(otpState) +function getActiveSearchRealtimeResponse (state) { + const search = getActiveSearch(state) return search && search.response } @@ -208,20 +208,20 @@ const hashItinerary = memoize( /** * Get the active itineraries for the active search, which is dependent on * whether realtime or non-realtime results should be displayed - * @param {Object} otpState the OTP state object + * @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( - otpState => otpState.config, + state => state.otp.config, getActiveSearchNonRealtimeResponse, - otpState => otpState.filter, + state => state.otp.filter, getActiveSearchRealtimeResponse, - otpState => otpState.useRealtime, + state => state.otp.useRealtime, ( config, nonRealtimeResponse, - otpStateFilter, + itinerarySortSettings, realtimeResponse, useRealtime ) => { @@ -251,7 +251,7 @@ export const getActiveItineraries = createSelector( } }) } - const {sort} = otpStateFilter + 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). @@ -394,13 +394,13 @@ export function getTotalFareAsString (itinerary) { /** * Get the active itinerary/profile for the active search object - * @param {Object} otpState the OTP state object + * @param {Object} state the redux state object * @returns {Object} an itinerary object from the OTP plan response, or null if * there is no active search or itinerary */ -export function getActiveItinerary (otpState) { - const search = getActiveSearch(otpState) - const itineraries = getActiveItineraries(otpState) +export function getActiveItinerary (state) { + const search = getActiveSearch(state) + const itineraries = getActiveItineraries(state) if (!itineraries || !search) return null return itineraries.length > search.activeItinerary && search.activeItinerary >= 0 ? itineraries[search.activeItinerary] @@ -421,19 +421,19 @@ export function hasValidLocation (query, locationKey) { /** * Determine if the current query is valid - * @param {Object} otpState the OTP state object + * @param {Object} state the redux state object * @returns {boolean} */ -export function queryIsValid (otpState) { - const {currentQuery} = otpState +export function queryIsValid (state) { + const {currentQuery} = state.otp return hasValidLocation(currentQuery, 'from') && hasValidLocation(currentQuery, 'to') // TODO: add mode validation // TODO: add date/time validation } -export function getRealtimeEffects (otpState) { - const search = getActiveSearch(otpState) +export function getRealtimeEffects (state) { + const search = getActiveSearch(state) const realtimeItineraries = search && search.response && @@ -477,7 +477,7 @@ export function getRealtimeEffects (otpState) { routesDiffer: !isEqual(normalRoutes, realtimeRoutes), normalDuration, realtimeDuration, - exceedsThreshold: Math.abs(normalDuration - realtimeDuration) >= otpState.config.realtimeEffectsDisplayThreshold + exceedsThreshold: Math.abs(normalDuration - realtimeDuration) >= state.otp.config.realtimeEffectsDisplayThreshold } // // TESTING: Return this instead to simulate a realtime-affected itinerary. // return { @@ -494,23 +494,23 @@ export function getRealtimeEffects (otpState) { /** * Determine whether user settings panel is enabled. */ -export function getShowUserSettings (otpState) { - return otpState.config.persistence && otpState.config.persistence.enabled +export function getShowUserSettings (state) { + return state.otp.config.persistence?.enabled } -export function getStopViewerConfig (otpState) { - return otpState.config.stopViewer +export function getStopViewerConfig (state) { + return state.otp.config.stopViewer } /** * Assemble any UI-state properties to be tracked via URL into a single object * TODO: Expand to include additional UI properties */ -export function getUiUrlParams (otpState) { - const activeSearch = getActiveSearch(otpState) +export function getUiUrlParams (state) { + const activeSearch = getActiveSearch(state) const uiParams = { ui_activeItinerary: activeSearch ? activeSearch.activeItinerary : 0, - ui_activeSearch: otpState.activeSearchId + ui_activeSearch: state.otp.activeSearchId } return uiParams } @@ -533,7 +533,7 @@ export function getTitle (state) { if (viewedStop && viewedStop.stopId) title += ` ${viewedStop.stopId}` break default: - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) if (activeSearch) { title += ` | ${coreUtils.query.summarizeQuery(activeSearch.query, user.locations)}` } From 6cc6a0d6f543688b21b8f0a0a918e68b3b3b9d30 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Wed, 16 Jun 2021 16:58:36 -0700 Subject: [PATCH 35/89] refactor: add a few otpState fixes that got forgotten --- lib/util/state.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/util/state.js b/lib/util/state.js index 3559387b4..872df2620 100644 --- a/lib/util/state.js +++ b/lib/util/state.js @@ -421,7 +421,7 @@ function activeSearchHasResponse (state) { * @param {*} state The entire redux state used to obtain the response. */ function getOtpResponse (state) { - return getActiveSearch(state.otp)?.response?.otp + return getActiveSearch(state)?.response?.otp } /** @@ -429,7 +429,7 @@ function getOtpResponse (state) { * @param {*} state The entire redux state in which the query is stored. */ function itineraryResponseExists (state) { - const activeSearch = getActiveSearch(state.otp) + const activeSearch = getActiveSearch(state) return activeSearch?.response?.length > 0 && activeSearch?.query?.routingType === 'ITINERARY' } @@ -439,7 +439,7 @@ function itineraryResponseExists (state) { * @param {*} state The entire redux state from which to retrieve the itinerary. */ export function getVisibleItineraryIndex (state) { - return getActiveSearch(state.otp)?.visibleItinerary + return getActiveSearch(state)?.visibleItinerary } /** @@ -448,8 +448,8 @@ export function getVisibleItineraryIndex (state) { */ function getItineraryToRender (state) { const visibleItineraryIndex = getVisibleItineraryIndex(state) - const activeItinerary = getActiveItinerary(state.otp) - const itins = getActiveItineraries(state.otp) + const activeItinerary = getActiveItinerary(state) + const itins = getActiveItineraries(state) return itins[visibleItineraryIndex] || activeItinerary } From 6c23d7ec0ea5ab18ea6f3ed68e96e6ad0752c0e5 Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Thu, 17 Jun 2021 11:08:04 -0400 Subject: [PATCH 36/89] fix(routingQuery): Properly revert to list view after planning trip. --- lib/actions/api.js | 3 +++ lib/components/form/batch-settings.js | 1 + 2 files changed, 4 insertions(+) diff --git a/lib/actions/api.js b/lib/actions/api.js index 0bf3a442c..820ba1564 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -9,6 +9,7 @@ import { createAction } from 'redux-actions' import qs from 'qs' import { rememberPlace } from './map' +import { ItineraryView, setItineraryView } from './ui' import { getStopViewerConfig, queryIsValid } from '../util/state' import { getSecureFetchOptions } from '../util/middleware' @@ -93,6 +94,8 @@ export function routingQuery (searchId = null) { console.warn('Query is invalid. Aborting routing query', otpState.currentQuery) return } + // Reset itinerary view to default (list view). + dispatch(setItineraryView(ItineraryView.DEFAULT)) const activeItinerary = getActiveItinerary(otpState) const routingType = otpState.currentQuery.routingType // For multiple mode combinations, gather injected params from config/query. diff --git a/lib/components/form/batch-settings.js b/lib/components/form/batch-settings.js index 88deaadac..b0e61e957 100644 --- a/lib/components/form/batch-settings.js +++ b/lib/components/form/batch-settings.js @@ -114,6 +114,7 @@ class BatchSettings extends Component { } // Close any expanded panels. this.setState({expanded: null}) + // Plan trip. routingQuery() } From 85f47abf2ff15e290c08c18bd4749f2bed4862c3 Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Thu, 17 Jun 2021 11:40:19 -0400 Subject: [PATCH 37/89] test(Update snapshots): --- __tests__/actions/__snapshots__/api.js.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/__tests__/actions/__snapshots__/api.js.snap b/__tests__/actions/__snapshots__/api.js.snap index 3c1375d6c..1932ad477 100644 --- a/__tests__/actions/__snapshots__/api.js.snap +++ b/__tests__/actions/__snapshots__/api.js.snap @@ -2,6 +2,9 @@ exports[`actions > api routingQuery should gracefully handle bad response 1`] = ` Array [ + Array [ + [Function], + ], Array [ Object { "payload": Object { @@ -16,6 +19,9 @@ Array [ Array [ [Function], ], + Array [ + [Function], + ], Array [ Object { "payload": Object { @@ -35,6 +41,9 @@ Array [ exports[`actions > api routingQuery should make a query to OTP 1`] = ` Array [ + Array [ + [Function], + ], Array [ Object { "payload": Object { From 6e4b2c0b492571f7f0215bf585f24758304d66d7 Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Thu, 17 Jun 2021 12:19:21 -0400 Subject: [PATCH 38/89] refactor(Add FIXME): --- lib/actions/ui.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/actions/ui.js b/lib/actions/ui.js index 955515798..fc6206b9a 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -240,6 +240,7 @@ export const MobileScreens = { * (currently only used in batch results). */ export const ItineraryView = { + /** FIXME: Duplicate value for 'list', DEFAULT and LIST */ DEFAULT: 'list', /** One itinerary is shown. (In mobile view, the map is hidden.) */ FULL: 'full', From 8ae80cb6da5e63712a54eedcd21a50ab18c5efd5 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 22 Jun 2021 17:56:07 -0400 Subject: [PATCH 39/89] refactor(mailables): address remaining mailables feedback --- lib/actions/call-taker.js | 13 +-- lib/components/admin/call-taker-controls.js | 6 +- lib/components/admin/mailables-window.js | 91 +++++++++++++-------- lib/components/app/app-menu.js | 30 ++++++- lib/index.css | 6 ++ lib/reducers/call-taker.js | 3 +- lib/util/mailables.js | 41 +++++----- 7 files changed, 126 insertions(+), 64 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index c46a455ee..b0413bc31 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -3,12 +3,14 @@ import { serialize } from 'object-to-formdata' import qs from 'qs' import { createAction } from 'redux-actions' -import {toggleFieldTrips} from './field-trip' -import {resetForm} from './form' 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 {resetForm} from './form' +import {toggleFieldTrips} from './field-trip' + if (typeof (fetch) === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -45,9 +47,10 @@ export function resetAndToggleCallHistory () { */ export function beginCallIfNeeded () { return function (dispatch, getState) { - const {callTaker, otp} = getState() - const calltakerConfig = otp.config.modules.find(m => m.id === 'call') - if (calltakerConfig && !callTaker.activeCall && !callTaker.fieldTrip.visible) { + const state = getState() + const {activeCall, fieldTrip} = state.callTaker + const callTakerEnabled = isModuleEnabled(state, Modules.CALL_TAKER) + if (callTakerEnabled && !activeCall && !fieldTrip.visible) { dispatch(beginCall()) } } diff --git a/lib/components/admin/call-taker-controls.js b/lib/components/admin/call-taker-controls.js index f4391ef63..ee5e1cf1a 100644 --- a/lib/components/admin/call-taker-controls.js +++ b/lib/components/admin/call-taker-controls.js @@ -6,6 +6,8 @@ import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' import * as uiActions from '../../actions/ui' import Icon from '../narrative/icon' +import { isModuleEnabled, Modules } from '../../util/config' + import { CallHistoryButton, CallTimeCounter, @@ -126,8 +128,8 @@ class CallTakerControls extends Component { const mapStateToProps = (state, ownProps) => { return { callTaker: state.callTaker, - callTakerEnabled: Boolean(state.otp.config.modules.find(m => m.id === 'call')), - fieldTripEnabled: Boolean(state.otp.config.modules.find(m => m.id === 'ft')), + callTakerEnabled: isModuleEnabled(state, Modules.CALL_TAKER), + fieldTripEnabled: isModuleEnabled(state, Modules.FIELD_TRIP), session: state.callTaker.session } } diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index 73674cf03..a29dbdfbb 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -1,13 +1,15 @@ import React, { Component } from 'react' -import { Badge } from 'react-bootstrap' +import { Badge, Button } from 'react-bootstrap' import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' -import DraggableWindow from './draggable-window' import Icon from '../narrative/icon' -import {MailablesList, WindowHeader} from './styled' +import {getModuleConfig, Modules} from '../../util/config' import {createLetter, MAILABLE_FIELDS} from '../../util/mailables' +import {MailablesList, WindowHeader} from './styled' +import DraggableWindow from './draggable-window' + /** * A window enabled through the Mailables module that allows Call Taker users * to generate a PDF with an invoice of items to be mailed to transit customers. @@ -55,41 +57,52 @@ class MailablesWindow extends Component { const selectableMailables = items.filter(m => !selectedMailables.find(mailable => mailable.name === m.name) ) + const MAILABLE_WIDTH = 300 return ( + + + + + } header={ <> - Mailables{' '} - - - + Mailables{' '} } + height='300px' onClickClose={toggleMailables} scroll={false} - style={{height: '340px', width: '600px'}} + style={{width: MAILABLE_WIDTH * 2}} >
- {MAILABLE_FIELDS.map(f => ( - +

Customer Address

+ {MAILABLE_FIELDS.map((row, r) => ( +
+ {row.map(f => ( + + ))} +
))}
-
+

All Mailables

{selectableMailables.map((mailable, i) => ( @@ -100,7 +113,7 @@ class MailablesWindow extends Component { ))}
-
+

Selected Mailables {selectedMailables.length}

{selectedMailables.length > 0 @@ -140,13 +153,16 @@ class MailableOption extends Component { render () { const {mailable, onClear} = this.props const isSelected = Boolean(onClear) + const containerStyle = {display: 'block', maxWidth: '290px'} + const label = ( +
+ {mailable.name} +
+ ) if (isSelected) { return ( -
- {mailable.name} - +
+ {label}
} +
) } return ( - ) } } const mapStateToProps = (state, ownProps) => { - const mailablesConfig = state.otp.config.modules?.find(m => m.id === 'mailables') return { callTaker: state.callTaker, - mailablesConfig + mailablesConfig: getModuleConfig(state, Modules.MAILABLES) } } diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index fae07c9f1..e175f76b4 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -6,9 +6,10 @@ import { DropdownButton, MenuItem } from 'react-bootstrap' import { withRouter } from 'react-router' import Icon from '../narrative/icon' - import * as callTakerActions from '../../actions/call-taker' +import * as fieldTripActions from '../../actions/field-trip' import { MainPanelContent, setMainPanelContent } from '../../actions/ui' +import { isModuleEnabled, Modules } from '../../util/config' // TODO: make menu items configurable via props/config @@ -43,7 +44,14 @@ class AppMenu extends Component { _toggleMailables = () => this.props.toggleMailables() render () { - const { languageConfig, mailablesEnabled } = this.props + const { + callTakerEnabled, + fieldTripEnabled, + languageConfig, + mailablesEnabled, + resetAndToggleCallHistory, + resetAndToggleFieldTrips + } = this.props return (
@@ -56,6 +64,16 @@ class AppMenu extends Component { {languageConfig.routeViewer || 'Route Viewer'} + {callTakerEnabled && + + Call History + + } + {fieldTripEnabled && + + Field Trip + + } {mailablesEnabled && Mailables @@ -73,15 +91,19 @@ class AppMenu extends Component { // connect to the redux store const mapStateToProps = (state, ownProps) => { - const {language, modules} = state.otp.config + const {language} = state.otp.config return { + callTakerEnabled: isModuleEnabled(state, Modules.CALL_TAKER), + fieldTripEnabled: isModuleEnabled(state, Modules.FIELD_TRIP), languageConfig: language, - mailablesEnabled: Boolean(modules?.find(m => m.id === 'mailables')) + mailablesEnabled: isModuleEnabled(state, Modules.MAILABLES) } } const mapDispatchToProps = { setMainPanelContent, + resetAndToggleCallHistory: callTakerActions.resetAndToggleCallHistory, + resetAndToggleFieldTrips: fieldTripActions.resetAndToggleFieldTrips, toggleMailables: callTakerActions.toggleMailables } diff --git a/lib/index.css b/lib/index.css index 80662157d..d87b78e7a 100644 --- a/lib/index.css +++ b/lib/index.css @@ -49,3 +49,9 @@ button.header, button.step, .clear-button-formatting { button.header, button.step { width: 100%; } + +.overflow-ellipsis { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 2694017dc..a8bf02574 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -3,9 +3,10 @@ import moment from 'moment' import {constructNewCall} from '../util/call-taker' import {FETCH_STATUS} from '../util/constants' +import {getModuleConfig, Modules} from '../util/config' function createCallTakerReducer (config) { - const calltakerConfig = config.modules.find(m => m.id === 'call') + const calltakerConfig = getModuleConfig({otp: {config}}, Modules.CALL_TAKER) const initialState = { activeCall: null, callHistory: { diff --git a/lib/util/mailables.js b/lib/util/mailables.js index d4c30221d..dd6c6c94c 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -4,16 +4,28 @@ import moment from 'moment' import PDFDocument from './PDFDocumentWithTables' export const MAILABLE_FIELDS = [ - {fieldName: 'firstname', placeholder: 'First name'}, - {fieldName: 'lastname', placeholder: 'Last name'}, - {fieldName: 'address1', placeholder: 'Address 1'}, - {fieldName: 'address2', placeholder: 'Address 2'}, - {fieldName: 'city', placeholder: 'City'}, - {fieldName: 'state', placeholder: 'State'}, - {fieldName: 'zip', placeholder: 'Zip'} + [ + {fieldName: 'firstname', placeholder: 'First name'}, + {fieldName: 'lastname', placeholder: 'Last name'} + ], + [ + {fieldName: 'address1', placeholder: 'Address 1'}, + {fieldName: 'address2', placeholder: 'Address 2'} + ], + [ + {fieldName: 'city', placeholder: 'City'}, + {fieldName: 'state', placeholder: 'State'}, + {fieldName: 'zip', placeholder: 'Zip'} + ] ] export function createLetter (formData, mailablesConfig) { + const imageUrl = mailablesConfig.header_graphic + // 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.`) + } // 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() @@ -30,7 +42,7 @@ export function createLetter (formData, mailablesConfig) { const imageData = {data, height: img.height, width: img.width} writePDF(formData, imageData, mailablesConfig) } - img.src = mailablesConfig.header_graphic + img.src = imageUrl } function writePDF (formData, imageData, otpConfig) { @@ -44,17 +56,13 @@ function writePDF (formData, imageData, otpConfig) { state = '', zip = '' } = formData - const { - horizontal_margin: horizontalMargin, - vertical_margin: verticalMargin - } = otpConfig + const {horizontalMargin, verticalMargin} = otpConfig const margins = { top: verticalMargin, bottom: verticalMargin, left: horizontalMargin, right: horizontalMargin } - console.log(margins) const doc = new PDFDocument({margins}) const stream = doc.pipe(blobStream()) preparePage(doc, imageData, otpConfig) @@ -117,12 +125,7 @@ function writePDF (formData, imageData, otpConfig) { } function preparePage (doc, imageData, otpConfig) { - const { - footer, - header_graphic: headerGraphic, - header_graphic_height: headerGraphicHeight, - header_graphic_width: headerGraphicWidth - } = 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 From b818fb2c0acc1ce3173ef93bcf93facfd465d69e Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 22 Jun 2021 17:59:00 -0400 Subject: [PATCH 40/89] refactor(mailables): check if mailables is enabled --- lib/components/admin/mailables-window.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index a29dbdfbb..24417fbcc 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' import Icon from '../narrative/icon' -import {getModuleConfig, Modules} from '../../util/config' +import {getModuleConfig, isModuleEnabled, Modules} from '../../util/config' import {createLetter, MAILABLE_FIELDS} from '../../util/mailables' import {MailablesList, WindowHeader} from './styled' @@ -50,7 +50,8 @@ class MailablesWindow extends Component { } render () { - const {mailablesConfig, callTaker, toggleMailables} = this.props + const {mailablesConfig, mailablesEnabled, callTaker, toggleMailables} = this.props + if (mailablesEnabled) return null const {mailables: selectedMailables} = this.state const {items} = mailablesConfig if (!callTaker.mailables.visible) return null @@ -208,7 +209,8 @@ class MailableOption extends Component { const mapStateToProps = (state, ownProps) => { return { callTaker: state.callTaker, - mailablesConfig: getModuleConfig(state, Modules.MAILABLES) + mailablesConfig: getModuleConfig(state, Modules.MAILABLES), + mailablesEnabled: isModuleEnabled(state, Modules.MAILABLES) } } From f04d90a0fbe5cd58d4c9461637031a23787d7a29 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 23 Jun 2021 10:05:09 -0400 Subject: [PATCH 41/89] refactor(mailables): address PR comments --- lib/components/admin/call-taker-windows.js | 4 ++-- lib/components/admin/mailables-window.js | 12 ++++++------ lib/util/mailables.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/components/admin/call-taker-windows.js b/lib/components/admin/call-taker-windows.js index 9cfe9581e..4a590f6d6 100644 --- a/lib/components/admin/call-taker-windows.js +++ b/lib/components/admin/call-taker-windows.js @@ -4,8 +4,8 @@ import CallHistoryWindow from './call-history-window' import MailablesWindow from './mailables-window' /** - * Collects the various draggable windows used in the Call Taker module to - * display, for example, the call record list and (TODO) the list of field trips. + * Collects and renders the call history and mailables windows used in the Call + * Taker module. */ export default class CallTakerWindows extends Component { render () { diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index 24417fbcc..b0c55d39e 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -51,7 +51,7 @@ class MailablesWindow extends Component { render () { const {mailablesConfig, mailablesEnabled, callTaker, toggleMailables} = this.props - if (mailablesEnabled) return null + if (!mailablesEnabled) return null const {mailables: selectedMailables} = this.state const {items} = mailablesConfig if (!callTaker.mailables.visible) return null @@ -123,7 +123,7 @@ class MailablesWindow extends Component { index={i} key={mailable.name} mailable={mailable} - onClear={this._removeMailable} + onRemove={this._removeMailable} updateField={this._updateMailableField} /> )) :
No mailables selected.
@@ -147,13 +147,13 @@ class MailableOption extends Component { updateField(index, 'quantity', evt.target.value) } - _onClear = () => this.props.onClear && this.props.onClear(this.props.mailable) + _onRemove = () => this.props.onRemove && this.props.onRemove(this.props.mailable) _onClick = () => this.props.onClick && this.props.onClick(this.props.mailable) render () { - const {mailable, onClear} = this.props - const isSelected = Boolean(onClear) + const {mailable, onRemove} = this.props + const isSelected = Boolean(onRemove) const containerStyle = {display: 'block', maxWidth: '290px'} const label = (
@@ -186,7 +186,7 @@ class MailableOption extends Component { } diff --git a/lib/util/mailables.js b/lib/util/mailables.js index dd6c6c94c..952b0c47c 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -20,7 +20,7 @@ export const MAILABLE_FIELDS = [ ] export function createLetter (formData, mailablesConfig) { - const imageUrl = mailablesConfig.header_graphic + 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) { From 5638633c6c4d0a30cb59c740193ab0ecbf63e2d3 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 23 Jun 2021 11:47:40 -0400 Subject: [PATCH 42/89] refactor(mailables): address remainder of PR feedback --- example-config.yml | 15 +++++ example.js | 6 +- lib/components/admin/call-history-window.js | 65 ++++++++++----------- lib/components/admin/call-taker-windows.js | 19 ------ lib/components/admin/mailables-window.js | 35 ++++++----- lib/components/app/app-menu.js | 7 +-- lib/index.js | 23 +++----- lib/util/PDFDocumentWithTables.js | 12 +++- lib/util/mailables.js | 17 +++++- 9 files changed, 110 insertions(+), 89 deletions(-) delete mode 100644 lib/components/admin/call-taker-windows.js diff --git a/example-config.yml b/example-config.yml index 921e180ac..abb7b4ffb 100644 --- a/example-config.yml +++ b/example-config.yml @@ -135,6 +135,21 @@ modes: label: Own Bike iconWidth: 18 +# # The following modules require the datastoreUrl and trinetReDirect properties +# # to be set. Note: Most of these components are currently only configured for +# # TriMet. +# datastoreUrl: https://localhost:9000 +# trinetReDirect: https://localhost:9001 +# modules: +# # Provides UI elements for Call Takers to record calls/trip queries. +# - id: call +# # Provides UI elements for planning field trips on transit vehicles. +# - id: ft +# # Provides a form for constructing PDF documents for mailing to customers. +# - id: mailables +# items: +# - name: Rte 1 Schedule (1-Vermont) +# largePrint: true routingTypes: - key: ITINERARY diff --git a/example.js b/example.js index 03bc3a591..6810e8403 100644 --- a/example.js +++ b/example.js @@ -16,12 +16,13 @@ import { BatchResultsScreen, BatchRoutingPanel, BatchSearchScreen, + CallHistoryWindow, CallTakerControls, CallTakerPanel, - CallTakerWindows, DefaultItinerary, DefaultMainPanel, FieldTripWindows, + MailablesWindow, MobileResultsScreen, MobileSearchScreen, ResponsiveWebapp, @@ -95,8 +96,9 @@ const components = { : DefaultMainPanel, MapWindows: isCallTakerModuleEnabled ? () => <> - + + : null, MobileResultsScreen: isBatchRoutingEnabled diff --git a/lib/components/admin/call-history-window.js b/lib/components/admin/call-history-window.js index a938168b8..9aa86f479 100644 --- a/lib/components/admin/call-history-window.js +++ b/lib/components/admin/call-history-window.js @@ -1,43 +1,42 @@ -import React, { Component } from 'react' +import React from 'react' import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' +import Icon from '../narrative/icon' + import CallRecord from './call-record' import DraggableWindow from './draggable-window' -import Icon from '../narrative/icon' import {WindowHeader} from './styled' -class CallHistoryWindow extends Component { - render () { - const {callTaker, fetchQueries, searches, toggleCallHistory} = this.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
- } -
- ) - } +function CallHistoryWindow (props) { + const {callTaker, fetchQueries, 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) => { diff --git a/lib/components/admin/call-taker-windows.js b/lib/components/admin/call-taker-windows.js deleted file mode 100644 index 4a590f6d6..000000000 --- a/lib/components/admin/call-taker-windows.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { Component } from 'react' - -import CallHistoryWindow from './call-history-window' -import MailablesWindow from './mailables-window' - -/** - * Collects and renders the call history and mailables windows used in the Call - * Taker module. - */ -export default class CallTakerWindows extends Component { - render () { - return ( - <> - - - - ) - } -} diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index b0c55d39e..cf5ce0593 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -1,11 +1,11 @@ -import React, { Component } from 'react' -import { Badge, Button } from 'react-bootstrap' -import { connect } from 'react-redux' +import React, {Component} from 'react' +import {Badge, Button} from 'react-bootstrap' +import {connect} from 'react-redux' import * as callTakerActions from '../../actions/call-taker' import Icon from '../narrative/icon' import {getModuleConfig, isModuleEnabled, Modules} from '../../util/config' -import {createLetter, MAILABLE_FIELDS} from '../../util/mailables' +import {createLetter, LETTER_FIELDS} from '../../util/mailables' import {MailablesList, WindowHeader} from './styled' import DraggableWindow from './draggable-window' @@ -45,7 +45,7 @@ class MailablesWindow extends Component { _onClickCreateLetter = () => createLetter(this.state, this.props.mailablesConfig) - _updateField = (evt) => { + _updateLetterField = (evt) => { this.setState({[evt.target.id]: evt.target.value}) } @@ -88,13 +88,13 @@ class MailablesWindow extends Component { >

Customer Address

- {MAILABLE_FIELDS.map((row, r) => ( + {LETTER_FIELDS.map((row, r) => (
{row.map(f => ( @@ -106,7 +106,7 @@ class MailablesWindow extends Component {

All Mailables

- {selectableMailables.map((mailable, i) => ( + {selectableMailables.map(mailable => ( {mailable.name} @@ -184,12 +191,14 @@ class MailableOption extends Component { } - + +
) diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js index e175f76b4..152ed812e 100644 --- a/lib/components/app/app-menu.js +++ b/lib/components/app/app-menu.js @@ -41,8 +41,6 @@ class AppMenu extends Component { window.location.href = startOverUrl } - _toggleMailables = () => this.props.toggleMailables() - render () { const { callTakerEnabled, @@ -50,7 +48,8 @@ class AppMenu extends Component { languageConfig, mailablesEnabled, resetAndToggleCallHistory, - resetAndToggleFieldTrips + resetAndToggleFieldTrips, + toggleMailables } = this.props return ( @@ -75,7 +74,7 @@ class AppMenu extends Component { } {mailablesEnabled && - + Mailables } diff --git a/lib/index.js b/lib/index.js index 3f1d1862c..918283f23 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,7 @@ import CallTakerControls from './components/admin/call-taker-controls' -import CallTakerWindows from './components/admin/call-taker-windows' +import CallHistoryWindow from './components/admin/call-history-window' import FieldTripWindows from './components/admin/field-trip-windows' - +import MailablesWindow from './components/admin/mailables-window' import DateTimeModal from './components/form/date-time-modal' import DateTimePreview from './components/form/date-time-preview' import DefaultSearchForm from './components/form/default-search-form' @@ -10,13 +10,11 @@ import LocationField from './components/form/connected-location-field' 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 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 ItineraryCarousel from './components/narrative/itinerary-carousel' import NarrativeItineraries from './components/narrative/narrative-itineraries' @@ -28,50 +26,45 @@ import TransportationNetworkCompanyLeg from './components/narrative/default/tnc- import TripDetails from './components/narrative/connected-trip-details' import TripTools from './components/narrative/trip-tools' import LineItinerary from './components/narrative/line-itin/line-itinerary' - import MobileMain from './components/mobile/main' import MobileResultsScreen from './components/mobile/results-screen' import MobileSearchScreen from './components/mobile/search-screen' - import NavLoginButton from './components/user/nav-login-button' import NavLoginButtonAuth0 from './components/user/nav-login-button-auth0' - import StopViewer from './components/viewers/stop-viewer' import ViewStopButton from './components/viewers/view-stop-button' import ViewTripButton from './components/viewers/view-trip-button' import ViewerContainer from './components/viewers/viewer-container' - import ResponsiveWebapp from './components/app/responsive-webapp' import AppMenu from './components/app/app-menu' import CallTakerPanel from './components/app/call-taker-panel' import DesktopNav from './components/app/desktop-nav' import DefaultMainPanel from './components/app/default-main-panel' - import BatchRoutingPanel from './components/app/batch-routing-panel' import BatchResultsScreen from './components/mobile/batch-results-screen' import BatchSearchScreen from './components/mobile/batch-search-screen' - import { setAutoPlan, setMapCenter } from './actions/config' import { getCurrentPosition } from './actions/location' import { setLocationToCurrent, clearLocation } from './actions/map' import { setUseRealtimeResponse } from './actions/narrative' import { findNearbyStops } from './actions/api' - import createCallTakerReducer from './reducers/call-taker' import createOtpReducer from './reducers/create-otp-reducer' import createUserReducer from './reducers/create-user-reducer' - import otpUtils from './util' export { - // form components + // module components + CallHistoryWindow, CallTakerControls, - CallTakerWindows, + FieldTripWindows, + MailablesWindow, + + // form components DateTimeModal, DateTimePreview, DefaultSearchForm, ErrorMessage, - FieldTripWindows, LocationField, PlanTripButton, SettingsPreview, diff --git a/lib/util/PDFDocumentWithTables.js b/lib/util/PDFDocumentWithTables.js index 8a9bf9ee6..1e5ba85e6 100644 --- a/lib/util/PDFDocumentWithTables.js +++ b/lib/util/PDFDocumentWithTables.js @@ -6,9 +6,19 @@ const PDFDocument = require('pdfkit') * This class extends the pdfkit functionality to provide a tables method, which * will write tableData (2-d array) to the pdf doc. * - * See https://www.andronio.me/2017/09/02/pdfkit-tables/ for more info. + * This was copied with minimal changes + * from https://www.andronio.me/2017/09/02/pdfkit-tables/ */ class PDFDocumentWithTables extends PDFDocument { + /** + * Construct table in the document. + * @param {headers: Array, rows: Array} table table data + * @param {number | Options} arg0 if provided with arg1, the start x + * location. If the only arg provided, the + * options object. + * @param {number} arg1 the start y location + * @param {Options} arg2 options object (if not provided in arg0) + */ table (table, arg0, arg1, arg2) { let startX = this.page.margins.left; let startY = this.y let options = {} diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 952b0c47c..2c185c90e 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -3,7 +3,7 @@ import moment from 'moment' import PDFDocument from './PDFDocumentWithTables' -export const MAILABLE_FIELDS = [ +export const LETTER_FIELDS = [ [ {fieldName: 'firstname', placeholder: 'First name'}, {fieldName: 'lastname', placeholder: 'Last name'} @@ -19,6 +19,11 @@ export const MAILABLE_FIELDS = [ ] ] +/** + * 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. + */ export function createLetter (formData, mailablesConfig) { const imageUrl = mailablesConfig.headerGraphic // A valid URL must be provided in the config in order for the create letter @@ -45,6 +50,10 @@ export function createLetter (formData, mailablesConfig) { img.src = imageUrl } +/** + * Write the provided formData and imageData (header image) to a PDF file and + * open as a new tab. + */ function writePDF (formData, imageData, otpConfig) { const { address1 = '', @@ -66,7 +75,7 @@ function writePDF (formData, imageData, otpConfig) { const doc = new PDFDocument({margins}) const stream = doc.pipe(blobStream()) preparePage(doc, imageData, otpConfig) - doc.on('pageAdded', () => preparePage(doc, otpConfig)) + doc.on('pageAdded', () => preparePage(doc, imageData, otpConfig)) // current date doc.text(moment().format('MMMM D, YYYY')) @@ -124,6 +133,10 @@ 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. + */ function preparePage (doc, imageData, otpConfig) { const {footer, headerGraphic, headerGraphicHeight, headerGraphicWidth} = otpConfig // Store true bottom of page while bottom is temporarily moved to 0. From dfc83e675df72c0e11ac846a3e59b71699d773a1 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 23 Jun 2021 12:26:10 -0400 Subject: [PATCH 43/89] refactor(config): add config util file --- lib/util/config.js | 13 +++++++++++++ lib/util/index.js | 2 ++ 2 files changed, 15 insertions(+) create mode 100644 lib/util/config.js diff --git a/lib/util/config.js b/lib/util/config.js new file mode 100644 index 000000000..ec14f4f43 --- /dev/null +++ b/lib/util/config.js @@ -0,0 +1,13 @@ +export const Modules = { + CALL_TAKER: 'call', + FIELD_TRIP: 'ft', + MAILABLES: 'mailables' +} + +export function isModuleEnabled (state, moduleName) { + return Boolean(getModuleConfig(state, moduleName)) +} + +export function getModuleConfig (state, moduleName) { + return state.otp.config?.modules?.find(m => m.id === moduleName) +} diff --git a/lib/util/index.js b/lib/util/index.js index 3fd88da6e..5bff9a0f6 100644 --- a/lib/util/index.js +++ b/lib/util/index.js @@ -1,9 +1,11 @@ import * as auth from './auth' +import * as config from './config' import * as itinerary from './itinerary' import * as state from './state' const OtpUtils = { auth, + config, itinerary, state } From 45471f744f600458eb0619ed379607408a387c71 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 23 Jun 2021 12:44:37 -0400 Subject: [PATCH 44/89] docs: fix jsdoc --- lib/util/PDFDocumentWithTables.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/util/PDFDocumentWithTables.js b/lib/util/PDFDocumentWithTables.js index 1e5ba85e6..402c36f9e 100644 --- a/lib/util/PDFDocumentWithTables.js +++ b/lib/util/PDFDocumentWithTables.js @@ -12,8 +12,8 @@ const PDFDocument = require('pdfkit') class PDFDocumentWithTables extends PDFDocument { /** * Construct table in the document. - * @param {headers: Array, rows: Array} table table data - * @param {number | Options} arg0 if provided with arg1, the start x + * @param {{headers: Array, rows: Array}} table table data + * @param {(number|Options)} arg0 if provided with arg1, the start x * location. If the only arg provided, the * options object. * @param {number} arg1 the start y location @@ -124,4 +124,4 @@ class PDFDocumentWithTables extends PDFDocument { } } -module.exports = PDFDocumentWithTables +export default PDFDocumentWithTables From f9492f4f320397959494b1b7a5361c430737318f Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 23 Jun 2021 13:41:11 -0400 Subject: [PATCH 45/89] feat(field-trip): add daily report feature; improve perf --- lib/components/admin/field-trip-list.js | 114 +++++++++++++----- .../form/call-taker/date-time-options.js | 2 + lib/components/form/form.css | 6 +- 3 files changed, 91 insertions(+), 31 deletions(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index 84152aa2d..b274a9120 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -1,19 +1,28 @@ +import memoize from 'lodash.memoize' +import moment from 'moment' import React, { Component } from 'react' -import { Badge } from 'react-bootstrap' +import { Badge, Button } from 'react-bootstrap' import { connect } from 'react-redux' import * as fieldTripActions from '../../actions/field-trip' -import DraggableWindow from './draggable-window' import Icon from '../narrative/icon' import Loading from '../narrative/loading' -import {FieldTripRecordButton, WindowHeader} from './styled' import {getVisibleRequests, TABS} from '../../util/call-taker' import {FETCH_STATUS} from '../../util/constants' +import {FieldTripRecordButton, WindowHeader} from './styled' +import DraggableWindow from './draggable-window' + /** * Displays a searchable list of field trip requests in a draggable window. */ class FieldTripList extends Component { + constructor (props) { + super(props) + this.state = { + date: moment().startOf('day').format('YYYY-MM-DD') + } + } _onClickFieldTrip = (request) => { const {callTaker, fetchFieldTripDetails, setActiveFieldTrip} = this.props if (request.id === callTaker.fieldTrip.activeId) { @@ -30,6 +39,12 @@ class FieldTripList extends Component { this.props.setActiveFieldTrip(null) } + _getReportUrl = () => { + const {date} = this.state + const [year, month, day] = date.split('-') + return `${this.props.datastoreUrl}/fieldtrip/opsReport?month=${month}&day=${day}&year=${year}` + } + /** * Change search input selectively. This is to prevent excessive rendering * each time the search input changes (on TriMet's production instance there @@ -51,13 +66,40 @@ class FieldTripList extends Component { this.props.setFieldTripFilter({tab: e.currentTarget.name}) } + _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 return ( + + + + } header={ <> @@ -83,27 +125,14 @@ class FieldTripList extends Component { /> - {TABS.map(tab => { - const active = tab.id === filter.tab - const style = { - backgroundColor: active ? 'navy' : undefined, - borderRadius: 5, - color: active ? 'white' : undefined, - padding: '2px 3px' - } - const requestCount = fieldTrip.requests.data.filter(tab.filter).length - return ( - - ) - })} + {TABS.map(tab => + + )} } onClickClose={toggleFieldTrips} @@ -127,6 +156,32 @@ class FieldTripList extends Component { } } +class Tab extends Component { + getCount = memoize((data, filter) => data.filter(filter).length) + + render () { + const {data, filter, onClick, tab} = this.props + 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 @@ -151,8 +206,10 @@ class FieldTripRequestRecord extends Component { schoolName, startLocation, teacherName, - timeStamp + timeStamp, + travelDate } = request + const formattedDate = travelDate.split(' ').splice(0, 3).join(' ') return (
  • - + Submitted by {teacherName} on {timeStamp} - - {startLocation} to {endLocation} + + {startLocation} to {endLocation} on {formattedDate}
  • @@ -190,6 +247,7 @@ const mapStateToProps = (state, ownProps) => { return { callTaker: state.callTaker, currentQuery: state.otp.currentQuery, + datastoreUrl: state.otp.config.datastoreUrl, searches: state.otp.searches, visibleRequests: getVisibleRequests(state) } diff --git a/lib/components/form/call-taker/date-time-options.js b/lib/components/form/call-taker/date-time-options.js index feb48b1e2..1852f6936 100644 --- a/lib/components/form/call-taker/date-time-options.js +++ b/lib/components/form/call-taker/date-time-options.js @@ -184,6 +184,7 @@ export default class DateTimeOptions extends Component { trigger={['focus', 'hover']} > Date: Wed, 23 Jun 2021 13:46:19 -0400 Subject: [PATCH 46/89] refactor(field-trip): clean up url method --- lib/components/admin/field-trip-list.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index b274a9120..220a5dbd9 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -1,5 +1,6 @@ import memoize from 'lodash.memoize' import moment from 'moment' +import qs from 'qs' import React, { Component } from 'react' import { Badge, Button } from 'react-bootstrap' import { connect } from 'react-redux' @@ -40,9 +41,11 @@ class FieldTripList extends Component { } _getReportUrl = () => { + const {datastoreUrl} = this.props const {date} = this.state const [year, month, day] = date.split('-') - return `${this.props.datastoreUrl}/fieldtrip/opsReport?month=${month}&day=${day}&year=${year}` + const params = {month, day, year} + return `${datastoreUrl}/fieldtrip/opsReport?month=${qs.stringify(params)}` } /** From 57fa70a9516e7712c74ffa702945bde345393441 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 24 Jun 2021 09:55:02 -0400 Subject: [PATCH 47/89] refactor(mailables): address PR comments --- example-config.yml | 10 ++- lib/components/admin/mailables-window.js | 77 ++++++++++++------------ 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/example-config.yml b/example-config.yml index 0fa67a61f..3c2bf0353 100644 --- a/example-config.yml +++ b/example-config.yml @@ -76,7 +76,7 @@ map: ### in the GTFS feed for those routes, or that a getTransitiveRouteLabel function is defined ### in the ComponentContext (see example.js for more). ### - styles.labels, - ### styles.segment_labels: styles attributes recognized by transitive.js. + ### styles.segment_labels: styles attributes recognized by transitive.js. ### For examples of applicable style attributes, see ### https://github.com/conveyal/transitive.js/blob/master/stories/Transitive.stories.js#L47. # transitive: @@ -175,6 +175,14 @@ modes: # items: # - name: Rte 1 Schedule (1-Vermont) # largePrint: true +# # The below settings allow for customizing the PDF letter. +# horizontalMargin: 108 +# verticalMargin: 120 +# introduction: 'Thank you for calling us to request information. We have enclosed for you the following item(s):' +# conclusion: Thank you for your patronage! +# footer: Transit Agency • 555-555-RIDE +# # NOTE: headerGraphic requires a valid URL to a png file. +# headerGraphic: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Trimet_logo.svg/1280px-Trimet_logo.svg.png' routingTypes: - key: ITINERARY diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index cf5ce0593..00ff29441 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -50,7 +50,7 @@ class MailablesWindow extends Component { } render () { - const {mailablesConfig, mailablesEnabled, callTaker, toggleMailables} = this.props + const {callTaker, mailablesConfig, mailablesEnabled, toggleMailables} = this.props if (!mailablesEnabled) return null const {mailables: selectedMailables} = this.state const {items} = mailablesConfig @@ -58,33 +58,27 @@ class MailablesWindow extends Component { const selectableMailables = items.filter(m => !selectedMailables.find(mailable => mailable.name === m.name) ) - const MAILABLE_WIDTH = 300 return ( - - - - + } header={ - <> - - Mailables{' '} - - + + Mailables + } height='300px' onClickClose={toggleMailables} scroll={false} - style={{width: MAILABLE_WIDTH * 2}} + style={{width: '600px'}} >

    Customer Address

    @@ -103,8 +97,15 @@ class MailablesWindow extends Component { ))}
    -
    -

    All Mailables

    +
    +

    + All Mailables +

    {selectableMailables.map(mailable => (
    -
    +

    Selected Mailables {selectedMailables.length}

    {selectedMailables.length > 0 @@ -152,14 +153,14 @@ class MailableOption extends Component { _onClick = () => this.props.onClick && this.props.onClick(this.props.mailable) render () { - const {mailable, onRemove} = this.props + const {index, mailable, onRemove} = this.props const isSelected = Boolean(onRemove) const containerStyle = { backgroundColor: '#eaeaea', display: 'block', - margin: '2px 0px', + margin: '0px 0px 2px 0px', maxWidth: '290px', - padding: '3px 0px', + padding: '3px 2px', width: '100%' } const label = ( @@ -168,10 +169,17 @@ class MailableOption extends Component {
    ) if (isSelected) { + const id = `largeFormat-${index}` return (
    {label} -
    +
    {mailable.largePrint && - <> +
    -
    } -
    From 64bb14744878019b5e7a56e106044ca15d2711cd Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Thu, 24 Jun 2021 15:39:38 -0700 Subject: [PATCH 48/89] refactor: separate mailable components --- lib/components/admin/mailables-window.js | 131 +++++++++++------------ lib/components/admin/styled.js | 26 ++++- 2 files changed, 89 insertions(+), 68 deletions(-) diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js index 00ff29441..656803cdb 100644 --- a/lib/components/admin/mailables-window.js +++ b/lib/components/admin/mailables-window.js @@ -7,7 +7,13 @@ import Icon from '../narrative/icon' import {getModuleConfig, isModuleEnabled, Modules} from '../../util/config' import {createLetter, LETTER_FIELDS} from '../../util/mailables' -import {MailablesList, WindowHeader} from './styled' +import { + MailablesList, + SelectableMailableButton, + SelectedMailableContainer, + SelectedMailableOptionsContainer, + WindowHeader +} from './styled' import DraggableWindow from './draggable-window' /** @@ -108,7 +114,7 @@ class MailablesWindow extends Component { {selectableMailables.map(mailable => ( - @@ -120,7 +126,7 @@ class MailablesWindow extends Component { {selectedMailables.length > 0 ? selectedMailables.map((mailable, i) => ( - this.props.onClick(this.props.mailable) + + render () { + const {mailable} = this.props + return ( + + + + ) + } +} + +class SelectedMailable extends Component { _changeLargeFormat = (evt) => { const {index, updateField} = this.props updateField(index, 'largeFormat', evt.target.checked) @@ -148,76 +170,51 @@ class MailableOption extends Component { updateField(index, 'quantity', evt.target.value) } - _onRemove = () => this.props.onRemove && this.props.onRemove(this.props.mailable) - - _onClick = () => this.props.onClick && this.props.onClick(this.props.mailable) + _onRemove = () => this.props.onRemove(this.props.mailable) render () { - const {index, mailable, onRemove} = this.props - const isSelected = Boolean(onRemove) - const containerStyle = { - backgroundColor: '#eaeaea', - display: 'block', - margin: '0px 0px 2px 0px', - maxWidth: '290px', - padding: '3px 2px', - width: '100%' - } - const label = ( -
    - {mailable.name} -
    - ) - if (isSelected) { - const id = `largeFormat-${index}` - return ( -
    - {label} -
    - - {mailable.largePrint && -
    - - -
    - } - -
    -
    - ) - } + const {index, mailable} = this.props + const id = `largeFormat-${index}` return ( - + + + + + {mailable.largePrint && +
    + + +
    + } + +
    +
    ) } } +function MailableLabel ({ mailable }) { + return ( +
    + {mailable.name} +
    + ) +} + const mapStateToProps = (state, ownProps) => { return { callTaker: state.callTaker, diff --git a/lib/components/admin/styled.js b/lib/components/admin/styled.js index 29d34cec3..45ff35aed 100644 --- a/lib/components/admin/styled.js +++ b/lib/components/admin/styled.js @@ -1,9 +1,10 @@ import { Button as BsButton } from 'react-bootstrap' import styled, {css} from 'styled-components' -import DefaultCounter from './call-time-counter' import Icon from '../narrative/icon' +import DefaultCounter from './call-time-counter' + // Call Taker Controls Components const RED = '#C35134' @@ -161,3 +162,26 @@ export const MailablesList = styled.div` max-height: 120px; overflow-y: scroll; ` + +export const MailableItem = css` + background-color: #eaeaea; + display: block; + margin: 0px 0px 2px 0px; + max-width: 290px; + padding: 3px 2px; + width: 100%; +` + +export const SelectableMailableButton = styled.button` + ${MailableItem} +` + +export const SelectedMailableContainer = styled.div` + ${MailableItem} +` + +export const SelectedMailableOptionsContainer = styled.div` + align-items: center; + display: flex; + justify-content: space-between; +` From 428ba0cd42f00c745097ae941154a8490b04107b Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 25 Jun 2021 09:56:44 -0400 Subject: [PATCH 49/89] feat(form): add plan first/previous/next/last buttons --- .../narrative/narrative-itineraries-header.js | 11 +- .../narrative/plan-first-last-buttons.js | 140 ++++++++++++++++++ lib/util/itinerary.js | 5 + 3 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 lib/components/narrative/plan-first-last-buttons.js diff --git a/lib/components/narrative/narrative-itineraries-header.js b/lib/components/narrative/narrative-itineraries-header.js index fbb90cc63..5b8671e3c 100644 --- a/lib/components/narrative/narrative-itineraries-header.js +++ b/lib/components/narrative/narrative-itineraries-header.js @@ -1,6 +1,8 @@ import styled from 'styled-components' import Icon from '../narrative/icon' + +import PlanFirstLastButtons from './plan-first-last-buttons' import SaveTripButton from './save-trip-button' const IssueButton = styled.button` @@ -46,7 +48,8 @@ export default function NarrativeItinerariesHeader ({ className='options header' style={{ alignItems: 'end', - display: 'flex' + display: 'flex', + flexWrap: 'wrap' }} > {(itineraryIsExpanded || showingErrors) @@ -57,7 +60,6 @@ export default function NarrativeItinerariesHeader ({ > View all options - {itineraryIsExpanded && ( // marginLeft: auto is a way of making something "float right" // within a flex container @@ -72,9 +74,7 @@ export default function NarrativeItinerariesHeader ({ style={{flexGrow: 1}} title={titleText} > - + {resultText} {errors.length > 0 && ( @@ -105,6 +105,7 @@ export default function NarrativeItinerariesHeader ({
    + }
    diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js new file mode 100644 index 000000000..a2148340e --- /dev/null +++ b/lib/components/narrative/plan-first-last-buttons.js @@ -0,0 +1,140 @@ +import {OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT} from '@opentripplanner/core-utils/lib/time' +import moment from 'moment' +import React, {Component} from 'react' +import {Button} from 'react-bootstrap' +import {connect} from 'react-redux' + +import * as apiActions from '../../actions/api' +import * as formActions from '../../actions/form' +import {getFirstStopId} from '../../util/itinerary' +import { + getActiveItineraries, + getActiveSearch, + getResponsesWithErrors, + getVisibleItineraryIndex +} from '../../util/state' + +const serviceBreakTime = '03:00am' +const NINETY_SECONDS = 90000 + +class PlanFirstLastButtons extends Component { + _planFirst = () => { + const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const itinerary = itineraries[activeItinerary] + const params = { + startTransitStopId: getFirstStopId(itinerary), + date: moment(currentQuery.date).format(OTP_API_DATE_FORMAT), + time: serviceBreakTime, + departArrive: 'DEPART', + originalQueryTime: currentQuery.time + } + setQueryParam(params) + routingQuery() + } + + _planPrevious = () => { + const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const itinerary = itineraries[activeItinerary] + const newEndTime = moment(itinerary.endTime - NINETY_SECONDS) + const time = newEndTime.format(OTP_API_TIME_FORMAT) + console.log(activeItinerary, itinerary, itinerary.endTime, time) + const params = { + startTransitStopId: getFirstStopId(itinerary), + time, + date: newEndTime.format(OTP_API_DATE_FORMAT), + departArrive: 'ARRIVE', + originalQueryTime: currentQuery.time + } + setQueryParam(params) + routingQuery() + } + + _planNext = () => { + const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const itinerary = itineraries[activeItinerary] + const newStartTime = moment(itinerary.startTime + NINETY_SECONDS) + const time = newStartTime.format(OTP_API_TIME_FORMAT) + console.log(time) + const params = { + startTransitStopId: getFirstStopId(itinerary), + time, + date: newStartTime.format(OTP_API_DATE_FORMAT), + departArrive: 'DEPART', + originalQueryTime: currentQuery.time + } + setQueryParam(params) + routingQuery() + } + + _planLast = () => { + const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const itinerary = itineraries[activeItinerary] + const params = { + startTransitStopId: getFirstStopId(itinerary), + date: moment(currentQuery.date).format(OTP_API_DATE_FORMAT), + time: serviceBreakTime, + departArrive: 'ARRIVE', + originalQueryTime: currentQuery.time + } + setQueryParam(params) + routingQuery() + } + + render () { + console.log(moment((this.props.itineraries[this.props.activeItinerary] || {}).endTime).format(OTP_API_TIME_FORMAT)) + return ( +
    + + + + +
    + ) + } +} + +const mapStateToProps = (state, ownProps) => { + const activeSearch = getActiveSearch(state.otp) + const activeItinerary = activeSearch && activeSearch.activeItinerary + const { errorMessages, modes } = state.otp.config + const { sort } = state.otp.filter + const pending = activeSearch ? Boolean(activeSearch.pending) : false + const itineraries = getActiveItineraries(state.otp) + + return { + // swap out realtime itineraries with non-realtime depending on boolean + activeItinerary, + activeLeg: activeSearch && activeSearch.activeLeg, + activeSearch, + activeStep: activeSearch && activeSearch.activeStep, + currentQuery: state.otp.currentQuery, + errors: getResponsesWithErrors(state.otp), + errorMessages, + itineraries, + // use a key so that the NarrativeItineraries component and its state is + // reset each time a new search is shown + key: state.otp.activeSearchId, + modes, + pending, + sort, + visibleItinerary: getVisibleItineraryIndex(state) + } +} + +const mapDispatchToProps = { + // beginCallIfNeeded: callTakerActions.beginCallIfNeeded, + // findRoutes: apiActions.findRoutes, + routingQuery: apiActions.routingQuery, + // setGroupSize: fieldTripActions.setGroupSize, + setQueryParam: formActions.setQueryParam +} + +export default connect(mapStateToProps, mapDispatchToProps)(PlanFirstLastButtons) diff --git a/lib/util/itinerary.js b/lib/util/itinerary.js index a8eec1c9c..4433b62f4 100644 --- a/lib/util/itinerary.js +++ b/lib/util/itinerary.js @@ -1,6 +1,7 @@ import { latLngBounds } from 'leaflet' import moment from 'moment' import coreUtils from '@opentripplanner/core-utils' + import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' export function getLeafletItineraryBounds (itinerary) { @@ -45,6 +46,10 @@ export function itineraryCanBeMonitored (itinerary) { return hasTransit && !hasRentalOrRideHail } +export function getFirstStopId (itinerary) { + return getFirstTransitLeg(itinerary)?.from.stopId +} + export function getMinutesUntilItineraryStart (itinerary) { return moment(itinerary.startTime).diff(moment(), 'minutes') } From 7b7dc52474980952b1b20a61e614ed4bb66ac176 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 25 Jun 2021 10:48:54 -0400 Subject: [PATCH 50/89] refactor(api): simplify getRoutingParams; skip empty string/null params --- lib/actions/api.js | 124 ++++++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/lib/actions/api.js b/lib/actions/api.js index 0bf3a442c..b61b5368a 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -8,10 +8,11 @@ import queryParams from '@opentripplanner/core-utils/lib/query-params' import { createAction } from 'redux-actions' import qs from 'qs' -import { rememberPlace } from './map' import { getStopViewerConfig, queryIsValid } from '../util/state' import { getSecureFetchOptions } from '../util/middleware' +import { rememberPlace } from './map' + if (typeof (fetch) === 'undefined') require('isomorphic-fetch') const { hasCar } = coreUtils.itinerary @@ -236,37 +237,36 @@ function constructRoutingQuery (otpState, ignoreRealtimeUpdates, injectedParams return `${planEndpoint}?${qs.stringify(params, { arrayFormat: 'repeat' })}` } -export function getRoutingParams (query, config, ignoreRealtimeUpdates) { - const routingType = query.routingType - const isItinerary = routingType === 'ITINERARY' - let params = {} +function isItineraryQuery (query) { + return query?.routingType === 'ITINERARY' +} - // Start with the universe of OTP parameters defined in query-params.js: - queryParams - .filter(qp => { - // A given parameter is included in the request if all of the following: - // 1. Must apply to the active routing type (ITINERARY or PROFILE) - // 2. Must be included in the current user-defined query - // 3. Must pass the parameter's applicability test, if one is specified - return qp.routingTypes.indexOf(routingType) !== -1 && - qp.name in query && - (typeof qp.applicable !== 'function' || qp.applicable(query, config)) - }) - .forEach(qp => { - // Translate the applicable parameters according to their rewrite - // functions (if provided) - const rewriteFunction = isItinerary - ? qp.itineraryRewrite - : qp.profileRewrite - params = Object.assign( - params, - rewriteFunction - ? rewriteFunction(query[qp.name]) - : { [qp.name]: query[qp.name] } - ) - }) +/** + * Set additional parameters for queries using the various CAR modes. This method + * directly modifies the input params. + */ +function addCarParams (params) { + if (params.mode?.includes('CAR_HAIL') || params.mode?.includes('CAR_RENT')) { + params.minTransitDistance = '50%' + // increase search timeout because these queries can take a while + params.searchTimeout = 10000 + } + // set onlyTransitTrips for car rental searches + if (params.mode?.includes('CAR_RENT')) { + params.onlyTransitTrips = true + } + // hack to add walking to driving/TNC trips + if (hasCar(params.mode)) { + params.mode += ',WALK' + } +} - // Additional processing specific to ITINERARY mode +/** + * Provides additional processing of the query params depending on the routing + * type and if realtime updates are ignored. This method directly modifies the + * input params. + */ +function processParamsForRoutingType (params, isItinerary, ignoreRealtimeUpdates) { if (isItinerary) { // override ignoreRealtimeUpdates if provided if (typeof ignoreRealtimeUpdates === 'boolean') { @@ -281,24 +281,9 @@ export function getRoutingParams (query, config, ignoreRealtimeUpdates) { delete params.time delete params.date } - - // temp: set additional parameters for CAR_HAIL or CAR_RENT trips - if ( - params.mode && - (params.mode.includes('CAR_HAIL') || params.mode.includes('CAR_RENT')) - ) { - params.minTransitDistance = '50%' - // increase search timeout because these queries can take a while - params.searchTimeout = 10000 - } - - // set onlyTransitTrips for car rental searches - if (params.mode && params.mode.includes('CAR_RENT')) { - params.onlyTransitTrips = true - } - - // Additional processing specific to PROFILE mode + addCarParams(params) } else { + // Additional processing specific to PROFILE mode // check start and end time validity; ignore both if either is invalid const startTimeValid = moment(params.startTime, OTP_API_TIME_FORMAT).isValid() const endTimeValid = moment(params.endTime, OTP_API_TIME_FORMAT).isValid() @@ -308,14 +293,49 @@ export function getRoutingParams (query, config, ignoreRealtimeUpdates) { delete params.endTimeValid } } +} - // TODO: check that valid from/to locations are provided +function paramIsDefined (query, key) { + return key in query && query[key] !== '' && query[key] !== null +} - // hack to add walking to driving/TNC trips - if (hasCar(params.mode)) { - params.mode += ',WALK' - } +/** + * A given parameter is included in the request if all of the following: + * 1. Must apply to the active routing type (ITINERARY or PROFILE) + * 2. Must be included in the current user-defined query and not an empty + * string nor null. + * 3. Must pass the parameter's applicability test, if one is specified + */ +function filterParam (qp, query, config) { + return qp.routingTypes.indexOf(query.routingType) !== -1 && + paramIsDefined(query, qp.name) && + (typeof qp.applicable !== 'function' || qp.applicable(query, config)) +} + +/** + * Converts a query object to valid routing params for an OTP request. + */ +export function getRoutingParams (query, config, ignoreRealtimeUpdates) { + const isItinerary = isItineraryQuery(query) + let params = {} + // Start with the universe of OTP parameters defined in query-params.js: + queryParams + .filter(qp => filterParam(qp, query, config)) + .forEach(qp => { + // Translate the applicable parameters according to their rewrite + // functions (if provided) + const rewriteFunction = isItinerary + ? qp.itineraryRewrite + : qp.profileRewrite + params = Object.assign( + params, + rewriteFunction + ? rewriteFunction(query[qp.name]) + : { [qp.name]: query[qp.name] } + ) + }) + processParamsForRoutingType(params, isItinerary, ignoreRealtimeUpdates) return params } From be7a1bcaf463472632119511f274076a8f73dc3f Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 25 Jun 2021 11:23:22 -0400 Subject: [PATCH 51/89] refactor(plan-first-last): fix time offset --- .../narrative/plan-first-last-buttons.js | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js index a2148340e..607d89efe 100644 --- a/lib/components/narrative/plan-first-last-buttons.js +++ b/lib/components/narrative/plan-first-last-buttons.js @@ -1,4 +1,5 @@ import {OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT} from '@opentripplanner/core-utils/lib/time' +import {getTimeZoneOffset} from '@opentripplanner/core-utils/lib/itinerary' import moment from 'moment' import React, {Component} from 'react' import {Button} from 'react-bootstrap' @@ -14,10 +15,19 @@ import { getVisibleItineraryIndex } from '../../util/state' -const serviceBreakTime = '03:00am' +const serviceBreakTime = '3:00am' const NINETY_SECONDS = 90000 class PlanFirstLastButtons extends Component { + _getOffsetTime = (unixTime) => { + let offset = 0 + const itinerary = this.props.itineraries[this.props.activeItinerary] + if (itinerary) { + offset = getTimeZoneOffset(itinerary) + } + return moment(unixTime + offset) + } + _planFirst = () => { const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props const itinerary = itineraries[activeItinerary] @@ -35,12 +45,10 @@ class PlanFirstLastButtons extends Component { _planPrevious = () => { const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props const itinerary = itineraries[activeItinerary] - const newEndTime = moment(itinerary.endTime - NINETY_SECONDS) - const time = newEndTime.format(OTP_API_TIME_FORMAT) - console.log(activeItinerary, itinerary, itinerary.endTime, time) + const newEndTime = this._getOffsetTime(itinerary.endTime - NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), - time, + time: newEndTime.format(OTP_API_TIME_FORMAT), date: newEndTime.format(OTP_API_DATE_FORMAT), departArrive: 'ARRIVE', originalQueryTime: currentQuery.time @@ -52,12 +60,10 @@ class PlanFirstLastButtons extends Component { _planNext = () => { const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props const itinerary = itineraries[activeItinerary] - const newStartTime = moment(itinerary.startTime + NINETY_SECONDS) - const time = newStartTime.format(OTP_API_TIME_FORMAT) - console.log(time) + const newStartTime = this._getOffsetTime(itinerary.startTime + NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), - time, + time: newStartTime.format(OTP_API_TIME_FORMAT), date: newStartTime.format(OTP_API_DATE_FORMAT), departArrive: 'DEPART', originalQueryTime: currentQuery.time @@ -81,9 +87,15 @@ class PlanFirstLastButtons extends Component { } render () { - console.log(moment((this.props.itineraries[this.props.activeItinerary] || {}).endTime).format(OTP_API_TIME_FORMAT)) + const containerStyle = { + alignItems: 'stretch', + display: 'flex', + flexDirection: 'row', + marginTop: '5px', + width: '100%' + } return ( -
    +
    From 71802b7940f0c1f49cedf449f2c857924244cb77 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 25 Jun 2021 14:21:36 -0400 Subject: [PATCH 52/89] refactor: update field-trip-list.js with React#useMemo Co-authored-by: Evan Siroky --- lib/components/admin/field-trip-list.js | 42 +++++++++++-------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index 220a5dbd9..910c35531 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -159,30 +159,26 @@ class FieldTripList extends Component { } } -class Tab extends Component { - getCount = memoize((data, filter) => data.filter(filter).length) - - render () { - const {data, filter, onClick, tab} = this.props - const active = tab.id === filter.tab - const style = { - backgroundColor: active ? 'navy' : undefined, - borderRadius: 5, - color: active ? 'white' : undefined, - padding: '2px 3px' - } - return ( - - ) +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 { From 28b6721ad42f944a7bcc8c58f19ecabd0ff8ba2b Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 25 Jun 2021 14:36:40 -0400 Subject: [PATCH 53/89] refactor(mailables): update LETTER_FIELDS jsdoc --- lib/util/mailables.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/util/mailables.js b/lib/util/mailables.js index 2c185c90e..324da5b73 100644 --- a/lib/util/mailables.js +++ b/lib/util/mailables.js @@ -3,6 +3,10 @@ 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'}, From f9c514cd2a18323716635ffa920ab214159e7556 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Fri, 25 Jun 2021 11:46:41 -0700 Subject: [PATCH 54/89] refactor(mailable): better name css-in-js variable --- lib/components/admin/styled.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/admin/styled.js b/lib/components/admin/styled.js index 45ff35aed..c1d7c66f1 100644 --- a/lib/components/admin/styled.js +++ b/lib/components/admin/styled.js @@ -163,7 +163,7 @@ export const MailablesList = styled.div` overflow-y: scroll; ` -export const MailableItem = css` +const mailableItemCss = css` background-color: #eaeaea; display: block; margin: 0px 0px 2px 0px; @@ -173,11 +173,11 @@ export const MailableItem = css` ` export const SelectableMailableButton = styled.button` - ${MailableItem} + ${mailableItemCss} ` export const SelectedMailableContainer = styled.div` - ${MailableItem} + ${mailableItemCss} ` export const SelectedMailableOptionsContainer = styled.div` From fa5dbec6aa11d3575572ee5a47a544ae6c07fb60 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 25 Jun 2021 15:54:54 -0400 Subject: [PATCH 55/89] fix(calltaker): Reinstate searchId _CALL suffix for previewing past calls. --- lib/actions/form.js | 12 +++++++++--- lib/components/admin/query-record.js | 3 ++- lib/reducers/call-taker.js | 12 ++++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/actions/form.js b/lib/actions/form.js index 077e85ae3..53440594e 100644 --- a/lib/actions/form.js +++ b/lib/actions/form.js @@ -5,6 +5,8 @@ import moment from 'moment' import qs from 'qs' import { createAction } from 'redux-actions' +import { queryIsValid } from '../util/state' + import { routingQuery } from './api' import { setLocation } from './map' import { @@ -13,7 +15,6 @@ import { setMainPanelContent, setMobileScreen } from './ui' -import { queryIsValid } from '../util/state' const { getDefaultQuery, @@ -87,15 +88,20 @@ export function setQueryParam (payload, searchId) { * performed according to the behavior of setQueryParam (i.e., if the passed * searchId is not null). * @param {Object} params an object containing URL query params + * @param {string} source optionally contains the source of the query params + * (e.g., _CALL indicates that the source is from a past + * query made during a call taker session, to prevent + * a search from being added to the current call) */ -export function parseUrlQueryString (params = getUrlParams()) { +export function parseUrlQueryString (params = getUrlParams(), source) { return function (dispatch, getState) { // Filter out the OTP (i.e. non-UI) params and set the initial query const planParams = {} Object.keys(params).forEach(key => { if (!key.startsWith('ui_')) planParams[key] = params[key] }) - const searchId = params.ui_activeSearch || coreUtils.storage.randId() + let searchId = params.ui_activeSearch || coreUtils.storage.randId() + if (source) searchId += source // Convert strings to numbers/objects and dispatch planParamsToQueryAsync(planParams, getState().otp.config) .then(query => dispatch(setQueryParam(query, searchId))) diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index d8fd28c36..b5154f516 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -5,6 +5,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import * as formActions from '../../actions/form' + import {CallRecordButton, CallRecordIcon} from './styled' /** @@ -20,7 +21,7 @@ class QueryRecordLayout extends Component { const {parseUrlQueryString} = this.props const params = this._getParams() params.departArrive = params.arriveBy ? 'ARRIVE' : 'DEPART' - parseUrlQueryString(params) + parseUrlQueryString(params, '_CALL') } render () { diff --git a/lib/reducers/call-taker.js b/lib/reducers/call-taker.js index 4fa3849b7..635d85dc5 100644 --- a/lib/reducers/call-taker.js +++ b/lib/reducers/call-taker.js @@ -107,11 +107,15 @@ function createCallTakerReducer (config) { const {searchId} = action.payload if (state.activeCall) { // If call is in progress, record search ID when a routing response is - // fulfilled. + // 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. - return update(state, { - activeCall: { searches: { $push: [searchId] } } - }) + if (!(state.callHistory.visible && searchId.indexOf('_CALL') !== -1)) { + return update(state, { + activeCall: { searches: { $push: [searchId] } } + }) + } } // Otherwise, ignore. return state From ae0daa46b2698d4ef127fad6dd4c26fa160f6fcd Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Mon, 28 Jun 2021 10:45:50 -0400 Subject: [PATCH 56/89] refactor(Remove DEFAULT itinerary view): --- lib/actions/api.js | 2 +- lib/actions/ui.js | 8 +++----- lib/components/mobile/batch-results-screen.js | 2 +- lib/components/narrative/narrative-itineraries.js | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/actions/api.js b/lib/actions/api.js index 820ba1564..fa910d897 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -95,7 +95,7 @@ export function routingQuery (searchId = null) { return } // Reset itinerary view to default (list view). - dispatch(setItineraryView(ItineraryView.DEFAULT)) + dispatch(setItineraryView(ItineraryView.LIST)) const activeItinerary = getActiveItinerary(otpState) const routingType = otpState.currentQuery.routingType // For multiple mode combinations, gather injected params from config/query. diff --git a/lib/actions/ui.js b/lib/actions/ui.js index fc6206b9a..a0809b4c0 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.js @@ -240,8 +240,6 @@ export const MobileScreens = { * (currently only used in batch results). */ export const ItineraryView = { - /** FIXME: Duplicate value for 'list', DEFAULT and LIST */ - DEFAULT: 'list', /** One itinerary is shown. (In mobile view, the map is hidden.) */ FULL: 'full', /** One itinerary is shown, itinerary and map are focused on a leg. (The mobile view is split.) */ @@ -263,13 +261,13 @@ const setPreviousItineraryView = createAction('SET_PREVIOUS_ITINERARY_VIEW') export function setItineraryView (value) { return function (dispatch, getState) { const urlParams = coreUtils.query.getUrlParams() - const prevItineraryView = urlParams.ui_itineraryView || ItineraryView.DEFAULT + const prevItineraryView = urlParams.ui_itineraryView || ItineraryView.LIST // If the itinerary value is changed, // set the desired ui query param, or remove it if same as default, // and store the current view as previousItineraryView. if (value !== urlParams.ui_itineraryView) { - if (value !== ItineraryView.DEFAULT) { + if (value !== ItineraryView.LIST) { urlParams.ui_itineraryView = value } else if (urlParams.ui_itineraryView) { delete urlParams.ui_itineraryView @@ -288,7 +286,7 @@ export function setItineraryView (value) { export function toggleBatchResultsMap () { return function (dispatch, getState) { const urlParams = coreUtils.query.getUrlParams() - const itineraryView = urlParams.ui_itineraryView || ItineraryView.DEFAULT + const itineraryView = urlParams.ui_itineraryView || ItineraryView.LIST if (itineraryView === ItineraryView.LEG) { dispatch(setItineraryView(ItineraryView.LEG_HIDDEN)) diff --git a/lib/components/mobile/batch-results-screen.js b/lib/components/mobile/batch-results-screen.js index 51370a897..04b84b5d8 100644 --- a/lib/components/mobile/batch-results-screen.js +++ b/lib/components/mobile/batch-results-screen.js @@ -135,7 +135,7 @@ const mapStateToProps = (state, ownProps) => { activeLeg: activeSearch ? activeSearch.activeLeg : null, errors: getResponsesWithErrors(state.otp), itineraries: getActiveItineraries(state.otp), - itineraryView: urlParams.ui_itineraryView || ItineraryView.DEFAULT + itineraryView: urlParams.ui_itineraryView || AULT } } diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 3f0283811..f45af3e74 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -220,7 +220,7 @@ const mapStateToProps = (state, ownProps) => { const realtimeEffects = getRealtimeEffects(state.otp) const useRealtime = state.otp.useRealtime const urlParams = coreUtils.query.getUrlParams() - const itineraryView = urlParams.ui_itineraryView || ItineraryView.DEFAULT + const itineraryView = urlParams.ui_itineraryView || ItineraryView.LIST const showDetails = itineraryView === ItineraryView.FULL || itineraryView === ItineraryView.LEG || itineraryView === ItineraryView.LEG_HIDDEN From 0876bd91daabe4647879ac9198e019eb254448b7 Mon Sep 17 00:00:00 2001 From: Phil Cline Date: Mon, 28 Jun 2021 10:56:31 -0400 Subject: [PATCH 57/89] refactor(Fix typos): --- lib/components/mobile/batch-results-screen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/mobile/batch-results-screen.js b/lib/components/mobile/batch-results-screen.js index 04b84b5d8..6a7097139 100644 --- a/lib/components/mobile/batch-results-screen.js +++ b/lib/components/mobile/batch-results-screen.js @@ -135,7 +135,7 @@ const mapStateToProps = (state, ownProps) => { activeLeg: activeSearch ? activeSearch.activeLeg : null, errors: getResponsesWithErrors(state.otp), itineraries: getActiveItineraries(state.otp), - itineraryView: urlParams.ui_itineraryView || AULT + itineraryView: urlParams.ui_itineraryView || ItineraryView.LIST } } From d9c780d885b7b4492ff36ac2a4277ed8aa92b92a Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 28 Jun 2021 13:26:27 -0400 Subject: [PATCH 58/89] refactor: remove unused import --- lib/components/admin/field-trip-list.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index 910c35531..2bf1c3d5b 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -1,4 +1,3 @@ -import memoize from 'lodash.memoize' import moment from 'moment' import qs from 'qs' import React, { Component } from 'react' From bbc7434214542818842ffa769cebd7459fb49d2a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 28 Jun 2021 16:26:21 -0400 Subject: [PATCH 59/89] fix(actions/call-taker): Wait for call query to save before refetching them. --- lib/actions/call-taker.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 61296b466..88c9a6ee1 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -3,12 +3,13 @@ import { serialize } from 'object-to-formdata' import qs from 'qs' import { createAction } from 'redux-actions' -import {toggleFieldTrips} from './field-trip' -import {resetForm} from './form' import {searchToQuery, sessionIsInvalid} from '../util/call-taker' import {URL_ROOT} from '../util/constants' import {getTimestamp} from '../util/state' +import {resetForm} from './form' +import {toggleFieldTrips} from './field-trip' + if (typeof (fetch) === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -56,7 +57,7 @@ export function beginCallIfNeeded () { * End the active call and store the queries made during the call. */ export function endCall () { - return function (dispatch, getState) { + return async function (dispatch, getState) { const {callTaker, otp} = getState() const {activeCall, session} = callTaker const { sessionId } = session @@ -73,9 +74,10 @@ export function endCall () { {method: 'POST', body: callData} ) .then(res => res.json()) - .then(id => { + .then(async id => { // Inject call ID into active call and save queries. - dispatch(saveQueriesForCall({...activeCall, id})) + await dispatch(saveQueriesForCall({...activeCall, id})) + // Wait until query was saved before re-fetching queries for this call. dispatch(fetchCalls()) }) .catch(err => { From 48a0718bfb93cdcbf804b991295cd0cfff3048e2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 28 Jun 2021 17:05:58 -0400 Subject: [PATCH 60/89] fix(QueryRecord): Convert arriveBy value to string for OTP-UI. --- lib/components/admin/query-record.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index b5154f516..ec6779147 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -14,13 +14,21 @@ import {CallRecordButton, CallRecordIcon} from './styled' class QueryRecordLayout extends Component { _getParams = () => { const {query} = this.props - return planParamsToQuery(JSON.parse(query.queryParams)) + const originalQueryParams = JSON.parse( + query.queryParams, + // convert boolean for the 'arriveBy' field to strings + // per https://github.com/opentripplanner/otp-ui/blob/master/packages/core-utils/src/query.js#L285 + // FIXME: move this elsewhere? + (key, value) => key === 'arriveBy' + ? value.toString() + : value // return everything else unchanged + ) + return planParamsToQuery(originalQueryParams) } _viewQuery = () => { const {parseUrlQueryString} = this.props const params = this._getParams() - params.departArrive = params.arriveBy ? 'ARRIVE' : 'DEPART' parseUrlQueryString(params, '_CALL') } From 72cd7de52e3bd5e38606f7fb7ffd3210ef034d3f Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 28 Jun 2021 17:40:18 -0400 Subject: [PATCH 61/89] refactor(field-trip): fix report url --- lib/components/admin/field-trip-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js index 2bf1c3d5b..356834eb3 100644 --- a/lib/components/admin/field-trip-list.js +++ b/lib/components/admin/field-trip-list.js @@ -44,7 +44,7 @@ class FieldTripList extends Component { const {date} = this.state const [year, month, day] = date.split('-') const params = {month, day, year} - return `${datastoreUrl}/fieldtrip/opsReport?month=${qs.stringify(params)}` + return `${datastoreUrl}/fieldtrip/opsReport?${qs.stringify(params)}` } /** From a2cfb7341f39d940744a3c02d4c637193df6eb97 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 11:25:25 -0400 Subject: [PATCH 62/89] refactor(util/call-taker): Add improved JSON param parsing. --- lib/actions/field-trip.js | 5 +++-- lib/components/admin/query-record.js | 17 +++-------------- lib/util/call-taker.js | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 50ea0c8be..bd1b39f5f 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -5,10 +5,11 @@ import { serialize } from 'object-to-formdata' import qs from 'qs' import { createAction } from 'redux-actions' +import {getGroupSize, getTripFromRequest, parseQueryParams, sessionIsInvalid} from '../util/call-taker' + import {routingQuery} from './api' import {toggleCallHistory} from './call-taker' import {resetForm, setQueryParam} from './form' -import {getGroupSize, getTripFromRequest, sessionIsInvalid} from '../util/call-taker' if (typeof (fetch) === 'undefined') require('isomorphic-fetch') @@ -214,7 +215,7 @@ export function planTrip (request, outbound) { else dispatch(planInbound(request)) } else { // Populate params from saved query params - const params = await planParamsToQueryAsync(JSON.parse(trip.queryParams)) + const params = await planParamsToQueryAsync(parseQueryParams(trip.queryParams)) dispatch(setQueryParam(params, trip.id)) } } diff --git a/lib/components/admin/query-record.js b/lib/components/admin/query-record.js index ec6779147..81510d0c3 100644 --- a/lib/components/admin/query-record.js +++ b/lib/components/admin/query-record.js @@ -5,26 +5,15 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import * as formActions from '../../actions/form' +import { parseQueryParams } from '../../util/call-taker' -import {CallRecordButton, CallRecordIcon} from './styled' +import { CallRecordButton, CallRecordIcon } from './styled' /** * Displays information for a query stored for the Call Taker module. */ class QueryRecordLayout extends Component { - _getParams = () => { - const {query} = this.props - const originalQueryParams = JSON.parse( - query.queryParams, - // convert boolean for the 'arriveBy' field to strings - // per https://github.com/opentripplanner/otp-ui/blob/master/packages/core-utils/src/query.js#L285 - // FIXME: move this elsewhere? - (key, value) => key === 'arriveBy' - ? value.toString() - : value // return everything else unchanged - ) - return planParamsToQuery(originalQueryParams) - } + _getParams = () => planParamsToQuery(parseQueryParams(this.props.query.queryParams)) _viewQuery = () => { const {parseUrlQueryString} = this.props diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index f3b378f99..dcc155567 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -3,6 +3,7 @@ import {randId} from '@opentripplanner/core-utils/lib/storage' import moment from 'moment' import {getRoutingParams} from '../actions/api' + import {getTimestamp} from './state' export const TICKET_TYPES = { @@ -269,3 +270,19 @@ function createItinerary (itinData, tripPlan) { }) return itin } + +/** + * @param query The query parameters string to convert. + * @returns An object with the parsed query params, + * where the arriveBy boolean is converted to a string + * per https://github.com/opentripplanner/otp-ui/blob/master/packages/core-utils/src/query.js#L285 + */ +export function parseQueryParams (queryParams) { + return JSON.parse( + queryParams, + // convert boolean for the 'arriveBy' field to strings + (key, value) => key === 'arriveBy' + ? value.toString() + : value // return everything else unchanged + ) +} From 789205533d4a85ad0ec30dfe71c19b84f60cd4ae Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 11:51:37 -0400 Subject: [PATCH 63/89] refactor(actions/call-taker): Convert .thens into awaits in endCall. --- lib/actions/call-taker.js | 28 ++++++++++++++-------------- lib/util/call-taker.js | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 88c9a6ee1..7e6f78ef6 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -70,20 +70,20 @@ export function endCall () { endTime: getTimestamp() } }) - fetch(`${otp.config.datastoreUrl}/calltaker/call`, - {method: 'POST', body: callData} - ) - .then(res => res.json()) - .then(async id => { - // Inject call ID into active call and save queries. - await dispatch(saveQueriesForCall({...activeCall, id})) - // Wait until query was saved before re-fetching queries for this call. - dispatch(fetchCalls()) - }) - .catch(err => { - console.error(err) - alert(`Could not save call: ${JSON.stringify(err)}`) - }) + try { + const fetchResult = await fetch(`${otp.config.datastoreUrl}/calltaker/call`, + {method: 'POST', body: callData} + ) + const id = await fetchResult.json() + + // Inject call ID into active call and save queries. + await dispatch(saveQueriesForCall({...activeCall, id})) + // Wait until query was saved before re-fetching queries for this call. + dispatch(fetchCalls()) + } catch (err) { + console.error(err) + alert(`Could not save call: ${JSON.stringify(err)}`) + } // Clear itineraries shown when ending call. dispatch(resetForm(true)) dispatch(endingCall()) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index dcc155567..9659f6534 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -272,7 +272,7 @@ function createItinerary (itinData, tripPlan) { } /** - * @param query The query parameters string to convert. + * @param queryParams The query parameters string to convert. * @returns An object with the parsed query params, * where the arriveBy boolean is converted to a string * per https://github.com/opentripplanner/otp-ui/blob/master/packages/core-utils/src/query.js#L285 From a317e28342bd133db3c9975c4072aa6c4cea4e45 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:10:35 -0400 Subject: [PATCH 64/89] Update lib/util/call-taker.js Co-authored-by: Landon Reed --- lib/util/call-taker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index 9659f6534..cf8b169b5 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -272,7 +272,7 @@ function createItinerary (itinData, tripPlan) { } /** - * @param queryParams The query parameters string to convert. + * @param queryParams The query parameters string to convert (stringified JSON). * @returns An object with the parsed query params, * where the arriveBy boolean is converted to a string * per https://github.com/opentripplanner/otp-ui/blob/master/packages/core-utils/src/query.js#L285 From 0a4379366e37933080a79ec08e72a45a4822f25c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 15:28:52 -0400 Subject: [PATCH 65/89] refactor(calltaker): Make fetchCalls async, change CallRecord key prop. --- lib/actions/call-taker.js | 17 ++++++++++------- lib/components/admin/call-taker-windows.js | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 7e6f78ef6..9fa2d3e1a 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -79,7 +79,7 @@ export function endCall () { // Inject call ID into active call and save queries. await dispatch(saveQueriesForCall({...activeCall, id})) // Wait until query was saved before re-fetching queries for this call. - dispatch(fetchCalls()) + await dispatch(fetchCalls()) } catch (err) { console.error(err) alert(`Could not save call: ${JSON.stringify(err)}`) @@ -151,17 +151,20 @@ function checkSession (datastoreUrl, sessionId) { * Fetch latest calls for a particular session. */ export function fetchCalls () { - return function (dispatch, getState) { + return async function (dispatch, getState) { dispatch(requestingCalls()) const {callTaker, otp} = getState() if (sessionIsInvalid(callTaker.session)) return const {datastoreUrl} = otp.config const {sessionId} = callTaker.session - const limit = 30 - fetch(`${datastoreUrl}/calltaker/call?${qs.stringify({limit, sessionId})}`) - .then(res => res.json()) - .then(calls => dispatch(receivedCalls({calls}))) - .catch(err => alert(`Error fetching calls: ${JSON.stringify(err)}`)) + const limit = 200 + try { + const fetchResult = await fetch(`${datastoreUrl}/calltaker/call?${qs.stringify({limit, sessionId})}`) + const calls = await fetchResult.json() + dispatch(receivedCalls({calls})) + } catch (err) { + alert(`Error fetching calls: ${JSON.stringify(err)}`) + } } } diff --git a/lib/components/admin/call-taker-windows.js b/lib/components/admin/call-taker-windows.js index 56e0ed712..4e60e3427 100644 --- a/lib/components/admin/call-taker-windows.js +++ b/lib/components/admin/call-taker-windows.js @@ -2,9 +2,10 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' +import Icon from '../narrative/icon' + import CallRecord from './call-record' import DraggableWindow from './draggable-window' -import Icon from '../narrative/icon' import {WindowHeader} from './styled' /** @@ -32,7 +33,9 @@ class CallTakerWindows extends Component { {callHistory.calls.data.length > 0 ? callHistory.calls.data.map((call, i) => ( From d0a9b8b8a6f6ef69677a6407565ef5aa6a7cfdd2 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 29 Jun 2021 15:38:11 -0400 Subject: [PATCH 66/89] refactor(plan-first-last): finalize buttons; use styled --- example-config.yml | 5 +- lib/actions/call-taker.js | 7 +- .../form/call-taker/date-time-options.js | 23 ++++-- .../narrative/narrative-itineraries.js | 7 +- .../narrative/plan-first-last-buttons.js | 81 ++++++++++--------- lib/components/narrative/styled.js | 11 +++ 6 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 lib/components/narrative/styled.js diff --git a/example-config.yml b/example-config.yml index 6aadbe494..f3e4f87da 100644 --- a/example-config.yml +++ b/example-config.yml @@ -76,7 +76,7 @@ map: ### in the GTFS feed for those routes, or that a getTransitiveRouteLabel function is defined ### in the ComponentContext (see example.js for more). ### - styles.labels, - ### styles.segment_labels: styles attributes recognized by transitive.js. + ### styles.segment_labels: styles attributes recognized by transitive.js. ### For examples of applicable style attributes, see ### https://github.com/conveyal/transitive.js/blob/master/stories/Transitive.stories.js#L47. # transitive: @@ -170,6 +170,9 @@ itinerary: # Show fares for each transit leg (false if omitted). # (Requires using LineItinerary.) showRouteFares: false + # Whether the plan first/previous/next/last buttons should be shown along with + # plan trip itineraries. + showPlanFirstLastButtons: false # The transitOperators key is a list of transit operators that can be used to # order transit agencies when sorting by route. Also, this can optionally diff --git a/lib/actions/call-taker.js b/lib/actions/call-taker.js index 61296b466..0ae927451 100644 --- a/lib/actions/call-taker.js +++ b/lib/actions/call-taker.js @@ -3,12 +3,13 @@ import { serialize } from 'object-to-formdata' import qs from 'qs' import { createAction } from 'redux-actions' -import {toggleFieldTrips} from './field-trip' -import {resetForm} from './form' import {searchToQuery, sessionIsInvalid} from '../util/call-taker' import {URL_ROOT} from '../util/constants' import {getTimestamp} from '../util/state' +import {resetForm} from './form' +import {toggleFieldTrips} from './field-trip' + if (typeof (fetch) === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS @@ -155,7 +156,7 @@ export function fetchCalls () { if (sessionIsInvalid(callTaker.session)) return const {datastoreUrl} = otp.config const {sessionId} = callTaker.session - const limit = 30 + const limit = 200 fetch(`${datastoreUrl}/calltaker/call?${qs.stringify({limit, sessionId})}`) .then(res => res.json()) .then(calls => dispatch(receivedCalls({calls}))) diff --git a/lib/components/form/call-taker/date-time-options.js b/lib/components/form/call-taker/date-time-options.js index feb48b1e2..5f24cff1d 100644 --- a/lib/components/form/call-taker/date-time-options.js +++ b/lib/components/form/call-taker/date-time-options.js @@ -80,7 +80,15 @@ export default class DateTimeOptions extends Component { } componentDidUpdate (prevProps) { - const {departArrive} = this.props + const {date, departArrive, time} = this.props + const dateTime = this.dateTimeAsMoment() + 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)) { + this._updateTimeInput(dateTime) + } // If departArrive has been changed to leave now, begin auto refresh. if (departArrive !== prevProps.departArrive) { if (departArrive === 'NOW') this._startAutoRefresh() @@ -151,19 +159,24 @@ export default class DateTimeOptions extends Component { */ handleTimeFocus = evt => evt.target.select() + parseInputAsTime = (timeInput, date = moment().startOf('day').format('YYYY-MM-DD')) => { + return moment(date + 'T' + timeInput, SUPPORTED_TIME_FORMATS) + } + + dateTimeAsMoment = () => moment(`${this.props.date}T${this.props.time}`) + handleTimeChange = evt => { if (this.state.timer) this._stopAutoRefresh() const timeInput = evt.target.value - const date = moment().startOf('day').format('YYYY-MM-DD') - const parsedTime = moment(date + 'T' + timeInput, SUPPORTED_TIME_FORMATS) + const parsedTime = this.parseInputAsTime(timeInput) this.handleDateTimeChange({ time: parsedTime.format(OTP_API_TIME_FORMAT) }) this.setState({timeInput}) } render () { - const {date, departArrive, onKeyDown, time, timeFormat} = this.props + const {departArrive, onKeyDown, timeFormat} = this.props const {timeInput} = this.state - const dateTime = moment(`${date} ${time}`) + const dateTime = this.dateTimeAsMoment() const cleanTime = dateTime.format(timeFormat) return ( <> diff --git a/lib/components/narrative/narrative-itineraries.js b/lib/components/narrative/narrative-itineraries.js index 8343d7a8a..cc700e054 100644 --- a/lib/components/narrative/narrative-itineraries.js +++ b/lib/components/narrative/narrative-itineraries.js @@ -13,8 +13,6 @@ import { updateItineraryFilter } from '../../actions/narrative' import * as uiActions from '../../actions/ui' -import NarrativeItinerariesErrors from './narrative-itineraries-errors' -import NarrativeItinerariesHeader from './narrative-itineraries-header' import { ComponentContext } from '../../util/contexts' import { getActiveItineraries, @@ -24,6 +22,9 @@ import { getVisibleItineraryIndex } from '../../util/state' +import NarrativeItinerariesErrors from './narrative-itineraries-errors' +import NarrativeItinerariesHeader from './narrative-itineraries-header' + const { ItineraryView } = uiActions class NarrativeItineraries extends Component { @@ -165,7 +166,7 @@ class NarrativeItineraries extends Component { overflowY: 'auto' }} > - {showingErrors + {showingErrors || itineraries.length === 0 ? ( { + const {routingQuery, setQueryParam} = this.props + setQueryParam(params) + routingQuery() + } + _getOffsetTime = (unixTime) => { let offset = 0 const itinerary = this.props.itineraries[this.props.activeItinerary] @@ -29,86 +37,84 @@ class PlanFirstLastButtons extends Component { } _planFirst = () => { - const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const {activeItinerary, currentQuery, itineraries} = this.props const itinerary = itineraries[activeItinerary] + const date = moment(currentQuery.date) + // If already planning for the "first" trip, subtract a day to mirror the + // behavior of _planLast. + if (this._isPlanningFirst()) date.subtract('days', 1) const params = { startTransitStopId: getFirstStopId(itinerary), - date: moment(currentQuery.date).format(OTP_API_DATE_FORMAT), - time: serviceBreakTime, - departArrive: 'DEPART', - originalQueryTime: currentQuery.time + date: date.format(OTP_API_DATE_FORMAT), + time: SERVICE_BREAK, + departArrive: 'DEPART' } - setQueryParam(params) - routingQuery() + this._updateParamsAndPlan(params) + } + + _isPlanningFirst = () => { + const {departArrive, time} = this.props.currentQuery + return departArrive === 'DEPART' && time === SERVICE_BREAK } _planPrevious = () => { - const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const {activeItinerary, itineraries} = this.props const itinerary = itineraries[activeItinerary] const newEndTime = this._getOffsetTime(itinerary.endTime - NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), time: newEndTime.format(OTP_API_TIME_FORMAT), date: newEndTime.format(OTP_API_DATE_FORMAT), - departArrive: 'ARRIVE', - originalQueryTime: currentQuery.time + departArrive: 'ARRIVE' } - setQueryParam(params) - routingQuery() + this._updateParamsAndPlan(params) } _planNext = () => { - const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const {activeItinerary, itineraries} = this.props const itinerary = itineraries[activeItinerary] const newStartTime = this._getOffsetTime(itinerary.startTime + NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), time: newStartTime.format(OTP_API_TIME_FORMAT), date: newStartTime.format(OTP_API_DATE_FORMAT), - departArrive: 'DEPART', - originalQueryTime: currentQuery.time + departArrive: 'DEPART' } - setQueryParam(params) - routingQuery() + this._updateParamsAndPlan(params) } _planLast = () => { - const {activeItinerary, currentQuery, itineraries, routingQuery, setQueryParam} = this.props + const {activeItinerary, currentQuery, itineraries} = this.props const itinerary = itineraries[activeItinerary] const params = { startTransitStopId: getFirstStopId(itinerary), - date: moment(currentQuery.date).format(OTP_API_DATE_FORMAT), - time: serviceBreakTime, - departArrive: 'ARRIVE', - originalQueryTime: currentQuery.time + date: moment(currentQuery.date).add('days', 1).format(OTP_API_DATE_FORMAT), + time: SERVICE_BREAK, + departArrive: 'ARRIVE' } - setQueryParam(params) - routingQuery() + this._updateParamsAndPlan(params) } render () { - const containerStyle = { - alignItems: 'stretch', - display: 'flex', - flexDirection: 'row', - marginTop: '5px', - width: '100%' + const {enabled, itineraries} = this.props + if (!enabled || itineraries.length === 0) { + return null } return ( -
    - - - - -
    + ) } } @@ -128,6 +134,7 @@ const mapStateToProps = (state, ownProps) => { activeSearch, activeStep: activeSearch && activeSearch.activeStep, currentQuery: state.otp.currentQuery, + enabled: state.otp.config.itinerary?.showPlanFirstLastButtons, errors: getResponsesWithErrors(state.otp), errorMessages, itineraries, diff --git a/lib/components/narrative/styled.js b/lib/components/narrative/styled.js new file mode 100644 index 000000000..4f2a2844c --- /dev/null +++ b/lib/components/narrative/styled.js @@ -0,0 +1,11 @@ +import {ButtonGroup} from 'react-bootstrap' +import styled from 'styled-components' + +export const FlexButtonGroup = styled(ButtonGroup)` + display: flex; + flex: 1; + margin-top: 5px; + button { + flex: 1; + } +` From 7fd9672ccdb7e5f40d29816060418475f43c25fd Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 29 Jun 2021 17:54:37 -0400 Subject: [PATCH 67/89] refactor(plan-first-last): address PR comments --- lib/actions/plan.js | 110 ++++++++++++ .../narrative/plan-first-last-buttons.js | 169 +++--------------- 2 files changed, 139 insertions(+), 140 deletions(-) create mode 100644 lib/actions/plan.js diff --git a/lib/actions/plan.js b/lib/actions/plan.js new file mode 100644 index 000000000..82d6bca4d --- /dev/null +++ b/lib/actions/plan.js @@ -0,0 +1,110 @@ +import {OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT} from '@opentripplanner/core-utils/lib/time' +import {getTimeZoneOffset} from '@opentripplanner/core-utils/lib/itinerary' +import moment from 'moment' + +import {getFirstStopId} from '../util/itinerary' +import {getActiveItinerary} from '../util/state' + +import {routingQuery} from './api' +import {setQueryParam} from './form' + +const SERVICE_BREAK = '03:00' +const NINETY_SECONDS = 90000 + +function updateParamsAndPlan (params) { + return function (dispatch, getState) { + dispatch(setQueryParam(params)) + dispatch(routingQuery()) + } +} + +function offsetTime (itinerary, unixTime) { + let offset = 0 + if (itinerary) offset = getTimeZoneOffset(itinerary) + return moment(unixTime + offset) +} + +function isPlanningFirst (query) { + const {departArrive, time} = query + return departArrive === 'DEPART' && time === SERVICE_BREAK +} + +/** + * 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 function (dispatch, getState) { + const state = getState() + const itinerary = getActiveItinerary(state.otp) + const currentQuery = state.otp.currentQuery + 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 params = { + startTransitStopId: getFirstStopId(itinerary), + date: date.format(OTP_API_DATE_FORMAT), + time: SERVICE_BREAK, + departArrive: 'DEPART' + } + dispatch(updateParamsAndPlan(params)) + } +} + +/** + * Plan the previous trip, setting the arrive by time to the current itinerary's + * end time (minus a small amount). + */ +export function planPrevious () { + return function (dispatch, getState) { + const state = getState() + const itinerary = getActiveItinerary(state.otp) + const newEndTime = offsetTime(itinerary, itinerary.endTime - NINETY_SECONDS) + const params = { + startTransitStopId: getFirstStopId(itinerary), + time: newEndTime.format(OTP_API_TIME_FORMAT), + date: newEndTime.format(OTP_API_DATE_FORMAT), + departArrive: 'ARRIVE' + } + dispatch(updateParamsAndPlan(params)) + } +} + +/** + * Plan the next trip, setting the depart at time to the current itinerary's + * start time (plus a small amount). + */ +export function planNext () { + return function (dispatch, getState) { + const state = getState() + const itinerary = getActiveItinerary(state.otp) + const newStartTime = offsetTime(itinerary, itinerary.startTime + NINETY_SECONDS) + const params = { + startTransitStopId: getFirstStopId(itinerary), + time: newStartTime.format(OTP_API_TIME_FORMAT), + date: newStartTime.format(OTP_API_DATE_FORMAT), + departArrive: 'DEPART' + } + dispatch(updateParamsAndPlan(params)) + } +} + +/** + * 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.otp) + const currentQuery = state.otp.currentQuery + const params = { + startTransitStopId: getFirstStopId(itinerary), + date: moment(currentQuery.date).add('days', 1).format(OTP_API_DATE_FORMAT), + time: SERVICE_BREAK, + departArrive: 'ARRIVE' + } + dispatch(updateParamsAndPlan(params)) + } +} diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js index 5b09107cb..c87466e95 100644 --- a/lib/components/narrative/plan-first-last-buttons.js +++ b/lib/components/narrative/plan-first-last-buttons.js @@ -1,159 +1,48 @@ -import {OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT} from '@opentripplanner/core-utils/lib/time' -import {getTimeZoneOffset} from '@opentripplanner/core-utils/lib/itinerary' -import moment from 'moment' -import React, {Component} from 'react' +import React from 'react' import {Button} from 'react-bootstrap' import {connect} from 'react-redux' -import * as apiActions from '../../actions/api' -import * as formActions from '../../actions/form' -import {getFirstStopId} from '../../util/itinerary' -import { - getActiveItineraries, - getActiveSearch, - getResponsesWithErrors, - getVisibleItineraryIndex -} from '../../util/state' +import * as planActions from '../../actions/plan' +import {getActiveItineraries} from '../../util/state' import {FlexButtonGroup} from './styled' -const SERVICE_BREAK = '03:00' -const NINETY_SECONDS = 90000 - -class PlanFirstLastButtons extends Component { - _updateParamsAndPlan = (params) => { - const {routingQuery, setQueryParam} = this.props - setQueryParam(params) - routingQuery() - } - - _getOffsetTime = (unixTime) => { - let offset = 0 - const itinerary = this.props.itineraries[this.props.activeItinerary] - if (itinerary) { - offset = getTimeZoneOffset(itinerary) - } - return moment(unixTime + offset) - } - - _planFirst = () => { - const {activeItinerary, currentQuery, itineraries} = this.props - const itinerary = itineraries[activeItinerary] - const date = moment(currentQuery.date) - // If already planning for the "first" trip, subtract a day to mirror the - // behavior of _planLast. - if (this._isPlanningFirst()) date.subtract('days', 1) - const params = { - startTransitStopId: getFirstStopId(itinerary), - date: date.format(OTP_API_DATE_FORMAT), - time: SERVICE_BREAK, - departArrive: 'DEPART' - } - this._updateParamsAndPlan(params) - } - - _isPlanningFirst = () => { - const {departArrive, time} = this.props.currentQuery - return departArrive === 'DEPART' && time === SERVICE_BREAK - } - - _planPrevious = () => { - const {activeItinerary, itineraries} = this.props - const itinerary = itineraries[activeItinerary] - const newEndTime = this._getOffsetTime(itinerary.endTime - NINETY_SECONDS) - const params = { - startTransitStopId: getFirstStopId(itinerary), - time: newEndTime.format(OTP_API_TIME_FORMAT), - date: newEndTime.format(OTP_API_DATE_FORMAT), - departArrive: 'ARRIVE' - } - this._updateParamsAndPlan(params) - } - - _planNext = () => { - const {activeItinerary, itineraries} = this.props - const itinerary = itineraries[activeItinerary] - const newStartTime = this._getOffsetTime(itinerary.startTime + NINETY_SECONDS) - const params = { - startTransitStopId: getFirstStopId(itinerary), - time: newStartTime.format(OTP_API_TIME_FORMAT), - date: newStartTime.format(OTP_API_DATE_FORMAT), - departArrive: 'DEPART' - } - this._updateParamsAndPlan(params) - } - - _planLast = () => { - const {activeItinerary, currentQuery, itineraries} = this.props - const itinerary = itineraries[activeItinerary] - const params = { - startTransitStopId: getFirstStopId(itinerary), - date: moment(currentQuery.date).add('days', 1).format(OTP_API_DATE_FORMAT), - time: SERVICE_BREAK, - departArrive: 'ARRIVE' - } - this._updateParamsAndPlan(params) - } - - render () { - const {enabled, itineraries} = this.props - if (!enabled || itineraries.length === 0) { - return null - } - return ( - - - - - - - ) - } +function PlanFirstLastButtons (props) { + const {enabled, itineraries, planFirst, planLast, planNext, planPrevious} = props + if (!enabled || itineraries.length === 0) { + return null + } + return ( + + + + + + + ) } const mapStateToProps = (state, ownProps) => { - const activeSearch = getActiveSearch(state.otp) - const activeItinerary = activeSearch && activeSearch.activeItinerary - const { errorMessages, modes } = state.otp.config - const { sort } = state.otp.filter - const pending = activeSearch ? Boolean(activeSearch.pending) : false const itineraries = getActiveItineraries(state.otp) - return { - // swap out realtime itineraries with non-realtime depending on boolean - activeItinerary, - activeLeg: activeSearch && activeSearch.activeLeg, - activeSearch, - activeStep: activeSearch && activeSearch.activeStep, - currentQuery: state.otp.currentQuery, enabled: state.otp.config.itinerary?.showPlanFirstLastButtons, - errors: getResponsesWithErrors(state.otp), - errorMessages, - itineraries, - // use a key so that the NarrativeItineraries component and its state is - // reset each time a new search is shown - key: state.otp.activeSearchId, - modes, - pending, - sort, - visibleItinerary: getVisibleItineraryIndex(state) + itineraries } } const mapDispatchToProps = { - // beginCallIfNeeded: callTakerActions.beginCallIfNeeded, - // findRoutes: apiActions.findRoutes, - routingQuery: apiActions.routingQuery, - // setGroupSize: fieldTripActions.setGroupSize, - setQueryParam: formActions.setQueryParam + planFirst: planActions.planFirst, + planLast: planActions.planLast, + planNext: planActions.planNext, + planPrevious: planActions.planPrevious } export default connect(mapStateToProps, mapDispatchToProps)(PlanFirstLastButtons) From d24513f87e3a2e46a07b0eb88c6c5531b41f5564 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 18:09:40 -0400 Subject: [PATCH 68/89] feat(field-trip): Add skeleton code for printing field trips. --- lib/components/admin/field-trip-details.js | 28 ++- .../admin/print-field-trip-layout.js | 171 ++++++++++++++++++ lib/components/app/responsive-webapp.js | 5 + 3 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 lib/components/admin/print-field-trip-layout.js diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index 91d2f2af6..e82ebef59 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -6,10 +6,17 @@ import { connect } from 'react-redux' import styled from 'styled-components' import * as fieldTripActions from '../../actions/field-trip' +import Icon from '../narrative/icon' +import { + getGroupSize, + GROUP_FIELDS, + PAYMENT_FIELDS, + TICKET_TYPES +} from '../../util/call-taker' + import DraggableWindow from './draggable-window' import EditableSection from './editable-section' import FieldTripNotes from './field-trip-notes' -import Icon from '../narrative/icon' import { Bold, Button, @@ -25,12 +32,6 @@ import { } from './styled' import TripStatus from './trip-status' import Updatable from './updatable' -import { - getGroupSize, - GROUP_FIELDS, - PAYMENT_FIELDS, - TICKET_TYPES -} from '../../util/call-taker' const WindowHeader = styled(DefaultWindowHeader)` margin-bottom: 0px; @@ -59,7 +60,9 @@ class FieldTripDetails extends Component { } _renderFooter = () => { - const cancelled = this.props.request.status === 'cancelled' + const { request, session } = this.props + const cancelled = request.status === 'cancelled' + const printFieldTripLink = `/#/printFieldTrip/?requestId=${request.id}&sessionId=${session.sessionId}` return (
    Receipt link + + Printable trip plan + + {' '} + +
    +

    Field Trip Plan: {schoolName} to {endLocation}

    +
    +
    +
      +
    • Teacher: {teacherName} ({schoolName}, Grade: {grade})
    • +
    • Teacher Address: {address}
    • +
    • Phone: {phoneNumber} / Fax: {faxNumber}
    • +
    • Email: {emailAddress}
    • +
    • Students Age 7 and Over: {numStudents || 0}
    • +
    • Students Age 6 and Under: {numFreeStudents || 0}
    • +
    • Chaperones: {numChaperones || 0}
    • +
    • Request submitted: {timeStamp}
    • +
    +
    + +
    +

    Outbound Trip (to Destination)

    +

    No Outbound Trip Planned

    + {/* outboundTrip + ? outboundTrip.groupItineraries?.map((groupItin, i) => { + //const itinerary = JSON.parse(groupItin.itinData) + return ( +
    + + +
    + ) + }) + :

    No Outbound Trip Planned

    + */} +
    +
    +

    Inbound Trip (to Destination)

    +

    No Inbound Trip Planned

    +
    + + {/* The map, if visible */} + {this.state.mapVisible && +
    + +
    + } +
    + ) + } +} + +// connect to the redux store + +const mapStateToProps = (state, ownProps) => { + const requestId = parseInt(state.router.location.query.requestId) + const { requests } = state.callTaker.fieldTrip + const request = requests.data.find(req => req.id === requestId) + return { + request, + requestId, + session: state.callTaker.session + } +} + +const mapDispatchToProps = { + fetchFieldTripDetails: fieldTripActions.fetchFieldTripDetails, + fetchFieldTrips: fieldTripActions.fetchFieldTrips, + initializeModules: callTakerActions.initializeModules +} + +export default connect(mapStateToProps, mapDispatchToProps)(PrintFieldTripLayout) diff --git a/lib/components/app/responsive-webapp.js b/lib/components/app/responsive-webapp.js index 80d5f698e..554434f2e 100644 --- a/lib/components/app/responsive-webapp.js +++ b/lib/components/app/responsive-webapp.js @@ -17,6 +17,7 @@ import * as formActions from '../../actions/form' import * as locationActions from '../../actions/location' import * as mapActions from '../../actions/map' import * as uiActions from '../../actions/ui' +import PrintFieldTripLayout from '../admin/print-field-trip-layout' import { frame } from '../app/app-frame' import { RedirectWithQuery } from '../form/connected-links' import Map from '../map/map' @@ -348,6 +349,10 @@ class RouterWrapperWithAuth0 extends Component { component={PrintLayout} path='/print' /> + {/* For any other route, simply return the web app. */} } From 73bfdbe4c817e0269edb87a0ef216a7c9a69efdf Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 29 Jun 2021 20:25:40 -0400 Subject: [PATCH 69/89] refactor(PrintFieldTripLayout): Decode saved group itineraries. --- .../admin/print-field-trip-layout.js | 23 ++++--- lib/util/call-taker.js | 62 +++++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 5ebf84624..93503e8a3 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -1,4 +1,4 @@ -// import PrintableItinerary from '@opentripplanner/printable-itinerary' +import PrintableItinerary from '@opentripplanner/printable-itinerary' import React, { Component } from 'react' import { Button } from 'react-bootstrap' import { connect } from 'react-redux' @@ -6,8 +6,8 @@ import { connect } from 'react-redux' import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' import DefaultMap from '../map/default-map' -// import TripDetails from '../narrative/connected-trip-details' -// import { getTripFromRequest } from '../../util/call-taker' +import TripDetails from '../narrative/connected-trip-details' +import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' import { @@ -63,8 +63,8 @@ class PrintFieldTripLayout extends Component { } render () { - const { request } = this.props - // const { LegIcon } = this.context + const { config, request } = this.props + const { LegIcon } = this.context if (!request) return null const { @@ -82,7 +82,7 @@ class PrintFieldTripLayout extends Component { timeStamp } = request - // const outboundTrip = getTripFromRequest(request, true) + const outboundTrip = getTripFromRequest(request, true) // const inboundTrip = getTripFromRequest(request, false) return ( @@ -115,15 +115,17 @@ class PrintFieldTripLayout extends Component {

    Outbound Trip (to Destination)

    + {/*

    No Outbound Trip Planned

    - {/* outboundTrip + */} + {outboundTrip ? outboundTrip.groupItineraries?.map((groupItin, i) => { - //const itinerary = JSON.parse(groupItin.itinData) + const itinerary = JSON.parse(lzwDecode(groupItin.itinData)) return (
    @@ -131,7 +133,7 @@ class PrintFieldTripLayout extends Component { ) }) :

    No Outbound Trip Planned

    - */} + }

    Inbound Trip (to Destination)

    @@ -156,6 +158,7 @@ const mapStateToProps = (state, ownProps) => { const { requests } = state.callTaker.fieldTrip const request = requests.data.find(req => req.id === requestId) return { + config: state.otp.config, request, requestId, session: state.callTaker.session diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index f3b378f99..1652cd6b4 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -3,6 +3,7 @@ import {randId} from '@opentripplanner/core-utils/lib/storage' import moment from 'moment' import {getRoutingParams} from '../actions/api' + import {getTimestamp} from './state' export const TICKET_TYPES = { @@ -269,3 +270,64 @@ function createItinerary (itinData, tripPlan) { }) return itin } + +/** + * LZW-compress a string + * + * LZW functions adaped from jsolait library (LGPL) + * via http://stackoverflow.com/questions/294297/javascript-implementation-of-gzip + */ +export function lzwEncode (s) { + var dict = {} + var data = (s + '').split('') + var out = [] + var currChar + var phrase = data[0] + var code = 256 + let i + for (i = 1; i < data.length; i++) { + currChar = data[i] + if (dict[phrase + currChar] != null) { + phrase += currChar + } else { + out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0)) + dict[phrase + currChar] = code + code++ + phrase = currChar + } + } + out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0)) + for (i = 0; i < out.length; i++) { + out[i] = String.fromCharCode(out[i]) + } + return out.join('') +} +/** + * Decompress an LZW-encoded string + * + * LZW functions adaped from jsolait library (LGPL) + * via http://stackoverflow.com/questions/294297/javascript-implementation-of-gzip + */ +export function lzwDecode (s) { + var dict = {} + var data = (s + '').split('') + var currChar = data[0] + var oldPhrase = currChar + var out = [currChar] + var code = 256 + var phrase + for (var i = 1; i < data.length; i++) { + var currCode = data[i].charCodeAt(0) + if (currCode < 256) { + phrase = data[i] + } else { + phrase = dict[currCode] ? dict[currCode] : (oldPhrase + currChar) + } + out.push(phrase) + currChar = phrase.charAt(0) + dict[code] = oldPhrase + currChar + code++ + oldPhrase = phrase + } + return out.join('') +} From da26d25cd8b767247a9ec3c135b6326f86e3d7f0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 11:57:31 -0400 Subject: [PATCH 70/89] refactor(PrintFieldTripLayout): Refine layout and styles. --- .../admin/print-field-trip-layout.js | 220 +++++++++++------- 1 file changed, 138 insertions(+), 82 deletions(-) diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 93503e8a3..4976a2f90 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -2,31 +2,91 @@ import PrintableItinerary from '@opentripplanner/printable-itinerary' import React, { Component } from 'react' import { Button } from 'react-bootstrap' import { connect } from 'react-redux' +import styled from 'styled-components' import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' -import DefaultMap from '../map/default-map' import TripDetails from '../narrative/connected-trip-details' import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' -import { - Val -} from './styled' - -class PrintFieldTripLayout extends Component { - static contextType = ComponentContext - - constructor (props) { - super(props) - this.state = { - mapVisible: true - } +// Styles specific for rendering PrintFieldTripLayout. +const PrintLayout = styled.div` + font-size: 16px; + line-height: 115%; + margin: 8px; +` + +const Header = styled.div`` + +const TripTitle = styled.h1` + border-bottom: 3px solid gray; + font-size: 30px; + font-weight: bold; +` + +const TripInfoList = styled.ul` + font-size: 16px; + list-style: none; + margin-top: 1em; + padding: 0; +` + +export const Val = styled.span` + :empty:before { + content: 'N/A'; } - - _toggleMap = () => { - this.setState({ mapVisible: !this.state.mapVisible }) +` + +// The styles below mirror those found in OTP native client. +const TripContainer = styled.div` + background: #ddd; + margin-top: 1em; + + & > h2 { + font-size: 20px; + font-weight: bold; + margin: 0; + padding: 4px; + } +` + +const TripBody = styled.div` + padding: 8px; +` + +const ItineraryContainer = styled.div` + border: 3px solid #444; + margin-top: .5em; + + & > h3 { + background: #444; + color: white; + font-size: 18px; + font-weight: bold; + margin: 0; + padding: 4px; } +` + +const ItineraryBody = styled.div` + background: white; + padding: 12px; +` + +const TripSummary = styled(TripDetails)` + background: #eee; + border: 1px solid #bbb; + border-radius: 0; + margin-top: 15px; + padding: 5px; +` + +/** + * Component that renders the print version of field trip itineraries. + */ +class PrintFieldTripLayout extends Component { + static contextType = ComponentContext _print = () => { window.print() @@ -46,10 +106,9 @@ class PrintFieldTripLayout extends Component { async componentDidUpdate (prevProps) { const { fetchFieldTrips, fetchFieldTripDetails, requestId, session } = this.props if (!prevProps.session && session) { - // When session is set - // Load all field trips + // When session is set, load all field trips, + // then load details for field trip per request id. await fetchFieldTrips() - // Load details for field trip per request id. fetchFieldTripDetails(requestId) } } @@ -69,6 +128,7 @@ class PrintFieldTripLayout extends Component { const { address, + classpassId, emailAddress, endLocation, faxNumber, @@ -82,71 +142,67 @@ class PrintFieldTripLayout extends Component { timeStamp } = request - const outboundTrip = getTripFromRequest(request, true) - // const inboundTrip = getTripFromRequest(request, false) + // Outbound/inbound template + const tripStructure = [ + { + title: 'Outbound Trip (to Destination)', + trip: getTripFromRequest(request, true), + tripAbsentMessage: 'No Outbound Trip Planned' + }, + { + title: 'Inbound Trip (from Destination)', + trip: getTripFromRequest(request, false), + tripAbsentMessage: 'No Inbound Trip Planned' + } + ] return ( -
    - {/* The header bar, including the Toggle Map and Print buttons */} -
    -
    - - {' '} - -
    -

    Field Trip Plan: {schoolName} to {endLocation}

    -
    -
    -
      -
    • Teacher: {teacherName} ({schoolName}, Grade: {grade})
    • -
    • Teacher Address: {address}
    • -
    • Phone: {phoneNumber} / Fax: {faxNumber}
    • -
    • Email: {emailAddress}
    • -
    • Students Age 7 and Over: {numStudents || 0}
    • -
    • Students Age 6 and Under: {numFreeStudents || 0}
    • -
    • Chaperones: {numChaperones || 0}
    • -
    • Request submitted: {timeStamp}
    • -
    -
    - -
    -

    Outbound Trip (to Destination)

    - {/* -

    No Outbound Trip Planned

    - */} - {outboundTrip - ? outboundTrip.groupItineraries?.map((groupItin, i) => { - const itinerary = JSON.parse(lzwDecode(groupItin.itinData)) - return ( -
    - - -
    - ) - }) - :

    No Outbound Trip Planned

    - } -
    -
    -

    Inbound Trip (to Destination)

    -

    No Inbound Trip Planned

    -
    - - {/* The map, if visible */} - {this.state.mapVisible && -
    - -
    - } -
    + +
    + + Field Trip Plan: {schoolName} to {endLocation} +
    + +
  • Teacher: {teacherName} ({schoolName}, Grade: {grade})
  • +
  • Teacher Address: {address}
  • +
  • Phone: {phoneNumber} / Fax: {faxNumber}
  • +
  • Email: {emailAddress}
  • +
  • Students Age 7 and Over: {numStudents || 0}
  • +
  • Students Age 6 and Under: {numFreeStudents || 0}
  • +
  • Chaperones: {numChaperones || 0}
  • + {classpassId &&
  • Class Pass Hop Card #: {classpassId}
  • } +
  • Request submitted: {timeStamp}
  • +
    + + {tripStructure.map(({ title, trip, tripAbsentMessage }) => ( + +

    {title}

    + {trip + ? trip.groupItineraries?.map((groupItin, i) => { + const itinerary = JSON.parse(lzwDecode(groupItin.itinData)) + return ( + + +

    {groupItin.passengers} passengers on following itinerary:

    + + + + +
    +
    + ) + }) + : {tripAbsentMessage} + } +
    + ))} +
    ) } } From 05c0ba28bd7c9ea414f3818c4721e2c01013f7a7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 12:04:14 -0400 Subject: [PATCH 71/89] improvement(PrintFieldTripLayout): Set correct window title for print. --- lib/components/admin/print-field-trip-layout.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 4976a2f90..87ab172d0 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -9,6 +9,7 @@ import * as fieldTripActions from '../../actions/field-trip' import TripDetails from '../narrative/connected-trip-details' import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' +import { getTitle } from '../../util/state' // Styles specific for rendering PrintFieldTripLayout. const PrintLayout = styled.div` @@ -93,12 +94,15 @@ class PrintFieldTripLayout extends Component { } componentDidMount () { - const { initializeModules } = this.props + const { initializeModules, title } = this.props // Add print-view class to html tag to ensure that iOS scroll fix only applies // to non-print views. const root = document.getElementsByTagName('html')[0] root.setAttribute('class', 'print-view') + // Set window title (appears in print headings) + document.title = title + // Load call-taker/field-trip functionality (performs a fetch). initializeModules() } @@ -217,7 +221,8 @@ const mapStateToProps = (state, ownProps) => { config: state.otp.config, request, requestId, - session: state.callTaker.session + session: state.callTaker.session, + title: getTitle(state) } } From a3f2599e23136e0ad5158537cefc987bf37aad46 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 12:14:55 -0400 Subject: [PATCH 72/89] docs(util/call-taker): Fix comment typos. --- lib/util/call-taker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index 1652cd6b4..9a097feeb 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -274,7 +274,7 @@ function createItinerary (itinData, tripPlan) { /** * LZW-compress a string * - * LZW functions adaped from jsolait library (LGPL) + * LZW functions adapted from jsolait library (LGPL) * via http://stackoverflow.com/questions/294297/javascript-implementation-of-gzip */ export function lzwEncode (s) { @@ -305,7 +305,7 @@ export function lzwEncode (s) { /** * Decompress an LZW-encoded string * - * LZW functions adaped from jsolait library (LGPL) + * LZW functions adapted from jsolait library (LGPL) * via http://stackoverflow.com/questions/294297/javascript-implementation-of-gzip */ export function lzwDecode (s) { From e1792f2e5e81859d5e81656d557fc6d7b8e25b0b Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 30 Jun 2021 12:36:48 -0400 Subject: [PATCH 73/89] refactor: address PR comments --- __tests__/actions/__snapshots__/api.js.snap | 2 +- lib/actions/api.js | 113 +----------------- lib/actions/plan.js | 13 +- .../narrative/plan-first-last-buttons.js | 2 +- lib/util/call-taker.js | 4 +- 5 files changed, 16 insertions(+), 118 deletions(-) diff --git a/__tests__/actions/__snapshots__/api.js.snap b/__tests__/actions/__snapshots__/api.js.snap index 1932ad477..6fe83a8fd 100644 --- a/__tests__/actions/__snapshots__/api.js.snap +++ b/__tests__/actions/__snapshots__/api.js.snap @@ -61,4 +61,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"`; +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"`; diff --git a/lib/actions/api.js b/lib/actions/api.js index 736a5c7c3..0e3714f25 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -1,10 +1,8 @@ /* globals fetch */ import { push, replace } from 'connected-react-router' import haversine from 'haversine' -import moment from 'moment' import hash from 'object-hash' import coreUtils from '@opentripplanner/core-utils' -import queryParams from '@opentripplanner/core-utils/lib/query-params' import { createAction } from 'redux-actions' import qs from 'qs' @@ -16,10 +14,8 @@ import { ItineraryView, setItineraryView } from './ui' if (typeof (fetch) === 'undefined') require('isomorphic-fetch') -const { hasCar } = coreUtils.itinerary -const { getTripOptionsFromQuery, getUrlParams } = coreUtils.query +const { getRoutingParams, getTripOptionsFromQuery, getUrlParams } = coreUtils.query const { randId } = coreUtils.storage -const { OTP_API_DATE_FORMAT, OTP_API_TIME_FORMAT } = coreUtils.time // Generic API actions @@ -241,116 +237,15 @@ function constructRoutingQuery (state, ignoreRealtimeUpdates, injectedParams = { const planEndpoint = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/plan` + console.log(currentQuery) const params = { - ...getRoutingParams(currentQuery, ignoreRealtimeUpdates), + ...getRoutingParams(config, currentQuery, ignoreRealtimeUpdates), // Apply mode override, if specified (for batch routing). ...injectedParams } return `${planEndpoint}?${qs.stringify(params, { arrayFormat: 'repeat' })}` } -function isItineraryQuery (query) { - return query?.routingType === 'ITINERARY' -} - -/** - * Set additional parameters for queries using the various CAR modes. This method - * directly modifies the input params. - */ -function addCarParams (params) { - if (params.mode?.includes('CAR_HAIL') || params.mode?.includes('CAR_RENT')) { - params.minTransitDistance = '50%' - // increase search timeout because these queries can take a while - params.searchTimeout = 10000 - } - // set onlyTransitTrips for car rental searches - if (params.mode?.includes('CAR_RENT')) { - params.onlyTransitTrips = true - } - // hack to add walking to driving/TNC trips - if (hasCar(params.mode)) { - params.mode += ',WALK' - } -} - -/** - * Provides additional processing of the query params depending on the routing - * type and if realtime updates are ignored. This method directly modifies the - * input params. - */ -function processParamsForRoutingType (params, isItinerary, ignoreRealtimeUpdates) { - if (isItinerary) { - // override ignoreRealtimeUpdates if provided - if (typeof ignoreRealtimeUpdates === 'boolean') { - params.ignoreRealtimeUpdates = ignoreRealtimeUpdates - } - - // check date/time validity; ignore both if either is invalid - const dateValid = moment(params.date, OTP_API_DATE_FORMAT).isValid() - const timeValid = moment(params.time, OTP_API_TIME_FORMAT).isValid() - - if (!dateValid || !timeValid) { - delete params.time - delete params.date - } - addCarParams(params) - } else { - // Additional processing specific to PROFILE mode - // check start and end time validity; ignore both if either is invalid - const startTimeValid = moment(params.startTime, OTP_API_TIME_FORMAT).isValid() - const endTimeValid = moment(params.endTime, OTP_API_TIME_FORMAT).isValid() - - if (!startTimeValid || !endTimeValid) { - delete params.startTimeValid - delete params.endTimeValid - } - } -} - -function paramIsDefined (query, key) { - return key in query && query[key] !== '' && query[key] !== null -} - -/** - * A given parameter is included in the request if all of the following: - * 1. Must apply to the active routing type (ITINERARY or PROFILE) - * 2. Must be included in the current user-defined query and not an empty - * string nor null. - * 3. Must pass the parameter's applicability test, if one is specified - */ -function filterParam (qp, query, config) { - return qp.routingTypes.indexOf(query.routingType) !== -1 && - paramIsDefined(query, qp.name) && - (typeof qp.applicable !== 'function' || qp.applicable(query, config)) -} - -/** - * Converts a query object to valid routing params for an OTP request. - */ -export function getRoutingParams (query, config, ignoreRealtimeUpdates) { - const isItinerary = isItineraryQuery(query) - let params = {} - - // Start with the universe of OTP parameters defined in query-params.js: - queryParams - .filter(qp => filterParam(qp, query, config)) - .forEach(qp => { - // Translate the applicable parameters according to their rewrite - // functions (if provided) - const rewriteFunction = isItinerary - ? qp.itineraryRewrite - : qp.profileRewrite - params = Object.assign( - params, - rewriteFunction - ? rewriteFunction(query[qp.name]) - : { [qp.name]: query[qp.name] } - ) - }) - processParamsForRoutingType(params, isItinerary, ignoreRealtimeUpdates) - return params -} - // Park and Ride location query export const parkAndRideError = createAction('PARK_AND_RIDE_ERROR') @@ -1062,7 +957,7 @@ export function setUrlSearch (params, replaceCurrent = false) { function updateOtpUrlParams (state, searchId) { const {config, currentQuery} = state.otp // Get updated OTP params from current query. - const otpParams = getRoutingParams(currentQuery, config, true) + const otpParams = getRoutingParams(config, currentQuery, true) return function (dispatch, getState) { const params = {} // Get all URL params and ensure non-routing params (UI, sessionId) remain diff --git a/lib/actions/plan.js b/lib/actions/plan.js index 82d6bca4d..09545fd95 100644 --- a/lib/actions/plan.js +++ b/lib/actions/plan.js @@ -24,6 +24,10 @@ function offsetTime (itinerary, unixTime) { 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. + */ function isPlanningFirst (query) { const {departArrive, time} = query return departArrive === 'DEPART' && time === SERVICE_BREAK @@ -36,7 +40,7 @@ function isPlanningFirst (query) { export function planFirst () { return function (dispatch, getState) { const state = getState() - const itinerary = getActiveItinerary(state.otp) + const itinerary = getActiveItinerary(state) const currentQuery = state.otp.currentQuery const date = moment(currentQuery.date) // If already planning for the "first" trip, subtract a day to mirror the @@ -58,8 +62,7 @@ export function planFirst () { */ export function planPrevious () { return function (dispatch, getState) { - const state = getState() - const itinerary = getActiveItinerary(state.otp) + const itinerary = getActiveItinerary(getState()) const newEndTime = offsetTime(itinerary, itinerary.endTime - NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), @@ -78,7 +81,7 @@ export function planPrevious () { export function planNext () { return function (dispatch, getState) { const state = getState() - const itinerary = getActiveItinerary(state.otp) + const itinerary = getActiveItinerary(state) const newStartTime = offsetTime(itinerary, itinerary.startTime + NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), @@ -97,7 +100,7 @@ export function planNext () { export function planLast () { return function (dispatch, getState) { const state = getState() - const itinerary = getActiveItinerary(state.otp) + const itinerary = getActiveItinerary(state) const currentQuery = state.otp.currentQuery const params = { startTransitStopId: getFirstStopId(itinerary), diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js index c87466e95..fae5e4bf9 100644 --- a/lib/components/narrative/plan-first-last-buttons.js +++ b/lib/components/narrative/plan-first-last-buttons.js @@ -31,7 +31,7 @@ function PlanFirstLastButtons (props) { } const mapStateToProps = (state, ownProps) => { - const itineraries = getActiveItineraries(state.otp) + const itineraries = getActiveItineraries(state) return { enabled: state.otp.config.itinerary?.showPlanFirstLastButtons, itineraries diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index f3b378f99..9434edbf6 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -1,8 +1,8 @@ import {isTransit} from '@opentripplanner/core-utils/lib/itinerary' +import {getRoutingParams} from '@opentripplanner/core-utils/lib/query' import {randId} from '@opentripplanner/core-utils/lib/storage' import moment from 'moment' -import {getRoutingParams} from '../actions/api' import {getTimestamp} from './state' export const TICKET_TYPES = { @@ -167,7 +167,7 @@ export function getVisibleRequests (state) { */ export function searchToQuery (search, call, otpConfig) { // FIXME: how to handle realtime updates? - const queryParams = getRoutingParams(search.query, otpConfig, true) + const queryParams = getRoutingParams(otpConfig, search.query, true) const {from, to} = search.query return { queryParams: JSON.stringify(queryParams), From 6918774a3402df9e029eff0dd73ad01842522d27 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 30 Jun 2021 16:32:52 -0400 Subject: [PATCH 74/89] refactor: address PR comments --- lib/actions/api.js | 1 - lib/actions/plan.js | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/actions/api.js b/lib/actions/api.js index 0e3714f25..105bdb6ef 100644 --- a/lib/actions/api.js +++ b/lib/actions/api.js @@ -237,7 +237,6 @@ function constructRoutingQuery (state, ignoreRealtimeUpdates, injectedParams = { const planEndpoint = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/plan` - console.log(currentQuery) const params = { ...getRoutingParams(config, currentQuery, ignoreRealtimeUpdates), // Apply mode override, if specified (for batch routing). diff --git a/lib/actions/plan.js b/lib/actions/plan.js index 09545fd95..1b6e8c38f 100644 --- a/lib/actions/plan.js +++ b/lib/actions/plan.js @@ -41,7 +41,7 @@ export function planFirst () { return function (dispatch, getState) { const state = getState() const itinerary = getActiveItinerary(state) - const currentQuery = state.otp.currentQuery + 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. @@ -80,8 +80,7 @@ export function planPrevious () { */ export function planNext () { return function (dispatch, getState) { - const state = getState() - const itinerary = getActiveItinerary(state) + const itinerary = getActiveItinerary(getState()) const newStartTime = offsetTime(itinerary, itinerary.startTime + NINETY_SECONDS) const params = { startTransitStopId: getFirstStopId(itinerary), @@ -101,7 +100,7 @@ export function planLast () { return function (dispatch, getState) { const state = getState() const itinerary = getActiveItinerary(state) - const currentQuery = state.otp.currentQuery + const {currentQuery} = state.otp const params = { startTransitStopId: getFirstStopId(itinerary), date: moment(currentQuery.date).add('days', 1).format(OTP_API_DATE_FORMAT), From 43394591393a93528709d26e71d805cf7295d7f2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 16:35:12 -0400 Subject: [PATCH 75/89] refactor(util/print.js): Extract common print boilerplate. --- .../admin/print-field-trip-layout.js | 10 +++------ lib/components/app/print-layout.js | 10 +++------ lib/util/print.js | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 lib/util/print.js diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 87ab172d0..8594462cd 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -9,6 +9,7 @@ import * as fieldTripActions from '../../actions/field-trip' import TripDetails from '../narrative/connected-trip-details' import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' +import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/print' import { getTitle } from '../../util/state' // Styles specific for rendering PrintFieldTripLayout. @@ -97,8 +98,7 @@ class PrintFieldTripLayout extends Component { const { initializeModules, title } = this.props // Add print-view class to html tag to ensure that iOS scroll fix only applies // to non-print views. - const root = document.getElementsByTagName('html')[0] - root.setAttribute('class', 'print-view') + addPrintViewClassToRootHtml() // Set window title (appears in print headings) document.title = title @@ -117,12 +117,8 @@ class PrintFieldTripLayout extends Component { } } - /** - * Remove class attribute from html tag on clean up. - */ componentWillUnmount () { - const root = document.getElementsByTagName('html')[0] - root.removeAttribute('class') + clearClassFromRootHtml() } render () { diff --git a/lib/components/app/print-layout.js b/lib/components/app/print-layout.js index da1a292f5..1a48d07db 100644 --- a/lib/components/app/print-layout.js +++ b/lib/components/app/print-layout.js @@ -9,6 +9,7 @@ import { routingQuery } from '../../actions/api' import DefaultMap from '../map/default-map' import TripDetails from '../narrative/connected-trip-details' import { ComponentContext } from '../../util/contexts' +import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/print' import { getActiveItinerary } from '../../util/state' class PrintLayout extends Component { @@ -38,20 +39,15 @@ class PrintLayout extends Component { const { location, parseUrlQueryString } = this.props // Add print-view class to html tag to ensure that iOS scroll fix only applies // to non-print views. - const root = document.getElementsByTagName('html')[0] - root.setAttribute('class', 'print-view') + addPrintViewClassToRootHtml() // Parse the URL query parameters, if present if (location && location.search) { parseUrlQueryString() } } - /** - * Remove class attribute from html tag on clean up. - */ componentWillUnmount () { - const root = document.getElementsByTagName('html')[0] - root.removeAttribute('class') + clearClassFromRootHtml() } render () { diff --git a/lib/util/print.js b/lib/util/print.js new file mode 100644 index 000000000..0437626c0 --- /dev/null +++ b/lib/util/print.js @@ -0,0 +1,22 @@ +/** + * @return the root tag. + */ +function getRootHtmlTag () { + return document.getElementsByTagName('html')[0] +} + +/** + * Add print-view class to the html tag on print layout components + * to ensure that iOS scroll fix only applies to non-print views. + */ +export function addPrintViewClassToRootHtml () { + getRootHtmlTag().setAttribute('class', 'print-view') +} + +/** + * Remove class attribute from html tag, + * for print layout components when componentWillUnmount runs. + */ +export function clearClassFromRootHtml () { + getRootHtmlTag().removeAttribute('class') +} From 13283281725771b5be5dc5af637cdd9f2eb8ca30 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 30 Jun 2021 16:51:09 -0400 Subject: [PATCH 76/89] refactor(trip-page): warn on navigating away with unsaved changes --- lib/components/user/monitored-trip/trip-basics-pane.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index fa0210d43..b5d3a0515 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -10,10 +10,12 @@ import { ProgressBar } from 'react-bootstrap' import { connect } from 'react-redux' +import { Prompt } from 'react-router' import styled from 'styled-components' import * as userActions from '../../../actions/user' import { getErrorStates } from '../../../util/ui' + import TripStatus from './trip-status' import TripSummary from './trip-summary' @@ -107,6 +109,12 @@ class TripBasicsPane extends Component { return (
    + {/* Prevent user from leaving when form is dirty */} + + {/* Do not show trip status when saving trip for the first time (it doesn't exist in backend yet). */} {!isCreating && } From 9e3a9ea50bb4c662aec3aa8d0565d822daf81196 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 16:57:18 -0400 Subject: [PATCH 77/89] refactor(PrintFieldTripLayout): Avoid fetching all field trips to print just one. --- lib/actions/field-trip.js | 8 +++---- .../admin/print-field-trip-layout.js | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/actions/field-trip.js b/lib/actions/field-trip.js index 50ea0c8be..90ecf910c 100644 --- a/lib/actions/field-trip.js +++ b/lib/actions/field-trip.js @@ -5,22 +5,22 @@ import { serialize } from 'object-to-formdata' import qs from 'qs' import { createAction } from 'redux-actions' +import {getGroupSize, getTripFromRequest, sessionIsInvalid} from '../util/call-taker' + import {routingQuery} from './api' import {toggleCallHistory} from './call-taker' import {resetForm, setQueryParam} from './form' -import {getGroupSize, getTripFromRequest, sessionIsInvalid} from '../util/call-taker' if (typeof (fetch) === 'undefined') require('isomorphic-fetch') /// PRIVATE ACTIONS -const receivedFieldTrips = createAction('RECEIVED_FIELD_TRIPS') -const requestingFieldTrips = createAction('REQUESTING_FIELD_TRIPS') const receivedFieldTripDetails = createAction('RECEIVED_FIELD_TRIP_DETAILS') +const requestingFieldTrips = createAction('REQUESTING_FIELD_TRIPS') const requestingFieldTripDetails = createAction('REQUESTING_FIELD_TRIP_DETAILS') // PUBLIC ACTIONS - +export const receivedFieldTrips = createAction('RECEIVED_FIELD_TRIPS') export const setFieldTripFilter = createAction('SET_FIELD_TRIP_FILTER') export const setActiveFieldTrip = createAction('SET_ACTIVE_FIELD_TRIP') export const setGroupSize = createAction('SET_GROUP_SIZE') diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 8594462cd..1da6452ac 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -107,12 +107,18 @@ class PrintFieldTripLayout extends Component { initializeModules() } - async componentDidUpdate (prevProps) { - const { fetchFieldTrips, fetchFieldTripDetails, requestId, session } = this.props + componentDidUpdate (prevProps) { + const { fetchFieldTripDetails, receivedFieldTrips, requestId, session } = this.props if (!prevProps.session && session) { - // When session is set, load all field trips, - // then load details for field trip per request id. - await fetchFieldTrips() + // When session is set, + // create a placeholder in the calltaker redux state that has just one request + // for fetching/receiving the details of the field trip per request id. + receivedFieldTrips({ + fieldTrips: [{ + endTime: 0, + id: requestId + }] + }) fetchFieldTripDetails(requestId) } } @@ -176,8 +182,8 @@ class PrintFieldTripLayout extends Component {
  • Request submitted: {timeStamp}
  • - {tripStructure.map(({ title, trip, tripAbsentMessage }) => ( - + {tripStructure.map(({ title, trip, tripAbsentMessage }, i) => ( +

    {title}

    {trip ? trip.groupItineraries?.map((groupItin, i) => { @@ -224,8 +230,8 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = { fetchFieldTripDetails: fieldTripActions.fetchFieldTripDetails, - fetchFieldTrips: fieldTripActions.fetchFieldTrips, - initializeModules: callTakerActions.initializeModules + initializeModules: callTakerActions.initializeModules, + receivedFieldTrips: fieldTripActions.receivedFieldTrips } export default connect(mapStateToProps, mapDispatchToProps)(PrintFieldTripLayout) From a266e27e162309aa649d2d1575b0781773dc66af Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 17:05:58 -0400 Subject: [PATCH 78/89] refactor(PrintTripFieldLayout): Extract styled components to ext. file. --- .../admin/print-field-trip-layout.js | 85 +++---------------- lib/components/admin/print-styled.js | 77 +++++++++++++++++ 2 files changed, 89 insertions(+), 73 deletions(-) create mode 100644 lib/components/admin/print-styled.js diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 1da6452ac..d842cc12f 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -2,87 +2,26 @@ import PrintableItinerary from '@opentripplanner/printable-itinerary' import React, { Component } from 'react' import { Button } from 'react-bootstrap' import { connect } from 'react-redux' -import styled from 'styled-components' import * as callTakerActions from '../../actions/call-taker' import * as fieldTripActions from '../../actions/field-trip' -import TripDetails from '../narrative/connected-trip-details' import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/print' import { getTitle } from '../../util/state' -// Styles specific for rendering PrintFieldTripLayout. -const PrintLayout = styled.div` - font-size: 16px; - line-height: 115%; - margin: 8px; -` - -const Header = styled.div`` - -const TripTitle = styled.h1` - border-bottom: 3px solid gray; - font-size: 30px; - font-weight: bold; -` - -const TripInfoList = styled.ul` - font-size: 16px; - list-style: none; - margin-top: 1em; - padding: 0; -` - -export const Val = styled.span` - :empty:before { - content: 'N/A'; - } -` - -// The styles below mirror those found in OTP native client. -const TripContainer = styled.div` - background: #ddd; - margin-top: 1em; - - & > h2 { - font-size: 20px; - font-weight: bold; - margin: 0; - padding: 4px; - } -` - -const TripBody = styled.div` - padding: 8px; -` - -const ItineraryContainer = styled.div` - border: 3px solid #444; - margin-top: .5em; - - & > h3 { - background: #444; - color: white; - font-size: 18px; - font-weight: bold; - margin: 0; - padding: 4px; - } -` - -const ItineraryBody = styled.div` - background: white; - padding: 12px; -` - -const TripSummary = styled(TripDetails)` - background: #eee; - border: 1px solid #bbb; - border-radius: 0; - margin-top: 15px; - padding: 5px; -` +import { + Header, + ItineraryBody, + ItineraryContainer, + PrintLayout, + TripBody, + TripContainer, + TripInfoList, + TripSummary, + TripTitle, + Val +} from './print-styled' /** * Component that renders the print version of field trip itineraries. diff --git a/lib/components/admin/print-styled.js b/lib/components/admin/print-styled.js new file mode 100644 index 000000000..249da4d0b --- /dev/null +++ b/lib/components/admin/print-styled.js @@ -0,0 +1,77 @@ +import styled from 'styled-components' + +import TripDetails from '../narrative/connected-trip-details' + +// This file contains styles specific for rendering PrintFieldTripLayout. +// They generally mimic the styles found in the OTP native client. + +export const PrintLayout = styled.div` + font-size: 16px; + line-height: 115%; + margin: 8px; +` + +export const Header = styled.div`` + +export const TripTitle = styled.h1` + border-bottom: 3px solid gray; + font-size: 30px; + font-weight: bold; +` + +export const TripInfoList = styled.ul` + font-size: 16px; + list-style: none; + margin-top: 1em; + padding: 0; +` + +export const Val = styled.span` + :empty:before { + content: 'N/A'; + } +` + +// The styles below mirror those found in OTP native client. +export const TripContainer = styled.div` + background: #ddd; + margin-top: 1em; + + & > h2 { + font-size: 20px; + font-weight: bold; + margin: 0; + padding: 4px; + } +` + +export const TripBody = styled.div` + padding: 8px; +` + +export const ItineraryContainer = styled.div` + border: 3px solid #444; + margin-top: .5em; + + & > h3 { + background: #444; + color: white; + font-size: 18px; + font-weight: bold; + margin: 0; + padding: 4px; + } +` + +export const ItineraryBody = styled.div` + background: white; + padding: 12px; +` + +export const TripSummary = styled(TripDetails)` + background: #eee; + border: 1px solid #bbb; + border-radius: 0; + margin-top: 15px; + padding: 5px; +` From 134ab2091e7a84b4f18cfece85852fe4ba62cc4b Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 17:18:48 -0400 Subject: [PATCH 79/89] Update lib/util/call-taker.js Co-authored-by: Landon Reed --- lib/util/call-taker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util/call-taker.js b/lib/util/call-taker.js index 9a097feeb..2330b22e0 100644 --- a/lib/util/call-taker.js +++ b/lib/util/call-taker.js @@ -302,6 +302,7 @@ export function lzwEncode (s) { } return out.join('') } + /** * Decompress an LZW-encoded string * From e5ace268f1d2f470a533eae0e70be3916c4076d0 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 17:20:22 -0400 Subject: [PATCH 80/89] refactor(PrintFieldTripLayout): Improve print page title. --- .../admin/print-field-trip-layout.js | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index d842cc12f..29b7d8635 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -8,7 +8,6 @@ import * as fieldTripActions from '../../actions/field-trip' import { getTripFromRequest, lzwDecode } from '../../util/call-taker' import { ComponentContext } from '../../util/contexts' import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/print' -import { getTitle } from '../../util/state' import { Header, @@ -34,20 +33,23 @@ class PrintFieldTripLayout extends Component { } componentDidMount () { - const { initializeModules, title } = this.props + const { initializeModules } = this.props // Add print-view class to html tag to ensure that iOS scroll fix only applies // to non-print views. addPrintViewClassToRootHtml() - // Set window title (appears in print headings) - document.title = title - // Load call-taker/field-trip functionality (performs a fetch). initializeModules() } componentDidUpdate (prevProps) { - const { fetchFieldTripDetails, receivedFieldTrips, requestId, session } = this.props + const { + fetchFieldTripDetails, + receivedFieldTrips, + request, + requestId, + session + } = this.props if (!prevProps.session && session) { // When session is set, // create a placeholder in the calltaker redux state that has just one request @@ -60,6 +62,13 @@ class PrintFieldTripLayout extends Component { }) fetchFieldTripDetails(requestId) } + + if (request && request !== prevProps.request) { + // Set window title when request has fully loaded + // (appears in print headings) + const { endLocation, schoolName } = request + document.title = `Field Trip: ${schoolName} to ${endLocation}` + } } componentWillUnmount () { @@ -162,8 +171,7 @@ const mapStateToProps = (state, ownProps) => { config: state.otp.config, request, requestId, - session: state.callTaker.session, - title: getTitle(state) + session: state.callTaker.session } } From c9bc5ff169b665c2d2f6c070af909633099e0681 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 30 Jun 2021 17:30:52 -0400 Subject: [PATCH 81/89] =?UTF-8?q?refactor(trip-page):=20don=E2=80=99t=20sh?= =?UTF-8?q?ow=20prevention=20dialog=20when=20submitting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/components/user/monitored-trip/trip-basics-pane.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index b5d3a0515..2cb652e2e 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -109,10 +109,11 @@ class TripBasicsPane extends Component { return (
    - {/* Prevent user from leaving when form is dirty */} + {/* Prevent user from leaving when form has been changed, but + don't show it when they click submit. */} {/* Do not show trip status when saving trip for the first time From ce51a6a0819b8e85c0acf4c2219a1c433699fc12 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 30 Jun 2021 17:44:16 -0400 Subject: [PATCH 82/89] refactor(trip-page): don't show navigation prevention prompt when clicking cancel --- .../user/monitored-trip/trip-basics-pane.js | 2 +- lib/components/user/stacked-pane-display.js | 51 ++++++++++--------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index 2cb652e2e..943b553f9 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -112,7 +112,7 @@ class TripBasicsPane extends Component { {/* Prevent user from leaving when form has been changed, but don't show it when they click submit. */} diff --git a/lib/components/user/stacked-pane-display.js b/lib/components/user/stacked-pane-display.js index 529fb1964..92e6ba1b7 100644 --- a/lib/components/user/stacked-pane-display.js +++ b/lib/components/user/stacked-pane-display.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, {useState} from 'react' import FormNavigationButtons from './form-navigation-buttons' import { PageHeading, StackedPaneContainer } from './styled' @@ -7,30 +7,33 @@ import { PageHeading, StackedPaneContainer } from './styled' /** * This component handles the flow between screens for new OTP user accounts. */ -const StackedPaneDisplay = ({ onCancel, paneSequence, title }) => ( - <> - {title && {title}} - { - paneSequence.map(({ pane: Pane, props, title }, index) => ( - -

    {title}

    -
    -
    - )) - } +const StackedPaneDisplay = ({ onCancel, paneSequence, title }) => { + const [isBeingCanceled, updateBeingCanceled] = useState(false) - - -) + return ( + <> + {title && {title}} + { + paneSequence.map(({ pane: Pane, props, title }, index) => ( + +

    {title}

    +
    +
    + )) + } + + { updateBeingCanceled(true); onCancel() }, + text: 'Cancel' + }} + okayButton={{ + text: 'Save Preferences', + type: 'submit' + }} + /> + ) +} StackedPaneDisplay.propTypes = { onCancel: PropTypes.func.isRequired, From 96f4939cd5823e2eb81228001359282879af5466 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 30 Jun 2021 17:45:16 -0400 Subject: [PATCH 83/89] refactor(trip-page): make comment match code --- lib/components/user/monitored-trip/trip-basics-pane.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index 943b553f9..1f2efa693 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -110,7 +110,7 @@ class TripBasicsPane extends Component { return (
    {/* Prevent user from leaving when form has been changed, but - don't show it when they click submit. */} + don't show it when they click submit or cancel. */} Date: Wed, 30 Jun 2021 17:46:39 -0400 Subject: [PATCH 84/89] refactor(trip-page): clarify use of new state variable --- lib/components/user/stacked-pane-display.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/components/user/stacked-pane-display.js b/lib/components/user/stacked-pane-display.js index 92e6ba1b7..1640b6013 100644 --- a/lib/components/user/stacked-pane-display.js +++ b/lib/components/user/stacked-pane-display.js @@ -8,6 +8,7 @@ import { PageHeading, StackedPaneContainer } from './styled' * This component handles the flow between screens for new OTP user accounts. */ const StackedPaneDisplay = ({ onCancel, paneSequence, title }) => { + // Create indicator of if cancel button was clicked so that child components can know const [isBeingCanceled, updateBeingCanceled] = useState(false) return ( From 6ef72b9b8d00366f5c62717405be677c0bb34233 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 30 Jun 2021 18:50:51 -0400 Subject: [PATCH 85/89] refactor(field-trip): Address PR comments --- lib/components/admin/field-trip-details.js | 6 +++--- lib/components/admin/print-field-trip-layout.js | 6 +++--- lib/components/admin/print-styled.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js index e82ebef59..42ce4a9bb 100644 --- a/lib/components/admin/field-trip-details.js +++ b/lib/components/admin/field-trip-details.js @@ -60,9 +60,9 @@ class FieldTripDetails extends Component { } _renderFooter = () => { - const { request, session } = this.props + const { request, sessionId } = this.props const cancelled = request.status === 'cancelled' - const printFieldTripLink = `/#/printFieldTrip/?requestId=${request.id}&sessionId=${session.sessionId}` + const printFieldTripLink = `/#/printFieldTrip/?requestId=${request.id}&sessionId=${sessionId}` return (
    { datastoreUrl: state.otp.config.datastoreUrl, dateFormat: getDateFormat(state.otp.config), request, - session: state.callTaker.session + sessionId: state.callTaker.session.sessionId } } diff --git a/lib/components/admin/print-field-trip-layout.js b/lib/components/admin/print-field-trip-layout.js index 29b7d8635..acd71279f 100644 --- a/lib/components/admin/print-field-trip-layout.js +++ b/lib/components/admin/print-field-trip-layout.js @@ -11,8 +11,8 @@ import { addPrintViewClassToRootHtml, clearClassFromRootHtml } from '../../util/ import { Header, - ItineraryBody, ItineraryContainer, + PrintableItineraryContainer, PrintLayout, TripBody, TripContainer, @@ -140,14 +140,14 @@ class PrintFieldTripLayout extends Component {

    {groupItin.passengers} passengers on following itinerary:

    - + - +
    ) diff --git a/lib/components/admin/print-styled.js b/lib/components/admin/print-styled.js index 249da4d0b..fb66fbe96 100644 --- a/lib/components/admin/print-styled.js +++ b/lib/components/admin/print-styled.js @@ -63,7 +63,7 @@ export const ItineraryContainer = styled.div` } ` -export const ItineraryBody = styled.div` +export const PrintableItineraryContainer = styled.div` background: white; padding: 12px; ` From f5efbc19c77c979477229b947c642788a1c560d2 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Fri, 2 Jul 2021 09:13:11 -0400 Subject: [PATCH 86/89] refactor(trip-basics-pane.js): address PR comments --- .../user/monitored-trip/trip-basics-pane.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index 1f2efa693..7ac9333f2 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -89,9 +89,25 @@ class TripBasicsPane extends Component { } render () { - const { errors, isCreating, itineraryExistence, values: monitoredTrip } = this.props + const { + canceled, + dirty, + errors, + isCreating, + isSubmitting, + itineraryExistence, + values: monitoredTrip + } = this.props const { itinerary } = monitoredTrip + // Prevent user from leaving when form has been changed, + // but don't show it when they click submit or cancel. + const unsavedChanges = dirty && !isSubmitting && !canceled + // Message changes depending on if the new or existing trip is being edited + const unsavedChangesMessage = `You have haven't saved your ${ + isCreating ? 'new ' : '' + }trip yet. If you leave, ${isCreating ? 'it' : 'changes'} will be lost.` + if (!itinerary) { return
    No itinerary to display.
    } else { @@ -109,11 +125,9 @@ class TripBasicsPane extends Component { return (
    - {/* Prevent user from leaving when form has been changed, but - don't show it when they click submit or cancel. */} {/* Do not show trip status when saving trip for the first time From 471c3f4f376d5b904c93f9470398f114de6eaeb4 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 7 Jul 2021 08:53:47 -0400 Subject: [PATCH 87/89] fix(trip-basics-pane.js): Add warning when navigating away from unsaved trip changes resolve typos, remove string assembly for internationalization --- lib/components/user/monitored-trip/trip-basics-pane.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index 7ac9333f2..6c883893d 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -104,9 +104,9 @@ class TripBasicsPane extends Component { // but don't show it when they click submit or cancel. const unsavedChanges = dirty && !isSubmitting && !canceled // Message changes depending on if the new or existing trip is being edited - const unsavedChangesMessage = `You have haven't saved your ${ - isCreating ? 'new ' : '' - }trip yet. If you leave, ${isCreating ? 'it' : 'changes'} will be lost.` + const unsavedChangesNewTripMessage = 'You haven\'t saved your new trip yet. If you leave, it will be lost.' + const unsavedChangesExistingTripMessage = 'You haven\'t saved your trip yet. If you leave, changes will be lost.' + const unsavedChangesMessage = isCreating ? unsavedChangesNewTripMessage : unsavedChangesExistingTripMessage if (!itinerary) { return
    No itinerary to display.
    From 7eb7b8d1fd5ce850b01c7c9dbceda579eb16828d Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 7 Jul 2021 09:55:43 -0400 Subject: [PATCH 88/89] style(stacked-pane-display.js): remove semicolons --- lib/components/user/stacked-pane-display.js | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/components/user/stacked-pane-display.js b/lib/components/user/stacked-pane-display.js index 1640b6013..8db7380b9 100644 --- a/lib/components/user/stacked-pane-display.js +++ b/lib/components/user/stacked-pane-display.js @@ -14,18 +14,21 @@ const StackedPaneDisplay = ({ onCancel, paneSequence, title }) => { return ( <> {title && {title}} - { - paneSequence.map(({ pane: Pane, props, title }, index) => ( - -

    {title}

    -
    -
    - )) - } + {paneSequence.map(({ pane: Pane, props, title }, index) => ( + +

    {title}

    +
    + +
    +
    + ))} { updateBeingCanceled(true); onCancel() }, + onClick: () => { + updateBeingCanceled(true) + onCancel() + }, text: 'Cancel' }} okayButton={{ @@ -33,7 +36,8 @@ const StackedPaneDisplay = ({ onCancel, paneSequence, title }) => { type: 'submit' }} /> - ) + + ) } StackedPaneDisplay.propTypes = { From 01d34e1f901bbe9f72b5b3ddd213db97cec247a4 Mon Sep 17 00:00:00 2001 From: miles-grant-ibi Date: Wed, 7 Jul 2021 10:09:05 -0400 Subject: [PATCH 89/89] docs(trip-basics-pane.js): add note of caveat of using Prompt component --- lib/components/user/monitored-trip/trip-basics-pane.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/components/user/monitored-trip/trip-basics-pane.js b/lib/components/user/monitored-trip/trip-basics-pane.js index 6c883893d..6d782cc56 100644 --- a/lib/components/user/monitored-trip/trip-basics-pane.js +++ b/lib/components/user/monitored-trip/trip-basics-pane.js @@ -125,6 +125,8 @@ class TripBasicsPane extends Component { return (
    + {/* TODO: This component does not block navigation on reload or using the back button. + This will have to be done at a higher level. See #376 */}