From 47154249de6378c283d81f121060eba2e7dd53c0 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 23 Mar 2017 13:48:06 -0400 Subject: [PATCH 001/265] fix(autofocus): fix components that need autofocus --- lib/common/components/EditableTextField.js | 8 ++++++-- lib/editor/components/timetable/EditableCell.js | 10 ++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/common/components/EditableTextField.js b/lib/common/components/EditableTextField.js index c56bb3fae..31e486e32 100644 --- a/lib/common/components/EditableTextField.js +++ b/lib/common/components/EditableTextField.js @@ -41,7 +41,7 @@ export default class EditableTextField extends Component { } save () { - const value = ReactDOM.findDOMNode(this.refs.input).value + const value = ReactDOM.findDOMNode(this.input).value if (value === this.state.value) { this.cancel() return @@ -112,7 +112,11 @@ export default class EditableTextField extends Component { { + this.input = input + // focus on text input when input is rendered + this.input && ReactDOM.findDOMNode(this.input).focus() + }} type={this.props.type ? this.props.type.toLowerCase() : 'text'} min={this.props.min != null ? this.props.min : null} step={this.props.step != null ? this.props.step : null} diff --git a/lib/editor/components/timetable/EditableCell.js b/lib/editor/components/timetable/EditableCell.js index 993622f73..eb0d33a81 100644 --- a/lib/editor/components/timetable/EditableCell.js +++ b/lib/editor/components/timetable/EditableCell.js @@ -47,7 +47,7 @@ export default class EditableCell extends Component { } } handleKeyDown (evt) { - const input = ReactDOM.findDOMNode(this.refs.cellInput) + const input = ReactDOM.findDOMNode(this.cellInput) switch (evt.keyCode) { case 88: // x if (this.props.renderTime) { @@ -134,6 +134,8 @@ export default class EditableCell extends Component { } } else { let data = this.state.data + console.log(this.state.data) + if (typeof this.state.data !== 'string') return this.cancel() const parts = this.state.data.split(':') const hours = +parts[0] let greaterThan24 = false @@ -233,7 +235,11 @@ export default class EditableCell extends Component { const cellHtml = this.state.isEditing ? { + this.cellInput = input + // focus on cell input when input is rendered + this.cellInput && ReactDOM.findDOMNode(this.cellInput).focus() + }} readOnly={!this.state.isEditing} defaultValue={this.cellRenderer(this.state.data)} placeholder={this.props.placeholder || ''} From f7951eb5b000000133aae194cc2fc1edc9ec40df Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 23 Mar 2017 13:50:41 -0400 Subject: [PATCH 002/265] fix(StopsLayer): remove rbush spatial index in favor of filtering stop lat/lng by bounds --- lib/editor/actions/stop.js | 7 +- lib/editor/components/map/EditorMap.js | 1 + lib/editor/components/map/StopsLayer.js | 129 ++++++++++++------------ lib/editor/reducers/data.js | 1 - lib/editor/reducers/mapState.js | 12 +-- 5 files changed, 70 insertions(+), 80 deletions(-) diff --git a/lib/editor/actions/stop.js b/lib/editor/actions/stop.js index bb29f300c..bce553091 100644 --- a/lib/editor/actions/stop.js +++ b/lib/editor/actions/stop.js @@ -12,11 +12,12 @@ export function savingStop (feedId, stop) { } } -export function receiveStop (feedId, stop) { +export function receiveStop (feedId, stop, stops) { return { type: 'RECEIVE_STOP', feedId, - stop + stop, + stops } } @@ -31,7 +32,7 @@ export function saveStop (feedId, stop) { return secureFetch(url, getState(), method, data) .then(res => res.json()) .then(newStop => { - dispatch(receiveStop(feedId, newStop)) + dispatch(receiveStop(feedId, newStop, getState().editor.data.tables.stop)) // only set active if stop.id === 'new', if id is undefined, do not set active entity if (stop.id === 'new') { dispatch(deletingStop(feedId, stop)) diff --git a/lib/editor/components/map/EditorMap.js b/lib/editor/components/map/EditorMap.js index 9c0f925d7..a759b8d89 100644 --- a/lib/editor/components/map/EditorMap.js +++ b/lib/editor/components/map/EditorMap.js @@ -200,6 +200,7 @@ export default class EditorMap extends Component { return ( r[2].id === activeEntity.id) === -1) { - results.push([0, 0, activeEntity]) - } const outOfZoom = !drawStops - // console.log(mapState.bounds, paddedBounds) return ( - {results - ? results.map(result => { - const stop = result[2] - const isActive = activeEntity && activeEntity.id === stop.id - const busIcon = divIcon({ - html: ` - - - `, - className: '', - iconSize: [24, 24] + {stops + ? stops + .filter(stop => { + if (!paddedBounds) return false + if (stop.stop_lat > paddedBounds.getNorth() || stop.stop_lat < paddedBounds.getSouth() || stop.stop_lon > paddedBounds.getEast() || stop.stop_lon < paddedBounds.getWest()) { + return false + } else { + return true + } }) - const activeBusIcon = divIcon({ - html: ` - - - `, - className: '', - iconSize: [24, 24] + .map(stop => { + const isActive = activeEntity && activeEntity.id === stop.id + const position = isActive ? [activeEntity.stop_lat, activeEntity.stop_lon] : [stop.stop_lat, stop.stop_lon] + const busIcon = divIcon({ + html: ` + + + `, + className: '', + iconSize: [24, 24] + }) + const activeBusIcon = divIcon({ + html: ` + + + `, + className: '', + iconSize: [24, 24] + }) + const hidden = !isActive && outOfZoom + if (hidden) { + return null + } + if (isNaN(stop.stop_lat) || isNaN(stop.stop_lon)) { + return null + } + const marker = ( + { + console.log(e) + const latlng = e.target.getLatLng() + const stopLatLng = clickToLatLng(latlng) + updateActiveEntity(activeEntity, 'stop', stopLatLng) + this.refs[`${stop.id}`].leafletElement.setLatLng(latlng) + }} + onClick={(e) => { + // set active entity + if (!isActive) { + setActiveEntity(feedSource.id, 'stop', stop) + } + }} + /> + ) + return marker }) - const hidden = !isActive && outOfZoom - if (hidden) { - return null - } - if (isNaN(stop.stop_lat) || isNaN(stop.stop_lon)) { - return null - } - const marker = ( - { - console.log(e) - const latlng = e.target.getLatLng() - const stopLatLng = clickToLatLng(latlng) - updateActiveEntity(activeEntity, 'stop', stopLatLng) - this.refs[`${stop.id}`].leafletElement.setLatLng(latlng) - }} - onClick={(e) => { - // set active entity - if (!isActive) { - setActiveEntity(feedSource.id, 'stop', stop) - } - }} - /> - ) - return marker - }) : null } diff --git a/lib/editor/reducers/data.js b/lib/editor/reducers/data.js index 8e9e3e284..df03cb978 100644 --- a/lib/editor/reducers/data.js +++ b/lib/editor/reducers/data.js @@ -395,7 +395,6 @@ const data = (state = defaultState, action) => { } stopIndex = state.tables.stop.findIndex(s => s.id === stop.id) - // TODO: handle adding to rbush tree // TODO: updated sort with stops array // if stop is active, update active entity diff --git a/lib/editor/reducers/mapState.js b/lib/editor/reducers/mapState.js index b2150cf56..7deac5e49 100644 --- a/lib/editor/reducers/mapState.js +++ b/lib/editor/reducers/mapState.js @@ -1,14 +1,12 @@ import update from 'react-addons-update' import { latLngBounds } from 'leaflet' -import rbush from 'rbush' -import { getEntityBounds, stopToGtfs } from '../util/gtfs' +import { getEntityBounds } from '../util/gtfs' const defaultState = { zoom: null, bounds: latLngBounds([[60, 60], [-60, -20]]), - target: null, - stopTree: null + target: null } const mapState = (state = defaultState, action) => { @@ -22,12 +20,6 @@ const mapState = (state = defaultState, action) => { }) } return state - case 'RECEIVE_STOPS': - const tree = rbush(9, ['[0]', '[1]', '[0]', '[1]']) - tree.load(action.stops.map(stopToGtfs).map(s => ([s.stop_lon, s.stop_lat, s]))) - return update(state, { - stopTree: {$set: tree} - }) case 'RECEIVED_ROUTES_SHAPEFILE': return update(state, { routesGeojson: {$set: action.geojson} From b0a479ecc1ff26207bd820c8fc4e221f07f72e91 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 23 Mar 2017 13:52:10 -0400 Subject: [PATCH 003/265] refactor(css): remove blue focus outline from EditableCell --- lib/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/style.css b/lib/style.css index 3daf423d3..ec7d144c6 100644 --- a/lib/style.css +++ b/lib/style.css @@ -150,3 +150,7 @@ width: 1.25em; } } + +/* TIMETABLE EDITOR */ + +.editable-cell:focus {outline:0;} From 4e7d1f21d4a1c0c681f44194a96390c70c96b955 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 23 Mar 2017 13:53:17 -0400 Subject: [PATCH 004/265] style(GlobalGtfsFilter): prefer const --- lib/gtfs/containers/GlobalGtfsFilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gtfs/containers/GlobalGtfsFilter.js b/lib/gtfs/containers/GlobalGtfsFilter.js index b5d8d710b..e84c47501 100644 --- a/lib/gtfs/containers/GlobalGtfsFilter.js +++ b/lib/gtfs/containers/GlobalGtfsFilter.js @@ -22,7 +22,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch, ownProps) => { return { onComponentMount: (initialProps) => { - let filter = initialProps.permissionFilter || 'view-feed' + const filter = initialProps.permissionFilter || 'view-feed' dispatch(updatePermissionFilter(filter)) if (initialProps.project && initialProps.user) { dispatch(updateGtfsFilter(initialProps.project, initialProps.user)) From 35148039df7f5d14e004d08413dda4bd652092e6 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 23 Mar 2017 13:54:14 -0400 Subject: [PATCH 005/265] refactor(GtfsPlusVersionSummary): remove unused listeners --- lib/gtfsplus/components/GtfsPlusVersionSummary.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/gtfsplus/components/GtfsPlusVersionSummary.js b/lib/gtfsplus/components/GtfsPlusVersionSummary.js index fb30a18a9..78bc806df 100644 --- a/lib/gtfsplus/components/GtfsPlusVersionSummary.js +++ b/lib/gtfsplus/components/GtfsPlusVersionSummary.js @@ -60,16 +60,7 @@ export default class GtfsPlusVersionSummary extends Component { const editingIsDisabled = !user.permissions.hasFeedPermission(feedSource.organizationId, feedSource.projectId, feedSource.id, 'edit-gtfs') const publishingIsDisabled = !user.permissions.hasFeedPermission(feedSource.organizationId, feedSource.projectId, feedSource.id, 'approve-gtfs') const header = ( -

{ - if (document.activeElement === e.target && e.which === 13) { - this._toggleExpanded() - } - }} - role='button' - tabIndex={0} - > +

GTFS+ for this Version

) From 7b7eb2cb77f6f5b0e17bb3dda2d03f2ff171d748 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 24 Mar 2017 16:54:00 -0400 Subject: [PATCH 006/265] fix(GtfsPlusTable): add default blank option to GtfsPlus dropdown --- lib/gtfsplus/components/GtfsPlusTable.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/gtfsplus/components/GtfsPlusTable.js b/lib/gtfsplus/components/GtfsPlusTable.js index 08d905dd2..146ebf6f6 100644 --- a/lib/gtfsplus/components/GtfsPlusTable.js +++ b/lib/gtfsplus/components/GtfsPlusTable.js @@ -73,6 +73,11 @@ export default class GtfsPlusTable extends Component { this.props.fieldEdited(table.id, row, field.name, evt.target.value) }} > + {/* Add field for empty string value if that is not an allowable option so that user selection triggers onChange */} + {field.options.findIndex(option => option.value === '') === -1 + ? + : null + } {field.options.map(option => { return
@@ -93,10 +97,9 @@ export default class AlertsList extends Component { {' '} this.props.agencyFilterChanged(evt.target.value)} - > + onChange={this._onAgencyFilterChange}> - {sortedFeeds.map(fs => ( + {feeds.map(fs => ( ))} @@ -106,20 +109,19 @@ export default class AlertsList extends Component { {/* List of alerts */} - {this.props.isFetching - ?

- : this.props.alerts.length - ? this.props.alerts.map((alert) => { - return + : alerts.length + ? alerts.map(alert => ( + - }) + editableFeeds={editableFeeds} + publishableFeeds={publishableFeeds} + onEditClick={onEditClick} + onZoomClick={onZoomClick} + onDeleteClick={onDeleteClick} /> + )) :

No alerts found.

} diff --git a/lib/alerts/components/AlertsViewer.js b/lib/alerts/components/AlertsViewer.js index a236648ea..845c8a91b 100644 --- a/lib/alerts/components/AlertsViewer.js +++ b/lib/alerts/components/AlertsViewer.js @@ -1,6 +1,6 @@ import Icon from '@conveyal/woonerf/components/icon' -import React from 'react' -import { Grid, Row, Col } from 'react-bootstrap' +import React, {Component, PropTypes} from 'react' +import { Button, Grid, Row, Col } from 'react-bootstrap' import ManagerPage from '../../common/components/ManagerPage' import CreateAlert from '../components/CreateAlert' @@ -8,19 +8,33 @@ import VisibleAlertsList from '../containers/VisibleAlertsList' import GlobalGtfsFilter from '../../gtfs/containers/GlobalGtfsFilter' import GtfsMapSearch from '../../gtfs/components/gtfsmapsearch' -export default class AlertsViewer extends React.Component { +export default class AlertsViewer extends Component { + static propTypes = { + activeFeeds: PropTypes.array, + createAlert: PropTypes.func, + fetched: PropTypes.bool, + isFetching: PropTypes.bool, + onStopClick: PropTypes.func, + onRouteClick: PropTypes.func, + project: PropTypes.object, + user: PropTypes.object + } componentWillMount () { this.props.onComponentMount(this.props) } + _onClickRefresh = () => { + this.props.fetchAlerts() + } render () { const { - project, - user, - createAlert, activeFeeds, + createAlert, fetched, + isFetching, onStopClick, - onRouteClick + onRouteClick, + project, + user } = this.props // disable alert creation if user does not have permission or if still fetching alerts (to prevent funky ui behavior) @@ -34,15 +48,24 @@ export default class AlertsViewer extends React.Component { > - +

+ Service Alerts +

+ + +

+ createAlert={createAlert} />

@@ -54,16 +77,14 @@ export default class AlertsViewer extends React.Component { + permissionFilter='edit-alert' /> + popupAction='Create Alert for' />
diff --git a/lib/alerts/containers/ActiveAlertEditor.js b/lib/alerts/containers/ActiveAlertEditor.js index 6438170e0..5bdf5ec1b 100644 --- a/lib/alerts/containers/ActiveAlertEditor.js +++ b/lib/alerts/containers/ActiveAlertEditor.js @@ -23,17 +23,20 @@ import { } from '../actions/activeAlert' import AlertEditor from '../components/AlertEditor' import { getFeedsForPermission } from '../../common/util/permissions' +import {updatePermissionFilter} from '../../gtfs/actions/filter' +import {getActiveFeeds} from '../../gtfs/selectors' import {fetchProjects} from '../../manager/actions/projects' import {getActiveProject} from '../../manager/selectors' const mapStateToProps = (state, ownProps) => { return { + activeFeeds: getActiveFeeds(state), alert: state.alerts.active, - activeFeeds: state.gtfs.filter.activeFeeds, - project: getActiveProject(state), - user: state.user, editableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'edit-alert'), - publishableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'approve-alert') + permissionFilter: state.gtfs.filter.permissionFilter, + project: getActiveProject(state), + publishableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'approve-alert'), + user: state.user } } @@ -61,6 +64,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { } else { dispatch(setActiveAlert(+alertId)) } + if (initialProps.permissionFilter !== 'edit-alert') { + dispatch(updatePermissionFilter('edit-alert')) + } }) }, onSaveClick: (alert) => dispatch(saveAlert(alert)), diff --git a/lib/alerts/containers/MainAlertsViewer.js b/lib/alerts/containers/MainAlertsViewer.js index 7c4367842..b58f18db3 100644 --- a/lib/alerts/containers/MainAlertsViewer.js +++ b/lib/alerts/containers/MainAlertsViewer.js @@ -2,17 +2,21 @@ import { connect } from 'react-redux' import { createAlert, fetchRtdAlerts } from '../actions/alerts' import AlertsViewer from '../components/AlertsViewer' +import {updatePermissionFilter} from '../../gtfs/actions/filter' +import {getAllFeeds, getActiveFeeds} from '../../gtfs/selectors' import {fetchProjects} from '../../manager/actions/projects' import {getActiveProject} from '../../manager/selectors' const mapStateToProps = (state, ownProps) => { return { - activeFeeds: state.gtfs.filter.activeFeeds, - allFeeds: state.gtfs.filter.allFeeds, + activeFeeds: getActiveFeeds(state), alerts: state.alerts.all, + allFeeds: getAllFeeds(state), fetched: state.alerts.fetched, - user: state.user, - project: getActiveProject(state) + isFetching: state.alerts.isFetching, + permissionFilter: state.gtfs.filter.permissionFilter, + project: getActiveProject(state), + user: state.user } } @@ -20,14 +24,21 @@ const mapDispatchToProps = (dispatch, ownProps) => { return { onComponentMount: (initialProps) => { const {alerts, project} = initialProps - if (!alerts || alerts.length === 0 || !project || !project.feedSources) { + if (project && project.feedSources && (!alerts || alerts.length === 0)) { + dispatch(fetchRtdAlerts()) + } + if (!project || !project.feedSources) { dispatch(fetchProjects(true)) .then(project => { return dispatch(fetchRtdAlerts()) }) } + if (initialProps.permissionFilter !== 'edit-alert') { + dispatch(updatePermissionFilter('edit-alert')) + } }, createAlert: () => dispatch(createAlert()), + fetchAlerts: () => dispatch(fetchRtdAlerts()), onStopClick: (stop, agency) => dispatch(createAlert(stop, agency)), onRouteClick: (route, agency) => dispatch(createAlert(route, agency)) } diff --git a/lib/alerts/containers/VisibleAlertsList.js b/lib/alerts/containers/VisibleAlertsList.js index d1ec3b2f1..63d6c6976 100644 --- a/lib/alerts/containers/VisibleAlertsList.js +++ b/lib/alerts/containers/VisibleAlertsList.js @@ -1,41 +1,41 @@ -import { connect } from 'react-redux' - -import { editAlert, deleteAlert } from '../actions/alerts' -import { setVisibilitySearchText, setVisibilityFilter, setAlertAgencyFilter, setAlertSort } from '../actions/visibilityFilter' +import {connect} from 'react-redux' +import { + editAlert, + deleteAlert +} from '../actions/alerts' +import { + setAlertAgencyFilter, + setAlertSort, + setVisibilityFilter, + setVisibilitySearchText +} from '../actions/visibilityFilter' +import {getFeedsForPermission} from '../../common/util/permissions' import AlertsList from '../components/AlertsList' - -import { getFeedsForPermission } from '../../common/util/permissions' import {getActiveProject} from '../../manager/selectors' import {getVisibleAlerts} from '../selectors' -import { FILTERS, filterAlertsByCategory } from '../util' const mapStateToProps = (state, ownProps) => { - // TODO: add filter count to receive alerts reducer - const filterCounts = {} - FILTERS.map(f => { - filterCounts[f] = filterAlertsByCategory(state.alerts.all, f).length - }) + const activeProject = getActiveProject(state) return { + fetched: state.alerts.fetched, isFetching: state.alerts.isFetching, alerts: getVisibleAlerts(state), visibilityFilter: state.alerts.filter, - feeds: getActiveProject(state) && getActiveProject(state).feedSources ? getActiveProject(state).feedSources : [], - editableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'edit-alert'), - publishableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'approve-alert'), - filterCounts + feeds: activeProject && activeProject.feedSources ? activeProject.feedSources : [], + editableFeeds: getFeedsForPermission(activeProject, state.user, 'edit-alert'), + publishableFeeds: getFeedsForPermission(activeProject, state.user, 'approve-alert'), + filterCounts: state.alerts.counts } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onEditClick: (alert) => dispatch(editAlert(alert)), - onDeleteClick: (alert) => dispatch(deleteAlert(alert)), - searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)), - visibilityFilterChanged: (filter) => dispatch(setVisibilityFilter(filter)), - agencyFilterChanged: (agency) => dispatch(setAlertAgencyFilter(agency)), - sortChanged: (sort) => dispatch(setAlertSort(sort)) - } +const mapDispatchToProps = { + onEditClick: editAlert, + onDeleteClick: deleteAlert, + searchTextChanged: setVisibilitySearchText, + visibilityFilterChanged: setVisibilityFilter, + agencyFilterChanged: setAlertAgencyFilter, + sortChanged: setAlertSort } const VisibleAlertsList = connect( diff --git a/lib/alerts/reducers/alerts.js b/lib/alerts/reducers/alerts.js index 32c865ec2..e3b692c6c 100644 --- a/lib/alerts/reducers/alerts.js +++ b/lib/alerts/reducers/alerts.js @@ -1,9 +1,10 @@ import clone from 'lodash.clonedeep' +import update from 'react-addons-update' import mergeable from 'redux-merge-reducers' -import modes from '../modes' -import update from 'react-addons-update' import { getFeedId } from '../../common/util/modules' +import modes from '../modes' +import { FILTERS, filterAlertsByCategory } from '../util' const defaultState = { fetched: false, @@ -13,7 +14,8 @@ const defaultState = { filter: { searchText: null, filter: 'ACTIVE' - } + }, + counts: {} } const alerts = (state = defaultState, action) => { @@ -29,14 +31,11 @@ const alerts = (state = defaultState, action) => { return update(state, {filter: {feedId: {$set: action.feedId}}}) case 'DELETE_ALERT': case 'REQUEST_RTD_ALERTS': - return { - isFetching: true, - all: [], - filter: { - searchText: null, - filter: 'ACTIVE' - } - } + return update(state, { + isFetching: {$set: true}, + fetched: {$set: false}, + all: {$set: []} + }) case 'RECEIVED_GTFS_STOPS_AND_ROUTES': if (action.module !== 'ALERTS') { return state @@ -68,16 +67,12 @@ const alerts = (state = defaultState, action) => { } } - return { - fetched: true, - isFetching: false, - all: alerts, - entities: [], - filter: { - searchText: null, - filter: 'ACTIVE' - } - } + return update(state, { + fetched: {$set: true}, + isFetching: {$set: false}, + all: {$set: alerts}, + entities: {$set: []} + }) case 'RECEIVED_RTD_ALERTS': const entityList = [] @@ -146,16 +141,16 @@ const alerts = (state = defaultState, action) => { return alert }) : [] - - return { - isFetching: false, - all: allAlerts, - entities: entityList, - filter: { - searchText: null, - filter: 'ACTIVE' - } - } + const filterCounts = {} + FILTERS.map(f => { + filterCounts[f] = filterAlertsByCategory(allAlerts, f).length + }) + return update(state, { + isFetching: {$set: false}, + all: {$set: allAlerts}, + entities: {$set: entityList}, + counts: {$set: filterCounts} + }) default: return state diff --git a/lib/alerts/selectors/index.js b/lib/alerts/selectors/index.js index cf6ea0ec5..03839bce2 100644 --- a/lib/alerts/selectors/index.js +++ b/lib/alerts/selectors/index.js @@ -11,12 +11,12 @@ export const getVisibleAlerts = createSelector( alert.title.toLowerCase().indexOf((visibilityFilter.searchText || '').toLowerCase()) !== -1) if (visibilityFilter.feedId && visibilityFilter.feedId !== 'ALL') { - console.log('filtering alerts by feedId' + visibilityFilter.feedId) + // console.log('filtering alerts by feedId' + visibilityFilter.feedId) visibleAlerts = visibleAlerts.filter(alert => alert.affectedEntities.findIndex(ent => getFeedId(ent.agency) === visibilityFilter.feedId) !== -1) } if (visibilityFilter.sort) { - console.log('sorting alerts by ' + visibilityFilter.sort.type + ' direction: ' + visibilityFilter.sort.direction) + // console.log('sorting alerts by ' + visibilityFilter.sort.type + ' direction: ' + visibilityFilter.sort.direction) visibleAlerts = visibleAlerts.sort((a, b) => { var aValue = visibilityFilter.sort.type === 'title' ? a[visibilityFilter.sort.type].toUpperCase() : a[visibilityFilter.sort.type] var bValue = visibilityFilter.sort.type === 'title' ? b[visibilityFilter.sort.type].toUpperCase() : b[visibilityFilter.sort.type] @@ -25,11 +25,8 @@ export const getVisibleAlerts = createSelector( return 0 }) } else { - visibleAlerts.sort((a, b) => { - if (a.id < b.id) return -1 - if (a.id > b.id) return 1 - return 0 - }) + // sort by id + visibleAlerts.sort((a, b) => a.id - b.id) } return filterAlertsByCategory(visibleAlerts, visibilityFilter.filter) } diff --git a/lib/alerts/util/index.js b/lib/alerts/util/index.js index 4f513c73b..265f9fff7 100644 --- a/lib/alerts/util/index.js +++ b/lib/alerts/util/index.js @@ -17,6 +17,17 @@ export const CAUSES = [ 'OTHER_CAUSE' ] +export const SORT_OPTIONS = [ + {value: 'id:asc', label: 'Oldest'}, + {value: 'id:desc', label: 'Newest'}, + {value: 'title:asc', label: 'Title'}, + {value: 'title:desc', label: 'Title (reverse)'}, + {value: 'start:asc', label: 'Starts earliest'}, + {value: 'start:desc', label: 'Starts latest'}, + {value: 'end:asc', label: 'Ends earliest'}, + {value: 'end:desc', label: 'Ends latest'} +] + export const EFFECTS = [ 'UNKNOWN_EFFECT', 'NO_SERVICE', diff --git a/lib/common/util/permissions.js b/lib/common/util/permissions.js index 9aee4cbbc..a4c8bfaef 100644 --- a/lib/common/util/permissions.js +++ b/lib/common/util/permissions.js @@ -1,7 +1,9 @@ export function getFeedsForPermission (project, user, permission) { - return project && project.feedSources ? project.feedSources.filter((feed) => { - return user.permissions.hasFeedPermission(project.organizationId, project.id, feed.id, permission) !== null - }) : [] + return project && project.feedSources + ? project.feedSources.filter((feed) => { + return user.permissions.hasFeedPermission(project.organizationId, project.id, feed.id, permission) !== null + }) + : [] } // ensure list of feeds contains all agency IDs for set of entities diff --git a/lib/common/util/util.js b/lib/common/util/util.js index 1d4066ecf..4b7165e3b 100644 --- a/lib/common/util/util.js +++ b/lib/common/util/util.js @@ -11,8 +11,6 @@ export function defaultSorter (a, b) { } export function versionsSorter (a, b) { - // if (a.isCreating && !b.isCreating) return -1 - // if (!a.isCreating && b.isCreating) return 1 if (a.feedSource.name < b.feedSource.name) return -1 if (a.feedSource.name > b.feedSource.name) return 1 return 0 diff --git a/lib/gtfs/actions/filter.js b/lib/gtfs/actions/filter.js index 1da54b938..2bfc42cba 100644 --- a/lib/gtfs/actions/filter.js +++ b/lib/gtfs/actions/filter.js @@ -1,6 +1,8 @@ import { secureFetch } from '../../common/util/util' import { getFeed } from '../../common/util/modules' -export const updatingGtfsFilter = (activeProject, user) => { +import {getActiveProject} from '../../manager/selectors' + +export function updatingGtfsFilter (activeProject, user) { return { type: 'UPDATE_GTFS_FILTER', activeProject, @@ -25,23 +27,16 @@ export function updateMapState (props) { export function updateGtfsFilter (activeProject, user) { return function (dispatch, getState) { dispatch(updatingGtfsFilter(activeProject, user)) + + // check GTFS API for feed IDs present in cache return secureFetch('/api/manager/feeds', getState()) .then(response => response.json()) .then((feedIds) => { - const activeFeeds = getState().projects.active.feedSources - // if (!activeFeeds) { - // return dispatch(fetchProjectFeeds(getState().projects.active.id)) - // .then((projectFeeds) => { - // console.log(projectFeeds) - // let feeds = feedIds.map(id => getFeed(projectFeeds, id)).filter(n => n) - // console.log(feeds) - // dispatch(updateLoadedFeeds(feeds)) - // }) - // } - // else { + const activeFeeds = getActiveProject(getState()).feedSources + + // filter out null values const feeds = feedIds.map(id => getFeed(activeFeeds, id)).filter(n => n) dispatch(updateLoadedFeeds(feeds)) - // } }) } } diff --git a/lib/gtfs/actions/general.js b/lib/gtfs/actions/general.js index c071ad5d0..1adb17b02 100644 --- a/lib/gtfs/actions/general.js +++ b/lib/gtfs/actions/general.js @@ -4,6 +4,7 @@ import { clearPatterns } from './patterns' import { clearRoutes } from './routes' import { stopsAndRoutes, compose, patternsAndStopsForBoundingBox } from '../util/graphql' import { getFeedId } from '../../common/util/modules' +import {getActiveProject} from '../../manager/selectors' export function clearGtfsElements () { return function (dispatch, getState) { @@ -45,7 +46,7 @@ export const receivedStopsAndRoutes = (results, module) => { } export function fetchStopsAndRoutes (entities, module) { return function (dispatch, getState) { - const activeProject = getState().projects.active + const activeProject = getActiveProject(getState()) const feedId = [] const routeId = [] const stopId = [] @@ -79,6 +80,7 @@ export function refreshGtfsElements (feedId, entities) { return function (dispatch, getState) { dispatch(requestGtfsElements(feedId, entities)) const bounds = getState().gtfs.filter.map.bounds + // if (!bounds) return const maxLat = bounds.getNorth() const maxLon = bounds.getEast() const minLat = bounds.getSouth() diff --git a/lib/gtfs/components/GtfsFilter.js b/lib/gtfs/components/GtfsFilter.js index d6ffc0f50..0edec1aec 100644 --- a/lib/gtfs/components/GtfsFilter.js +++ b/lib/gtfs/components/GtfsFilter.js @@ -1,90 +1,104 @@ -import React from 'react' +import React, {Component, PropTypes} from 'react' import { Button, DropdownButton, MenuItem, Label, ButtonToolbar } from 'react-bootstrap' -export default class GtfsFilter extends React.Component { - componentWillMount () { - this.props.onComponentMount(this.props) +export default class GtfsFilter extends Component { + static propTypes ={ + activeFeeds: PropTypes.array, + activeAndLoadedFeeds: PropTypes.array, + allFeeds: PropTypes.array, + loadedFeeds: PropTypes.array, + onAddAllFeeds: PropTypes.func, + onAddFeed: PropTypes.func, + onRemoveAllFeed: PropTypes.func, + onRemoveFeed: PropTypes.func + } + getFeedSummary (feeds) { + return feeds.length === 0 + ? 'No feeds selected' + : feeds.length < 3 + ? `Searching ${ + feeds.map(feed => feed.name.length > 11 + ? feed.name.substr(0, 11) + '...' + : feed.name).join(' and ') + }` + : `Searching ${feeds.length} feeds` } render () { const { activeFeeds, - loadedFeeds, + activeAndLoadedFeeds, allFeeds, + loadedFeeds, onAddFeed, + onAddAllFeeds, onRemoveFeed, - onRemoveAllFeeds, - onAddAllFeeds + onRemoveAllFeeds } = this.props const buttonMinimalStyle = { marginTop: '10px', marginBottom: '5px' } - var activeAndLoadedFeeds = activeFeeds.filter(f => f && loadedFeeds.findIndex(feed => feed.id === f.id) !== -1) - - var feedLookup = {} - for (const f of allFeeds) feedLookup[f.id] = f return ( feed.name.length > 11 ? feed.name.substr(0, 11) + '...' : feed.name).join(' and ')}` - : `Searching ${activeAndLoadedFeeds.length} feeds`} + title={this.getFeedSummary(activeAndLoadedFeeds)} alt={activeAndLoadedFeeds.join(', ')} onSelect={eventKey => { - const feed = feedLookup[eventKey] + const feed = allFeeds.find(f => f.id === eventKey) activeFeeds.indexOf(feed) === -1 ? onAddFeed(feed) : onRemoveFeed(feed) - }} - > - {allFeeds.map((feed) => { - const isPublished = feed.publishedVersionId !== null - const disabled = loadedFeeds.findIndex(f => f.id === feed.id) === -1 - return ( - - - - {feed.shortName || feed.name.length > 11 ? feed.name.substr(0, 11) + '...' : feed.name} - - - ) - })} + }}> + {allFeeds.length > 0 + ? allFeeds.map((feed) => { + const isPublished = feed.publishedVersionId !== null + const disabled = loadedFeeds.findIndex(f => f.id === feed.id) === -1 + return ( + + + + {feed.shortName || feed.name.length > 11 ? feed.name.substr(0, 11) + '...' : feed.name} + + + ) + }) + : No feeds available + } diff --git a/lib/gtfs/components/GtfsMap.js b/lib/gtfs/components/GtfsMap.js index 4d3066c6f..021a20144 100644 --- a/lib/gtfs/components/GtfsMap.js +++ b/lib/gtfs/components/GtfsMap.js @@ -10,22 +10,24 @@ import { getFeedsBounds } from '../../common/util/geo' export default class GtfsMap extends Component { static propTypes = { - searchFocus: PropTypes.string, bounds: PropTypes.array, + entities: PropTypes.array, feeds: PropTypes.array, - version: PropTypes.object, - onStopClick: PropTypes.func, + height: PropTypes.number, // only px + isochroneBand: PropTypes.number, + newEntityId: PropTypes.number, onRouteClick: PropTypes.func, + onStopClick: PropTypes.func, onZoomChange: PropTypes.func, + pattern: PropTypes.object, + patterns: PropTypes.array, popupAction: PropTypes.string, - newEntityId: PropTypes.number, - entities: PropTypes.array, position: PropTypes.array, - stops: PropTypes.array, - patterns: PropTypes.array, routes: PropTypes.array, - width: PropTypes.string, // % or px - height: PropTypes.number // only px + searchFocus: PropTypes.string, + stops: PropTypes.array, + version: PropTypes.object, + width: PropTypes.string // % or px } constructor (props) { super(props) @@ -109,11 +111,11 @@ export default class GtfsMap extends Component { this.props.refreshGtfsElements(feedIds, ents) } } - renderIsochrones () { + renderIsochrones (isochroneBand, version) { let comps = [] - const bandTime = this.props.isochroneBand || 60 * 60 - if (this.props.version && this.props.version.isochrones && this.props.version.isochrones.features) { - comps = this.props.version.isochrones.features.map((iso, index) => { + const bandTime = isochroneBand || 60 * 60 + if (version && version.isochrones && version.isochrones.features) { + comps = version.isochrones.features.map((iso, index) => { if (iso.properties.time !== bandTime) return null return ( + }} /> ) }) } @@ -136,9 +137,7 @@ export default class GtfsMap extends Component { key='marker' position={this.state.lastClicked} draggable - onDragEnd={(e) => { - this.fetchIsochrones(e.target.getLatLng()) - }} + onDragEnd={(e) => { this.fetchIsochrones(e.target.getLatLng()) }} /> ) } @@ -160,23 +159,25 @@ export default class GtfsMap extends Component { } render () { const { - width, - height, disableScroll, - showBounds, - stops, - routes, feeds, - renderTransferPerformance, - onStopClick, + height, + isochroneBand, newEntityId, - popupAction, - stop, - patterns, onRouteClick, + onStopClick, pattern, + patterns, + popupAction, + renderTransferPerformance, + routes, + showBounds, showIsochrones, - sidebarExpanded + sidebarExpanded, + stop, + stops, + version, + width } = this.props let mapWidth = width if (width.indexOf('px') !== -1) { @@ -189,6 +190,7 @@ export default class GtfsMap extends Component { } const MAPBOX_MAP_ID = process.env.MAPBOX_MAP_ID const MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN + const MAPBOX_ATTRIBUTION = process.env.MAPBOX_ATTRIBUTION return (
this.mapClicked(e)} onMoveEnd={(e) => this.mapMoved(e)} onLayerAdd={(e) => this.layerAddHandler(e)} - className='Gtfs-Map' - > + className='Gtfs-Map'> {/* feed bounds */} {showBounds && @@ -269,7 +270,7 @@ export default class GtfsMap extends Component { {/* Isochrones from map click */} - {showIsochrones && this.renderIsochrones()} + {showIsochrones && this.renderIsochrones(isochroneBand, version)}
diff --git a/lib/gtfs/components/gtfsmapsearch.js b/lib/gtfs/components/gtfsmapsearch.js index cbd067f55..5d7e9cdd3 100644 --- a/lib/gtfs/components/gtfsmapsearch.js +++ b/lib/gtfs/components/gtfsmapsearch.js @@ -1,27 +1,26 @@ import React, { Component, PropTypes } from 'react' +import {connect} from 'react-redux' import fetch from 'isomorphic-fetch' import { Button } from 'react-bootstrap' import ActiveGtfsMap from '../containers/ActiveGtfsMap' import GtfsSearch from './gtfssearch' -export default class GtfsMapSearch extends Component { +class GtfsMapSearch extends Component { static propTypes = { placeholder: PropTypes.string } state = { stop: null, pattern: null, - message: '', - searching: ['stops', 'routes'], - map: {} + searching: ['stops', 'routes'] } getPatterns (input) { return fetch(`/api/manager/patterns?route=${input.route.route_id}&feed=${input.route.feed_id}`) .then((response) => { return response.json() }) - .then((json) => { + .then(json => { const pattern = json[0] // hack to associate route to pattern pattern.route = input.route @@ -45,11 +44,18 @@ export default class GtfsMapSearch extends Component { } } render () { + const { + feeds, + mapState, + placeholder, + onStopClick, + onRouteClick, + newEntityId, + popupAction + } = this.props let zoomMessage = 'Zoom in to view ' + this.state.searching.join(' and ') - if (this.refs.map && this.refs.map.refs.map) { - const mapZoom = this.refs.map.refs.map.leafletElement.getZoom() - zoomMessage = mapZoom <= 13 ? zoomMessage : '' - } + const mapZoom = mapState.zoom + zoomMessage = mapZoom <= 13 ? zoomMessage : '' const searchProps = { stop: this.state.stop, pattern: this.state.pattern, @@ -61,9 +67,9 @@ export default class GtfsMapSearch extends Component {
this.handleSelection(input)} entities={this.state.searching} /> @@ -89,11 +95,11 @@ export default class GtfsMapSearch extends Component { { + return { + mapState: state.gtfs.filter.map + } +} + +const mapDispatchToProps = (dispatch, ownProps) => { + return {} +} + +export default connect(mapStateToProps, mapDispatchToProps)(GtfsMapSearch) diff --git a/lib/gtfs/containers/ActiveGtfsMap.js b/lib/gtfs/containers/ActiveGtfsMap.js index a7773197d..2350490ee 100644 --- a/lib/gtfs/containers/ActiveGtfsMap.js +++ b/lib/gtfs/containers/ActiveGtfsMap.js @@ -2,54 +2,25 @@ import { connect } from 'react-redux' import GtfsMap from '../components/GtfsMap' import { clearGtfsElements, refreshGtfsElements } from '../actions/general' -import { stopPatternFilterChange, stopRouteFilterChange, stopDateTimeFilterChange } from '../actions/stops' import { updateMapState } from '../actions/filter' import { fetchFeedVersionIsochrones } from '../../manager/actions/versions' const mapStateToProps = (state, ownProps) => { return { - stops: state.gtfs.stops.data, - routes: state.gtfs.routes.data, + dateTime: state.gtfs.filter.dateTimeFilter, patterns: state.gtfs.patterns.data, + routes: state.gtfs.routes.data, routing: state.routing.locationBeforeTransitions && state.routing.locationBeforeTransitions.pathname, - dateTime: state.gtfs.filter.dateTimeFilter, - sidebarExpanded: state.ui.sidebarExpanded + sidebarExpanded: state.ui.sidebarExpanded, + stops: state.gtfs.stops.data } } -const mapDispatchToProps = (dispatch, ownProps) => { - const feedId = ownProps.version && ownProps.version.id.replace('.zip', '') - return { - onComponentMount: (initialProps) => { - // if (!initialProps.routes.fetchStatus.fetched) { - // dispatch(fetchRoutes(feedId)) - // } - // if (!initialProps.patterns.fetchStatus.fetched) { - // dispatch(fetchPatterns(feedId, null)) - // } - }, - updateMapState: (props) => { - dispatch(updateMapState(props)) - }, - clearGtfsElements: () => { - dispatch(clearGtfsElements()) - }, - refreshGtfsElements: (feedIds, entities) => { - dispatch(refreshGtfsElements(feedIds, entities)) - }, - stopRouteFilterChange: (newValue) => { - dispatch(stopRouteFilterChange(feedId, newValue)) - }, - stopPatternFilterChange: (newValue) => { - dispatch(stopPatternFilterChange(feedId, newValue)) - }, - stopDateTimeFilterChange: (props) => { - dispatch(stopDateTimeFilterChange(feedId, props)) - }, - fetchIsochrones: (feedVersion, fromLat, fromLon, toLat, toLon, date, fromTime, toTime) => { - dispatch(fetchFeedVersionIsochrones(feedVersion, fromLat, fromLon, toLat, toLon, date, fromTime, toTime)) - } - } +const mapDispatchToProps = { + updateMapState, + clearGtfsElements, + refreshGtfsElements, + fetchIsochrones: fetchFeedVersionIsochrones } const ActiveGtfsMap = connect( diff --git a/lib/gtfs/containers/GlobalGtfsFilter.js b/lib/gtfs/containers/GlobalGtfsFilter.js index e84c47501..0378d07c0 100644 --- a/lib/gtfs/containers/GlobalGtfsFilter.js +++ b/lib/gtfs/containers/GlobalGtfsFilter.js @@ -4,40 +4,29 @@ import GtfsFilter from '../components/GtfsFilter' import { addActiveFeed, removeActiveFeed, addAllActiveFeeds, - removeAllActiveFeeds, - updateGtfsFilter, - updatePermissionFilter + removeAllActiveFeeds } from '../actions/filter' +import {getActiveProject} from '../../manager/selectors' +import {getActiveFeeds, getActiveAndLoadedFeeds, getAllFeeds} from '../../gtfs/selectors' const mapStateToProps = (state, ownProps) => { return { - activeFeeds: state.gtfs.filter.activeFeeds, - allFeeds: state.gtfs.filter.allFeeds, + activeFeeds: getActiveFeeds(state), + allFeeds: getAllFeeds(state), + activeAndLoadedFeeds: getActiveAndLoadedFeeds(state), loadedFeeds: state.gtfs.filter.loadedFeeds, user: state.user, - project: state.projects.active + project: getActiveProject(state) } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onComponentMount: (initialProps) => { - const filter = initialProps.permissionFilter || 'view-feed' - dispatch(updatePermissionFilter(filter)) - if (initialProps.project && initialProps.user) { - dispatch(updateGtfsFilter(initialProps.project, initialProps.user)) - } - }, - onAddFeed: (feed) => dispatch(addActiveFeed(feed)), - onRemoveFeed: (feed) => dispatch(removeActiveFeed(feed)), - onAddAllFeeds: () => dispatch(addAllActiveFeeds()), - onRemoveAllFeeds: () => dispatch(removeAllActiveFeeds()) - } +const mapDispatchToProps = { + onAddFeed: addActiveFeed, + onRemoveFeed: removeActiveFeed, + onAddAllFeeds: addAllActiveFeeds, + onRemoveAllFeeds: removeAllActiveFeeds } -const GlobalGtfsFilter = connect( - mapStateToProps, - mapDispatchToProps -)(GtfsFilter) +const GlobalGtfsFilter = connect(mapStateToProps, mapDispatchToProps)(GtfsFilter) export default GlobalGtfsFilter diff --git a/lib/gtfs/reducers/filter.js b/lib/gtfs/reducers/filter.js index c8ab27eaa..236396dec 100644 --- a/lib/gtfs/reducers/filter.js +++ b/lib/gtfs/reducers/filter.js @@ -1,9 +1,9 @@ import update from 'react-addons-update' import moment from 'moment' -const gtfsFilter = (state = { - allFeeds: [], - activeFeeds: [], +const defaultState = { + project: null, + activeFeeds: {}, loadedFeeds: [], typeFilter: ['stops', 'routes'], map: { @@ -14,13 +14,23 @@ const gtfsFilter = (state = { version: null, dateTimeFilter: { date: moment().format('YYYY-MM-DD'), - from: 60 * 60 * 6, - to: 60 * 60 * 9 + from: 60 * 60 * 6, // 6 AM + to: 60 * 60 * 9 // 9 AM } -}, action) => { - // console.log(action) - let activeFeeds, dateTimeFilter +} +const setAllFeeds = (feeds, value) => { + const activeFeeds = Object.assign({}, feeds) + for (const feedId in activeFeeds) { + if (activeFeeds.hasOwnProperty(feedId)) { + activeFeeds[feedId] = value + } + } + return activeFeeds +} +const gtfsFilter = (state = defaultState, action) => { switch (action.type) { + case 'SET_ACTIVE_PROJECT': + return update(state, { project: { $set: action.project ? action.project.id : null } }) case 'SET_ACTIVE_FEEDVERSION': return update(state, {version: {$set: action.feedVersion ? action.feedVersion.id : null}}) case 'UPDATE_GTFS_MAP_STATE': @@ -28,63 +38,25 @@ const gtfsFilter = (state = { case 'UPDATE_GTFS_PERMISSION_FILTER': return update(state, {permissionFilter: {$set: action.permission}}) case 'UPDATE_GTFS_DATETIME_FILTER': - dateTimeFilter = {...state.dateTimeFilter} + const dateTimeFilter = {...state.dateTimeFilter} for (const key in action.props) { dateTimeFilter[key] = action.props[key] } - return update(state, { - dateTimeFilter: {$set: dateTimeFilter} - }) - case 'UPDATE_GTFS_FILTER': - let userFeeds = [] - if (action.user.permissions.isProjectAdmin(action.activeProject.id, action.activeProject.organizationId)) { - userFeeds = action.activeProject.feedSources || [] - } else if (action.user.permissions.hasProjectPermission(action.activeProject.organizationId, action.activeProject.id, state.permissionFilter)) { - userFeeds = action.activeProject.feedSources ? action.activeProject.feedSources.filter((feed) => { - return action.user.permissions.hasFeedPermission(action.activeProject.organizationId, action.activeProject.id, feed.id, state.permissionFilter) !== null - }) : [] - } - const validatedFeeds = userFeeds.filter((feed) => { - return feed.latestVersionId !== undefined - }) - return update(state, { - allFeeds: {$set: validatedFeeds}, - activeFeeds: {$set: validatedFeeds.sort((a, b) => { - var aName = a.shortName || a.name - var bName = b.shortName || b.name - if (a.name && b.name && aName.toLowerCase() < bName.toLowerCase()) return -1 - if (a.name && b.name && aName.toLowerCase() > bName.toLowerCase()) return 1 - return 0 - })} - }) + return update(state, {dateTimeFilter: {$set: dateTimeFilter}}) + case 'RECEIVE_FEEDSOURCES': + const activeFeeds = {} + action.feedSources && action.feedSources.forEach(fs => { activeFeeds[fs.id] = true }) + return update(state, {activeFeeds: {$set: activeFeeds}}) case 'UPDATE_LOADED_FEEDS': - return update(state, { - loadedFeeds: {$set: action.loadedFeeds} - }) + return update(state, {loadedFeeds: {$set: action.loadedFeeds}}) case 'ADD_ACTIVE_FEED': - activeFeeds = [ - ...state.activeFeeds, - action.feed - ] - return update(state, {activeFeeds: {$set: activeFeeds}}) - + return update(state, {activeFeeds: {[action.feed.id]: {$set: true}}}) case 'REMOVE_ACTIVE_FEED': - const foundIndex = state.activeFeeds.findIndex(f => f.id === action.feed.id) - if (foundIndex !== -1) { - activeFeeds = [ - ...state.activeFeeds.slice(0, foundIndex), - ...state.activeFeeds.slice(foundIndex + 1) - ] - return update(state, {activeFeeds: {$set: activeFeeds}}) - } - return update(state, {activeFeeds: {$set: activeFeeds}}) - + return update(state, {activeFeeds: {[action.feed.id]: {$set: false}}}) case 'ADD_ALL_ACTIVE_FEEDS': - return update(state, {activeFeeds: {$set: state.allFeeds}}) - + return update(state, {activeFeeds: {$set: setAllFeeds(state.activeFeeds, true)}}) case 'REMOVE_ALL_ACTIVE_FEEDS': - return update(state, {activeFeeds: {$set: []}}) - + return update(state, {activeFeeds: {$set: setAllFeeds(state.activeFeeds, false)}}) default: return state } diff --git a/lib/gtfs/selectors/index.js b/lib/gtfs/selectors/index.js new file mode 100644 index 000000000..0d5b204f9 --- /dev/null +++ b/lib/gtfs/selectors/index.js @@ -0,0 +1,27 @@ +import { createSelector } from 'reselect' + +import {getFeedsForPermission} from '../../common/util/permissions' +import {getActiveProject} from '../../manager/selectors' + +const getPermissionFilter = (state) => state.gtfs.filter.permissionFilter + +export const getAllFeeds = createSelector( + [ state => state.user, getPermissionFilter, getActiveProject ], + (user, filter, project) => { + return getFeedsForPermission(project, user, filter) + } +) + +export const getActiveFeeds = createSelector( + [ getAllFeeds, state => state.gtfs.filter.activeFeeds ], + (all, active) => { + return all.filter((feed, index) => active && active[feed.id]) + } +) + +export const getActiveAndLoadedFeeds = createSelector( + [ getActiveFeeds, state => state.gtfs.filter.loadedFeeds ], + (active, loaded) => { + return active.filter(f => f && loaded.findIndex(feed => feed.id === f.id) !== -1) + } +) diff --git a/lib/manager/actions/projects.js b/lib/manager/actions/projects.js index 897f35d09..32e1e6704 100644 --- a/lib/manager/actions/projects.js +++ b/lib/manager/actions/projects.js @@ -1,4 +1,3 @@ -import { getConfigProperty } from '../../common/util/config' import { secureFetch } from '../../common/util/util' import { updateGtfsFilter } from '../../gtfs/actions/filter' import { setErrorMessage, startJobMonitor } from './status' @@ -40,18 +39,18 @@ export function fetchProjects (getActive = false) { dispatch(requestingProjects()) return secureFetch('/api/manager/secure/project', getState()) .then(response => response.json()) - // .catch(err => console.log(err)) .then(projects => { dispatch(receiveProjects(projects)) // return active project if requested if (getActive) { let activeProject = getActiveProject(getState()) - if (!activeProject) { - activeProject = projects.find(proj => proj.id === getConfigProperty('application.active_project')) || - projects[0] - return dispatch(setActiveProject(activeProject)) + if (!activeProject.feedSources) { + return dispatch(fetchProjectFeeds(activeProject.id)) .then(() => { - return activeProject + return dispatch(updateGtfsFilter(getActiveProject(getState()), getState().user)) + .then(() => { + return activeProject + }) }) } } diff --git a/lib/manager/reducers/projects.js b/lib/manager/reducers/projects.js index 8e4b8b390..a2954ec60 100644 --- a/lib/manager/reducers/projects.js +++ b/lib/manager/reducers/projects.js @@ -67,10 +67,13 @@ const projects = (state = { case 'RECEIVE_PROJECTS': activeProjectId = state.active ? state.active.id : getConfigProperty('application.active_project') activeIndex = action.projects.findIndex(p => p.id === activeProjectId) + if (activeIndex === -1) { + activeIndex = 0 + } return { isFetching: false, all: action.projects, - active: activeIndex !== -1 ? action.projects[activeIndex] : null, + active: action.projects[activeIndex] && action.projects[activeIndex].id, filter: { searchText: null } From 6d76b3d083874da3df7697a887e503fc77ef1d35 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 4 Apr 2017 15:11:10 -0400 Subject: [PATCH 031/265] refactor(status): add handleFetchError func --- lib/manager/actions/status.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/manager/actions/status.js b/lib/manager/actions/status.js index 1b86a11c9..093138bc2 100644 --- a/lib/manager/actions/status.js +++ b/lib/manager/actions/status.js @@ -149,3 +149,9 @@ export function handleFinishedJob (job) { } } } + +export function handleFetchError (err) { + return function (dispatch, getState) { + dispatch(setErrorMessage(err)) + } +} From 93a9d60ae0f26d36dbc7cc9404b10788837d7c4e Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Tue, 4 Apr 2017 16:32:53 -0400 Subject: [PATCH 032/265] refactor(signs): use selectors in signs module --- lib/alerts/containers/VisibleAlertsList.js | 5 +- lib/gtfs/components/gtfssearch.js | 4 +- lib/signs/components/DisplaySelector.js | 231 +++++++++++---------- lib/signs/containers/ActiveSignEditor.js | 8 +- lib/signs/containers/MainSignsViewer.js | 10 +- lib/signs/containers/VisibleSignsList.js | 51 ++--- lib/signs/reducers/signs.js | 39 ++-- lib/signs/selectors/index.js | 13 ++ lib/signs/util/index.js | 13 ++ 9 files changed, 197 insertions(+), 177 deletions(-) create mode 100644 lib/signs/selectors/index.js diff --git a/lib/alerts/containers/VisibleAlertsList.js b/lib/alerts/containers/VisibleAlertsList.js index 63d6c6976..771733bd3 100644 --- a/lib/alerts/containers/VisibleAlertsList.js +++ b/lib/alerts/containers/VisibleAlertsList.js @@ -38,9 +38,6 @@ const mapDispatchToProps = { sortChanged: setAlertSort } -const VisibleAlertsList = connect( - mapStateToProps, - mapDispatchToProps -)(AlertsList) +const VisibleAlertsList = connect(mapStateToProps, mapDispatchToProps)(AlertsList) export default VisibleAlertsList diff --git a/lib/gtfs/components/gtfssearch.js b/lib/gtfs/components/gtfssearch.js index 8b02e0302..4d66fac17 100644 --- a/lib/gtfs/components/gtfssearch.js +++ b/lib/gtfs/components/gtfssearch.js @@ -7,7 +7,7 @@ import { getFeed, getFeedId } from '../../common/util/modules' export default class GtfsSearch extends Component { static propTypes = { - value: PropTypes.object + entities: PropTypes.array } constructor (props) { super(props) @@ -38,10 +38,12 @@ export default class GtfsSearch extends Component { ) } + onChange (value) { this.props.onChange && this.props.onChange(value) this.setState({value}) } + render () { const getRouteName = (route) => { const routeName = route.route_short_name && route.route_long_name diff --git a/lib/signs/components/DisplaySelector.js b/lib/signs/components/DisplaySelector.js index 9464d94b1..40f0806e1 100644 --- a/lib/signs/components/DisplaySelector.js +++ b/lib/signs/components/DisplaySelector.js @@ -1,4 +1,4 @@ -import React, { PropTypes } from 'react' +import React, {Component, PropTypes} from 'react' import fetch from 'isomorphic-fetch' import { Glyphicon, Label } from 'react-bootstrap' import { shallowEqual } from 'react-pure-render' @@ -6,7 +6,7 @@ import Select from 'react-select' import { getDisplaysUrl } from '../../common/util/modules' -export default class DisplaySelector extends React.Component { +export default class DisplaySelector extends Component { static propTypes = { sign: PropTypes.object } @@ -26,135 +26,146 @@ export default class DisplaySelector extends React.Component { componentWillReceiveProps (nextProps) { if (!shallowEqual(nextProps.value, this.props.value)) { this.setState({value: nextProps.value}) - console.log('props received', this.state.value) } } - onChange (value) { - this.setState({value}) + + _loadOptions = (input) => { + const url = getDisplaysUrl() + return fetch(url) + .then(res => res.json()) + .then((displays) => { + const options = displays !== null && displays.length > 0 + ? displays.map(display => ( + {display, value: display.Id, label: display.DisplayTitle, location: display.LocationDescription} + )) + : [] + return { options } + }) + .catch((error) => { + console.log(error) + return [] + }) } - render () { - if (!this.props.sign) { - return '' - } - var style = { - marginBottom: '15px' - } - const renderValue = (option) => { - const combinedLabel = getDisplayStatusLabel(option.display) - return {option.label} {combinedLabel} - } - const renderOption = (option) => { - const combinedLabel = getDisplayStatusLabel(option.display) - return {option.label} {option.location} {combinedLabel} {option.link} - } - const getDisplayStatusLabel = (display) => { - if (!display) return '' - const displayDraftId = display.DraftDisplayConfigurationId - const displayPublishedId = display.PublishedDisplayConfigurationId - const label = displayPublishedId !== this.props.sign.id && displayPublishedId > 0 && displayDraftId === this.props.sign.id ? - : displayPublishedId !== this.props.sign.id && displayPublishedId > 0 ? - : displayPublishedId !== null && displayDraftId !== null ? - : displayDraftId === null ? - : displayDraftId !== this.props.sign.id && displayDraftId > 0 ? - : - return label + _onChange = (value) => { + console.log('new value', value) + if (value.length && value[value.length - 1] && value[value.length - 1].create === true) { + console.log('creating display!!!') + this.props.createDisplay(value[value.length - 1].value) + return } - const handleValueClick = (val) => { - // Toggle value of draft/published config ID - const pubId = val.display.PublishedDisplayConfigurationId - const draftId = val.display.DraftDisplayConfigurationId + this.setState({value}) + this.props.onChange(value) + } - // allow publishing or unpublishing to display if sign config is published AND display isn't assigned elsewhere - if (this.props.sign.published && (pubId === null || pubId === this.props.sign.id)) { - const newPubId = pubId ? null - : this.props.sign.id - this.props.toggleConfigForDisplay(val.display, 'PUBLISHED', newPubId) - // if (newPub) - // this.props.toggleConfigForDisplay(val.display, 'DRAFT', null) - } else if (pubId === this.props.sign.id) { // else already published to this config (but config not published), you can set it to null - this.props.toggleConfigForDisplay(val.display, 'PUBLISHED', null) - } else { // if config is draft, you can toggle draftId - const newDraftId = draftId ? null - : this.props.sign.id - this.props.toggleConfigForDisplay(val.display, 'DRAFT', newDraftId) - } - } - const filterOptions = (options, filter, values) => { - // Filter already selected values - const valueKeys = values.map(i => i.value) - let filteredOptions = options.filter(option => { - return valueKeys.indexOf(option.value) === -1 - }) + _onFocus = (input) => { + // clear options to onFocus to ensure only valid route/stop combinations are selected + this.refs.displaySelect.loadOptions('') + } - // Filter by label - if (filter !== undefined && filter != null && filter.length > 0) { - filteredOptions = filteredOptions.filter(option => { - return RegExp(filter, 'ig').test(option.label) - }) - } + _renderOption = (option) => { + const combinedLabel = this.getDisplayStatusLabel(option.display) + return {option.label} {option.location} {combinedLabel} {option.link} + } + + _renderValue = (option) => { + const combinedLabel = this.getDisplayStatusLabel(option.display) + return {option.label} {combinedLabel} + } + + getDisplayStatusLabel = (display) => { + if (!display) return '' + const displayDraftId = display.DraftDisplayConfigurationId + const displayPublishedId = display.PublishedDisplayConfigurationId - // Append Addition option - if (filteredOptions.length === 0) { - filteredOptions.push({ - label: Create display: {filter}, - value: filter, - create: true - }) - } + const label = displayPublishedId !== this.props.sign.id && displayPublishedId > 0 && displayDraftId === this.props.sign.id + ? + : displayPublishedId !== this.props.sign.id && displayPublishedId > 0 + ? + : displayPublishedId !== null && displayDraftId !== null + ? + : displayDraftId === null + ? + : displayDraftId !== this.props.sign.id && displayDraftId > 0 + ? + : + return label + } + + handleValueClick = (val) => { + // Toggle value of draft/published config ID + const pubId = val.display.PublishedDisplayConfigurationId + const draftId = val.display.DraftDisplayConfigurationId - return filteredOptions + // allow publishing or unpublishing to display if sign config is published AND display isn't assigned elsewhere + if (this.props.sign.published && (pubId === null || pubId === this.props.sign.id)) { + const newPubId = pubId + ? null + : this.props.sign.id + this.props.toggleConfigForDisplay(val.display, 'PUBLISHED', newPubId) + } else if (pubId === this.props.sign.id) { + // else already published to this config (but config not published), you can set it to null + this.props.toggleConfigForDisplay(val.display, 'PUBLISHED', null) + } else { + // if config is draft, you can toggle draftId + const newDraftId = draftId + ? null + : this.props.sign.id + this.props.toggleConfigForDisplay(val.display, 'DRAFT', newDraftId) } - const getDisplays = (input) => { - const url = getDisplaysUrl() - return fetch(url) - .then((response) => { - return response.json() - }) - .then((displays) => { - const displayOptions = displays !== null && displays.length > 0 ? displays.map(display => ({display, value: display.Id, label: display.DisplayTitle, location: display.LocationDescription})) : [] - return { options: displayOptions } - }) - .catch((error) => { - console.log(error) - return [] - }) + } + + filterOptions = (options, filter, values) => { + // Filter already selected values + const valueKeys = values.map(i => i.value) + let filteredOptions = options.filter(option => { + return valueKeys.indexOf(option.value) === -1 + }) + // Filter by label + if (filter !== undefined && filter != null && filter.length > 0) { + filteredOptions = filteredOptions.filter(option => { + return RegExp(filter, 'ig').test(option.label) + }) } - const handleChange = (input) => { - console.log('new value', input) - if (input.length && input[input.length - 1] && input[input.length - 1].create === true) { - console.log('creating display!!!') - this.props.createDisplay(input[input.length - 1].value) - return - } - this.onChange(input) - this.props.onChange(input) + // Append Addition option + if (filteredOptions.length === 0) { + filteredOptions.push({ + label: Create display: {filter}, + value: filter, + create: true + }) } + return filteredOptions + } - const onFocus = (input) => { - // clear options to onFocus to ensure only valid route/stop combinations are selected - this.refs.displaySelect.loadOptions('') + render () { + const { + sign, + minimumInput, + clearable, + placeholder + } = this.props + if (!sign) { + return '' } - - const placeholder = '' return ( + optionRenderer={this._renderOption} + valueRenderer={this._renderValue} + onChange={this._onChange} /> ) } } diff --git a/lib/signs/containers/ActiveSignEditor.js b/lib/signs/containers/ActiveSignEditor.js index eb1336c9d..f95e64d9f 100644 --- a/lib/signs/containers/ActiveSignEditor.js +++ b/lib/signs/containers/ActiveSignEditor.js @@ -25,16 +25,19 @@ import { } from '../actions/activeSign' import SignEditor from '../components/SignEditor' import { getFeedsForPermission } from '../../common/util/permissions' +import {updatePermissionFilter} from '../../gtfs/actions/filter' +import {getActiveFeeds} from '../../gtfs/selectors' import { fetchProjects } from '../../manager/actions/projects' import {getActiveProject} from '../../manager/selectors' const mapStateToProps = (state, ownProps) => { return { sign: state.signs.active, - activeFeeds: state.gtfs.filter.activeFeeds, + activeFeeds: getActiveFeeds(state), project: getActiveProject(state), user: state.user, editableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'edit-etid'), + permissionFilter: state.gtfs.filter.permissionFilter, publishableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'approve-etid') } } @@ -65,6 +68,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { dispatch(setActiveSign(+signId)) } }) + if (initialProps.permissionFilter !== 'edit-etid') { + dispatch(updatePermissionFilter('edit-etid')) + } }, createDisplay: (displayName) => dispatch(createDisplay(displayName)), updateDisplays: (displayList) => dispatch(updateDisplays(displayList)), diff --git a/lib/signs/containers/MainSignsViewer.js b/lib/signs/containers/MainSignsViewer.js index 4d67d07b7..11b960358 100644 --- a/lib/signs/containers/MainSignsViewer.js +++ b/lib/signs/containers/MainSignsViewer.js @@ -2,14 +2,17 @@ import { connect } from 'react-redux' import SignsViewer from '../components/SignsViewer' import { createSign, fetchRtdSigns } from '../actions/signs' +import {updatePermissionFilter} from '../../gtfs/actions/filter' +import {getAllFeeds, getActiveFeeds} from '../../gtfs/selectors' import { fetchProjects } from '../../manager/actions/projects' import {getActiveProject} from '../../manager/selectors' const mapStateToProps = (state, ownProps) => { return { - activeFeeds: state.gtfs.filter.activeFeeds, - allFeeds: state.gtfs.filter.allFeeds, + activeFeeds: getActiveFeeds(state), + allFeeds: getAllFeeds(state), fetched: state.signs.fetched, + permissionFilter: state.gtfs.filter.permissionFilter, project: getActiveProject(state), signs: state.signs.all, user: state.user @@ -26,6 +29,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { return dispatch(fetchRtdSigns()) }) } + if (initialProps.permissionFilter !== 'edit-etid') { + dispatch(updatePermissionFilter('edit-etid')) + } }, onStopClick: (stop, agency) => dispatch(createSign(stop, agency)), onRouteClick: (route, agency) => dispatch(createSign(route, agency)), diff --git a/lib/signs/containers/VisibleSignsList.js b/lib/signs/containers/VisibleSignsList.js index e4a7e6568..aa7e5443c 100644 --- a/lib/signs/containers/VisibleSignsList.js +++ b/lib/signs/containers/VisibleSignsList.js @@ -5,54 +5,27 @@ import { setVisibilitySearchText, setVisibilityFilter } from '../actions/visibil import { getFeedsForPermission } from '../../common/util/permissions' import SignsList from '../components/SignsList' import {getActiveProject} from '../../manager/selectors' -import { FILTERS } from '../util' - -const getVisibleSigns = (signs, visibilityFilter) => { - if (!signs) return [] - const visibleSigns = signs.filter(sign => - sign.title.toLowerCase().indexOf((visibilityFilter.searchText || '').toLowerCase()) !== -1) - switch (visibilityFilter.filter) { - case 'ALL': - return visibleSigns - case 'PUBLISHED': - return visibleSigns.filter(sign => sign.published) - case 'DRAFT': - return visibleSigns.filter(sign => !sign.published) - default: - return visibleSigns - } -} +import {getVisibleSigns} from '../selectors' const mapStateToProps = (state, ownProps) => { - // if (getActiveProject(state) !== null && getActiveProject(state).feeds !== null ) - const filterCounts = {} - if (!state.signs.isFetching) { - FILTERS.map(f => { - filterCounts[f] = getVisibleSigns(state.signs.all, {filter: f}).length - }) - } + const activeProject = getActiveProject(state) return { isFetching: state.signs.isFetching, - signs: getVisibleSigns(state.signs.all, state.signs.filter), + signs: getVisibleSigns(state), visibilityFilter: state.signs.filter, - editableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'edit-etid'), - publishableFeeds: getFeedsForPermission(getActiveProject(state), state.user, 'approve-etid'), - filterCounts + editableFeeds: getFeedsForPermission(activeProject, state.user, 'edit-etid'), + publishableFeeds: getFeedsForPermission(activeProject, state.user, 'approve-etid'), + filterCounts: state.signs.counts } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onEditClick: (sign) => dispatch(editSign(sign)), - onDeleteClick: (sign) => dispatch(deleteSign(sign)), - searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)), - visibilityFilterChanged: (filter) => dispatch(setVisibilityFilter(filter)) - } +const mapDispatchToProps = { + onEditClick: editSign, + onDeleteClick: deleteSign, + searchTextChanged: setVisibilitySearchText, + visibilityFilterChanged: setVisibilityFilter } -const VisibleSignsList = connect( - mapStateToProps, - mapDispatchToProps -)(SignsList) +const VisibleSignsList = connect(mapStateToProps, mapDispatchToProps)(SignsList) export default VisibleSignsList diff --git a/lib/signs/reducers/signs.js b/lib/signs/reducers/signs.js index 32bd0d3db..58ac174fb 100644 --- a/lib/signs/reducers/signs.js +++ b/lib/signs/reducers/signs.js @@ -2,7 +2,8 @@ import update from 'react-addons-update' import clone from 'lodash.clonedeep' import mergeable from 'redux-merge-reducers' -import { getFeedId } from '../../common/util/modules' +import {getFeedId} from '../../common/util/modules' +import {filterSignsByCategory, FILTERS} from '../util' const signs = (state = { fetched: false, @@ -12,7 +13,8 @@ const signs = (state = { filter: { searchText: null, filter: 'ALL' - } + }, + counts: {} }, action) => { let signs, entities switch (action.type) { @@ -21,14 +23,10 @@ const signs = (state = { case 'SET_SIGN_VISIBILITY_FILTER': return update(state, {filter: {filter: {$set: action.filter}}}) case 'REQUEST_RTD_SIGNS': - return { - isFetching: true, - all: [], - filter: { - searchText: null, - filter: 'ALL' - } - } + return update(state, { + isFetching: {$set: true}, + all: {$set: []} + }) case 'RECEIVED_GTFS_STOPS_AND_ROUTES': if (action.module !== 'SIGNS') { return state @@ -162,16 +160,17 @@ const signs = (state = { } return sign }) - return { - fetched: true, - isFetching: false, - all: allSigns, - entities: entityList, - filter: { - searchText: null, - filter: 'ALL' - } - } + const filterCounts = {} + FILTERS.map(f => { + filterCounts[f] = filterSignsByCategory(allSigns, f).length + }) + return update(state, { + fetched: {$set: true}, + isFetching: {$set: false}, + all: {$set: allSigns}, + entities: {$set: entityList}, + counts: {$set: filterCounts} + }) default: return state } diff --git a/lib/signs/selectors/index.js b/lib/signs/selectors/index.js new file mode 100644 index 000000000..629ff1c92 --- /dev/null +++ b/lib/signs/selectors/index.js @@ -0,0 +1,13 @@ +import { createSelector } from 'reselect' + +import {filterSignsByCategory} from '../util' + +export const getVisibleSigns = createSelector( + [state => state.signs.all, state => state.signs.filter], + (signs, visibilityFilter) => { + if (!signs) return [] + const visibleSigns = signs.filter(sign => + sign.title.toLowerCase().indexOf((visibilityFilter.searchText || '').toLowerCase()) !== -1) + return filterSignsByCategory(visibleSigns, visibilityFilter.filter) + } +) diff --git a/lib/signs/util/index.js b/lib/signs/util/index.js index 3a44c30cf..a9016a8b8 100644 --- a/lib/signs/util/index.js +++ b/lib/signs/util/index.js @@ -1 +1,14 @@ export const FILTERS = ['ALL', 'PUBLISHED', 'DRAFT'] + +export function filterSignsByCategory (signs, filter) { + switch (filter) { + case 'ALL': + return signs + case 'PUBLISHED': + return signs.filter(sign => sign.published) + case 'DRAFT': + return signs.filter(sign => !sign.published) + default: + return signs + } +} From 2bdec1da7c07436984b67d69f70afe5460cc9d28 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 5 Apr 2017 09:31:30 -0400 Subject: [PATCH 033/265] refactor(EditableTextField): refactor component, clean up functions --- lib/common/components/EditableTextField.js | 168 ++++++++++----------- 1 file changed, 82 insertions(+), 86 deletions(-) diff --git a/lib/common/components/EditableTextField.js b/lib/common/components/EditableTextField.js index 31e486e32..f77291f7f 100644 --- a/lib/common/components/EditableTextField.js +++ b/lib/common/components/EditableTextField.js @@ -18,49 +18,45 @@ export default class EditableTextField extends Component { tabIndex: PropTypes.number, type: PropTypes.string, value: PropTypes.string, - onChange: PropTypes.func } - constructor (props) { - super(props) - this.state = { - isEditing: this.props.isEditing || false, - value: this.props.value - } + static defaultProps = { + style: {}, + type: 'text' + } + + state = { + isEditing: this.props.isEditing || false, + value: this.props.value } componentWillReceiveProps (nextProps) { if (this.state.value !== nextProps.value) this.setState({ value: nextProps.value }) } - edit () { + edit = () => { this.setState({ isEditing: true }) } - save () { + save = () => { const value = ReactDOM.findDOMNode(this.input).value if (value === this.state.value) { - this.cancel() - return - } - if (this.props.onChange) { - this.props.onChange(value) + return this.cancel() } + this.props.onChange && this.props.onChange(value) this.setState({ isEditing: false, value }) } - cancel (e) { - this.setState({ - isEditing: false - }) + cancel () { + this.setState({isEditing: false}) } - handleKeyDown (e) { + handleKeyDown = (e) => { // if [Enter] or [Tab] is pressed if ((e.keyCode === 9 || e.keyCode === 13) && this.state.isEditing) { this.save() @@ -71,85 +67,85 @@ export default class EditableTextField extends Component { } } - render () { - var iconStyle = { - cursor: 'pointer' - } + _ref = input => { + this.input = input + // auto-focus on text input when input is rendered (instead of disallowed autofocus prop) + this.input && ReactDOM.findDOMNode(this.input).focus() + } - const saveButton = ( - - //feed.name.length > 11 ? feed.name.substr(0, 11) + '...' : feed.name - - ) - const displayValue = this.props.maxLength !== null && this.state.value && this.state.value.length > this.props.maxLength - ? this.state.value.substr(0, this.props.maxLength) + '...' - : this.state.value - const style = { - ...this.props.style - } - const spanStyle = {} - if (this.props.inline) { + _onInputFocus = (e) => { + // select entire text string on input focus + e.target.select() + } + + render () { + const { + disabled, + hideEditButton, + inline, + link, + maxLength, + min, + placeholder, + step, + style, + tabIndex, + type + } = this.props + const { + isEditing, + value + } = this.state + // trim length of display text to fit content + const displayValue = maxLength !== null && value && value.length > maxLength + ? `${value.substr(0, maxLength)}...` + : value || '(none)' + if (inline) { style.display = 'inline-block' } - if (this.props.maxWidth) { - spanStyle.maxWidth = this.props.maxWidth - spanStyle.textOverflow = 'ellipsis' - spanStyle.whiteSpace = 'nowrap' - spanStyle.overflow = 'hidden' - } return ( -
- {this.state.isEditing - ?
+
+ {isEditing + ? { - this.input = input - // focus on text input when input is rendered - this.input && ReactDOM.findDOMNode(this.input).focus() - }} - type={this.props.type ? this.props.type.toLowerCase() : 'text'} - min={this.props.min != null ? this.props.min : null} - step={this.props.step != null ? this.props.step : null} - placeholder={this.props.placeholder ? this.props.placeholder : ''} - onKeyDown={(e) => this.handleKeyDown(e)} - onFocus={(e) => e.target.select()} - defaultValue={this.state.value} + ref={this._ref} + type={type.toLowerCase()} + min={min} + step={step} + placeholder={placeholder} + onKeyDown={this.handleKeyDown} + onFocus={this._onInputFocus} + defaultValue={value} /> - {saveButton} + + + - : - {this.props.link - ? {displayValue} - : displayValue || '(none)' - } - {this.props.hideEditButton + : + + {link + ? {displayValue} + : displayValue + } + + {hideEditButton ? null - : - {' '} - - + : } } From cbaa20a0d9e22e5f71f23b805f2b57ee2ebe81af Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 5 Apr 2017 09:32:48 -0400 Subject: [PATCH 034/265] fix(EditorFeedSourcePanel): fix dom nesting warnings, resort snapshots, use confirmation modals --- .../components/EditorFeedSourcePanel.js | 118 +++++++++++------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/lib/editor/components/EditorFeedSourcePanel.js b/lib/editor/components/EditorFeedSourcePanel.js index 6e893a037..497affbd5 100644 --- a/lib/editor/components/EditorFeedSourcePanel.js +++ b/lib/editor/components/EditorFeedSourcePanel.js @@ -18,20 +18,33 @@ export default class EditorFeedSourcePanel extends Component { deleteSnapshot: PropTypes.func.isRequired, loadFeedVersionForEditing: PropTypes.func.isRequired } + + messages = getComponentMessages('EditorFeedSourcePanel') + + state = { expanded: false } + componentWillMount () { this.props.getSnapshots(this.props.feedSource) } - constructor (props) { - super(props) - this.state = { expanded: false } + + _onCreateSnapshot = (name, comment) => { + this.props.createSnapshot(this.props.feedSource, name, comment) } + + _onLoadVersion = () => { + const {feedSource} = this.props + const version = feedSource.feedVersions[feedSource.feedVersions.length - 1] + this.refs.confirmModal.open({ + title: getMessage(this.messages, 'load'), + body: getMessage(this.messages, 'confirmLoad'), + onConfirm: () => this.props.loadFeedVersionForEditing(version) + }) + } + render () { const { - feedSource, - createSnapshot, - loadFeedVersionForEditing + feedSource } = this.props - const messages = getComponentMessages('EditorFeedSourcePanel') const hasVersions = feedSource && feedSource.feedVersions && feedSource.feedVersions.length > 0 const currentSnapshot = feedSource.editorSnapshots && feedSource.editorSnapshots.length ? feedSource.editorSnapshots.find(s => s.current) @@ -41,19 +54,19 @@ export default class EditorFeedSourcePanel extends Component { : [] return ( - { - createSnapshot(feedSource, name, comment) - }} - /> - + + {feedSource.editorSnapshots && feedSource.editorSnapshots.length ?
Active snapshot}> {currentSnapshot ? - + : No active snapshot @@ -66,7 +79,7 @@ export default class EditorFeedSourcePanel extends Component { ? No other snapshots : inactiveSnapshots.map(s => { return ( - + ) }) } @@ -78,22 +91,15 @@ export default class EditorFeedSourcePanel extends Component { {' '}or{' '}
} @@ -112,9 +118,9 @@ export default class EditorFeedSourcePanel extends Component { onClick={() => this.refs.snapshotModal.open()}> Take snapshot of latest changes - What are snapshots?}> -

Snapshots are save points you can always revert back to when editing a GTFS feed.

-

A snapshot might represent a work-in-progress, future planning scenario or even different service patterns (e.g., summer schedule markup).

+ {getMessage(this.messages, 'help.title')}}> +

{getMessage(this.messages, 'help.body.0')}

+

{getMessage(this.messages, 'help.body.1')}

@@ -127,50 +133,76 @@ class SnapshotItem extends Component { snapshot: PropTypes.object, feedSource: PropTypes.object } + + messages = getComponentMessages('EditorFeedSourcePanel') + + _onDeleteSnapshot = () => { + const {deleteSnapshot, feedSource, snapshot} = this.props + this.props.modal.open({ + title: `${getMessage(this.messages, 'delete')}`, + body: getMessage(this.messages, 'confirmDelete'), + onConfirm: () => deleteSnapshot(feedSource, snapshot) + }) + } + + _onRestoreSnapshot = () => { + const {restoreSnapshot, feedSource, snapshot} = this.props + this.props.modal.open({ + title: `${getMessage(this.messages, 'restore')}`, + body: getMessage(this.messages, 'confirmLoad'), + onConfirm: () => restoreSnapshot(feedSource, snapshot) + }) + } + render () { const { snapshot, feedSource, - restoreSnapshot, downloadSnapshot, - exportSnapshotAsVersion, - deleteSnapshot + exportSnapshotAsVersion } = this.props const dateFormat = getConfigProperty('application.date_format') const timeFormat = 'h:MMa' - const messages = getComponentMessages('EditorFeedSourcePanel') return ( - -

+ +

+ {snapshot.name} +

+
- created {moment(snapshot.snapshotTime).fromNow()} -

+

+ created {moment(snapshot.snapshotTime).fromNow()} +

+
) } From 87f95dfc0ea92a37916e52cce3a20fd44542736c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 5 Apr 2017 11:13:46 -0400 Subject: [PATCH 035/265] fix(FeedVersionViewer): fix key warning on version list and size of buttons --- lib/manager/components/FeedVersionReport.js | 3 +-- lib/manager/components/FeedVersionViewer.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/manager/components/FeedVersionReport.js b/lib/manager/components/FeedVersionReport.js index e95706ac8..057e0b758 100644 --- a/lib/manager/components/FeedVersionReport.js +++ b/lib/manager/components/FeedVersionReport.js @@ -197,7 +197,7 @@ export default class FeedVersionReport extends Component { : feedVersionRenamed(version, value)} /> } @@ -215,7 +215,6 @@ export default class FeedVersionReport extends Component { search: true, hover: true, exportCSV: true, - // maxHeight: '500px', pagination: true, options: { paginationShowsTotal: true, diff --git a/lib/manager/components/FeedVersionViewer.js b/lib/manager/components/FeedVersionViewer.js index d4535878e..59abf45d5 100644 --- a/lib/manager/components/FeedVersionViewer.js +++ b/lib/manager/components/FeedVersionViewer.js @@ -5,7 +5,6 @@ import moment from 'moment' import { LinkContainer } from 'react-router-bootstrap' import GtfsValidationViewer from './validation/GtfsValidationViewer' -// import GtfsValidationExplorer from './validation/GtfsValidationExplorer' import FeedVersionReport from './FeedVersionReport' import NotesViewer from './NotesViewer' import ConfirmModal from '../../common/components/ConfirmModal' @@ -137,6 +136,7 @@ export class VersionButtonToolbar extends Component { hasVersions, isPublic, loadFeedVersionForEditing, + size, version } = this.props const messages = getComponentMessages('FeedVersionViewer') @@ -148,6 +148,7 @@ export class VersionButtonToolbar extends Component { {/* "Download Feed" Button */}
+ } else if (!version || !version.validationResult) { + return
Feed has not yet been validated.
+ } const breakWordStyle = { wordWrap: 'break-word', overflowWrap: 'break-word' } - const problemMap = {} - this.props.invalidValues && this.props.invalidValues.map(val => { + version.validationResult.errors && version.validationResult.errors.map(val => { if (!problemMap[val.errorType]) { problemMap[val.errorType] = { - count: 1, + count: 0, priority: val.priority, file: val.file, affected: [val.affectedEntityId], @@ -119,22 +80,33 @@ class ResultTable extends React.Component { problemMap[val.errorType].affected.push(val.affectedEntityId) problemMap[val.errorType].data.push(val.problemData) }) - return ( - - {Object.keys(problemMap).map((key) => { - return ( - - {problemMap[key].file} - {key.replace(/([A-Z])/g, ' $1')} - {problemMap[key].priority} - {problemMap[key].count} + + + + + + + + - ) - })} - + + + {Object.keys(problemMap).map((key) => { + return ( + + + + + + + ) + })} + +
FileIssuePriorityCount
{problemMap[key].file}{key.replace(/([A-Z])/g, ' $1')}{problemMap[key].priority}{problemMap[key].count}
+
) } } - -// export default connect()(GtfsValidationSummary) diff --git a/lib/manager/components/validation/GtfsValidationViewer.js b/lib/manager/components/validation/GtfsValidationViewer.js index d9e9f3609..448a9a6ab 100644 --- a/lib/manager/components/validation/GtfsValidationViewer.js +++ b/lib/manager/components/validation/GtfsValidationViewer.js @@ -1,24 +1,23 @@ -import React from 'react' -import { Panel, Table +import moment from 'moment' +import React, {Component, PropTypes} from 'react' +import { // Badge, // Button } from 'react-bootstrap' -// import { LinkContainer } from 'react-router-bootstrap' import BootstrapTable from 'react-bootstrap-table/lib/BootstrapTable' import TableHeaderColumn from 'react-bootstrap-table/lib/TableHeaderColumn' -// import { getConfigProperty } from '../../../common/util/config' -export default class GtfsValidationViewer extends React.Component { - constructor (props) { - super(props) - this.state = { expanded: false } +import {ValidationSummaryTable} from './GtfsValidationSummary' + +export default class GtfsValidationViewer extends Component { + static propTypes = { + fetchValidationResult: PropTypes.func, + validationResult: PropTypes.object, + version: PropTypes.object } componentWillMount () { this.props.fetchValidationResult() } - componentWillReceiveProps (nextProps) { - if (!nextProps.validationResult) this.setState({ expanded: false }) - } sortPriority (a, b, order) { const priorityToNum = p => { switch (p) { @@ -42,7 +41,12 @@ export default class GtfsValidationViewer extends React.Component { return {cell} } render () { - const result = this.props.validationResult + const { + validationResult: result, + version + } = this.props + const dateFormat = 'MMM. DD, YYYY' + const timeFormat = 'h:MMa' // const messages = getComponentMessages('GtfsValidationViewer') const tableOptions = { striped: true, @@ -68,33 +72,10 @@ export default class GtfsValidationViewer extends React.Component { } errors[key].push(error) }) - // if (result && errors) { - // report = ( - //
- // {files.map(file => { - // return ( - // - // ) - // })} - // - //
- // ) - // } else if (result) { - // report = (
{getMessage(messages, 'noResults')}
) - // } - return (
- Validation Summary}> - - +

{version.name} {moment(version.updated).format(dateFormat + ', ' + timeFormat)}

+ {this.props.title} 0)} -// > -// No issues found. -// -// ) -// } -// return ( -// {this.props.title} {this.props.invalidValues.length})} -// > -//
-// -// -// -// -// -// -// -// -// -// -// {this.props.invalidValues.map((val, index) => { -// return ( -// -// -// -// -// -// -// -// ) -// })} -// -//
{getMessage(messages, 'problemType')}{getMessage(messages, 'priority')}{getMessage(messages, 'affectedIds')}{getMessage(messages, 'line')}{getMessage(messages, 'description')}
{val.errorType}{val.priority}{val.affectedEntityId}{val.line}{val.message}
-//
-// ) -// } -// } diff --git a/lib/manager/containers/validation/ActiveGtfsValidationExplorer.js b/lib/manager/containers/validation/ActiveGtfsValidationExplorer.js index 3ae05b8eb..5663abba2 100644 --- a/lib/manager/containers/validation/ActiveGtfsValidationExplorer.js +++ b/lib/manager/containers/validation/ActiveGtfsValidationExplorer.js @@ -57,12 +57,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { }) } }, - fetchValidationResult: (feedVersion) => { - dispatch(fetchValidationResult(feedVersion)) - }, - fetchIsochrones: (feedVersion, fromLat, fromLon, toLat, toLon) => { + fetchValidationResult: (feedVersion) => dispatch(fetchValidationResult(feedVersion)), + fetchIsochrones: (feedVersion, fromLat, fromLon, toLat, toLon) => dispatch(fetchFeedVersionIsochrones(feedVersion, fromLat, fromLon, toLat, toLon)) - } } } From ce513de880b500154d67395525c719e83cbec1d5 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 5 Apr 2017 12:58:41 -0400 Subject: [PATCH 038/265] fix(AlertsList): make sort and feed filter controlled components --- lib/alerts/components/AlertsList.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/alerts/components/AlertsList.js b/lib/alerts/components/AlertsList.js index d3b53dd59..b2626e3b3 100644 --- a/lib/alerts/components/AlertsList.js +++ b/lib/alerts/components/AlertsList.js @@ -25,9 +25,11 @@ export default class AlertsList extends Component { visibilityFilter: PropTypes.object, visibilityFilterChanged: PropTypes.func } + _onAgencyFilterChange = (evt) => { this.props.agencyFilterChanged(evt.target.value) } + _onSortChange = (evt) => { const values = evt.target.value.split(':') const sort = { @@ -36,6 +38,7 @@ export default class AlertsList extends Component { } this.props.sortChanged(sort) } + render () { const { alerts, @@ -52,6 +55,7 @@ export default class AlertsList extends Component { visibilityFilter, visibilityFilterChanged } = this.props + const {sort} = visibilityFilter return (
@@ -86,6 +90,7 @@ export default class AlertsList extends Component { {' '} {SORT_OPTIONS.map(option => ( @@ -97,6 +102,7 @@ export default class AlertsList extends Component { {' '} {feeds.map(fs => ( From d8bf19c4032fcde5a5a9794eeb3b7e19e1b7fd51 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 5 Apr 2017 13:13:55 -0400 Subject: [PATCH 039/265] fix(FeedVersionReport): fix momentjs display of minutes --- lib/manager/components/FeedVersionReport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/manager/components/FeedVersionReport.js b/lib/manager/components/FeedVersionReport.js index 057e0b758..8d7f6717a 100644 --- a/lib/manager/components/FeedVersionReport.js +++ b/lib/manager/components/FeedVersionReport.js @@ -22,7 +22,7 @@ import TripsChart from './validation/TripsChart' import ActiveDateTimeFilter from './reporter/containers/ActiveDateTimeFilter' const dateFormat = 'MMM. DD, YYYY' -const timeFormat = 'h:MMa' +const timeFormat = 'h:mma' const MAP_HEIGHTS = [200, 400] const ISO_BANDS = [] for (var i = 0; i < 24; i++) { @@ -197,7 +197,7 @@ export default class FeedVersionReport extends Component { : feedVersionRenamed(version, value)} /> } From bce0447fc84c39e953d657d442ccc7876407351c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 6 Apr 2017 10:58:28 -0400 Subject: [PATCH 040/265] feat(secureFetch): handle bad server responses with error message --- lib/admin/actions/admin.js | 10 +++---- lib/admin/actions/organizations.js | 12 ++++----- lib/alerts/actions/alerts.js | 4 +-- lib/common/actions/index.js | 42 ++++++++++++++++++++++++++++++ lib/editor/actions/active.js | 4 +-- lib/editor/actions/agency.js | 8 +++--- lib/editor/actions/calendar.js | 14 +++++----- lib/editor/actions/editor.js | 2 +- lib/editor/actions/fare.js | 8 +++--- lib/editor/actions/feedInfo.js | 10 +++---- lib/editor/actions/route.js | 8 +++--- lib/editor/actions/snapshots.js | 16 ++++++------ lib/editor/actions/stop.js | 10 +++---- lib/editor/actions/trip.js | 14 +++++----- lib/editor/actions/tripPattern.js | 12 ++++----- lib/gtfs/actions/filter.js | 4 +-- lib/gtfsplus/actions/gtfsplus.js | 8 +++--- lib/manager/actions/deployments.js | 22 ++++++++-------- lib/manager/actions/feeds.js | 22 ++++++++-------- lib/manager/actions/notes.js | 10 +++---- lib/manager/actions/projects.js | 24 ++++++++--------- lib/manager/actions/status.js | 6 ++--- lib/manager/actions/user.js | 16 ++++++------ lib/manager/actions/versions.js | 24 ++++++++--------- lib/signs/actions/signs.js | 4 +-- 25 files changed, 178 insertions(+), 136 deletions(-) create mode 100644 lib/common/actions/index.js diff --git a/lib/admin/actions/admin.js b/lib/admin/actions/admin.js index fc0c839d0..f5ef94f91 100644 --- a/lib/admin/actions/admin.js +++ b/lib/admin/actions/admin.js @@ -21,12 +21,12 @@ export function fetchUsers () { let countUrl = '/api/manager/secure/usercount' if (queryString) countUrl += `?queryString=${queryString}` - const getCount = secureFetch(countUrl, getState()) + const getCount = dispatch(secureFetch(countUrl)) .then(response => response.json()) let usersUrl = `/api/manager/secure/user?page=${getState().admin.users.page}` if (queryString) usersUrl += `&queryString=${queryString}` - const getUsers = secureFetch(usersUrl, getState()) + const getUsers = dispatch(secureFetch(usersUrl)) .then(response => response.json()) Promise.all([getCount, getUsers]).then((results) => { @@ -39,7 +39,7 @@ export function fetchUserCount () { return function (dispatch, getState) { dispatch(requestingUsers()) const url = '/api/manager/secure/user/count' - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(data => { console.log(data) @@ -68,7 +68,7 @@ export function createUser (credentials) { dispatch(creatingUser()) console.log(credentials) const url = '/api/manager/secure/user' - return secureFetch(url, getState(), 'post', credentials) + return dispatch(secureFetch(url, 'post', credentials)) .then(response => response.json()) .then(profile => { dispatch(createdUser(profile)) @@ -96,7 +96,7 @@ export function deleteUser (user) { return function (dispatch, getState) { dispatch(deletingUser(user)) const url = `/api/manager/secure/user/${user.user_id}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(response => response.json()) .then(result => { dispatch(deletedUser(user)) diff --git a/lib/admin/actions/organizations.js b/lib/admin/actions/organizations.js index 02ebf2bac..93ea9749c 100644 --- a/lib/admin/actions/organizations.js +++ b/lib/admin/actions/organizations.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { fetchProjects } from '../../manager/actions/projects' function requestingOrganizations () { return { @@ -16,7 +16,7 @@ function receiveOrganizations (organizations) { export function fetchOrganizations () { return function (dispatch, getState) { dispatch(requestingOrganizations()) - return secureFetch('/api/manager/secure/organization', getState()) + return dispatch(secureFetch('/api/manager/secure/organization')) .then(response => response.json()) // .catch(err => console.log(err)) .then(organizations => { @@ -46,7 +46,7 @@ export function fetchOrganization (organizationId, unsecure) { dispatch(requestingOrganization()) const apiRoot = unsecure ? 'public' : 'secure' const url = `/api/manager/${apiRoot}/organization/${organizationId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) // .catch(err => console.log(err)) .then(organization => { @@ -75,7 +75,7 @@ export function deleteOrganization (organization) { return function (dispatch, getState) { dispatch(deletingOrganization()) const url = `/api/manager/secure/organization/${organization.id}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(response => response.json()) // .catch(err => console.log(err)) .then(organization => { @@ -97,7 +97,7 @@ export function updateOrganization (organization, changes, fetchFeeds = false) { return function (dispatch, getState) { dispatch(savingOrganization(organization, changes)) const url = `/api/manager/secure/organization/${organization.id}` - return secureFetch(url, getState(), 'put', changes) + return dispatch(secureFetch(url, 'put', changes)) .then((res) => { // fetch projects because a project may have been (re)assigned to an org dispatch(fetchProjects()) @@ -126,7 +126,7 @@ export function createOrganization (organization) { dispatch(creatingOrganization(organization)) console.log(organization) const url = '/api/manager/secure/organization' - return secureFetch(url, getState(), 'post', organization) + return dispatch(secureFetch(url, 'post', organization)) .then(response => response.json()) .then(org => { dispatch(createdOrganization(org)) diff --git a/lib/alerts/actions/alerts.js b/lib/alerts/actions/alerts.js index 3bad9417e..80ba8d436 100644 --- a/lib/alerts/actions/alerts.js +++ b/lib/alerts/actions/alerts.js @@ -2,7 +2,7 @@ import { browserHistory } from 'react-router' import fetch from 'isomorphic-fetch' import { fetchStopsAndRoutes } from '../../gtfs/actions/general' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { getAlertsUrl, getFeedId } from '../../common/util/modules' import { setErrorMessage } from '../../manager/actions/status' import {getActiveProject} from '../../manager/selectors' @@ -99,7 +99,7 @@ export function setActiveAlert (alertId) { export function fetchRtdAlerts () { return function (dispatch, getState) { dispatch(requestRtdAlerts()) - return secureFetch(getAlertsUrl(), getState()) + return dispatch(secureFetch(getAlertsUrl())) .then((res) => { if (!res || res.status >= 400) { dispatch(setErrorMessage('Error fetching alerts!')) diff --git a/lib/common/actions/index.js b/lib/common/actions/index.js new file mode 100644 index 000000000..dca16f1ac --- /dev/null +++ b/lib/common/actions/index.js @@ -0,0 +1,42 @@ +import fetch from 'isomorphic-fetch' +import { setErrorMessage } from '../../manager/actions/status' + +export function secureFetch (url, method = 'get', payload, raw) { + return function (dispatch, getState) { + const {user} = getState() + var opts = { + method: method, + headers: { + 'Authorization': `Bearer ${user.token}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + } + if (payload) opts.body = JSON.stringify(payload) + return fetch(url, opts) + .catch(err => { + dispatch(setErrorMessage(`Error making request: (${err})`)) + }) + .then(res => { + // if raw response is requested + if (raw) return res + + // check for errors + if (res.status >= 500) { + dispatch(setErrorMessage('Network error!')) + return null + } else if (res.status >= 300) { + // res.text().then(text => { + // dispatch(setErrorMessage(text)) + // }) + res.json().then(json => { + const message = json.message || `Unknown (${res.status}) error occurred while making request` + dispatch(setErrorMessage(message)) + }) + return null + } else { + return res + } + }) + } +} diff --git a/lib/editor/actions/active.js b/lib/editor/actions/active.js index 3b61b2159..d0c9d58f8 100644 --- a/lib/editor/actions/active.js +++ b/lib/editor/actions/active.js @@ -2,7 +2,7 @@ import clone from 'lodash.clonedeep' import { browserHistory } from 'react-router' import { setErrorMessage } from '../../manager/actions/status' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { saveFeedInfo } from './feedInfo' import { saveAgency } from './agency' import { saveStop } from './stop' @@ -169,7 +169,7 @@ export function deleteGtfsEntity (feedId, component, entityId, routeId) { } var error = false const url = `/api/manager/secure/${component}/${entityId}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => { if (res.status >= 300) { error = true diff --git a/lib/editor/actions/agency.js b/lib/editor/actions/agency.js index d97fa945f..1b0937c23 100644 --- a/lib/editor/actions/agency.js +++ b/lib/editor/actions/agency.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setActiveGtfsEntity } from './active' export function savingAgency (feedId, agency) { @@ -39,7 +39,7 @@ export function saveAgency (feedId, agency) { const url = agency.id !== 'new' ? `/api/manager/secure/agency/${agency.id}?feedId=${feedId}` : `/api/manager/secure/agency?feedId=${feedId}` - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(a => { // dispatch(receiveAgency(feedId, agency)) @@ -73,7 +73,7 @@ export function fetchAgencies (feedId) { return function (dispatch, getState) { dispatch(requestingAgencies(feedId)) const url = `/api/manager/secure/agency?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(agencies => { dispatch(receiveAgencies(feedId, agencies)) @@ -97,7 +97,7 @@ export function deleteAgency (feedId, agency) { return dispatch(fetchAgencies(feedId)) } const url = `/api/manager/secure/agency/${agency.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(agency => { dispatch(fetchAgencies(feedId)) diff --git a/lib/editor/actions/calendar.js b/lib/editor/actions/calendar.js index 5a0e3f335..9ea2c25b7 100644 --- a/lib/editor/actions/calendar.js +++ b/lib/editor/actions/calendar.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setErrorMessage } from '../../manager/actions/status' import { setActiveGtfsEntity } from './active' @@ -23,7 +23,7 @@ export function fetchCalendars (feedId) { return function (dispatch, getState) { dispatch(requestingCalendars(feedId)) const url = `/api/manager/secure/calendar?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(calendars => { dispatch(receiveCalendars(feedId, calendars)) @@ -65,7 +65,7 @@ export function saveCalendar (feedId, calendar) { endDate: calendar.end_date, id: calendar.id === 'new' ? null : calendar.id } - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(c => { // dispatch(receiveCalendar(feedId, calendar)) @@ -96,7 +96,7 @@ export function deleteCalendar (feedId, calendar) { return dispatch(fetchCalendars(feedId)) } const url = `/api/manager/secure/calendar/${calendar.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => { if (res.status >= 300) { console.log(res) @@ -147,7 +147,7 @@ export function saveScheduleException (feedId, scheduleException) { // gtfs spec props // gtfs_prop: scheduleException.gtfs_prop } - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(s => { // dispatch(receiveScheduleException(feedId, scheduleException)) @@ -181,7 +181,7 @@ export function fetchScheduleExceptions (feedId) { return function (dispatch, getState) { dispatch(requestingScheduleExceptions(feedId)) const url = `/api/manager/secure/scheduleexception?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(scheduleExceptions => { dispatch(receiveScheduleExceptions(feedId, scheduleExceptions)) @@ -205,7 +205,7 @@ export function deleteScheduleException (feedId, scheduleException) { return dispatch(fetchScheduleExceptions(feedId)) } const url = `/api/manager/secure/scheduleexception/${scheduleException.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(scheduleException => { dispatch(fetchScheduleExceptions(feedId)) diff --git a/lib/editor/actions/editor.js b/lib/editor/actions/editor.js index c6bffbd25..c7a16308d 100644 --- a/lib/editor/actions/editor.js +++ b/lib/editor/actions/editor.js @@ -132,7 +132,7 @@ export function getGtfsTable (tableId, feedId) { return dispatch(fetchFeedInfo(feedId)) default: const url = `/api/manager/secure/${tableId}?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(entities => { console.log('got editor result', entities) diff --git a/lib/editor/actions/fare.js b/lib/editor/actions/fare.js index 3488d0e2e..09ebe7eec 100644 --- a/lib/editor/actions/fare.js +++ b/lib/editor/actions/fare.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setActiveGtfsEntity } from './active' export function savingFare (feedId, fare) { @@ -38,7 +38,7 @@ export function saveFare (feedId, fare) { const url = fare.id !== 'new' ? `/api/manager/secure/fare/${fare.id}?feedId=${feedId}` : `/api/manager/secure/fare?feedId=${feedId}` - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(f => { // dispatch(receiveFare(feedId, fare)) @@ -68,7 +68,7 @@ export function deleteFare (feedId, fare) { return dispatch(fetchFares(feedId)) } const url = `/api/manager/secure/fare/${fare.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(fare => { dispatch(fetchFares(feedId)) @@ -95,7 +95,7 @@ export function fetchFares (feedId) { return function (dispatch, getState) { dispatch(requestingFares(feedId)) const url = `/api/manager/secure/fare?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(fares => { dispatch(receiveFares(feedId, fares)) diff --git a/lib/editor/actions/feedInfo.js b/lib/editor/actions/feedInfo.js index d26df270b..349eb607f 100644 --- a/lib/editor/actions/feedInfo.js +++ b/lib/editor/actions/feedInfo.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' export function requestingFeedInfo (feedId) { return { @@ -26,7 +26,7 @@ export function fetchFeedInfo (feedId) { return function (dispatch, getState) { dispatch(requestingFeedInfo(feedId)) const url = `/api/manager/secure/feedinfo/${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(feedInfo => { dispatch(receiveFeedInfo(feedInfo)) @@ -60,7 +60,7 @@ export function createFeedInfo (feedId) { // feedVersion: feedInfo.feed_version, } const url = `/api/manager/secure/feedinfo/${feedId}` - return secureFetch(url, getState(), 'post', data) + return dispatch(secureFetch(url, 'post', data)) .then((res) => { return dispatch(fetchFeedInfo(feedId)) }) @@ -88,7 +88,7 @@ export function saveFeedInfo (feedId, feedInfo) { } const url = `/api/manager/secure/feedinfo/${feedId}` const method = getState().editor.data.tables.feedinfo ? 'put' : 'post' - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then((res) => { return dispatch(fetchFeedInfo(feedId)) }) @@ -99,7 +99,7 @@ export function updateFeedInfo (feedInfo, changes) { return function (dispatch, getState) { dispatch(savingFeedInfo(feedInfo.id)) const url = `/api/manager/secure/feedinfo/${feedInfo.id}` - return secureFetch(url, getState(), 'put', changes) + return dispatch(secureFetch(url, 'put', changes)) .then(res => res.json()) .then(feedInfo => { dispatch(receiveFeedInfo(feedInfo)) diff --git a/lib/editor/actions/route.js b/lib/editor/actions/route.js index 4f69e3095..0737e1ccb 100644 --- a/lib/editor/actions/route.js +++ b/lib/editor/actions/route.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { updateEditSetting, setActiveGtfsEntity } from './active' export function savingRoute (feedId, route) { @@ -40,7 +40,7 @@ export function saveRoute (feedId, route) { routeTextColor: route.route_text_color, id: route.id === 'new' ? null : route.id } - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(r => { return dispatch(fetchRoutes(feedId)) @@ -69,7 +69,7 @@ export function deleteRoute (feedId, routeId) { return dispatch(fetchRoutes(feedId)) } const url = `/api/manager/secure/route/${routeId}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(route => { dispatch(fetchRoutes(feedId)) @@ -96,7 +96,7 @@ export function fetchRoutes (feedId) { return function (dispatch, getState) { dispatch(requestingRoutes(feedId)) const url = `/api/manager/secure/route?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(routes => { dispatch(receiveRoutes(feedId, routes)) diff --git a/lib/editor/actions/snapshots.js b/lib/editor/actions/snapshots.js index 183b82fb5..1df3f075c 100644 --- a/lib/editor/actions/snapshots.js +++ b/lib/editor/actions/snapshots.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { fetchBaseGtfs } from './editor' import { clearGtfsContent } from './active' import { handleFetchError, startJobMonitor } from '../../manager/actions/status' @@ -23,7 +23,7 @@ export function fetchSnapshots (feedSource) { return function (dispatch, getState) { dispatch(requestingSnapshots()) const url = `/api/manager/secure/snapshot?feedId=${feedSource.id}` - return secureFetch(url, getState(), 'get') + return dispatch(secureFetch(url, 'get')) .then((response) => { return response.json() }).then((snapshots) => { @@ -49,7 +49,7 @@ export function restoreSnapshot (feedSource, snapshot) { return function (dispatch, getState) { dispatch(restoringSnapshot()) const url = `/api/manager/secure/snapshot/${snapshot.id}/restore` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then((response) => { return response.json() }).then((stops) => { @@ -81,7 +81,7 @@ export function downloadFeedViaToken (feedVersion, isPublic) { return function (dispatch, getState) { const route = isPublic ? 'public' : 'secure' const url = `/api/manager/${route}/feedversion/${feedVersion.id}/downloadtoken` - secureFetch(url, getState()) + dispatch(secureFetch(url)) .then(response => response.json()) .then(result => { window.location.assign(`/api/manager/downloadfeed/${result.id}`) @@ -93,7 +93,7 @@ export function downloadSnapshotViaToken (feedSource, snapshot) { return function (dispatch, getState) { dispatch(downloadingSnapshot(feedSource, snapshot)) const url = `/api/manager/secure/snapshot/${snapshot.id}/downloadtoken` - secureFetch(url, getState()) + dispatch(secureFetch(url)) .then((response) => response.json()) .then((result) => { console.log(result) @@ -124,7 +124,7 @@ export function createSnapshot (feedSource, name, comment) { name, comment } - return secureFetch(url, getState(), 'post', payload) + return dispatch(secureFetch(url, 'post', payload)) .then((response) => { return response.json() }).then(() => { @@ -143,7 +143,7 @@ export function deleteSnapshot (feedSource, snapshot) { return function (dispatch, getState) { dispatch(deletingSnapshot()) const url = `/api/manager/secure/snapshot/${snapshot.id}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then((response) => { return response.json() }).then(() => { @@ -164,7 +164,7 @@ export function loadFeedVersionForEditing (feedVersion) { return function (dispatch, getState) { dispatch(loadingFeedVersionForEditing()) const url = `/api/manager/secure/snapshot/import?feedVersionId=${feedVersion.id}` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then((res) => { if (res.status >= 400) { return res.json() diff --git a/lib/editor/actions/stop.js b/lib/editor/actions/stop.js index bce553091..42896a79a 100644 --- a/lib/editor/actions/stop.js +++ b/lib/editor/actions/stop.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setActiveGtfsEntity } from './active' import { isNew, stopFromGtfs } from '../util/gtfs' @@ -29,7 +29,7 @@ export function saveStop (feedId, stop) { ? `/api/manager/secure/stop/${stop.id}?feedId=${feedId}` : `/api/manager/secure/stop?feedId=${feedId}` const data = stopFromGtfs(stop) - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => res.json()) .then(newStop => { dispatch(receiveStop(feedId, newStop, getState().editor.data.tables.stop)) @@ -62,7 +62,7 @@ export function fetchStops (feedId) { return function (dispatch, getState) { dispatch(requestingStops(feedId)) const url = `/api/manager/secure/stop?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(stops => { dispatch(receiveStops(feedId, stops)) @@ -78,7 +78,7 @@ export function fetchStopsForTripPattern (feedId, tripPatternId) { } dispatch(requestingStops(feedId)) const url = `/api/manager/secure/stop?feedId=${feedId}&patternId=${tripPatternId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 400) { // dispatch(setErrorMessage('Error getting stops for trip pattern')) @@ -109,7 +109,7 @@ export function deleteStop (feedId, stop) { return // dispatch(removeStop(feedId, stop)) } const url = `/api/manager/secure/stop/${stop.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(route => { dispatch(fetchStops(feedId)) diff --git a/lib/editor/actions/trip.js b/lib/editor/actions/trip.js index 5d7d26185..10abcff45 100644 --- a/lib/editor/actions/trip.js +++ b/lib/editor/actions/trip.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setErrorMessage } from '../../manager/actions/status' export function requestingTripsForCalendar (feedId, pattern, calendarId) { @@ -24,7 +24,7 @@ export function fetchTripsForCalendar (feedId, pattern, calendarId) { return function (dispatch, getState) { dispatch(requestingTripsForCalendar(feedId, pattern, calendarId)) const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${pattern.id}&calendarId=${calendarId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(trips => { dispatch(receiveTripsForCalendar(feedId, pattern, calendarId, trips)) @@ -62,7 +62,7 @@ export function saveTripsForCalendar (feedId, pattern, calendarId, trips) { ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}` : `/api/manager/secure/trip?feedId=${feedId}` trip.id = tripExists ? trip.id : null - return secureFetch(url, getState(), method, trip) + return dispatch(secureFetch(url, method, trip)) .then(res => { if (res.status >= 300) { errorCount++ @@ -102,7 +102,7 @@ export function saveMultipleTripsForCalendar (feedId, pattern, calendarId, trips } }) const createUrl = `/api/manager/secure/trip?feedId=${feedId}` - return secureFetch(createUrl, getState(), 'post') + return dispatch(secureFetch(createUrl, 'post')) .then(res => { if (res.status >= 300) { errorCount++ @@ -130,7 +130,7 @@ export function saveMultipleTripsForCalendar (feedId, pattern, calendarId, trips // const url = trip.id !== 'new' // ? `/api/manager/secure/trip/${trip.id}?feedId=${feedId}` // : `/api/manager/secure/trip?feedId=${feedId}` -// return secureFetch(url, getState()) +// return dispatch(secureFetch(url)) // .then(res => res.json()) // .then(trips => { // return trip @@ -164,7 +164,7 @@ export function deleteTripsForCalendar (feedId, pattern, calendarId, trips) { dispatch(deletingTrips(feedId, pattern, calendarId, trips)) return Promise.all(trips.map(trip => { const url = `/api/manager/secure/trip/${trip.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete', trip) + return dispatch(secureFetch(url, 'delete', trip)) .then(res => res.json()) .catch(error => { console.log(error) @@ -234,7 +234,7 @@ export function removeTrips (indexes) { // return function (dispatch, getState) { // dispatch(requestingTripsForCalendar(feedId, patternId, calendarId)) // const url = `/api/manager/secure/trip?feedId=${feedId}&patternId=${patternId}&calendarId=${calendarId}` -// return secureFetch(url, getState()) +// return dispatch(secureFetch(url)) // .then(res => res.json()) // .then(trips => { // dispatch(receiveTripsForCalendar(feedId, patternId, calendarId, trips)) diff --git a/lib/editor/actions/tripPattern.js b/lib/editor/actions/tripPattern.js index f0120b37a..e4cc62978 100644 --- a/lib/editor/actions/tripPattern.js +++ b/lib/editor/actions/tripPattern.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setErrorMessage } from '../../manager/actions/status' import { setActiveGtfsEntity } from './active' import { getStopsForPattern, getTimetableColumns } from '../util' @@ -32,7 +32,7 @@ export function fetchTripPatterns (feedId) { return function (dispatch, getState) { dispatch(requestingTripPatterns(feedId)) const url = `/api/manager/secure/trippattern?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 400) return [] return res.json() @@ -48,7 +48,7 @@ export function fetchTripPattern (feedId, tripPatternId) { return function (dispatch, getState) { dispatch(requestingTripPatternsForRoute(feedId)) const url = `/api/manager/secure/trippattern/${tripPatternId}?feedId=${feedId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 300) return null return res.json() @@ -110,7 +110,7 @@ export function fetchTripPatternsForRoute (feedId, routeId) { } dispatch(requestingTripPatternsForRoute(feedId)) const url = `/api/manager/secure/trippattern?feedId=${feedId}&routeId=${routeId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 400) { // dispatch(setErrorMessage('Error getting stops for trip pattern')) @@ -144,7 +144,7 @@ export function deleteTripPattern (feedId, tripPattern) { return dispatch(fetchTripPatternsForRoute(feedId, routeId)) } const url = `/api/manager/secure/trippattern/${tripPattern.id}?feedId=${feedId}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(res => res.json()) .then(tripPattern => { dispatch(fetchTripPatternsForRoute(feedId, routeId)) @@ -169,7 +169,7 @@ export function saveTripPattern (feedId, tripPattern) { ? `/api/manager/secure/trippattern/${tripPattern.id}?feedId=${feedId}` : `/api/manager/secure/trippattern?feedId=${feedId}` data.id = tripPattern.id === 'new' ? null : tripPattern.id - return secureFetch(url, getState(), method, data) + return dispatch(secureFetch(url, method, data)) .then(res => { if (res.status >= 300) { dispatch(setErrorMessage('Error saving trip pattern')) diff --git a/lib/gtfs/actions/filter.js b/lib/gtfs/actions/filter.js index 2bfc42cba..dd357b424 100644 --- a/lib/gtfs/actions/filter.js +++ b/lib/gtfs/actions/filter.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { getFeed } from '../../common/util/modules' import {getActiveProject} from '../../manager/selectors' @@ -29,7 +29,7 @@ export function updateGtfsFilter (activeProject, user) { dispatch(updatingGtfsFilter(activeProject, user)) // check GTFS API for feed IDs present in cache - return secureFetch('/api/manager/feeds', getState()) + return dispatch(secureFetch('/api/manager/feeds')) .then(response => response.json()) .then((feedIds) => { const activeFeeds = getActiveProject(getState()).feedSources diff --git a/lib/gtfsplus/actions/gtfsplus.js b/lib/gtfsplus/actions/gtfsplus.js index fea297863..fdb054033 100644 --- a/lib/gtfsplus/actions/gtfsplus.js +++ b/lib/gtfsplus/actions/gtfsplus.js @@ -1,7 +1,7 @@ import JSZip from 'jszip' import fetch from 'isomorphic-fetch' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { getConfigProperty } from '../../common/util/config' import { fetchFeedVersions } from '../../manager/actions/feeds' import { getFeedId } from '../../common/util/modules' @@ -81,7 +81,7 @@ export function downloadGtfsPlusFeed (feedVersionId) { return response.blob() }) - const fetchTimestamp = secureFetch(`/api/manager/secure/gtfsplus/${feedVersionId}/timestamp`, getState()) + const fetchTimestamp = dispatch(secureFetch(`/api/manager/secure/gtfsplus/${feedVersionId}/timestamp`)) .then(response => response.json()) Promise.all([fetchFeed, fetchTimestamp]).then(([feed, timestamp]) => { @@ -128,7 +128,7 @@ export function validateGtfsPlusFeed (feedVersionId) { return function (dispatch, getState) { dispatch(validatingGtfsPlusFeed()) const url = `/api/manager/secure/gtfsplus/${feedVersionId}/validation` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => res.json()) .then(validationIssues => { dispatch(receiveGtfsPlusValidation(validationIssues)) @@ -254,7 +254,7 @@ export function publishGtfsPlusFeed (feedVersion) { return function (dispatch, getState) { dispatch(publishingGtfsPlusFeed()) const url = `/api/manager/secure/gtfsplus/${feedVersion.id}/publish` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then((res) => { console.log('published done') return dispatch(fetchFeedVersions(feedVersion.feedSource)) diff --git a/lib/manager/actions/deployments.js b/lib/manager/actions/deployments.js index a77349496..32d6ba406 100644 --- a/lib/manager/actions/deployments.js +++ b/lib/manager/actions/deployments.js @@ -1,6 +1,6 @@ import { browserHistory } from 'react-router' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { receiveProject } from './projects' import { startJobMonitor } from './status' @@ -24,7 +24,7 @@ export function fetchProjectDeployments (projectId) { return function (dispatch, getState) { dispatch(requestingDeployments()) const url = '/api/manager/secure/deployments?projectId=' + projectId - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(deployments => { dispatch(receiveDeployments(projectId, deployments)) @@ -52,7 +52,7 @@ export function deployToTarget (deployment, target) { return function (dispatch, getState) { dispatch(deployingToTarget(deployment, target)) const url = `/api/manager/secure/deployments/${deployment.id}/deploy/${target}` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then(response => { console.log(response) if (response.status >= 300) { @@ -97,7 +97,7 @@ export function fetchDeploymentStatus (deployment, target) { return function (dispatch, getState) { dispatch(requestingDeploymentStatus()) const url = `/api/manager/secure/deployments/status/${deployment.id}?target=${target}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(status => { console.log(status) @@ -110,7 +110,7 @@ export function fetchDeployment (id) { return function (dispatch, getState) { dispatch(requestingDeployment()) const url = '/api/manager/secure/deployments/' + id - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(deployment => { dispatch(receiveDeployment(deployment.project.id, deployment)) @@ -123,7 +123,7 @@ export function downloadDeployment (deployment) { // dispatch(downloadingDeployment()) const url = '/api/manager/secure/deployments/' + deployment.id + '/download' window.location.assign(url) - // return secureFetch(url, getState()) + // return dispatch(secureFetch(url)) // .then(response => response.json()) // .then(deployment => { // console.log(deployment) @@ -136,7 +136,7 @@ export function fetchDeploymentAndProject (id) { return function (dispatch, getState) { dispatch(requestingDeployment()) const url = '/api/manager/secure/deployments/' + id - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(deployment => { dispatch(receiveProject(deployment.project)) @@ -179,7 +179,7 @@ export function deleteDeployment (deployment) { return dispatch(fetchProjectDeployments(deployment.project.id)) } const url = '/api/manager/secure/deployments/' + deployment.id - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then((res) => { return dispatch(fetchProjectDeployments(deployment.project.id)) }) @@ -195,7 +195,7 @@ export function saveDeployment (props) { return function (dispatch, getState) { dispatch(savingDeployment()) const url = '/api/manager/secure/deployments' - return secureFetch(url, getState(), 'post', props) + return dispatch(secureFetch(url, 'post', props)) .then(response => response.json()) .then(deployment => { console.log('created deployment', deployment) @@ -208,7 +208,7 @@ export function createDeploymentFromFeedSource (feedSource) { return function (dispatch, getState) { dispatch(savingDeployment()) const url = '/api/manager/secure/deployments/fromfeedsource/' + feedSource.id - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then(response => response.json()) .then(deployment => { console.log('created deployment', deployment) @@ -222,7 +222,7 @@ export function updateDeployment (deployment, changes) { return function (dispatch, getState) { dispatch(savingDeployment()) const url = '/api/manager/secure/deployments/' + deployment.id - return secureFetch(url, getState(), 'put', changes) + return dispatch(secureFetch(url, 'put', changes)) .then(response => response.json()) .then(deployment => { dispatch(fetchDeployment(deployment.id)) diff --git a/lib/manager/actions/feeds.js b/lib/manager/actions/feeds.js index d5b34d561..f3919e882 100644 --- a/lib/manager/actions/feeds.js +++ b/lib/manager/actions/feeds.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { fetchProject, fetchProjectWithFeeds } from './projects' import { setErrorMessage, startJobMonitor } from './status' import { fetchFeedVersions, feedNotModified } from './versions' @@ -22,7 +22,7 @@ export function fetchProjectFeeds (projectId) { return function (dispatch, getState) { dispatch(requestingFeedSources()) const url = '/api/manager/secure/feedsource?projectId=' + projectId - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(feedSources => { dispatch(receiveFeedSources(projectId, feedSources)) @@ -35,7 +35,7 @@ export function fetchUserFeeds (userId) { return function (dispatch, getState) { dispatch(requestingFeedSources()) const url = '/api/manager/secure/feedsource?userId=' + userId - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(feedSources => { dispatch(receiveFeedSources(feedSources)) @@ -67,7 +67,7 @@ export function saveFeedSource (props) { return function (dispatch, getState) { dispatch(savingFeedSource()) const url = '/api/manager/secure/feedsource' - return secureFetch(url, getState(), 'post', props) + return dispatch(secureFetch(url, 'post', props)) .then((res) => { return dispatch(fetchProjectWithFeeds(props.projectId)) }) @@ -78,7 +78,7 @@ export function updateFeedSource (feedSource, changes) { return function (dispatch, getState) { dispatch(savingFeedSource()) const url = '/api/manager/secure/feedsource/' + feedSource.id - return secureFetch(url, getState(), 'put', changes) + return dispatch(secureFetch(url, 'put', changes)) .then((res) => { if (res.status >= 400) { console.log(res.json()) @@ -95,7 +95,7 @@ export function updateExternalFeedResource (feedSource, resourceType, properties console.log('updateExternalFeedResource', feedSource, resourceType, properties) dispatch(savingFeedSource()) const url = `/api/manager/secure/feedsource/${feedSource.id}/updateExternal?resourceType=${resourceType}` - return secureFetch(url, getState(), 'put', properties) + return dispatch(secureFetch(url, 'put', properties)) .then((res) => { return dispatch(fetchFeedSource(feedSource.id, true)) }) @@ -113,7 +113,7 @@ export function deleteFeedSource (feedSource, changes) { return function (dispatch, getState) { dispatch(deletingFeedSource(feedSource)) const url = '/api/manager/secure/feedsource/' + feedSource.id - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then((res) => { // if (res.status >= 400) { // return dispatch(setErrorMessage('Error deleting feed source')) @@ -141,7 +141,7 @@ export function fetchFeedSource (feedSourceId, withVersions = false, withSnapsho console.log('fetchFeedSource', feedSourceId) dispatch(requestingFeedSource()) const url = '/api/manager/secure/feedsource/' + feedSourceId - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 400) { // dispatch(setErrorMessage('Error getting feed source')) @@ -169,7 +169,7 @@ export function fetchFeedSourceAndProject (feedSourceId, unsecured) { dispatch(requestingFeedSource()) const apiRoot = unsecured ? 'public' : 'secure' const url = `/api/manager/${apiRoot}/feedsource/${feedSourceId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { if (res.status >= 400) { // dispatch(setErrorMessage('Error getting feed source')) @@ -196,7 +196,7 @@ export function fetchPublicFeedSource (feedSourceId) { return function (dispatch, getState) { dispatch(requestingFeedSource()) const url = '/api/manager/public/feedsource/' + feedSourceId - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(feedSource => { dispatch(receivePublicFeeds()) @@ -222,7 +222,7 @@ export function runFetchFeed (feedSource) { return function (dispatch, getState) { dispatch(runningFetchFeed()) const url = `/api/manager/secure/feedsource/${feedSource.id}/fetch` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then(res => { if (res.status === 304) { dispatch(feedNotModified(feedSource, 'Feed fetch cancelled because it matches latest feed version.')) diff --git a/lib/manager/actions/notes.js b/lib/manager/actions/notes.js index 6252591a3..fc062c2a0 100644 --- a/lib/manager/actions/notes.js +++ b/lib/manager/actions/notes.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' export function requestingNotes () { return { @@ -18,7 +18,7 @@ export function fetchNotesForFeedSource (feedSource) { return function (dispatch, getState) { dispatch(requestingNotes()) const url = `/api/manager/secure/note?type=FEED_SOURCE&objectId=${feedSource.id}` - secureFetch(url, getState()) + dispatch(secureFetch(url)) .then(response => response.json()) .then(notes => { dispatch(receiveNotesForFeedSource(feedSource, notes)) @@ -29,7 +29,7 @@ export function fetchNotesForFeedSource (feedSource) { export function postNoteForFeedSource (feedSource, note) { return function (dispatch, getState) { const url = `/api/manager/secure/note?type=FEED_SOURCE&objectId=${feedSource.id}` - secureFetch(url, getState(), 'post', note) + dispatch(secureFetch(url, 'post', note)) .then(response => response.json()) .then(note => { dispatch(fetchNotesForFeedSource(feedSource)) @@ -49,7 +49,7 @@ export function fetchNotesForFeedVersion (feedVersion) { return function (dispatch, getState) { dispatch(requestingNotes()) const url = `/api/manager/secure/note?type=FEED_VERSION&objectId=${feedVersion.id}` - secureFetch(url, getState()) + dispatch(secureFetch(url)) .then(response => response.json()) .then(notes => { dispatch(receiveNotesForFeedVersion(feedVersion, notes)) @@ -60,7 +60,7 @@ export function fetchNotesForFeedVersion (feedVersion) { export function postNoteForFeedVersion (feedVersion, note) { return function (dispatch, getState) { const url = `/api/manager/secure/note?type=FEED_VERSION&objectId=${feedVersion.id}` - secureFetch(url, getState(), 'post', note) + dispatch(secureFetch(url, 'post', note)) .then(response => response.json()) .then(note => { dispatch(fetchNotesForFeedVersion(feedVersion)) diff --git a/lib/manager/actions/projects.js b/lib/manager/actions/projects.js index 32e1e6704..15ae0a83b 100644 --- a/lib/manager/actions/projects.js +++ b/lib/manager/actions/projects.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { updateGtfsFilter } from '../../gtfs/actions/filter' import { setErrorMessage, startJobMonitor } from './status' import { fetchProjectFeeds } from './feeds' @@ -37,7 +37,7 @@ export function setActiveProject (project) { export function fetchProjects (getActive = false) { return function (dispatch, getState) { dispatch(requestingProjects()) - return secureFetch('/api/manager/secure/project', getState()) + return dispatch(secureFetch('/api/manager/secure/project')) .then(response => response.json()) .then(projects => { dispatch(receiveProjects(projects)) @@ -63,7 +63,7 @@ export function fetchProjectsWithPublicFeeds () { return function (dispatch, getState) { dispatch(requestingProjects()) const url = '/api/manager/public/project' - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) // .catch(err => console.log(err)) .then(projects => { @@ -92,7 +92,7 @@ export function fetchProject (projectId, unsecure) { dispatch(requestingProject()) const apiRoot = unsecure ? 'public' : 'secure' const url = `/api/manager/${apiRoot}/project/${projectId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) // .catch(err => console.log(err)) .then(project => { @@ -109,7 +109,7 @@ export function fetchProjectWithFeeds (projectId, unsecure) { dispatch(requestingProject()) const apiRoot = unsecure ? 'public' : 'secure' const url = `/api/manager/${apiRoot}/project/${projectId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) // .catch(err => console.log(err)) .then(project => { @@ -138,7 +138,7 @@ export function deleteProject (project) { return function (dispatch, getState) { dispatch(deletingProject()) const url = `/api/manager/secure/project/${project.id}` - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then(response => response.json()) // .catch(err => console.log(err)) .then(project => { @@ -152,7 +152,7 @@ export function updateProject (project, changes, fetchFeeds = false) { return function (dispatch, getState) { dispatch(savingProject()) const url = `/api/manager/secure/project/${project.id}` - return secureFetch(url, getState(), 'put', changes) + return dispatch(secureFetch(url, 'put', changes)) .then((res) => { if (fetchFeeds) { return dispatch(fetchProjectWithFeeds(project.id)) @@ -167,7 +167,7 @@ export function deployPublic (project) { return function (dispatch, getState) { // dispatch(savingProject()) const url = `/api/manager/secure/project/${project.id}/deployPublic` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then((res) => res.json()) .then(json => { return json @@ -198,7 +198,7 @@ export function thirdPartySync (projectId, type) { return function (dispatch, getState) { dispatch(requestingSync()) const url = '/api/manager/secure/project/' + projectId + '/thirdPartySync/' + type - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) // .catch(err => console.log(err)) .then(project => { @@ -225,7 +225,7 @@ export function fetchFeedsForProject (project) { return function (dispatch, getState) { dispatch(runningFetchFeedsForProject()) const url = `/api/manager/secure/project/${project.id}/fetch` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then(res => { if (res.status === 304) { // dispatch(feedNotModified(feedSource, 'Feed fetch cancelled because it matches latest feed version.')) @@ -257,7 +257,7 @@ export function saveProject (props) { return function (dispatch, getState) { dispatch(savingProject(props)) const url = '/api/manager/secure/project' - return secureFetch(url, getState(), 'post', props) + return dispatch(secureFetch(url, 'post', props)) .then((res) => { return dispatch(fetchProjects()) }) @@ -270,7 +270,7 @@ export function downloadFeedForProject (project) { return function (dispatch, getState) { const url = `/api/manager/public/project/${project.id}/download` window.location.assign(url) - // return secureFetch(url, getState()) + // return dispatch(secureFetch(url)) // .then(response => { // console.log(response.body) // return response.body diff --git a/lib/manager/actions/status.js b/lib/manager/actions/status.js index 093138bc2..da6ef4394 100644 --- a/lib/manager/actions/status.js +++ b/lib/manager/actions/status.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { fetchFeedSource } from './feeds' import { fetchSnapshots } from '../../editor/actions/snapshots' @@ -33,7 +33,7 @@ export function watchStatus (job) { const active = getState().status.popover.active; if (active) { - return secureFetch(`/api/manager/secure/${job}/status`, getState()) + return dispatch(secureFetch(`/api/manager/secure/${job}/status`)) .then(response => response.json()) .then(status => dispatch(receiveProjects(projects)) @@ -61,7 +61,7 @@ export function receiveJobs (jobs) { export function checkJobStatus () { return function (dispatch, getState) { - return secureFetch(`/api/manager/secure/status/jobs`, getState()) + return dispatch(secureFetch(`/api/manager/secure/status/jobs`)) .then(response => response.json()) .then(jobs => { // check for any just-finished jobs diff --git a/lib/manager/actions/user.js b/lib/manager/actions/user.js index 21b33def1..ea26c58ad 100644 --- a/lib/manager/actions/user.js +++ b/lib/manager/actions/user.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import objectPath from 'object-path' export function checkingExistingLogin (loginProps) { @@ -53,7 +53,7 @@ export function checkExistingLogin (loginProps) { export function fetchUser (user) { return function (dispatch, getState) { const url = '/api/manager/secure/user/' + user - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(user => { // console.log(user) @@ -90,7 +90,7 @@ export function updateUserMetadata (profile, props) { const payload = {user_metadata: userMetadata} dispatch(updatingUserMetadata(profile, props, payload)) const url = `https://${process.env.AUTH0_DOMAIN}/api/v2/users/${profile.user_id}` - return secureFetch(url, getState(), 'PATCH', payload) + return dispatch(secureFetch(url, 'PATCH', payload)) .then(response => response.json()) .then(user => { console.log(user) @@ -158,7 +158,7 @@ export function updateUserData (user, userData) { data: datatools } const url = '/api/manager/secure/user/' + user.user_id - return secureFetch(url, getState(), 'put', payload) + return dispatch(secureFetch(url, 'put', payload)) .then(response => response.json()) .then(user => { console.log(user) @@ -177,7 +177,7 @@ export function updateUserData (user, userData) { // data: permissions // } // -// return secureFetch(url, getState(), 'put', payload) +// return dispatch(secureFetch(url, 'put', payload)) // // }).done((data) => { // console.log('update user ok', data) @@ -205,7 +205,7 @@ export function createPublicUser (credentials) { dispatch(creatingPublicUser()) console.log(credentials) const url = '/api/manager/public/user' - return secureFetch(url, getState(), 'post', credentials) + return dispatch(secureFetch(url, 'post', credentials)) .then(response => response.json()) .then(profile => { return dispatch(createdPublicUser(profile)) @@ -234,7 +234,7 @@ export function login (credentials, user, lockOptions) { credentials.grant_type = 'password' credentials.scope = 'openid' const url = 'https://conveyal.eu.auth0.com/oauth/ro' - return secureFetch(url, getState(), 'post', credentials) + return dispatch(secureFetch(url, 'post', credentials)) .then(response => response.json()) .then(token => { // save token to localStorage @@ -274,7 +274,7 @@ export function getRecentActivity (user) { return function (dispatch, getState) { const userId = user.profile.user_id const url = `/api/manager/secure/user/${userId}/recentactivity` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(activity => { return dispatch(receiveRecentActivity(activity)) diff --git a/lib/manager/actions/versions.js b/lib/manager/actions/versions.js index cf6a3b73c..fcda40cd4 100644 --- a/lib/manager/actions/versions.js +++ b/lib/manager/actions/versions.js @@ -1,7 +1,7 @@ import qs from 'qs' import fetch from 'isomorphic-fetch' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { setErrorMessage, startJobMonitor } from './status' import { fetchFeedSource } from './feeds' @@ -31,7 +31,7 @@ export function fetchFeedVersions (feedSource, unsecured) { dispatch(requestingFeedVersions()) const apiRoot = unsecured ? 'public' : 'secure' const url = `/api/manager/${apiRoot}/feedversion?feedSourceId=${feedSource.id}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(versions => { dispatch(receiveFeedVersions(feedSource, versions)) @@ -57,7 +57,7 @@ export function fetchFeedVersion (feedVersionId) { return function (dispatch, getState) { dispatch(requestingFeedVersion()) const url = `/api/manager/secure/feedversion/${feedVersionId}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(version => { return dispatch(receiveFeedVersion(version)) @@ -83,7 +83,7 @@ export function publishFeedVersion (feedVersion) { return function (dispatch, getState) { dispatch(publishingFeedVersion(feedVersion)) const url = `/api/manager/secure/feedversion/${feedVersion.id}/publish` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then(response => response.json()) .then(version => { return dispatch(publishedFeedVersion(version)) @@ -95,7 +95,7 @@ export function fetchPublicFeedVersions (feedSource) { return function (dispatch, getState) { dispatch(requestingFeedVersions()) const url = `/api/manager/public/feedversion?feedSourceId=${feedSource.id}&public=true` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(versions => { dispatch(receiveFeedVersions(feedSource, versions)) @@ -168,7 +168,7 @@ export function deleteFeedVersion (feedVersion, changes) { return function (dispatch, getState) { dispatch(deletingFeedVersion(feedVersion)) const url = '/api/manager/secure/feedversion/' + feedVersion.id - return secureFetch(url, getState(), 'delete') + return dispatch(secureFetch(url, 'delete')) .then((res) => { // fetch feed source with versions + snapshots return dispatch(fetchFeedSource(feedVersion.feedSource.id, true, true)) @@ -193,7 +193,7 @@ export function fetchValidationResult (feedVersion, isPublic) { dispatch(requestingValidationResult(feedVersion)) const route = isPublic ? 'public' : 'secure' const url = `/api/manager/${route}/feedversion/${feedVersion.id}/validation` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(response => response.json()) .then(result => { dispatch(receiveValidationResult(feedVersion, result)) @@ -241,7 +241,7 @@ export function fetchFeedVersionIsochrones (feedVersion, fromLat, fromLon, toLat dispatch(requestingFeedVersionIsochrones(feedVersion, fromLat, fromLon, toLat, toLon, date, fromTime, toTime)) const params = {fromLat, fromLon, toLat, toLon, date, fromTime, toTime} const url = `/api/manager/secure/feedversion/${feedVersion.id}/isochrones?${qs.stringify(params)}` - return secureFetch(url, getState()) + return dispatch(secureFetch(url)) .then(res => { console.log(res.status) if (res.status === 202) { @@ -264,7 +264,7 @@ export function downloadFeedViaToken (feedVersion, isPublic) { return function (dispatch, getState) { const route = isPublic ? 'public' : 'secure' const url = `/api/manager/${route}/feedversion/${feedVersion.id}/downloadtoken` - secureFetch(url, getState()) + dispatch(secureFetch(url)) .then(response => response.json()) .then(result => { window.location.assign(`/api/manager/downloadfeed/${result.id}`) @@ -281,9 +281,9 @@ export function createFeedVersionFromSnapshot (feedSource, snapshotId) { return function (dispatch, getState) { dispatch(creatingFeedVersionFromSnapshot()) const url = `/api/manager/secure/feedversion/fromsnapshot?feedSourceId=${feedSource.id}&snapshotId=${snapshotId}` - return secureFetch(url, getState(), 'post') + return dispatch(secureFetch(url, 'post')) .then((res) => { - dispatch(startJobMonitor()) + if (res) dispatch(startJobMonitor()) }) } } @@ -298,7 +298,7 @@ export function renameFeedVersion (feedVersion, name) { return function (dispatch, getState) { dispatch(renamingFeedVersion()) const url = `/api/manager/secure/feedversion/${feedVersion.id}/rename?name=${name}` - return secureFetch(url, getState(), 'put') + return dispatch(secureFetch(url, 'put')) .then((res) => { dispatch(fetchFeedVersion(feedVersion.id)) }) diff --git a/lib/signs/actions/signs.js b/lib/signs/actions/signs.js index f20b7581d..30bd433de 100644 --- a/lib/signs/actions/signs.js +++ b/lib/signs/actions/signs.js @@ -2,7 +2,7 @@ import { browserHistory } from 'react-router' import fetch from 'isomorphic-fetch' import { getSignConfigUrl, getDisplaysUrl, getFeedId } from '../../common/util/modules' -import { secureFetch } from '../../common/util/util' +import { secureFetch } from '../../common/actions' import { fetchStopsAndRoutes } from '../../gtfs/actions/general' import {getActiveProject} from '../../manager/selectors' @@ -233,7 +233,7 @@ export function saveSign (sign) { } const url = getSignConfigUrl() + (sign.id < 0 ? '' : '/' + sign.id) const method = sign.id < 0 ? 'post' : 'put' - secureFetch(url, getState(), method, json) + dispatch(secureFetch(url, method, json)) .then(res => res.json()) .then(json => { const saveDisplays = [] From e26535062db6d3801208832131755fdf04bed0ae Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 6 Apr 2017 11:32:27 -0400 Subject: [PATCH 041/265] refactor(secureFetch): delete previous definition of secureFetch --- lib/common/util/util.js | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/lib/common/util/util.js b/lib/common/util/util.js index 4b7165e3b..da477e840 100644 --- a/lib/common/util/util.js +++ b/lib/common/util/util.js @@ -1,5 +1,3 @@ -import fetch from 'isomorphic-fetch' -// import { setErrorMessage } from '../../manager/actions/status' import gravatar from 'gravatar' export function defaultSorter (a, b) { @@ -24,41 +22,6 @@ export function retrievalMethodString (method) { } } -export function secureFetch (url, state, method, payload, raw) { - // return function (dispatch, getState) { - var opts = { - method: method || 'get', - headers: { - 'Authorization': 'Bearer ' + state.user.token, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - } - if (payload) opts.body = JSON.stringify(payload) - return fetch(url, opts) - .catch(err => { - console.log(err) - }) - // .then(res => { - // // if raw response is requested - // if (raw) return res - // - // // check for errors - // if (res.status >= 500) { - // dispatch(setErrorMessage('Network error!')) - // return null - // } else if (res.status >= 300) { - // res.text().then(text => { - // dispatch(setErrorMessage(text)) - // }) - // return null - // } else { - // return res.json() - // } - // }) - // } -} - export function generateUID () { return ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4) } From 8a0ecab74fb2b7dab3bbcda2760b8d503b69af9d Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 6 Apr 2017 15:08:34 -0400 Subject: [PATCH 042/265] refactor(mount): refactor app to use woonerf mount --- index.html | 2 +- lib/main.js | 50 +++++++++++++++++--------------------------------- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/index.html b/index.html index 3acf1d168..124173b83 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,7 @@ -
+
diff --git a/lib/main.js b/lib/main.js index db2e91dc3..87c168529 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,14 +1,17 @@ import 'babel-polyfill' -import React from 'react' -import ReactDOM from 'react-dom' -import { Provider } from 'react-redux' -import { createStore, applyMiddleware, combineReducers } from 'redux' -import thunkMiddleware from 'redux-thunk' -import { browserHistory } from 'react-router' -import { syncHistoryWithStore, routerReducer } from 'react-router-redux' -import createLogger from 'redux-logger' +import mount from '@conveyal/woonerf/mount' +import { combineReducers } from 'redux' +import { routerReducer } from 'react-router-redux' import App from './common/containers/App' +import * as managerReducers from './manager/reducers' +import admin from './admin/reducers' +import alerts from './alerts/reducers' +import signs from './signs/reducers' + +import * as gtfsPlusReducers from './gtfsplus/reducers' +import editor from './editor/reducers' +import gtfs from './gtfs/reducers' const config = JSON.parse(process.env.SETTINGS) if (config.modules.gtfsplus && config.modules.gtfsplus.enabled) { @@ -34,37 +37,18 @@ config.messages.active = lang.find(l => l.id === languageId) || lang.find(l => l // console.log('config', config) window.DT_CONFIG = config -import * as managerReducers from './manager/reducers' -import admin from './admin/reducers' -import alerts from './alerts/reducers' -import signs from './signs/reducers' - -import * as gtfsPlusReducers from './gtfsplus/reducers' -import editor from './editor/reducers' -import gtfs from './gtfs/reducers' - -const logger = createLogger({duration: true, collapsed: true}) -const store = createStore( -combineReducers({ +const reducers = combineReducers({ ...managerReducers, admin, alerts, signs, ...gtfsPlusReducers, editor, - // ...reportReducers, routing: routerReducer, gtfs -}), -applyMiddleware(thunkMiddleware, logger) -) - -// console.log('initial store', store.getState()) - -const appHistory = syncHistoryWithStore(browserHistory, store) +}) -ReactDOM.render( - - - , -document.getElementById('main')) +mount({ + app: App, + reducers +}) From aa7f3accf8be47b1776d30a18484664742fdbb8f Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Thu, 6 Apr 2017 14:50:52 -0700 Subject: [PATCH 043/265] refactor(store): build store with woonerf --- lib/main.js | 23 +++++++++-------------- package.json | 5 +---- yarn.lock | 13 +++++++------ 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/main.js b/lib/main.js index 87c168529..4c7a2782c 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,7 +1,5 @@ import 'babel-polyfill' import mount from '@conveyal/woonerf/mount' -import { combineReducers } from 'redux' -import { routerReducer } from 'react-router-redux' import App from './common/containers/App' import * as managerReducers from './manager/reducers' @@ -37,18 +35,15 @@ config.messages.active = lang.find(l => l.id === languageId) || lang.find(l => l // console.log('config', config) window.DT_CONFIG = config -const reducers = combineReducers({ - ...managerReducers, - admin, - alerts, - signs, - ...gtfsPlusReducers, - editor, - routing: routerReducer, - gtfs -}) - mount({ app: App, - reducers + reducers: { + ...managerReducers, + admin, + alerts, + signs, + ...gtfsPlusReducers, + editor, + gtfs + } }) diff --git a/package.json b/package.json index a4873ae7e..a9ff75ff0 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@conveyal/lonlat": "^1.1.2", - "@conveyal/woonerf": "^2.1.0", + "@conveyal/woonerf": "^2.2.1", "auth0-lock": "9", "babel-polyfill": "^6.22.0", "bootstrap": "^3.3.7", @@ -61,16 +61,13 @@ "react-redux": "^5.0.3", "react-router": "^3.0.0-alpha.1", "react-router-bootstrap": "0.23.1", - "react-router-redux": "^4.0.0", "react-select": "^1.0.0-beta14", "react-toggle": "^3.0.0", "react-virtualized": "^9.2.2", "react-virtualized-select": "^2.1.0", "reduce-reducers": "^0.1.2", "redux": "^3.3.1", - "redux-logger": "^2.6.1", "redux-merge-reducers": "^0.0.2", - "redux-thunk": "^2.0.1", "reselect": "^3.0.0", "shpjs": "^3.3.2", "truncate": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index c20382e15..2d933c635 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,9 +6,9 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@conveyal/lonlat/-/lonlat-1.2.0.tgz#da64624e111da65161b640c98d27765951130fa8" -"@conveyal/woonerf@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@conveyal/woonerf/-/woonerf-2.1.0.tgz#b1aff1d1a93a88654d780f440da94e6102bb1e50" +"@conveyal/woonerf@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@conveyal/woonerf/-/woonerf-2.2.1.tgz#039f5ed38457eeff5c7cba0e59a74930002e5d89" dependencies: auth0-lock "^10.10.2" isomorphic-fetch "^2.2.1" @@ -22,6 +22,7 @@ redux "^3.6.0" redux-actions "^1.2.1" redux-logger "^2.7.4" + redux-thunk "^2.2.0" "@semantic-release/commit-analyzer@^2.0.0": version "2.0.0" @@ -5789,7 +5790,7 @@ react-router-bootstrap@0.23.1: version "0.23.1" resolved "https://registry.yarnpkg.com/react-router-bootstrap/-/react-router-bootstrap-0.23.1.tgz#a1e0b82f49d25a6083c72202ad16aaa64bba0e0c" -react-router-redux@^4.0.0, react-router-redux@^4.0.7: +react-router-redux@^4.0.7: version "4.0.8" resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.8.tgz#227403596b5151e182377dab835b5d45f0f8054e" @@ -5992,7 +5993,7 @@ redux-actions@^1.2.1: lodash "^4.13.1" reduce-reducers "^0.1.0" -redux-logger@^2.6.1, redux-logger@^2.7.4: +redux-logger@^2.7.4: version "2.8.2" resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-2.8.2.tgz#52140a89afa1c1d25312cc17649c116650bf7fca" dependencies: @@ -6002,7 +6003,7 @@ redux-merge-reducers@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/redux-merge-reducers/-/redux-merge-reducers-0.0.2.tgz#2d83da42de18636bd526be183de19e9a3a0d647b" -redux-thunk@^2.0.1: +redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" From c923b93b93b70edcaa4356b7813825d05d980ef9 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 7 Apr 2017 12:36:34 -0400 Subject: [PATCH 044/265] refactor(config): move config initialization to util function --- lib/common/util/config.js | 26 ++++++++++++++++++++++++++ lib/main.js | 25 ++----------------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/lib/common/util/config.js b/lib/common/util/config.js index c412634e5..5ff901ddc 100644 --- a/lib/common/util/config.js +++ b/lib/common/util/config.js @@ -20,3 +20,29 @@ export const isModuleEnabled = (moduleName) => { export const isExtensionEnabled = (extensionName) => { return Boolean(objectPath.get(window.DT_CONFIG, ['extensions', extensionName, 'enabled'])) } + +export function initializeConfig () { + const config = JSON.parse(process.env.SETTINGS) + if (config.modules.gtfsplus && config.modules.gtfsplus.enabled) { + config.modules.gtfsplus.spec = require('../../../gtfsplus.yml') + } + config.modules.editor.spec = require('../../../gtfs.yml') + + const lang = [ + require('../../../i18n/english.yml'), + require('../../../i18n/espanol.yml'), + require('../../../i18n/francais.yml') + ] + + // is an array containing all the matching modules + config.messages = {} + config.messages.all = lang + const languageId = window.localStorage.getItem('lang') + ? window.localStorage.getItem('lang') + : navigator.language || navigator.userLanguage + + config.messages.active = lang.find(l => l.id === languageId) || lang.find(l => l.id === 'en-US') + + // console.log('config', config) + window.DT_CONFIG = config +} diff --git a/lib/main.js b/lib/main.js index 4c7a2782c..9d4ccceb4 100644 --- a/lib/main.js +++ b/lib/main.js @@ -2,6 +2,7 @@ import 'babel-polyfill' import mount from '@conveyal/woonerf/mount' import App from './common/containers/App' +import {initializeConfig} from './common/util/config' import * as managerReducers from './manager/reducers' import admin from './admin/reducers' import alerts from './alerts/reducers' @@ -11,29 +12,7 @@ import * as gtfsPlusReducers from './gtfsplus/reducers' import editor from './editor/reducers' import gtfs from './gtfs/reducers' -const config = JSON.parse(process.env.SETTINGS) -if (config.modules.gtfsplus && config.modules.gtfsplus.enabled) { - config.modules.gtfsplus.spec = require('../gtfsplus.yml') -} -config.modules.editor.spec = require('../gtfs.yml') - -const lang = [ - require('../i18n/english.yml'), - require('../i18n/espanol.yml'), - require('../i18n/francais.yml') -] - -// is an array containing all the matching modules -config.messages = {} -config.messages.all = lang -const languageId = window.localStorage.getItem('lang') -? window.localStorage.getItem('lang') -: navigator.language || navigator.userLanguage - -config.messages.active = lang.find(l => l.id === languageId) || lang.find(l => l.id === 'en-US') - -// console.log('config', config) -window.DT_CONFIG = config +initializeConfig() mount({ app: App, From 344c74f4731ac51c0b7d7062e819202cab9ec299 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 11:19:13 -0400 Subject: [PATCH 045/265] refactor(container): refactor mapDispatchToProps --- lib/common/containers/ActiveSidebar.js | 2 - lib/common/containers/ActiveSidebarNavItem.js | 4 +- lib/common/containers/App.js | 12 ++-- lib/common/containers/CurrentStatusModal.js | 6 +- lib/common/containers/PageContent.js | 18 +++--- lib/common/containers/ProtectedButton.js | 5 +- .../containers/ActiveEditorFeedSourcePanel.js | 26 +++----- lib/editor/containers/ActiveEntityList.js | 6 +- lib/editor/containers/ActiveFeedInfoPanel.js | 12 ++-- lib/editor/containers/ActiveGtfsEditor.js | 3 - .../containers/ActiveTimetableEditor.js | 30 +++++----- .../containers/ActiveTripPatternList.js | 49 ++++++--------- .../ActiveGtfsPlusVersionSummary.js | 12 +--- .../containers/ActiveDeploymentViewer.js | 20 ++----- .../containers/ActiveFeedSourceViewer.js | 60 ++++++------------- lib/manager/containers/ActiveProjectViewer.js | 46 +++++++------- lib/manager/containers/ActiveUserHomePage.js | 7 +-- .../ActivePublicFeedSourceViewer.js | 8 +-- .../containers/ActivePublicFeedsViewer.js | 2 +- lib/public/containers/ActivePublicHeader.js | 5 +- lib/public/containers/ActiveSignupPage.js | 10 +--- lib/public/containers/ActiveUserAccount.js | 15 +++-- 22 files changed, 130 insertions(+), 228 deletions(-) diff --git a/lib/common/containers/ActiveSidebar.js b/lib/common/containers/ActiveSidebar.js index cdff536cc..c7c2a42fd 100644 --- a/lib/common/containers/ActiveSidebar.js +++ b/lib/common/containers/ActiveSidebar.js @@ -2,7 +2,6 @@ import { connect } from 'react-redux' import Sidebar from '../components/Sidebar' import { login, logout, resetPassword, revokeToken } from '../../manager/actions/user' -import { setActiveProject } from '../../manager/actions/projects' import { setActiveLanguage } from '../../manager/actions/languages' import { setJobMonitorVisible, removeRetiredJob, startJobMonitor } from '../../manager/actions/status' import { setSidebarExpanded, setTutorialHidden } from '../../manager/actions/ui' @@ -28,7 +27,6 @@ const mapDispatchToProps = { resetPassword, revokeToken, setActiveLanguage, - setActiveProject, startJobMonitor, setJobMonitorVisible, setSidebarExpanded, diff --git a/lib/common/containers/ActiveSidebarNavItem.js b/lib/common/containers/ActiveSidebarNavItem.js index c2dcf72cf..a15f01b74 100644 --- a/lib/common/containers/ActiveSidebarNavItem.js +++ b/lib/common/containers/ActiveSidebarNavItem.js @@ -8,9 +8,7 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return {} -} +const mapDispatchToProps = {} var ActiveSidebarNavItem = connect( mapStateToProps, diff --git a/lib/common/containers/App.js b/lib/common/containers/App.js index d046965dc..b643d7964 100644 --- a/lib/common/containers/App.js +++ b/lib/common/containers/App.js @@ -37,7 +37,7 @@ class App extends Component { .then((action) => { if (this.props.user.profile === null) { // replace(null, '/') - this.props.login({closable: false}, callback) + this.props.login(null, null, {closable: false}, callback) .then(() => { callback() }) @@ -121,12 +121,10 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - checkJobStatus: () => dispatch(checkJobStatus()), - checkExistingLogin: (props) => dispatch(checkExistingLogin(props)), - login: (options) => dispatch(login(null, null, options)) - } +const mapDispatchToProps = { + checkJobStatus, + checkExistingLogin, + login } export default connect( diff --git a/lib/common/containers/CurrentStatusModal.js b/lib/common/containers/CurrentStatusModal.js index 36b3fcc5d..95e7b217b 100644 --- a/lib/common/containers/CurrentStatusModal.js +++ b/lib/common/containers/CurrentStatusModal.js @@ -9,10 +9,8 @@ const mapStateToProps = (state, ownProps) => { body: state.status.modal ? state.status.modal.body : null } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - clearStatusModal: () => { dispatch(clearStatusModal()) } - } +const mapDispatchToProps = { + clearStatusModal } var CurrentStatusModal = connect( mapStateToProps, diff --git a/lib/common/containers/PageContent.js b/lib/common/containers/PageContent.js index 190a64fd5..f5edd81b2 100644 --- a/lib/common/containers/PageContent.js +++ b/lib/common/containers/PageContent.js @@ -1,16 +1,6 @@ import React, {Component, PropTypes} from 'react' import { connect } from 'react-redux' -const mapStateToProps = (state, ownProps) => { - return { - sidebarExpanded: state.ui.sidebarExpanded - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return {} -} - class Content extends Component { static propTypes = { children: PropTypes.node, @@ -34,6 +24,14 @@ class Content extends Component { } } +const mapStateToProps = (state, ownProps) => { + return { + sidebarExpanded: state.ui.sidebarExpanded + } +} + +const mapDispatchToProps = {} + var PageContent = connect( mapStateToProps, mapDispatchToProps diff --git a/lib/common/containers/ProtectedButton.js b/lib/common/containers/ProtectedButton.js index fa225dbbf..8ba460885 100644 --- a/lib/common/containers/ProtectedButton.js +++ b/lib/common/containers/ProtectedButton.js @@ -18,15 +18,12 @@ class ProtectedButton extends Component { } const mapStateToProps = (state, ownProps) => { - console.log(ownProps) return { user: state.user } } -const mapDispatchToProps = (dispatch, ownProps) => { - return {} -} +const mapDispatchToProps = {} export default connect( mapStateToProps, diff --git a/lib/editor/containers/ActiveEditorFeedSourcePanel.js b/lib/editor/containers/ActiveEditorFeedSourcePanel.js index b20bfdb82..a3ba375f7 100644 --- a/lib/editor/containers/ActiveEditorFeedSourcePanel.js +++ b/lib/editor/containers/ActiveEditorFeedSourcePanel.js @@ -5,25 +5,17 @@ import { createFeedVersionFromSnapshot } from '../../manager/actions/versions' import EditorFeedSourcePanel from '../components/EditorFeedSourcePanel' const mapStateToProps = (state, ownProps) => { - return { - } + return {} } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - getSnapshots: (feedSource) => { dispatch(fetchSnapshots(feedSource)) }, - createSnapshot: (feedSource, name, comments) => { - dispatch(createSnapshot(feedSource, name, comments)) - .then(() => { - dispatch(fetchSnapshots(feedSource)) - }) - }, - restoreSnapshot: (feedSource, snapshot) => { dispatch(restoreSnapshot(feedSource, snapshot)) }, - deleteSnapshot: (feedSource, snapshot) => { dispatch(deleteSnapshot(feedSource, snapshot)) }, - downloadSnapshot: (feedSource, snapshot) => { dispatch(downloadSnapshotViaToken(feedSource, snapshot)) }, - exportSnapshotAsVersion: (feedSource, snapshot) => { dispatch(createFeedVersionFromSnapshot(feedSource, snapshot.id)) }, - loadFeedVersionForEditing: (feedVersion) => { dispatch(loadFeedVersionForEditing(feedVersion)) } - } +const mapDispatchToProps = { + getSnapshots: fetchSnapshots, + createSnapshot, + restoreSnapshot, + deleteSnapshot, + downloadSnapshot: downloadSnapshotViaToken, + exportSnapshotAsVersion: createFeedVersionFromSnapshot, + loadFeedVersionForEditing } const ActiveEditorFeedSourcePanel = connect( diff --git a/lib/editor/containers/ActiveEntityList.js b/lib/editor/containers/ActiveEntityList.js index 1214d52e7..a82ede383 100644 --- a/lib/editor/containers/ActiveEntityList.js +++ b/lib/editor/containers/ActiveEntityList.js @@ -25,10 +25,8 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - enterTimetableEditor: () => dispatch(enterTimetableEditor()) - } +const mapDispatchToProps = { + enterTimetableEditor } const ActiveEntityList = connect(mapStateToProps, mapDispatchToProps)(EntityList) diff --git a/lib/editor/containers/ActiveFeedInfoPanel.js b/lib/editor/containers/ActiveFeedInfoPanel.js index abbe88dfe..98680dba6 100644 --- a/lib/editor/containers/ActiveFeedInfoPanel.js +++ b/lib/editor/containers/ActiveFeedInfoPanel.js @@ -6,13 +6,11 @@ import FeedInfoPanel from '../components/FeedInfoPanel' const mapStateToProps = (state, ownProps) => { return { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - createSnapshot: (feedSource, name, comment) => { dispatch(createSnapshot(feedSource, name, comment)) }, - getSnapshots: (feedSource) => { dispatch(fetchSnapshots(feedSource)) }, - restoreSnapshot: (feedSource, snapshot) => { dispatch(restoreSnapshot(feedSource, snapshot)) }, - displayRoutesShapefile: (feedSource, file) => { dispatch(displayRoutesShapefile(feedSource, file)) } - } +const mapDispatchToProps = { + createSnapshot, + getSnapshots: fetchSnapshots, + restoreSnapshot, + displayRoutesShapefile } const ActiveFeedInfoPanel = connect(mapStateToProps, mapDispatchToProps)(FeedInfoPanel) diff --git a/lib/editor/containers/ActiveGtfsEditor.js b/lib/editor/containers/ActiveGtfsEditor.js index 3b6b2321e..2d904e045 100644 --- a/lib/editor/containers/ActiveGtfsEditor.js +++ b/lib/editor/containers/ActiveGtfsEditor.js @@ -186,9 +186,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { updateMapSetting: (props) => { dispatch(updateMapSetting(props)) }, - gtfsEntitySelected: (type, entity) => { - dispatch(receiveGtfsEntities([entity])) - }, setActiveEntity: (feedSourceId, component, entity, subComponent, subEntity, subSubComponent, subSubEntity) => { const entityId = entity && entity.id const subEntityId = subEntity && subEntity.id diff --git a/lib/editor/containers/ActiveTimetableEditor.js b/lib/editor/containers/ActiveTimetableEditor.js index 739f98e40..a6408d7cc 100644 --- a/lib/editor/containers/ActiveTimetableEditor.js +++ b/lib/editor/containers/ActiveTimetableEditor.js @@ -15,7 +15,9 @@ import TimetableEditor from '../components/timetable/TimetableEditor' const mapStateToProps = (state, ownProps) => { const activePattern = state.editor.data.active.subEntity - const activeSchedule = state.editor.data.tables.calendar ? state.editor.data.tables.calendar.find(c => c.id === ownProps.activeScheduleId) : null + const activeSchedule = state.editor.data.tables.calendar + ? state.editor.data.tables.calendar.find(c => c.id === ownProps.activeScheduleId) + : null return { activePattern, activeSchedule, @@ -23,21 +25,19 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - // NOTE: fetchTripsForCalendar is handled in ActiveGtfsEditor where it is used to fetch trips - saveTripsForCalendar: (feedSourceId, pattern, calendarId, trips) => dispatch(saveTripsForCalendar(feedSourceId, pattern, calendarId, trips)), - deleteTripsForCalendar: (feedSourceId, pattern, calendarId, trips) => dispatch(deleteTripsForCalendar(feedSourceId, pattern, calendarId, trips)), +const mapDispatchToProps = { + // NOTE: fetchTripsForCalendar is handled in ActiveGtfsEditor where it is used to fetch trips + saveTripsForCalendar, + deleteTripsForCalendar, - // TIMETABLE FUNCTIONS - updateCellValue: (value, rowIndex, key) => dispatch(updateCellValue(value, rowIndex, key)), - addNewTrip: (trip) => dispatch(addNewTrip(trip)), - removeTrips: (indexes) => dispatch(removeTrips(indexes)), - toggleAllRows: (select) => dispatch(toggleAllRows(select)), - toggleRowSelection: (rowIndex) => dispatch(toggleRowSelection(rowIndex)), - toggleDepartureTimes: () => dispatch(toggleDepartureTimes()), - setOffset: (seconds) => dispatch(setOffset(seconds)) - } + // TIMETABLE FUNCTIONS + updateCellValue, + addNewTrip, + removeTrips, + toggleAllRows, + toggleRowSelection, + toggleDepartureTimes, + setOffset } const ActiveTimetableEditor = connect(mapStateToProps, mapDispatchToProps)(TimetableEditor) diff --git a/lib/editor/containers/ActiveTripPatternList.js b/lib/editor/containers/ActiveTripPatternList.js index 2ab7d224a..536af3435 100644 --- a/lib/editor/containers/ActiveTripPatternList.js +++ b/lib/editor/containers/ActiveTripPatternList.js @@ -1,24 +1,21 @@ -import { connect } from 'react-redux' +import {connect} from 'react-redux' import { updateEditSetting, setActiveGtfsEntity, deleteGtfsEntity, saveActiveGtfsEntity, updateActiveGtfsEntity, - resetActiveGtfsEntity } from '../actions/active' + resetActiveGtfsEntity +} from '../actions/active' import { newGtfsEntity, updateMapSetting, - cloneGtfsEntity } from '../actions/editor' -import { - // removeStopFromPattern, - // addStopAtPoint, - // addStopAtIntersection, - // addStopAtInterval, - addStopToPattern } from '../actions/map/stopStrategies' -import { setErrorMessage } from '../../manager/actions/status' -import { undoActiveTripPatternEdits } from '../actions/tripPattern' -import { findProjectByFeedSource } from '../../manager/util' + cloneGtfsEntity +} from '../actions/editor' +import {addStopToPattern} from '../actions/map/stopStrategies' +import {setErrorMessage} from '../../manager/actions/status' +import {undoActiveTripPatternEdits} from '../actions/tripPattern' +import {findProjectByFeedSource} from '../../manager/util' import TripPatternList from '../components/pattern/TripPatternList' @@ -59,28 +56,16 @@ const mapDispatchToProps = (dispatch, ownProps) => { const subSubEntityId = subSubEntity && subSubEntity.id dispatch(setActiveGtfsEntity(feedSourceId, component, entityId, subComponent, subEntityId, subSubComponent, subSubEntityId)) }, - updateActiveEntity: (entity, component, props) => { - dispatch(updateActiveGtfsEntity(entity, component, props)) - }, - resetActiveEntity: (entity, component) => { - dispatch(resetActiveGtfsEntity(entity, component)) - }, - deleteEntity: (feedSourceId, component, entityId, routeId) => { - dispatch(deleteGtfsEntity(feedSourceId, component, entityId, routeId)) - }, - saveActiveEntity: (component) => { - return dispatch(saveActiveGtfsEntity(component)) - }, - cloneEntity: (feedSourceId, component, entityId, save) => { - dispatch(cloneGtfsEntity(feedSourceId, component, entityId, save)) - }, - newGtfsEntity: (feedSourceId, component, props, save) => { - return dispatch(newGtfsEntity(feedSourceId, component, props, save)) - }, + updateActiveEntity: (entity, component, props) => dispatch(updateActiveGtfsEntity(entity, component, props)), + resetActiveEntity: (entity, component) => dispatch(resetActiveGtfsEntity(entity, component)), + deleteEntity: (feedSourceId, component, entityId, routeId) => dispatch(deleteGtfsEntity(feedSourceId, component, entityId, routeId)), + saveActiveEntity: (component) => dispatch(saveActiveGtfsEntity(component)), + cloneEntity: (feedSourceId, component, entityId, save) => dispatch(cloneGtfsEntity(feedSourceId, component, entityId, save)), + newGtfsEntity: (feedSourceId, component, props, save) => dispatch(newGtfsEntity(feedSourceId, component, props, save)), addStopToPattern: (pattern, stop, index) => dispatch(addStopToPattern(pattern, stop, index)), - undoActiveTripPatternEdits: () => { dispatch(undoActiveTripPatternEdits()) }, - setErrorMessage: (message) => { dispatch(setErrorMessage(message)) } + undoActiveTripPatternEdits: () => dispatch(undoActiveTripPatternEdits()), + setErrorMessage: (message) => dispatch(setErrorMessage(message)) } } diff --git a/lib/gtfsplus/containers/ActiveGtfsPlusVersionSummary.js b/lib/gtfsplus/containers/ActiveGtfsPlusVersionSummary.js index 24a1eefbe..a2842dee0 100644 --- a/lib/gtfsplus/containers/ActiveGtfsPlusVersionSummary.js +++ b/lib/gtfsplus/containers/ActiveGtfsPlusVersionSummary.js @@ -14,15 +14,9 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - gtfsPlusDataRequested: (version) => { - dispatch(downloadGtfsPlusFeed(version.id)) - }, - publishClicked: (version) => { - dispatch(publishGtfsPlusFeed(version)) - } - } +const mapDispatchToProps = { + gtfsPlusDataRequested: downloadGtfsPlusFeed, + publishClicked: publishGtfsPlusFeed } const ActiveGtfsPlusVersionSummary = connect( diff --git a/lib/manager/containers/ActiveDeploymentViewer.js b/lib/manager/containers/ActiveDeploymentViewer.js index b23038c13..3ff03ba48 100644 --- a/lib/manager/containers/ActiveDeploymentViewer.js +++ b/lib/manager/containers/ActiveDeploymentViewer.js @@ -32,33 +32,25 @@ const mapDispatchToProps = (dispatch, ownProps) => { }) } }, - deployToTargetClicked: (deployment, target) => { dispatch(deployToTarget(deployment, target)) }, - downloadDeployment: (deployment) => { dispatch(downloadDeployment(deployment)) }, - // updateProjectSettings: (project, newSettings) => { dispatch(updateProject(project, newSettings)) }, // dispatch(updateProject(project, { [propName] : newValue })) - // thirdPartySync: (type) => { dispatch(thirdPartySync(projectId, type)) }, - // updateAllFeeds: (project) => { dispatch(fetchFeedsForProject(project)) }, - // feedSourcePropertyChanged: (feedSource, propName, newValue) => { - // dispatch(updateFeedSource(feedSource, { [propName] : newValue })) - // }, - // deploymentsRequested: () => { dispatch(fetchProjectDeployments(projectId)) }, - // searchTextChanged: (text) => { dispatch(setVisibilitySearchText(text))}, - getDeploymentStatus: (deployment, target) => { dispatch(fetchDeploymentStatus(deployment, target)) }, + deployToTargetClicked: (deployment, target) => dispatch(deployToTarget(deployment, target)), + downloadDeployment: (deployment) => dispatch(downloadDeployment(deployment)), + getDeploymentStatus: (deployment, target) => dispatch(fetchDeploymentStatus(deployment, target)), updateVersionForFeedSource: (deployment, feedSource, feedVersion) => { const feedVersions = [...deployment.feedVersions] const index = feedVersions.findIndex(v => v.feedSource.id === feedSource.id) feedVersions.splice(index, 1) feedVersions.push(feedVersion) - dispatch(updateDeployment(deployment, {feedVersions})) + return dispatch(updateDeployment(deployment, {feedVersions})) }, addFeedVersion: (deployment, feedVersion) => { const feedVersions = [...deployment.feedVersions, feedVersion] - dispatch(updateDeployment(deployment, {feedVersions})) + return dispatch(updateDeployment(deployment, {feedVersions})) }, deleteFeedVersion: (deployment, feedVersion) => { const feedVersions = deployment.feedVersions const index = feedVersions.findIndex(v => v.id === feedVersion.id) feedVersions.splice(index, 1) - dispatch(updateDeployment(deployment, {feedVersions})) + return dispatch(updateDeployment(deployment, {feedVersions})) } } } diff --git a/lib/manager/containers/ActiveFeedSourceViewer.js b/lib/manager/containers/ActiveFeedSourceViewer.js index 8f1c66527..7326c2629 100644 --- a/lib/manager/containers/ActiveFeedSourceViewer.js +++ b/lib/manager/containers/ActiveFeedSourceViewer.js @@ -63,40 +63,18 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = (dispatch, ownProps) => { const feedSourceId = ownProps.routeParams.feedSourceId return { - createDeployment: (feedSource) => { - dispatch(createDeploymentFromFeedSource(feedSource)) - }, - loadFeedVersionForEditing: (feedVersion) => { - dispatch(loadFeedVersionForEditing(feedVersion)) - }, - deleteFeedVersion: (feedSource, feedVersion) => { - dispatch(deleteFeedVersion(feedSource, feedVersion)) - }, + createDeployment: (feedSource) => dispatch(createDeploymentFromFeedSource(feedSource)), + loadFeedVersionForEditing: (feedVersion) => dispatch(loadFeedVersionForEditing(feedVersion)), + deleteFeedVersion: (feedSource, feedVersion) => dispatch(deleteFeedVersion(feedSource, feedVersion)), downloadFeedClicked: (feedVersion) => { dispatch(downloadFeedViaToken(feedVersion)) }, - externalPropertyChanged: (feedSource, resourceType, propName, newValue) => { - dispatch(updateExternalFeedResource(feedSource, resourceType, { [propName]: newValue })) - }, - feedSourcePropertyChanged: (feedSource, propName, newValue) => { - return dispatch(updateFeedSource(feedSource, { [propName]: newValue })) - }, - feedVersionRenamed: (feedVersion, name) => { - dispatch(renameFeedVersion(feedVersion, name)) - }, - gtfsPlusDataRequested: (feedVersion) => { - dispatch(downloadGtfsPlusFeed(feedVersion.id)) - }, - newNotePostedForFeedSource: (feedSource, note) => { - dispatch(postNoteForFeedSource(feedSource, note)) - }, - newNotePostedForVersion: (version, note) => { - dispatch(postNoteForFeedVersion(version, note)) - }, - notesRequestedForFeedSource: (feedSource) => { - dispatch(fetchNotesForFeedSource(feedSource)) - }, - notesRequestedForVersion: (feedVersion) => { - dispatch(fetchNotesForFeedVersion(feedVersion)) - }, + externalPropertyChanged: (feedSource, resourceType, propName, newValue) => dispatch(updateExternalFeedResource(feedSource, resourceType, { [propName]: newValue })), + feedSourcePropertyChanged: (feedSource, propName, newValue) => dispatch(updateFeedSource(feedSource, { [propName]: newValue })), + feedVersionRenamed: (feedVersion, name) => dispatch(renameFeedVersion(feedVersion, name)), + gtfsPlusDataRequested: (feedVersionId) => dispatch(downloadGtfsPlusFeed(feedVersionId)), + newNotePostedForFeedSource: (feedSource, note) => dispatch(postNoteForFeedSource(feedSource, note)), + newNotePostedForVersion: (version, note) => dispatch(postNoteForFeedVersion(version, note)), + notesRequestedForFeedSource: (feedSource) => dispatch(fetchNotesForFeedSource(feedSource)), + notesRequestedForVersion: (feedVersion) => dispatch(fetchNotesForFeedVersion(feedVersion)), onComponentMount: (initialProps) => { let unsecured = true if (initialProps.user.profile !== null) { @@ -133,16 +111,12 @@ const mapDispatchToProps = (dispatch, ownProps) => { }) } }, - fetchFeed: (feedSource) => { dispatch(runFetchFeed(feedSource)) }, - deleteFeedSource: (feedSource) => { return dispatch(deleteFeedSource(feedSource)) }, - updateUserSubscription: (profile, target, subscriptionType) => { dispatch(updateTargetForSubscription(profile, target, subscriptionType)) }, - uploadFeed: (feedSource, file) => { dispatch(uploadFeed(feedSource, file)) }, - fetchValidationResult: (feedSource, feedVersion) => { - dispatch(fetchValidationResult(feedVersion)) - }, - createFeedInfo: (feedSourceId) => { - dispatch(createFeedInfo(feedSourceId)) - } + fetchFeed: (feedSource) => dispatch(runFetchFeed(feedSource)), + deleteFeedSource: (feedSource) => dispatch(deleteFeedSource(feedSource)), + updateUserSubscription: (profile, target, subscriptionType) => dispatch(updateTargetForSubscription(profile, target, subscriptionType)), + uploadFeed: (feedSource, file) => dispatch(uploadFeed(feedSource, file)), + fetchValidationResult: (feedSource, feedVersion) => dispatch(fetchValidationResult(feedVersion)), + createFeedInfo: (feedSourceId) => dispatch(createFeedInfo(feedSourceId)) } } diff --git a/lib/manager/containers/ActiveProjectViewer.js b/lib/manager/containers/ActiveProjectViewer.js index 7ca768c66..091d2f6bd 100644 --- a/lib/manager/containers/ActiveProjectViewer.js +++ b/lib/manager/containers/ActiveProjectViewer.js @@ -62,35 +62,29 @@ const mapDispatchToProps = (dispatch, ownProps) => { dispatch(fetchProjectFeeds(projectId)) } }, - onNewFeedSourceClick: () => { dispatch(createFeedSource(projectId)) }, - updateProjectSettings: (project, newSettings) => { - return dispatch(updateProject(project, newSettings, true)) - }, - thirdPartySync: (type) => { dispatch(thirdPartySync(projectId, type)) }, - updateAllFeeds: (project) => { dispatch(fetchFeedsForProject(project)) }, - saveFeedSource: (name) => { dispatch(saveFeedSource({ projectId, name })) }, - updateFeedSourceProperty: (feedSource, propName, newValue) => { - dispatch(updateFeedSource(feedSource, { [propName]: newValue })) - }, - deploymentsRequested: () => { dispatch(fetchProjectDeployments(projectId)) }, - deployPublic: (project) => { return dispatch(deployPublic(project)) }, - createDeploymentFromFeedSource: (feedSource) => { - dispatch(createDeploymentFromFeedSource(feedSource)) - }, - onNewDeploymentClick: () => { dispatch(createDeployment(projectId)) }, - newDeploymentNamed: (name) => { dispatch(saveDeployment({ projectId, name })) }, - updateDeployment: (deployment, changes) => { dispatch(updateDeployment(deployment, changes)) }, - searchTextChanged: (text) => { dispatch(setVisibilitySearchText(text)) }, + onNewFeedSourceClick: () => dispatch(createFeedSource(projectId)), + updateProjectSettings: (project, newSettings) => dispatch(updateProject(project, newSettings, true)), + thirdPartySync: (type) => dispatch(thirdPartySync(projectId, type)), + updateAllFeeds: (project) => dispatch(fetchFeedsForProject(project)), + saveFeedSource: (name) => dispatch(saveFeedSource({ projectId, name })), + updateFeedSourceProperty: (feedSource, propName, newValue) => dispatch(updateFeedSource(feedSource, { [propName]: newValue })), + deploymentsRequested: () => dispatch(fetchProjectDeployments(projectId)), + deployPublic: (project) => dispatch(deployPublic(project)), + createDeploymentFromFeedSource: (feedSource) => dispatch(createDeploymentFromFeedSource(feedSource)), + onNewDeploymentClick: () => dispatch(createDeployment(projectId)), + newDeploymentNamed: (name) => dispatch(saveDeployment({ projectId, name })), + updateDeployment: (deployment, changes) => dispatch(updateDeployment(deployment, changes)), + searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)), visibilityFilterChanged: (filter) => dispatch(setVisibilityFilter(filter)), - uploadFeed: (feedSource, file) => { dispatch(uploadFeed(feedSource, file)) }, - fetchFeed: (feedSource) => { dispatch(runFetchFeed(feedSource)) }, - deleteFeedSource: (feedSource) => { dispatch(deleteFeedSource(feedSource)) }, + uploadFeed: (feedSource, file) => dispatch(uploadFeed(feedSource, file)), + fetchFeed: (feedSource) => dispatch(runFetchFeed(feedSource)), + deleteFeedSource: (feedSource) => dispatch(deleteFeedSource(feedSource)), deleteProject: (project) => { - dispatch(deleteProject(project)) - .then(() => browserHistory.push('/home')) + return dispatch(deleteProject(project)) + .then(() => browserHistory.push('/home')) }, - deleteDeploymentConfirmed: (deployment) => { dispatch(deleteDeployment(deployment)) }, - downloadMergedFeed: (project) => { dispatch(downloadFeedForProject(project)) } + deleteDeploymentConfirmed: (deployment) => dispatch(deleteDeployment(deployment)), + downloadMergedFeed: (project) => dispatch(downloadFeedForProject(project)) } } diff --git a/lib/manager/containers/ActiveUserHomePage.js b/lib/manager/containers/ActiveUserHomePage.js index 0cd76a7f2..0f0986115 100644 --- a/lib/manager/containers/ActiveUserHomePage.js +++ b/lib/manager/containers/ActiveUserHomePage.js @@ -28,7 +28,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { return { onComponentMount: (initialProps) => { dispatch(getRecentActivity(initialProps.user)) - // dispatch(fetchOrganizationForUser(initialProps.user)) dispatch(fetchProjects()) .then(projects => { if (!activeProjectId) { @@ -45,9 +44,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { } }) }, - fetchProjectFeeds: (projectId) => { dispatch(fetchProjectFeeds(projectId)) }, - logoutHandler: () => { dispatch(logout()) }, - searchTextChanged: (text) => { dispatch(setVisibilitySearchText(text)) }, + fetchProjectFeeds: (projectId) => dispatch(fetchProjectFeeds(projectId)), + logoutHandler: () => dispatch(logout()), + searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)), visibilityFilterChanged: (filter) => dispatch(setVisibilityFilter(filter)) } } diff --git a/lib/public/containers/ActivePublicFeedSourceViewer.js b/lib/public/containers/ActivePublicFeedSourceViewer.js index 7977c4033..cc66296db 100644 --- a/lib/public/containers/ActivePublicFeedSourceViewer.js +++ b/lib/public/containers/ActivePublicFeedSourceViewer.js @@ -49,11 +49,9 @@ const mapDispatchToProps = (dispatch, ownProps) => { dispatch(fetchFeedVersions(initialProps.feedSource, unsecured)) } }, - feedSourcePropertyChanged: (feedSource, propName, newValue) => { - dispatch(updateFeedSource(feedSource, { [propName]: newValue })) - }, - updateFeedClicked: (feedSource) => { dispatch(runFetchFeed(feedSource)) }, - uploadFeedClicked: (feedSource, file) => { dispatch(uploadFeed(feedSource, file)) } + feedSourcePropertyChanged: (feedSource, propName, newValue) => dispatch(updateFeedSource(feedSource, { [propName]: newValue })), + updateFeedClicked: (feedSource) => dispatch(runFetchFeed(feedSource)), + uploadFeedClicked: (feedSource, file) => dispatch(uploadFeed(feedSource, file)) } } diff --git a/lib/public/containers/ActivePublicFeedsViewer.js b/lib/public/containers/ActivePublicFeedsViewer.js index e57626872..7704b697a 100644 --- a/lib/public/containers/ActivePublicFeedsViewer.js +++ b/lib/public/containers/ActivePublicFeedsViewer.js @@ -17,7 +17,7 @@ const mapDispatchToProps = (dispatch, ownProps) => { onComponentMount: (initialProps) => { dispatch(fetchProjectsWithPublicFeeds()) }, - searchTextChanged: (text) => { dispatch(setVisibilitySearchText(text)) } + searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)) } } diff --git a/lib/public/containers/ActivePublicHeader.js b/lib/public/containers/ActivePublicHeader.js index a4c33d776..083022c95 100644 --- a/lib/public/containers/ActivePublicHeader.js +++ b/lib/public/containers/ActivePublicHeader.js @@ -24,7 +24,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { loginHandler: () => { dispatch(login()) .then((success) => { - console.log(success) if (success) { browserHistory.push('/home') } @@ -32,8 +31,8 @@ const mapDispatchToProps = (dispatch, ownProps) => { console.log(failure) }) }, - logoutHandler: () => { dispatch(logout()) }, - resetPassword: () => { dispatch(resetPassword()) } + logoutHandler: () => dispatch(logout()), + resetPassword: () => dispatch(resetPassword()) } } diff --git a/lib/public/containers/ActiveSignupPage.js b/lib/public/containers/ActiveSignupPage.js index 091891de5..9d77d77f7 100644 --- a/lib/public/containers/ActiveSignupPage.js +++ b/lib/public/containers/ActiveSignupPage.js @@ -14,13 +14,10 @@ const mapStateToProps = (state, ownProps) => { } const mapDispatchToProps = (dispatch, ownProps) => { - // const projectId = ownProps.routeParams.projectId return { - onComponentMount: (initialProps) => { - dispatch(fetchProjectsWithPublicFeeds()) - }, - loginHandler: () => { dispatch(login()) }, - signupHandler: (credentials) => { + onComponentMount: (initialProps) => dispatch(fetchProjectsWithPublicFeeds()), + loginHandler: () => dispatch(login()), + signupHandler: (credentials) => dispatch(createPublicUser(credentials)) .then((user) => { dispatch(login(credentials, user)) @@ -28,7 +25,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { browserHistory.push('/home') }) }) - } } } diff --git a/lib/public/containers/ActiveUserAccount.js b/lib/public/containers/ActiveUserAccount.js index 1cb522a36..32c591131 100644 --- a/lib/public/containers/ActiveUserAccount.js +++ b/lib/public/containers/ActiveUserAccount.js @@ -25,7 +25,6 @@ const mapStateToProps = (state, ownProps) => { } const mapDispatchToProps = (dispatch, ownProps) => { - // const projectId = ownProps.routeParams.projectId return { onComponentMount: (initialProps) => { if (!ownProps.routeParams.subpage) { @@ -66,13 +65,13 @@ const mapDispatchToProps = (dispatch, ownProps) => { } } }, - searchTextChanged: (text) => { dispatch(setVisibilitySearchText(text)) }, - updateUserName: (user, permissions) => { dispatch(updateUserData(user, permissions)) }, - resetPassword: () => { dispatch(resetPassword()) }, - updateUserSubscription: (profile, target, subscriptionType) => { dispatch(updateTargetForSubscription(profile, target, subscriptionType)) }, - unsubscribeAll: (profile, target, subscriptionType) => { dispatch(unsubscribeAll(profile)) }, - removeUserSubscription: (profile, subscriptionType) => { dispatch(removeUserSubscription(profile, subscriptionType)) }, - fetchUser: (user, permissions) => { dispatch(fetchUser(user)) } + searchTextChanged: (text) => dispatch(setVisibilitySearchText(text)), + updateUserName: (user, permissions) => dispatch(updateUserData(user, permissions)), + resetPassword: () => dispatch(resetPassword()), + updateUserSubscription: (profile, target, subscriptionType) => dispatch(updateTargetForSubscription(profile, target, subscriptionType)), + unsubscribeAll: (profile, target, subscriptionType) => dispatch(unsubscribeAll(profile)), + removeUserSubscription: (profile, subscriptionType) => dispatch(removeUserSubscription(profile, subscriptionType)), + fetchUser: (user, permissions) => dispatch(fetchUser(user)) } } From a3e1eb34a3c67ffcd03530e8d4e84098aa80de5a Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 11:19:52 -0400 Subject: [PATCH 046/265] fix(gtfs.yml): clean up route helpContent --- gtfs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtfs.yml b/gtfs.yml index 7d0dafccd..f2d1e04a4 100644 --- a/gtfs.yml +++ b/gtfs.yml @@ -254,7 +254,7 @@ required: true inputType: TEXT columnWidth: 7 - helpContent: The route_short_name contains the short name of a route. This will often be a short, abstract identifier like + helpContent: The route_short_name contains the short name of a route. This will often be a short, abstract identifier like "32", "100X", or "Green" that riders use to identify a route, but which doesn't give any indication of what places the route serves. - name: agency_id required: false inputType: GTFS_AGENCY @@ -269,7 +269,7 @@ required: false inputType: TEXT columnWidth: 12 - helpContent: The route_desc field contains a description of a route. Please provide useful, quality information. Do not simply duplicate the name of the route. For example, + helpContent: 'Contains a description of a route. Please provide useful, quality information. Do not simply duplicate the name of the route. For example, "A trains operate between Inwood-207 St, Manhattan and Far Rockaway-Mott Avenue, Queens at all times. Also from about 6AM until about midnight, additional A trains operate between Inwood-207 St and Lefferts Boulevard (trains typically alternate between Lefferts Blvd and Far Rockaway)."' - name: route_type required: true inputType: DROPDOWN From a14c77dfdd312628e4a4e4241d54c2863ab03ca5 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 11:21:23 -0400 Subject: [PATCH 047/265] fix(config): add useS3Storage prop to config (for handling feed download action) --- configurations/default/settings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/configurations/default/settings.yml b/configurations/default/settings.yml index 5d62f0a25..1d4e07621 100644 --- a/configurations/default/settings.yml +++ b/configurations/default/settings.yml @@ -4,6 +4,7 @@ entries: application: data: gtfs_s3_bucket: bucket-name + use_s3_storage: false title: Data Manager logo: http://gtfs-assets-dev.conveyal.com/data_manager.png # defaults to src/main/client/assets/application_logo.png active_project: project-id From 7914213a2a5e5a33a6594650dabbf345e159cc51 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 11:23:04 -0400 Subject: [PATCH 048/265] refactor(EditorInput): factor out ZoneSelect and ColorField; refactor EditorInput --- lib/editor/components/ColorField.js | 79 ++++++ lib/editor/components/EditorInput.js | 360 +++++++-------------------- lib/editor/components/ZoneSelect.js | 53 ++++ 3 files changed, 225 insertions(+), 267 deletions(-) create mode 100644 lib/editor/components/ColorField.js create mode 100644 lib/editor/components/ZoneSelect.js diff --git a/lib/editor/components/ColorField.js b/lib/editor/components/ColorField.js new file mode 100644 index 000000000..d95bde9a4 --- /dev/null +++ b/lib/editor/components/ColorField.js @@ -0,0 +1,79 @@ +import React, {PropTypes, Component} from 'react' +import {FormGroup} from 'react-bootstrap' +import SketchPicker from 'react-color/lib/components/sketch/Sketch' + +import ClickOutside from '../../common/components/ClickOutside' + +export default class ColorField extends Component { + static propTypes = { + field: PropTypes.object, + formProps: PropTypes.object, + label: PropTypes.any, + onChange: PropTypes.func, + value: PropTypes.number + } + + state = { + color: {r: '241', g: '112', b: '19', a: '1'} // default color + } + + _handleClick = (e) => { + e.preventDefault() + this.setState({ open: !this.state.open }) + } + + _handleClose = () => { + this.setState({ open: !this.state.open }) + } + + render () { + const {formProps, label, value} = this.props + const hexColor = value !== null ? `#${value}` : '#000000' + const colorStyle = { + width: '36px', + height: '20px', + borderRadius: '2px', + background: hexColor + } + const styles = { + swatch: { + padding: '5px', + marginRight: '30px', + background: '#fff', + borderRadius: '4px', + display: 'inline-block', + cursor: 'pointer' + }, + popover: { + position: 'absolute', + zIndex: '200' + }, + cover: { + position: 'fixed', + top: '0', + right: '0', + bottom: '0', + left: '0' + } + } + return ( + + {label} + + {this.state.open + ? + + + : null + } + + ) + } +} diff --git a/lib/editor/components/EditorInput.js b/lib/editor/components/EditorInput.js index 548d7b1eb..fe8ca17cd 100644 --- a/lib/editor/components/EditorInput.js +++ b/lib/editor/components/EditorInput.js @@ -1,71 +1,73 @@ import React, {Component, PropTypes} from 'react' import { Checkbox, FormControl, FormGroup, ControlLabel, Tooltip, OverlayTrigger } from 'react-bootstrap' -import SketchPicker from 'react-color/lib/components/sketch/Sketch' import Select from 'react-select' import moment from 'moment' import DateTimeField from 'react-bootstrap-datetimepicker' +import Dropzone from 'react-dropzone' +import ColorField from './ColorField' import { getEntityName } from '../util/gtfs' import VirtualizedEntitySelect from './VirtualizedEntitySelect' -import GtfsSearch from '../../gtfs/components/gtfssearch' -import ClickOutside from '../../common/components/ClickOutside' import TimezoneSelect from '../../common/components/TimezoneSelect' import LanguageSelect from '../../common/components/LanguageSelect' import toSentenceCase from '../../common/util/to-sentence-case' -import Dropzone from 'react-dropzone' +import ZoneSelect from './ZoneSelect' export default class EditorInput extends Component { + static propTypes = { activeEntity: PropTypes.object, updateActiveEntity: PropTypes.func, activeComponent: PropTypes.string, uploadBrandingAsset: PropTypes.func, feedSource: PropTypes.object, - getGtfsEntity: PropTypes.func, - fieldEdited: PropTypes.bool, - gtfsEntitySelected: PropTypes.func, tableData: PropTypes.object, - row: PropTypes.number, field: PropTypes.object, approveGtfsDisabled: PropTypes.bool, zoneOptions: PropTypes.array, table: PropTypes.object, hasRoutes: PropTypes.bool } - constructor (props) { - super(props) - this.state = { - color: { - r: '241', - g: '112', - b: '19', - a: '1' - } - } + + _onColorChange = (color) => { + const {updateActiveEntity, activeEntity, activeComponent, field} = this.props + updateActiveEntity(activeEntity, activeComponent, {[field.name]: color.hex.split('#')[1]}) + } + + _onDateChange = (millis) => { + const {updateActiveEntity, activeEntity, activeComponent, field} = this.props + updateActiveEntity(activeEntity, activeComponent, {[field.name]: +millis}) + } + + _onDowChange = (evt) => { + const {updateActiveEntity, activeEntity, activeComponent, field} = this.props + updateActiveEntity(activeEntity, activeComponent, {[field.name]: evt.target.checked ? 1 : 0}) } - handleClick (field) { - this.setState({ [field]: !this.state[field] }) + + _onInputChange = (evt) => { + const {updateActiveEntity, activeEntity, activeComponent, field} = this.props + updateActiveEntity(activeEntity, activeComponent, {[field.name]: evt.target.value}) } - handleClose (field) { - this.setState({ [field]: !this.state[field] }) + + _onSelectChange = (option) => { + const {updateActiveEntity, activeEntity, activeComponent, field} = this.props + updateActiveEntity(activeEntity, activeComponent, {[field.name]: option ? option.value : null}) } + + _uploadBranding = (files) => { + const {uploadBrandingAsset, feedSource, activeEntity, activeComponent} = this.props + uploadBrandingAsset(feedSource.id, activeEntity.id, activeComponent, files[0]) + } + render () { const { activeEntity, - updateActiveEntity, activeComponent, - uploadBrandingAsset, - feedSource, - getGtfsEntity, - fieldEdited, - gtfsEntitySelected, tableData, - row, field, currentValue, approveGtfsDisabled, zoneOptions, - table, isNotValid } = this.props const editorField = field.displayName || field.name @@ -78,31 +80,6 @@ export default class EditorInput extends Component { } else if (field.required) { formProps.validationState = 'success' } - const simpleFormProps = { - controlId: `${editorField}`, - className: `col-xs-${field.columnWidth}` - } - const styles = { - swatch: { - padding: '5px', - marginRight: '30px', - background: '#fff', - borderRadius: '4px', - display: 'inline-block', - cursor: 'pointer' - }, - popover: { - position: 'absolute', - zIndex: '200' - }, - cover: { - position: 'fixed', - top: '0', - right: '0', - bottom: '0', - left: '0' - } - } const basicLabel = field.helpContent ? {field.helpContent}}> {editorField}{field.required ? ' *' : ''} @@ -116,11 +93,7 @@ export default class EditorInput extends Component { { - const props = {} - props[field.name] = evt.target.value - updateActiveEntity(activeEntity, activeComponent, props) - }} + onChange={this._onInputChange} /> ) @@ -136,11 +109,7 @@ export default class EditorInput extends Component { { - const props = {} - props[field.name] = evt.target.value - updateActiveEntity(activeEntity, activeComponent, props) - }} + onChange={this._onInputChange} /> ) @@ -151,11 +120,7 @@ export default class EditorInput extends Component { { - const props = {} - props[field.name] = evt.target.value - updateActiveEntity(activeEntity, activeComponent, props) - }} + onChange={this._onInputChange} /> ] @@ -168,13 +133,13 @@ export default class EditorInput extends Component { { - uploadBrandingAsset(feedSource.id, activeEntity.id, activeComponent, files[0]) - this.setState({file: files[0]}) - }}> + onDrop={this._uploadBranding}>
Drop {activeComponent} image here, or click to select image to upload.
{activeEntity && activeEntity[field.name] - ? agency branding + ? agency branding : null }
@@ -184,17 +149,12 @@ export default class EditorInput extends Component { return {elements} case 'EMAIL': return ( - + {basicLabel} { - const props = {} - props[field.name] = evt.target.value - updateActiveEntity(activeEntity, activeComponent, props) - }} + onChange={this._onInputChange} /> ) @@ -202,43 +162,11 @@ export default class EditorInput extends Component { return ( {basicLabel} - { - console.log(input) - const props = {} - const val = input ? input.value : null - props[field.name] = val - this.setState({[editorField]: val}) - updateActiveEntity(activeEntity, activeComponent, props) - }} - options={tableData.agency - ? tableData.agency.map(agency => { - return { - value: agency.id, - label: agency.agency_name, - agency - } - }) - : [] - } /> + onChange={this._onSelectChange} + options={agencies.map(agency => ({ + value: agency.id, + label: agency.agency_name, + agency + }))} /> ) case 'GTFS_STOP': @@ -505,24 +337,18 @@ export default class EditorInput extends Component { const stop = tableData.stop.find(s => s.id === currentValue) const stops = [...tableData.stop] // remove current entity from list of stops + // (because this is only used for parent_station selection and we can't make the self the parent) if (stopIndex !== -1) { stops.splice(stopIndex, 1) } return ( - + {basicLabel} { - console.log(input) - const props = {} - const val = input ? input.value : null - props[field.name] = val - this.setState({[editorField]: val}) - updateActiveEntity(activeEntity, activeComponent, props) - }} /> + onChange={this._onSelectChange} /> ) default: diff --git a/lib/editor/components/ZoneSelect.js b/lib/editor/components/ZoneSelect.js new file mode 100644 index 000000000..1fd9f3604 --- /dev/null +++ b/lib/editor/components/ZoneSelect.js @@ -0,0 +1,53 @@ +import React, {PropTypes, Component} from 'react' +import Select from 'react-select' + +export default class ZoneSelect extends Component { + static propTypes = { + value: PropTypes.any + } + + state = {} + + // _filterZoneOptions adds "Create new zone" custom option when using ZoneSelect to edit stop entities + _filterZoneOptions = (options, filter, values) => { + // Filter already selected values + const valueKeys = values && values.map(i => i.value) + let filteredOptions = options.filter(option => { + return valueKeys ? valueKeys.indexOf(option.value) === -1 : [] + }) + // Filter by label + if (filter !== undefined && filter != null && filter.length > 0) { + filteredOptions = filteredOptions.filter(option => { + return RegExp(filter, 'ig').test(option.label) + }) + } + // Append Addition option + if (filteredOptions.length === 0) { + filteredOptions.push({ + label: Create new zone: {filter}, + value: filter, + create: true + }) + } + return filteredOptions + } + + onChange (option) { + this.setState({value: option ? option.value : null}) + } + + render () { + const {addCreateOption, onChange, placeholder, value, zoneOptions} = this.props + return ( + Date: Fri, 14 Apr 2017 11:27:23 -0400 Subject: [PATCH 052/265] style(EditableTextField): add whitespace --- lib/common/components/EditableTextField.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/common/components/EditableTextField.js b/lib/common/components/EditableTextField.js index f77291f7f..f78739d72 100644 --- a/lib/common/components/EditableTextField.js +++ b/lib/common/components/EditableTextField.js @@ -56,6 +56,7 @@ export default class EditableTextField extends Component { cancel () { this.setState({isEditing: false}) } + handleKeyDown = (e) => { // if [Enter] or [Tab] is pressed if ((e.keyCode === 9 || e.keyCode === 13) && this.state.isEditing) { From ebdc6b7e89287ed6b07848168c07cf14fbde13bc Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 13:08:32 -0400 Subject: [PATCH 053/265] feat(editor validation): enhance validation report for editor entities --- lib/editor/components/EntityDetails.js | 29 +-- lib/editor/components/EntityDetailsHeader.js | 203 ++++++++++++------- lib/editor/util/validation.js | 122 +++++++---- 3 files changed, 221 insertions(+), 133 deletions(-) diff --git a/lib/editor/components/EntityDetails.js b/lib/editor/components/EntityDetails.js index 70abfd51a..14bf67237 100644 --- a/lib/editor/components/EntityDetails.js +++ b/lib/editor/components/EntityDetails.js @@ -27,8 +27,6 @@ export default class EntityDetails extends Component { deleteEntity: PropTypes.func.isRequired, newGtfsEntity: PropTypes.func.isRequired, uploadBrandingAsset: PropTypes.func, - fieldEdited: PropTypes.func, - gtfsEntitySelected: PropTypes.func, getGtfsEntity: PropTypes.func, showConfirmModal: PropTypes.func, updateMapSetting: PropTypes.func, @@ -40,10 +38,9 @@ export default class EntityDetails extends Component { entityEdited: PropTypes.bool, subComponent: PropTypes.string } - constructor (props) { - super(props) - this.state = {} - } + + state = {} + shouldComponentUpdate (nextProps, nextState) { return !shallowEqual(nextState, this.state) || // for editFareRules !shallowEqual(nextProps.feedSource, this.props.feedSource) || @@ -56,6 +53,9 @@ export default class EntityDetails extends Component { !shallowEqual(nextProps.activeEntityId, this.props.activeEntityId) || !shallowEqual(nextProps.width, this.props.width) } + + _toggleFareRules = (editFareRules) => this.setState({editFareRules}) + render () { const { activeComponent, @@ -87,7 +87,7 @@ export default class EntityDetails extends Component { if (!activeEntity) { return (
-
+

@@ -103,23 +103,24 @@ export default class EntityDetails extends Component { const currentTable = getEditorTable(activeComponent) const renderDefault = subComponent !== 'trippattern' && !this.state.editFareRules && activeComponent !== 'scheduleexception' const inputs = renderDefault && currentTable.fields.map((field, colIndex) => { - const isNotValid = validate(field.inputType, field.required, field.name, activeEntity[field.name], entities, activeEntity.id) + const isNotValid = validate(field, activeEntity[field.name], entities, activeEntity, tableData) isNotValid && validationErrors.push(isNotValid) return ( ) }) const detailsBody = ( -
+
{/* Render relevant form based on entity type */} {subComponent === 'trippattern' ? -
+
this.setState({editFareRules: bool})} + toggleEditFareRules={this._toggleFareRules} {...this.props} /> {detailsBody}
diff --git a/lib/editor/components/EntityDetailsHeader.js b/lib/editor/components/EntityDetailsHeader.js index 2e1a0468f..8f0fef413 100644 --- a/lib/editor/components/EntityDetailsHeader.js +++ b/lib/editor/components/EntityDetailsHeader.js @@ -1,60 +1,126 @@ import Icon from '@conveyal/woonerf/components/icon' import React, {Component, PropTypes} from 'react' import update from 'react-addons-update' -import { Button, ButtonToolbar, Tooltip, OverlayTrigger, Nav, NavItem } from 'react-bootstrap' +import {Badge, Button, ButtonToolbar, Tooltip, OverlayTrigger, Nav, NavItem} from 'react-bootstrap' import { getEntityBounds, getEntityName } from '../util/gtfs' import { gtfsIcons } from '../util/ui' export default class EntityDetailsHeader extends Component { static propTypes = { - activeComponent: PropTypes.string + activeComponent: PropTypes.string, + activeEntity: PropTypes.object, + activePattern: PropTypes.object, + editFareRules: PropTypes.bool, + entityEdited: PropTypes.bool, + feedSource: PropTypes.object, + mapState: PropTypes.object, + resetActiveEntity: PropTypes.func, + saveActiveEntity: PropTypes.func, + setActiveEntity: PropTypes.func, + subEntityId: PropTypes.string, + subComponent: PropTypes.string, + tableData: PropTypes.object, + toggleEditFareRules: PropTypes.func, + updateMapSetting: PropTypes.func, + validationErrors: PropTypes.array } + + _onClickSave = () => { + if (this.props.subComponent === 'trippattern') { + this.props.saveActiveEntity('trippattern') + } else { + this.props.saveActiveEntity(this.props.activeComponent) + } + } + + _onClickUndo = () => { + if (this.props.subComponent === 'trippattern') { + const pattern = this.props.activeEntity.tripPatterns.find(p => p.id === this.props.activePattern.id) + this.props.resetActiveEntity(pattern, 'trippattern') + } else { + this.props.resetActiveEntity(this.props.activeEntity, this.props.activeComponent) + } + const stateUpdate = {} + for (var key in this.state) { + stateUpdate[key] = {$set: null} + } + this.setState(update(this.state, stateUpdate)) + } + + _onClickZoom = (e) => { + const {activeEntity, subEntityId, updateMapSetting} = this.props + if (subEntityId) { + const pattern = activeEntity.tripPatterns.find(p => p.id === subEntityId) + updateMapSetting({bounds: getEntityBounds(pattern), target: subEntityId}) + } else { + updateMapSetting({bounds: getEntityBounds(activeEntity), target: activeEntity.id}) + } + } + + _showFareAttributes = () => this.props.toggleEditFareRules(false) + + _showFareRules = () => this.props.toggleEditFareRules(true) + + _showRoute = () => { + const {activeComponent, activeEntity, feedSource, setActiveEntity, subComponent} = this.props + if (subComponent === 'trippattern') { + setActiveEntity(feedSource.id, activeComponent, activeEntity) + } + } + + _showTripPatterns = () => { + const {activeComponent, activeEntity, feedSource, setActiveEntity, subComponent} = this.props + if (subComponent !== 'trippattern') { + setActiveEntity(feedSource.id, activeComponent, activeEntity, 'trippattern') + } + } + render () { const { activeComponent, - subComponent, + activeEntity, + editFareRules, + entityEdited, mapState, + subComponent, subEntityId, - updateMapSetting, - entityEdited, - resetActiveEntity, - saveActiveEntity, - activeEntity, - activePattern, - validationErrors, - setActiveEntity, - feedSource, tableData, - editFareRules, - toggleEditFareRules + validationErrors } = this.props + const validationTooltip = ( + + {validationErrors.map(v => ( +

{v.field}: {v.reason}

+ ))} +
+ ) const entityName = activeComponent === 'feedinfo' ? 'Feed Info' : getEntityName(activeComponent, activeEntity) const icon = gtfsIcons.find(i => i.id === activeComponent) + const zoomDisabled = activeEntity && !subComponent ? mapState.target === activeEntity.id : mapState.target === subEntityId const iconName = icon ? icon.icon : null const nameWidth = activeComponent === 'stop' || activeComponent === 'route' ? '130px' : '170px' + const entityNameStyle = { + width: nameWidth, + paddingTop: '8px', + display: 'inline-block', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden' + } return (
-
+
+ {/* Zoom, undo, and save buttons */} {activeComponent === 'stop' || activeComponent === 'route' ? Zoom to {activeComponent}}> @@ -64,20 +130,7 @@ export default class EntityDetailsHeader extends Component { @@ -86,18 +139,12 @@ export default class EntityDetailsHeader extends Component { bsSize='small' bsStyle='primary' disabled={!entityEdited || validationErrors.length > 0} - onClick={(e) => { - if (subComponent === 'trippattern') { - saveActiveEntity('trippattern') - } else { - saveActiveEntity(activeComponent) - } - }} - > + onClick={this._onClickSave}> + {/* Entity Icon */} {activeComponent === 'route' && activeEntity ? @@ -109,7 +156,7 @@ export default class EntityDetailsHeader extends Component { - // schedule exception icon if no icon founds + // schedule exception icon if no icon found : @@ -117,44 +164,54 @@ export default class EntityDetailsHeader extends Component { } {' '} + {/* Entity name */} - {entityName - // `${entityName && entityName.length > 18 ? entityName.substr(0, 18) + '...' : entityName}` - } + style={entityNameStyle}> + {entityName}
{!tableData[activeComponent] && activeComponent === 'feedinfo' ? Complete feed info to begin editing GTFS. : null } - {validationErrors.length > 0 - ? Fix validation issues before saving - : null - } + {/* Validation issues */} +

+ 0 ? ' text-danger' : ' text-success'}`}> + {validationErrors.length > 0 + ? + + {' '} + Fix + {' '} + + {validationErrors.length} validation issue(s) + + {' '} + before saving + + : No validation issues + } + +

+
{activeComponent === 'route' ? @@ -163,18 +220,14 @@ export default class EntityDetailsHeader extends Component { { - toggleEditFareRules(false) - }}> + onClick={this._showFareAttributes}> Attributes { - toggleEditFareRules(true) - }} + onClick={this._showFareRules} > Rules diff --git a/lib/editor/util/validation.js b/lib/editor/util/validation.js index 379d9aec4..4be91f712 100644 --- a/lib/editor/util/validation.js +++ b/lib/editor/util/validation.js @@ -1,10 +1,15 @@ import validator from 'validator' -export function validate (type, required, name, value, entities, id) { - let isNotValid - switch (type) { +function doesNotExist (value) { + return value === '' || value === null || typeof value === 'undefined' +} + +export function validate (field, value, entities, entity, tableData) { + const {inputType, required, name} = field + const isRequiredButEmpty = required && doesNotExist(value) + let reason = 'Required field must not be empty' + switch (inputType) { case 'GTFS_ID': - isNotValid = required && !value const indices = [] const idList = entities.map(e => e[name]) let idx = idList.indexOf(value) @@ -12,84 +17,103 @@ export function validate (type, required, name, value, entities, id) { indices.push(idx) idx = idList.indexOf(value, idx + 1) } - const isNotUnique = value && (indices.length > 1 || (indices.length && entities[indices[0]].id !== id)) - if (isNotValid || isNotUnique) { - return {field: name, invalid: isNotValid || isNotUnique} + const isNotUnique = value && (indices.length > 1 || (indices.length && entities[indices[0]].id !== entity.id)) + if (isRequiredButEmpty || isNotUnique) { + if (isNotUnique) { + reason = 'Identifier must be unique' + } + return {field: name, invalid: isRequiredButEmpty || isNotUnique, reason} } else { return false } case 'TEXT': + if (name === 'route_short_name' && !value && entity.route_long_name) { + return false + } else if (name === 'route_long_name' && !value && entity.route_short_name) { + return false + } else { + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} + } else { + return false + } + } case 'GTFS_TRIP': case 'GTFS_SHAPE': case 'GTFS_BLOCK': case 'GTFS_FARE': case 'GTFS_SERVICE': - isNotValid = required && !value - if (isNotValid) { - return {field: name, invalid: isNotValid} + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} } else { return false } case 'URL': - isNotValid = (required && !value) || (value && !validator.isURL(value)) - if (isNotValid) { - return {field: name, invalid: isNotValid} + const isNotUrl = value && !validator.isURL(value) + if (isRequiredButEmpty || isNotUrl) { + if (isNotUrl) { + reason = 'Field must contain valid URL.' + } + return {field: name, invalid: isRequiredButEmpty || isNotUrl, reason} } else { return false } case 'EMAIL': - isNotValid = (required && !value) || (value && !validator.isEmail(value)) - if (isNotValid) { - return {field: name, invalid: isNotValid} + const isNotEmail = value && !validator.isEmail(value) + if (isRequiredButEmpty || isNotEmail) { + if (isNotEmail) { + reason = 'Field must contain valid email address.' + } + return {field: name, invalid: isRequiredButEmpty || isNotEmail, reason} } else { return false } case 'GTFS_ZONE': - isNotValid = required && (value === null || typeof value === 'undefined') - if (isNotValid) { - return {field: name, invalid: isNotValid} + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} } else { return false } case 'TIMEZONE': - isNotValid = required && (value === null || typeof value === 'undefined') - if (isNotValid) { - return {field: name, invalid: isNotValid} + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} } else { return false } case 'LANGUAGE': - isNotValid = required && (value === null || typeof value === 'undefined') - if (isNotValid) { - return {field: name, invalid: isNotValid} + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} } else { return false } case 'LATITUDE': - isNotValid = required && (value === null || typeof value === 'undefined') - if (value > 90 || value < -90) { - isNotValid = true - } - if (isNotValid) { - return {field: name, invalid: isNotValid} + const isNotLat = value > 90 || value < -90 + if (isRequiredButEmpty || isNotLat) { + if (isNotLat) { + reason = 'Field must be valid latitude.' + } + return {field: name, invalid: isRequiredButEmpty || isNotLat, reason} } else { return false } case 'LONGITUDE': - isNotValid = required && (value === null || typeof value === 'undefined') - if (value > 180 || value < -180) { - isNotValid = true - } - if (isNotValid) { - return {field: name, invalid: isNotValid} + const isNotLng = value > 180 || value < -180 + if (isRequiredButEmpty || isNotLng) { + if (isNotLng) { + reason = 'Field must be valid longitude.' + } + return {field: name, invalid: isRequiredButEmpty || isNotLng, reason} } else { return false } case 'TIME': case 'NUMBER': - isNotValid = required && (value === null || typeof value === 'undefined') - if (isNotValid) { - return {field: name, invalid: isNotValid} + const isNotANumber = isNaN(value) + if (isRequiredButEmpty || isNotANumber) { + if (isNotANumber) { + reason = 'Field must be valid number' + } + return {field: name, invalid: isRequiredButEmpty || isNotANumber, reason} } else { return false } @@ -99,13 +123,21 @@ export function validate (type, required, name, value, entities, id) { case 'POSITIVE_NUM': case 'DAY_OF_WEEK_BOOLEAN': case 'DROPDOWN': - // isNotValid = required && (value === null || typeof value === 'undefined') - // if (isNotValid) { - // return {field: name, invalid: isNotValid} - // } - break case 'GTFS_ROUTE': case 'GTFS_AGENCY': + if (name === 'agency_id' && tableData.agency && tableData.agency.length > 1) { + const missingId = doesNotExist(value) + if (missingId) { + reason = 'Field must be populated for feeds with more than one agency.' + return {field: name, invalid: missingId, reason} + } + } + return false case 'GTFS_STOP': + default: + if (isRequiredButEmpty) { + return {field: name, invalid: isRequiredButEmpty, reason} + } + return false } } From 69d72c68da9152ed0dc909a28253fbaafd767ef2 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 13:10:10 -0400 Subject: [PATCH 054/265] refactor(Auth0Manager): refactor, remove logs --- lib/common/user/Auth0Manager.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/common/user/Auth0Manager.js b/lib/common/user/Auth0Manager.js index e757fe053..b58677347 100644 --- a/lib/common/user/Auth0Manager.js +++ b/lib/common/user/Auth0Manager.js @@ -59,7 +59,8 @@ export default class Auth0Manager { } loginFromToken (token, props) { - return fetch('https://' + this.props.domain + '/tokeninfo?id_token=' + token, { + const url = `https://${this.props.domain}/tokeninfo?id_token=${token}` + return fetch(url, { method: 'post' }).then(res => res.json()).then((profile) => { return constructUserObj(token, profile) @@ -79,13 +80,9 @@ export default class Auth0Manager { return new Promise((resolve, reject) => { var lockOptions = { connections: ['Username-Password-Authentication'], - // callbackURL: getConfigProperty('application.url'), icon: getConfigProperty('application.logo') ? getConfigProperty('application.logo') : icon, disableSignupAction: true, - // disableResetAction: true, - authParams: { - state: window.location.href - }, + authParams: {state: window.location.href}, ...additionalLockOptions } if (this.props.logo) lockOptions.icon = this.props.logo @@ -96,11 +93,6 @@ export default class Auth0Manager { } // save token to localStorage window.localStorage.setItem('userToken', token) - - // initialize auto log out check - // this.setupSingleLogout() - console.log(token, profile) - // this.userLoggedIn(token, profile) resolve(constructUserObj(token, profile)) }) }) From ab3236d541d62e74c5b99a001cc55190ecda7318 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 13:13:43 -0400 Subject: [PATCH 055/265] refactor(EditorFeedSourcePanel): sort snapshots from newest to oldest, refactor anon functions --- .../components/EditorFeedSourcePanel.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/editor/components/EditorFeedSourcePanel.js b/lib/editor/components/EditorFeedSourcePanel.js index 497affbd5..715f466ab 100644 --- a/lib/editor/components/EditorFeedSourcePanel.js +++ b/lib/editor/components/EditorFeedSourcePanel.js @@ -50,7 +50,7 @@ export default class EditorFeedSourcePanel extends Component { ? feedSource.editorSnapshots.find(s => s.current) : null const inactiveSnapshots = feedSource.editorSnapshots - ? feedSource.editorSnapshots.filter(s => !s.current) + ? feedSource.editorSnapshots.filter(s => !s.current).sort((a, b) => b.snapshotTime - a.snapshotTime) : [] return ( @@ -65,6 +65,7 @@ export default class EditorFeedSourcePanel extends Component { {currentSnapshot ? @@ -130,12 +131,17 @@ export default class EditorFeedSourcePanel extends Component { class SnapshotItem extends Component { static propTypes = { - snapshot: PropTypes.object, - feedSource: PropTypes.object + modal: PropTypes.object.isRequired, + snapshot: PropTypes.object.isRequired, + feedSource: PropTypes.object.isRequired } messages = getComponentMessages('EditorFeedSourcePanel') + _onClickDownload = () => this.props.downloadSnapshot(this.props.feedSource, this.props.snapshot) + + _onClickExport = () => this.props.exportSnapshotAsVersion(this.props.feedSource, this.props.snapshot.id) + _onDeleteSnapshot = () => { const {deleteSnapshot, feedSource, snapshot} = this.props this.props.modal.open({ @@ -155,12 +161,7 @@ class SnapshotItem extends Component { } render () { - const { - snapshot, - feedSource, - downloadSnapshot, - exportSnapshotAsVersion - } = this.props + const {snapshot} = this.props const dateFormat = getConfigProperty('application.date_format') const timeFormat = 'h:MMa' return ( @@ -185,12 +186,12 @@ class SnapshotItem extends Component { -

+

created {moment(snapshot.snapshotTime).fromNow()}

From fbb60b43a87fd1cd276eb0d298b81ab8f6564bdd Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 18:17:16 -0400 Subject: [PATCH 056/265] fix(gtfsFilter): check for null feedIds response, clean up mapDispatch --- lib/gtfs/actions/filter.js | 2 +- lib/gtfs/components/gtfsmapsearch.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/gtfs/actions/filter.js b/lib/gtfs/actions/filter.js index dd357b424..ff5bf5a3d 100644 --- a/lib/gtfs/actions/filter.js +++ b/lib/gtfs/actions/filter.js @@ -35,7 +35,7 @@ export function updateGtfsFilter (activeProject, user) { const activeFeeds = getActiveProject(getState()).feedSources // filter out null values - const feeds = feedIds.map(id => getFeed(activeFeeds, id)).filter(n => n) + const feeds = feedIds ? feedIds.map(id => getFeed(activeFeeds, id)).filter(n => n) : [] dispatch(updateLoadedFeeds(feeds)) }) } diff --git a/lib/gtfs/components/gtfsmapsearch.js b/lib/gtfs/components/gtfsmapsearch.js index 5d7e9cdd3..99878087b 100644 --- a/lib/gtfs/components/gtfsmapsearch.js +++ b/lib/gtfs/components/gtfsmapsearch.js @@ -115,8 +115,6 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return {} -} +const mapDispatchToProps = {} export default connect(mapStateToProps, mapDispatchToProps)(GtfsMapSearch) From aeebe89075ff2c8cf1fb46a13e5fa4334f651d38 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 18:52:38 -0400 Subject: [PATCH 057/265] fix(style): refactor GtfsFilter so that dropdown scrolls, and add additional style fixes --- lib/gtfs/components/GtfsFilter.js | 138 +++++++++++++++++------------- lib/style.css | 53 +++++++++++- 2 files changed, 127 insertions(+), 64 deletions(-) diff --git a/lib/gtfs/components/GtfsFilter.js b/lib/gtfs/components/GtfsFilter.js index 0edec1aec..1d6f711d0 100644 --- a/lib/gtfs/components/GtfsFilter.js +++ b/lib/gtfs/components/GtfsFilter.js @@ -1,5 +1,5 @@ import React, {Component, PropTypes} from 'react' -import { Button, DropdownButton, MenuItem, Label, ButtonToolbar } from 'react-bootstrap' +import { Button, Dropdown, MenuItem, Label, ButtonToolbar } from 'react-bootstrap' export default class GtfsFilter extends Component { static propTypes ={ @@ -12,6 +12,7 @@ export default class GtfsFilter extends Component { onRemoveAllFeed: PropTypes.func, onRemoveFeed: PropTypes.func } + getFeedSummary (feeds) { return feeds.length === 0 ? 'No feeds selected' @@ -23,82 +24,97 @@ export default class GtfsFilter extends Component { }` : `Searching ${feeds.length} feeds` } + + _onSelect = eventKey => { + const feed = this.props.allFeeds.find(f => f.id === eventKey) + if (this.props.activeFeeds.indexOf(feed) === -1) { + this.props.onAddFeed(feed) + } else { + this.props.onRemoveFeed(feed) + } + } + + _onToggleAll = () => this.props.activeFeeds.length > 0 + ? this.props.onRemoveAllFeeds() + : this.props.onAddAllFeeds() + render () { const { activeFeeds, activeAndLoadedFeeds, allFeeds, - loadedFeeds, - onAddFeed, - onAddAllFeeds, - onRemoveFeed, - onRemoveAllFeeds + loadedFeeds } = this.props const buttonMinimalStyle = { marginTop: '10px', marginBottom: '5px' } return ( - - + { - const feed = allFeeds.find(f => f.id === eventKey) - activeFeeds.indexOf(feed) === -1 ? onAddFeed(feed) : onRemoveFeed(feed) - }}> - {allFeeds.length > 0 - ? allFeeds.map((feed) => { - const isPublished = feed.publishedVersionId !== null - const disabled = loadedFeeds.findIndex(f => f.id === feed.id) === -1 - return ( - - - - {feed.shortName || feed.name.length > 11 ? feed.name.substr(0, 11) + '...' : feed.name} - - - ) - }) - : No feeds available - } - + title={activeAndLoadedFeeds.map(f => f.name).join(', ')} + onSelect={this._onSelect} + id='gtfs-feed-filter'> + + {this.getFeedSummary(activeAndLoadedFeeds)} + + + {allFeeds.length > 0 + ? allFeeds.map((feed) => { + const isPublished = feed.publishedVersionId !== null + const disabled = loadedFeeds.findIndex(f => f.id === feed.id) === -1 + return ( + + + + + {feed.shortName || feed.name.length > 11 + ? feed.name.substr(0, 11) + '...' + : feed.name + } + + + + ) + }) + : + No feeds available + + } + + diff --git a/lib/style.css b/lib/style.css index ec7d144c6..9e6616dfe 100644 --- a/lib/style.css +++ b/lib/style.css @@ -125,8 +125,9 @@ } -.gtfs-map-select > .Select > .Select-menu-outer { - z-index: 2000; +/* Ensure react-select menu is on top of leaflet */ +.Select > .Select-menu-outer { + z-index: 2000000; } .loading-ellipsis:after { @@ -153,4 +154,50 @@ /* TIMETABLE EDITOR */ -.editable-cell:focus {outline:0;} +/* remove blue focus outline from timetable cell */ +.editable-cell:focus { + outline:0; +} + +/* Bootstrap button dropdown fix for menu showing up underneath leaflet zoom controls */ +.dropdown.open.btn-group > ul.dropdown-menu { + z-index: 20000; +} + +/* Line splitter for trip pattern editor sections */ +h4.line { + font-size: 13px; + position: relative; + overflow: hidden; + text-align:left; +} + + +h4.line:after { + display: inline-block; + content: ""; + height: 2px; + background: #eee; + position: absolute; + width: 100%; + top: 50%; + margin-top: 0px; + margin-left: 10px; +} + + +.nomargin { + margin: 0px; +} + +/* Add error outline to react-select component */ +.form-group.has-error > .Select > .Select-control { + border-color: #a94442; +} + +/* Scrollable button menu... requires custom Dropdown.Menu component (not simply DropdownButton) */ +.btn-group.dropdown > .dropdown-menu.scrollable-dropdown { + height: auto; + max-height: 400px; + overflow-x: hidden; +} From 1ada05a9705ba719fcea4c3336172198fafae9e1 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 18:53:03 -0400 Subject: [PATCH 058/265] refactor(FeedInfoPanel): anon func fixes --- lib/editor/components/FeedInfoPanel.js | 127 +++++++++++++------------ 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/lib/editor/components/FeedInfoPanel.js b/lib/editor/components/FeedInfoPanel.js index b267bb2a7..7576cd4a2 100644 --- a/lib/editor/components/FeedInfoPanel.js +++ b/lib/editor/components/FeedInfoPanel.js @@ -7,6 +7,8 @@ import CreateSnapshotModal from './CreateSnapshotModal' import SelectFileModal from '../../common/components/SelectFileModal.js' import { gtfsIcons } from '../util/ui' +const PANEL_WIDTH = 400 + export default class FeedInfoPanel extends Component { static propTypes = { feedSource: PropTypes.object, @@ -16,13 +18,52 @@ export default class FeedInfoPanel extends Component { setActiveEntity: PropTypes.func } - constructor (props) { - super(props) - this.state = { - right: 5 + state = { + right: 5 + } + + _onAddSelect = key => { + this.props.setActiveEntity(this.props.feedSource.id, key, {id: 'new'}) + } + + _onDropdownArrowClick = () => this.props.getSnapshots(this.props.feedSource) + + _onNavigate = key => { + switch (key) { + case '1': + return browserHistory.push(`/project/${this.props.project.id}`) + case '2': + return browserHistory.push(`/feed/${this.props.feedSource.id}`) + } + } + + _onOkClicked = (name, comment) => { + this.props.createSnapshot(this.props.feedSource, name, comment) + } + + _onSelectSnapshot = key => { + const snapshot = this.props.feedSource.editorSnapshots.find(s => s.id === key) + this.props.showConfirmModal({ + title: `Restore ${key}?`, + body: `Are you sure you want to restore this snapshot?`, + onConfirm: () => { + this.props.restoreSnapshot(this.props.feedSource, snapshot) + } + }) + } + + _onToggleHide = () => { + const toolbarVisible = this.state.right > 0 + if (toolbarVisible) { + this.setState({right: 30 - PANEL_WIDTH}) + } else { + this.setState({right: 5}) } } - showUploadFileModal (feedSource) { + + _openSnapshotModal = () => this.refs.snapshotModal.open() + + showUploadFileModal = () => { this.refs.selectFileModal.open({ title: 'Upload route shapefile', body: 'Select a zipped shapefile to display on map:', @@ -31,27 +72,24 @@ export default class FeedInfoPanel extends Component { if (files[0].type !== 'application/zip' || nameArray[nameArray.length - 1] !== 'zip') { return false } else { - this.props.displayRoutesShapefile(feedSource, files[0]) + this.props.displayRoutesShapefile(this.props.feedSource, files[0]) return true } }, errorMessage: 'Uploaded file must be a valid zip file (.zip).' }) } + render () { const { feedSource, feedInfo } = this.props if (!feedInfo) return null - const panelWidth = 400 - // let panelHeight = '100px' const panelStyle = { - // backgroundColor: 'white', position: 'absolute', right: this.state.right, bottom: 20, zIndex: 500, borderRadius: '5px', - // height: panelHeight, - width: `${panelWidth}px` + width: `${PANEL_WIDTH}px` } if (!feedInfo || !feedSource) { return null @@ -67,22 +105,14 @@ export default class FeedInfoPanel extends Component {
- { - this.props.createSnapshot(feedSource, name, comment) - }} - /> + {/* Hide toolbar toggle */} {toolbarVisible ? 'Hide toolbar' : 'Show toolbar'}}> @@ -92,21 +122,12 @@ export default class FeedInfoPanel extends Component { dropup title={Editing {feedName}} id='navigation-dropdown' - onSelect={key => { - switch (key) { - case '1': - return browserHistory.push(`/project/${this.props.project.id}`) - case '2': - return browserHistory.push(`/feed/${this.props.feedSource.id}`) - } - }} - > + onSelect={this._onNavigate}> Back to project Back to feed source {/* Add entity dropdown */} @@ -114,11 +135,7 @@ export default class FeedInfoPanel extends Component { pullRight dropup title={} id='add-entity-dropdown' - onSelect={key => { - console.log(key) - this.props.setActiveEntity(feedSource.id, key, {id: 'new'}) - }} - > + onSelect={this._onAddSelect}> {gtfsIcons.map(c => { if (!c.addable) return null const name = c.id === 'scheduleexception' ? 'schedule exception' : c.id @@ -133,38 +150,26 @@ export default class FeedInfoPanel extends Component { dropup pullRight id='snapshot-dropdown' - onSelect={key => { - const snapshot = this.props.feedSource.editorSnapshots.find(s => s.id === key) - this.props.showConfirmModal({ - title: `Restore ${key}?`, - body: `Are you sure you want to restore this snapshot?`, - onConfirm: () => { - this.props.restoreSnapshot(feedSource, snapshot) - } - }) - }} - > + onSelect={this._onSelectSnapshot}> Take snapshot}> { - this.props.getSnapshots(feedSource) - }} - /> + onClick={this._onDropdownArrowClick} /> - {this.props.feedSource && this.props.feedSource.editorSnapshots - ? this.props.feedSource.editorSnapshots.map(snapshot => { + {feedSource && feedSource.editorSnapshots + ? feedSource.editorSnapshots.map(snapshot => { return ( - Revert to {snapshot.name} + + Revert to {snapshot.name} + ) }) : No snapshots From 843b4df909b05e7097be56638992769a8b9e4d9f Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 14 Apr 2017 18:54:24 -0400 Subject: [PATCH 059/265] refactor(EditShape): move editSettings into separate component --- lib/editor/components/pattern/EditSettings.js | 147 ++++++++++++++++++ .../components/pattern/EditShapePanel.js | 99 +----------- 2 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 lib/editor/components/pattern/EditSettings.js diff --git a/lib/editor/components/pattern/EditSettings.js b/lib/editor/components/pattern/EditSettings.js new file mode 100644 index 000000000..77ff60458 --- /dev/null +++ b/lib/editor/components/pattern/EditSettings.js @@ -0,0 +1,147 @@ +import React, { Component, PropTypes } from 'react' +import { Alert, Checkbox, FormGroup, Form, FormControl, ControlLabel } from 'react-bootstrap' +import Rcslider from 'rc-slider' + +import { CLICK_OPTIONS } from '../../util' +import {isExtensionEnabled} from '../../../common/util/config' +import toSentenceCase from '../../../common/util/to-sentence-case' + +export default class EditSettings extends Component { + static propTypes = { + editSettings: PropTypes.object, + updateEditSetting: PropTypes.func.isRequired + } + + _formatSliderTip = (value) => `${value}m (${Math.round(value * 0.000621371 * 100) / 100}mi)` + + _onCheckboxChange = (evt) => this.props.updateEditSetting(evt.target.name, evt.target.checked) + + _onSelectChange = (evt) => this.props.updateEditSetting(evt.target.name, evt.target.value) + + _onSliderChange = (value) => this.props.updateEditSetting('stopInterval', value) + + render () { + const {editSettings, updateEditSetting} = this.props + const { + editGeometry, + onMapClick, + stopInterval + } = editSettings + const SETTINGS = [ + {type: 'followStreets', label: 'Snap to streets'}, + {type: 'snapToStops', label: 'Snap to stops'}, + {type: 'showStops', label: 'Show stops'} + ] + + if (!editGeometry) return null + + return ( + + Edit settings + {SETTINGS.map((s, i) => ( + + {s.label} + + ))} + Editing mode + + {CLICK_OPTIONS.map(v => { + // ADD_STOPS_AT_INTERSECTIONS only enabled for nysdot extenstion (due to custom r5 deployment) + const disabled = v === 'ADD_STOPS_AT_INTERSECTIONS' && !isExtensionEnabled('nysdot') + return + })} + + {onMapClick === 'ADD_STOPS_AT_INTERVAL' + ?
+ 400m, + 800: '800m', + 1600: '1600m' + }} + tipFormatter={this._formatSliderTip} + /> +
+ : onMapClick === 'ADD_STOPS_AT_INTERSECTIONS' + ? + : null + } + {/* Show alert/warning if experimental mode is chosen. */} + {onMapClick.includes('ADD_') + ? + Warning! This editing mode creates new stops. Unless no existing stops are nearby, this mode is not recommended. + + : null + } +
+ ) + } +} + +class AddStopAtIntersectionSettings extends Component { + static propTypes = { + editSettings: PropTypes.object, + updateEditSetting: PropTypes.func.isRequired + } + + _onNumberChange = (evt) => this.props.updateEditSetting(evt.target.name, +evt.target.value) + + render () { + const {editSettings} = this.props + const { + distanceFromIntersection, + afterIntersection, + intersectionStep + } = editSettings + return ( +
+ {/* distance from intersection */} + + meters + {/* before/after intersection */} + + + + + every + {/* every n intersections */} + + intersections + + ) + } +} diff --git a/lib/editor/components/pattern/EditShapePanel.js b/lib/editor/components/pattern/EditShapePanel.js index bafb12ad6..a64ceefac 100644 --- a/lib/editor/components/pattern/EditShapePanel.js +++ b/lib/editor/components/pattern/EditShapePanel.js @@ -1,11 +1,9 @@ import Icon from '@conveyal/woonerf/components/icon' -import React, { Component } from 'react' -import { Button, Alert, Checkbox, ButtonToolbar, FormGroup, Form, FormControl, ControlLabel } from 'react-bootstrap' -import Rcslider from 'rc-slider' +import React, {Component} from 'react' +import {Button, ButtonToolbar} from 'react-bootstrap' +import EditSettings from './EditSettings' import { polyline as getPolyline } from '../../../scenario-editor/utils/valhalla' -import { CLICK_OPTIONS } from '../../util' -import toSentenceCase from '../../../common/util/to-sentence-case' export default class EditShapePanel extends Component { async drawPatternFromStops (pattern, stops) { @@ -19,89 +17,6 @@ export default class EditShapePanel extends Component { return false } } - renderEditSettings (isEditing) { - return isEditing - ? - Edit settings - this.props.updateEditSetting('followStreets', !this.props.editSettings.followStreets)}> - Snap to streets - - this.props.updateEditSetting('snapToStops', !this.props.editSettings.snapToStops)}> - Snap to stops - - this.props.updateEditSetting('hideStops', !this.props.editSettings.hideStops)}> - Show stops - - Editing mode - this.props.updateEditSetting('onMapClick', evt.target.value)} - > - {CLICK_OPTIONS.map(v => { - return - })} - - {this.props.editSettings.onMapClick === 'ADD_STOPS_AT_INTERVAL' - ?
- this.props.updateEditSetting('stopInterval', value)} - step={25} - marks={{ - 100: '100m', - 400: 400m, - 800: '800m', - 1600: '1600m' - }} - tipFormatter={(value) => { - return `${value}m (${Math.round(value * 0.000621371 * 100) / 100}mi)` - }} - /> -
- : this.props.editSettings.onMapClick === 'ADD_STOPS_AT_INTERSECTIONS' - ?
- {/* distance from intersection */} - this.props.updateEditSetting('distanceFromIntersection', +evt.target.value)} - style={{width: '60px', marginTop: '10px'}} - /> - meters - {/* before/after intersection */} - this.props.updateEditSetting('afterIntersection', +evt.target.value)} - style={{width: '80px', marginTop: '10px'}} - > - - - - every - {/* every n intersections */} - this.props.updateEditSetting('intersectionStep', +evt.target.value)} - style={{width: '55px', marginTop: '10px'}} - /> - intersections - - : null - } - {this.props.editSettings.onMapClick.includes('ADD_') - ? - Warning! This editing mode creates new stops. Unless no existing stops are nearby, this mode is not recommended. - - : null - } -
- : null - } renderEditButtons (isEditing, activePattern) { const buttons = [] if (isEditing) { @@ -198,12 +113,12 @@ export default class EditShapePanel extends Component { const { activePattern } = this.props return (
-

- Pattern Shape -

+

Pattern Shape

{this.renderEditButtons(this.props.editSettings.editGeometry, activePattern)} - {this.renderEditSettings(this.props.editSettings.editGeometry)} +
) From 7d343808f584fe20fb0bd8d8bbe885e5570e9aa2 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 09:27:40 -0400 Subject: [PATCH 060/265] style(reporter): destructure this.props, simplify mapDispatchToProps --- .../reporter/components/StopLayout.js | 50 +++++++++++-------- .../containers/ActiveDateTimeFilter.js | 10 +--- .../components/reporter/containers/Stops.js | 7 --- 3 files changed, 31 insertions(+), 36 deletions(-) diff --git a/lib/manager/components/reporter/components/StopLayout.js b/lib/manager/components/reporter/components/StopLayout.js index 4f333c52c..5ebf2ec00 100644 --- a/lib/manager/components/reporter/components/StopLayout.js +++ b/lib/manager/components/reporter/components/StopLayout.js @@ -16,7 +16,6 @@ export default class StopLayout extends Component { stopRouteFilterChange: PropTypes.func, stopPatternFilterChange: PropTypes.func, stopDateTimeFilterChange: PropTypes.func, - version: PropTypes.object, routes: PropTypes.object, patterns: PropTypes.object, @@ -29,7 +28,18 @@ export default class StopLayout extends Component { this.props.onComponentMount(this.props) } render () { - const rows = this.props.stops.data.map(s => { + const { + stops, + dateTime, + stopDateTimeFilterChange, + routes, + stopRouteFilterChange, + patterns, + stopPatternFilterChange, + version, + tableOptions + } = this.props + const rows = stops.data.map(s => { const flat = clone(s) if (s.stats) { delete flat.stats @@ -41,15 +51,13 @@ export default class StopLayout extends Component { }) const dateTimeProps = { mode: 'date', - dateTime: this.props.dateTime.date ? +moment(this.props.dateTime.date, 'YYYY-MM-DD') : +moment(), + dateTime: dateTime.date ? +moment(dateTime.date, 'YYYY-MM-DD') : +moment(), onChange: (millis) => { - console.log(+millis) const date = moment(+millis).format('YYYY-MM-DD') - console.log(date) - this.props.stopDateTimeFilterChange({date}) + stopDateTimeFilterChange({date}) } } - if (!this.props.dateTime.date) { + if (!dateTime.date) { dateTimeProps.defaultText = 'Please select a date' } @@ -59,44 +67,44 @@ export default class StopLayout extends Component { } - {this.props.stops.fetchStatus.fetching && + {stops.fetchStatus.fetching && } - {this.props.stops.fetchStatus.error && + {stops.fetchStatus.error && An error occurred while trying to fetch the data } - {this.props.stops.fetchStatus.fetched && + {stops.fetchStatus.fetched && ID Name diff --git a/lib/manager/components/reporter/containers/ActiveDateTimeFilter.js b/lib/manager/components/reporter/containers/ActiveDateTimeFilter.js index 37441ca2c..3e49f9e3b 100644 --- a/lib/manager/components/reporter/containers/ActiveDateTimeFilter.js +++ b/lib/manager/components/reporter/containers/ActiveDateTimeFilter.js @@ -9,14 +9,8 @@ const mapStateToProps = (state, ownProps) => { } } -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onComponentMount: (initialProps) => { - }, - updateDateTimeFilter: (props) => { - dispatch(updateDateTimeFilter(props)) - } - } +const mapDispatchToProps = { + updateDateTimeFilter } const ActiveDateTimeFilter = connect( diff --git a/lib/manager/components/reporter/containers/Stops.js b/lib/manager/components/reporter/containers/Stops.js index 90a20d29a..51bc366ba 100644 --- a/lib/manager/components/reporter/containers/Stops.js +++ b/lib/manager/components/reporter/containers/Stops.js @@ -20,9 +20,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { if (!initialProps.routes.fetchStatus.fetched) { dispatch(fetchRoutes(feedId)) } - // if (!initialProps.patterns.fetchStatus.fetched) { - // dispatch(fetchPatterns(feedId, null)) - // } }, stopRouteFilterChange: (newValue) => { dispatch(stopRouteFilterChange(feedId, newValue)) @@ -33,10 +30,6 @@ const mapDispatchToProps = (dispatch, ownProps) => { stopDateTimeFilterChange: (props) => { dispatch(stopDateTimeFilterChange(feedId, props)) } - // viewStops: (row) => { - // dispatch(stopPatternFilterChange(feedId, row)) - // dispatch(ownProps.selectTab('stops')) - // } } } From d494c08f0c3006d87019e2012b8d604f9754369d Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 09:29:31 -0400 Subject: [PATCH 061/265] refactor(FeedVersionNavigator): update gtfsPlusDataRequested for changed args --- lib/manager/components/FeedVersionNavigator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/manager/components/FeedVersionNavigator.js b/lib/manager/components/FeedVersionNavigator.js index 2c7208763..9261a56a0 100644 --- a/lib/manager/components/FeedVersionNavigator.js +++ b/lib/manager/components/FeedVersionNavigator.js @@ -216,7 +216,7 @@ export default class FeedVersionNavigator extends Component { fetchValidationResult(version, isPublic) }} gtfsPlusDataRequested={(version) => { - gtfsPlusDataRequested(version) + gtfsPlusDataRequested(version.id) }} notesRequested={() => { notesRequestedForVersion(version) }} newNotePosted={(note) => { newNotePostedForVersion(version, note) }} From e9db803d3dbba869d3aa56f3b34222c5e4d3b64c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 09:33:46 -0400 Subject: [PATCH 062/265] style(manager-components): add new lines, remove old commented code --- lib/manager/components/FeedSourceDropdown.js | 3 ++- lib/manager/components/FeedSourceTable.js | 18 ++++++--------- lib/manager/components/HomeProjectDropdown.js | 23 +++++++++++++------ lib/manager/components/UserHomePage.js | 1 - 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/manager/components/FeedSourceDropdown.js b/lib/manager/components/FeedSourceDropdown.js index 88df02e25..267e80e75 100644 --- a/lib/manager/components/FeedSourceDropdown.js +++ b/lib/manager/components/FeedSourceDropdown.js @@ -73,7 +73,8 @@ export default class FeedSourceDropdown extends Component { onClose={() => setHold(false)} /> - this.confirmUpload(files)} diff --git a/lib/manager/components/FeedSourceTable.js b/lib/manager/components/FeedSourceTable.js index fd99d0b61..86c0003c0 100644 --- a/lib/manager/components/FeedSourceTable.js +++ b/lib/manager/components/FeedSourceTable.js @@ -66,7 +66,8 @@ export default class FeedSourceTable extends Component { ? : feedSources.length ? feedSources.map((feedSource) => { - return + minWidth={200} /> + minWidth={40} /> + minWidth={40} /> {isModuleEnabled('deployment') ? + title={feedSource.deployable ? 'Feed source may be deployed to trip planner' : 'Depoyment to trip planner currently disabled'} /> : null } {feedSource.url @@ -226,8 +223,7 @@ class FeedSourceAttributes extends Component { icon={'link'} title={feedSource.url} className={'text-muted'} - minWidth={40} - /> + minWidth={40} /> : null } diff --git a/lib/manager/components/HomeProjectDropdown.js b/lib/manager/components/HomeProjectDropdown.js index f66a0fc35..a761dd939 100644 --- a/lib/manager/components/HomeProjectDropdown.js +++ b/lib/manager/components/HomeProjectDropdown.js @@ -5,7 +5,9 @@ import { LinkContainer } from 'react-router-bootstrap' export default class HomeProjectDropdown extends Component { static propTypes = { - activeProject: PropTypes.object + activeProject: PropTypes.object, + user: PropTypes.object, + visibleProjects: PropTypes.array } render () { const { @@ -25,17 +27,24 @@ export default class HomeProjectDropdown extends Component { {activeProject.name} - : {user.email} {user.profile.nickname} + ? + {activeProject.name} + + : + {user.email} + {' '} + {user.profile.nickname} + } - // onSelect={(eventKey) => { - // setActiveProject(eventKey) - // }} > {activeProject && ( - {user.email} {user.profile.nickname} + + {user.email} + {' '} + {user.profile.nickname} + )} diff --git a/lib/manager/components/UserHomePage.js b/lib/manager/components/UserHomePage.js index de0ea84e9..cc781af7a 100644 --- a/lib/manager/components/UserHomePage.js +++ b/lib/manager/components/UserHomePage.js @@ -49,7 +49,6 @@ export default class UserHomePage extends Component { project, user, logoutHandler, - // setActiveProject, visibilityFilter, searchTextChanged, visibilityFilterChanged From 09a0b7837719c20e14a01100928e81e93facb643 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 09:38:31 -0400 Subject: [PATCH 063/265] refactor(FareRules): refactor fare rules form --- lib/editor/components/FareRuleSelections.js | 110 ++++++++++++++++++++ lib/editor/components/FareRulesForm.js | 95 ++--------------- 2 files changed, 121 insertions(+), 84 deletions(-) create mode 100644 lib/editor/components/FareRuleSelections.js diff --git a/lib/editor/components/FareRuleSelections.js b/lib/editor/components/FareRuleSelections.js new file mode 100644 index 000000000..268ae4a37 --- /dev/null +++ b/lib/editor/components/FareRuleSelections.js @@ -0,0 +1,110 @@ +import React, {Component, PropTypes} from 'react' +import Select from 'react-select' + +import VirtualizedEntitySelect from './VirtualizedEntitySelect' +import { getEntityName } from '../util/gtfs' +import ZoneSelect from './ZoneSelect' + +export default class FareRuleSelections extends Component { + + static propTypes = { + rule: PropTypes.object, + tableData: PropTypes.object, + activeEntity: PropTypes.object, + activeComponent: PropTypes.string, + zones: PropTypes.object, + zoneOptions: PropTypes.array, + routeEntity: PropTypes.object, + updateActiveEntity: PropTypes.func + } + + _onContainsChange = (input) => { + const {activeComponent, activeEntity, rule, updateActiveEntity} = this.props + const rules = [...activeEntity.fareRules] + rule.contains_id = input ? input.value : null + updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) + } + + _onDestinationChange = (input) => { + const {activeComponent, activeEntity, rule, updateActiveEntity} = this.props + const rules = [...activeEntity.fareRules] + rule.destination_id = input ? input.value : null + updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) + } + + _onOriginChange = (input) => { + const {activeComponent, activeEntity, rule, updateActiveEntity} = this.props + const rules = [...activeEntity.fareRules] + rule.origin_id = input ? input.value : null + updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) + } + + _onRouteChange = (input) => { + const {activeComponent, activeEntity, rule, updateActiveEntity} = this.props + const rules = [...activeEntity.fareRules] + rule.route_id = input ? input.value : null + updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) + } + + render () { + const {rule, tableData, zones, zoneOptions, routeEntity} = this.props + return ( +
+ {rule.route_id + ? + : null + } + {rule.contains_id // can be boolean (no zone specified) or string (specified zone_id) + ? + : null + } + {rule.origin_id || rule.destination_id + ? [ + , + { - console.log(input) - const rules = [...activeEntity.fareRules] - rule.contains_id = input ? input.value : null - this.props.updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) - }} - options={zoneOptions} - /> - : null - } - {rule.origin_id || rule.destination_id - ? [ - { - console.log(input) - const rules = [...activeEntity.fareRules] - rule.destination_id = input ? input.value : null - this.props.updateActiveEntity(activeEntity, activeComponent, {fareRules: rules}) - }} - options={zoneOptions} - /> - ] - : null - } -
- ) - } + render () { const { activeEntity, @@ -167,7 +86,15 @@ export default class FareRulesForm extends Component { ) })} - {this.renderRuleSelections(rule, tableData, activeEntity, activeComponent, zones, zoneOptions, routeEntity)} + ) })} From c2771ccee27f2e91a55cbef417c0764aca14f475 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 09:39:43 -0400 Subject: [PATCH 064/265] fix(editor-reducer): sort trip patterns on receive --- lib/editor/reducers/data.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/editor/reducers/data.js b/lib/editor/reducers/data.js index df03cb978..27d441cb9 100644 --- a/lib/editor/reducers/data.js +++ b/lib/editor/reducers/data.js @@ -2,6 +2,7 @@ import update from 'react-addons-update' import clone from 'lodash.clonedeep' import ll from '@conveyal/lonlat' +import {defaultSorter} from '../../common/util/util' import { stopToGtfs, routeToGtfs, agencyToGtfs, calendarToGtfs, fareToGtfs, gtfsSort } from '../util/gtfs' import { getStopsForPattern } from '../util' @@ -317,8 +318,9 @@ const data = (state = defaultState, action) => { } }) case 'RECEIVE_TRIP_PATTERNS_FOR_ROUTE': + const tripPatterns = action.tripPatterns.sort(defaultSorter) routeIndex = state.tables.route.findIndex(r => r.id === action.routeId) - activePattern = state.active.subEntityId && action.tripPatterns.find(p => p.id === state.active.subEntityId) + activePattern = state.active.subEntityId && tripPatterns.find(p => p.id === state.active.subEntityId) if (activePattern) { activePattern.stops = getStopsForPattern(activePattern, state.tables.stop) } @@ -327,15 +329,15 @@ const data = (state = defaultState, action) => { } if (state.active.entity.id === action.routeId) { return update(state, { - tables: {route: {[routeIndex]: {$merge: {tripPatterns: action.tripPatterns}}}}, + tables: {route: {[routeIndex]: {$merge: {tripPatterns}}}}, active: { - entity: {$merge: {tripPatterns: action.tripPatterns}}, + entity: {$merge: {tripPatterns}}, subEntity: {$set: Object.assign({}, activePattern)} } }) } else { return update(state, { - tables: {route: {[routeIndex]: {$merge: {tripPatterns: action.tripPatterns}}}} + tables: {route: {[routeIndex]: {$merge: {tripPatterns: tripPatterns}}}} }) } // case 'RECEIVE_TRIPS_FOR_CALENDAR': From 123f910c3086e873d1c1d7290677d68c69c63b52 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Sat, 15 Apr 2017 10:33:20 -0400 Subject: [PATCH 065/265] refactor(scheduleException): refactor exception dates into separate component --- lib/editor/components/ExceptionDate.js | 79 +++++++++ .../components/ScheduleExceptionForm.js | 160 +++++++----------- 2 files changed, 143 insertions(+), 96 deletions(-) create mode 100644 lib/editor/components/ExceptionDate.js diff --git a/lib/editor/components/ExceptionDate.js b/lib/editor/components/ExceptionDate.js new file mode 100644 index 000000000..7215f8a5e --- /dev/null +++ b/lib/editor/components/ExceptionDate.js @@ -0,0 +1,79 @@ +import Icon from '@conveyal/woonerf/components/icon' +import React, {Component, PropTypes} from 'react' +import {Button} from 'react-bootstrap' +import DateTimeField from 'react-bootstrap-datetimepicker' +import moment from 'moment' + +export default class ExceptionDate extends Component { + + static propTypes = { + activeEntity: PropTypes.object, + updateActiveEntity: PropTypes.func, + activeComponent: PropTypes.string, + index: PropTypes.number, + tableData: PropTypes.object, + validate: PropTypes.func + } + + _onDateChange = (millis) => { + const {activeComponent, activeEntity, index, updateActiveEntity} = this.props + const dates = [...activeEntity.dates] + dates[index] = +millis + updateActiveEntity(activeEntity, activeComponent, {dates}) + } + + _onRemoveDate = () => { + const {activeComponent, activeEntity, index, updateActiveEntity} = this.props + const dates = [...activeEntity.dates] + dates.splice(index, 1) + updateActiveEntity(activeEntity, activeComponent, {dates: dates}) + } + + render () { + const {date, dateMap, index, validate} = this.props + let isNotValid = false + const dateString = moment(+date).format('YYYYMMDD') + // check if date already exists in this or other exceptions + if (dateMap[dateString] && dateMap[dateString].length > 1) { + validate({field: `dates-${index}`, invalid: true}) + isNotValid = true + } + const dateTimeProps = { + mode: 'date', + dateTime: date ? +moment(date) : +moment(), + onChange: this._onDateChange + } + if (!date) { + dateTimeProps.defaultText = 'Please select a date' + } + return ( +
+ + {/* + + */} + + {isNotValid + ? + {moment(+date).format('MM/DD/YYYY')} appears in another schedule exception. Please choose another date. + + : null + } +
+ ) + } +} diff --git a/lib/editor/components/ScheduleExceptionForm.js b/lib/editor/components/ScheduleExceptionForm.js index 43e100fcc..a6cdd47e3 100644 --- a/lib/editor/components/ScheduleExceptionForm.js +++ b/lib/editor/components/ScheduleExceptionForm.js @@ -3,8 +3,8 @@ import React, {Component, PropTypes} from 'react' import { Button, Form, FormControl, FormGroup, ControlLabel } from 'react-bootstrap' import Select from 'react-select' import moment from 'moment' -import DateTimeField from 'react-bootstrap-datetimepicker' +import ExceptionDate from './ExceptionDate' import { EXEMPLARS } from '../util' import toSentenceCase from '../../common/util/to-sentence-case' @@ -13,13 +13,54 @@ export default class ScheduleExceptionForm extends Component { activeEntity: PropTypes.object, updateActiveEntity: PropTypes.func, activeComponent: PropTypes.string, - tableData: PropTypes.object + tableData: PropTypes.object, + validate: PropTypes.func } + + _onAddDate = () => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + const dates = [...activeEntity.dates] + dates.push(0) + updateActiveEntity(activeEntity, activeComponent, {dates: dates}) + } + + _onAddedServiceChange = (input) => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + const val = input ? input.map(i => i.value) : null + updateActiveEntity(activeEntity, activeComponent, {addedService: val}) + } + + _onCustomScheduleChange = (input) => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + const val = input ? input.map(i => i.value) : null + updateActiveEntity(activeEntity, activeComponent, {customSchedule: val}) + } + + _onExemplarChange = (evt) => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + updateActiveEntity(activeEntity, activeComponent, {exemplar: evt.target.value, customSchedule: null}) + } + + _onNameChange = (evt) => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + updateActiveEntity(activeEntity, activeComponent, {name: evt.target.value}) + } + + _onRemovedServiceChange = (input) => { + const {activeComponent, activeEntity, updateActiveEntity} = this.props + const val = input ? input.map(i => i.value) : null + updateActiveEntity(activeEntity, activeComponent, {removedService: val}) + } + + calendarToOption = calendar => ({ + value: calendar.id, + label: calendar.description, + calendar + }) + render () { const { activeEntity, - updateActiveEntity, - activeComponent, tableData, validate } = this.props @@ -53,9 +94,7 @@ export default class ScheduleExceptionForm extends Component { Exception name { - updateActiveEntity(activeEntity, activeComponent, {name: evt.target.value}) - }} /> + onChange={this._onNameChange} /> Run the following schedule: { - // const props = {} - // props[field.name] = evt.target.value - updateActiveEntity(activeEntity, activeComponent, {exemplar: evt.target.value, customSchedule: null}) - }}> + onChange={this._onExemplarChange}> {EXEMPLARS.map(exemplar => { return ( @@ -166,61 +172,23 @@ export default class ScheduleExceptionForm extends Component { className={`col-xs-12`}> On these dates: {activeEntity && activeEntity.dates.length - ? activeEntity.dates.map((date, index) => { - let isNotValid = false - const dateString = moment(+date).format('YYYYMMDD') - // check if date already exists in this or other exceptions - if (dateMap[dateString] && dateMap[dateString].length > 1) { - validate({field: `dates-${index}`, invalid: true}) - isNotValid = true - } - const dateTimeProps = { - mode: 'date', - dateTime: date ? +moment(date) : +moment(), - onChange: (millis) => { - const dates = [...activeEntity.dates] - dates[index] = +millis - updateActiveEntity(activeEntity, activeComponent, {dates}) - } - } - if (!date) { - dateTimeProps.defaultText = 'Please select a date' - } - return ( -
- - - {isNotValid - ? {moment(+date).format('MM/DD/YYYY')} appears in another schedule exception. Please choose another date. - : null - } -
- ) - } + ? activeEntity.dates.map((date, index) => ( + + ) ) :
No dates specified
}
+ onClick={this._onAddDate}> + Add date +
From ad81e5740a12ec427507636f9b3abc0ce66e27a4 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 09:14:48 -0400 Subject: [PATCH 066/265] refactor(pattern-editor): fix anon functions, clean up component appearance --- .../components/pattern/EditSchedulePanel.js | 127 +++++++++++------- .../components/pattern/PatternStopCard.js | 1 + .../pattern/PatternStopContainer.js | 15 ++- .../components/pattern/PatternStopsPanel.js | 81 +++++------ .../components/pattern/TripPatternList.js | 2 +- .../components/pattern/TripPatternViewer.js | 4 +- 6 files changed, 132 insertions(+), 98 deletions(-) diff --git a/lib/editor/components/pattern/EditSchedulePanel.js b/lib/editor/components/pattern/EditSchedulePanel.js index 761514fd1..9f13939c0 100644 --- a/lib/editor/components/pattern/EditSchedulePanel.js +++ b/lib/editor/components/pattern/EditSchedulePanel.js @@ -1,70 +1,95 @@ import Icon from '@conveyal/woonerf/components/icon' -import React, { Component } from 'react' -import { Button, FormGroup, ControlLabel, ButtonGroup, DropdownButton, MenuItem } from 'react-bootstrap' +import React, {Component, PropTypes} from 'react' +import {Button, FormGroup, ControlLabel, ButtonGroup, DropdownButton, MenuItem} from 'react-bootstrap' + +const DIRECTIONS = [0, 1] export default class EditSchedulePanel extends Component { + + static propTypes = { + feedSource: PropTypes.object + } + + _editTimetables = () => { + const {setActiveEntity, feedSource, activeEntity, activePattern} = this.props + setActiveEntity(feedSource.id, 'route', activeEntity, 'trippattern', activePattern, 'timetable') + } + + _onChangeDirection = (evt) => { + const patternDirection = typeof evt.target.value === 'undefined' ? +!this.props.activePattern.patternDirection : +evt.target.value + if (this.props.activePattern.patternDirection !== patternDirection && DIRECTIONS.indexOf(patternDirection) !== -1) { + this.props.updateActiveEntity(this.props.activePattern, 'trippattern', {patternDirection}) + this.props.saveActiveEntity('trippattern') + } + } + + _onChangeUseFrequency = (key) => { + const useFrequency = key !== 'timetables' + const other = key === 'timetables' ? 'frequencies' : 'timetables' + this.props.showConfirmModal({ + title: `Use ${key} for ${this.props.activePattern.name}?`, + body: `Are you sure you want to use ${key} for this trip pattern? Any trips created using ${other} will be lost.`, + onConfirm: () => { + this.props.updateActiveEntity(this.props.activePattern, 'trippattern', {useFrequency}) + this.props.saveActiveEntity('trippattern') + } + }) + } + render () { - const { activePattern, activePatternId, feedSource, activeEntity } = this.props + const { activePattern, activePatternId } = this.props const timetableOptions = [ Use timetables, Use frequencies ] + const editSchedulesDisabled = activePatternId === 'new' || + (activePattern && activePattern.patternStops && activePattern.patternStops.length === 0) return (
-

Schedules

- - { - const useFrequency = key !== 'timetables' - const other = key === 'timetables' ? 'frequencies' : 'timetables' - this.props.showConfirmModal({ - title: `Use ${key} for ${activePattern.name}?`, - body: `Are you sure you want to use ${key} for this trip pattern? Any trips created using ${other} will be lost.`, - onConfirm: () => { - console.log('use ' + key) - this.props.updateActiveEntity(activePattern, 'trippattern', {useFrequency}) - this.props.saveActiveEntity('trippattern') - } - }) - }} - title={activePattern.useFrequency ? timetableOptions[1] : timetableOptions[0]} id='frequency-dropdown'> - {activePattern.useFrequency ? timetableOptions[0] : timetableOptions[1]} - - +

+ Schedules {`(${activePattern.numberOfTrips} trip${activePattern.numberOfTrips !== 1 ? 's' : ''})`} +

+ + + + + {activePattern.useFrequency ? timetableOptions[0] : timetableOptions[1]} + + + + Type + - Direction -
- - - + + {DIRECTIONS.map(dir => ( + + ))} + Direction
diff --git a/lib/editor/components/pattern/PatternStopCard.js b/lib/editor/components/pattern/PatternStopCard.js index b47ba4b3b..462ec39eb 100644 --- a/lib/editor/components/pattern/PatternStopCard.js +++ b/lib/editor/components/pattern/PatternStopCard.js @@ -93,6 +93,7 @@ class PatternStopCard extends Component {
{ diff --git a/lib/editor/components/pattern/PatternStopContainer.js b/lib/editor/components/pattern/PatternStopContainer.js index 18444ce91..7b0685b4f 100644 --- a/lib/editor/components/pattern/PatternStopContainer.js +++ b/lib/editor/components/pattern/PatternStopContainer.js @@ -22,6 +22,7 @@ class PatternStopContainer extends Component { stops: PropTypes.array, cardStyle: PropTypes.object } + constructor (props) { super(props) this.moveCard = this.moveCard.bind(this) @@ -30,6 +31,7 @@ class PatternStopContainer extends Component { cards: this.props.activePattern.patternStops.map(this.addUniqueId) } } + // we need to add a unique ID to patternStops received from props in order to make the card drag and drop work addUniqueId (stop, index) { const id = `${index}-${stop.stopId}` @@ -38,19 +40,22 @@ class PatternStopContainer extends Component { id } } + componentWillReceiveProps (nextProps) { // Updates cards when pattern stops order changes (does not account for changes to default travel/dwell times) if (nextProps.activePattern.patternStops && !shallowEqual(nextProps.activePattern.patternStops, this.props.activePattern.patternStops)) { this.setState({cards: nextProps.activePattern.patternStops.map(this.addUniqueId)}) } } - dropCard () { + + dropCard = () => { const patternStops = [...this.state.cards] const pattern = Object.assign({}, this.props.activePattern) pattern.patternStops = patternStops this.props.updateActiveEntity(this.props.activePattern, 'trippattern', {patternStops}) this.props.saveActiveEntity('trippattern') } + moveCard (id, atIndex) { const { card, index } = this.findCard(id) this.setState(update(this.state, { @@ -71,6 +76,9 @@ class PatternStopContainer extends Component { index: cards.indexOf(card) } } + + setActiveStop = (stopKey) => this.setState({activeStop: stopKey}) + render () { const { connectDropTarget } = this.props const { cards } = this.state @@ -99,10 +107,9 @@ class PatternStopContainer extends Component { patternStop={card} moveCard={this.moveCard} findCard={this.findCard} - dropCard={() => this.dropCard()} + dropCard={this.dropCard} activeStop={this.state.activeStop} - setActiveStop={(stopKey) => this.setState({activeStop: stopKey})} - /> + setActiveStop={this.setActiveStop} /> ) })}
diff --git a/lib/editor/components/pattern/PatternStopsPanel.js b/lib/editor/components/pattern/PatternStopsPanel.js index f2c94dafc..6432aff29 100644 --- a/lib/editor/components/pattern/PatternStopsPanel.js +++ b/lib/editor/components/pattern/PatternStopsPanel.js @@ -16,6 +16,9 @@ export default class PatternStopsPanel extends Pure { updateActiveEntity: PropTypes.func, updateEditSetting: PropTypes.func } + + _onClickAddStop = () => this.props.updateEditSetting('addStops', !this.props.editSettings.addStops) + addStopFromSelect = (input) => { if (!input) { return @@ -37,48 +40,48 @@ export default class PatternStopsPanel extends Pure { border: '1px dashed gray', padding: '0.5rem 0.5rem', marginBottom: '.5rem', - backgroundColor: '#f2f2f2', - cursor: 'pointer' + backgroundColor: '#f2f2f2' } return (
-

- Stops

+
+ - - {editSettings.addStops && mapState.zoom <= 14 - ? Zoom to view stops - : null - } - Stops -

- {/* List of pattern stops */} -
-

Stop sequence

+ bsStyle={editSettings.addStops ? 'default' : 'success'} + bsSize='small'> + {editSettings.addStops + ? Cancel + : Add stop + } + + {editSettings.addStops && mapState.zoom <= 14 + ? 'Zoom to view stops' + : `Add stops via map` + }
-
-

Travel time

+ {/* List of pattern stops */} +
+
+

Stop sequence

+
+
+

Travel time

+
+
-
- + {activePattern.patternStops && activePattern.patternStops.length > 0 + ? + :

This pattern has no stops.

+ } {/* Add stop selector */} {editSettings.addStops ?
@@ -92,7 +95,7 @@ export default class PatternStopsPanel extends Pure { bsSize='small' bsStyle='default' block - onClick={() => updateEditSetting('addStops', !editSettings.addStops)} + onClick={this._onClickAddStop} > Cancel @@ -102,9 +105,9 @@ export default class PatternStopsPanel extends Pure { bsSize='small' bsStyle='success' block - onClick={() => updateEditSetting('addStops', !editSettings.addStops)} + onClick={this._onClickAddStop} > - Add stop + Add stop by name }
diff --git a/lib/editor/components/pattern/TripPatternList.js b/lib/editor/components/pattern/TripPatternList.js index e3fa78f9e..757f04d1d 100644 --- a/lib/editor/components/pattern/TripPatternList.js +++ b/lib/editor/components/pattern/TripPatternList.js @@ -44,7 +44,7 @@ export default class TripPatternList extends Component { paddingBottom: 5 } const isActive = patternId && pattern.id === patternId - const patternName = `${`${pattern.name.length > 35 ? pattern.name.substr(0, 35) + '...' : pattern.name}`} ${pattern.patternStops ? `(${pattern.patternStops.length} stops)` : ''}` + const patternName = `${`${pattern.name.length > 29 ? pattern.name.substr(0, 29) + '...' : pattern.name}`} ${pattern.patternStops ? `(${pattern.patternStops.length} stops)` : ''}` return ( -
-
-
From 3939eee99e894d52821a212f91874d45a96cf54c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 09:15:36 -0400 Subject: [PATCH 067/265] refactor(editor-reducer): rename edit setting for clarity --- lib/editor/reducers/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/editor/reducers/settings.js b/lib/editor/reducers/settings.js index e4c021f79..2ffdec3f8 100644 --- a/lib/editor/reducers/settings.js +++ b/lib/editor/reducers/settings.js @@ -13,7 +13,7 @@ const defaultState = { intersectionStep: 2, snapToStops: true, addStops: false, - hideStops: false, + showStops: true, controlPoints: [], coordinatesHistory: [], actions: [], From 0b4315e7666c47289c5a214dc829f74a0ddab437 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 09:54:08 -0400 Subject: [PATCH 068/265] style(lint): fix build --- lib/editor/containers/ActiveGtfsEditor.js | 1 - lib/manager/actions/projects.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/editor/containers/ActiveGtfsEditor.js b/lib/editor/containers/ActiveGtfsEditor.js index 2d904e045..f0060454c 100644 --- a/lib/editor/containers/ActiveGtfsEditor.js +++ b/lib/editor/containers/ActiveGtfsEditor.js @@ -42,7 +42,6 @@ import { newGtfsEntities, cloneGtfsEntity, getGtfsTable, - receiveGtfsEntities, uploadBrandingAsset } from '../actions/editor' import { updateUserMetadata } from '../../manager/actions/user' diff --git a/lib/manager/actions/projects.js b/lib/manager/actions/projects.js index 15ae0a83b..03a3d557e 100644 --- a/lib/manager/actions/projects.js +++ b/lib/manager/actions/projects.js @@ -43,7 +43,7 @@ export function fetchProjects (getActive = false) { dispatch(receiveProjects(projects)) // return active project if requested if (getActive) { - let activeProject = getActiveProject(getState()) + const activeProject = getActiveProject(getState()) if (!activeProject.feedSources) { return dispatch(fetchProjectFeeds(activeProject.id)) .then(() => { From 2ddabc914a411fe0197d9d6801eeaa42a0c60745 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 10:02:12 -0400 Subject: [PATCH 069/265] style(lint): fix build --- lib/editor/components/EditorInput.js | 1 - lib/editor/components/ExceptionDate.js | 1 - lib/editor/components/FareRuleSelections.js | 1 - lib/editor/components/pattern/EditSchedulePanel.js | 1 - lib/editor/components/pattern/PatternStopsPanel.js | 2 +- 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/editor/components/EditorInput.js b/lib/editor/components/EditorInput.js index fe8ca17cd..f97e38c1a 100644 --- a/lib/editor/components/EditorInput.js +++ b/lib/editor/components/EditorInput.js @@ -14,7 +14,6 @@ import toSentenceCase from '../../common/util/to-sentence-case' import ZoneSelect from './ZoneSelect' export default class EditorInput extends Component { - static propTypes = { activeEntity: PropTypes.object, updateActiveEntity: PropTypes.func, diff --git a/lib/editor/components/ExceptionDate.js b/lib/editor/components/ExceptionDate.js index 7215f8a5e..33a04e080 100644 --- a/lib/editor/components/ExceptionDate.js +++ b/lib/editor/components/ExceptionDate.js @@ -5,7 +5,6 @@ import DateTimeField from 'react-bootstrap-datetimepicker' import moment from 'moment' export default class ExceptionDate extends Component { - static propTypes = { activeEntity: PropTypes.object, updateActiveEntity: PropTypes.func, diff --git a/lib/editor/components/FareRuleSelections.js b/lib/editor/components/FareRuleSelections.js index 268ae4a37..2ad5b2a31 100644 --- a/lib/editor/components/FareRuleSelections.js +++ b/lib/editor/components/FareRuleSelections.js @@ -6,7 +6,6 @@ import { getEntityName } from '../util/gtfs' import ZoneSelect from './ZoneSelect' export default class FareRuleSelections extends Component { - static propTypes = { rule: PropTypes.object, tableData: PropTypes.object, diff --git a/lib/editor/components/pattern/EditSchedulePanel.js b/lib/editor/components/pattern/EditSchedulePanel.js index 9f13939c0..f6e4138a4 100644 --- a/lib/editor/components/pattern/EditSchedulePanel.js +++ b/lib/editor/components/pattern/EditSchedulePanel.js @@ -5,7 +5,6 @@ import {Button, FormGroup, ControlLabel, ButtonGroup, DropdownButton, MenuItem} const DIRECTIONS = [0, 1] export default class EditSchedulePanel extends Component { - static propTypes = { feedSource: PropTypes.object } diff --git a/lib/editor/components/pattern/PatternStopsPanel.js b/lib/editor/components/pattern/PatternStopsPanel.js index 6432aff29..a4f202f6a 100644 --- a/lib/editor/components/pattern/PatternStopsPanel.js +++ b/lib/editor/components/pattern/PatternStopsPanel.js @@ -1,7 +1,7 @@ import Icon from '@conveyal/woonerf/components/icon' import Pure from '@conveyal/woonerf/components/pure' import React, {PropTypes} from 'react' -import { Button, ButtonToolbar } from 'react-bootstrap' +import { Button } from 'react-bootstrap' import PatternStopContainer from './PatternStopContainer' import VirtualizedEntitySelect from '../VirtualizedEntitySelect' From 0b5e11296532024af4d09b4d627f3e164cd76c64 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 13:56:40 -0400 Subject: [PATCH 070/265] fix(actions): fix bad find/replace for secureFetch --- lib/admin/actions/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/actions/admin.js b/lib/admin/actions/admin.js index f5ef94f91..d1e32d7fe 100644 --- a/lib/admin/actions/admin.js +++ b/lib/admin/actions/admin.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/util.js' +import { secureFetch } from '../../common/util/actions' export function requestingUsers () { return { From ee3fd2b7c2441c2c391e6e47baabad06f6b57223 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Mon, 17 Apr 2017 14:08:45 -0400 Subject: [PATCH 071/265] fix(actions): fix path for common actions --- lib/admin/actions/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/admin/actions/admin.js b/lib/admin/actions/admin.js index d1e32d7fe..2765fda0a 100644 --- a/lib/admin/actions/admin.js +++ b/lib/admin/actions/admin.js @@ -1,4 +1,4 @@ -import { secureFetch } from '../../common/util/actions' +import { secureFetch } from '../../common/actions' export function requestingUsers () { return { From f5230088e28ddec1adf783d889f3a00b7fb60fd9 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Wed, 19 Apr 2017 16:18:53 -0400 Subject: [PATCH 072/265] fix(ControlPoint): fix remove control point and remove stop bug --- .gitignore | 1 + lib/editor/actions/map/index.js | 8 +++--- lib/editor/actions/map/stopStrategies.js | 2 +- lib/editor/components/map/ControlPoint.js | 28 +++++++++++++------ .../components/map/ControlPointsLayer.js | 19 ++++++++----- lib/editor/components/map/EditorMap.js | 19 +++++++------ lib/editor/components/map/PatternsLayer.js | 2 ++ lib/editor/reducers/data.js | 4 --- lib/editor/reducers/settings.js | 21 ++++++++------ lib/editor/util/gtfs.js | 16 ++++------- lib/editor/util/map.js | 12 +++++++- 11 files changed, 78 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 617404234..c93b7aad3 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ assets # Secret config files env.yml env.yml-original +.env diff --git a/lib/editor/actions/map/index.js b/lib/editor/actions/map/index.js index faa90c227..06f9adb94 100644 --- a/lib/editor/actions/map/index.js +++ b/lib/editor/actions/map/index.js @@ -4,8 +4,8 @@ import point from 'turf-point' import lineDistance from 'turf-line-distance' import ll from '@conveyal/lonlat' -import { updateActiveGtfsEntity, saveActiveGtfsEntity } from '../active' -import { handlePatternEdit, getControlPointSnap } from '../../util/map' +import {updateActiveGtfsEntity, saveActiveGtfsEntity} from '../active' +import {getControlPointSnap, handlePatternEdit, newControlPoint} from '../../util/map' import {polyline as getPolyline} from '../../../scenario-editor/utils/valhalla' export function updateMapSetting (props) { @@ -65,7 +65,7 @@ export function constructControlPoint (pattern, latlng, controlPoints) { // measure line segment const distTraveled = lineDistance(lineSegment, 'meters') - const controlPoint = {distance: distTraveled, point: clickPoint} + const controlPoint = newControlPoint(distTraveled, clickPoint) // find splice index based on shape dist traveled let index = 0 @@ -95,7 +95,7 @@ export function removeControlPoint (pattern, index, begin, end) { // update pattern dispatch(updateActiveGtfsEntity(pattern, 'trippattern', {shape: {type: 'LineString', coordinates}})) // remove controlPoint - dispatch(removingControlPoint(index)) + dispatch(removingControlPoint(pattern, index, begin, end)) } } diff --git a/lib/editor/actions/map/stopStrategies.js b/lib/editor/actions/map/stopStrategies.js index 56254a393..a207cd20a 100644 --- a/lib/editor/actions/map/stopStrategies.js +++ b/lib/editor/actions/map/stopStrategies.js @@ -212,7 +212,7 @@ export function removeStopFromPattern (pattern, stop, index, controlPoints) { const coordinates = await handlePatternEdit(null, begin, end, pattern, followStreets, patternCoordinates) const patternStops = [...pattern.patternStops] patternStops.splice(index, 1) - dispatch(updateActiveGtfsEntity(pattern, 'trippattern', {patternStops: patternStops, shape: {type: 'LineString', coordinates: coordinates}})) + dispatch(updateActiveGtfsEntity(pattern, 'trippattern', {patternStops, shape: {type: 'LineString', coordinates}})) dispatch(saveActiveGtfsEntity('trippattern')) } } diff --git a/lib/editor/components/map/ControlPoint.js b/lib/editor/components/map/ControlPoint.js index 48701385c..3738d4697 100644 --- a/lib/editor/components/map/ControlPoint.js +++ b/lib/editor/components/map/ControlPoint.js @@ -6,6 +6,7 @@ import { shallowEqual } from 'react-pure-render' export default class ControlPoint extends Component { static propTypes = { activePattern: PropTypes.object, + controlPoint: PropTypes.object, icon: PropTypes.object, index: PropTypes.number, handleControlPointDrag: PropTypes.func, @@ -19,39 +20,49 @@ export default class ControlPoint extends Component { updateControlPoint: PropTypes.func, updatePatternCoordinates: PropTypes.func } + state = { latlng: null } + shouldComponentUpdate (nextProps) { // TODO: fix this hack that keeps unknown position change (perhaps react-leaflet is updating it) from triggering // a component update, which funks with the position of the marker return !shallowEqual(nextProps.controlPoint.snap, this.props.controlPoint.snap) } + _onClick = (e) => { - console.log('control point clicked', e) + const {permanent, removeControlPoint, activePattern, index, previous, next} = this.props // only remove controlPoint if it's not based on pattern stop (i.e., has permanent prop) - if (!this.props.permanent) { - this.props.removeControlPoint(this.props.activePattern, this.props.index, this.props.previous, this.props.next) + if (!permanent) { + removeControlPoint(activePattern, index, previous, next) } } + handleDrag = () => { + const {activePattern, handleControlPointDrag, next, previous} = this.props const latlng = this.refs.marker.leafletElement.getLatLng() this.setState({latlng}) - this.props.handleControlPointDrag(latlng, this.props.previous, this.props.next, this.props.activePattern) + handleControlPointDrag(latlng, previous, next, activePattern) } + _onDragEnd = (e) => { + const {activePattern, controlPoint, handleControlPointDragEnd, index} = this.props this.setState({latlng: null}) - this.props.handleControlPointDragEnd(this.props.index, this.props.controlPoint, e, this.props.activePattern) + handleControlPointDragEnd(index, controlPoint, e, activePattern) } + render () { if (this.props.index === 1) console.log(this.props) - const { position, icon } = this.props + const {position, icon} = this.props // keep track of position in state because we need this to be cleared once the user has // stopped dragging the marker, at which point this.state.latlng will be null and the marker // will "snap" back to the polyline const {latlng} = this.state - const markerPosition = latlng ? [latlng.lat, latlng.lng] : position + const markerPosition = latlng + ? [latlng.lat, latlng.lng] + : position return ( + color='black' /> ) } } diff --git a/lib/editor/components/map/ControlPointsLayer.js b/lib/editor/components/map/ControlPointsLayer.js index 68c3ce1d9..006a11591 100644 --- a/lib/editor/components/map/ControlPointsLayer.js +++ b/lib/editor/components/map/ControlPointsLayer.js @@ -7,12 +7,18 @@ import ControlPoint from './ControlPoint' export default class ControlPointsLayer extends Component { static propTypes = { - stops: PropTypes.array, - handlePatternEdit: PropTypes.func, - polyline: PropTypes.object, + activePattern: PropTypes.object, + controlPoints: PropTypes.array, + editSettings: PropTypes.object, + handleControlPointDrag: PropTypes.func, handleControlPointDragEnd: PropTypes.func, onDrag: PropTypes.func, - removeControlPoint: PropTypes.func + polyline: PropTypes.object, + removeControlPoint: PropTypes.func, + stops: PropTypes.array, + updateActiveEntity: PropTypes.func, + updateControlPoint: PropTypes.func, + updatePatternCoordinates: PropTypes.func } getPrevious (index, controlPoints, pattern) { const prevControlPoint = controlPoints[index - 1] @@ -64,7 +70,7 @@ export default class ControlPointsLayer extends Component { position={[position.geometry.coordinates[1], position.geometry.coordinates[0]]} icon={icon} controlPoint={cp} - key={`controlPoint-${index}`} + key={cp.id} index={index} permanent={cp.permanent} previous={this.getPrevious(index, controlPoints, activePattern)} @@ -75,8 +81,7 @@ export default class ControlPointsLayer extends Component { removeControlPoint={removeControlPoint} updateActiveEntity={updateActiveEntity} updateControlPoint={updateControlPoint} - updatePatternCoordinates={updatePatternCoordinates} - /> + updatePatternCoordinates={updatePatternCoordinates} /> ) }) : null diff --git a/lib/editor/components/map/EditorMap.js b/lib/editor/components/map/EditorMap.js index c0462b55d..710676c65 100644 --- a/lib/editor/components/map/EditorMap.js +++ b/lib/editor/components/map/EditorMap.js @@ -63,16 +63,18 @@ export default class EditorMap extends Component { return shouldUpdate } async _mapRightClicked (e) { - switch (this.props.activeComponent) { + const {activeEntity, updateActiveEntity, activeComponent, entities, feedSource, newGtfsEntity} = this.props + switch (activeComponent) { case 'stop': - // if newly created stop is selected + // if newly created stop is already selected const stopLatLng = clickToLatLng(e.latlng) - if (this.props.activeEntity && this.props.activeEntity.id === 'new') { - this.props.updateActiveEntity(this.props.activeEntity, this.props.activeComponent, stopLatLng) - this.refs[this.props.activeEntity.id].leafletElement.setLatLng(e.latlng) - } else if (this.props.entities && this.props.entities.findIndex(e => e.id === 'new') === -1) { - const stop = await constructStop(e.latlng, this.props.feedSource.id) - this.props.newGtfsEntity(this.props.feedSource.id, this.props.activeComponent, stop) + if (activeEntity && activeEntity.id === 'new') { + updateActiveEntity(activeEntity, activeComponent, stopLatLng) + this.refs[activeEntity.id].leafletElement.setLatLng(e.latlng) + } else if (entities && entities.findIndex(e => e.id === 'new') === -1) { + // if a new stop should be constructed + const stop = await constructStop(e.latlng, feedSource.id) + newGtfsEntity(feedSource.id, activeComponent, stop) } break default: @@ -151,7 +153,6 @@ export default class EditorMap extends Component { stops={stops} activePattern={activePattern} editSettings={editSettings} - handlePatternEdit={this.props.handlePatternEdit} handleControlPointDrag={this.props.handleControlPointDrag} handleControlPointDragEnd={this.props.handleControlPointDragEnd} updateControlPoint={this.props.updateControlPoint} diff --git a/lib/editor/components/map/PatternsLayer.js b/lib/editor/components/map/PatternsLayer.js index 6cb6ff4db..07da6cc7b 100644 --- a/lib/editor/components/map/PatternsLayer.js +++ b/lib/editor/components/map/PatternsLayer.js @@ -5,10 +5,12 @@ export default class PatternsLayer extends Component { static propTypes = { route: PropTypes.object } + _onClick (pattern, isActive, controlPoints, e) { return isActive && this.props.constructControlPoint(pattern, e.latlng, controlPoints) // TODO: make clicked pattern active if non-active? } + render () { const { route, diff --git a/lib/editor/reducers/data.js b/lib/editor/reducers/data.js index 27d441cb9..c6db06735 100644 --- a/lib/editor/reducers/data.js +++ b/lib/editor/reducers/data.js @@ -52,10 +52,6 @@ const data = (state = defaultState, action) => { } return update(newState || state, { tables: {[component]: {$unshift: [activeEntity]}} - // active: { - // entity: {$set: activeEntity}, - // edited: {$set: typeof action.props !== 'undefined'} - // } }) } case 'SETTING_ACTIVE_GTFS_ENTITY': diff --git a/lib/editor/reducers/settings.js b/lib/editor/reducers/settings.js index 2ffdec3f8..619a1412e 100644 --- a/lib/editor/reducers/settings.js +++ b/lib/editor/reducers/settings.js @@ -21,7 +21,7 @@ const defaultState = { } const editSettings = (state = defaultState, action) => { - let stateUpdate, controlPoints, coordinates + let stateUpdate, controlPoints, coordinates, controlPointsCopy switch (action.type) { case 'SETTING_ACTIVE_GTFS_ENTITY': switch (action.subComponent) { @@ -126,19 +126,24 @@ const editSettings = (state = defaultState, action) => { return state } case 'REMOVE_CONTROL_POINT': - controlPoints = [...state.controlPoints[state.controlPoints.length - 1]] - controlPoints.splice(action.index, 1) + controlPointsCopy = [] + controlPoints = state.controlPoints[state.controlPoints.length - 1] + for (let i = 0; i < controlPoints.length; i++) { + controlPointsCopy.push(Object.assign({}, controlPoints[i])) + } + // const newest = update(controlPointsCopy, {[action.index]: {point: {$set: action.point}, distance: {$set: action.distance}, snap: {$set: Math.random()}}}) + controlPointsCopy.splice(action.index, 1) return update(state, { - controlPoints: {$push: [controlPoints]}, + controlPoints: {$push: [controlPointsCopy]}, actions: {$push: [action.type]} }) case 'UPDATE_CONTROL_POINT': - const newControlPoints = [] + controlPointsCopy = [] controlPoints = state.controlPoints[state.controlPoints.length - 1] - for (var i = 0; i < controlPoints.length; i++) { - newControlPoints.push(Object.assign({}, controlPoints[i])) + for (let i = 0; i < controlPoints.length; i++) { + controlPointsCopy.push(Object.assign({}, controlPoints[i])) } - const newest = update(newControlPoints, {[action.index]: {point: {$set: action.point}, distance: {$set: action.distance}, snap: {$set: Math.random()}}}) + const newest = update(controlPointsCopy, {[action.index]: {point: {$set: action.point}, distance: {$set: action.distance}, snap: {$set: Math.random()}}}) return update(state, { controlPoints: {$push: [newest]}, actions: {$push: [action.type]} diff --git a/lib/editor/util/gtfs.js b/lib/editor/util/gtfs.js index 4c497421b..31fc17da1 100644 --- a/lib/editor/util/gtfs.js +++ b/lib/editor/util/gtfs.js @@ -2,6 +2,8 @@ import along from 'turf-along' import lineDistance from 'turf-line-distance' import { latLngBounds } from 'leaflet' +import {newControlPoint} from './map' + export const componentList = ['route', 'stop', 'fare', 'feedinfo', 'calendar', 'scheduleexception', 'agency'] export const subComponentList = ['trippattern'] export const subSubComponentList = ['timetable'] @@ -121,18 +123,10 @@ export function getControlPoints (pattern, snapToStops) { ? (pattern.patternStops[index + 1].shapeDistTraveled + ps.shapeDistTraveled) / 2 : lineDistance(pattern.shape, 'meters') const point = pattern.shape && along(pattern.shape, distance, 'meters') - const controlPoint = { - point, - distance: distance, - permanent: true - } + const PERMANENT = true + const controlPoint = newControlPoint(distance, point, PERMANENT) const stopPoint = pattern.shape && along(pattern.shape, ps.shapeDistTraveled, 'meters') - const stopControl = { - point: stopPoint, - permanent: true, - distance: ps.shapeDistTraveled, - ...ps - } + const stopControl = newControlPoint(ps.shapeDistTraveled, stopPoint, PERMANENT, ps) if (snapToStops) { stopControl.hidden = true } diff --git a/lib/editor/util/map.js b/lib/editor/util/map.js index 229356294..31772ddeb 100644 --- a/lib/editor/util/map.js +++ b/lib/editor/util/map.js @@ -46,6 +46,16 @@ export function zoomToEntity (entity, map) { } } +export function newControlPoint (distance, point, permanent = false, props = {}) { + return { + distance, + id: generateUID(), + permanent, + point, + ...props + } +} + export async function constructStop (latlng, feedId) { const stopLatLng = clickToLatLng(latlng) const result = await reverse(latlng) @@ -116,7 +126,7 @@ export async function handlePatternEdit (position, begin, end, pattern, followSt let originalLatLngs let originalEndPoint let from, to - const coords = ll.toCoordinates(position) + const coords = position && ll.toCoordinates(position) // set from, to for endPoint if we have position if (begin && position) { From 60476f17734c849b2ea507f5914137c098b6ea0c Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 20 Apr 2017 14:03:55 -0400 Subject: [PATCH 073/265] fix(project-reducer): check isochrone features before mapping --- lib/manager/reducers/projects.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/manager/reducers/projects.js b/lib/manager/reducers/projects.js index a2954ec60..96be39760 100644 --- a/lib/manager/reducers/projects.js +++ b/lib/manager/reducers/projects.js @@ -286,10 +286,12 @@ const projects = (state = { versionIndex = state.all[projectIndex].feedSources[sourceIndex].feedVersions.findIndex(v => v.id === action.feedVersion.id) const { fromLat, fromLon, date, fromTime, toTime } = action action.isochrones.properties = { fromLat, fromLon, date, fromTime, toTime } - action.isochrones.features = action.isochrones.features.map(f => { - f.type = 'Feature' - return f - }) + action.isochrones.features = action.isochrones && action.isochrones.features + ? action.isochrones.features.map(f => { + f.type = 'Feature' + return f + }) + : null return update(state, { all: { [projectIndex]: { From 0f7e6d83007ef0431d98d85bdb2652c1d3265888 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Thu, 20 Apr 2017 15:32:47 -0400 Subject: [PATCH 074/265] refactor(FeedVersionReport): refactor large component into smaller files --- i18n/english.yml | 2 +- i18n/espanol.yml | 2 +- i18n/francais.yml | 2 +- lib/manager/components/FeedVersionReport.js | 338 ------------------ .../version/FeedVersionAccessibility.js | 82 +++++ .../components/version/FeedVersionDetails.js | 86 +++++ .../components/version/FeedVersionMap.js | 61 ++++ .../{ => version}/FeedVersionNavigator.js | 10 +- .../components/version/FeedVersionReport.js | 111 ++++++ .../components/version/FeedVersionTabs.js | 123 +++++++ .../{ => version}/FeedVersionViewer.js | 104 +++--- 11 files changed, 514 insertions(+), 407 deletions(-) delete mode 100644 lib/manager/components/FeedVersionReport.js create mode 100644 lib/manager/components/version/FeedVersionAccessibility.js create mode 100644 lib/manager/components/version/FeedVersionDetails.js create mode 100644 lib/manager/components/version/FeedVersionMap.js rename lib/manager/components/{ => version}/FeedVersionNavigator.js (96%) create mode 100644 lib/manager/components/version/FeedVersionReport.js create mode 100644 lib/manager/components/version/FeedVersionTabs.js rename lib/manager/components/{ => version}/FeedVersionViewer.js (78%) diff --git a/i18n/english.yml b/i18n/english.yml index c869bf25b..9f7c37cde 100644 --- a/i18n/english.yml +++ b/i18n/english.yml @@ -127,7 +127,7 @@ FeedVersionViewer: feed: Feed delete: Delete confirmDelete: Are you sure you want to delete this version? This cannot be undone. -FeedVersionReport: +FeedVersionTabs: agencyCount: Agency Count routeCount: Route Count tripCount: Trip Count diff --git a/i18n/espanol.yml b/i18n/espanol.yml index 2e466e2b8..2df47471c 100644 --- a/i18n/espanol.yml +++ b/i18n/espanol.yml @@ -120,7 +120,7 @@ FeedVersionViewer: feed: Feed delete: Delete confirmDelete: Are you sure you want to delete this version? This cannot be undone. -FeedVersionReport: +FeedVersionTabs: agencyCount: Agency Count routeCount: Route Count tripCount: Trip Count diff --git a/i18n/francais.yml b/i18n/francais.yml index 0030970df..98a46db62 100644 --- a/i18n/francais.yml +++ b/i18n/francais.yml @@ -120,7 +120,7 @@ FeedVersionViewer: feed: Feed delete: Delete confirmDelete: Are you sure you want to delete this version? This cannot be undone. -FeedVersionReport: +FeedVersionTabs: agencyCount: Agency Count routeCount: Route Count tripCount: Trip Count diff --git a/lib/manager/components/FeedVersionReport.js b/lib/manager/components/FeedVersionReport.js deleted file mode 100644 index 8d7f6717a..000000000 --- a/lib/manager/components/FeedVersionReport.js +++ /dev/null @@ -1,338 +0,0 @@ -import Icon from '@conveyal/woonerf/components/icon' -import React, {Component, PropTypes} from 'react' -import { Row, Col, Button, Panel, ControlLabel, Label, Tabs, Tab, ButtonGroup, ListGroup, ListGroupItem } from 'react-bootstrap' -import moment from 'moment' -import numeral from 'numeral' -import Rcslider from 'rc-slider' -import area from 'turf-area' -import bboxPoly from 'turf-bbox-polygon' - -import EditableTextField from '../../common/components/EditableTextField' -import fileDownload from '../../common/util/file-download' -import ActiveGtfsMap from '../../gtfs/containers/ActiveGtfsMap' -import { VersionButtonToolbar } from './FeedVersionViewer' -import { getComponentMessages, getConfigProperty, getMessage, isModuleEnabled, isExtensionEnabled } from '../../common/util/config' -import { getProfileLink } from '../../common/util/util' -// import { downloadAsShapefile } from '../util' -import Patterns from './reporter/containers/Patterns' -import Routes from './reporter/containers/Routes' -import Stops from './reporter/containers/Stops' -import GtfsValidationSummary from './validation/GtfsValidationSummary' -import TripsChart from './validation/TripsChart' -import ActiveDateTimeFilter from './reporter/containers/ActiveDateTimeFilter' - -const dateFormat = 'MMM. DD, YYYY' -const timeFormat = 'h:mma' -const MAP_HEIGHTS = [200, 400] -const ISO_BANDS = [] -for (var i = 0; i < 24; i++) { - ISO_BANDS.push(300 * (i + 1)) -} -export default class FeedVersionReport extends Component { - static propTypes = { - version: PropTypes.object, - versions: PropTypes.array, - feedVersionIndex: PropTypes.number, - isPublic: PropTypes.bool, - hasVersions: PropTypes.bool, - feedVersionRenamed: PropTypes.func, - fetchValidationResult: PropTypes.func, - publishFeedVersion: PropTypes.func - } - constructor (props) { - super(props) - this.state = { - tab: 'feed', - mapHeight: MAP_HEIGHTS[0], - isochroneBand: 60 * 60 - } - } - getBoundsArea (bounds) { - const poly = bounds && bboxPoly([bounds.west, bounds.south, bounds.east, bounds.east]) - return poly && area(poly) - } - getVersionDates (version, validationJob) { - const text = validationJob - ? Processing feed ({validationJob.status.percentComplete}%) - : `Valid from ${moment(version.validationSummary.startDate).format(dateFormat)} to ${moment(version.validationSummary.endDate).format(dateFormat)}` - return {text} - } - getVersionDateLabel (version, validationJob) { - if (validationJob) return null - const now = +moment() - const future = version.validationSummary && version.validationSummary.startDate > now - const expired = version.validationSummary && version.validationSummary.endDate < now - return version.validationSummary - ? - : null - } - selectTab (tab) { - this.setState({tab}) - } - renderIsochroneMessage (version) { - if (version.isochrones && version.isochrones.features) { - return - Move marker or change date/time to recalculate travel shed.
- -
- } else if (version.isochrones) { - return 'Reading transport network, please try again later.' - } else { - return 'Click on map above to show travel shed for this feed.' - } - } - renderValidatorTabs (version) { - if (!isModuleEnabled('validator')) { - return null - } - const validatorTabs = [ - { - title: 'Validation', - key: 'validation', - component: { this.props.fetchValidationResult(version) }} - /> - }, - { - title: 'Accessibility', - key: 'accessibility', - component:
- - - {/* isochrone message */} -

- {this.renderIsochroneMessage(version)} -

- -
- - - Travel time - this.setState({isochroneBand: value * 60})} - step={5} - marks={{ - 15: '¼ hour', - 30: '½ hour', - 60: 1 hour, - 120: '2 hours' - }} - tipFormatter={(value) => { - return `${value} minutes` - }} - /> - - - -
- }, - { - title: 'Timeline', - key: 'timeline', - component:
- - -

Number of trips per date of service.

- -
- { this.props.fetchValidationResult(version) }} - /> -
- } - ] - return validatorTabs.map(t => ( - - {t.component} - - )) - } - render () { - const { - feedVersionRenamed, - isPublic, - isPublished, - publishFeedVersion, - validationJob, - version - } = this.props - const messages = getComponentMessages('FeedVersionReport') - - if (!version) return

{getMessage(messages, 'noVersionsExist')}

- - const versionHeader = ( -
-

- {/* Name Display / Editor */} - {validationJob - ? - : version.validationSummary.loadStatus === 'SUCCESS' && version.validationSummary.errorCount === 0 - ? - : version.validationSummary.errorCount > 0 - ? - : - } - {isPublic - ? {version.name} - : feedVersionRenamed(version, value)} /> - } - -

- - Version published {moment(version.updated).fromNow()} by {version.user ? {version.user} : '[unknown]'} - -
- ) - const tableOptions = { - striped: true, - search: true, - hover: true, - exportCSV: true, - pagination: true, - options: { - paginationShowsTotal: true, - sizePerPageList: [10, 20, 50, 100] - } - } - const countFields = ['agencyCount', 'routeCount', 'tripCount', 'stopTimesCount'] - return ( - {numeral(version.fileSize || 0).format('0 b')} zip file last modified at {version.fileTimestamp ? moment(version.fileTimestamp).format(timeFormat + ', ' + dateFormat) : 'N/A' }} - > - - - - - - - - - -

- {isExtensionEnabled('mtc') - ? - : null - } - {this.getVersionDates(version, validationJob)} - {' '} - {this.getVersionDateLabel(version, validationJob)} -

-

- {version.validationSummary && version.validationSummary.avgDailyRevenueTime - ? {Math.floor(version.validationSummary.avgDailyRevenueTime / 60 / 60 * 100) / 100} hours daily service (Tuesday) - : null - } - {version.validationSummary && version.validationSummary.bounds && getConfigProperty('application.dev') - ? {this.getBoundsArea(version.validationSummary.bounds)} square meters - : null - } -

-
- - { - if (this.state.tab !== key) { - this.selectTab(key) - } - }} - id='uncontrolled-tab-example' - bsStyle='pills' - unmountOnExit - > - - - {countFields.map(c => ( - -

{numeral(version.validationSummary[c]).format('0 a')}

-

{getMessage(messages, c)}

- - ))} -
-
- - this.selectTab(key)} - tableOptions={tableOptions} - /> - - - this.selectTab(key)} - tableOptions={tableOptions} - /> - - - this.selectTab(key)} - tableOptions={tableOptions} - /> - - {this.renderValidatorTabs(version)} -
-
-
-
- ) - } -} diff --git a/lib/manager/components/version/FeedVersionAccessibility.js b/lib/manager/components/version/FeedVersionAccessibility.js new file mode 100644 index 000000000..98779136b --- /dev/null +++ b/lib/manager/components/version/FeedVersionAccessibility.js @@ -0,0 +1,82 @@ +import Icon from '@conveyal/woonerf/components/icon' +import Rcslider from 'rc-slider' +import React, {PropTypes, Component} from 'react' +import {Row, Col, Button, ControlLabel} from 'react-bootstrap' + +import fileDownload from '../../../common/util/file-download' +import ActiveDateTimeFilter from '../reporter/containers/ActiveDateTimeFilter' + +export default class FeedVersionAccessibility extends Component { + static propTypes = { + version: PropTypes.object + } + + _downloadIsochrones = () => { + const {version} = this.props + // TODO: add shapefile download (currently shp-write does not support isochrones) + // downloadAsShapefile(version.isochrones, {folder: 'isochrones', types: {line: 'isochrones'}}) + fileDownload(JSON.stringify(version.isochrones), `isochrones_${version.feedSource.name.replace(/[^a-zA-Z0-9]/g, '_')}.json`) + } + + _formatSliderTips = (value) => { + return `${value} minutes` + } + + _renderIsochroneMessage () { + const {version} = this.props + if (version.isochrones && version.isochrones.features) { + return + Move marker or change date/time to recalculate travel shed.
+ +
+ } else if (version.isochrones) { + return 'Reading transport network, please try again later.' + } else { + return 'Click on map above to show travel shed for this feed.' + } + } + + render () { + const {version} = this.props + return ( +
+ + + {/* isochrone message */} +

+ {this._renderIsochroneMessage(version)} +

+ +
+ + + Travel time + 1 hour, + 120: '2 hours' + }} + tipFormatter={this._formatSliderTips} /> + + + +
+ ) + } +} diff --git a/lib/manager/components/version/FeedVersionDetails.js b/lib/manager/components/version/FeedVersionDetails.js new file mode 100644 index 000000000..7f5e8d149 --- /dev/null +++ b/lib/manager/components/version/FeedVersionDetails.js @@ -0,0 +1,86 @@ +import Icon from '@conveyal/woonerf/components/icon' +import moment from 'moment' +import React, {PropTypes, Component} from 'react' +import {Button, Label, ListGroupItem} from 'react-bootstrap' +import area from 'turf-area' +import bboxPoly from 'turf-bbox-polygon' + +import {getConfigProperty, isExtensionEnabled} from '../../../common/util/config' + +const dateFormat = 'MMM. DD, YYYY' + +export default class FeedVersionDetails extends Component { + static propTypes = { + version: PropTypes.object + } + + getBoundsArea (bounds) { + const poly = bounds && bboxPoly([bounds.west, bounds.south, bounds.east, bounds.east]) + return poly && area(poly) + } + + getVersionDateLabel (version, validationJob) { + if (validationJob) return null + const now = +moment() + const future = version.validationSummary && version.validationSummary.startDate > now + const expired = version.validationSummary && version.validationSummary.endDate < now + return version.validationSummary + ? + : null + } + + getVersionDates (version, validationJob) { + const text = validationJob + ? Processing feed ({validationJob.status.percentComplete}%) + : `Valid from ${moment(version.validationSummary.startDate).format(dateFormat)} to ${moment(version.validationSummary.endDate).format(dateFormat)}` + return {text} + } + + _onClickPublish = () => this.props.publishFeedVersion(this.props.version) + + render () { + const {user, validationJob, version} = this.props + const isPublished = version.id === version.feedSource.publishedVersionId + const userCanManageFeed = user.permissions.hasFeedPermission(version.feedSource.organizationId, version.feedSource.projectId, version.feedSource.id, 'manage-feed') + return ( + +

+ {isExtensionEnabled('mtc') + ? + : null + } + {this.getVersionDates(version, validationJob)} + {' '} + {this.getVersionDateLabel(version, validationJob)} +

+

+ {version.validationSummary && version.validationSummary.avgDailyRevenueTime + ? + + {' '} + {Math.floor(version.validationSummary.avgDailyRevenueTime / 60 / 60 * 100) / 100} hours daily service (Tuesday) + + : null + } + {version.validationSummary && version.validationSummary.bounds && getConfigProperty('application.dev') + ? + + {' '} + {this.getBoundsArea(version.validationSummary.bounds)} square meters + + : null + } +

+
+ ) + } +} diff --git a/lib/manager/components/version/FeedVersionMap.js b/lib/manager/components/version/FeedVersionMap.js new file mode 100644 index 000000000..9aca538ca --- /dev/null +++ b/lib/manager/components/version/FeedVersionMap.js @@ -0,0 +1,61 @@ +import React, {PropTypes, Component} from 'react' +import {Button, ButtonGroup, ListGroupItem} from 'react-bootstrap' + +import ActiveGtfsMap from '../../../gtfs/containers/ActiveGtfsMap' + +const MAP_HEIGHTS = [200, 400] + +export default class FeedVersionMap extends Component { + static propTypes = { + version: PropTypes.object + } + + state = { + mapHeight: MAP_HEIGHTS[0] + } + + render () { + const {version} = this.props + const itemStyle = { + maxHeight: `${this.state.mapHeight}px`, + overflowY: 'hidden', + padding: '0px' + } + const buttonGroupStyle = { + position: 'absolute', + zIndex: 20000, + right: 5, + top: 5 + } + return ( + + {/* Map size buttons */} + + + + + {/* Primary map component */} + + + ) + } +} diff --git a/lib/manager/components/FeedVersionNavigator.js b/lib/manager/components/version/FeedVersionNavigator.js similarity index 96% rename from lib/manager/components/FeedVersionNavigator.js rename to lib/manager/components/version/FeedVersionNavigator.js index 9261a56a0..f37647b01 100644 --- a/lib/manager/components/FeedVersionNavigator.js +++ b/lib/manager/components/version/FeedVersionNavigator.js @@ -4,11 +4,11 @@ import { Row, Col, ButtonGroup, ButtonToolbar, DropdownButton, MenuItem, Button, import { browserHistory } from 'react-router' import { LinkContainer } from 'react-router-bootstrap' -import { isModuleEnabled, getComponentMessages, getMessage } from '../../common/util/config' -import { isValidZipFile } from '../../common/util/util' +import { isModuleEnabled, getComponentMessages, getMessage } from '../../../common/util/config' +import { isValidZipFile } from '../../../common/util/util' import FeedVersionViewer from './FeedVersionViewer' -import ConfirmModal from '../../common/components/ConfirmModal' -import SelectFileModal from '../../common/components/SelectFileModal' +import ConfirmModal from '../../../common/components/ConfirmModal' +import SelectFileModal from '../../../common/components/SelectFileModal' export default class FeedVersionNavigator extends Component { static propTypes = { @@ -79,7 +79,7 @@ export default class FeedVersionNavigator extends Component { } else if (hasVersions && versions.length >= feedVersionIndex) { version = sortedVersions[feedVersionIndex - 1] } else { - console.log(`Error version ${feedVersionIndex} does not exist`) + // console.log(`Error version ${feedVersionIndex} does not exist`) } return ( diff --git a/lib/manager/components/version/FeedVersionReport.js b/lib/manager/components/version/FeedVersionReport.js new file mode 100644 index 000000000..dc5241004 --- /dev/null +++ b/lib/manager/components/version/FeedVersionReport.js @@ -0,0 +1,111 @@ +import Icon from '@conveyal/woonerf/components/icon' +import React, {Component, PropTypes} from 'react' +import {Panel, ListGroup} from 'react-bootstrap' +import moment from 'moment' +import numeral from 'numeral' + +import EditableTextField from '../../../common/components/EditableTextField' +import FeedVersionDetails from './FeedVersionDetails' +import FeedVersionMap from './FeedVersionMap' +import FeedVersionTabs from './FeedVersionTabs' +import { VersionButtonToolbar } from './FeedVersionViewer' +import { getProfileLink } from '../../../common/util/util' + +const dateFormat = 'MMM. DD, YYYY' +const timeFormat = 'h:mma' + +export default class FeedVersionReport extends Component { + static propTypes = { + version: PropTypes.object, + versions: PropTypes.array, + feedVersionIndex: PropTypes.number, + isPublic: PropTypes.bool, + hasVersions: PropTypes.bool, + feedVersionRenamed: PropTypes.func, + fetchValidationResult: PropTypes.func, + publishFeedVersion: PropTypes.func, + user: PropTypes.object + } + + // TODO: move tab and isochroneBand to redux store + state = { + tab: 'feed', + isochroneBand: 60 * 60 + } + + selectTab = (tab) => this.setState({tab}) + + _onVersionNameChange = (value) => this.props.feedVersionRenamed(this.props.version, value) + + _onChangeIsochroneBand = (value) => this.setState({isochroneBand: value * 60}) + + render () { + const { + isPublic, + validationJob, + version + } = this.props + const versionLastModified = version.fileTimestamp + ? moment(version.fileTimestamp).format(timeFormat + ', ' + dateFormat) + : 'N/A' + const userLink = version.user + ? {version.user} + : '[unknown]' + return ( + +

+ {/* Name Display / Editor */} + {validationJob + ? + : version.validationSummary.loadStatus === 'SUCCESS' && version.validationSummary.errorCount === 0 + ? + : version.validationSummary.errorCount > 0 + ? + : + } + {isPublic + ? {version.name} + : + } + +

+ + + {' '} + Version published {moment(version.updated).fromNow()} by {userLink} + +
+ } + footer={ + + + {' '} + {numeral(version.fileSize || 0).format('0 b')} zip file last modified at {versionLastModified} + }> + + + + + + + ) + } +} diff --git a/lib/manager/components/version/FeedVersionTabs.js b/lib/manager/components/version/FeedVersionTabs.js new file mode 100644 index 000000000..e1d98a04a --- /dev/null +++ b/lib/manager/components/version/FeedVersionTabs.js @@ -0,0 +1,123 @@ +import React, {PropTypes, Component} from 'react' +import { Row, Col, Tabs, Tab, ListGroupItem } from 'react-bootstrap' +import numeral from 'numeral' + +import Patterns from '../reporter/containers/Patterns' +import Routes from '../reporter/containers/Routes' +import Stops from '../reporter/containers/Stops' +import GtfsValidationSummary from '../validation/GtfsValidationSummary' +import TripsChart from '../validation/TripsChart' +import FeedVersionAccessibility from './FeedVersionAccessibility' +import {getComponentMessages, getMessage, isModuleEnabled} from '../../../common/util/config' + +export default class FeedVersionTabs extends Component { + static propTypes = { + version: PropTypes.object + } + + _fetchValidationResult = () => this.props.fetchValidationResult(this.props.version) + + _onClickTab = (key) => { + if (this.props.tab !== key) { + this.props.selectTab(key) + } + } + + renderValidatorTabs (version) { + if (!isModuleEnabled('validator')) { + return null + } + const VALIDATOR_TABS = [{ + title: 'Validation', + key: 'validation', + component: + }, { + title: 'Accessibility', + key: 'accessibility', + component: + }, { + title: 'Timeline', + key: 'timeline', + component:
+ + +

Number of trips per date of service.

+ +
+ +
+ }] + return VALIDATOR_TABS.map(t => ( + + {t.component} + + )) + } + + render () { + const {version} = this.props + const messages = getComponentMessages('FeedVersionTabs') + const tableOptions = { + striped: true, + search: true, + hover: true, + exportCSV: true, + pagination: true, + options: { + paginationShowsTotal: true, + sizePerPageList: [10, 20, 50, 100] + } + } + const countFields = ['agencyCount', 'routeCount', 'tripCount', 'stopTimesCount'] + return ( + + {/* Tabs - Summary, Routes, Patterns, Stops, (Validation, Accessibility, Timeline) */} + + + + {countFields.map(c => ( + +

{numeral(version.validationSummary[c]).format('0 a')}

+

{getMessage(messages, c)}

+ + ))} +
+
+ + + + + + + + + + {this.renderValidatorTabs(version)} +
+
+ ) + } +} diff --git a/lib/manager/components/FeedVersionViewer.js b/lib/manager/components/version/FeedVersionViewer.js similarity index 78% rename from lib/manager/components/FeedVersionViewer.js rename to lib/manager/components/version/FeedVersionViewer.js index 59abf45d5..8966003d5 100644 --- a/lib/manager/components/FeedVersionViewer.js +++ b/lib/manager/components/version/FeedVersionViewer.js @@ -4,12 +4,12 @@ import { Row, Col, Button, Panel, Label, Glyphicon, ButtonToolbar, ListGroup, Li import moment from 'moment' import { LinkContainer } from 'react-router-bootstrap' -import GtfsValidationViewer from './validation/GtfsValidationViewer' +import GtfsValidationViewer from '../validation/GtfsValidationViewer' import FeedVersionReport from './FeedVersionReport' -import NotesViewer from './NotesViewer' -import ConfirmModal from '../../common/components/ConfirmModal' -import ActiveGtfsPlusVersionSummary from '../../gtfsplus/containers/ActiveGtfsPlusVersionSummary' -import { isModuleEnabled, getComponentMessages, getMessage } from '../../common/util/config' +import NotesViewer from '../NotesViewer' +import ConfirmModal from '../../../common/components/ConfirmModal' +import ActiveGtfsPlusVersionSummary from '../../../gtfsplus/containers/ActiveGtfsPlusVersionSummary' +import {isModuleEnabled, getComponentMessages, getMessage} from '../../../common/util/config' export default class FeedVersionViewer extends Component { static propTypes = { @@ -18,23 +18,22 @@ export default class FeedVersionViewer extends Component { versions: PropTypes.array, feedVersionIndex: PropTypes.number, versionSection: PropTypes.string, - isPublic: PropTypes.bool, hasVersions: PropTypes.bool, listView: PropTypes.bool, - newNotePosted: PropTypes.func, notesRequested: PropTypes.func, fetchValidationResult: PropTypes.func, downloadFeedClicked: PropTypes.func, loadFeedVersionForEditing: PropTypes.func, + user: PropTypes.object, validationJob: PropTypes.object } render () { const { - feedSource, feedVersionIndex, fetchValidationResult, + listView, newNotePosted, notesRequested, validationJob, @@ -46,7 +45,7 @@ export default class FeedVersionViewer extends Component { if (!version) return

{getMessage(messages, 'noVersionsExist')}

- if (this.props.listView) { + if (listView) { // List view of feed versions return ( @@ -58,58 +57,41 @@ export default class FeedVersionViewer extends Component { ) } - - switch (this.props.versionSection) { - // case 'validation': - // return ( - // - // ) - default: - return ( - - - - - - {!versionSection - ? - : versionSection === 'issues' - ? { fetchValidationResult(version) }} - /> - : versionSection === 'gtfsplus' && isModuleEnabled('gtfsplus') - ? - : versionSection === 'comments' - ? { notesRequested() }} - newNotePosted={(note) => { newNotePosted(note) }} - /> - : null - } - - - ) - } + return ( + + + + + + {!versionSection + ? + : versionSection === 'issues' + ? { fetchValidationResult(version) }} /> + : versionSection === 'gtfsplus' && isModuleEnabled('gtfsplus') + ? + : versionSection === 'comments' + ? { notesRequested() }} + newNotePosted={(note) => { newNotePosted(note) }} /> + : null + } + + + ) } } From 2a307fb33a1ae4546b7ad83d19150934fe42dbe3 Mon Sep 17 00:00:00 2001 From: Landon Reed Date: Fri, 21 Apr 2017 11:17:08 -0400 Subject: [PATCH 075/265] refactor(editor-components): factor out anon functions --- lib/editor/components/CreateSnapshotModal.js | 16 ++- .../components/EditorFeedSourcePanel.js | 4 +- lib/editor/components/EditorHelpModal.js | 58 ++++---- lib/editor/components/EntityDetailsHeader.js | 3 +- lib/editor/components/EntityList.js | 37 +++-- lib/editor/components/EntityListButtons.js | 133 +++++++++++------- .../components/EntityListSecondaryActions.js | 62 ++++---- 7 files changed, 186 insertions(+), 127 deletions(-) diff --git a/lib/editor/components/CreateSnapshotModal.js b/lib/editor/components/CreateSnapshotModal.js index c70a0f99c..562b42448 100644 --- a/lib/editor/components/CreateSnapshotModal.js +++ b/lib/editor/components/CreateSnapshotModal.js @@ -13,7 +13,7 @@ export default class CreateSnapshotModal extends Component { } } - close () { + close = () => { this.setState({ showModal: false }) @@ -25,7 +25,7 @@ export default class CreateSnapshotModal extends Component { }) } - ok () { + ok = () => { const name = ReactDOM.findDOMNode(this.refs.name).value const comment = ReactDOM.findDOMNode(this.refs.comment).value this.props.onOkClicked(name, comment) @@ -52,8 +52,16 @@ export default class CreateSnapshotModal extends Component { - - + + ) diff --git a/lib/editor/components/EditorFeedSourcePanel.js b/lib/editor/components/EditorFeedSourcePanel.js index 715f466ab..c3ed88dd3 100644 --- a/lib/editor/components/EditorFeedSourcePanel.js +++ b/lib/editor/components/EditorFeedSourcePanel.js @@ -31,6 +31,8 @@ export default class EditorFeedSourcePanel extends Component { this.props.createSnapshot(this.props.feedSource, name, comment) } + _openModal = () => this.refs.snapshotModal.open() + _onLoadVersion = () => { const {feedSource} = this.props const version = feedSource.feedVersions[feedSource.feedVersions.length - 1] @@ -116,7 +118,7 @@ export default class EditorFeedSourcePanel extends Component { block bsStyle='primary' style={{marginBottom: '20px'}} - onClick={() => this.refs.snapshotModal.open()}> + onClick={this._openModal}> Take snapshot of latest changes {getMessage(this.messages, 'help.title')}}> diff --git a/lib/editor/components/EditorHelpModal.js b/lib/editor/components/EditorHelpModal.js index b1cec30c0..a5c45d869 100644 --- a/lib/editor/components/EditorHelpModal.js +++ b/lib/editor/components/EditorHelpModal.js @@ -12,63 +12,69 @@ export default class EditorHelpModal extends Component { hideTutorial: this.props.hideTutorial } } - close () { + + _onToggleTutorial = () => this.setState({hideTutorial: !this.state.hideTutorial}) + + close = () => { if (this.state.hideTutorial !== this.props.hideTutorial) { this.props.setTutorialHidden(!this.props.hideTutorial) } this.setState({ showModal: false }) } + open () { this.setState({ showModal: true }) } + render () { if (!this.props.show) { return null } + const {Body, Footer, Header, Title} = Modal + const {Caption, Item} = Carousel return ( this.close()} - bsSize='large' - > - - Welcome to the GTFS Editor - - + show={this.state.showModal} + onHide={this.close} + bsSize='large'> +
+ Welcome to the GTFS Editor +
+ - + 900x500 - +

First slide label

Nulla vitae elit libero, a pharetra augue mollis interdum.

-
-
- + + + 900x500 - +

Second slide label

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

-
-
- + + + 900x500 - +

Third slide label

Praesent commodo cursus magna, vel scelerisque nisl consectetur.

-
-
+ +
-
- + +
this.setState({hideTutorial: !this.state.hideTutorial})} - > + onChange={this._onToggleTutorial}> Do not show when editor opens - - + +
) } diff --git a/lib/editor/components/EntityDetailsHeader.js b/lib/editor/components/EntityDetailsHeader.js index 8f0fef413..f2b890676 100644 --- a/lib/editor/components/EntityDetailsHeader.js +++ b/lib/editor/components/EntityDetailsHeader.js @@ -227,8 +227,7 @@ export default class EntityDetailsHeader extends Component { eventKey={'rules'} disabled={!activeEntity || activeEntity.id === 'new'} active={editFareRules} - onClick={this._showFareRules} - > + onClick={this._showFareRules}> Rules diff --git a/lib/editor/components/EntityList.js b/lib/editor/components/EntityList.js index b36a99d14..ff64b8626 100644 --- a/lib/editor/components/EntityList.js +++ b/lib/editor/components/EntityList.js @@ -10,16 +10,17 @@ import {getEntityName} from '../util/gtfs' export default class EntityList extends Component { static propTypes = { - feedSource: PropTypes.object, - entities: PropTypes.array, + activeComponent: PropTypes.string.isRequired, activeEntity: PropTypes.object, activeEntityId: PropTypes.string, - width: PropTypes.number.isRequired, - setActiveEntity: PropTypes.func.isRequired, - updateActiveEntity: PropTypes.func.isRequired, deleteEntity: PropTypes.func.isRequired, + enterTimetableEditor: PropTypes.func, + entities: PropTypes.array, + feedSource: PropTypes.object, newGtfsEntity: PropTypes.func.isRequired, - activeComponent: PropTypes.string.isRequired + setActiveEntity: PropTypes.func.isRequired, + updateActiveEntity: PropTypes.func.isRequired, + width: PropTypes.number.isRequired } state = {} @@ -27,21 +28,26 @@ export default class EntityList extends Component { _onResize = () => { this.setState({width: window.innerWidth, height: window.innerHeight}) } + componentWillMount () { this._onResize() } + componentDidMount () { window.addEventListener('resize', this._onResize) } + componentWillUnmount () { window.removeEventListener('resize', this._onResize) } + componentWillReceiveProps (nextProps) { let fromIndex, toIndex if (nextProps.activeComponent !== this.props.activeComponent) { this.setState({fromIndex, toIndex}) } } + shouldComponentUpdate (nextProps) { // simply running shallowEqual on all props does not give us the performance we need here // (especially with many, many stops) @@ -51,6 +57,7 @@ export default class EntityList extends Component { !shallowEqual(nextProps.activeComponent, this.props.activeComponent) || !shallowEqual(nextProps.feedSource, this.props.feedSource) } + _optionRenderer = ({ focusedOption, focusedOptionIndex, focusOption, key, labelKey, option, options, selectValue, style, valueArray }) => { const className = ['VirtualizedSelectOption'] if (option === focusedOption) { @@ -89,6 +96,7 @@ export default class EntityList extends Component {
) } + _getRowStyle (index, list) { const activeColor = '#F2F2F2' const rowStyle = { @@ -101,9 +109,15 @@ export default class EntityList extends Component { } return rowStyle } + updateIndexes = (fromIndex, toIndex) => { this.setState({fromIndex, toIndex}) } + + _onClickTimetableEditor = () => this.props.enterTimetableEditor() + + _onClickNew = () => this.props.newGtfsEntity(this.props.feedSource.id, this.props.activeComponent) + _onRowClick (index, list, shiftKey) { let fromIndex, toIndex if (shiftKey && this.props.activeEntity && !list[index].isActive) { @@ -133,7 +147,6 @@ export default class EntityList extends Component { hasRoutes, feedSource, entities, - newGtfsEntity, enterTimetableEditor } = this.props const sidePadding = '5px' @@ -190,18 +203,14 @@ export default class EntityList extends Component {
return (
+ style={{paddingRight: sidePadding, marginBottom: '5px', height: '80px', paddingTop: sidePadding}}> enterTimetableEditor()} + onClick={this._onClickTimetableEditor} > Edit schedules diff --git a/lib/editor/components/EntityListButtons.js b/lib/editor/components/EntityListButtons.js index 426b932fd..b486b7c2b 100644 --- a/lib/editor/components/EntityListButtons.js +++ b/lib/editor/components/EntityListButtons.js @@ -1,38 +1,95 @@ import Icon from '@conveyal/woonerf/components/icon' -import React, {Component} from 'react' +import React, {Component, PropTypes} from 'react' import { ButtonToolbar, Tooltip, OverlayTrigger, Button } from 'react-bootstrap' export default class EntityListButtons extends Component { - render () { + static propTypes = { + activeComponent: PropTypes.string, + activeEntity: PropTypes.object, + cloneEntity: PropTypes.func, + deleteEntity: PropTypes.func, + entities: PropTypes.array, + feedSource: PropTypes.object, + fromIndex: PropTypes.number, + list: PropTypes.array, + newGtfsEntity: PropTypes.func, + toIndex: PropTypes.number, + setActiveEntity: PropTypes.func, + showConfirmModal: PropTypes.func, + updateIndexes: PropTypes.func + } + + _mergeEntities = () => { + // TODO: add merge routes button + } + + _onClickClone = () => { + const {activeComponent, activeEntity, cloneEntity, feedSource} = this.props + cloneEntity(feedSource.id, activeComponent, activeEntity.id) + } + + _onClickDelete = () => { const { activeComponent, - cloneEntity, - feedSource, - showConfirmModal, + activeEntity, deleteEntity, + feedSource, + list, setActiveEntity, + showConfirmModal, + updateIndexes + } = this.props + let fromIndex, toIndex + if (activeEntity) { + showConfirmModal({ + title: `Delete ${activeComponent}?`, + body: `Are you sure you want to delete this ${activeComponent}?`, + onConfirm: () => { + deleteEntity(feedSource.id, activeComponent, activeEntity.id) + updateIndexes(fromIndex, toIndex) + setActiveEntity(feedSource.id, activeComponent) + } + }) + // deleteEntity(feedSource.id, activeComponent, activeEntity.id) + } else { + showConfirmModal({ + title: `Delete ${+toIndex - +fromIndex} ${activeComponent}s?`, + body: `Are you sure you want to delete these ${toIndex - fromIndex} ${activeComponent}s?`, + onConfirm: () => { + for (var i = 0; i < list.length; i++) { + if (list[i].isSelected) { + deleteEntity(feedSource.id, activeComponent, list[i].id) + } + } + updateIndexes(fromIndex, toIndex) + setActiveEntity(feedSource.id, activeComponent) + } + }) + } + } + + _onClickNew = () => { + this.props.newGtfsEntity(this.props.feedSource.id, this.props.activeComponent) + } + + render () { + const { + activeComponent, + activeEntity, entities, - newGtfsEntity, - toIndex, fromIndex, - activeEntity, - list, - updateIndexes + toIndex } = this.props return (
+ className='pull-right'> {activeComponent === 'route' ? Merge routes}> @@ -42,10 +99,7 @@ export default class EntityListButtons extends Component { @@ -54,36 +108,7 @@ export default class EntityListButtons extends Component { bsSize='small' disabled={!activeEntity && typeof fromIndex === 'undefined'} bsStyle='danger' - onClick={() => { - let fromIndex, toIndex - if (activeEntity) { - showConfirmModal({ - title: `Delete ${activeComponent}?`, - body: `Are you sure you want to delete this ${activeComponent}?`, - onConfirm: () => { - deleteEntity(feedSource.id, activeComponent, activeEntity.id) - updateIndexes(fromIndex, toIndex) - setActiveEntity(feedSource.id, activeComponent) - } - }) - // deleteEntity(feedSource.id, activeComponent, activeEntity.id) - } else { - showConfirmModal({ - title: `Delete ${+toIndex - +fromIndex} ${activeComponent}s?`, - body: `Are you sure you want to delete these ${toIndex - fromIndex} ${activeComponent}s?`, - onConfirm: () => { - for (var i = 0; i < list.length; i++) { - if (list[i].isSelected) { - deleteEntity(feedSource.id, activeComponent, list[i].id) - } - } - updateIndexes(fromIndex, toIndex) - setActiveEntity(feedSource.id, activeComponent) - } - }) - } - }} - > + onClick={this._onClickDelete}> @@ -94,10 +119,10 @@ export default class EntityListButtons extends Component { : }
diff --git a/lib/editor/components/EntityListSecondaryActions.js b/lib/editor/components/EntityListSecondaryActions.js index 1738ef88f..b106ed8a9 100644 --- a/lib/editor/components/EntityListSecondaryActions.js +++ b/lib/editor/components/EntityListSecondaryActions.js @@ -5,13 +5,37 @@ import VirtualizedEntitySelect from './VirtualizedEntitySelect' export default class EntityListSecondaryActions extends Component { static propTypes = { - activeComponent: PropTypes.string + activeComponent: PropTypes.string, + activeEntity: PropTypes.object, + entities: PropTypes.array, + feedSource: PropTypes.object, + setActiveEntity: PropTypes.func } + + _onChangeEntity = (value) => { + const {activeComponent, feedSource, setActiveEntity} = this.props + if (!value) { + setActiveEntity(feedSource.id, activeComponent) + } else { + setActiveEntity(feedSource.id, activeComponent, value.entity) + } + } + + _onSelectCalendar = () => { + if (this.props.activeComponent !== 'calendar') { + this.props.setActiveEntity(this.props.feedSource.id, 'calendar') + } + } + + _onSelectException = () => { + if (this.props.activeComponent !== 'scheduleexception') { + this.props.setActiveEntity(this.props.feedSource.id, 'scheduleexception') + } + } + render () { const { activeComponent, - feedSource, - setActiveEntity, activeEntity, entities } = this.props @@ -19,27 +43,20 @@ export default class EntityListSecondaryActions extends Component { case 'calendar': case 'scheduleexception': return ( -