Skip to content

Commit 06ea784

Browse files
committed
fix(pattern-editor): handle uncaught error and improve add stop by name
fix #617
1 parent 646dfcf commit 06ea784

File tree

13 files changed

+344
-123
lines changed

13 files changed

+344
-123
lines changed

lib/common/components/Loading.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import Icon from '@conveyal/woonerf/components/icon'
44
import React, {Component} from 'react'
55
import { Row, Col } from 'react-bootstrap'
66

7+
import type {Style} from '../../types'
8+
79
type Props = {
810
inline?: boolean,
911
small?: boolean,
10-
style?: {[string]: string | number}
12+
style?: Style
1113
}
1214

1315
export default class Loading extends Component<Props> {

lib/editor/components/MinuteSecondInput.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import {
88
convertMMSSStringToSeconds
99
} from '../../common/util/date-time'
1010

11+
import type {Style} from '../../types'
12+
1113
type Props = {
1214
disabled?: boolean,
1315
onChange: number => void,
1416
seconds: number,
15-
style?: {[string]: string | number}
17+
style?: Style
1618
}
1719

1820
type State = {

lib/editor/components/VirtualizedEntitySelect.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import VirtualizedSelect from 'react-virtualized-select'
55

66
import {getEntityName} from '../util/gtfs'
77

8-
import type {Entity} from '../../types'
8+
import type {Entity, Style} from '../../types'
99

1010
export type EntityOption = {
1111
entity: Entity,
@@ -20,7 +20,7 @@ type Props = {
2020
entityKey: string,
2121
onChange: any => void,
2222
optionRenderer?: Function,
23-
style?: {[string]: number | string},
23+
style?: Style,
2424
value?: any
2525
}
2626

lib/editor/components/map/AddableStop.js

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// @flow
22

3-
import Icon from '@conveyal/woonerf/components/icon'
43
import { divIcon } from 'leaflet'
54
import React, {Component} from 'react'
6-
import {Button, Dropdown, MenuItem} from 'react-bootstrap'
75
import {Marker, Popup} from 'react-leaflet'
86

97
import * as stopStrategiesActions from '../../actions/map/stopStrategies'
8+
import AddPatternStopDropdown from '../pattern/AddPatternStopDropdown'
109

1110
import type {GtfsStop, Pattern} from '../../../types'
1211

@@ -28,6 +27,7 @@ export default class AddableStop extends Component<Props> {
2827
render () {
2928
const {
3029
activePattern,
30+
addStopToPattern,
3131
stop
3232
} = this.props
3333
const color = 'blue'
@@ -40,44 +40,19 @@ export default class AddableStop extends Component<Props> {
4040
className: '',
4141
iconSize: [24, 24]
4242
})
43-
// TODO: Refactor to share code with PatternStopButtons
4443
return (
4544
<Marker
4645
position={[stop.stop_lat, stop.stop_lon]}
4746
icon={transparentBusIcon}>
4847
<Popup>
4948
<div style={{minWidth: '180px'}}>
5049
<h5>{stopName}</h5>
51-
<Dropdown
52-
id={`add-stop-dropdown`}
53-
pullRight
54-
onSelect={this._onSelectStop}>
55-
<Button
56-
bsStyle='success'
57-
onClick={this._onClickAddStopToEnd}>
58-
<Icon type='plus' /> Add stop
59-
</Button>
60-
<Dropdown.Toggle
61-
bsStyle='success' />
62-
<Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
63-
<MenuItem
64-
value={activePattern.patternStops.length}
65-
eventKey={activePattern.patternStops.length}>
66-
Add to end (default)
67-
</MenuItem>
68-
{activePattern.patternStops && activePattern.patternStops.map((stop, i) => {
69-
const index = activePattern.patternStops.length - i
70-
return (
71-
<MenuItem
72-
value={index - 1}
73-
eventKey={index - 1}
74-
key={i}>
75-
{index === 1 ? 'Add to beginning' : `Insert as stop #${index}`}
76-
</MenuItem>
77-
)
78-
})}
79-
</Dropdown.Menu>
80-
</Dropdown>
50+
<AddPatternStopDropdown
51+
activePattern={activePattern}
52+
addStopToPattern={addStopToPattern}
53+
label='Add stop'
54+
stop={stop}
55+
/>
8156
</div>
8257
</Popup>
8358
</Marker>

lib/editor/components/map/pattern-debug-lines.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {Polyline} from 'react-leaflet'
55
import lineDistance from 'turf-line-distance'
66
import lineString from 'turf-linestring'
77

8-
import {POINT_TYPE} from '../../constants'
9-
import {isValidPoint} from '../../util/map'
8+
import {PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS} from '../../constants'
9+
import {getStopControlPoints} from '../../util/map'
1010

1111
import type {ControlPoint, GtfsStop, Pattern} from '../../../types'
1212
import type {EditSettingsState} from '../../../types/reducers'
@@ -20,8 +20,6 @@ type Props = {
2020
stops: Array<GtfsStop>
2121
}
2222

23-
const DISTANCE_THRESHOLD = 50
24-
2523
/**
2624
* This react-leaflet component draws connecting lines between a pattern
2725
* geometry's anchor points (that are associated with stops) and their
@@ -44,33 +42,33 @@ export default class PatternDebugLines extends PureComponent<Props> {
4442
// i.e., whether the line should be rendered.
4543
.map((cp, index) => ({...cp, cpIndex: index}))
4644
// Filter out the user-added anchors
47-
.filter(cp => cp.pointType === POINT_TYPE.STOP)
45+
.filter(getStopControlPoints)
4846
// The remaining number should match the number of stops
4947
.map((cp, index) => {
5048
const {cpIndex, point, stopId} = cp
51-
if (!isValidPoint(point)) {
52-
return null
53-
}
49+
// If hiding inactive segments (and this control point is not along
50+
// a visible segment), do not show debug line.
5451
if (editSettings.hideInactiveSegments && (cpIndex > patternSegment + 1 || cpIndex < patternSegment - 1)) {
5552
return null
5653
}
5754
const patternStopIsActive = patternStop.index === index
5855
// Do not render if some other pattern stop is active
59-
// $FlowFixMe
60-
if ((patternStop.index || patternStop.index === 0) && !patternStopIsActive) {
56+
if (typeof patternStop.index === 'number' && !patternStopIsActive) {
6157
return null
6258
}
6359
const {coordinates: cpCoord} = point.geometry
60+
// Find stop entity for control point.
6461
const stop = stops.find(s => s.stop_id === stopId)
6562
if (!stop) {
66-
// console.warn(`Could not find stop for pattern stop index=${index} patternStop#stopId=${stopId}`)
63+
// If no stop entity found, do not attempt to draw a line to the
64+
// missing stop.
6765
return null
6866
}
6967
const coordinates = [[cpCoord[1], cpCoord[0]], [stop.stop_lat, stop.stop_lon]]
7068
const distance: number = lineDistance(lineString(coordinates), 'meters')
71-
const distanceGreaterThanThreshold = distance > DISTANCE_THRESHOLD
69+
const distanceGreaterThanThreshold = distance > PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS
7270
if (distanceGreaterThanThreshold) {
73-
console.warn(`Distance from pattern stop index=${index} to projected point is greater than ${DISTANCE_THRESHOLD} (${distance}).`)
71+
console.warn(`Distance from pattern stop index=${index} to projected point is greater than ${PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS} (${distance}).`)
7472
}
7573
return (
7674
<Polyline
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// @flow
2+
3+
import Icon from '@conveyal/woonerf/components/icon'
4+
import React, {Component} from 'react'
5+
import { Button, Dropdown, MenuItem } from 'react-bootstrap'
6+
7+
import * as stopStrategiesActions from '../../actions/map/stopStrategies'
8+
9+
import type {GtfsStop, Pattern, PatternStop, Style} from '../../../types'
10+
11+
type Props = {
12+
activePattern: Pattern,
13+
addStopToPattern: typeof stopStrategiesActions.addStopToPattern,
14+
index?: number, // current pattern stop index (if dropdown shown for current pattern stop)
15+
label?: string,
16+
patternStop?: PatternStop, // optional current pattern stop
17+
size?: string,
18+
stop: GtfsStop,
19+
style?: Style
20+
}
21+
22+
const DISABLED_MESSAGE = 'Cannot have the same stop appear consecutively in list'
23+
24+
/**
25+
* Dropdown button that adds a stop as a new pattern stop to the actively
26+
* selected pattern.
27+
*/
28+
export default class AddPatternStopDropdown extends Component<Props> {
29+
_addStop = (index?: number) => {
30+
const {activePattern, addStopToPattern, stop} = this.props
31+
addStopToPattern(activePattern, stop, index)
32+
}
33+
34+
_matchesStopAtIndex = (index: number) => {
35+
const {activePattern, stop} = this.props
36+
const patternStopAtIndex = activePattern.patternStops[index]
37+
return patternStopAtIndex && patternStopAtIndex.stopId === stop.stop_id
38+
}
39+
40+
_onAddToEnd = () => this._addStop()
41+
42+
_onSelectStop = (key: number) => this._addStop(key)
43+
44+
render () {
45+
const {activePattern, index, label, size, style} = this.props
46+
const {patternStops} = activePattern
47+
const lastIndex = patternStops.length - 1
48+
// Check that first/last stop is not already set to this stop.
49+
let addToEndDisabled = this._matchesStopAtIndex(lastIndex)
50+
let addToBeginningDisabled = this._matchesStopAtIndex(0)
51+
// Also, disable end/beginning if the current pattern stop being viewed
52+
// occupies one of these positions.
53+
if (typeof index === 'number') {
54+
addToEndDisabled = addToEndDisabled || index >= lastIndex
55+
addToBeginningDisabled = addToBeginningDisabled || index === 0
56+
}
57+
return (
58+
<Dropdown
59+
id={`add-stop-dropdown`}
60+
pullRight
61+
onSelect={this._onSelectStop}
62+
style={style}
63+
>
64+
<Button
65+
bsSize={size}
66+
bsStyle='success'
67+
disabled={addToEndDisabled}
68+
title={addToEndDisabled ? DISABLED_MESSAGE : ''}
69+
onClick={this._onAddToEnd}>
70+
<Icon type='plus' /> {label}
71+
</Button>
72+
<Dropdown.Toggle
73+
bsSize={size}
74+
bsStyle='success' />
75+
<Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
76+
<MenuItem
77+
disabled={addToEndDisabled}
78+
title={addToEndDisabled ? DISABLED_MESSAGE : ''}
79+
value={activePattern.patternStops.length}
80+
eventKey={activePattern.patternStops.length}>
81+
Add to end (default)
82+
</MenuItem>
83+
{activePattern.patternStops && activePattern.patternStops.map((s, i) => {
84+
// addIndex is in "reverse" order
85+
const addIndex = activePattern.patternStops.length - i
86+
let disableAdjacent = false
87+
// If showing for current pattern stop, do not allow adding as an
88+
// adjacent stop.
89+
if (typeof index === 'number') {
90+
disableAdjacent = (index >= addIndex - 2 && index < addIndex)
91+
}
92+
// Disable adding stop to current position or directly before/after
93+
// current position
94+
const addAtIndexDisabled = disableAdjacent ||
95+
this._matchesStopAtIndex(addIndex - 2) ||
96+
this._matchesStopAtIndex(addIndex - 1)
97+
// Skip MenuItem index is the same as the pattern stop index
98+
if (index === addIndex - 1 || addIndex === 1) {
99+
return null
100+
}
101+
return (
102+
<MenuItem
103+
disabled={addAtIndexDisabled}
104+
value={addIndex - 1}
105+
title={addAtIndexDisabled ? DISABLED_MESSAGE : ''}
106+
key={i}
107+
eventKey={addIndex - 1}>
108+
{`Insert as stop #${addIndex}`}
109+
</MenuItem>
110+
)
111+
})}
112+
<MenuItem
113+
disabled={addToBeginningDisabled}
114+
title={addToBeginningDisabled ? DISABLED_MESSAGE : ''}
115+
value={0}
116+
eventKey={0}>
117+
Add to beginning
118+
</MenuItem>
119+
</Dropdown.Menu>
120+
</Dropdown>
121+
)
122+
}
123+
}

0 commit comments

Comments
 (0)