Skip to content

Commit 42ddb83

Browse files
authored
Merge pull request #59 from opentripplanner/micromobility
Micromobility
2 parents ab5b4ed + 7dbc8eb commit 42ddb83

30 files changed

+1197
-490
lines changed

Diff for: .travis.yml

+1-5
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,14 @@ notifications:
66
webhooks: https://outlook.office.com/webhook/03fa4a79-572f-4c68-b756-e4e851d0215a@9093f1a3-8771-4fb7-8596-d51eeef18cda/TravisCI/449087910d3647598cf3d7c6387fb8fc/286c079f-6085-4aa0-8f8d-e2a3e8d1f568
77
node_js:
88
- '12' # mastarm 5 requires node.js 10 or greater
9-
before_install:
10-
- npm i -g yarn codecov
119
after_success:
10+
- bash <(curl -s https://codecov.io/bash)
1211
- semantic-release
13-
before_script:
14-
- yarn global add codecov
1512
script:
1613
- yarn run lint
1714
- yarn run lint-docs
1815
- yarn run cover
1916
- yarn run build
20-
- codecov
2117
branches:
2218
except:
2319
- /^v\d+\.\d+\.\d+$/

Diff for: lib/actions/api.js

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* globals fetch */
22
import { push, replace } from 'connected-react-router'
3+
import hashObject from 'hash-obj'
34
import { createAction } from 'redux-actions'
45
import qs from 'qs'
56
import moment from 'moment'
@@ -274,6 +275,18 @@ export function carRentalQuery (params) {
274275
return createQueryAction('car_rental', carRentalResponse, carRentalError)
275276
}
276277

278+
// Vehicle rental locations lookup query. For now, there are 3 seperate
279+
// "vehicle" rental endpoints - 1 for cars, 1 for bicycle rentals and another
280+
// for micromobility. In the future, the hope is to consolidate these 3
281+
// endpoints into one.
282+
283+
export const vehicleRentalResponse = createAction('VEHICLE_RENTAL_RESPONSE')
284+
export const vehicleRentalError = createAction('VEHICLE_RENTAL_ERROR')
285+
286+
export function vehicleRentalQuery (params) {
287+
return createQueryAction('vehicle_rental', vehicleRentalResponse, vehicleRentalError)
288+
}
289+
277290
// Single stop lookup query
278291

279292
// Stop times for stop query
@@ -651,8 +664,26 @@ export function findStopsWithinBBox (params) {
651664

652665
export const clearStops = createAction('CLEAR_STOPS_OVERLAY')
653666

667+
const throttledUrls = {}
668+
669+
function now () {
670+
return (new Date()).getTime()
671+
}
672+
673+
const TEN_SECONDS = 10000
674+
675+
// automatically clear throttled urls older than 10 seconds
676+
window.setInterval(() => {
677+
Object.keys(throttledUrls).forEach(key => {
678+
if (throttledUrls[key] < now() - TEN_SECONDS) {
679+
delete throttledUrls[key]
680+
}
681+
})
682+
}, 1000)
683+
654684
/**
655-
* Generic helper for constructing API queries
685+
* Generic helper for constructing API queries. Automatically throttles queries
686+
* to url to no more than once per 10 seconds.
656687
*
657688
* @param {string} endpoint - The API endpoint path (does not include
658689
* '../otp/routers/router_id/')
@@ -683,6 +714,19 @@ function createQueryAction (endpoint, responseAction, errorAction, options = {})
683714
const api = otpState.config.api
684715
url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}`
685716
}
717+
718+
// don't make a request to a URL that has already seen the same request
719+
// within the last 10 seconds
720+
const throttleKey = options.fetchOptions
721+
? `${url}-${hashObject(options.fetchOptions)}`
722+
: url
723+
if (throttledUrls[throttleKey] && throttledUrls[throttleKey] > now() - TEN_SECONDS) {
724+
// URL already had a request within last 10 seconds, warn and exit
725+
console.warn(`Request throttled for url: ${url}`)
726+
return
727+
} else {
728+
throttledUrls[throttleKey] = now()
729+
}
686730
let payload
687731
try {
688732
const response = await fetch(url, options.fetchOptions)

Diff for: lib/components/form/mode-button.js

+105-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, {PropTypes, Component} from 'react'
1+
import React, {PropTypes, Component, PureComponent} from 'react'
22

3-
import { getModeIcon, isTransit } from '../../util/itinerary'
3+
import { getIcon, isTransit } from '../../util/itinerary'
44

55
export default class ModeButton extends Component {
66
static propTypes = {
@@ -11,13 +11,12 @@ export default class ModeButton extends Component {
1111
onClick: PropTypes.func
1212
}
1313

14-
render () {
15-
const {active, enabled, icons, label, mode, onClick, inlineLabel, showPlusTransit} = this.props
16-
const height = this.props.height || 48
17-
const iconSize = height - 20
18-
19-
const iconColor = enabled ? '#000' : '#ccc'
20-
const modeStr = mode.mode || mode
14+
_getButtonStyle ({
15+
active,
16+
enabled,
17+
height,
18+
modeStr
19+
}) {
2120
const buttonStyle = { height }
2221

2322
if (modeStr !== 'TRANSIT' && isTransit(modeStr)) {
@@ -30,42 +29,124 @@ export default class ModeButton extends Component {
3029
if (active) buttonStyle.backgroundColor = '#add8e6'
3130
}
3231

32+
return buttonStyle
33+
}
34+
35+
render () {
36+
const {
37+
active,
38+
enabled,
39+
icons,
40+
label,
41+
mode,
42+
onClick,
43+
inlineLabel,
44+
showPlusTransit
45+
} = this.props
46+
const height = this.props.height || 48
47+
const iconSize = height - 20
48+
const iconColor = enabled ? '#000' : '#ccc'
49+
const modeStr = mode.company || mode.mode || mode
50+
const buttonStyle = this._getButtonStyle({ active, enabled, height, modeStr })
51+
3352
return (
34-
<div className={`mode-button-container ${enabled ? 'enabled' : 'disabled'}`} style={{ height: height + (inlineLabel ? 8 : 24), textAlign: 'center' }}>
53+
<div
54+
className={`mode-button-container ${enabled ? 'enabled' : 'disabled'}`}
55+
style={{ height: height + (inlineLabel ? 8 : 24), textAlign: 'center' }}
56+
>
3557
<button
3658
className='mode-button'
3759
onClick={onClick}
3860
title={label}
3961
style={buttonStyle}
4062
disabled={!enabled}
4163
>
42-
{/* Show the 'plus' and general transit icons, if enabled */}
4364
{showPlusTransit && (
44-
<span>
45-
<div style={{ display: 'inline-block', width: iconSize, height: iconSize, verticalAlign: 'middle' }}>
46-
{enabled
47-
? getModeIcon('TRANSIT', icons)
48-
: <div style={{ width: iconSize, height: iconSize, backgroundColor: iconColor, borderRadius: iconSize / 2 }} />
49-
}
50-
</div>
51-
<i className='fa fa-plus' style={{ verticalAlign: 'middle', color: iconColor, margin: '0px 5px', fontSize: 14 }} />
52-
</span>
65+
<PlusTransit
66+
enabled={enabled}
67+
iconColor={iconColor}
68+
icons={icons}
69+
iconSize={iconSize}
70+
/>
5371
)}
5472

5573
{/* Show the primary mode icon */}
5674
<div
5775
className='mode-icon'
58-
style={{ display: 'inline-block', fill: iconColor, width: iconSize, height: iconSize, verticalAlign: 'middle' }}>
59-
{getModeIcon(mode, icons)}
76+
style={{
77+
display: 'inline-block',
78+
fill: iconColor,
79+
width: iconSize,
80+
height: iconSize,
81+
verticalAlign: 'middle'
82+
}}
83+
>
84+
{getIcon(modeStr, icons)}
6085
</div>
6186

6287
{/* Show the inline label, if enabled */}
63-
{inlineLabel && <span style={{ fontSize: iconSize * 0.8, marginLeft: 10, verticalAlign: 'middle', fontWeight: active ? 600 : 300 }}>{label}</span>}
88+
{inlineLabel && (
89+
<span style={{
90+
fontSize: iconSize * 0.8,
91+
marginLeft: 10,
92+
verticalAlign: 'middle',
93+
fontWeight: active ? 600 : 300
94+
}}
95+
>
96+
{label}
97+
</span>
98+
)}
6499
</button>
65100

66101
{/* If not in inline-label mode, label directly below the button */}
67-
{!inlineLabel && <div className='mode-label' style={{ color: iconColor, fontWeight: active ? 600 : 300 }}>{label}</div>}
102+
{!inlineLabel && (
103+
<div
104+
className='mode-label'
105+
style={{ color: iconColor, fontWeight: active ? 600 : 300 }}
106+
>
107+
{label}
108+
</div>
109+
)}
68110
</div>
69111
)
70112
}
71113
}
114+
115+
class PlusTransit extends PureComponent {
116+
render () {
117+
const {enabled, iconColor, icons, iconSize} = this.props
118+
return (
119+
<span>
120+
<div
121+
style={{
122+
display: 'inline-block',
123+
width: iconSize,
124+
height: iconSize,
125+
verticalAlign: 'middle'
126+
}}
127+
>
128+
{enabled
129+
? getIcon('TRANSIT', icons)
130+
: (
131+
<div style={{
132+
width: iconSize,
133+
height: iconSize,
134+
backgroundColor: iconColor,
135+
borderRadius: iconSize / 2
136+
}} />
137+
)
138+
}
139+
</div>
140+
<i
141+
className='fa fa-plus'
142+
style={{
143+
verticalAlign: 'middle',
144+
color: iconColor,
145+
margin: '0px 5px',
146+
fontSize: 14
147+
}}
148+
/>
149+
</span>
150+
)
151+
}
152+
}

0 commit comments

Comments
 (0)