1
+ import coreUtils from '@opentripplanner/core-utils'
1
2
import React , { Component } from 'react'
2
- import { Button } from 'react-bootstrap'
3
3
import { connect } from 'react-redux'
4
+ import styled from 'styled-components'
4
5
5
6
import * as apiActions from '../../actions/api'
7
+ import * as formActions from '../../actions/form'
8
+ import BatchSettingsPanel from '../form/batch-settings-panel'
6
9
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'
8
13
import Icon from '../narrative/icon'
9
14
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'
10
24
import { hasValidLocation , getActiveSearch , getShowUserSettings } from '../../util/state'
11
25
import ViewerContainer from '../viewers/viewer-container'
12
26
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
+
13
96
/**
14
97
* Main panel for the batch/trip comparison form.
15
- * @extends Component
16
98
*/
17
99
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
+
18
124
_planTrip = ( ) => {
19
125
const { currentQuery, routingQuery} = this . props
126
+ // Check for any validation issues in query.
20
127
const issues = [ ]
21
128
if ( ! hasValidLocation ( currentQuery , 'from' ) ) issues . push ( 'from' )
22
129
if ( ! hasValidLocation ( currentQuery , 'to' ) ) issues . push ( 'to' )
@@ -25,18 +132,24 @@ class BatchRoutingPanel extends Component {
25
132
window . alert ( `Please define the following fields to plan a trip: ${ issues . join ( ', ' ) } ` )
26
133
return
27
134
}
135
+ // Close any expanded panels.
136
+ this . setState ( { expanded : null } )
137
+ // Plan trip.
28
138
routingQuery ( )
29
139
}
30
140
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
+
31
147
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
37
150
const actionText = mobile ? 'tap' : 'click'
38
151
return (
39
- < ViewerContainer >
152
+ < ViewerContainer className = 'batch-routing-panel' >
40
153
< LocationField
41
154
inputPlaceholder = { `Enter start location or ${ actionText } on map...` }
42
155
locationType = 'from'
@@ -47,64 +160,70 @@ class BatchRoutingPanel extends Component {
47
160
locationType = 'to'
48
161
showClearButton = { ! mobile }
49
162
/>
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 }
63
174
>
175
+ { coreUtils . query . isNotDefaultQuery ( currentQuery , config ) &&
176
+ < Dot className = 'dot' />
177
+ }
64
178
< 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
81
193
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
+ >
89
196
< 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 >
94
203
}
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' >
96
216
< NarrativeItineraries
97
217
containerStyle = { {
98
218
display : 'flex' ,
99
219
flexDirection : 'column' ,
100
220
position : 'absolute' ,
101
- top : '218px' , // This is variable dependent on height of the form above.
102
221
right : '0' ,
103
222
left : '0' ,
104
223
bottom : '0'
105
224
} }
106
225
/>
107
- </ div >
226
+ </ NarrativeContainer >
108
227
</ ViewerContainer >
109
228
)
110
229
}
@@ -115,14 +234,17 @@ const mapStateToProps = (state, ownProps) => {
115
234
const showUserSettings = getShowUserSettings ( state . otp )
116
235
return {
117
236
activeSearch : getActiveSearch ( state . otp ) ,
237
+ config : state . otp . config ,
118
238
currentQuery : state . otp . currentQuery ,
119
239
expandAdvanced : state . otp . user . expandAdvanced ,
240
+ possibleCombinations : state . otp . config . modes . combinations ,
120
241
showUserSettings
121
242
}
122
243
}
123
244
124
245
const mapDispatchToProps = {
125
- routingQuery : apiActions . routingQuery
246
+ routingQuery : apiActions . routingQuery ,
247
+ setQueryParam : formActions . setQueryParam
126
248
}
127
249
128
250
export default connect ( mapStateToProps , mapDispatchToProps ) ( BatchRoutingPanel )
0 commit comments