Skip to content

Commit

Permalink
Merge pull request #1272 from opentripplanner/trip-preview
Browse files Browse the repository at this point in the history
Trip Preview
  • Loading branch information
binh-dam-ibigroup authored Oct 16, 2024
2 parents 89c4258 + 68d2b45 commit e5a4356
Show file tree
Hide file tree
Showing 16 changed files with 386 additions and 147 deletions.
2 changes: 2 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,8 @@ components:
oneHour: 1 hour
realtimeAlertFlagged: There is a realtime alert flagged on my journey
timeBefore: "{time} before"
TripPreviewLayout:
previewTrip: Preview Trip
TripStatus:
alerts: "{alerts, plural, one {# alert!} other {# alerts!}}"
deleteTrip: Delete Trip
Expand Down
2 changes: 2 additions & 0 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ components:
oneHour: 1 heure
realtimeAlertFlagged: Une alerte en temps réel affecte mon trajet
timeBefore: "{time} avant"
TripPreviewLayout:
previewTrip: Aperçu du trajet
TripStatus:
alerts: "{alerts, plural, =0 {# alerte !} one {# alerte !} other {# alertes !}}"
deleteTrip: Supprimer le trajet
Expand Down
129 changes: 20 additions & 109 deletions lib/components/app/print-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,164 +1,75 @@
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import { FormattedMessage, injectIntl, IntlShape } from 'react-intl'
import { Itinerary } from '@opentripplanner/types'
import { Map } from '@styled-icons/fa-solid/Map'
import { Print } from '@styled-icons/fa-solid/Print'
import { Times } from '@styled-icons/fa-solid/Times'
// @ts-expect-error not typescripted yet
import PrintableItinerary from '@opentripplanner/printable-itinerary'
import React, { Component } from 'react'

import * as apiActions from '../../actions/api'
import * as formActions from '../../actions/form'
import {
addPrintViewClassToRootHtml,
clearClassFromRootHtml
} from '../../util/print'
import { ComponentContext } from '../../util/contexts'
import { AppReduxState } from '../../util/state-types'
import { getActiveItinerary, getActiveSearch } from '../../util/state'
import { IconWithText } from '../util/styledIcon'
import { summarizeQuery } from '../form/user-settings-i18n'
import { User } from '../user/types'
import DefaultMap from '../map/default-map'
import PageTitle from '../util/page-title'
import SpanWithSpace from '../util/span-with-space'
import TripDetails from '../narrative/connected-trip-details'

import TripPreviewLayoutBase from './trip-preview-layout-base'

type Props = {
// TODO: Typescript activeSearch type
activeSearch: any
// TODO: Typescript config type
config: any
currentQuery: any
intl: IntlShape
itinerary: Itinerary
location?: { search?: string }
parseUrlQueryString: (params?: any, source?: string) => any
// TODO: Typescript user type
user: any
}

type State = {
mapVisible?: boolean
user: User
}

class PrintLayout extends Component<Props, State> {
static contextType = ComponentContext

constructor(props: Props) {
super(props)
this.state = {
mapVisible: true
}
}

_toggleMap = () => {
this.setState({ mapVisible: !this.state.mapVisible })
}

_print = () => {
window.print()
}

class PrintLayout extends Component<Props> {
_close = () => {
window.location.replace(String(window.location).replace('print/', ''))
}

componentDidMount() {
const { itinerary, location, parseUrlQueryString } = this.props

// Add print-view class to html tag to ensure that iOS scroll fix only applies
// to non-print views.
addPrintViewClassToRootHtml()
// Parse the URL query parameters, if present
if (!itinerary && location && location.search) {
parseUrlQueryString()
}

// TODO: use currentQuery to pan/zoom to the correct part of the map
}

componentWillUnmount() {
clearClassFromRootHtml()
}

render() {
const { activeSearch, config, intl, itinerary, user } = this.props
const { LegIcon } = this.context
const { activeSearch, intl, itinerary, user } = this.props
const printVerb = intl.formatMessage({ id: 'common.forms.print' })

return (
<div className="otp print-layout">
<PageTitle
title={[
printVerb,
activeSearch &&
summarizeQuery(activeSearch.query, intl, user.savedLocations)
]}
/>
{/* The header bar, including the Toggle Map and Print buttons */}
<div className="header">
<div style={{ float: 'right' }}>
<SpanWithSpace margin={0.25}>
<Button
aria-expanded={this.state.mapVisible}
bsSize="small"
onClick={this._toggleMap}
>
<IconWithText Icon={Map}>
<FormattedMessage id="components.PrintLayout.toggleMap" />
</IconWithText>
</Button>
</SpanWithSpace>
<SpanWithSpace margin={0.25}>
<Button bsSize="small" onClick={this._print}>
<IconWithText Icon={Print}>{printVerb}</IconWithText>
</Button>
</SpanWithSpace>
<Button bsSize="small" onClick={this._close} role="link">
<IconWithText Icon={Times}>
<FormattedMessage id="common.forms.close" />
</IconWithText>
</Button>
</div>
<FormattedMessage id="components.PrintLayout.itinerary" />
</div>

{/* The map, if visible */}
{this.state.mapVisible && (
<TripPreviewLayoutBase
header={<FormattedMessage id="components.PrintLayout.itinerary" />}
itinerary={itinerary}
mapElement={
<div className="map-container">
{/* FIXME: Improve reframing/setting map bounds when itinerary is received. */}
<DefaultMap />
</div>
)}

{/* The main itinerary body */}
{itinerary && (
<>
<PrintableItinerary
config={config}
itinerary={itinerary}
LegIcon={LegIcon}
/>
<TripDetails className="percy-hide" itinerary={itinerary} />
</>
)}
</div>
}
onClose={this._close}
subTitle={
activeSearch &&
summarizeQuery(activeSearch.query, intl, user.savedLocations)
}
title={printVerb}
/>
)
}
}

// connect to the redux store

// TODO: Typescript state
const mapStateToProps = (state: any) => {
const mapStateToProps = (state: AppReduxState) => {
const activeSearch = getActiveSearch(state)
const { localUser, loggedInUser } = state.user
const user = loggedInUser || localUser
return {
activeSearch,
config: state.otp.config,
currentQuery: state.otp.currentQuery,
itinerary: getActiveItinerary(state) as Itinerary,
user
}
Expand Down
3 changes: 2 additions & 1 deletion lib/components/app/responsive-webapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import BeforeSignInScreen from '../user/before-signin-screen'
import Map from '../map/map'
import MobileMain from '../mobile/main'
import printRoutes from '../../util/webapp-print-routes'
import tripPreviewRoutes from '../../util/webapp-trip-preview-routes'
import webAppRoutes from '../../util/webapp-routes'
import withLoggedInUserSupport from '../user/with-logged-in-user-support'
import withMap from '../map/with-map'
Expand All @@ -43,7 +44,7 @@ import SessionTimeout from './session-timeout'

const { isMobile } = coreUtils.ui

const routes = [...webAppRoutes, ...printRoutes]
const routes = [...webAppRoutes, ...printRoutes, ...tripPreviewRoutes]

class ResponsiveWebapp extends Component {
static propTypes = {
Expand Down
138 changes: 138 additions & 0 deletions lib/components/app/trip-preview-layout-base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import { FormattedMessage } from 'react-intl'
import { Itinerary } from '@opentripplanner/types'
import { Map } from '@styled-icons/fa-solid/Map'
import { Print } from '@styled-icons/fa-solid/Print'
import { Times } from '@styled-icons/fa-solid/Times'
// @ts-expect-error not typescripted yet
import PrintableItinerary from '@opentripplanner/printable-itinerary'
import React, { Component, ReactNode } from 'react'

import {
addPrintViewClassToRootHtml,
clearClassFromRootHtml
} from '../../util/print'
import { AppConfig } from '../../util/config-types'
import { AppReduxState } from '../../util/state-types'
import { ComponentContext } from '../../util/contexts'
import { IconWithText } from '../util/styledIcon'
import PageTitle from '../util/page-title'
import SpanWithSpace from '../util/span-with-space'
import TripDetails from '../narrative/connected-trip-details'

type Props = {
config: AppConfig
header?: ReactNode
itinerary?: Itinerary
mapElement?: ReactNode
onClose?: () => void
subTitle?: string
title: string
}

type State = {
mapVisible?: boolean
}

class TripPreviewLayoutBase extends Component<Props, State> {
static contextType = ComponentContext

constructor(props: Props) {
super(props)
this.state = {
mapVisible: true
}
}

_toggleMap = () => {
this.setState({ mapVisible: !this.state.mapVisible })
}

_print = () => {
window.print()
}

componentDidUpdate() {
// Add print-view class to html tag to ensure that iOS scroll fix only applies
// to non-print views.
addPrintViewClassToRootHtml()
}

componentWillUnmount() {
clearClassFromRootHtml()
}

render() {
const {
config,
header,
itinerary,
mapElement,
onClose,
subTitle = '',
title
} = this.props
const { LegIcon } = this.context

return (
<div className="otp print-layout">
<PageTitle title={[title, subTitle]} />
{/* The header bar, including the Toggle Map and Print buttons */}
<div className="header">
<div style={{ float: 'right' }}>
<SpanWithSpace margin={0.25}>
<Button
aria-expanded={this.state.mapVisible}
bsSize="small"
onClick={this._toggleMap}
>
<IconWithText Icon={Map}>
<FormattedMessage id="components.PrintLayout.toggleMap" />
</IconWithText>
</Button>
</SpanWithSpace>
<SpanWithSpace margin={0.25}>
<Button bsSize="small" onClick={this._print}>
<IconWithText Icon={Print}>
<FormattedMessage id="common.forms.print" />
</IconWithText>
</Button>
</SpanWithSpace>
{onClose && (
<Button bsSize="small" onClick={onClose} role="link">
<IconWithText Icon={Times}>
<FormattedMessage id="common.forms.close" />
</IconWithText>
</Button>
)}
</div>
{header}
</div>

{/* The map, if visible */}
{this.state.mapVisible && mapElement}

{/* The main itinerary body */}
{itinerary && (
<>
<PrintableItinerary
config={config}
itinerary={itinerary}
LegIcon={LegIcon}
/>
<TripDetails className="percy-hide" itinerary={itinerary} />
</>
)}
</div>
)
}
}

// connect to the redux store

const mapStateToProps = (state: AppReduxState) => ({
config: state.otp.config
})

export default connect(mapStateToProps)(TripPreviewLayoutBase)
Loading

0 comments on commit e5a4356

Please sign in to comment.