updated stations
@ -91,3 +91,54 @@ li {
|
||||
margin: 6px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/* The snackbar - position it at the bottom and in the middle of the screen */
|
||||
#snackbar {
|
||||
visibility: hidden; /* Hidden by default. Visible on click */
|
||||
min-width: 250px; /* Set a default minimum width */
|
||||
margin-left: -125px; /* Divide value of min-width by 2 */
|
||||
background-color: #333; /* Black background color */
|
||||
color: #fff; /* White text color */
|
||||
text-align: center; /* Centered text */
|
||||
border-radius: 2px; /* Rounded borders */
|
||||
padding: 16px; /* Padding */
|
||||
position: fixed; /* Sit on top of the screen */
|
||||
z-index: 1; /* Add a z-index if needed */
|
||||
left: 50%; /* Center the snackbar */
|
||||
bottom: 30px; /* 30px from the bottom */
|
||||
}
|
||||
|
||||
/* Show the snackbar when clicking on a button (class added with JavaScript) */
|
||||
#snackbar.show {
|
||||
visibility: visible; /* Show the snackbar */
|
||||
|
||||
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
|
||||
However, delay the fade out process for 2.5 seconds */
|
||||
/* -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;*/
|
||||
|
||||
-webkit-animation: fadein 0.5s;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
|
||||
/* Animations to fade the snackbar in and out */
|
||||
@-webkit-keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
||||
|
BIN
src/img/Icon-144.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
src/img/Icon-192.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/img/Icon-36.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/img/Icon-48.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/img/Icon-512.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
src/img/Icon-72.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/img/Icon-96.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
@ -33,6 +33,8 @@
|
||||
|
||||
<div id='trainResults' class="mui--hide"></div>
|
||||
</div>
|
||||
|
||||
<div id="snackbar">⚡ Offline, waiting to reconnect...</div>
|
||||
<!--<script src="//cdn.muicss.com/mui-0.9.26/js/mui.min.js"></script>-->
|
||||
<script src="js/vendor.js" async></script>
|
||||
<script src="js/bundle.js" async></script>
|
||||
|
@ -1,12 +1,13 @@
|
||||
require('muicss');
|
||||
|
||||
const $ = require('jquery');
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
const { TrainModel, TrainView } = require('./train');
|
||||
const { RouteModel, RouteView } = require('./route');
|
||||
|
||||
const Minibus = require('minibus');
|
||||
|
||||
(function () {
|
||||
const bus = Minibus.create();
|
||||
let offline = false;
|
||||
const $snackbar = $('#snackbar');
|
||||
|
||||
const app = {
|
||||
'routes' : [
|
||||
@ -20,15 +21,33 @@ const Minibus = require('minibus');
|
||||
|
||||
};
|
||||
|
||||
const routeView = new RouteView( { 'model': new RouteModel() });
|
||||
|
||||
app.eventBus = _.extend({}, Backbone.Events);
|
||||
|
||||
app.eventBus.on('offline', function() {
|
||||
if (offline === false) {
|
||||
offline = true;
|
||||
// console.log('Set it once!');
|
||||
$snackbar.addClass('show');
|
||||
}
|
||||
}, this);
|
||||
|
||||
app.eventBus.on('online', function() {
|
||||
if (offline === true) {
|
||||
offline = false;
|
||||
// console.log('Set it once!');
|
||||
$snackbar.removeClass('show');
|
||||
}
|
||||
}, this);
|
||||
|
||||
app.routeView = new RouteView( { 'model': new RouteModel(), 'eventBus': app.eventBus });
|
||||
|
||||
app.createViews = function() {
|
||||
for (const route of this.routes) {
|
||||
console.log(route);
|
||||
var key = Symbol(route.from + route.to);
|
||||
console.log(key);
|
||||
|
||||
this.views[key] = new TrainView({ 'model': new TrainModel(route), 'routeView': routeView });
|
||||
// console.log(route);
|
||||
const key = Symbol(route.from + route.to);
|
||||
// console.log(key);
|
||||
route.bus = this.eventBus;
|
||||
this.views[key] = new TrainView({ 'model': new TrainModel(route), 'eventBus': app.eventBus });
|
||||
}
|
||||
};
|
||||
|
||||
@ -40,9 +59,4 @@ const Minibus = require('minibus');
|
||||
});
|
||||
|
||||
app.createViews();
|
||||
|
||||
/* app.views.dbqglqView = new TrainView({ 'model': new TrainModel({ 'from': 'dbe', 'to': 'glq' }) });
|
||||
app.views.glqdbeView = new TrainView({ 'model': new TrainModel({ 'from': 'glq', 'to': 'dbe' }) });
|
||||
app.views.glqhymView = new TrainView({ 'model': new TrainModel({ 'from': 'glq', 'to': 'hym' }) });
|
||||
app.views.hymglqView = new TrainView({ 'model': new TrainModel({ 'from': 'hym', 'to': 'glq' }) });*/
|
||||
})();
|
||||
|
137
src/js/route.js
@ -6,19 +6,24 @@ const RouteModel = Backbone.Model.extend({
|
||||
'initialize': function () {
|
||||
const fromStation = this.get('from');
|
||||
const toStation = this.get('to');
|
||||
const routeUrl = `/gettrainsbob?from=${ this.get('from') }&to=${ this.get('to')}`;
|
||||
const routeUrl = `/gettrains?from=${ this.get('from') }&to=${ this.get('to')}`;
|
||||
const target = this.get('from') + this.get('to');
|
||||
this.set('url', routeUrl);
|
||||
this.set('routeUrl', routeUrl);
|
||||
this.set('target', target);
|
||||
this.set('visible', false);
|
||||
this.set('trainData', { 'eta':'OFF', 'sta': 'OFF' });
|
||||
this.set('interval', null);
|
||||
this.update();
|
||||
},
|
||||
'update': function () {
|
||||
console.log('Route model update');
|
||||
if (!this.get('to'))
|
||||
return;
|
||||
|
||||
const now = new Date;
|
||||
const hours = now.getHours();
|
||||
const limit = (hours < 6) ? 3600000 : 60000;
|
||||
const limit = (hours < 6) ? 3600000 : 95000;
|
||||
|
||||
const mod = limit - (now.getTime() % limit);
|
||||
|
||||
@ -28,45 +33,44 @@ const RouteModel = Backbone.Model.extend({
|
||||
this.set('trainData', { 'eta':'OFF', 'sta': 'OFF' });
|
||||
|
||||
const routeUpdateFn = function () {
|
||||
console.log('routeUpdateFn');
|
||||
this.update();
|
||||
};
|
||||
|
||||
setTimeout(routeUpdateFn.bind(this), mod + 10);
|
||||
|
||||
if (!this.get('interval'))
|
||||
this.set('interval', setInterval(routeUpdateFn.bind(this), mod + 10)) ;
|
||||
},
|
||||
'getRoute': function () {
|
||||
const url = this.get('routeUrl');
|
||||
const url = `/gettrains?from=${ this.get('from') }&to=${ this.get('to')}`;
|
||||
const self = this;
|
||||
|
||||
if (this.get('visible') === true)
|
||||
this.set('visible', false);
|
||||
else
|
||||
$.ajax({
|
||||
'type': 'GET',
|
||||
'url': url,
|
||||
'data': '',
|
||||
'dataType': 'json',
|
||||
$.ajax({
|
||||
'type': 'GET',
|
||||
'url': url,
|
||||
'data': '',
|
||||
'dataType': 'json',
|
||||
|
||||
'timeout': 10000,
|
||||
'headers': {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
'timeout': 10000,
|
||||
'headers': {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
|
||||
},
|
||||
'success': function (data) {
|
||||
// getTrainsCB(data);
|
||||
// console.log('Got', data);
|
||||
},
|
||||
'success': function (data) {
|
||||
// getTrainsCB(data);
|
||||
console.log('Got', data);
|
||||
|
||||
self.set('route', data);
|
||||
self.set('visible', true);
|
||||
},
|
||||
'error': function (xhr, type) {
|
||||
console.error('ajax error');
|
||||
console.log('readyState', xhr.readyState);
|
||||
console.log('status', xhr.status);
|
||||
console.error(type);
|
||||
}
|
||||
});
|
||||
self.set('route', data);
|
||||
self.set('visible', true);
|
||||
},
|
||||
'error': function (xhr, type) {
|
||||
console.error('ajax error');
|
||||
console.log('readyState', xhr.readyState);
|
||||
console.log('status', xhr.status);
|
||||
console.error(type);
|
||||
}
|
||||
});
|
||||
},
|
||||
'setRoute': function(from, to) {
|
||||
this.set('from', from);
|
||||
@ -76,21 +80,84 @@ const RouteModel = Backbone.Model.extend({
|
||||
|
||||
const RouteView = Backbone.View.extend({
|
||||
'tagName': 'div',
|
||||
'initialize': function () {
|
||||
'initialize': function (options) {
|
||||
_.bindAll(this, 'render');
|
||||
this.model.bind('change', this.render);
|
||||
this.eventBus = options.eventBus;
|
||||
// this.model.bind('change', this.render);
|
||||
/* this.model.bind('change:route', function(e){
|
||||
console.log('event:', e);
|
||||
});*/
|
||||
this.listenTo(this.model, 'change:route', _.debounce(_.bind(this.render), 128));
|
||||
this.$trains = $('#trains');
|
||||
this.$traininfo = $('#traininfo');
|
||||
this.$traintext = $('#trainResults');
|
||||
this.$el = this.$traintext;
|
||||
|
||||
this.eventBus.on('showRoute', function(d) {
|
||||
console.log('Showroute', d);
|
||||
|
||||
this.model.set('to', d.to);
|
||||
this.model.set('from', d.from);
|
||||
this.model.update();
|
||||
}, this);
|
||||
|
||||
this.initView();
|
||||
},
|
||||
'events': {
|
||||
'click': 'showTrains'
|
||||
|
||||
},
|
||||
'render': function () {
|
||||
console.log('Routeview:Render');
|
||||
const route = this.model.get('route');
|
||||
|
||||
console.log(route);
|
||||
|
||||
let ws = `<div class="mui--text-center mui--text-accent">${route.locationName} TO ${route.filterLocationName}</div>
|
||||
<table class="mui-table mui-table-bordered">
|
||||
<tr><th>Destination</th>
|
||||
<th>Time</th>
|
||||
<th>Status</th>
|
||||
<th>Platform</th></tr>
|
||||
`;
|
||||
|
||||
const services = [];
|
||||
if (typeof route.trainServices === 'object' && route.trainServices !== null)
|
||||
for (const item of route.trainServices) {
|
||||
const dest = item.destination[0];
|
||||
const via = dest.via !== null ? `<em class="mui--text-accent">${dest.via}</em>` : '';
|
||||
const platform = item.platform !== null ? item.platform : '💠';
|
||||
const time = item.sta !== null ? item.sta : `D ${item.std}`;
|
||||
const status = item.eta !== null ? item.eta : item.etd;
|
||||
|
||||
const statusMode = (item.eta.toLowerCase() === 'on time') ? 'ontime' : 'delayed';
|
||||
|
||||
services.push({ 'location':dest.locationName, 'time':time, 'status':status, 'platform':platform, 'cancel':item.cancelReason, 'type':'train' });
|
||||
if (!item.isCancelled)
|
||||
ws = `${ws }<tr><td>${dest.locationName} ${via}</td>
|
||||
<td>${time}</td>
|
||||
<td class="${statusMode}">${status}</td>
|
||||
<td>${platform}</td>
|
||||
</tr>`;
|
||||
else
|
||||
ws = `${ws }<tr><td>${dest.locationName} ${via}</td><td>${time}</td>
|
||||
<td colspan="2" class="delayed">❌ ${item.cancelReason}</td></tr>`;
|
||||
}
|
||||
|
||||
if (typeof route.busServices === 'object' && route.busServices !== null)
|
||||
for (const item of route.busServices) {
|
||||
const dest = item.destination[0];
|
||||
const via = dest.via !== null ? `<em>${dest.via}</em>` : '';
|
||||
const platform = item.platform !== null ? item.platform : '';
|
||||
const time = item.sta !== null ? item.sta : `D ${item.std}`;
|
||||
const status = item.eta !== null ? item.eta : item.etd;
|
||||
services.push({ 'location':dest.locationName, 'time':time, 'status':status, 'platform':platform, 'cancel':item.cancelReason, 'type':'bus' });
|
||||
|
||||
ws = `${ws }<tr><td>🚌 ${dest.locationName} ${via}</td><td>${time}</td><td>${status}</td><td>${platform}</td></tr>`;
|
||||
}
|
||||
|
||||
ws = `${ws }</table>`;
|
||||
this.$traintext.empty().html(ws);
|
||||
this.$traintext.removeClass('mui--hide').addClass('mui--show');
|
||||
},
|
||||
'initView': function () {
|
||||
},
|
||||
|
@ -44,6 +44,7 @@ const TrainModel = Backbone.Model.extend({
|
||||
'getTrain': function () {
|
||||
const url = this.get('url');
|
||||
const self = this;
|
||||
const bus = this.get('bus');
|
||||
$.ajax({
|
||||
'type': 'GET',
|
||||
'url': url,
|
||||
@ -56,13 +57,19 @@ const TrainModel = Backbone.Model.extend({
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
},
|
||||
'success': function (data) {
|
||||
bus.trigger('online');
|
||||
self.set('trainData', data);
|
||||
},
|
||||
'error': function (xhr, type) {
|
||||
/*
|
||||
console.log('ajax error');
|
||||
console.log('readyState', xhr.readyState);
|
||||
console.log('status', xhr.status);
|
||||
console.log(type);
|
||||
*/
|
||||
if (xhr.readyState === 0 && xhr.status === 0)
|
||||
|
||||
bus.trigger('offline');
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -95,8 +102,8 @@ const TrainModel = Backbone.Model.extend({
|
||||
},
|
||||
'error': function (xhr, type) {
|
||||
console.error('ajax error');
|
||||
console.error(xhr);
|
||||
console.error(type);
|
||||
/*console.error(xhr);
|
||||
console.error(type);*/
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -104,15 +111,17 @@ const TrainModel = Backbone.Model.extend({
|
||||
|
||||
const TrainView = Backbone.View.extend({
|
||||
'tagName': 'div',
|
||||
'initialize': function () {
|
||||
'initialize': function (options) {
|
||||
_.bindAll(this, 'render');
|
||||
this.eventBus = options.eventBus;
|
||||
this.model.bind('change', this.render);
|
||||
this.$trains = $('#trains');
|
||||
this.$traininfo = $('#traininfo');
|
||||
this.$traintext = $('#trainResults');
|
||||
this.$el = this.$trains;
|
||||
this.initView();
|
||||
// console.log(this.get('routeView'));
|
||||
// console.log(this.get('routeView'));
|
||||
// console.log(this);
|
||||
},
|
||||
'events': {
|
||||
'click': 'showTrains'
|
||||
@ -128,8 +137,8 @@ const TrainView = Backbone.View.extend({
|
||||
this.$button.html(output);
|
||||
this.$button.removeClass('delayed').removeClass('ontime').addClass(status);
|
||||
|
||||
if (visible) {
|
||||
let ws = `<div>${route.locationName} TO ${route.filterLocationName}</div>
|
||||
/*if (visible) {
|
||||
let ws = `<div class="mui--text-center mui--text-accent">${route.locationName} TO ${route.filterLocationName}</div>
|
||||
<table class="mui-table mui-table-bordered">
|
||||
<tr><th>Destination</th>
|
||||
<th>Time</th>
|
||||
@ -175,25 +184,27 @@ const TrainView = Backbone.View.extend({
|
||||
this.$traintext.removeClass('mui--hide').addClass('mui--show');
|
||||
}
|
||||
else
|
||||
this.$traintext.removeClass('mui--show').addClass('mui--hide');
|
||||
this.$traintext.removeClass('mui--show').addClass('mui--hide');*/
|
||||
},
|
||||
'initView': function () {
|
||||
|
||||
const self = this;
|
||||
const target = this.model.get('target');
|
||||
const html = `
|
||||
<div class="mui-row card">
|
||||
<div class='mui-col-xs-8 mui-col-md-8 entry'>${target.toUpperCase()}</div>
|
||||
<div class='mui-col-xs-4 mui-col-md-4'>
|
||||
<div class='mui-col-xs-7 mui-col-md-7 entry'>${target.toUpperCase()}</div>
|
||||
<div class='mui-col-xs-5 mui-col-md-5 mui--text-right'>
|
||||
<button class="mui-btn mui-btn--flat" id="${target}"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
this.$html = $(html);
|
||||
this.$html.on('click', function () {
|
||||
this.$html.on('click', () => {
|
||||
// console.log('from', self.model.get('from'));
|
||||
// console.log('to', self.model.get('to'));
|
||||
self.eventBus.trigger('showRoute', { 'from': self.model.get('from'), 'to': self.model.get('to') });
|
||||
// console.log(self)
|
||||
self.model.getRoute();
|
||||
// self.model.getRoute();
|
||||
});
|
||||
this.$trains.append(this.$html);
|
||||
|
||||
@ -209,7 +220,8 @@ const TrainView = Backbone.View.extend({
|
||||
this.events[cevent] = 'showTrains';
|
||||
},
|
||||
'showTrains': function () {
|
||||
console.log('Show train');
|
||||
// console.log('Show train');
|
||||
this.eventBus.trigger('showRoute');
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -3,9 +3,39 @@
|
||||
"short_name": "Train Times",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/img/android-chrome-192x192.png",
|
||||
"src": "/img/Icon-36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/img/Icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
|
@ -26,7 +26,14 @@ const filesToCache = [
|
||||
'/fonts/Roboto_Condensed-normal-400.woff',
|
||||
'/fonts/Roboto_Slab-normal-400.woff',
|
||||
'/js/bundle.js',
|
||||
'/js/vendor.js'
|
||||
'/js/vendor.js',
|
||||
'/img/Icon-36.png',
|
||||
'/img/Icon-48.png',
|
||||
'/img/Icon-72.png',
|
||||
'/img/Icon-96.png',
|
||||
'/img/Icon-144.png',
|
||||
'/img/Icon-192.png',
|
||||
'/img/Icon-512.png'
|
||||
];
|
||||
|
||||
self.addEventListener('install', function(e) {
|
||||
|