Skip to content

Commit

Permalink
fix(pattern-editor): handle uncaught error and improve add stop by name
Browse files Browse the repository at this point in the history
fix #617
  • Loading branch information
landonreed committed Oct 16, 2020
1 parent 646dfcf commit 06ea784
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 123 deletions.
4 changes: 3 additions & 1 deletion lib/common/components/Loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import { Row, Col } from 'react-bootstrap'

import type {Style} from '../../types'

type Props = {
inline?: boolean,
small?: boolean,
style?: {[string]: string | number}
style?: Style
}

export default class Loading extends Component<Props> {
Expand Down
4 changes: 3 additions & 1 deletion lib/editor/components/MinuteSecondInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
convertMMSSStringToSeconds
} from '../../common/util/date-time'

import type {Style} from '../../types'

type Props = {
disabled?: boolean,
onChange: number => void,
seconds: number,
style?: {[string]: string | number}
style?: Style
}

type State = {
Expand Down
4 changes: 2 additions & 2 deletions lib/editor/components/VirtualizedEntitySelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import VirtualizedSelect from 'react-virtualized-select'

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

import type {Entity} from '../../types'
import type {Entity, Style} from '../../types'

export type EntityOption = {
entity: Entity,
Expand All @@ -20,7 +20,7 @@ type Props = {
entityKey: string,
onChange: any => void,
optionRenderer?: Function,
style?: {[string]: number | string},
style?: Style,
value?: any
}

Expand Down
41 changes: 8 additions & 33 deletions lib/editor/components/map/AddableStop.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// @flow

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

import * as stopStrategiesActions from '../../actions/map/stopStrategies'
import AddPatternStopDropdown from '../pattern/AddPatternStopDropdown'

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

Expand All @@ -28,6 +27,7 @@ export default class AddableStop extends Component<Props> {
render () {
const {
activePattern,
addStopToPattern,
stop
} = this.props
const color = 'blue'
Expand All @@ -40,44 +40,19 @@ export default class AddableStop extends Component<Props> {
className: '',
iconSize: [24, 24]
})
// TODO: Refactor to share code with PatternStopButtons
return (
<Marker
position={[stop.stop_lat, stop.stop_lon]}
icon={transparentBusIcon}>
<Popup>
<div style={{minWidth: '180px'}}>
<h5>{stopName}</h5>
<Dropdown
id={`add-stop-dropdown`}
pullRight
onSelect={this._onSelectStop}>
<Button
bsStyle='success'
onClick={this._onClickAddStopToEnd}>
<Icon type='plus' /> Add stop
</Button>
<Dropdown.Toggle
bsStyle='success' />
<Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
<MenuItem
value={activePattern.patternStops.length}
eventKey={activePattern.patternStops.length}>
Add to end (default)
</MenuItem>
{activePattern.patternStops && activePattern.patternStops.map((stop, i) => {
const index = activePattern.patternStops.length - i
return (
<MenuItem
value={index - 1}
eventKey={index - 1}
key={i}>
{index === 1 ? 'Add to beginning' : `Insert as stop #${index}`}
</MenuItem>
)
})}
</Dropdown.Menu>
</Dropdown>
<AddPatternStopDropdown
activePattern={activePattern}
addStopToPattern={addStopToPattern}
label='Add stop'
stop={stop}
/>
</div>
</Popup>
</Marker>
Expand Down
24 changes: 11 additions & 13 deletions lib/editor/components/map/pattern-debug-lines.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {Polyline} from 'react-leaflet'
import lineDistance from 'turf-line-distance'
import lineString from 'turf-linestring'

import {POINT_TYPE} from '../../constants'
import {isValidPoint} from '../../util/map'
import {PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS} from '../../constants'
import {getStopControlPoints} from '../../util/map'

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

const DISTANCE_THRESHOLD = 50

/**
* This react-leaflet component draws connecting lines between a pattern
* geometry's anchor points (that are associated with stops) and their
Expand All @@ -44,33 +42,33 @@ export default class PatternDebugLines extends PureComponent<Props> {
// i.e., whether the line should be rendered.
.map((cp, index) => ({...cp, cpIndex: index}))
// Filter out the user-added anchors
.filter(cp => cp.pointType === POINT_TYPE.STOP)
.filter(getStopControlPoints)
// The remaining number should match the number of stops
.map((cp, index) => {
const {cpIndex, point, stopId} = cp
if (!isValidPoint(point)) {
return null
}
// If hiding inactive segments (and this control point is not along
// a visible segment), do not show debug line.
if (editSettings.hideInactiveSegments && (cpIndex > patternSegment + 1 || cpIndex < patternSegment - 1)) {
return null
}
const patternStopIsActive = patternStop.index === index
// Do not render if some other pattern stop is active
// $FlowFixMe
if ((patternStop.index || patternStop.index === 0) && !patternStopIsActive) {
if (typeof patternStop.index === 'number' && !patternStopIsActive) {
return null
}
const {coordinates: cpCoord} = point.geometry
// Find stop entity for control point.
const stop = stops.find(s => s.stop_id === stopId)
if (!stop) {
// console.warn(`Could not find stop for pattern stop index=${index} patternStop#stopId=${stopId}`)
// If no stop entity found, do not attempt to draw a line to the
// missing stop.
return null
}
const coordinates = [[cpCoord[1], cpCoord[0]], [stop.stop_lat, stop.stop_lon]]
const distance: number = lineDistance(lineString(coordinates), 'meters')
const distanceGreaterThanThreshold = distance > DISTANCE_THRESHOLD
const distanceGreaterThanThreshold = distance > PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS
if (distanceGreaterThanThreshold) {
console.warn(`Distance from pattern stop index=${index} to projected point is greater than ${DISTANCE_THRESHOLD} (${distance}).`)
console.warn(`Distance from pattern stop index=${index} to projected point is greater than ${PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS} (${distance}).`)
}
return (
<Polyline
Expand Down
123 changes: 123 additions & 0 deletions lib/editor/components/pattern/AddPatternStopDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// @flow

import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import { Button, Dropdown, MenuItem } from 'react-bootstrap'

import * as stopStrategiesActions from '../../actions/map/stopStrategies'

import type {GtfsStop, Pattern, PatternStop, Style} from '../../../types'

type Props = {
activePattern: Pattern,
addStopToPattern: typeof stopStrategiesActions.addStopToPattern,
index?: number, // current pattern stop index (if dropdown shown for current pattern stop)
label?: string,
patternStop?: PatternStop, // optional current pattern stop
size?: string,
stop: GtfsStop,
style?: Style
}

const DISABLED_MESSAGE = 'Cannot have the same stop appear consecutively in list'

/**
* Dropdown button that adds a stop as a new pattern stop to the actively
* selected pattern.
*/
export default class AddPatternStopDropdown extends Component<Props> {
_addStop = (index?: number) => {
const {activePattern, addStopToPattern, stop} = this.props
addStopToPattern(activePattern, stop, index)
}

_matchesStopAtIndex = (index: number) => {
const {activePattern, stop} = this.props
const patternStopAtIndex = activePattern.patternStops[index]
return patternStopAtIndex && patternStopAtIndex.stopId === stop.stop_id
}

_onAddToEnd = () => this._addStop()

_onSelectStop = (key: number) => this._addStop(key)

render () {
const {activePattern, index, label, size, style} = this.props
const {patternStops} = activePattern
const lastIndex = patternStops.length - 1
// Check that first/last stop is not already set to this stop.
let addToEndDisabled = this._matchesStopAtIndex(lastIndex)
let addToBeginningDisabled = this._matchesStopAtIndex(0)
// Also, disable end/beginning if the current pattern stop being viewed
// occupies one of these positions.
if (typeof index === 'number') {
addToEndDisabled = addToEndDisabled || index >= lastIndex
addToBeginningDisabled = addToBeginningDisabled || index === 0
}
return (
<Dropdown
id={`add-stop-dropdown`}
pullRight
onSelect={this._onSelectStop}
style={style}
>
<Button
bsSize={size}
bsStyle='success'
disabled={addToEndDisabled}
title={addToEndDisabled ? DISABLED_MESSAGE : ''}
onClick={this._onAddToEnd}>
<Icon type='plus' /> {label}
</Button>
<Dropdown.Toggle
bsSize={size}
bsStyle='success' />
<Dropdown.Menu style={{maxHeight: '200px', overflowY: 'scroll'}}>
<MenuItem
disabled={addToEndDisabled}
title={addToEndDisabled ? DISABLED_MESSAGE : ''}
value={activePattern.patternStops.length}
eventKey={activePattern.patternStops.length}>
Add to end (default)
</MenuItem>
{activePattern.patternStops && activePattern.patternStops.map((s, i) => {
// addIndex is in "reverse" order
const addIndex = activePattern.patternStops.length - i
let disableAdjacent = false
// If showing for current pattern stop, do not allow adding as an
// adjacent stop.
if (typeof index === 'number') {
disableAdjacent = (index >= addIndex - 2 && index < addIndex)
}
// Disable adding stop to current position or directly before/after
// current position
const addAtIndexDisabled = disableAdjacent ||
this._matchesStopAtIndex(addIndex - 2) ||
this._matchesStopAtIndex(addIndex - 1)
// Skip MenuItem index is the same as the pattern stop index
if (index === addIndex - 1 || addIndex === 1) {
return null
}
return (
<MenuItem
disabled={addAtIndexDisabled}
value={addIndex - 1}
title={addAtIndexDisabled ? DISABLED_MESSAGE : ''}
key={i}
eventKey={addIndex - 1}>
{`Insert as stop #${addIndex}`}
</MenuItem>
)
})}
<MenuItem
disabled={addToBeginningDisabled}
title={addToBeginningDisabled ? DISABLED_MESSAGE : ''}
value={0}
eventKey={0}>
Add to beginning
</MenuItem>
</Dropdown.Menu>
</Dropdown>
)
}
}
Loading

0 comments on commit 06ea784

Please # to comment.