Compare commits
3 Commits
master
...
db9e8acb5b
Author | SHA1 | Date | |
---|---|---|---|
|
db9e8acb5b | ||
|
b3802eba76 | ||
|
ceed2be120 |
2
.urbitrc
@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
URBIT_PIERS: [
|
||||
"../zod/home",
|
||||
"/home/greg/code/zod/home",
|
||||
]
|
||||
};
|
||||
|
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 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.
|
||||
|
||||
|
16
dist/index.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/index.js";import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Root } from '/components/root';
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { subscription } from "/subscription";
|
||||
|
||||
api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
subscription.start();
|
||||
|
||||
ReactDOM.render((
|
||||
React.createElement(Root, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 15}} )
|
||||
), document.querySelectorAll("#root")[0]);
|
49
dist/js/api.js
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
class UrbitApi {
|
||||
setAuthTokens(authTokens) {
|
||||
this.authTokens = authTokens;
|
||||
this.bindPaths = [];
|
||||
}
|
||||
|
||||
bind(path, method, ship = this.authTokens.ship, appl = "barttile", success, fail) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = window.urb.subscribe(ship, appl, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
(event) => {
|
||||
success({
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
fail(err);
|
||||
});
|
||||
}
|
||||
|
||||
barttile(data) {
|
||||
this.action("barttile", "json", data);
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.urb.poke(ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
export let api = new UrbitApi();
|
||||
window.api = api;
|
48
dist/js/components/lib/header-bar.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/js/components/lib/header-bar.js";import React, { Component } from "react";
|
||||
import { cite } from '../../lib/util';
|
||||
import { IconHome } from "/components/lib/icons/icon-home";
|
||||
import { Sigil } from "/components/lib/icons/sigil";
|
||||
|
||||
export class HeaderBar extends Component {
|
||||
render() {
|
||||
|
||||
let title = document.title === "Home" ? "" : document.title;
|
||||
|
||||
return (
|
||||
React.createElement('div', {
|
||||
className:
|
||||
"bg-white bg-gray0-d w-100 justify-between relative tc pt3 db"
|
||||
,
|
||||
style: { height: 40 }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 12}}
|
||||
, React.createElement('a', {
|
||||
className: "dib gray2 f9 inter absolute left-0" ,
|
||||
href: "/",
|
||||
style: { top: 14 }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 17}}
|
||||
, React.createElement(IconHome, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 21}})
|
||||
, React.createElement('span', {
|
||||
className: "ml2 white-d v-top lh-title" ,
|
||||
style: { paddingTop: 3 }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 22}}, "Home"
|
||||
|
||||
)
|
||||
)
|
||||
, React.createElement('span', {
|
||||
className: "f9 white-d inter dib" ,
|
||||
style: {
|
||||
verticalAlign: "text-top",
|
||||
paddingTop: 3
|
||||
}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 28}}
|
||||
, title
|
||||
)
|
||||
, React.createElement('div', { className: "absolute right-0 lh-copy" , style: { top: 8 }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 36}}
|
||||
, React.createElement(Sigil, {
|
||||
ship: "~" + window.ship,
|
||||
classes: "v-mid mix-blend-diff" ,
|
||||
size: 16,
|
||||
color: "#000000", __self: this, __source: {fileName: _jsxFileName, lineNumber: 37}}
|
||||
)
|
||||
, React.createElement('span', { className: "mono white-d f9 ml2 c-default" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 43}}, cite(window.ship))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
15
dist/js/components/lib/icons/icon-home.js
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/js/components/lib/icons/icon-home.js";import React, { Component } from "react";
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
return (
|
||||
React.createElement('img', {
|
||||
className: "invert-d " + classes,
|
||||
src: "/~barttile/img/Home.png",
|
||||
width: 16,
|
||||
height: 16, __self: this, __source: {fileName: _jsxFileName, lineNumber: 7}}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
9
dist/js/components/lib/icons/icon-spinner.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/js/components/lib/icons/icon-spinner.js";import React, { Component } from 'react';
|
||||
|
||||
export class IconSpinner extends Component {
|
||||
render() {
|
||||
return (
|
||||
React.createElement('div', { className: "spinner-pending", __self: this, __source: {fileName: _jsxFileName, lineNumber: 6}})
|
||||
);
|
||||
}
|
||||
}
|
32
dist/js/components/lib/icons/sigil.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/js/components/lib/icons/sigil.js";import React, { Component } from 'react';
|
||||
import { sigil, reactRenderer } from 'urbit-sigil-js';
|
||||
|
||||
|
||||
export class Sigil extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
let classes = props.classes || "";
|
||||
|
||||
if (props.ship.length > 14) {
|
||||
return (
|
||||
React.createElement('div', {
|
||||
className: "bg-black dib " + classes,
|
||||
style: { width: props.size, height: props.size }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 13}}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
React.createElement('div', { className: "dib " + classes, style: { flexBasis: 32, backgroundColor: props.color }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 20}}
|
||||
, sigil({
|
||||
patp: props.ship,
|
||||
renderer: reactRenderer,
|
||||
size: props.size,
|
||||
colors: [props.color, "white"]
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
dist/js/components/root.js
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/src/js/components/root.js";import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route } from "react-router-dom";
|
||||
import _ from 'lodash';
|
||||
import { HeaderBar } from "./lib/header-bar.js"
|
||||
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
React.createElement(BrowserRouter, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 15}}
|
||||
, React.createElement('div', { className: "absolute h-100 w-100 bg-gray0-d ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 16}}
|
||||
, React.createElement(HeaderBar, {__self: this, __source: {fileName: _jsxFileName, lineNumber: 17}})
|
||||
, React.createElement(Route, { exact: true, path: "/~barttile", render: () => {
|
||||
return (
|
||||
React.createElement('div', { className: "cf w-100 flex flex-column pa4 ba-m ba-l ba-xl b--gray2 br1 h-100 h-100-minus-40-s h-100-minus-40-m h-100-minus-40-l h-100-minus-40-xl f9 white-d overflow-x-hidden" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 20}}
|
||||
, React.createElement('h1', { className: "mt0 f8 fw4" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 21}}, "BART Info" )
|
||||
, React.createElement('div', { style: {display: "grid", gridTemplateColumns: "66% 34%", gridGap: "10px" }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 22}}
|
||||
, React.createElement('div', { className: "topinfo", style: {gridColumn: "1 / 2"}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 23}}
|
||||
, React.createElement('p', { className: "lh-copy measure pt3" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 24}}, "Current time is (current time)" )
|
||||
, React.createElement('p', { className: "lh-copy measure pt3" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 25}}, "Today's bart map:" )
|
||||
)
|
||||
, React.createElement('div', { className: "map", style: {gridColumn: "1", gridRow: "2"}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 27}}
|
||||
, React.createElement('img', { src: "/~barttile/img/BART-Map-Weekday-Saturday.png", __self: this, __source: {fileName: _jsxFileName, lineNumber: 28}} )
|
||||
)
|
||||
, React.createElement('div', { className: "searchsidebar", style: {gridColumn: "2", gridRow: "2"}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 30}}, "Search stuff here"
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
)}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 18}}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
82
dist/js/lib/util.js
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
import _ from 'lodash';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export function uuid() {
|
||||
let str = "0v"
|
||||
str += Math.ceil(Math.random()*8)+"."
|
||||
for (var i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random()*10000000).toString(32);
|
||||
_str = ("00000"+_str).substr(-5,5);
|
||||
str += _str+".";
|
||||
}
|
||||
|
||||
return str.slice(0,-1);
|
||||
}
|
||||
|
||||
export function isPatTa(str) {
|
||||
const r = /^[a-z,0-9,\-,\.,_,~]+$/.exec(str)
|
||||
return !!r;
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
To:
|
||||
(javascript Date object)
|
||||
*/
|
||||
export function daToDate(st) {
|
||||
var dub = function(n) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
};
|
||||
var da = st.split('..');
|
||||
var bigEnd = da[0].split('.');
|
||||
var lilEnd = da[1].split('.');
|
||||
var ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(lilEnd[0])}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
return new Date(ds);
|
||||
}
|
||||
|
||||
/*
|
||||
Goes from:
|
||||
(javascript Date object)
|
||||
To:
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
*/
|
||||
|
||||
export function dateToDa(d, mil) {
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
`${(d.getUTCMonth() + 1)}.` +
|
||||
`${fil(d.getUTCDate())}..` +
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
export function deSig(ship) {
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship) {
|
||||
let patp = ship, shortened = "";
|
||||
if (patp.startsWith("~")) {
|
||||
patp = patp.substr(1);
|
||||
}
|
||||
// comet
|
||||
if (patp.length === 56) {
|
||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
||||
return shortened;
|
||||
}
|
||||
// moon
|
||||
if (patp.length === 27) {
|
||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
||||
return shortened;
|
||||
}
|
||||
return `~${patp}`;
|
||||
}
|
11
dist/js/reducers/config.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class ConfigReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'barttile', false);
|
||||
if (data) {
|
||||
state.inbox = data.inbox;
|
||||
}
|
||||
}
|
||||
}
|
11
dist/js/reducers/initial.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class InitialReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.inbox = data.inbox;
|
||||
}
|
||||
}
|
||||
}
|
17
dist/js/reducers/update.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class UpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'update', false);
|
||||
if (data) {
|
||||
this.reduceInbox(_.get(data, 'inbox', false), state);
|
||||
}
|
||||
}
|
||||
|
||||
reduceInbox(inbox, state) {
|
||||
if (inbox) {
|
||||
state.inbox = inbox;
|
||||
}
|
||||
}
|
||||
}
|
35
dist/js/store.js
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
import { InitialReducer } from '/reducers/initial';
|
||||
import { ConfigReducer } from '/reducers/config';
|
||||
import { UpdateReducer } from '/reducers/update';
|
||||
|
||||
|
||||
class Store {
|
||||
constructor() {
|
||||
this.state = {
|
||||
inbox: {}
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
this.configReducer = new ConfigReducer();
|
||||
this.updateReducer = new UpdateReducer();
|
||||
this.setState = () => { };
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(data) {
|
||||
let json = data.data;
|
||||
|
||||
console.log(json);
|
||||
this.initialReducer.reduce(json, this.state);
|
||||
this.configReducer.reduce(json, this.state);
|
||||
this.updateReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
window.store = store;
|
34
dist/js/subscription.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
// this.initializebarttile();
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
// initializebarttile() {
|
||||
// api.bind('/primary', 'PUT', api.authTokens.ship, 'barttile',
|
||||
// this.handleEvent.bind(this),
|
||||
// this.handleError.bind(this));
|
||||
// }
|
||||
|
||||
handleEvent(diff) {
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'barttile',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
export let subscription = new Subscription();
|
1
dist/js/vendor/sigils-1.2.5.js
vendored
Normal file
20
dist/tile.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
const _jsxFileName = "/home/greg/code/bart-tile/tile/tile.js";import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export default class barttileTile extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
React.createElement('div', { className: "w-100 h-100 relative bg-white bg-gray0-d ba b--black b--gray1-d" , __self: this, __source: {fileName: _jsxFileName, lineNumber: 9}}
|
||||
, React.createElement('a', { className: "w-100 h-100 db pa2 no-underline" , href: "/~barttile", __self: this, __source: {fileName: _jsxFileName, lineNumber: 10}}
|
||||
, React.createElement('p', { className: "black white-d absolute f9" , style: { left: 8, top: 8 }, __self: this, __source: {fileName: _jsxFileName, lineNumber: 11}}, "BART")
|
||||
, React.createElement('img', { className: "absolute", src: "/~barttile/img/Temp-Bart-Icon.png", style: {top: 40, left: 20, width: "50%"}, __self: this, __source: {fileName: _jsxFileName, lineNumber: 12}})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.barttileTile = barttileTile;
|
14
gulpfile.js
@ -29,7 +29,7 @@ gulp.task('css-bundle', function() {
|
||||
.src('src/index.css')
|
||||
.pipe(cssimport())
|
||||
.pipe(postcss(plugins))
|
||||
.pipe(gulp.dest('./urbit/app/bartinfo/css'));
|
||||
.pipe(gulp.dest('./urbit/app/barttile/css'));
|
||||
});
|
||||
|
||||
gulp.task('jsx-transform', function(cb) {
|
||||
@ -71,7 +71,7 @@ gulp.task('js-imports', function(cb) {
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('./urbit/app/bartinfo/js/'))
|
||||
.pipe(gulp.dest('./urbit/app/barttile/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
@ -97,21 +97,21 @@ gulp.task('tile-js-imports', function(cb) {
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('./urbit/app/bartinfo/js/'))
|
||||
.pipe(gulp.dest('./urbit/app/barttile/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
|
||||
gulp.task('js-minify', function () {
|
||||
return gulp.src('./urbit/app/bartinfo/js/index.js')
|
||||
return gulp.src('./urbit/app/barttile/js/index.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('./urbit/app/bartinfo/js/'));
|
||||
.pipe(gulp.dest('./urbit/app/barttile/js/'));
|
||||
});
|
||||
|
||||
gulp.task('tile-js-minify', function () {
|
||||
return gulp.src('./urbit/app/bartinfo/js/tile.js')
|
||||
return gulp.src('./urbit/app/barttile/js/tile.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('./urbit/app/bartinfo/js/'));
|
||||
.pipe(gulp.dest('./urbit/app/barttile/js/'));
|
||||
});
|
||||
|
||||
gulp.task('urbit-copy', function () {
|
||||
|
@ -8,8 +8,7 @@ class UrbitApi {
|
||||
this.bindPaths = [];
|
||||
}
|
||||
|
||||
bind(path, method, ship = this.authTokens.ship, appl = "bartinfo", success, fail) {
|
||||
console.log(`Calling api.bind() with path ${path} ship ${ship} method ${method}`);
|
||||
bind(path, method, ship = this.authTokens.ship, appl = "barttile", success, fail) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = window.urb.subscribe(ship, appl, path,
|
||||
@ -30,8 +29,8 @@ class UrbitApi {
|
||||
});
|
||||
}
|
||||
|
||||
bartinfo(data) {
|
||||
this.action("bartinfo", "json", data);
|
||||
barttile(data) {
|
||||
this.action("barttile", "json", data);
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
|
@ -6,7 +6,7 @@ export class IconHome extends Component {
|
||||
return (
|
||||
<img
|
||||
className={"invert-d " + classes}
|
||||
src="/~bartinfo/img/Home.png"
|
||||
src="/~barttile/img/Home.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
|
@ -1,369 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route, Link } from "react-router-dom";
|
||||
import { BrowserRouter, Route } from "react-router-dom";
|
||||
import _ from 'lodash';
|
||||
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) {
|
||||
// Deliberately switch over the effective day in the middle of the
|
||||
// night.
|
||||
const dayOfWeek = curTime.getDay();
|
||||
const hour = curTime.getHours();
|
||||
const isSunday = (dayOfWeek === 0 && hour > 4) || (dayOfWeek === 1 && hour < 4);
|
||||
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 {
|
||||
|
||||
statuses() {
|
||||
const elevatorStatuses = this.props.elevatorStatuses;
|
||||
if (elevatorStatuses.length === 0) {
|
||||
return <p>No elevators are known to be out of service.</p>;
|
||||
}
|
||||
return (<div>
|
||||
{ _.map(elevatorStatuses, (st, idx) => {
|
||||
const desc = st.description['#cdata-section'];
|
||||
return <p key={idx}>{desc}</p>;
|
||||
})
|
||||
}
|
||||
</div>);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="cf w-100 flex flex-column pa4 ba-m ba-l ba-xl b--gray2 br1 h-100 h-100-minus-40-s h-100-minus-40-m h-100-minus-40-l h-100-minus-40-xl f9 white-d overflow-x-hidden">
|
||||
<h1 className="mt0 f8 fw4">BART Info - Elevator status</h1>
|
||||
<Link to="/~bartinfo">Route planner</Link>
|
||||
{ this.statuses() }
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeScheduleWidget extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { curTime: new Date() };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.timerId = setInterval(() => this.tick(), 1000);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.timerId);
|
||||
}
|
||||
|
||||
tick() {
|
||||
this.setState({curTime: new Date()});
|
||||
}
|
||||
|
||||
render() {
|
||||
const curTime = this.state.curTime;
|
||||
const timeStr = curTime.toLocaleTimeString();
|
||||
const serviceStr = isSundaySchedule(curTime) ? "Sunday service" : "Weekday / Saturday service";
|
||||
return (<div style={{textAlign: "center"}}>
|
||||
<p className="lh-copy measure pt3">Current time: <b>{timeStr}</b></p>
|
||||
<p className="lh-copy measure pt3">{serviceStr}</p>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
class RouteSearch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
this.state = {
|
||||
fromStation: "",
|
||||
toStation: "",
|
||||
depart: 'now',
|
||||
min: now.getMinutes(),
|
||||
hour: hours === 0 ? 12 : hours % 12,
|
||||
isPM: hours >= 12
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
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) {
|
||||
const target = evt.target.name;
|
||||
const value = evt.target.value;
|
||||
if (target === "fromStation") {
|
||||
this.setState({fromStation: value});
|
||||
} else if (target === "toStation") {
|
||||
this.setState({toStation: value});
|
||||
}
|
||||
}
|
||||
|
||||
setDepartNow(evt) {
|
||||
evt.preventDefault();
|
||||
this.setState({depart: "now"});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
renderTimePicker() {
|
||||
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)}>
|
||||
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() {
|
||||
const curTime = this.props.curTime;
|
||||
const mapFilename = "BART-system-map.png";
|
||||
const mapPath=`/~bartinfo/img/${mapFilename}`;
|
||||
|
||||
return (
|
||||
<div className="cf w-100 flex flex-column pa4 ba-m ba-l ba-xl b--gray2 br1 h-100 h-100-minus-40-s h-100-minus-40-m h-100-minus-40-l h-100-minus-40-xl f9 white-d overflow-x-hidden">
|
||||
<h1 className="mt0 f8 fw4">BART Info</h1>
|
||||
<Link to="/~bartinfo/elevators">Elevator information</Link>
|
||||
<div style={{display: "grid", gridTemplateColumns: "66% 34%", gridGap: "10px" }}>
|
||||
<div className="topinfo" style={{gridColumn: "1 / 2"}}>
|
||||
<TimeScheduleWidget/>
|
||||
</div>
|
||||
<div className="map" style={{gridColumn: "1", gridRow: "2"}}>
|
||||
<img src={mapPath} />
|
||||
</div>
|
||||
<div className="searchsidebar" style={{gridColumn: "2", gridRow: "2"}}>
|
||||
Search scheduled trains:
|
||||
<RouteSearch stations={this.props.stations} curTime={curTime} />
|
||||
<br/>
|
||||
<RouteResults routes={this.props.routes} />
|
||||
or see the official bart scheduler for a given station, date and time:
|
||||
<ScheduleWidget stations={this.props.stations}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = store.state;
|
||||
store.setStateHandler((newState) => {
|
||||
this.setState(newState);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="absolute h-100 w-100 bg-gray0-d ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl">
|
||||
<HeaderBar/>
|
||||
<Route exact path="/~bartinfo/elevators" render={ () =>
|
||||
<ElevatorWidget
|
||||
elevatorStatuses={this.state.elevators || []}
|
||||
/> }/>
|
||||
<Route exact path="/~bartinfo" render={ () =>
|
||||
<RoutePlanner
|
||||
curTime={new Date() }
|
||||
stations={this.state.stations || []}
|
||||
routes={this.state.routes}
|
||||
/> } />
|
||||
<HeaderBar/>
|
||||
<Route exact path="/~barttile" render={ () => {
|
||||
return (
|
||||
<div className="cf w-100 flex flex-column pa4 ba-m ba-l ba-xl b--gray2 br1 h-100 h-100-minus-40-s h-100-minus-40-m h-100-minus-40-l h-100-minus-40-xl f9 white-d overflow-x-hidden">
|
||||
<h1 className="mt0 f8 fw4">BART Info</h1>
|
||||
<div style={{display: "grid", gridTemplateColumns: "66% 34%", gridGap: "10px" }}>
|
||||
<div className="topinfo" style={{gridColumn: "1 / 2"}}>
|
||||
<p className="lh-copy measure pt3">Current time is (current time)</p>
|
||||
<p className="lh-copy measure pt3">Today's bart map:</p>
|
||||
</div>
|
||||
<div className="map" style={{gridColumn: "1", gridRow: "2"}}>
|
||||
<img src="/~barttile/img/BART-Map-Weekday-Saturday.png" />
|
||||
</div>
|
||||
<div className="searchsidebar" style={{gridColumn: "2", gridRow: "2"}}>
|
||||
Search stuff here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}}
|
||||
/>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
11
src/js/reducers/config.js
Normal file
@ -0,0 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class ConfigReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'barttile', false);
|
||||
if (data) {
|
||||
state.inbox = data.inbox;
|
||||
}
|
||||
}
|
||||
}
|
11
src/js/reducers/initial.js
Normal file
@ -0,0 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export class InitialReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.inbox = data.inbox;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,20 +5,13 @@ export class UpdateReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'update', false);
|
||||
if (data) {
|
||||
const stations = _.get(data, 'stations', false);
|
||||
if (stations) {
|
||||
state.stations = stations;
|
||||
}
|
||||
this.reduceInbox(_.get(data, 'inbox', false), state);
|
||||
}
|
||||
}
|
||||
|
||||
const elevators = _.get(data, "elevators", false);
|
||||
if (elevators) {
|
||||
state.elevators = elevators;
|
||||
}
|
||||
|
||||
const routes = _.get(data, "routes", false);
|
||||
if (routes) {
|
||||
state.routes = routes;
|
||||
}
|
||||
reduceInbox(inbox, state) {
|
||||
if (inbox) {
|
||||
state.inbox = inbox;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,32 @@
|
||||
import { InitialReducer } from '/reducers/initial';
|
||||
import { ConfigReducer } from '/reducers/config';
|
||||
import { UpdateReducer } from '/reducers/update';
|
||||
|
||||
|
||||
class Store {
|
||||
constructor() {
|
||||
this.state = {}
|
||||
this.state = {
|
||||
inbox: {}
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
this.configReducer = new ConfigReducer();
|
||||
this.updateReducer = new UpdateReducer();
|
||||
this.setState = () => { };
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
console.log("Calling setStateHandler");
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(data) {
|
||||
console.log("Handling event");
|
||||
console.log(data);
|
||||
let json = data.data;
|
||||
|
||||
console.log(json);
|
||||
this.initialReducer.reduce(json, this.state);
|
||||
this.configReducer.reduce(json, this.state);
|
||||
this.updateReducer.reduce(json, this.state);
|
||||
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
@ -7,26 +7,17 @@ import urbitOb from 'urbit-ob';
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
this.initializebartinfo();
|
||||
// this.initializebarttile();
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
initializebartinfo() {
|
||||
api.bind("/routes", "PUT", api.authTokens.ship, "bartinfo",
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
|
||||
api.bind("/elevators", "PUT", api.authTokens.ship, "bartinfo",
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
|
||||
api.bind("/bartstations", "PUT", api.authTokens.ship, 'bartinfo',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
|
||||
}
|
||||
// initializebarttile() {
|
||||
// api.bind('/primary', 'PUT', api.authTokens.ship, 'barttile',
|
||||
// this.handleEvent.bind(this),
|
||||
// this.handleError.bind(this));
|
||||
// }
|
||||
|
||||
handleEvent(diff) {
|
||||
store.handleEvent(diff);
|
||||
@ -34,6 +25,9 @@ export class Subscription {
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'barttile',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
10
tile/tile.js
@ -2,14 +2,14 @@ import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export default class bartinfoTile extends Component {
|
||||
export default class barttileTile extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="w-100 h-100 relative bg-white bg-gray0-d ba b--black b--gray1-d">
|
||||
<a className="w-100 h-100 db pa2 no-underline" href="/~bartinfo">
|
||||
<p className="black white-d absolute f9" style={{ left: 8, top: 8 }}>BART Info</p>
|
||||
<img className="absolute" src="/~bartinfo/img/Temp-Bart-Icon.png" style={{top: 40, left: 20, width: "50%"}}/>
|
||||
<a className="w-100 h-100 db pa2 no-underline" href="/~barttile">
|
||||
<p className="black white-d absolute f9" style={{ left: 8, top: 8 }}>BART</p>
|
||||
<img className="absolute" src="/~barttile/img/Temp-Bart-Icon.png" style={{top: 40, left: 20, width: "50%"}}/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
@ -17,4 +17,4 @@ export default class bartinfoTile extends Component {
|
||||
|
||||
}
|
||||
|
||||
window.bartinfoTile = bartinfoTile;
|
||||
window.barttileTile = barttileTile;
|
||||
|
@ -1,255 +0,0 @@
|
||||
/+ *server, default-agent
|
||||
/= index
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/bartinfo/index
|
||||
/| /html/
|
||||
/~ ~
|
||||
==
|
||||
/= tile-js
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/bartinfo/js/tile
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= script
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/bartinfo/js/index
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= style
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/bartinfo/css/index
|
||||
/| /css/
|
||||
/~ ~
|
||||
==
|
||||
/= bartinfo-png
|
||||
/^ (map knot @)
|
||||
/: /===/app/bartinfo/img /_ /png/
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ bol=bowl:gall
|
||||
+* this .
|
||||
bartinfo-core +>
|
||||
cc ~(. bartinfo-core bol)
|
||||
def ~(. (default-agent this %|) bol)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
=/ launcha [%launch-action !>([%add %bartinfo / '/~bartinfo/js/tile.js'])]
|
||||
:_ this
|
||||
:~ [%pass / %arvo %e %connect [~ /'~bartinfo'] %bartinfo]
|
||||
[%pass /bartinfo %agent [our.bol %launch] %poke launcha]
|
||||
==
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%handle-http-request
|
||||
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
|
||||
:_ this
|
||||
%+ give-simple-payload:app eyre-id
|
||||
%+ require-authorization:app inbound-request
|
||||
poke-handle-http-request:cc
|
||||
%json
|
||||
=+ !<(jon=json vase)
|
||||
:_ this
|
||||
(poke-handle-json:cc jon)
|
||||
::
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
~& "on-watch path: {<path>}"
|
||||
?: ?=([%bartstations *] path)
|
||||
=/ bart-station-request
|
||||
=/ out *outbound-config:iris
|
||||
=/ req bart-api-request-stations:cc
|
||||
[%pass /bartstationrequest %arvo %i %request req out]
|
||||
[~[bart-station-request] this]
|
||||
?: ?=([%elevators *] path)
|
||||
=/ elevator-status-request
|
||||
=/ out *outbound-config:iris
|
||||
=/ req bart-api-elevator-status:cc
|
||||
[%pass /elevators %arvo %i %request req out]
|
||||
[~[elevator-status-request] this]
|
||||
?: ?=([%routes *] path)
|
||||
[[~] this]
|
||||
?: ?=([%http-response *] path)
|
||||
`this
|
||||
?. =(/ path)
|
||||
(on-watch:def path)
|
||||
[[%give %fact ~ %json !>(*json)]~ this]
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
~& "The on-arvo wire: {<wire>}"
|
||||
?: ?=(%http-response +<.sign-arvo)
|
||||
=/ http-moves=(list card)
|
||||
?+ wire ~
|
||||
[%bartstationrequest *]
|
||||
=/ value=json (parse-request-stations-response:cc client-response.sign-arvo)
|
||||
?> ?=(%o -.value)
|
||||
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
||||
[%give %fact ~[/bartstations] %json !>(update)]~
|
||||
::
|
||||
[%elevators *]
|
||||
=/ value=json (parse-elevator-status-response:cc client-response.sign-arvo)
|
||||
?> ?=(%o -.value)
|
||||
=/ update=json (pairs:enjs:format [update+o+p.value ~])
|
||||
[%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]
|
||||
?. ?=(%bound +<.sign-arvo)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
[~ this]
|
||||
::
|
||||
++ on-save on-save:def
|
||||
++ on-load on-load:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
::
|
||||
:: request to http://api.bart.gov/api/stn.aspx?cmd=stns&key=Q5RQ-PUEB-999T-DWEI&json=y
|
||||
:: get .root | .stations | .station for list of stations
|
||||
++ bart-api-key "Q5RQ-PUEB-999T-DWEI"
|
||||
++ bart-api-url-base "http://api.bart.gov/api"
|
||||
++ with-json-handler
|
||||
|= [response=client-response:iris jsonhandler=$-(json json)]
|
||||
^- json
|
||||
=, format
|
||||
?. ?=(%finished -.response)
|
||||
%- pairs:enjs [fulltext+s+'bart response error' ~]
|
||||
=/ data=(unit mime-data:iris) full-file.response
|
||||
?~ data %- pairs:enjs ~
|
||||
=/ ujon=(unit json) (de-json:html q.data.u.data)
|
||||
?~ ujon %- pairs:enjs ~
|
||||
?> ?=(%o -.u.ujon)
|
||||
=/ parsed-json=json u.ujon
|
||||
(jsonhandler parsed-json)
|
||||
++ bart-api-request-stations
|
||||
^- request:http
|
||||
=/ url (crip "{bart-api-url-base}/stn.aspx?cmd=stns&key={bart-api-key}&json=y")
|
||||
=/ headers [['Accept' 'application/json']]~
|
||||
[%'GET' url headers *(unit octs)]
|
||||
::
|
||||
++ parse-request-stations-response
|
||||
|= response=client-response:iris
|
||||
^- json
|
||||
=, format
|
||||
=/ handler |= jon=json
|
||||
=/ root ((ot:dejs ~[['root' same]]) jon)
|
||||
=/ stations ((ot:dejs ~[['stations' same]]) root)
|
||||
=/ station ((ot:dejs ~[['station' (ar:dejs same)]]) stations)
|
||||
=/ abbr-and-name %- turn :- station |= item=json
|
||||
^- json
|
||||
=/ [name=tape abbr=tape]
|
||||
((ot:dejs ~[['name' sa:dejs] ['abbr' sa:dejs]]) item)
|
||||
(pairs:enjs ~[name+(tape:enjs name) abbr+(tape:enjs abbr)])
|
||||
(pairs:enjs [[%stations %a abbr-and-name] ~])
|
||||
(with-json-handler response handler)
|
||||
::
|
||||
++ bart-api-elevator-status
|
||||
^- request:http
|
||||
=/ url (crip "{bart-api-url-base}/bsa.aspx?cmd=elev&key={bart-api-key}&json=y")
|
||||
=/ headers [['Accept' 'application/json']]~
|
||||
[%'GET' url headers *(unit octs)]
|
||||
++ parse-elevator-status-response
|
||||
|= response=client-response:iris
|
||||
^- json
|
||||
=, format
|
||||
=/ handler |= jon=json
|
||||
=/ root=json ((ot:dejs ~[['root' same]]) jon)
|
||||
=/ bsa=(list json) ((ot:dejs ~[['bsa' (ar:dejs same)]]) root)
|
||||
(pairs:enjs [[%elevators %a bsa] ~])
|
||||
(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
|
||||
|= =inbound-request:eyre
|
||||
^- simple-payload:http
|
||||
=+ url=(parse-request-line url.request.inbound-request)
|
||||
?+ site.url not-found:gen
|
||||
[%'~bartinfo' %css %index ~] (css-response:gen style)
|
||||
[%'~bartinfo' %js %tile ~] (js-response:gen tile-js)
|
||||
[%'~bartinfo' %js %index ~] (js-response:gen script)
|
||||
::
|
||||
[%'~bartinfo' %img @t *]
|
||||
=/ name=@t i.t.t.site.url
|
||||
=/ img (~(get by bartinfo-png) name)
|
||||
?~ img
|
||||
not-found:gen
|
||||
(png-response:gen (as-octs:mimes:html u.img))
|
||||
::
|
||||
[%'~bartinfo' *] (html-response:gen index)
|
||||
==
|
||||
--
|
Before Width: | Height: | Size: 5.3 MiB |
113
urbit/app/barttile.hoon
Normal file
@ -0,0 +1,113 @@
|
||||
/+ *server, default-agent
|
||||
/= index
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/barttile/index
|
||||
/| /html/
|
||||
/~ ~
|
||||
==
|
||||
/= tile-js
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/barttile/js/tile
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= script
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/barttile/js/index
|
||||
/| /js/
|
||||
/~ ~
|
||||
==
|
||||
/= style
|
||||
/^ octs
|
||||
/; as-octs:mimes:html
|
||||
/: /===/app/barttile/css/index
|
||||
/| /css/
|
||||
/~ ~
|
||||
==
|
||||
/= barttile-png
|
||||
/^ (map knot @)
|
||||
/: /===/app/barttile/img /_ /png/
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
--
|
||||
^- agent:gall
|
||||
=<
|
||||
|_ bol=bowl:gall
|
||||
+* this .
|
||||
barttile-core +>
|
||||
cc ~(. barttile-core bol)
|
||||
def ~(. (default-agent this %|) bol)
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
=/ launcha [%launch-action !>([%add %barttile / '/~barttile/js/tile.js'])]
|
||||
:_ this
|
||||
:~ [%pass / %arvo %e %connect [~ /'~barttile'] %barttile]
|
||||
[%pass /barttile %agent [our.bol %launch] %poke launcha]
|
||||
==
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bol src.bol)
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%handle-http-request
|
||||
=+ !<([eyre-id=@ta =inbound-request:eyre] vase)
|
||||
:_ this
|
||||
%+ give-simple-payload:app eyre-id
|
||||
%+ require-authorization:app inbound-request
|
||||
poke-handle-http-request:cc
|
||||
::
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall _this)
|
||||
?: ?=([%http-response *] path)
|
||||
`this
|
||||
?. =(/ path)
|
||||
(on-watch:def path)
|
||||
[[%give %fact ~ %json !>(*json)]~ this]
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card _this)
|
||||
?. ?=(%bound +<.sign-arvo)
|
||||
(on-arvo:def wire sign-arvo)
|
||||
[~ this]
|
||||
::
|
||||
++ on-save on-save:def
|
||||
++ on-load on-load:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
||||
::
|
||||
::
|
||||
|_ bol=bowl:gall
|
||||
::
|
||||
++ poke-handle-http-request
|
||||
|= =inbound-request:eyre
|
||||
^- simple-payload:http
|
||||
=+ url=(parse-request-line url.request.inbound-request)
|
||||
?+ site.url not-found:gen
|
||||
[%'~barttile' %css %index ~] (css-response:gen style)
|
||||
[%'~barttile' %js %tile ~] (js-response:gen tile-js)
|
||||
[%'~barttile' %js %index ~] (js-response:gen script)
|
||||
::
|
||||
[%'~barttile' %img @t *]
|
||||
=/ name=@t i.t.t.site.url
|
||||
=/ img (~(get by barttile-png) name)
|
||||
?~ img
|
||||
not-found:gen
|
||||
(png-response:gen (as-octs:mimes:html u.img))
|
||||
::
|
||||
[%'~barttile' *] (html-response:gen index)
|
||||
==
|
||||
::
|
||||
--
|
1
urbit/app/barttile/css/index.css
Normal file
BIN
urbit/app/barttile/img/BART-Map-Sunday.png
Normal file
After Width: | Height: | Size: 4.5 MiB |
BIN
urbit/app/barttile/img/BART-Map-Weekday-Saturday.png
Normal file
After Width: | Height: | Size: 4.5 MiB |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -1,16 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>bartinfo</title>
|
||||
<title>barttile</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||
<link rel="stylesheet" href="/~bartinfo/css/index.css" />
|
||||
<link rel="stylesheet" href="/~barttile/css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
<script src="/~/channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~bartinfo/js/index.js"></script>
|
||||
<script src="/~barttile/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|