Skip to content

Commit 5509225

Browse files
feat(RouteTypeSelect): Add route type selector with new extended GTFS route types.
1 parent 9b26e23 commit 5509225

File tree

7 files changed

+215
-19
lines changed

7 files changed

+215
-19
lines changed

gtfs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
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)."'
272272
- name: route_type
273273
required: true
274-
inputType: DROPDOWN
274+
inputType: GTFS_ROUTE_TYPE
275275
bulkEditEnabled: true
276276
options:
277277
- value: 3

lib/editor/components/EditorInput.js

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// @flow
22

3+
import moment from 'moment'
34
import * as React from 'react'
45
import {Checkbox, FormControl, FormGroup, ControlLabel, Tooltip, OverlayTrigger} from 'react-bootstrap'
5-
import Select from 'react-select'
6-
import moment from 'moment'
76
import DateTimeField from 'react-bootstrap-datetimepicker'
7+
import ReactDOM from 'react-dom'
8+
import DropdownTreeSelect from 'react-dropdown-tree-select'
89
import Dropzone from 'react-dropzone'
10+
import Select from 'react-select'
911

1012
import * as activeActions from '../actions/active'
1113
import * as editorActions from '../actions/editor'
@@ -19,6 +21,7 @@ import type {Entity, Feed, GtfsSpecField, GtfsAgency, GtfsStop} from '../../type
1921
import type {EditorTables} from '../../types/reducers'
2022

2123
import ColorField from './ColorField'
24+
import RouteTypeSelect from './RouteTypeSelect'
2225
import VirtualizedEntitySelect from './VirtualizedEntitySelect'
2326
import ZoneSelect from './ZoneSelect'
2427

@@ -102,6 +105,18 @@ export default class EditorInput extends React.Component<Props> {
102105
})
103106
}
104107

108+
_onRouteTypeChange = (currentNode: any) => {
109+
if (currentNode.value && currentNode.value !== 'extended-route-types-header') {
110+
const {activeComponent, activeEntity, field, onChange, updateActiveGtfsEntity} = this.props
111+
onChange && onChange(currentNode.value)
112+
updateActiveGtfsEntity && activeEntity && updateActiveGtfsEntity({
113+
component: activeComponent,
114+
entity: activeEntity,
115+
props: {[field.name]: currentNode.value}
116+
})
117+
}
118+
}
119+
105120
_onSelectChange = (option: any) => {
106121
const {onChange, updateActiveGtfsEntity, activeEntity, activeComponent, field} = this.props
107122
const val = option ? option.value : null
@@ -341,14 +356,6 @@ export default class EditorInput extends React.Component<Props> {
341356
</option>
342357
: null
343358
}
344-
{field.name === 'route_type'
345-
? this._renderRouteTypeOptions(field)
346-
: options.map(o => (
347-
<option value={o.value} key={o.value}>
348-
{o.text || o.value}
349-
</option>
350-
))
351-
}
352359
</FormControl>
353360
</FormGroup>
354361
)
@@ -365,6 +372,18 @@ export default class EditorInput extends React.Component<Props> {
365372
onChange={this._onSelectChange} />
366373
)
367374
}
375+
case 'GTFS_ROUTE_TYPE': {
376+
return (
377+
<FormGroup {...formProps}>
378+
{basicLabel}
379+
<RouteTypeSelect
380+
field={field}
381+
onRouteTypeChange={this._onRouteTypeChange}
382+
routeType={currentValue}
383+
/>
384+
</FormGroup>
385+
)
386+
}
368387
case 'GTFS_AGENCY':
369388
const agencies = getTableById(tableData, 'agency')
370389
const agency = agencies.find(a => a.agency_id === currentValue)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// @flow
2+
3+
import React, { Component } from 'react'
4+
import DropdownTreeSelect from 'react-dropdown-tree-select'
5+
6+
import type { GtfsSpecField } from '../../types'
7+
8+
type Props = {
9+
field: GtfsSpecField,
10+
onRouteTypeChange: any => void,
11+
routeType: string
12+
}
13+
14+
type State = {
15+
data: any
16+
}
17+
18+
/**
19+
* Creates the route types tree structure for the route type selector.
20+
*/
21+
function getRouteTypeOptions (field: GtfsSpecField, routeType: string) {
22+
const routeTypes = field.options || []
23+
const standardRouteTypes = routeTypes.filter(opt => parseInt(opt.value, 10) < 100)
24+
const extendedRouteTypes = routeTypes.filter(opt => parseInt(opt.value, 10) >= 100)
25+
26+
const routeTypeData = [
27+
// Show standard route types (< 100) at the top level
28+
...standardRouteTypes.map(({ text, value }) => ({
29+
checked: value === routeType,
30+
label: `${text} (${value})`,
31+
value
32+
})),
33+
34+
// If any, show extended route types initially collapsed
35+
...(extendedRouteTypes.length > 0 ? [{
36+
children: [
37+
// Group children by type (e.g. all 101-199 route types fall under 100-Railway)
38+
...extendedRouteTypes
39+
.filter(opt => parseInt(opt.value, 10) % 100 === 0)
40+
.map(category => {
41+
const routeTypesForCategory = extendedRouteTypes.filter(
42+
opt => opt.value > category.value && opt.value < category.value + 100
43+
)
44+
45+
return {
46+
checked: category.value === routeType,
47+
children: routeTypesForCategory.map(({ text, value }) => ({
48+
checked: value === routeType,
49+
label: `${text} (${value})`,
50+
value
51+
})),
52+
label: `${category.text} (${category.value})`,
53+
value: category.value
54+
}
55+
})
56+
],
57+
label: 'Extended GTFS Route Types',
58+
value: 'extended-route-types-header'
59+
}] : [])
60+
]
61+
62+
return routeTypeData
63+
}
64+
65+
/**
66+
* Encapsulates a drop-down selector with a hierarchical (tree) list of choices.
67+
*/
68+
export default class RouteTypeSelect extends Component<Props, State> {
69+
constructor(props: Props) {
70+
super(props)
71+
this.state = { data: getRouteTypeOptions(props.field, props.routeType) }
72+
}
73+
74+
/**
75+
* Prevent resetting of any expanded status of the tree hierarchy
76+
* (datatools polls the backend every 10 seconds and that causes new props
77+
* to be passed to the editor component).
78+
*/
79+
shouldComponentUpdate(nextProps: Props) {
80+
return nextProps.routeType !== this.props.routeType && nextProps.field !== this.props.field
81+
}
82+
83+
render() {
84+
return (
85+
<DropdownTreeSelect
86+
className='route-type-select'
87+
data={this.state.data}
88+
inlineSearchInput
89+
mode='radioSelect'
90+
onChange={this.props.onRouteTypeChange}
91+
/>
92+
);
93+
}
94+
}

lib/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
@import url(node_modules/rc-slider/assets/index.css);
1818
@import url(node_modules/react-toggle/style.css);
1919
@import url(node_modules/react-toastify/dist/ReactToastify.min.css);
20+
@import url(node_modules/react-dropdown-tree-select/dist/styles.css);
2021

2122
@import url(lib/style.css);

lib/style.css

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,3 +747,74 @@ h4.line:after {
747747
.recent-activity-inner {
748748
margin-left: 36;
749749
}
750+
751+
752+
/* Custom classes for the DropdownTreeSelect component that
753+
* override CSS from 'react-dropdown-tree-select/dist/styles.css'.
754+
* Modified from https://dowjones.github.io/react-dropdown-tree-select/#/story/with-bootstrap-styles.
755+
*/
756+
757+
.route-type-select .dropdown {
758+
display: block;
759+
}
760+
761+
/* Make some styles look like bootstrap. */
762+
.route-type-select .dropdown-trigger {
763+
background-color: #fff;
764+
border-color: #ccc!important;
765+
border-radius: 4px;
766+
color: #555;
767+
padding: 0px 6px!important;
768+
width: 100%;
769+
}
770+
771+
/* Remove border and background of tags (a "tag" is a selected item). */
772+
.route-type-select .dropdown-trigger .tag {
773+
background: none!important;
774+
border: none!important;
775+
}
776+
777+
/* Hide the search box and the mini "delete" button next to the value. */
778+
.route-type-select .dropdown-trigger .tag-item,
779+
.route-type-select .dropdown-trigger .tag-item .search,
780+
.route-type-select .dropdown-trigger .tag-remove {
781+
display: none;
782+
}
783+
784+
.route-type-select .dropdown-trigger .tag-item:first-of-type {
785+
display: inherit;
786+
}
787+
788+
/* Position the drop-down arrow. */
789+
.route-type-select .dropdown-trigger.arrow:after {
790+
position: absolute;
791+
right: 2px;
792+
}
793+
794+
/* Hide the checkboxes in the tree list. */
795+
.route-type-select .dropdown-content input[type=radio] {
796+
display: none;
797+
}
798+
799+
/* Reduce v-space between tree items. */
800+
.route-type-select .dropdown-content .node {
801+
padding: 2px;
802+
}
803+
804+
.route-type-select .toggle {
805+
font: normal normal normal 12px/1 FontAwesome;
806+
color: #555;
807+
}
808+
809+
.route-type-select .toggle.collapsed::after {
810+
content: "\f067";
811+
}
812+
813+
.route-type-select .toggle.expanded::after {
814+
content: "\f068";
815+
}
816+
817+
.route-type-select .root {
818+
padding: 0px;
819+
margin: 0px;
820+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"react-dnd": "^2.1.4",
7979
"react-dnd-html5-backend": "^2.1.2",
8080
"react-dom": "^15.4.1",
81+
"react-dropdown-tree-select": "^2.5.1",
8182
"react-dropzone": "^3.5.3",
8283
"react-ga": "^2.3.5",
8384
"react-leaflet": "1.1.0",

yarn.lock

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,11 @@ array-unique@^0.3.2:
18991899
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
19001900
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
19011901

1902+
array.partial@^1.0.5:
1903+
version "1.0.5"
1904+
resolved "https://registry.yarnpkg.com/array.partial/-/array.partial-1.0.5.tgz#f514595aacbf6a99ea20bbcffbcacc10316f0d56"
1905+
integrity sha512-nkHH1dU6JXrwppCqdUD5M1R85vihgBqhk9miq+3WFwwRayNY1ggpOT6l99PppqYQ1Hcjv2amFfUzhe25eAcYfA==
1906+
19021907
19031908
version "2.0.0"
19041909
resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.0.tgz#56a9ab1edde2a7701ed6d9166acec338919d8430"
@@ -9695,7 +9700,6 @@ npm@^6.14.8:
96959700
cmd-shim "^3.0.3"
96969701
columnify "~1.5.4"
96979702
config-chain "^1.1.12"
9698-
debuglog "*"
96999703
detect-indent "~5.0.0"
97009704
detect-newline "^2.1.0"
97019705
dezalgo "~1.0.3"
@@ -9710,7 +9714,6 @@ npm@^6.14.8:
97109714
has-unicode "~2.0.1"
97119715
hosted-git-info "^2.8.8"
97129716
iferr "^1.0.2"
9713-
imurmurhash "*"
97149717
infer-owner "^1.0.4"
97159718
inflight "~1.0.6"
97169719
inherits "^2.0.4"
@@ -9729,14 +9732,8 @@ npm@^6.14.8:
97299732
libnpx "^10.2.4"
97309733
lock-verify "^2.1.0"
97319734
lockfile "^1.0.4"
9732-
lodash._baseindexof "*"
97339735
lodash._baseuniq "~4.6.0"
9734-
lodash._bindcallback "*"
9735-
lodash._cacheindexof "*"
9736-
lodash._createcache "*"
9737-
lodash._getnative "*"
97389736
lodash.clonedeep "~4.5.0"
9739-
lodash.restparam "*"
97409737
lodash.union "~4.6.0"
97419738
lodash.uniq "~4.5.0"
97429739
lodash.without "~4.4.0"
@@ -11794,6 +11791,14 @@ react-dom@^15.4.1, react-dom@^15.6.2:
1179411791
object-assign "^4.1.0"
1179511792
prop-types "^15.5.10"
1179611793

11794+
react-dropdown-tree-select@^2.5.1:
11795+
version "2.5.1"
11796+
resolved "https://registry.yarnpkg.com/react-dropdown-tree-select/-/react-dropdown-tree-select-2.5.1.tgz#071bef6193352cda8f3177d7f56e906a8fa175e3"
11797+
integrity sha512-9sN62pIci6EZ0Fni5fqB7b5e7UG/FQ/qSwYtcc0+P3VaE1bHnEYQ9XxTSkFqpbVsy9dfq94RGaECzWcErgB+jw==
11798+
dependencies:
11799+
array.partial "^1.0.5"
11800+
react-infinite-scroll-component "^4.0.2"
11801+
1179711802
react-dropzone@^3.5.3:
1179811803
version "3.13.4"
1179911804
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-3.13.4.tgz#84da26815c40339691c49b4544c2ef7a16912ccc"
@@ -11807,6 +11812,11 @@ react-ga@^2.3.5:
1180711812
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.6.tgz#5a2e2fa78ae298e5b0a4498210eeec631ef1b562"
1180811813
integrity sha512-g04dz6zrbdHRxVaURPWT3RUbjLflh74sS6dCuhGeZupj7ii+UEt9lwTjALb2ST2w+7wAmzG1YqYlNX4yvRXe1g==
1180911814

11815+
react-infinite-scroll-component@^4.0.2:
11816+
version "4.5.3"
11817+
resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-4.5.3.tgz#008c2ec358628b490117ffc4aa6ce6982b26f8be"
11818+
integrity sha512-8O0PIeYZx0xFVS1ChLlLl/1obn64vylzXeheLsm+t0qUibmet7U6kDaKFg6jVRQJwDikWBTcyqEFFsxrbFCO5w==
11819+
1181011820
react-input-autosize@^2.1.2:
1181111821
version "2.2.1"
1181211822
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"

0 commit comments

Comments
 (0)