Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trip Preview #1272

Merged
merged 21 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4c68565
feat(TripPreviewLayout): Add trip preview path+UI by duplicating prin…
binh-dam-ibigroup Sep 10, 2024
36f166e
refactor(TripPreviewLayout): Display trip from tripId and redux state
binh-dam-ibigroup Sep 10, 2024
9a6cd8c
feat(SavedTripList): Add link to preview trips
binh-dam-ibigroup Sep 11, 2024
dd64366
refactor(SavedTripList): Add trip preview i18n, combine strings
binh-dam-ibigroup Sep 11, 2024
51546dc
refactor(TripPreviewLayout): Remove unneeded code
binh-dam-ibigroup Sep 11, 2024
662651f
refactor(TripPreviewLayout): Update title
binh-dam-ibigroup Sep 11, 2024
f2057ef
refactor(SimpleMap): Introduce component, use with TripPreviewLayout.
binh-dam-ibigroup Sep 11, 2024
ae22791
refactor: Improve types
binh-dam-ibigroup Sep 11, 2024
8d1da24
refactor(webapp-trip-preview-routes): Tweak comments
binh-dam-ibigroup Sep 11, 2024
af5f569
refactor(SimpleMap): Move container out of component
binh-dam-ibigroup Sep 11, 2024
861323a
Merge branch 'dev' into trip-preview
binh-dam-ibigroup Oct 1, 2024
650f390
refactor(TripPreviewLayoutBase): Extract common component between pri…
binh-dam-ibigroup Oct 1, 2024
c5b622e
refactor(SimpleMap): Add checks for null itinerary
binh-dam-ibigroup Oct 1, 2024
6be5b3d
refactor(TripPreviewLayoutBase): Fix types
binh-dam-ibigroup Oct 1, 2024
2db9284
refactor: Remove unused code and tweak comments
binh-dam-ibigroup Oct 1, 2024
5723432
refactor(TripPreviewLayoutBase): Actually render header prop
binh-dam-ibigroup Oct 1, 2024
d827154
style(SimpleMap): Apply code style feedback.
binh-dam-ibigroup Oct 10, 2024
6b97adf
refactor(SimpleMap): Fix types
binh-dam-ibigroup Oct 10, 2024
83cadc9
refactor(SimpleMap): Remove EndpointOverlay handler after updating OT…
binh-dam-ibigroup Oct 11, 2024
8a83ee5
Merge branch 'dev' into trip-preview
binh-dam-ibigroup Oct 14, 2024
68d2b45
Merge branch 'dev' into trip-preview
binh-dam-ibigroup Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,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 @@ -667,6 +667,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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason behind going for a class component here rather than a function component?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy/paste is the reason...

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 && (
miles-grant-ibigroup marked this conversation as resolved.
Show resolved Hide resolved
<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
Loading