1
1
// @flow
2
2
3
3
import Icon from '@conveyal/woonerf/components/icon'
4
- import objectPath from 'object-path'
4
+ // $FlowFixMe coalesce method is missing in flow type
5
+ import { coalesce , get , set } from 'object-path'
5
6
import React , { Component } from 'react'
6
- import { Row , Col , Button , Panel , Glyphicon , Radio , FormGroup , ControlLabel , FormControl } from 'react-bootstrap'
7
+ import {
8
+ Button ,
9
+ Checkbox ,
10
+ Col ,
11
+ ControlLabel ,
12
+ FormControl ,
13
+ FormGroup ,
14
+ Glyphicon ,
15
+ Panel ,
16
+ Radio ,
17
+ Row
18
+ } from 'react-bootstrap'
7
19
import update from 'react-addons-update'
8
20
import { shallowEqual } from 'react-pure-render'
9
21
import { withRouter } from 'react-router'
@@ -25,6 +37,7 @@ type Props = {
25
37
}
26
38
27
39
type State = {
40
+ autoDeploy ?: boolean ,
28
41
buildConfig : Object ,
29
42
routerConfig : Object ,
30
43
useCustomOsmBounds ?: boolean
@@ -34,27 +47,29 @@ class DeploymentSettings extends Component<Props, State> {
34
47
messages = getComponentMessages ( 'DeploymentSettings' )
35
48
36
49
state = {
37
- buildConfig : objectPath . get ( this . props , 'project.buildConfig' ) || { } ,
38
- routerConfig : objectPath . get ( this . props , 'project.routerConfig' ) || { }
50
+ buildConfig : get ( this . props , 'project.buildConfig' ) || { } ,
51
+ routerConfig : get ( this . props , 'project.routerConfig' ) || { }
39
52
}
40
53
41
54
componentWillReceiveProps ( nextProps ) {
42
55
if ( nextProps . project . lastUpdated !== this . props . project . lastUpdated ) {
43
56
// Reset state using project data if it is updated.
44
57
this . setState ( {
45
- buildConfig : objectPath . get ( nextProps , 'project.buildConfig' ) || { } ,
46
- routerConfig : objectPath . get ( nextProps , 'project.routerConfig' ) || { }
58
+ buildConfig : get ( nextProps , 'project.buildConfig' ) || { } ,
59
+ routerConfig : get ( nextProps , 'project.routerConfig' ) || { }
47
60
} )
48
61
}
49
62
}
50
63
51
64
componentDidMount ( ) {
52
- // FIXME: This is broken. Check for edits does not always return correct value.
53
- // this.props.router.setRouteLeaveHook(this.props.route, () => {
54
- // if (!this._noEdits()) {
55
- // return 'You have unsaved information, are you sure you want to leave this page?'
56
- // }
57
- // })
65
+ // $FlowFixMe react-router 3.x is not available in flow-typed.
66
+ const { routes , router } = this . props
67
+ // Check for unsaved edits and warn user if they attempt to navigate away.
68
+ router . setRouteLeaveHook ( routes [ 0 ] , ( ) => {
69
+ if ( ! this . _noEdits ( ) ) {
70
+ return 'You have unsaved information, are you sure you want to leave this page?'
71
+ }
72
+ } )
58
73
}
59
74
60
75
_clearBuildConfig = ( ) => {
@@ -72,7 +87,7 @@ class DeploymentSettings extends Component<Props, State> {
72
87
if ( item ) {
73
88
const stateUpdate = { }
74
89
item . effects && item . effects . forEach ( e => {
75
- objectPath . set ( stateUpdate , `${ e . key } .$set` , e . value )
90
+ set ( stateUpdate , `${ e . key } .$set` , e . value )
76
91
} )
77
92
switch ( item . type ) {
78
93
case 'checkbox' :
@@ -96,19 +111,19 @@ class DeploymentSettings extends Component<Props, State> {
96
111
97
112
_onChangeCheckbox = ( evt , stateUpdate = { } , index = null ) = > {
98
113
const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
99
- objectPath . set ( stateUpdate , `${ name } .$set` , evt . target . checked )
114
+ set ( stateUpdate , `${ name } .$set` , evt . target . checked )
100
115
this . setState ( update ( this . state , stateUpdate ) )
101
116
}
102
117
103
118
_onChangeSplit = ( evt , stateUpdate = { } , index = null ) => {
104
119
const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
105
- objectPath . set ( stateUpdate , `${ name } .$set` , evt . target . value . split ( ',' ) )
120
+ set ( stateUpdate , `${ name } .$set` , evt . target . value . split ( ',' ) )
106
121
this . setState ( update ( this . state , stateUpdate ) )
107
122
}
108
123
109
124
_onAddUpdater = ( ) => {
110
125
const stateUpdate = { }
111
- objectPath . set ( stateUpdate ,
126
+ set ( stateUpdate ,
112
127
`routerConfig.updaters.$${ this . state . routerConfig . updaters ? 'push' : 'set' } ` ,
113
128
[ { type : '' , url : '' , frequencySec : 30 , sourceType : '' , defaultAgencyId : '' } ]
114
129
)
@@ -117,27 +132,27 @@ class DeploymentSettings extends Component<Props, State> {
117
132
118
133
_onRemoveUpdater = ( index ) => {
119
134
const stateUpdate = { }
120
- objectPath . set ( stateUpdate , `routerConfig.updaters.$splice` , [ [ index , 1 ] ] )
135
+ set ( stateUpdate , `routerConfig.updaters.$splice` , [ [ index , 1 ] ] )
121
136
this . setState ( update ( this . state , stateUpdate ) )
122
137
}
123
138
124
139
_onChange = ( evt , stateUpdate = { } , index = null ) => {
125
140
const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
126
141
// If value is empty string or undefined, set to null in settings object.
127
142
// Otherwise, certain fields (such as 'fares') would cause issues with OTP.
128
- objectPath . set ( stateUpdate , `${ name } .$set` , evt . target . value || null )
143
+ set ( stateUpdate , `${ name } .$set` , evt . target . value || null )
129
144
this . setState ( update ( this . state , stateUpdate ) )
130
145
}
131
146
132
147
_onChangeNumber = ( evt , stateUpdate = { } , index = null ) = > {
133
148
const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
134
- objectPath . set ( stateUpdate , `${ name } .$set` , + evt . target . value )
149
+ set ( stateUpdate , `${ name } .$set` , + evt . target . value )
135
150
this . setState ( update ( this . state , stateUpdate ) )
136
151
}
137
152
138
153
_onSelectBool = ( evt , stateUpdate = { } , index = null ) => {
139
154
const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
140
- objectPath . set ( stateUpdate , `${ name } .$set` , ( evt . target . value === 'true' ) )
155
+ set ( stateUpdate , `${ name } .$set` , ( evt . target . value === 'true' ) )
141
156
this . setState ( update ( this . state , stateUpdate ) )
142
157
}
143
158
@@ -149,15 +164,15 @@ class DeploymentSettings extends Component<Props, State> {
149
164
// check for conditional render, e.g. elevationBucket is dependent on fetchElevationUS
150
165
if ( f . condition ) {
151
166
const { key, value} = f . condition
152
- const val = objectPath . get ( state , `${ key } ` )
167
+ const val = get ( state , `${ key } ` )
153
168
if ( val !== value ) return false
154
169
}
155
170
return true
156
171
}
157
172
return (
158
173
< FormInput
159
174
key = { `${ index } ` }
160
- value = { objectPath . get ( state , `${ f . name } ` ) }
175
+ value = { get ( state , `${ f . name } ` ) }
161
176
field = { f }
162
177
onChange = { this . _getOnChange }
163
178
data = { state }
@@ -166,7 +181,13 @@ class DeploymentSettings extends Component<Props, State> {
166
181
} )
167
182
}
168
183
169
- _onSave = ( evt ) => this . props . updateProject ( this . props . project . id , this . state )
184
+ _onSave = ( evt ) = > this . props . updateProject ( this . props . project . id , this . state , true )
185
+
186
+ _onToggleAutoDeploy = ( evt ) => {
187
+ console . log ( evt . target . checked )
188
+ const stateUpdate = { autoDeploy : { $set : evt . target . checked } }
189
+ this . setState ( update ( this . state , stateUpdate ) )
190
+ }
170
191
171
192
_onToggleCustomBounds = ( evt ) => {
172
193
const stateUpdate = { useCustomOsmBounds : { $set : ( evt . target . value === 'true' ) } }
@@ -189,6 +210,12 @@ class DeploymentSettings extends Component<Props, State> {
189
210
}
190
211
}
191
212
213
+ /**
214
+ * Get value for key from state or, if undefined, default to project property
215
+ * from props.
216
+ */
217
+ _getValue = ( key ) => coalesce ( this . state , [ key ] , this . props . project [ key ] )
218
+
192
219
/**
193
220
* Determine if deployment settings have been modified by checking that every
194
221
* item in the state matches the original object found in the project object.
@@ -198,22 +225,55 @@ class DeploymentSettings extends Component<Props, State> {
198
225
. every ( key => shallowEqual ( this . state [ key ] , this . props . project [ key ] ) )
199
226
200
227
render ( ) {
201
- const updaters = objectPath . get ( this . state , 'routerConfig.updaters' ) || [ ]
228
+ const updaters = get ( this . state , 'routerConfig.updaters' ) || [ ]
202
229
const { project , editDisabled } = this . props
230
+ const { pinnedDeploymentId} = project
231
+ const pinnedDeployment = pinnedDeploymentId && project . deployments && project . deployments . find ( d => d . id === pinnedDeploymentId )
203
232
return (
204
233
< div key = { project . lastUpdated } className = 'deployment-settings-panel' >
205
234
< LinkContainer to = { `/admin/servers` } style = { { marginBottom : '20px' } } >
206
235
< Button block bsStyle = 'primary' bsSize = 'large' >
207
- < Icon type = 'server' /> Manage deployment servers
236
+ < Icon type = 'server' /> { this . messages ( 'manageServers' ) }
208
237
</ Button >
209
238
</ LinkContainer >
239
+ { /* Auto-deploy settings */ }
240
+ < Panel header = {
241
+ < h4 >
242
+ < Icon type = 'rocket' /> { ' ' }
243
+ { this . messages ( 'autoDeploy.title' ) }
244
+ </ h4 >
245
+ } >
246
+ < p > { this . messages ( 'autoDeploy.description' ) } </ p >
247
+ < p >
248
+ Pinned Deployment:{ ' ' }
249
+ { pinnedDeployment
250
+ ? pinnedDeployment . name
251
+ : < span className = 'text-muted' >
252
+ { this . messages ( 'autoDeploy.noPinnedDeployment' ) }
253
+ </ span >
254
+ }
255
+ </ p >
256
+ { ! pinnedDeployment &&
257
+ < small className = 'text-danger' >
258
+ { this . messages ( 'autoDeploy.pinnedDeploymentHelp' ) }
259
+ </ small >
260
+ }
261
+ < Checkbox
262
+ checked = { this . _getValue ( 'autoDeploy' ) }
263
+ disabled = { ! pinnedDeployment }
264
+ name = { 'autoDeploy' }
265
+ onChange = { this . _onToggleAutoDeploy }
266
+ >
267
+ { this . messages ( 'autoDeploy.label' ) }
268
+ </ Checkbox >
269
+ </ Panel >
210
270
{ /* Build config settings */ }
211
271
< Panel header = {
212
272
< h4 >
213
273
< Button
214
274
bsSize = 'xsmall'
215
275
onClick = { this . _clearBuildConfig }
216
- className = 'pull-right' > Clear
276
+ className = 'pull-right' > { this . messages ( 'clear' ) }
217
277
</ Button >
218
278
< Icon type = 'cog' /> { ' ' }
219
279
{ this . messages ( 'buildConfig.title' ) }
@@ -227,7 +287,7 @@ class DeploymentSettings extends Component<Props, State> {
227
287
< Button
228
288
bsSize = 'xsmall'
229
289
onClick = { this . _clearRouterConfig }
230
- className = 'pull-right' > Clear
290
+ className = 'pull-right' > { this . messages ( 'clear' ) }
231
291
</ Button >
232
292
< Icon type = 'cog' /> { ' ' }
233
293
{ this . messages ( 'routerConfig.title' ) }
@@ -278,14 +338,14 @@ class DeploymentSettings extends Component<Props, State> {
278
338
< FormGroup
279
339
onChange = { this . _onToggleCustomBounds } >
280
340
< Radio
341
+ checked = { ! this . _getValue ( 'useCustomOsmBounds' ) }
281
342
name = 'osm-extract'
282
- checked = { typeof this . state . useCustomOsmBounds !== 'undefined' ? ! this . state . useCustomOsmBounds : ! project . useCustomOsmBounds }
283
343
value = { false } >
284
344
{ this . messages ( 'osm.gtfs' ) }
285
345
</ Radio >
286
346
< Radio
347
+ checked = { this . _getValue ( 'useCustomOsmBounds' ) }
287
348
name = 'osm-extract'
288
- checked = { typeof this . state . useCustomOsmBounds !== 'undefined' ? this . state . useCustomOsmBounds : project . useCustomOsmBounds }
289
349
value >
290
350
{ this . messages ( 'osm.custom' ) }
291
351
</ Radio >
0 commit comments