Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
84db0687c2 | ||
|
9327f750f6 | ||
|
4437eb0794 | ||
|
bb0852ed97 | ||
|
ed70fce6a7 | ||
|
c718c49ceb | ||
|
0293b9c6ee | ||
|
f10946b96d | ||
|
c538f145b1 | ||
|
e4bd7d3ced | ||
|
701a55ce29 | ||
|
ec47ad1622 | ||
|
656374a2fb | ||
|
c329543e66 | ||
|
7502be1c5e |
35
README.md
35
README.md
@ -1,5 +1,38 @@
|
|||||||
BART runner app for [Urbit](http://urbit.org).
|
BART (Bay Area Rapid Transit) landscape app for [Urbit](http://urbit.org).
|
||||||
|
|
||||||
The Bart App Map was created by Trucy Phan (https://github.com/trucy/bart-map) and is used under
|
The Bart App Map was created by Trucy Phan (https://github.com/trucy/bart-map) and is used under
|
||||||
the terms of the Creative Commons Attribution 3.0 Unported License.
|
the terms of the Creative Commons Attribution 3.0 Unported License.
|
||||||
|
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
This app is based off of the [create-landscape-app](https://github.com/urbit/create-landscape-app) scaffolding.
|
||||||
|
|
||||||
|
To install, first boot your ship, and mount its pier using `|mount %` in the Dojo.
|
||||||
|
|
||||||
|
Then clone this repo, and create a file called `.urbitrc` at the root of the repo directory
|
||||||
|
with the following contents:
|
||||||
|
|
||||||
|
```
|
||||||
|
module.exports = {
|
||||||
|
URBIT_PIERS: [
|
||||||
|
"/path/to/ship/home",
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance, if the repo was cloned into the same directory as a planet with a pier
|
||||||
|
`zod`, you might make the path `../zod/home`.
|
||||||
|
|
||||||
|
Then run the following Unix commands from the root of the repo:
|
||||||
|
```
|
||||||
|
$ yarn
|
||||||
|
$ yarn run build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build and package the javascript files, and move them into the directory
|
||||||
|
specified in the `.urbitrc` file.
|
||||||
|
|
||||||
|
Finally, run `|commit %home` in your ship's Dojo to make Urbit aware of those files,
|
||||||
|
and then run `|start %barttile` to start the app. You should then see a `BART info`
|
||||||
|
tile on your Landscape home screen.
|
||||||
|
|
||||||
|
@ -3,6 +3,16 @@ import { BrowserRouter, Route, Link } from "react-router-dom";
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { HeaderBar } from "./lib/header-bar.js"
|
import { HeaderBar } from "./lib/header-bar.js"
|
||||||
|
|
||||||
|
function padNumber(number) {
|
||||||
|
if (number == 0) {
|
||||||
|
return "00";
|
||||||
|
}
|
||||||
|
if (number <= 9) {
|
||||||
|
return `0${number}`
|
||||||
|
}
|
||||||
|
return number.toString();
|
||||||
|
}
|
||||||
|
|
||||||
function isSundaySchedule(curTime) {
|
function isSundaySchedule(curTime) {
|
||||||
// Deliberately switch over the effective day in the middle of the
|
// Deliberately switch over the effective day in the middle of the
|
||||||
// night.
|
// night.
|
||||||
@ -12,6 +22,63 @@ function isSundaySchedule(curTime) {
|
|||||||
return isSunday;
|
return isSunday;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptionsFromStations(stations) {
|
||||||
|
return _.map(stations, (station) => {
|
||||||
|
const abbr = station.abbr;
|
||||||
|
const name = station.name;
|
||||||
|
return <option key={abbr} value={abbr}>{name}</option>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScheduleWidget extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { station: "" };
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
if (state.station === "" && props.stations && props.stations[0]) {
|
||||||
|
const abbr = props.stations[0].abbr;
|
||||||
|
return { station: abbr }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
getSchedule(evt) {
|
||||||
|
// Needs to make a request at https://www.bart.gov/schedules/bystationresults?station=12TH&date=06/03/2020&time=6%3A30%20PM
|
||||||
|
const station = this.state.station;
|
||||||
|
const t = new Date();
|
||||||
|
const date = `${t.getMonth()}/${t.getDay()}/${t.getYear()}`
|
||||||
|
|
||||||
|
const hours = t.getHours();
|
||||||
|
const h = hours === 0 ? 12 : hours % 12;
|
||||||
|
const m = padNumber(t.getMinutes());
|
||||||
|
const meridian = hours >= 12 ? "PM": "AM"
|
||||||
|
const timeStr = `${h}:${m} ${meridian}`;
|
||||||
|
const url = `https://www.bart.gov/schedules/bystationresults?station=${station}&date=${date}&time=${timeStr}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeStation(evt) {
|
||||||
|
const value = evt.target.value;
|
||||||
|
this.setState({station: value});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const stations = this.props.stations;
|
||||||
|
return (<div>
|
||||||
|
<form name="getSchedule" onSubmit={this.getSchedule.bind(this)}>
|
||||||
|
<select disabled={!stations} name="stations" value={this.state.fromStation} onChange={this.changeStation.bind(this)}>
|
||||||
|
{ getOptionsFromStations(stations) }
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Get schedule"/>
|
||||||
|
</form>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ElevatorWidget extends Component {
|
class ElevatorWidget extends Component {
|
||||||
|
|
||||||
statuses() {
|
statuses() {
|
||||||
@ -37,50 +104,6 @@ class ElevatorWidget extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cf. https://github.com/urbit/urbit/blob/0c57e65b3871f1c40f1ecaf784722d4595c0d0ea/pkg/interface/chat/src/js/components/lib/ship-search.js
|
|
||||||
class StationPicker extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
searchTerm: ""
|
|
||||||
};
|
|
||||||
this.search = this.search.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
search(evt) {
|
|
||||||
this.setState({searchTerm: evt.target.value});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const props = this.props;
|
|
||||||
const state = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="b--gray2 b--solid ba bg-white bg-gray0-d"
|
|
||||||
>
|
|
||||||
{props.from ? "From: " : "To: "}
|
|
||||||
<textarea
|
|
||||||
style={{ resize: 'none', maxWidth: '200px' }}
|
|
||||||
className="ma2 pa2 b--gray4 ba b--solid w7 db bg-gray0-d white-d"
|
|
||||||
rows={1}
|
|
||||||
autocapitalise="none"
|
|
||||||
autoFocus={
|
|
||||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(
|
|
||||||
navigator.userAgent
|
|
||||||
)
|
|
||||||
? false
|
|
||||||
: true
|
|
||||||
}
|
|
||||||
placeholder="Station..."
|
|
||||||
value={state.searchTerm}
|
|
||||||
onChange={this.search}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimeScheduleWidget extends Component {
|
class TimeScheduleWidget extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@ -109,17 +132,38 @@ class TimeScheduleWidget extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoutePlanner extends Component {
|
class RouteSearch extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const now = new Date();
|
||||||
|
const hours = now.getHours();
|
||||||
this.state = {
|
this.state = {
|
||||||
fromStation: null,
|
fromStation: "",
|
||||||
toStation: null,
|
toStation: "",
|
||||||
|
depart: 'now',
|
||||||
|
min: now.getMinutes(),
|
||||||
|
hour: hours === 0 ? 12 : hours % 12,
|
||||||
|
isPM: hours >= 12
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
stationSearch() {
|
static getDerivedStateFromProps(props, state) {
|
||||||
console.log("Searching");
|
if (state.fromStation === "" && props.stations && props.stations[0]) {
|
||||||
|
const abbr = props.stations[0].abbr;
|
||||||
|
return { ...state, fromStation: abbr, toStation: abbr};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
stationSearch(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
api.action("bartinfo", "json", {
|
||||||
|
from: this.state.fromStation,
|
||||||
|
to: this.state.toStation,
|
||||||
|
min: this.state.min,
|
||||||
|
hour: this.state.hour,
|
||||||
|
isPM: this.state.isPM,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
changeStation(evt) {
|
changeStation(evt) {
|
||||||
@ -132,33 +176,142 @@ class RoutePlanner extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStationOptions() {
|
setDepartNow(evt) {
|
||||||
const stations = this.props.stations;
|
evt.preventDefault();
|
||||||
return _.map(stations, (station) => {
|
this.setState({depart: "now"});
|
||||||
const abbr = station.abbr;
|
}
|
||||||
const name = station.name;
|
|
||||||
return <option key={abbr} value={abbr}>{name}</option>;
|
setDepartAt(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
const now = new Date();
|
||||||
|
const hours = now.getHours();
|
||||||
|
this.setState({
|
||||||
|
depart: "givenTime",
|
||||||
|
min: now.getMinutes(),
|
||||||
|
hour: hours === 0 ? 12 : hours % 12,
|
||||||
|
isPM: hours >= 12
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStationForm() {
|
renderTimePicker() {
|
||||||
return (<form name="bartSearch" onSubmit={this.stationSearch.bind(this)}>
|
const state = this.state;
|
||||||
From:
|
const departNow = this.state.depart === 'now';
|
||||||
<select name="fromStation" value={this.state.fromStation || ""} onChange={this.changeStation.bind(this)}>
|
return (<div style={{display: "flex"}}>
|
||||||
{ this.renderStationOptions() }
|
<div>
|
||||||
|
<a href="" onClick={ this.setDepartNow.bind(this) }>
|
||||||
|
<div>
|
||||||
|
<p>{ departNow ? <b>Now</b> : "Now" }</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="" onClick={ this.setDepartAt.bind(this)}>
|
||||||
|
<div>
|
||||||
|
<p>{ departNow ? "At..." : <b>At...</b>}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
name="hour"
|
||||||
|
value={this.state.hour}
|
||||||
|
onChange={(evt) => this.setState({hour: parseInt(evt.target.value)}) } disabled={departNow}
|
||||||
|
>
|
||||||
|
{ _.map(_.range(1, 13), (hour) => { return <option key={`h-${hour}`} value={hour}>{padNumber(hour)}</option>;}) }
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<span>:</span>
|
||||||
To:
|
<select
|
||||||
<select name="toStation" value={this.state.toStation || ""} onChange={this.changeStation.bind(this)}>
|
name="min"
|
||||||
{ this.renderStationOptions() }
|
value={this.state.min}
|
||||||
|
onChange={(evt) => this.setState({min: parseInt(evt.target.value)}) } disabled={departNow}
|
||||||
|
>
|
||||||
|
{ _.map(_.range(0, 60), (min) => { return <option key={`m-${min}`} value={min}>{padNumber(min)}</option>;}) }
|
||||||
</select>
|
</select>
|
||||||
<input type="submit" value="Search"/>
|
<select
|
||||||
</form>);
|
name="isPM"
|
||||||
|
value={this.state.isPM ? "PM" : "AM"}
|
||||||
|
disabled={departNow}
|
||||||
|
onChange={(evt) => this.setState({isPM: evt.target.value === "PM"})}
|
||||||
|
>
|
||||||
|
<option value="AM">AM</option>
|
||||||
|
<option value="PM">PM</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const receivedStations = this.props.stations;
|
||||||
|
return (<form name="bartSearch" onSubmit={this.stationSearch.bind(this)}>
|
||||||
|
From:
|
||||||
|
<select disabled={!receivedStations} name="fromStation" value={this.state.fromStation} onChange={this.changeStation.bind(this)}>
|
||||||
|
{ getOptionsFromStations(receivedStations) }
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
To:
|
||||||
|
<select disabled={!receivedStations} name="toStation" value={this.state.toStation} onChange={this.changeStation.bind(this)}>
|
||||||
|
{ getOptionsFromStations(receivedStations) }
|
||||||
|
</select>
|
||||||
|
<div>
|
||||||
|
Depart at:
|
||||||
|
{ this.renderTimePicker() }
|
||||||
|
</div>
|
||||||
|
<div style={{padding: '5px'}}>
|
||||||
|
<input type="submit" value="Search"/>
|
||||||
|
</div>
|
||||||
|
</form>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IndividualRouteResult extends Component {
|
||||||
|
render() {
|
||||||
|
const trip = this.props.trip;
|
||||||
|
return (<div>
|
||||||
|
Depart: {trip.depart} Arrive: {trip.arrive} ({trip.time})
|
||||||
|
<br/>
|
||||||
|
Cost: {trip.fare}
|
||||||
|
<br/>
|
||||||
|
Legs:
|
||||||
|
{ _.map(trip.legs, (leg) => `${leg.line} line`) }
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RouteResults extends Component {
|
||||||
|
render() {
|
||||||
|
const routes = this.props.routes;
|
||||||
|
console.log(this.props.routes);
|
||||||
|
if (!routes) {
|
||||||
|
return (<div></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = routes.request;
|
||||||
|
const trip = request.trip;
|
||||||
|
const trips = _.map(trip, (t) => {
|
||||||
|
return {
|
||||||
|
fare: t['@fare'],
|
||||||
|
depart: t['@origTimeMin'],
|
||||||
|
arrive: t['@destTimeMin'],
|
||||||
|
time: t['@tripTime'],
|
||||||
|
legs: _.map(t.leg, (leg) => {
|
||||||
|
return {line: leg['@trainHeadStation'] };
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<div>
|
||||||
|
Trains:
|
||||||
|
<br/>
|
||||||
|
{ _.map(trips, (trip, idx) => <IndividualRouteResult key={idx} trip={trip} />) }
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoutePlanner extends Component {
|
||||||
render() {
|
render() {
|
||||||
const curTime = this.props.curTime;
|
const curTime = this.props.curTime;
|
||||||
const mapFilename = isSundaySchedule(curTime) ? "BART-Map-Sunday.png" : "BART-Map-Weekday-Saturday.png";
|
const mapFilename = "BART-system-map.png";
|
||||||
const mapPath=`/~bartinfo/img/${mapFilename}`;
|
const mapPath=`/~bartinfo/img/${mapFilename}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -174,11 +327,11 @@ class RoutePlanner extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="searchsidebar" style={{gridColumn: "2", gridRow: "2"}}>
|
<div className="searchsidebar" style={{gridColumn: "2", gridRow: "2"}}>
|
||||||
Search scheduled trains:
|
Search scheduled trains:
|
||||||
<div>
|
<RouteSearch stations={this.props.stations} curTime={curTime} />
|
||||||
<StationPicker from/>
|
|
||||||
<br/>
|
<br/>
|
||||||
<StationPicker to/>
|
<RouteResults routes={this.props.routes} />
|
||||||
</div>
|
or see the official bart scheduler for a given station, date and time:
|
||||||
|
<ScheduleWidget stations={this.props.stations}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -209,6 +362,7 @@ export class Root extends Component {
|
|||||||
<RoutePlanner
|
<RoutePlanner
|
||||||
curTime={new Date() }
|
curTime={new Date() }
|
||||||
stations={this.state.stations || []}
|
stations={this.state.stations || []}
|
||||||
|
routes={this.state.routes}
|
||||||
/> } />
|
/> } />
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
@ -14,6 +14,11 @@ export class UpdateReducer {
|
|||||||
if (elevators) {
|
if (elevators) {
|
||||||
state.elevators = elevators;
|
state.elevators = elevators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const routes = _.get(data, "routes", false);
|
||||||
|
if (routes) {
|
||||||
|
state.routes = routes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,9 @@ export class Subscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializebartinfo() {
|
initializebartinfo() {
|
||||||
/*
|
api.bind("/routes", "PUT", api.authTokens.ship, "bartinfo",
|
||||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'bartinfo',
|
|
||||||
this.handleEvent.bind(this),
|
this.handleEvent.bind(this),
|
||||||
this.handleError.bind(this));
|
this.handleError.bind(this));
|
||||||
*/
|
|
||||||
|
|
||||||
api.bind("/elevators", "PUT", api.authTokens.ship, "bartinfo",
|
api.bind("/elevators", "PUT", api.authTokens.ship, "bartinfo",
|
||||||
this.handleEvent.bind(this),
|
this.handleEvent.bind(this),
|
||||||
|
@ -60,6 +60,10 @@
|
|||||||
%+ give-simple-payload:app eyre-id
|
%+ give-simple-payload:app eyre-id
|
||||||
%+ require-authorization:app inbound-request
|
%+ require-authorization:app inbound-request
|
||||||
poke-handle-http-request:cc
|
poke-handle-http-request:cc
|
||||||
|
%json
|
||||||
|
=+ !<(jon=json vase)
|
||||||
|
:_ this
|
||||||
|
(poke-handle-json:cc jon)
|
||||||
::
|
::
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -79,6 +83,8 @@
|
|||||||
=/ req bart-api-elevator-status:cc
|
=/ req bart-api-elevator-status:cc
|
||||||
[%pass /elevators %arvo %i %request req out]
|
[%pass /elevators %arvo %i %request req out]
|
||||||
[~[elevator-status-request] this]
|
[~[elevator-status-request] this]
|
||||||
|
?: ?=([%routes *] path)
|
||||||
|
[[~] this]
|
||||||
?: ?=([%http-response *] path)
|
?: ?=([%http-response *] path)
|
||||||
`this
|
`this
|
||||||
?. =(/ path)
|
?. =(/ path)
|
||||||
@ -99,12 +105,18 @@
|
|||||||
?> ?=(%o -.value)
|
?> ?=(%o -.value)
|
||||||
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
||||||
[%give %fact ~[/bartstations] %json !>(update)]~
|
[%give %fact ~[/bartstations] %json !>(update)]~
|
||||||
::
|
::
|
||||||
[%elevators *]
|
[%elevators *]
|
||||||
=/ value=json (parse-elevator-status-response:cc client-response.sign-arvo)
|
=/ value=json (parse-elevator-status-response:cc client-response.sign-arvo)
|
||||||
?> ?=(%o -.value)
|
?> ?=(%o -.value)
|
||||||
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
||||||
[%give %fact ~[/elevators] %json !>(update)]~
|
[%give %fact ~[/elevators] %json !>(update)]~
|
||||||
|
::
|
||||||
|
[%routeplan *]
|
||||||
|
=/ value=json (parse-routeplan-response:cc client-response.sign-arvo)
|
||||||
|
?> ?=(%o -.value)
|
||||||
|
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
||||||
|
[%give %fact ~[/routes] %json !>(update)]~
|
||||||
==
|
==
|
||||||
[http-moves this]
|
[http-moves this]
|
||||||
?. ?=(%bound +<.sign-arvo)
|
?. ?=(%bound +<.sign-arvo)
|
||||||
@ -148,23 +160,15 @@
|
|||||||
|= response=client-response:iris
|
|= response=client-response:iris
|
||||||
^- json
|
^- json
|
||||||
=, format
|
=, format
|
||||||
=/ handler |= parsed-json=json
|
=/ handler |= jon=json
|
||||||
?> ?=(%o -.parsed-json)
|
=/ root ((ot:dejs ~[['root' same]]) jon)
|
||||||
=/ root=json (~(got by p.parsed-json) 'root')
|
=/ stations ((ot:dejs ~[['stations' same]]) root)
|
||||||
?> ?=(%o -.root)
|
=/ station ((ot:dejs ~[['station' (ar:dejs same)]]) stations)
|
||||||
=/ stations (~(got by p.root) 'stations')
|
=/ abbr-and-name %- turn :- station |= item=json
|
||||||
?> ?=(%o -.stations)
|
|
||||||
=/ station=json (~(got by p.stations) 'station')
|
|
||||||
?> ?=(%a -.station)
|
|
||||||
=/ inner p.station
|
|
||||||
=/ abbr-and-name %- turn :- inner |= item=json
|
|
||||||
^- json
|
^- json
|
||||||
?> ?=(%o -.item)
|
=/ [name=tape abbr=tape]
|
||||||
=/ name (~(got by p.item) 'name')
|
((ot:dejs ~[['name' sa:dejs] ['abbr' sa:dejs]]) item)
|
||||||
?> ?=(%s -.name)
|
(pairs:enjs ~[name+(tape:enjs name) abbr+(tape:enjs abbr)])
|
||||||
=/ abbr (~(got by p.item) 'abbr')
|
|
||||||
?> ?=(%s -.abbr)
|
|
||||||
(pairs:enjs [name+s+p.name abbr+s+p.abbr ~])
|
|
||||||
(pairs:enjs [[%stations %a abbr-and-name] ~])
|
(pairs:enjs [[%stations %a abbr-and-name] ~])
|
||||||
(with-json-handler response handler)
|
(with-json-handler response handler)
|
||||||
::
|
::
|
||||||
@ -178,15 +182,58 @@
|
|||||||
^- json
|
^- json
|
||||||
=, format
|
=, format
|
||||||
=/ handler |= jon=json
|
=/ handler |= jon=json
|
||||||
?> ?=(%o -.jon)
|
=/ root=json ((ot:dejs ~[['root' same]]) jon)
|
||||||
=/ root=json (~(got by p.jon) 'root')
|
=/ bsa=(list json) ((ot:dejs ~[['bsa' (ar:dejs same)]]) root)
|
||||||
?> ?=(%o -.root)
|
(pairs:enjs [[%elevators %a bsa] ~])
|
||||||
=/ bsa=json (~(got by p.root) 'bsa')
|
|
||||||
?> ?=(%a -.bsa)
|
|
||||||
~& -.bsa
|
|
||||||
(pairs:enjs [[%elevators %a p.bsa] ~])
|
|
||||||
(with-json-handler response handler)
|
(with-json-handler response handler)
|
||||||
::
|
::
|
||||||
|
++ bart-api-routeplan
|
||||||
|
:: Documentation: http://api.bart.gov/docs/sched/depart.aspx
|
||||||
|
|= [from=tape to=tape hour=@ min=@ ispm=?]
|
||||||
|
^- request:http
|
||||||
|
=/ meridian ?:(ispm "pm" "am")
|
||||||
|
=/ minstr ?: =(min 0) "00"
|
||||||
|
?: (lte min 9) "0{<min>}"
|
||||||
|
"{<min>}"
|
||||||
|
=/ time "{<hour>}:{minstr}{meridian}"
|
||||||
|
=/ before 1
|
||||||
|
=/ after 3
|
||||||
|
=/ url (crip "{bart-api-url-base}/sched.aspx?cmd=depart&orig={from}&a={<after>}&b={<before>}&dest={to}&time={time}&key={bart-api-key}&json=y")
|
||||||
|
~& "Making BART API request to {<url>}"
|
||||||
|
=/ headers [['Accept' 'application/json']]~
|
||||||
|
[%'GET' url headers *(unit octs)]
|
||||||
|
++ parse-routeplan-response
|
||||||
|
|= response=client-response:iris
|
||||||
|
^- json
|
||||||
|
=, format
|
||||||
|
=/ handler
|
||||||
|
|= jon=json
|
||||||
|
=/ root=json ((ot:dejs [['root' same] ~]) jon)
|
||||||
|
=/ schedule=json ((ot:dejs [['schedule' same] ~]) root)
|
||||||
|
(pairs:enjs ~[[%routes schedule]])
|
||||||
|
(with-json-handler response handler)
|
||||||
|
++ poke-handle-json
|
||||||
|
|= jon=json
|
||||||
|
^- (list card)
|
||||||
|
~& jon
|
||||||
|
=, format
|
||||||
|
?. ?=(%o -.jon)
|
||||||
|
[~]
|
||||||
|
=/ [hour=@ min=@ ispm=? from-station=tape to-station=tape]
|
||||||
|
%.
|
||||||
|
jon
|
||||||
|
%: ot:dejs
|
||||||
|
['hour' ni:dejs]
|
||||||
|
['min' ni:dejs]
|
||||||
|
['isPM' bo:dejs]
|
||||||
|
['from' sa:dejs]
|
||||||
|
['to' sa:dejs]
|
||||||
|
~
|
||||||
|
==
|
||||||
|
=/ req (bart-api-routeplan from-station to-station hour min ispm)
|
||||||
|
=/ out *outbound-config:iris
|
||||||
|
[[%pass /routeplan %arvo %i %request req out] ~]
|
||||||
|
::
|
||||||
++ poke-handle-http-request
|
++ poke-handle-http-request
|
||||||
|= =inbound-request:eyre
|
|= =inbound-request:eyre
|
||||||
^- simple-payload:http
|
^- simple-payload:http
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.5 MiB |
Binary file not shown.
Before Width: | Height: | Size: 4.5 MiB |
BIN
urbit/app/bartinfo/img/BART-system-map.png
Normal file
BIN
urbit/app/bartinfo/img/BART-system-map.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 MiB |
Loading…
Reference in New Issue
Block a user