Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2b14f7b605 |
35
README.md
35
README.md
@ -1,38 +1,5 @@
|
|||||||
BART (Bay Area Rapid Transit) landscape app for [Urbit](http://urbit.org).
|
BART runner 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,16 +3,6 @@ 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.
|
||||||
@ -22,63 +12,6 @@ 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() {
|
||||||
@ -104,6 +37,50 @@ 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)
|
||||||
@ -132,38 +109,17 @@ class TimeScheduleWidget extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RouteSearch extends Component {
|
class RoutePlanner extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const now = new Date();
|
|
||||||
const hours = now.getHours();
|
|
||||||
this.state = {
|
this.state = {
|
||||||
fromStation: "",
|
fromStation: null,
|
||||||
toStation: "",
|
toStation: null,
|
||||||
depart: 'now',
|
|
||||||
min: now.getMinutes(),
|
|
||||||
hour: hours === 0 ? 12 : hours % 12,
|
|
||||||
isPM: hours >= 12
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
stationSearch() {
|
||||||
if (state.fromStation === "" && props.stations && props.stations[0]) {
|
console.log("Searching");
|
||||||
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) {
|
||||||
@ -176,142 +132,33 @@ class RouteSearch extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDepartNow(evt) {
|
renderStationOptions() {
|
||||||
evt.preventDefault();
|
const stations = this.props.stations;
|
||||||
this.setState({depart: "now"});
|
return _.map(stations, (station) => {
|
||||||
}
|
const abbr = station.abbr;
|
||||||
|
const name = station.name;
|
||||||
setDepartAt(evt) {
|
return <option key={abbr} value={abbr}>{name}</option>;
|
||||||
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTimePicker() {
|
renderStationForm() {
|
||||||
const state = this.state;
|
|
||||||
const departNow = this.state.depart === 'now';
|
|
||||||
return (<div style={{display: "flex"}}>
|
|
||||||
<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>
|
|
||||||
<span>:</span>
|
|
||||||
<select
|
|
||||||
name="min"
|
|
||||||
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
|
|
||||||
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)}>
|
return (<form name="bartSearch" onSubmit={this.stationSearch.bind(this)}>
|
||||||
From:
|
From:
|
||||||
<select disabled={!receivedStations} name="fromStation" value={this.state.fromStation} onChange={this.changeStation.bind(this)}>
|
<select name="fromStation" value={this.state.fromStation || ""} onChange={this.changeStation.bind(this)}>
|
||||||
{ getOptionsFromStations(receivedStations) }
|
{ this.renderStationOptions() }
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<br/>
|
||||||
To:
|
To:
|
||||||
<select disabled={!receivedStations} name="toStation" value={this.state.toStation} onChange={this.changeStation.bind(this)}>
|
<select name="toStation" value={this.state.toStation || ""} onChange={this.changeStation.bind(this)}>
|
||||||
{ getOptionsFromStations(receivedStations) }
|
{ this.renderStationOptions() }
|
||||||
</select>
|
</select>
|
||||||
<div>
|
|
||||||
Depart at:
|
|
||||||
{ this.renderTimePicker() }
|
|
||||||
</div>
|
|
||||||
<div style={{padding: '5px'}}>
|
|
||||||
<input type="submit" value="Search"/>
|
<input type="submit" value="Search"/>
|
||||||
</div>
|
|
||||||
</form>);
|
</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 = "BART-system-map.png";
|
const mapFilename = isSundaySchedule(curTime) ? "BART-Map-Sunday.png" : "BART-Map-Weekday-Saturday.png";
|
||||||
const mapPath=`/~bartinfo/img/${mapFilename}`;
|
const mapPath=`/~bartinfo/img/${mapFilename}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -327,11 +174,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:
|
||||||
<RouteSearch stations={this.props.stations} curTime={curTime} />
|
<div>
|
||||||
|
<StationPicker from/>
|
||||||
<br/>
|
<br/>
|
||||||
<RouteResults routes={this.props.routes} />
|
<StationPicker to/>
|
||||||
or see the official bart scheduler for a given station, date and time:
|
</div>
|
||||||
<ScheduleWidget stations={this.props.stations}/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -362,7 +209,6 @@ 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,11 +14,6 @@ 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,9 +14,11 @@ 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,10 +60,6 @@
|
|||||||
%+ 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)
|
|
||||||
::
|
::
|
||||||
==
|
==
|
||||||
::
|
::
|
||||||
@ -83,8 +79,6 @@
|
|||||||
=/ 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)
|
||||||
@ -111,12 +105,6 @@
|
|||||||
?> ?=(%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)
|
||||||
@ -160,15 +148,23 @@
|
|||||||
|= response=client-response:iris
|
|= response=client-response:iris
|
||||||
^- json
|
^- json
|
||||||
=, format
|
=, format
|
||||||
=/ handler |= jon=json
|
=/ handler |= parsed-json=json
|
||||||
=/ root ((ot:dejs ~[['root' same]]) jon)
|
?> ?=(%o -.parsed-json)
|
||||||
=/ stations ((ot:dejs ~[['stations' same]]) root)
|
=/ root=json (~(got by p.parsed-json) 'root')
|
||||||
=/ station ((ot:dejs ~[['station' (ar:dejs same)]]) stations)
|
?> ?=(%o -.root)
|
||||||
=/ abbr-and-name %- turn :- station |= item=json
|
=/ stations (~(got by p.root) 'stations')
|
||||||
|
?> ?=(%o -.stations)
|
||||||
|
=/ station=json (~(got by p.stations) 'station')
|
||||||
|
?> ?=(%a -.station)
|
||||||
|
=/ inner p.station
|
||||||
|
=/ abbr-and-name %- turn :- inner |= item=json
|
||||||
^- json
|
^- json
|
||||||
=/ [name=tape abbr=tape]
|
?> ?=(%o -.item)
|
||||||
((ot:dejs ~[['name' sa:dejs] ['abbr' sa:dejs]]) item)
|
=/ name (~(got by p.item) 'name')
|
||||||
(pairs:enjs ~[name+(tape:enjs name) abbr+(tape:enjs abbr)])
|
?> ?=(%s -.name)
|
||||||
|
=/ 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)
|
||||||
::
|
::
|
||||||
@ -182,58 +178,15 @@
|
|||||||
^- json
|
^- json
|
||||||
=, format
|
=, format
|
||||||
=/ handler |= jon=json
|
=/ handler |= jon=json
|
||||||
=/ root=json ((ot:dejs ~[['root' same]]) jon)
|
?> ?=(%o -.jon)
|
||||||
=/ bsa=(list json) ((ot:dejs ~[['bsa' (ar:dejs same)]]) root)
|
=/ root=json (~(got by p.jon) 'root')
|
||||||
(pairs:enjs [[%elevators %a bsa] ~])
|
?> ?=(%o -.root)
|
||||||
|
=/ 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
|
||||||
|
BIN
urbit/app/bartinfo/img/BART-Map-Sunday.png
Normal file
BIN
urbit/app/bartinfo/img/BART-Map-Sunday.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 MiB |
BIN
urbit/app/bartinfo/img/BART-Map-Weekday-Saturday.png
Normal file
BIN
urbit/app/bartinfo/img/BART-Map-Weekday-Saturday.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 MiB |
Binary file not shown.
Before Width: | Height: | Size: 5.3 MiB |
Loading…
Reference in New Issue
Block a user