diff --git a/lib/components/app/print-layout.js b/lib/components/app/print-layout.js index 0222b0f0c..231c479ae 100644 --- a/lib/components/app/print-layout.js +++ b/lib/components/app/print-layout.js @@ -1,16 +1,17 @@ import BaseMap from '@opentripplanner/base-map' import EndpointsOverlay from '@opentripplanner/endpoints-overlay' +import TriMetLegIcon from '@opentripplanner/icons/lib/trimet-leg-icon' +import PrintableItinerary from '@opentripplanner/printable-itinerary' import TransitiveOverlay from '@opentripplanner/transitive-overlay' import PropTypes from 'prop-types' import React, { Component } from 'react' import { Button } from 'react-bootstrap' import { connect } from 'react-redux' -import PrintableItinerary from '../narrative/printable/printable-itinerary' import { parseUrlQueryString } from '../../actions/form' import { routingQuery } from '../../actions/api' import { getActiveItinerary } from '../../util/state' -import { getTimeFormat } from '../../util/time' +import TripDetails from '../narrative/connected-trip-details' class PrintLayout extends Component { static propTypes = { @@ -34,14 +35,14 @@ class PrintLayout extends Component { } componentDidMount () { - const { location } = this.props + 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') // Parse the URL query parameters, if present if (location && location.search) { - this.props.parseUrlQueryString() + parseUrlQueryString() } } @@ -53,8 +54,24 @@ class PrintLayout extends Component { root.removeAttribute('class') } + /** + * Use one of the customIcons if provided, + * (similar to @opentriplanner/trip-form/ModeIcon) + * otherwise, fall back on TriMetLegIcon. + * TODO: Combine all custom icon rendering in one place. + */ + customLegIcon = icons => { + return function ({leg, props}) { + // Check if there is a custom icon (exact match required). + if (icons && leg.mode in icons) { + return icons[leg.mode] + } + return TriMetLegIcon({leg, props}) + } + } + render () { - const { configCompanies, customIcons, itinerary, timeFormat } = this.props + const { config, customIcons, itinerary } = this.props return (
{/* The header bar, including the Toggle Map and Print buttons */} @@ -82,13 +99,15 @@ class PrintLayout extends Component { } {/* The main itinerary body */} - {itinerary - ? - : null + {itinerary && + <> + + + }
) @@ -99,9 +118,8 @@ class PrintLayout extends Component { const mapStateToProps = (state, ownProps) => { return { - itinerary: getActiveItinerary(state.otp), - configCompanies: state.otp.config.companies, - timeFormat: getTimeFormat(state.otp.config) + config: state.otp.config, + itinerary: getActiveItinerary(state.otp) } } diff --git a/lib/components/narrative/connected-trip-details.js b/lib/components/narrative/connected-trip-details.js new file mode 100644 index 000000000..16183ea1e --- /dev/null +++ b/lib/components/narrative/connected-trip-details.js @@ -0,0 +1,25 @@ +import { connect } from 'react-redux' +import styled from 'styled-components' +import TripDetailsBase from '@opentripplanner/trip-details' + +import { getTimeFormat, getLongDateFormat } from '../../util/time' + +const TripDetails = styled(TripDetailsBase)` + border: 2px solid gray; + border-radius: 0; + padding: 6px 10px; + margin: 16px 0 10px; +` + +// Connect imported TripDetails class to redux store. + +const mapStateToProps = (state, ownProps) => { + return { + routingType: state.otp.currentQuery.routingType, + tnc: state.otp.tnc, + timeFormat: getTimeFormat(state.otp.config), + longDateFormat: getLongDateFormat(state.otp.config) + } +} + +export default connect(mapStateToProps)(TripDetails) diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js index b6b66a227..57f2717de 100644 --- a/lib/components/narrative/default/default-itinerary.js +++ b/lib/components/narrative/default/default-itinerary.js @@ -3,7 +3,7 @@ import React from 'react' import NarrativeItinerary from '../narrative-itinerary' import ItinerarySummary from './itinerary-summary' import ItineraryDetails from './itinerary-details' -import TripDetails from '../trip-details' +import TripDetails from '../connected-trip-details' import TripTools from '../trip-tools' import { formatDuration, formatTime } from '../../../util/time' diff --git a/lib/components/narrative/line-itin/itin-body.js b/lib/components/narrative/line-itin/itin-body.js index c38ced30d..713e02b7b 100644 --- a/lib/components/narrative/line-itin/itin-body.js +++ b/lib/components/narrative/line-itin/itin-body.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import isEqual from 'lodash.isequal' -import TripDetails from '../trip-details' +import TripDetails from '../connected-trip-details' import TripTools from '../trip-tools' import PlaceRow from './place-row' diff --git a/lib/components/narrative/narrative.css b/lib/components/narrative/narrative.css index 21c1ab760..ac8c4effa 100644 --- a/lib/components/narrative/narrative.css +++ b/lib/components/narrative/narrative.css @@ -116,58 +116,6 @@ fontSize: 16px; } -/* TRIP DETAILS */ - -.otp .trip-details { - border: 2px solid gray; - padding: 6px 10px; - margin-top: 16px; -} - -.otp .trip-details .trip-details-header { - font-size: 18px; - font-weight: 600; -} - -.otp .trip-details .trip-detail { - margin-top: 6px; -} - -.otp .trip-details .trip-detail .icon { - float: left; - font-size: 17px; -} - -.otp .trip-details .trip-detail .summary { - margin-left: 28px; - padding-top: 2px; -} - -.otp .trip-details .trip-detail .expand-button { - margin-left: 6px; - margin-top: -2px; - font-size: 16px; - color: blue; -} - -.otp .trip-details .trip-detail .description { - background-color: #fff; - border: 1px solid #888; - padding: 8px; - margin-top: 2px; - font-size: 12px; -} - -.otp .trip-details .trip-detail b { - font-weight: 600; -} - -.otp .trip-details .trip-detail .hide-button { - float: right; - top: 5; - right: 5; -} - /* TRIP TOOLS ROW */ .otp .trip-tools { margin-top: 10px; diff --git a/lib/components/narrative/printable/itinerary.css b/lib/components/narrative/printable/itinerary.css deleted file mode 100644 index 07b6c9dfe..000000000 --- a/lib/components/narrative/printable/itinerary.css +++ /dev/null @@ -1,33 +0,0 @@ -.otp .printable-itinerary .leg { - margin-bottom: 10px; - border-top: 1px solid gray; - padding-top: 18px; -} - -.otp .printable-itinerary .leg.collapse-top { - border-top: none; - padding-top: 0px; -} - -.otp .printable-itinerary .mode-icon { - float: left; - width: 32px; - height: 32px; -} - -.otp .printable-itinerary .leg-body { - margin-left: 40px; -} - -.otp .printable-itinerary .leg-header { - font-size: 18px; -} - -.otp .printable-itinerary .leg-details { - margin-top: 5px; -} - -.otp .printable-itinerary .leg-detail { - margin-top: 3px; - font-size: 14px; -} diff --git a/lib/components/narrative/printable/printable-itinerary.js b/lib/components/narrative/printable/printable-itinerary.js deleted file mode 100644 index ecb13c545..000000000 --- a/lib/components/narrative/printable/printable-itinerary.js +++ /dev/null @@ -1,221 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -import TripDetails from '../trip-details' -import { distanceString } from '../../../util/distance' -import { formatTime, formatDuration } from '../../../util/time' -import { - getCompaniesLabelFromNetworks, - getLegIcon, - getLegModeLabel, - getPlaceName, - getStepDirection, - getStepStreetName, - getTimeZoneOffset -} from '../../../util/itinerary' - -export default class PrintableItinerary extends Component { - static propTypes = { - itinerary: PropTypes.object - } - - render () { - const { - configCompanies, - customIcons, - itinerary, - timeFormat - } = this.props - - const timeOptions = { - format: timeFormat, - offset: getTimeZoneOffset(itinerary) - } - - return ( -
- {itinerary.legs.length > 0 && ( -
-
-
- Depart from {itinerary.legs[0].from.name} -
-
-
- )} - {itinerary.legs.map((leg, k) => leg.transitLeg - ? - : leg.hailedCar - ? - : - )} - -
- ) - } -} - -class TransitLeg extends Component { - static propTypes = { - leg: PropTypes.object - } - - render () { - const { customIcons, leg, interlineFollows, timeOptions } = this.props - - // Handle case of transit leg interlined w/ previous - if (leg.interlineWithPreviousLeg) { - return ( -
-
-
- Continues as{' '} - {leg.routeShortName} {leg.routeLongName}{' '} - to {leg.to.name} -
-
-
- Get off at {leg.to.name}{' '} - at {formatTime(leg.endTime, timeOptions)} -
-
-
-
- ) - } - - return ( -
-
{getLegIcon(leg, customIcons)}
-
-
- {leg.routeShortName} {leg.routeLongName} to {leg.to.name} -
-
-
- Board at {leg.from.name}{' '} - at {formatTime(leg.startTime, timeOptions)} -
-
- {interlineFollows - ? Stay on board at {leg.to.name} - : - Get off at {leg.to.name}{' '} - at {formatTime(leg.endTime, timeOptions)} - - } -
-
-
-
- ) - } -} - -class AccessLeg extends Component { - static propTypes = { - leg: PropTypes.object - } - - render () { - const { configCompanies, customIcons, leg } = this.props - - // calculate leg mode label in a special way for this component - let legModeLabel = getLegModeLabel(leg) - - if (leg.rentedBike) { - // FIXME: Special case for TriMet that needs to be refactored to - // incorporate actual company. - legModeLabel = 'Ride BIKETOWN bike' - } else if (leg.rentedCar) { - // Add extra information to printview that would otherwise clutter up - // other places that use the getLegModeLabel function - const companiesLabel = getCompaniesLabelFromNetworks( - leg.from.networks, - configCompanies - ) - legModeLabel = `Drive ${companiesLabel} ${leg.from.name}` - } else if (leg.rentedVehicle) { - const companiesLabel = getCompaniesLabelFromNetworks( - leg.from.networks, - configCompanies - ) - legModeLabel = `Ride ${companiesLabel} E-scooter` - } - - return ( -
-
{getLegIcon(leg, customIcons)}
-
-
- {legModeLabel}{' '} - {!leg.hailedCar && - leg.distance > 0 && - {distanceString(leg.distance)} } - to {getPlaceName(leg.to, configCompanies)} -
- {!leg.hailedCar && ( -
- {leg.steps.map((step, k) => { - return ( -
- {getStepDirection(step)} on {getStepStreetName(step)} -
- ) - })} -
- )} -
-
- ) - } -} - -class TNCLeg extends Component { - static propTypes = { - leg: PropTypes.object - } - - render () { - const { customIcons, leg } = this.props - const { tncData } = leg - if (!tncData) return null - - return ( -
-
{getLegIcon(leg, customIcons)}
-
-
- Take {tncData.displayName} to {leg.to.name} -
-
-
- Estimated wait time for pickup:{' '} - {formatDuration(tncData.estimatedArrival)} -
-
- Estimated travel time:{' '} - {formatDuration(leg.duration)} (does not account for traffic) -
-
-
-
- ) - } -} diff --git a/lib/components/narrative/trip-details.js b/lib/components/narrative/trip-details.js deleted file mode 100644 index 0b7118d4f..000000000 --- a/lib/components/narrative/trip-details.js +++ /dev/null @@ -1,157 +0,0 @@ -import React, { Component } from 'react' -import { connect } from 'react-redux' -import { Button } from 'react-bootstrap' -import { VelocityTransitionGroup } from 'velocity-react' -import moment from 'moment' - -import { calculateFares, calculatePhysicalActivity, getTimeZoneOffset } from '../../util/itinerary' -import { formatTime, getTimeFormat, getLongDateFormat } from '../../util/time' - -class TripDetails extends Component { - render () { - const { itinerary, timeFormat, longDateFormat } = this.props - const date = moment(itinerary.startTime) - - // process the transit fare - const { centsToString, dollarsToString, maxTNCFare, minTNCFare, transitFare } = calculateFares(itinerary) - let companies - itinerary.legs.forEach(leg => { - if (leg.tncData) { - companies = leg.tncData.company - } - }) - let fare - if (transitFare || minTNCFare) { - fare = ( - - {transitFare && ( - Transit Fare: {centsToString(transitFare)} - )} - {minTNCFare !== 0 && ( - -
- - {companies.toLowerCase()} - {' '} - Fare: {dollarsToString(minTNCFare)} - {dollarsToString(maxTNCFare)} -
- )} -
- ) - } - - // Compute calories burned. - const { bikeDuration, caloriesBurned, walkDuration } = calculatePhysicalActivity(itinerary) - - const timeOptions = { - format: timeFormat, - offset: getTimeZoneOffset(itinerary) - } - - return ( -
-
Trip Details
-
- } - summary={ - - Depart {date.format(longDateFormat)} - {this.props.routingType === 'ITINERARY' && at {formatTime(itinerary.startTime, timeOptions)}} - - } - /> - {fare && ( - } - summary={fare} - /> - )} - {caloriesBurned > 0 && ( - } - summary={Calories Burned: {Math.round(caloriesBurned)}} - description={ - - Calories burned is based on {Math.round(walkDuration / 60)} minute(s){' '} - spent walking and {Math.round(bikeDuration / 60)} minute(s){' '} - spent biking during this trip. Adapted from{' '} - - Dietary Guidelines for Americans 2005, page 16, Table 4 - . - - } - /> - )} -
-
- ) - } -} - -class TripDetail extends Component { - constructor (props) { - super(props) - this.state = { - expanded: false - } - } - - _toggle = () => this.state.expanded ? this._onHideClick() : this._onExpandClick() - - _onExpandClick = () => { - this.setState({ expanded: true }) - } - - _onHideClick = () => { - this.setState({ expanded: false }) - } - - render () { - const { icon, summary, description } = this.props - return ( -
-
{icon}
-
- {summary} - {description && ( - - )} - - {this.state.expanded && ( -
- - {description} -
- )} -
-
-
- ) - } -} - -// Connect main class to redux store - -const mapStateToProps = (state, ownProps) => { - return { - routingType: state.otp.currentQuery.routingType, - tnc: state.otp.tnc, - timeFormat: getTimeFormat(state.otp.config), - longDateFormat: getLongDateFormat(state.otp.config) - } -} - -export default connect(mapStateToProps)(TripDetails) diff --git a/lib/index.css b/lib/index.css index c987003c1..0fe0005bc 100644 --- a/lib/index.css +++ b/lib/index.css @@ -12,7 +12,6 @@ @import url(lib/components/narrative/narrative.css); @import url(lib/components/narrative/default/itinerary.css); @import url(lib/components/narrative/line-itin/itinerary.css); -@import url(lib/components/narrative/printable/itinerary.css); @import url(lib/components/mobile/mobile.css); @import url(lib/components/viewers/viewers.css); diff --git a/lib/index.js b/lib/index.js index 40a8df088..ad1bb1d41 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,7 +22,7 @@ import NarrativeRoutingResults from './components/narrative/narrative-routing-re import RealtimeAnnotation from './components/narrative/realtime-annotation' import SimpleRealtimeAnnotation from './components/narrative/simple-realtime-annotation' import TransportationNetworkCompanyLeg from './components/narrative/default/tnc-leg' -import TripDetails from './components/narrative/trip-details' +import TripDetails from './components/narrative/connected-trip-details' import TripTools from './components/narrative/trip-tools' import LineItinerary from './components/narrative/line-itin/line-itinerary' diff --git a/package.json b/package.json index efce70e74..765b004ed 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,16 @@ "@opentripplanner/endpoints-overlay": "^0.0.18", "@opentripplanner/from-to-location-picker": "^0.0.18", "@opentripplanner/geocoder": "^0.0.18", + "@opentripplanner/icons": "^0.0.18", "@opentripplanner/location-field": "^0.0.18", "@opentripplanner/location-icon": "^0.0.18", "@opentripplanner/park-and-ride-overlay": "^0.0.18", "@opentripplanner/route-viewer-overlay": "^0.0.18", "@opentripplanner/stop-viewer-overlay": "^0.0.18", "@opentripplanner/stops-overlay": "^0.0.18", + "@opentripplanner/printable-itinerary": "^0.0.18", "@opentripplanner/transitive-overlay": "^0.0.18", + "@opentripplanner/trip-details": "^0.0.18", "@opentripplanner/trip-form": "^0.0.18", "@opentripplanner/trip-viewer-overlay": "^0.0.18", "@opentripplanner/vehicle-rental-overlay": "^0.0.18", diff --git a/yarn.lock b/yarn.lock index 17d14b221..7b89dac62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1415,6 +1415,27 @@ dependencies: styled-icons "^9.1.0" +"@opentripplanner/printable-itinerary@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-0.0.18.tgz#4137fa0000f8c1164e5fa4226243e0b887412105" + integrity sha512-uxUm80WU4m8Pni3OzkBzMI/7kmtgRSR7e3wJqy6N9PpZRyEEzUzzSaDmAS8+/nswgZyrNpeenp5ZK5e+7AJA+g== + dependencies: + "@opentripplanner/core-utils" "^0.0.18" + "@opentripplanner/humanize-distance" "^0.0.18" + prop-types "^15.7.2" + +"@opentripplanner/trip-details@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@opentripplanner/trip-details/-/trip-details-0.0.18.tgz#69d8fa4e8a70977c9aaa71a777a34021aec0fa12" + integrity sha512-jCBgmPQhl9gsUniYSbPqDvpyFUHqsGNHUwHg+jqAplPHWu60Hn2F/P1oWf5PmjXlBV7ZFOsuTeH7HVmGfVrPrw== + dependencies: + "@opentripplanner/core-utils" "^0.0.18" + "@opentripplanner/humanize-distance" "^0.0.18" + moment "^2.24.0" + prop-types "^15.7.2" + styled-icons "^9.1.0" + velocity-react "^1.4.3" + "@opentripplanner/park-and-ride-overlay@^0.0.18": version "0.0.18" resolved "https://registry.yarnpkg.com/@opentripplanner/park-and-ride-overlay/-/park-and-ride-overlay-0.0.18.tgz#2e466a4e0ab47531d595c58119371a5a2a11ec7a" @@ -16016,7 +16037,7 @@ velocity-animate@^1.4.0: resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-1.5.2.tgz#5a351d75fca2a92756f5c3867548b873f6c32105" integrity sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg== -velocity-react@^1.3.3: +velocity-react@^1.3.3, velocity-react@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/velocity-react/-/velocity-react-1.4.3.tgz#63e41d92e334d5a3bea8b2fa02ee170f62ef4d36" integrity sha512-zvefGm85A88S3KdF9/dz5vqyFLAiwKYlXGYkHH2EbXl+CZUD1OT0a0aS1tkX/WXWTa/FUYqjBaAzAEFYuSobBQ==