Skip to content

Commit 56e4243

Browse files
authored
Merge pull request #307 from opentripplanner/dev
Feature release
2 parents 0a3887d + 05b1b5b commit 56e4243

15 files changed

+646
-86
lines changed

Diff for: example.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
.sidebar {
3434
height: 100%;
3535
padding: 10px;
36-
overflow-y: scroll;
36+
overflow-y: auto;
3737
box-shadow: 3px 0px 12px #00000052;
3838
z-index: 1000;
3939
}

Diff for: lib/actions/api.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ export function routingQuery (searchId = null) {
9898
// For multiple mode combinations, gather injected params from config/query.
9999
// Otherwise, inject nothing (rely on what's in current query) and perform
100100
// one iteration.
101-
const iterations = otpState.config.modes && otpState.config.modes.combinations
102-
? otpState.config.modes.combinations.map(({mode, params}) => ({mode, ...params}))
101+
const iterations = otpState.currentQuery.combinations
102+
? otpState.currentQuery.combinations.map(({mode, params}) => ({mode, ...params}))
103103
: [{}]
104104
dispatch(routingRequest({ activeItinerary, routingType, searchId, pending: iterations.length }))
105105
iterations.forEach((injectedParams, i) => {

Diff for: lib/components/app/batch-routing-panel.js

+175-53
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,129 @@
1+
import coreUtils from '@opentripplanner/core-utils'
12
import React, { Component } from 'react'
2-
import { Button } from 'react-bootstrap'
33
import { connect } from 'react-redux'
4+
import styled from 'styled-components'
45

56
import * as apiActions from '../../actions/api'
7+
import * as formActions from '../../actions/form'
8+
import BatchSettingsPanel from '../form/batch-settings-panel'
69
import LocationField from '../form/connected-location-field'
7-
import UserSettings from '../form/user-settings'
10+
import DateTimeModal from '../form/date-time-modal'
11+
import ModeButtons, {MODE_OPTIONS, StyledModeButton} from '../form/mode-buttons'
12+
// import UserSettings from '../form/user-settings'
813
import Icon from '../narrative/icon'
914
import NarrativeItineraries from '../narrative/narrative-itineraries'
15+
import {
16+
BatchSettingsPanelContainer,
17+
DateTimeModalContainer,
18+
Dot,
19+
MainSettingsRow,
20+
PlanTripButton,
21+
SettingsPreview,
22+
StyledDateTimePreview
23+
} from './styled'
1024
import { hasValidLocation, getActiveSearch, getShowUserSettings } from '../../util/state'
1125
import ViewerContainer from '../viewers/viewer-container'
1226

27+
/**
28+
* Simple utility to check whether a list of mode strings contains the provided
29+
* mode. This handles exact match and prefix/suffix matches (i.e., checking
30+
* 'BICYCLE' will return true if 'BICYCLE' or 'BICYCLE_RENT' is in the list).
31+
*
32+
* FIXME: This might need to be modified to be a bit looser in how it handles
33+
* the 'contains' check. E.g., we might not want to remove WALK,TRANSIT if walk
34+
* is turned off, but we DO want to remove it if TRANSIT is turned off.
35+
*/
36+
function listHasMode (modes, mode) {
37+
return modes.some(m => mode.indexOf(m) !== -1)
38+
}
39+
40+
function combinationHasAnyOfModes (combination, modes) {
41+
return combination.mode.split(',').some(m => listHasMode(modes, m))
42+
}
43+
44+
// List of possible modes that can be selected via mode buttons.
45+
const POSSIBLE_MODES = MODE_OPTIONS.map(b => b.mode)
46+
47+
const ModeButtonsFullWidthContainer = styled.div`
48+
display: flex;
49+
justify-content: space-between;
50+
margin-bottom: 5px;
51+
`
52+
53+
// Define Mode Button styled components here to avoid circular imports. I.e., we
54+
// cannot define them in styled.js (because mode-buttons.js imports buttonCss
55+
// and then we would need to import ModeButtons/StyledModeButton from that file
56+
// in turn).
57+
const StyledModeButtonsFullWidth = styled(ModeButtons)`
58+
&:last-child {
59+
margin-right: 0px;
60+
}
61+
`
62+
63+
const ModeButtonsContainerCompressed = styled.div`
64+
display: contents;
65+
`
66+
67+
const ModeButtonsCompressed = styled(ModeButtons)`
68+
${StyledModeButton} {
69+
border-radius: 0px;
70+
}
71+
&:first-child {
72+
border-radius: 5px 0px 0px 5px;
73+
}
74+
&:last-child {
75+
margin-right: 5px;
76+
border-radius: 0px 5px 5px 0px;
77+
}
78+
`
79+
// Style for setting the top of the narrative itineraries based on the width of the window.
80+
// If the window width is less than 1200px (Bootstrap's "large" size), the
81+
// mode buttons will be shown on their own row, meaning that the
82+
// top position of this component needs to be lower (higher value
83+
// equals lower position on the page).
84+
// TODO: figure out a better way to use flex rendering for accommodating the mode button overflow.
85+
const NarrativeContainer = styled.div`
86+
& .options.itinerary {
87+
@media (min-width: 1200px) {
88+
top: 160px;
89+
}
90+
@media (max-width: 1199px) {
91+
top: 210px;
92+
}
93+
}
94+
`
95+
1396
/**
1497
* Main panel for the batch/trip comparison form.
15-
* @extends Component
1698
*/
1799
class BatchRoutingPanel extends Component {
100+
state = {
101+
expanded: null,
102+
selectedModes: POSSIBLE_MODES
103+
}
104+
105+
_onClickMode = (mode) => {
106+
const {possibleCombinations, setQueryParam} = this.props
107+
const {selectedModes} = this.state
108+
const index = selectedModes.indexOf(mode)
109+
const enableMode = index === -1
110+
const newModes = [...selectedModes]
111+
if (enableMode) newModes.push(mode)
112+
else newModes.splice(index, 1)
113+
// Update selected modes for mode buttons.
114+
this.setState({selectedModes: newModes})
115+
// Update the available mode combinations based on the new modes selection.
116+
const disabledModes = POSSIBLE_MODES.filter(m => !newModes.includes(m))
117+
// Do not include combination if any of its modes are found in disabled
118+
// modes list.
119+
const newCombinations = possibleCombinations
120+
.filter(c => !combinationHasAnyOfModes(c, disabledModes))
121+
setQueryParam({combinations: newCombinations})
122+
}
123+
18124
_planTrip = () => {
19125
const {currentQuery, routingQuery} = this.props
126+
// Check for any validation issues in query.
20127
const issues = []
21128
if (!hasValidLocation(currentQuery, 'from')) issues.push('from')
22129
if (!hasValidLocation(currentQuery, 'to')) issues.push('to')
@@ -25,18 +132,24 @@ class BatchRoutingPanel extends Component {
25132
window.alert(`Please define the following fields to plan a trip: ${issues.join(', ')}`)
26133
return
27134
}
135+
// Close any expanded panels.
136+
this.setState({expanded: null})
137+
// Plan trip.
28138
routingQuery()
29139
}
30140

141+
_updateExpanded = (type) => ({expanded: this.state.expanded === type ? null : type})
142+
143+
_toggleDateTime = () => this.setState(this._updateExpanded('DATE_TIME'))
144+
145+
_toggleSettings = () => this.setState(this._updateExpanded('SETTINGS'))
146+
31147
render () {
32-
const {
33-
activeSearch,
34-
mobile,
35-
showUserSettings
36-
} = this.props
148+
const {config, currentQuery, mobile} = this.props
149+
const {expanded, selectedModes} = this.state
37150
const actionText = mobile ? 'tap' : 'click'
38151
return (
39-
<ViewerContainer>
152+
<ViewerContainer className='batch-routing-panel'>
40153
<LocationField
41154
inputPlaceholder={`Enter start location or ${actionText} on map...`}
42155
locationType='from'
@@ -47,64 +160,70 @@ class BatchRoutingPanel extends Component {
47160
locationType='to'
48161
showClearButton={!mobile}
49162
/>
50-
<div style={{
51-
display: 'flex',
52-
justifyContent: 'flex-start',
53-
flexDirection: 'row',
54-
alignItems: 'center'
55-
}} className='comparison-form'>
56-
<button
57-
style={{
58-
height: '50px',
59-
width: '50px',
60-
margin: '0px',
61-
marginRight: '5px'
62-
}}
163+
<ModeButtonsFullWidthContainer className='hidden-lg'>
164+
<StyledModeButtonsFullWidth
165+
className='flex'
166+
onClick={this._onClickMode}
167+
selectedModes={selectedModes}
168+
/>
169+
</ModeButtonsFullWidthContainer>
170+
<MainSettingsRow>
171+
<SettingsPreview
172+
expanded={expanded === 'SETTINGS'}
173+
onClick={this._toggleSettings}
63174
>
175+
{coreUtils.query.isNotDefaultQuery(currentQuery, config) &&
176+
<Dot className='dot' />
177+
}
64178
<Icon type='cog' className='fa-2x' />
65-
</button>
66-
<button
67-
style={{
68-
height: '50px',
69-
width: '100px',
70-
margin: '0px',
71-
fontSize: 'small',
72-
textAlign: 'left'
73-
}}
74-
>
75-
<Icon type='calendar' /> Today<br />
76-
<Icon type='clock-o' /> Now<br />
77-
</button>
78-
<Button
79-
bsStyle='default'
80-
bsSize='small'
179+
</SettingsPreview>
180+
<StyledDateTimePreview
181+
// as='button'
182+
expanded={expanded === 'DATE_TIME'}
183+
hideButton
184+
onClick={this._toggleDateTime} />
185+
<ModeButtonsContainerCompressed>
186+
<ModeButtonsCompressed
187+
className='visible-lg straight-corners'
188+
onClick={this._onClickMode}
189+
selectedModes={selectedModes}
190+
/>
191+
</ModeButtonsContainerCompressed>
192+
<PlanTripButton
81193
onClick={this._planTrip}
82-
style={{
83-
height: '50px',
84-
width: '50px',
85-
margin: '0px',
86-
marginLeft: 'auto',
87-
backgroundColor: '#F5F5A7'
88-
}} >
194+
title='Plan trip'
195+
>
89196
<Icon type='search' className='fa-2x' />
90-
</Button>
91-
</div>
92-
{!activeSearch && showUserSettings &&
93-
<UserSettings />
197+
</PlanTripButton>
198+
</MainSettingsRow>
199+
{expanded === 'DATE_TIME' &&
200+
<DateTimeModalContainer>
201+
<DateTimeModal />
202+
</DateTimeModalContainer>
94203
}
95-
<div className='desktop-narrative-container'>
204+
{expanded === 'SETTINGS' &&
205+
<BatchSettingsPanelContainer>
206+
<BatchSettingsPanel />
207+
</BatchSettingsPanelContainer>
208+
}
209+
{/* FIXME: Add back user settings (home, work, etc.) once connected to
210+
the middleware persistence.
211+
!activeSearch && showUserSettings &&
212+
<UserSettings />
213+
*/}
214+
{/* TODO: Implement mobile view */}
215+
<NarrativeContainer className='desktop-narrative-container'>
96216
<NarrativeItineraries
97217
containerStyle={{
98218
display: 'flex',
99219
flexDirection: 'column',
100220
position: 'absolute',
101-
top: '218px', // This is variable dependent on height of the form above.
102221
right: '0',
103222
left: '0',
104223
bottom: '0'
105224
}}
106225
/>
107-
</div>
226+
</NarrativeContainer>
108227
</ViewerContainer>
109228
)
110229
}
@@ -115,14 +234,17 @@ const mapStateToProps = (state, ownProps) => {
115234
const showUserSettings = getShowUserSettings(state.otp)
116235
return {
117236
activeSearch: getActiveSearch(state.otp),
237+
config: state.otp.config,
118238
currentQuery: state.otp.currentQuery,
119239
expandAdvanced: state.otp.user.expandAdvanced,
240+
possibleCombinations: state.otp.config.modes.combinations,
120241
showUserSettings
121242
}
122243
}
123244

124245
const mapDispatchToProps = {
125-
routingQuery: apiActions.routingQuery
246+
routingQuery: apiActions.routingQuery,
247+
setQueryParam: formActions.setQueryParam
126248
}
127249

128250
export default connect(mapStateToProps, mapDispatchToProps)(BatchRoutingPanel)

0 commit comments

Comments
 (0)