11// @flow
22
33import 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'
56import 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'
719import update from 'react-addons-update'
820import { shallowEqual } from 'react-pure-render'
921import { withRouter } from 'react-router'
@@ -25,6 +37,7 @@ type Props = {
2537}
2638
2739type State = {
40+ autoDeploy ?: boolean ,
2841 buildConfig : Object ,
2942 routerConfig : Object ,
3043 useCustomOsmBounds ?: boolean
@@ -34,27 +47,29 @@ class DeploymentSettings extends Component<Props, State> {
3447 messages = getComponentMessages ( 'DeploymentSettings' )
3548
3649 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' ) || { }
3952 }
4053
4154 componentWillReceiveProps ( nextProps ) {
4255 if ( nextProps . project . lastUpdated !== this . props . project . lastUpdated ) {
4356 // Reset state using project data if it is updated.
4457 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' ) || { }
4760 } )
4861 }
4962 }
5063
5164 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+ } )
5873 }
5974
6075 _clearBuildConfig = ( ) => {
@@ -72,7 +87,7 @@ class DeploymentSettings extends Component<Props, State> {
7287 if ( item ) {
7388 const stateUpdate = { }
7489 item . effects && item . effects . forEach ( e => {
75- objectPath . set ( stateUpdate , `${ e . key } .$set` , e . value )
90+ set ( stateUpdate , `${ e . key } .$set` , e . value )
7691 } )
7792 switch ( item . type ) {
7893 case 'checkbox' :
@@ -96,19 +111,19 @@ class DeploymentSettings extends Component<Props, State> {
96111
97112 _onChangeCheckbox = ( evt , stateUpdate = { } , index = null ) = > {
98113 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 )
100115 this . setState ( update ( this . state , stateUpdate ) )
101116 }
102117
103118 _onChangeSplit = ( evt , stateUpdate = { } , index = null ) => {
104119 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 ( ',' ) )
106121 this . setState ( update ( this . state , stateUpdate ) )
107122 }
108123
109124 _onAddUpdater = ( ) => {
110125 const stateUpdate = { }
111- objectPath . set ( stateUpdate ,
126+ set ( stateUpdate ,
112127 `routerConfig.updaters.$${ this . state . routerConfig . updaters ? 'push' : 'set' } ` ,
113128 [ { type : '' , url : '' , frequencySec : 30 , sourceType : '' , defaultAgencyId : '' } ]
114129 )
@@ -117,27 +132,27 @@ class DeploymentSettings extends Component<Props, State> {
117132
118133 _onRemoveUpdater = ( index ) => {
119134 const stateUpdate = { }
120- objectPath . set ( stateUpdate , `routerConfig.updaters.$splice` , [ [ index , 1 ] ] )
135+ set ( stateUpdate , `routerConfig.updaters.$splice` , [ [ index , 1 ] ] )
121136 this . setState ( update ( this . state , stateUpdate ) )
122137 }
123138
124139 _onChange = ( evt , stateUpdate = { } , index = null ) => {
125140 const name = index !== null ? evt . target . name . replace ( '$index' , `${ index } ` ) : evt . target . name
126141 // If value is empty string or undefined, set to null in settings object.
127142 // 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 )
129144 this . setState ( update ( this . state , stateUpdate ) )
130145 }
131146
132147 _onChangeNumber = ( evt , stateUpdate = { } , index = null ) = > {
133148 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 )
135150 this . setState ( update ( this . state , stateUpdate ) )
136151 }
137152
138153 _onSelectBool = ( evt , stateUpdate = { } , index = null ) => {
139154 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' ) )
141156 this . setState ( update ( this . state , stateUpdate ) )
142157 }
143158
@@ -149,15 +164,15 @@ class DeploymentSettings extends Component<Props, State> {
149164 // check for conditional render, e.g. elevationBucket is dependent on fetchElevationUS
150165 if ( f . condition ) {
151166 const { key, value} = f . condition
152- const val = objectPath . get ( state , `${ key } ` )
167+ const val = get ( state , `${ key } ` )
153168 if ( val !== value ) return false
154169 }
155170 return true
156171 }
157172 return (
158173 < FormInput
159174 key = { `${ index } ` }
160- value = { objectPath . get ( state , `${ f . name } ` ) }
175+ value = { get ( state , `${ f . name } ` ) }
161176 field = { f }
162177 onChange = { this . _getOnChange }
163178 data = { state }
@@ -166,7 +181,13 @@ class DeploymentSettings extends Component<Props, State> {
166181 } )
167182 }
168183
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+ }
170191
171192 _onToggleCustomBounds = ( evt ) => {
172193 const stateUpdate = { useCustomOsmBounds : { $set : ( evt . target . value === 'true' ) } }
@@ -189,6 +210,12 @@ class DeploymentSettings extends Component<Props, State> {
189210 }
190211 }
191212
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+
192219 /**
193220 * Determine if deployment settings have been modified by checking that every
194221 * item in the state matches the original object found in the project object.
@@ -198,22 +225,55 @@ class DeploymentSettings extends Component<Props, State> {
198225 . every ( key => shallowEqual ( this . state [ key ] , this . props . project [ key ] ) )
199226
200227 render ( ) {
201- const updaters = objectPath . get ( this . state , 'routerConfig.updaters' ) || [ ]
228+ const updaters = get ( this . state , 'routerConfig.updaters' ) || [ ]
202229 const { project , editDisabled } = this . props
230+ const { pinnedDeploymentId} = project
231+ const pinnedDeployment = pinnedDeploymentId && project . deployments && project . deployments . find ( d => d . id === pinnedDeploymentId )
203232 return (
204233 < div key = { project . lastUpdated } className = 'deployment-settings-panel' >
205234 < LinkContainer to = { `/admin/servers` } style = { { marginBottom : '20px' } } >
206235 < Button block bsStyle = 'primary' bsSize = 'large' >
207- < Icon type = 'server' /> Manage deployment servers
236+ < Icon type = 'server' /> { this . messages ( 'manageServers' ) }
208237 </ Button >
209238 </ 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 >
210270 { /* Build config settings */ }
211271 < Panel header = {
212272 < h4 >
213273 < Button
214274 bsSize = 'xsmall'
215275 onClick = { this . _clearBuildConfig }
216- className = 'pull-right' > Clear
276+ className = 'pull-right' > { this . messages ( 'clear' ) }
217277 </ Button >
218278 < Icon type = 'cog' /> { ' ' }
219279 { this . messages ( 'buildConfig.title' ) }
@@ -227,7 +287,7 @@ class DeploymentSettings extends Component<Props, State> {
227287 < Button
228288 bsSize = 'xsmall'
229289 onClick = { this . _clearRouterConfig }
230- className = 'pull-right' > Clear
290+ className = 'pull-right' > { this . messages ( 'clear' ) }
231291 </ Button >
232292 < Icon type = 'cog' /> { ' ' }
233293 { this . messages ( 'routerConfig.title' ) }
@@ -278,14 +338,14 @@ class DeploymentSettings extends Component<Props, State> {
278338 < FormGroup
279339 onChange = { this . _onToggleCustomBounds } >
280340 < Radio
341+ checked = { ! this . _getValue ( 'useCustomOsmBounds' ) }
281342 name = 'osm-extract'
282- checked = { typeof this . state . useCustomOsmBounds !== 'undefined' ? ! this . state . useCustomOsmBounds : ! project . useCustomOsmBounds }
283343 value = { false } >
284344 { this . messages ( 'osm.gtfs' ) }
285345 </ Radio >
286346 < Radio
347+ checked = { this . _getValue ( 'useCustomOsmBounds' ) }
287348 name = 'osm-extract'
288- checked = { typeof this . state . useCustomOsmBounds !== 'undefined' ? this . state . useCustomOsmBounds : project . useCustomOsmBounds }
289349 value >
290350 { this . messages ( 'osm.custom' ) }
291351 </ Radio >
0 commit comments