Skip to content

Commit

Permalink
Merge pull request #59 from opentripplanner/micromobility
Browse files Browse the repository at this point in the history
Micromobility
  • Loading branch information
landonreed authored Jun 28, 2019
2 parents ab5b4ed + 7dbc8eb commit 42ddb83
Show file tree
Hide file tree
Showing 30 changed files with 1,197 additions and 490 deletions.
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@ notifications:
webhooks: https://outlook.office.com/webhook/03fa4a79-572f-4c68-b756-e4e851d0215a@9093f1a3-8771-4fb7-8596-d51eeef18cda/TravisCI/449087910d3647598cf3d7c6387fb8fc/286c079f-6085-4aa0-8f8d-e2a3e8d1f568
node_js:
- '12' # mastarm 5 requires node.js 10 or greater
before_install:
- npm i -g yarn codecov
after_success:
- bash <(curl -s https://codecov.io/bash)
- semantic-release
before_script:
- yarn global add codecov
script:
- yarn run lint
- yarn run lint-docs
- yarn run cover
- yarn run build
- codecov
branches:
except:
- /^v\d+\.\d+\.\d+$/
46 changes: 45 additions & 1 deletion lib/actions/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* globals fetch */
import { push, replace } from 'connected-react-router'
import hashObject from 'hash-obj'
import { createAction } from 'redux-actions'
import qs from 'qs'
import moment from 'moment'
Expand Down Expand Up @@ -274,6 +275,18 @@ export function carRentalQuery (params) {
return createQueryAction('car_rental', carRentalResponse, carRentalError)
}

// Vehicle rental locations lookup query. For now, there are 3 seperate
// "vehicle" rental endpoints - 1 for cars, 1 for bicycle rentals and another
// for micromobility. In the future, the hope is to consolidate these 3
// endpoints into one.

export const vehicleRentalResponse = createAction('VEHICLE_RENTAL_RESPONSE')
export const vehicleRentalError = createAction('VEHICLE_RENTAL_ERROR')

export function vehicleRentalQuery (params) {
return createQueryAction('vehicle_rental', vehicleRentalResponse, vehicleRentalError)
}

// Single stop lookup query

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

export const clearStops = createAction('CLEAR_STOPS_OVERLAY')

const throttledUrls = {}

function now () {
return (new Date()).getTime()
}

const TEN_SECONDS = 10000

// automatically clear throttled urls older than 10 seconds
window.setInterval(() => {
Object.keys(throttledUrls).forEach(key => {
if (throttledUrls[key] < now() - TEN_SECONDS) {
delete throttledUrls[key]
}
})
}, 1000)

/**
* Generic helper for constructing API queries
* Generic helper for constructing API queries. Automatically throttles queries
* to url to no more than once per 10 seconds.
*
* @param {string} endpoint - The API endpoint path (does not include
* '../otp/routers/router_id/')
Expand Down Expand Up @@ -683,6 +714,19 @@ function createQueryAction (endpoint, responseAction, errorAction, options = {})
const api = otpState.config.api
url = `${api.host}${api.port ? ':' + api.port : ''}${api.path}/${endpoint}`
}

// don't make a request to a URL that has already seen the same request
// within the last 10 seconds
const throttleKey = options.fetchOptions
? `${url}-${hashObject(options.fetchOptions)}`
: url
if (throttledUrls[throttleKey] && throttledUrls[throttleKey] > now() - TEN_SECONDS) {
// URL already had a request within last 10 seconds, warn and exit
console.warn(`Request throttled for url: ${url}`)
return
} else {
throttledUrls[throttleKey] = now()
}
let payload
try {
const response = await fetch(url, options.fetchOptions)
Expand Down
129 changes: 105 additions & 24 deletions lib/components/form/mode-button.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {PropTypes, Component} from 'react'
import React, {PropTypes, Component, PureComponent} from 'react'

import { getModeIcon, isTransit } from '../../util/itinerary'
import { getIcon, isTransit } from '../../util/itinerary'

export default class ModeButton extends Component {
static propTypes = {
Expand All @@ -11,13 +11,12 @@ export default class ModeButton extends Component {
onClick: PropTypes.func
}

render () {
const {active, enabled, icons, label, mode, onClick, inlineLabel, showPlusTransit} = this.props
const height = this.props.height || 48
const iconSize = height - 20

const iconColor = enabled ? '#000' : '#ccc'
const modeStr = mode.mode || mode
_getButtonStyle ({
active,
enabled,
height,
modeStr
}) {
const buttonStyle = { height }

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

return buttonStyle
}

render () {
const {
active,
enabled,
icons,
label,
mode,
onClick,
inlineLabel,
showPlusTransit
} = this.props
const height = this.props.height || 48
const iconSize = height - 20
const iconColor = enabled ? '#000' : '#ccc'
const modeStr = mode.company || mode.mode || mode
const buttonStyle = this._getButtonStyle({ active, enabled, height, modeStr })

return (
<div className={`mode-button-container ${enabled ? 'enabled' : 'disabled'}`} style={{ height: height + (inlineLabel ? 8 : 24), textAlign: 'center' }}>
<div
className={`mode-button-container ${enabled ? 'enabled' : 'disabled'}`}
style={{ height: height + (inlineLabel ? 8 : 24), textAlign: 'center' }}
>
<button
className='mode-button'
onClick={onClick}
title={label}
style={buttonStyle}
disabled={!enabled}
>
{/* Show the 'plus' and general transit icons, if enabled */}
{showPlusTransit && (
<span>
<div style={{ display: 'inline-block', width: iconSize, height: iconSize, verticalAlign: 'middle' }}>
{enabled
? getModeIcon('TRANSIT', icons)
: <div style={{ width: iconSize, height: iconSize, backgroundColor: iconColor, borderRadius: iconSize / 2 }} />
}
</div>
<i className='fa fa-plus' style={{ verticalAlign: 'middle', color: iconColor, margin: '0px 5px', fontSize: 14 }} />
</span>
<PlusTransit
enabled={enabled}
iconColor={iconColor}
icons={icons}
iconSize={iconSize}
/>
)}

{/* Show the primary mode icon */}
<div
className='mode-icon'
style={{ display: 'inline-block', fill: iconColor, width: iconSize, height: iconSize, verticalAlign: 'middle' }}>
{getModeIcon(mode, icons)}
style={{
display: 'inline-block',
fill: iconColor,
width: iconSize,
height: iconSize,
verticalAlign: 'middle'
}}
>
{getIcon(modeStr, icons)}
</div>

{/* Show the inline label, if enabled */}
{inlineLabel && <span style={{ fontSize: iconSize * 0.8, marginLeft: 10, verticalAlign: 'middle', fontWeight: active ? 600 : 300 }}>{label}</span>}
{inlineLabel && (
<span style={{
fontSize: iconSize * 0.8,
marginLeft: 10,
verticalAlign: 'middle',
fontWeight: active ? 600 : 300
}}
>
{label}
</span>
)}
</button>

{/* If not in inline-label mode, label directly below the button */}
{!inlineLabel && <div className='mode-label' style={{ color: iconColor, fontWeight: active ? 600 : 300 }}>{label}</div>}
{!inlineLabel && (
<div
className='mode-label'
style={{ color: iconColor, fontWeight: active ? 600 : 300 }}
>
{label}
</div>
)}
</div>
)
}
}

class PlusTransit extends PureComponent {
render () {
const {enabled, iconColor, icons, iconSize} = this.props
return (
<span>
<div
style={{
display: 'inline-block',
width: iconSize,
height: iconSize,
verticalAlign: 'middle'
}}
>
{enabled
? getIcon('TRANSIT', icons)
: (
<div style={{
width: iconSize,
height: iconSize,
backgroundColor: iconColor,
borderRadius: iconSize / 2
}} />
)
}
</div>
<i
className='fa fa-plus'
style={{
verticalAlign: 'middle',
color: iconColor,
margin: '0px 5px',
fontSize: 14
}}
/>
</span>
)
}
}
Loading

0 comments on commit 42ddb83

Please sign in to comment.