diff --git a/src/css/custom.scss b/src/css/custom.scss index b660186..d8123ea 100644 --- a/src/css/custom.scss +++ b/src/css/custom.scss @@ -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;} +} diff --git a/src/img/Icon-144.png b/src/img/Icon-144.png new file mode 100644 index 0000000..45e0459 Binary files /dev/null and b/src/img/Icon-144.png differ diff --git a/src/img/Icon-192.png b/src/img/Icon-192.png new file mode 100644 index 0000000..daf222c Binary files /dev/null and b/src/img/Icon-192.png differ diff --git a/src/img/Icon-36.png b/src/img/Icon-36.png new file mode 100644 index 0000000..b1ad90e Binary files /dev/null and b/src/img/Icon-36.png differ diff --git a/src/img/Icon-48.png b/src/img/Icon-48.png new file mode 100644 index 0000000..15d29c1 Binary files /dev/null and b/src/img/Icon-48.png differ diff --git a/src/img/Icon-512.png b/src/img/Icon-512.png new file mode 100644 index 0000000..fa3720b Binary files /dev/null and b/src/img/Icon-512.png differ diff --git a/src/img/Icon-72.png b/src/img/Icon-72.png new file mode 100644 index 0000000..649b07c Binary files /dev/null and b/src/img/Icon-72.png differ diff --git a/src/img/Icon-96.png b/src/img/Icon-96.png new file mode 100644 index 0000000..eace2dd Binary files /dev/null and b/src/img/Icon-96.png differ diff --git a/src/index.html b/src/index.html index 6548960..ee8eabb 100644 --- a/src/index.html +++ b/src/index.html @@ -33,6 +33,8 @@
+ +
⚡ Offline, waiting to reconnect...
diff --git a/src/js/app.js b/src/js/app.js index d94b256..7239008 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -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' }) });*/ })(); diff --git a/src/js/route.js b/src/js/route.js index 2e6857a..2194b37 100644 --- a/src/js/route.js +++ b/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 = `
${route.locationName} TO ${route.filterLocationName}
+ + + + + + `; + + 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 ? `${dest.via}` : ''; + 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 } + + + + `; + else + ws = `${ws } + `; + } + + if (typeof route.busServices === 'object' && route.busServices !== null) + for (const item of route.busServices) { + const dest = item.destination[0]; + const via = dest.via !== null ? `${dest.via}` : ''; + 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 }`; + } + + ws = `${ws }
DestinationTimeStatusPlatform
${dest.locationName} ${via}${time}${status}${platform}
${dest.locationName} ${via}${time}❌ ${item.cancelReason}
🚌 ${dest.locationName} ${via}${time}${status}${platform}
`; + this.$traintext.empty().html(ws); + this.$traintext.removeClass('mui--hide').addClass('mui--show'); }, 'initView': function () { }, diff --git a/src/js/train.js b/src/js/train.js index b27d08a..05057ae 100644 --- a/src/js/train.js +++ b/src/js/train.js @@ -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 = `
${route.locationName} TO ${route.filterLocationName}
+ /*if (visible) { + let ws = `
${route.locationName} TO ${route.filterLocationName}
@@ -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 = `
-
${target.toUpperCase()}
-
+
${target.toUpperCase()}
+
`; 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'); } }); diff --git a/src/manifest.json b/src/manifest.json index 4bb58c5..30f8da3 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -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", diff --git a/src/service-worker.js b/src/service-worker.js index a7e87eb..848bd46 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -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) {
Destination Time