diff --git a/example-config.yml b/example-config.yml index 408d955ad..37acdcc6a 100644 --- a/example-config.yml +++ b/example-config.yml @@ -294,6 +294,15 @@ itinerary: # defaultRouteTextColor: '000000' # longNameSplitter: ' - ' # order: 2 +# Use this config to overwrite the accessibility score gradation +# map that ships with otp-ui +#accessibilityScore: +# gradationMap: +# 0.0: +# color: "#ffb5b9" +# # The text can be overridden in the language section +# text: 'Not Accessible' +# icon: thumbs-down ### Use this config for the standard mode selector # modeGroups: @@ -319,9 +328,13 @@ itinerary: # common: # accessModes: # bikeshare: Blue Bike -# . config: -# menuItems: -# demo-item: Demo Item +# config: +# acessibilityScore: +# gradationMap: +# 0.0: 'Not Accessible' +# 0.9: 'Mostly Accessible' +# menuItems: +# demo-item: Demo Item ### Localization section to provide language/locale settings #localization: diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index c106c1ff3..128e408dc 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -1,8 +1,9 @@ import coreUtils from '@opentripplanner/core-utils' import React from 'react' -import { FormattedMessage, FormattedNumber, FormattedTime } from 'react-intl' +import { FormattedMessage, FormattedNumber, FormattedTime, injectIntl } from 'react-intl' import { connect } from 'react-redux' import styled from 'styled-components' +import { AccessibilityRating } from '@opentripplanner/itinerary-body' import FieldTripGroupSize from '../../admin/field-trip-itinerary-group-size' import NarrativeItinerary from '../narrative-itinerary' @@ -10,6 +11,11 @@ import ItineraryBody from '../line-itin/connected-itinerary-body' import SimpleRealtimeAnnotation from '../simple-realtime-annotation' import FormattedDuration from '../../util/formatted-duration' import { getTotalFare } from '../../../util/state' +import { + getAccessibilityScoreForItinerary, + itineraryHasAccessibilityScores +} from '../../util/accessibility-routing' +import Icon from '../../util/icon' import ItinerarySummary from './itinerary-summary' @@ -164,6 +170,7 @@ class DefaultItinerary extends NarrativeItinerary { render () { const { + accessibilityScoreGradationMap, active, configCosts, currency, @@ -181,7 +188,9 @@ class DefaultItinerary extends NarrativeItinerary { return (
+ {itineraryHasAccessibilityScores(itinerary) && ( + + )} - {(active && !expanded) && + {active && !expanded && ( - } + )} - {(active && expanded) && + {active && expanded && ( <> {showRealtimeAnnotation && } - + - } + )} ) } } const mapStateToProps = (state, ownProps) => { + const {intl} = ownProps + const gradationMap = state.otp.config.accessibilityScore?.gradationMap + + // Generate icons based on fa icon keys in config + // Override text fields if translation set + gradationMap && Object.keys(gradationMap).forEach(key => { + const {icon} = gradationMap[key] + if (icon && typeof icon === 'string') { + gradationMap[key].icon = + } + + // As these localization keys are in the config, rather than + // standard language files, the message ids must be dynamically generated + const localizationId = `config.acessibilityScore.gradationMap.${key}` + const localizedText = intl.formatMessage({id: localizationId}) + // Override the config label if a localized label exists + if (localizationId !== localizedText) { + gradationMap[key].text = localizedText + } + }) + return { + accessibilityScoreGradationMap: gradationMap, configCosts: state.otp.config.itinerary?.costs, // The configured (ambient) currency is needed for rendering the cost // of itineraries whether they include a fare or not, in which case @@ -250,4 +295,4 @@ const mapStateToProps = (state, ownProps) => { } } -export default connect(mapStateToProps)(DefaultItinerary) +export default injectIntl(connect(mapStateToProps)(DefaultItinerary)) diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js index 4e41498f4..79b8f167b 100644 --- a/lib/components/narrative/line-itin/connected-itinerary-body.js +++ b/lib/components/narrative/line-itin/connected-itinerary-body.js @@ -43,6 +43,7 @@ class ConnectedItineraryBody extends Component { render () { const { + accessibilityScoreGradationMap, config, diagramVisible, itinerary, @@ -56,6 +57,7 @@ class ConnectedItineraryBody extends Component { return ( { + return !!itinerary.legs.find(leg => !!leg.accessibilityScore) +} + +/** + * Calculates the total itinerary score based on leg score by weighting + * each leg equally + */ +export const getAccessibilityScoreForItinerary = (itinerary) => { + const scores = itinerary.legs + .map((leg) => leg.accessibilityScore || null) + .filter((score) => score !== null) + + return scores.reduce((prev, cur) => prev + (cur * (1 / scores.length)), 0) +} diff --git a/package.json b/package.json index 0126801ce..c62a565c0 100644 --- a/package.json +++ b/package.json @@ -40,18 +40,18 @@ "@opentripplanner/geocoder": "^1.1.1", "@opentripplanner/humanize-distance": "^1.1.0", "@opentripplanner/icons": "^1.2.0", - "@opentripplanner/itinerary-body": "^2.4.0", - "@opentripplanner/location-field": "^1.7.0", + "@opentripplanner/itinerary-body": "^2.5.0", + "@opentripplanner/location-field": "^1.7.1", "@opentripplanner/location-icon": "^1.4.0", "@opentripplanner/park-and-ride-overlay": "^1.2.2", - "@opentripplanner/printable-itinerary": "^1.3.0", + "@opentripplanner/printable-itinerary": "^1.3.1", "@opentripplanner/route-viewer-overlay": "^1.1.1", "@opentripplanner/stop-viewer-overlay": "^1.1.1", "@opentripplanner/stops-overlay": "^3.3.1", "@opentripplanner/transit-vehicle-overlay": "^2.3.1", "@opentripplanner/transitive-overlay": "^1.1.2", "@opentripplanner/trip-details": "^1.4.0", - "@opentripplanner/trip-form": "^1.4.0", + "@opentripplanner/trip-form": "^1.6.0", "@opentripplanner/trip-viewer-overlay": "^1.1.1", "@opentripplanner/vehicle-rental-overlay": "^1.2.1", "blob-stream": "^0.1.3", diff --git a/yarn.lock b/yarn.lock index b340ec254..298767336 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1071,7 +1071,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.14.0": +"@babel/runtime@^7.14.0", "@babel/runtime@^7.15.3": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== @@ -1127,6 +1127,15 @@ "@conveyal/lonlat" "^1.3.0" geocoder-arcgis "^2.0.4" +"@conveyal/geocoder-arcgis-geojson@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@conveyal/geocoder-arcgis-geojson/-/geocoder-arcgis-geojson-0.0.3.tgz#0d1381c1d4f0e18bcf0ab1958fc63d148e2d80da" + integrity sha512-eb6EgqzbaUloxYyB44TF6YIefz302azcwbdUgVYUBJY4CzPLqKxBk6c6V+cx7iqxgtug3DqLuABDCSp1wGWuag== + dependencies: + "@conveyal/lonlat" "^1.4.1" + eslint-config-standard-jsx "^10.0.0" + geocoder-arcgis "^2.0.5" + "@conveyal/lonlat@^1.1.2", "@conveyal/lonlat@^1.3.0", "@conveyal/lonlat@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@conveyal/lonlat/-/lonlat-1.4.0.tgz#18a5c1349078a779e710d24af11bc02b24127ba0" @@ -1691,33 +1700,34 @@ "@opentripplanner/core-utils" "^4.1.0" prop-types "^15.7.2" -"@opentripplanner/itinerary-body@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-2.4.0.tgz#46010f813ff341d29b1a8fe96963584b1fbf8312" - integrity sha512-tzGVs7Yg7O7CeSCe2FFshshQz7Xt80bGf3Rjj/ABAXt+DArq6KkUHRdHVcMACn3SwC2DdMPMRVvW6otsm22Azg== +"@opentripplanner/itinerary-body@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-2.5.0.tgz#c533712b7a7f1da2e9625739d0b36a26a5587c99" + integrity sha512-r1ZRUpK0GuvCiRV/3hakQKtt4cQe9xYqUAMghh/V/EhBDa3MAW1mX5bGC/4QUBRa4rmU2HGQKALn5j8qrcDzrA== dependencies: "@opentripplanner/core-utils" "^4.1.0" "@opentripplanner/humanize-distance" "^1.1.0" "@opentripplanner/icons" "^1.1.0" "@opentripplanner/location-icon" "^1.3.0" "@styled-icons/fa-solid" "^10.34.0" + "@styled-icons/foundation" "^10.34.0" currency-formatter "^1.5.5" moment "^2.24.0" prop-types "^15.7.2" react-resize-detector "^4.2.1" velocity-react "^1.4.3" -"@opentripplanner/location-field@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.7.0.tgz#57e808dfdd90c78bb2b8941bb9f8422d02244142" - integrity sha512-xN/cyMueF5cJiGwzjPhST69aqYEZNmWoMgt9lRE/QjhoGRi/20w9/fsuVeqBZe3rb/wOnIro1Uakt3c+utHxWQ== +"@opentripplanner/location-field@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.7.1.tgz#90aa4c06eb13a91a5c95076d33c34ff043fd50b2" + integrity sha512-I98YSf0JXdqax2B9f8zlf7f2f3e3g5nw62zv5lGeC2DdmPb8rwDXskUxqq4c9xzsXW8aMN4+ihkuyMpbhhJiNA== dependencies: + "@conveyal/geocoder-arcgis-geojson" "^0.0.3" "@opentripplanner/core-utils" "^4.2.0" "@opentripplanner/geocoder" "^1.1.1" "@opentripplanner/humanize-distance" "^1.1.0" - "@opentripplanner/location-icon" "^1.3.0" + "@opentripplanner/location-icon" "^1.4.0" "@styled-icons/fa-solid" "^10.34.0" - prop-types "^15.7.2" throttle-debounce "^2.1.0" "@opentripplanner/location-icon@^1.0.1", "@opentripplanner/location-icon@^1.3.0": @@ -1744,10 +1754,10 @@ "@opentripplanner/from-to-location-picker" "^1.2.1" prop-types "^15.7.2" -"@opentripplanner/printable-itinerary@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-1.3.0.tgz#266c1eb945b28939739f4823bfe9a2b78b3ec2c8" - integrity sha512-J/UPhaXtEgpnRcejCLDMPkHngNXSukgRBsG9cFpMQVwIrLLxmG7gkbYxVaLBcyPBKVlqeSnVW+EmmwFVC2bt4w== +"@opentripplanner/printable-itinerary@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-1.3.1.tgz#e82ecfc91ecd5fb8f4ec4d7d3cc1548f33b3da7e" + integrity sha512-3T+r/U+0Pfe6CvUSWbqBBFS5uUyq3T3v19xV3jb7KplsQpYLy4nKjQcqO2/doxf/fVpWZ3XPXAFMntFHqx7pTA== dependencies: "@opentripplanner/core-utils" "^4.1.0" "@opentripplanner/humanize-distance" "^1.1.0" @@ -1815,14 +1825,19 @@ prop-types "^15.7.2" velocity-react "^1.4.3" -"@opentripplanner/trip-form@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.4.0.tgz#aa73c6bc8fc900db4d1e9a621713b89e82cabe19" - integrity sha512-K9a7nsOp8qXc/Gj5AKY/l8gPfanIita29cK0FzfHIQLyC4ap83NlIiGEnSA3pXfEFx0iW5jhmlcuXwTCGXT+rA== +"@opentripplanner/trip-form@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.6.0.tgz#d681b6dc34a9a682bd20dcc5f8a68a621d62cfb5" + integrity sha512-y7jR9HigmS3khepPwSKvJmYdStvBhR9q0QioGY0ErVH+PfW1aIo4V7ClVHo0TGH4vOFSJakRVyrVnOC+ENRQJw== dependencies: "@opentripplanner/core-utils" "^4.1.0" - "@opentripplanner/icons" "^1.1.0" + "@opentripplanner/icons" "^1.2.0" + "@styled-icons/bootstrap" "^10.34.0" + "@styled-icons/boxicons-regular" "^10.38.0" + "@styled-icons/fa-regular" "^10.34.0" + "@styled-icons/fa-solid" "^10.34.0" moment "^2.17.1" + react-indiana-drag-scroll "^2.0.1" "@opentripplanner/trip-viewer-overlay@^1.1.1": version "1.1.1" @@ -1980,6 +1995,14 @@ lodash "^4.17.4" read-pkg-up "^7.0.0" +"@styled-icons/bootstrap@^10.34.0": + version "10.34.0" + resolved "https://registry.yarnpkg.com/@styled-icons/bootstrap/-/bootstrap-10.34.0.tgz#d9142e9eb70dc437f7ef62ffc40168e1ae13ab12" + integrity sha512-UpzdVUR7r9BNqEfPrMchJdgMZEg9eXQxLQJUXM0ouvbI5o9j21/y1dGameO4PZtYbutT/dWv5O6y24z5JWzd5w== + dependencies: + "@babel/runtime" "^7.14.0" + "@styled-icons/styled-icon" "^10.6.3" + "@styled-icons/boxicons-logos@^9.4.1": version "9.4.1" resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-logos/-/boxicons-logos-9.4.1.tgz#774e7f73839e834445dcc0ae4c0c356221767664" @@ -1988,6 +2011,14 @@ "@styled-icons/styled-icon" "^9.4.1" tslib "^1.9.3" +"@styled-icons/boxicons-regular@^10.38.0": + version "10.38.0" + resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-regular/-/boxicons-regular-10.38.0.tgz#1eb80b4f94a18a9b77b11dee5204aa23378d37ec" + integrity sha512-xjhafoa0/EeYtQOyTw+ohuSlBx599t8l8++JH1tQlM0l73dP4icTpF4znYL+HhZeaUe3D+UhrkfWuBBua2w2Qw== + dependencies: + "@babel/runtime" "^7.15.3" + "@styled-icons/styled-icon" "^10.6.3" + "@styled-icons/boxicons-regular@^9.4.1": version "9.4.1" resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-regular/-/boxicons-regular-9.4.1.tgz#742a1d0b5798c54f189b9278810d8110df8f9754" @@ -5008,6 +5039,11 @@ classnames@^2.2.5: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== +classnames@^2.2.6: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + clean-stack@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.1.0.tgz#9e7fec7f3f8340a2ab4f127c80273085e8fbbdd0" @@ -6217,6 +6253,11 @@ debounce@^1.0.0: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== +debounce@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.5.1, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -6721,6 +6762,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +easy-bem@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03" + integrity sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -7079,6 +7125,11 @@ escodegen@~1.2.0: optionalDependencies: source-map "~0.1.30" +eslint-config-standard-jsx@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz#dc24992661325a2e480e2c3091d669f19034e18d" + integrity sha512-hLeA2f5e06W1xyr/93/QJulN/rLbUVUmqTlexv9PRKHFwEC9ffJcH2LvJhMoEqYQBEYafedgGZXH2W8NUpt5lA== + 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" @@ -8251,7 +8302,7 @@ gentle-fs@^2.3.0, gentle-fs@^2.3.1: read-cmd-shim "^1.0.1" slide "^1.1.6" -geocoder-arcgis@^2.0.4: +geocoder-arcgis@^2.0.4, geocoder-arcgis@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/geocoder-arcgis/-/geocoder-arcgis-2.0.5.tgz#61cd1ff2b5b325e6d36b6e7da8138611936b2f26" integrity sha512-KebkgRbjJ5+682DVUswqRrNHGhcCz8CG3kqP21l6xeGWDbaoSkuFzgE+fMHrKs0Ij2NJHe2/R4Z779EaXdR/3w== @@ -14813,6 +14864,15 @@ react-fontawesome@^1.5.0: dependencies: prop-types "^15.5.6" +react-indiana-drag-scroll@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-indiana-drag-scroll/-/react-indiana-drag-scroll-2.1.0.tgz#37654eae8caced01cdecc8bce55f0382871a021d" + integrity sha512-Tj94Dv9PkmoKqc9nxK/dzwhtE8pP7NTxmeHqkD8KN0zad1NNE+/JsvRcK4EdqE6CdA2nMESzqPMvv1AzRXdBew== + dependencies: + classnames "^2.2.6" + debounce "^1.2.0" + easy-bem "^1.1.1" + react-input-autosize@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2"