init
55
.eslintrc.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "stroustrup", {}],
|
||||
"camelcase": "error",
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": [1, "last"],
|
||||
"consistent-this": [1, "_this"],
|
||||
"curly": [1, "multi"],
|
||||
"eol-last": 1,
|
||||
"eqeqeq": 1,
|
||||
"func-names": 1,
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||
"max-len": [1, 120, 2], // 2 spaces per tab, max 80 chars per line
|
||||
"new-cap": 1,
|
||||
"newline-before-return": "error",
|
||||
"no-array-constructor": 1,
|
||||
"no-inner-declarations": [1, "both"],
|
||||
"no-mixed-spaces-and-tabs": 1,
|
||||
"no-multi-spaces": 2,
|
||||
"no-new-object": 1,
|
||||
"no-shadow-restricted-names": 1,
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||
"prefer-const": "error",
|
||||
"prefer-template": "error",
|
||||
"one-var": 0,
|
||||
"quote-props": ["error", "always"],
|
||||
"quotes": [1, "single"],
|
||||
"radix": 1,
|
||||
"semi": [1, "always"],
|
||||
"space-before-blocks": [1, "always"],
|
||||
"space-infix-ops": 1,
|
||||
"vars-on-top": 1,
|
||||
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||
}
|
||||
|
||||
}
|
153
.gitignore
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
/src/bundle.js
|
||||
/src/bundle.js.map
|
||||
/src/react/bundle.js
|
||||
/src/backbone/bundle.js
|
||||
/src/react/bundle.js.map
|
||||
/src/es2016/bundle.js
|
||||
/src/es2016/bundle.js.map
|
||||
/src/backbone/bundle.js.map
|
||||
/live/*
|
35
gulp/backbone.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const browserify = require('browserify');
|
||||
const gulp = require('gulp');
|
||||
const source = require('vinyl-source-stream');
|
||||
const buffer = require('vinyl-buffer');
|
||||
const uglify = require('gulp-uglify-es').default;
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const gutil = require('gulp-util');
|
||||
const rename = require('gulp-rename');
|
||||
|
||||
gulp.task('bundleBackbone', function () {
|
||||
// set up the browserify instance on a task basis
|
||||
const b = browserify({
|
||||
'debug': true,
|
||||
'entries': './src/js/app.js'
|
||||
});
|
||||
|
||||
return b.bundle()
|
||||
.pipe(source('app.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(rename('bundle.js'))
|
||||
|
||||
.pipe(sourcemaps.init({ 'loadMaps': true }))
|
||||
// Add transformation tasks to the pipeline here.
|
||||
.pipe(uglify())
|
||||
.on('error', gutil.log)
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('./live/js'));
|
||||
});
|
||||
|
||||
gulp.task('buildBackbone', ['bundleBackbone'], function() {
|
||||
gulp.watch('src/js/**/*.js', ['bundleBackbone']);
|
||||
});
|
||||
|
65
gulp/build.js
Normal file
@ -0,0 +1,65 @@
|
||||
const gulp = require('gulp'),
|
||||
|
||||
autoprefixer = require('gulp-autoprefixer'),
|
||||
cssnano = require('gulp-cssnano'),
|
||||
uglify = require('gulp-uglify'),
|
||||
|
||||
rename = require('gulp-rename'),
|
||||
concat = require('gulp-concat'),
|
||||
cache = require('gulp-cache'),
|
||||
htmlmin = require('gulp-htmlmin'),
|
||||
inject = require('gulp-inject'),
|
||||
del = require('del'),
|
||||
htmlreplace = require('gulp-html-replace');
|
||||
|
||||
const scss = require('gulp-scss');
|
||||
const sass = require('gulp-sass');
|
||||
const googleWebFonts = require('gulp-google-webfonts');
|
||||
|
||||
const fontOptions = { };
|
||||
|
||||
gulp.task('styles', function() {
|
||||
return gulp.src(['src/css/common.css'])
|
||||
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
|
||||
|
||||
/* .pipe(gulp.dest('dist/css'))*/
|
||||
/* .pipe(rename({suffix: '.min'}))*/
|
||||
.pipe(cssnano())
|
||||
.pipe(gulp.dest('live/css'));
|
||||
});
|
||||
|
||||
gulp.task('copy', function() {
|
||||
gulp.src(['src/img/**/*']).pipe(gulp.dest('live/img'));
|
||||
gulp.src(['src/browserconfig.xml', 'src/manifest.json', 'src/service-worker.js']).pipe(gulp.dest('live'));
|
||||
gulp.src(['src/index.html']).pipe(gulp.dest('live'));
|
||||
});
|
||||
|
||||
gulp.task('clean', function() {
|
||||
return del(['live']);
|
||||
});
|
||||
|
||||
gulp.task('customMUI', function() {
|
||||
return gulp.src(['src/css/custom.scss'])
|
||||
.pipe(sass({ 'outputStyle': 'compressed' }).on('error', sass.logError))
|
||||
// .pipe(cssnano())
|
||||
.pipe(rename('mui.custom.css'))
|
||||
// .pipe(gulp.dest(`${dest}/css`));
|
||||
.pipe(gulp.dest('live/css'));
|
||||
});
|
||||
|
||||
gulp.task('vendor', function() {
|
||||
return gulp.src([
|
||||
'node_modules/muicss/dist/js/mui.min.js'
|
||||
])
|
||||
.pipe(concat('vendor.js'))
|
||||
|
||||
/* .pipe(uglify({ 'mangle': false }))*/
|
||||
.pipe(gulp.dest(`live/js`));
|
||||
});
|
||||
|
||||
gulp.task('fonts', function() {
|
||||
return gulp.src('src/fonts.list')
|
||||
.pipe(googleWebFonts(fontOptions))
|
||||
.pipe(gulp.dest(`live/fonts`))
|
||||
;
|
||||
});
|
7
gulpfile.js
Normal file
@ -0,0 +1,7 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const requireDir = require('require-dir');
|
||||
|
||||
requireDir('./gulp');
|
||||
|
||||
gulp.task('BuildAll', ['bundleBackbone', 'styles', 'copy', 'customMUI', 'vendor', 'fonts']);
|
13914
package-lock.json
generated
Normal file
64
package.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "traintimespwa",
|
||||
"version": "1.0.0",
|
||||
"description": "Train Times Progressive Web App",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"apicache": "^1.2.0",
|
||||
"babelify": "^8.0.0",
|
||||
"backbone": "^1.3.3",
|
||||
"browserify": "^14.5.0",
|
||||
"es6-promise": "^4.1.1",
|
||||
"express": "^4.16.2",
|
||||
"jquery": "^3.2.1",
|
||||
"log4js": "^2.4.1",
|
||||
"minibus": "^3.1.0",
|
||||
"muicss": "^0.9.33",
|
||||
"underscore": "^1.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.12.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"expect.js": "^0.3.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-babel": "^7.0.0",
|
||||
"gulp-browserify": "^0.5.1",
|
||||
"gulp-cache": "^1.0.1",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-cssnano": "^2.1.2",
|
||||
"gulp-google-webfonts": "0.0.14",
|
||||
"gulp-html-replace": "^1.6.2",
|
||||
"gulp-htmlmin": "^3.0.0",
|
||||
"gulp-inject": "^4.3.0",
|
||||
"gulp-jshint": "^2.0.4",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-scss": "^1.4.0",
|
||||
"gulp-sourcemaps": "^2.6.1",
|
||||
"gulp-streamify": "^1.0.2",
|
||||
"gulp-tasks": "0.0.2",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-uglify-es": "^0.1.3",
|
||||
"gulp-util": "^3.0.8",
|
||||
"gulp-webpack": "^1.5.0",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"mocha": "^3.5.3",
|
||||
"require-dir": "^0.3.2",
|
||||
"sinon": "^4.1.1",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.9.0"
|
||||
}
|
||||
}
|
28
server.js
Normal file
@ -0,0 +1,28 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const apicache = require('apicache');
|
||||
const logger = require('log4js').getLogger('Server');
|
||||
|
||||
const train = require('./server/lib/train');
|
||||
logger.level = 'debug';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const sitePath = 'live';
|
||||
|
||||
apicache.options({ 'debug': true });
|
||||
const cache = apicache.middleware;
|
||||
|
||||
app.use(express.static(path.join(__dirname, sitePath)));
|
||||
|
||||
app.use('/gettrains', train.getTrainTimes);
|
||||
app.use('/getnexttraintimes', train.getNextTrainTimes);
|
||||
app.use('/getroute', train.getRoute);
|
||||
|
||||
app.listen(port, (err) => {
|
||||
if (err)
|
||||
return logger.error('Server error:', err);
|
||||
|
||||
logger.info(`TrainTime Server is listening on ${port}`);
|
||||
});
|
169
server/lib/train.js
Normal file
@ -0,0 +1,169 @@
|
||||
// train.js
|
||||
const http = require('http');
|
||||
const logger = require('log4js').getLogger('train');
|
||||
const trainCache = {
|
||||
'last': {},
|
||||
'data': {}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'dbe_glq': function (req, res) {
|
||||
logger.info('DBE:GLQ request');
|
||||
|
||||
const now = new Date();
|
||||
const nowSeconds = (now.getHours() * (60 * 60)) + (now.getMinutes() * 60);
|
||||
|
||||
if (trainCache.last.dbeglq === null || nowSeconds !== trainCache.last.dbeglq)
|
||||
Query(function (a, b) {
|
||||
const ts = a.departures[0].service;
|
||||
const output = {};
|
||||
logger.debug(ts);
|
||||
|
||||
logger.debug(ts.sta);
|
||||
output.sta = ts.sta;
|
||||
output.eta = ts.eta;
|
||||
trainCache.data.dbeglq = output;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(trainCache.data.dbeglq));
|
||||
}, res, 'huxley.apphb.com', '/next/dbe/to/glq/1?accessToken=215b99fe-b237-4a01-aadc-cf315d6756d8');
|
||||
},
|
||||
'glq_dbe': function (req, res) {
|
||||
logger.info('GLQ:DBE request');
|
||||
|
||||
const now = new Date();
|
||||
const nowSeconds = (now.getHours() * (60 * 60)) + (now.getMinutes() * 60);
|
||||
|
||||
if (trainCache.last.glqdbe === null || nowSeconds !== trainCache.last.dbeglq)
|
||||
Query(function (a, b) {
|
||||
const ts = a.departures[0].service;
|
||||
const output = {};
|
||||
logger.debug(ts);
|
||||
// GLOBAL.lastcheck = now;
|
||||
logger.debug(ts.sta);
|
||||
// logger.debug(toSeconds(ts.sta));
|
||||
|
||||
output.sta = ts.sta;
|
||||
output.eta = ts.eta;
|
||||
trainCache.data.glqdbe = output;
|
||||
// trainCache.last.glqdbe = toSeconds(ts.sta);
|
||||
// console.log(ts);
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(trainCache.data.glqdbe));
|
||||
}, res, 'huxley.apphb.com', '/next/glq/to/dbe/1?accessToken=215b99fe-b237-4a01-aadc-cf315d6756d8');
|
||||
},
|
||||
'getTrainTimes': function (req, res) {
|
||||
// console.log(req);
|
||||
logger.info(`getTrainTimes: ${ JSON.stringify(req.query)}`);
|
||||
if (req.query.hasOwnProperty('from') && req.query.hasOwnProperty('from')) {
|
||||
const url = `/all/${ req.query.from }/to/${ req.query.to }/10?accessToken=215b99fe-b237-4a01-aadc-cf315d6756d8`;
|
||||
|
||||
Query(function (a, b) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(a));
|
||||
}, res, 'huxley.apphb.com', url);
|
||||
}
|
||||
else {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({}));
|
||||
}
|
||||
},
|
||||
'getNextTrainTimes': function (req, res) {
|
||||
logger.info(`getNextTrainTimes: ${ JSON.stringify(req.query)}`);
|
||||
let trainFrom, trainTo, trainToken, url;
|
||||
if (req.query.hasOwnProperty('from') && req.query.hasOwnProperty('from')) {
|
||||
trainFrom = req.query.from;
|
||||
trainTo = req.query.to;
|
||||
trainToken = trainFrom + trainTo;
|
||||
url = `/next/${ trainFrom }/to/${ trainTo }/1?accessToken=215b99fe-b237-4a01-aadc-cf315d6756d8`;
|
||||
logger.info(`Requesting latest time for : ${ trainToken}`);
|
||||
|
||||
const now = new Date();
|
||||
const nowSeconds = (now.getHours() * (60 * 60)) + (now.getMinutes() * 60);
|
||||
logger.info(`Now Seconds: ${ nowSeconds}`);
|
||||
if (trainCache.last[trainToken] === null || nowSeconds !== trainCache.last[trainToken])
|
||||
|
||||
Query(function (a, b) {
|
||||
const output = {};
|
||||
const ts = a.departures[0].service;
|
||||
if (ts !== null) {
|
||||
// console.log(ts);
|
||||
// GLOBAL.lastcheck = now;
|
||||
logger.debug(ts.sta, ts.std);
|
||||
// logger.debug(toSeconds(ts.sta));
|
||||
|
||||
output.sta = (ts.sta !== null) ? ts.sta : ts.std;
|
||||
output.eta = (ts.eta !== null ? ts.eta : ts.etd);
|
||||
|
||||
// trainCache.last.glqdbe = toSeconds(ts.sta);
|
||||
// console.log(ts);
|
||||
}
|
||||
else {
|
||||
logger.warn('*** NO SERVICE');
|
||||
output.sta = 'No Service';
|
||||
output.eta = 'No Service';
|
||||
}
|
||||
|
||||
trainCache.data[trainToken] = output;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(trainCache.data[trainToken]));
|
||||
}, res, 'huxley.apphb.com', url);
|
||||
}
|
||||
}, 'getRoute': function (req, res) {
|
||||
logger.info(`getRoute: ${ JSON.stringify(req.query)}`);
|
||||
let routeID;
|
||||
let data = {};
|
||||
if (req.query.hasOwnProperty('route')) {
|
||||
routeID = req.query.route;
|
||||
Query(function (a, b) {
|
||||
if (a !== null && a.message === null)
|
||||
data = a;
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(data));
|
||||
}, res, 'huxley.apphb.com', `/service/${ routeID }?accessToken=215b99fe-b237-4a01-aadc-cf315d6756d8`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function toSeconds(inval) {
|
||||
console.log('inval', typeof inval);
|
||||
if (typeof inval === 'string') {
|
||||
const a = inval.split(':');
|
||||
|
||||
return ((parseInt(a[0]) * (60 * 60)) + (parseInt(a[1]) * 60));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function Query(callback, r, host, path) {
|
||||
logger.debug(path);
|
||||
const req = r;
|
||||
const options = {
|
||||
'host': host,
|
||||
// port: 80,
|
||||
'path': path,
|
||||
// method: 'GET',
|
||||
'headers': {}
|
||||
};
|
||||
|
||||
try {
|
||||
http.request(options).on('response', function (response) {
|
||||
let data = '';
|
||||
response.on('data', function (chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
response.on('end', function () {
|
||||
callback(JSON.parse(data), r);
|
||||
});
|
||||
response.on('error', function (e) {
|
||||
console.error(e);
|
||||
});
|
||||
}).end();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
9
src/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/img/mstile-150x150.png"/>
|
||||
<TileColor>#2b5797</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
88
src/css/common.css
Normal file
@ -0,0 +1,88 @@
|
||||
body {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
min-height:48px;
|
||||
margin: 8px;
|
||||
border-bottom-color: #666666;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.mui--text-display4, .mui--text-display3 {
|
||||
font-family: "Roboto Slab", "Helvetica Neue", Helvetica, Arial;
|
||||
}
|
||||
|
||||
.temp0, .temp1, .temp2, .temp3, .temp4, .temp5 {
|
||||
color: rgb(80,181,221)
|
||||
}
|
||||
|
||||
.temp6 {
|
||||
color: rgb(78,178,206)
|
||||
}
|
||||
|
||||
.temp7 {
|
||||
color: rgb(76, 176, 190)
|
||||
}
|
||||
|
||||
.temp8 {
|
||||
color: rgb(73, 173, 175)
|
||||
}
|
||||
|
||||
.temp9 {
|
||||
color: rgb(72, 171, 159)
|
||||
}
|
||||
|
||||
.temp10 {
|
||||
color: rgb(70, 168, 142)
|
||||
}
|
||||
|
||||
.temp11 {
|
||||
color: rgb(68, 166, 125)
|
||||
}
|
||||
|
||||
.temp12 {
|
||||
color: rgb(66, 164, 108)
|
||||
}
|
||||
|
||||
.temp13 {
|
||||
color: rgb(102, 173, 94)
|
||||
}
|
||||
|
||||
.temp14 {
|
||||
color: rgb(135, 190, 64)
|
||||
}
|
||||
|
||||
.temp15 {
|
||||
color: rgb(179, 204, 26)
|
||||
}
|
||||
|
||||
.temp16 {
|
||||
color: rgb(214, 213, 28)
|
||||
}
|
||||
|
||||
.temp17 {
|
||||
color: rgb(249, 202, 3)
|
||||
}
|
||||
|
||||
.temp18 {
|
||||
color: rgb(246, 181, 3)
|
||||
}
|
||||
|
||||
.temp19 {
|
||||
color: rgb(244, 150, 26)
|
||||
}
|
||||
|
||||
.temp20 {
|
||||
color: rgb(236, 110, 5)
|
||||
}
|
||||
|
||||
.day {
|
||||
font-family: "Roboto Slab", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.summary::first-letter {
|
||||
text-transform: capitalize
|
||||
}
|
93
src/css/custom.scss
Normal file
@ -0,0 +1,93 @@
|
||||
// import MUI colors
|
||||
@import "./node_modules/muicss/lib/sass/mui/colors";
|
||||
|
||||
// customize MUI variables
|
||||
$mui-primary-color: mui-color('blue-grey', '500');
|
||||
$mui-primary-color-dark: mui-color('blue-grey', '700');
|
||||
$mui-primary-color-light: mui-color('blue-grey', '100');
|
||||
|
||||
$mui-accent-color: mui-color('deep-purple', '900');
|
||||
$mui-accent-color-dark: mui-color('indigo', 'A100');
|
||||
$mui-accent-color-light: mui-color('indigo', 'A400');
|
||||
|
||||
$mui-base-font-family: 'Roboto Slab', "Helvetica Neue", Helvetica, Arial, Verdana,"Trebuchet MS";
|
||||
|
||||
// import MUI SASS
|
||||
@import "./node_modules/muicss/lib/sass/mui";
|
||||
|
||||
////
|
||||
|
||||
body {
|
||||
background-color:mui-color('grey', '100');
|
||||
}
|
||||
|
||||
#header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
transition: left 0.2s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0 4px 0 0;
|
||||
}
|
||||
|
||||
.dates {
|
||||
padding: 2px;
|
||||
border: solid 1px #80007e;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#btc, #fx, #trend {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.up, .ontime, .trendUp {
|
||||
color: mui-color('green') !important;
|
||||
}
|
||||
|
||||
.down, .delayed, .trendDown {
|
||||
color: mui-color('red') !important;
|
||||
}
|
||||
|
||||
.nochange {
|
||||
color: #000000;
|
||||
}
|
||||
.password {
|
||||
border: 1px solid mui-color('grey','400');
|
||||
background-color: mui-color('grey','200');
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.trendUp:before {
|
||||
content: "▲";
|
||||
}
|
||||
|
||||
.trendDown:before{content:'▼'}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
min-height:48px;
|
||||
margin: 8px;
|
||||
border-bottom-color: #666666;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.entry {
|
||||
height: 36px;
|
||||
margin: 6px 0;
|
||||
vertical-align: middle;
|
||||
}
|
2
src/fonts.list
Normal file
@ -0,0 +1,2 @@
|
||||
Roboto+Slab
|
||||
Roboto+Condensed
|
BIN
src/img/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
src/img/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/img/favicon-16x16.png
Normal file
After Width: | Height: | Size: 984 B |
BIN
src/img/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/img/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/img/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
33
src/img/safari-pinned-tab.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="192.000000pt" height="192.000000pt" viewBox="0 0 192.000000 192.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,192.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M440 1789 c-80 -4 -104 -10 -149 -35 -63 -34 -117 -94 -145 -162 -19
|
||||
-45 -20 -74 -20 -637 l0 -590 25 -56 c32 -67 91 -128 156 -160 l48 -23 605 -1
|
||||
605 0 53 28 c69 35 118 86 151 156 l26 56 0 585 c-1 534 -2 590 -18 637 -33
|
||||
94 -133 180 -231 199 -47 8 -969 11 -1106 3z m389 -79 c21 0 51 -44 51 -74 l0
|
||||
-34 80 -1 c79 -1 80 -1 80 23 0 40 21 74 51 85 57 20 112 -21 112 -83 0 -17 5
|
||||
-26 16 -26 68 0 143 -50 172 -115 17 -37 19 -74 19 -455 0 -265 -4 -429 -11
|
||||
-454 -6 -23 -28 -55 -55 -81 l-44 -43 85 -89 c91 -94 101 -118 63 -153 -41
|
||||
-38 -61 -28 -194 105 l-125 125 -168 0 -167 0 -128 -125 c-136 -134 -157 -145
|
||||
-196 -103 -35 38 -26 60 65 155 l85 88 -35 30 c-19 16 -44 48 -55 70 -18 38
|
||||
-19 65 -19 468 -1 404 1 431 19 467 32 63 105 110 171 110 11 0 16 8 14 23 -3
|
||||
48 53 104 93 91 8 -2 17 -4 21 -4z"/>
|
||||
<path d="M819 1532 c-48 -15 -65 -75 -31 -106 16 -14 43 -16 172 -17 169 0
|
||||
190 7 190 62 0 25 -21 57 -38 60 -22 4 -282 4 -293 1z"/>
|
||||
<path d="M735 1342 c-93 -5 -94 -6 -94 -192 0 -143 2 -160 19 -175 18 -16 48
|
||||
-18 301 -18 l282 -1 18 23 c17 20 19 42 19 176 0 133 -2 154 -18 168 -15 14
|
||||
-52 17 -242 19 -124 2 -252 2 -285 0z"/>
|
||||
<path d="M685 741 c-35 -22 -50 -67 -35 -107 12 -32 55 -57 93 -56 44 1 88 50
|
||||
81 89 -9 52 -15 63 -43 78 -34 17 -63 16 -96 -4z"/>
|
||||
<path d="M1138 745 c-49 -27 -58 -97 -18 -137 55 -54 139 -31 154 43 7 38 -9
|
||||
71 -44 92 -34 21 -56 21 -92 2z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
42
src/index.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TestMVC</title>
|
||||
<link href="fonts/fonts.css" rel="stylesheet">
|
||||
<link href="css/mui.custom.css" rel="stylesheet" type="text/css"/>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="mask-icon" href="/img/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="apple-mobile-web-app-title" content="Train Times">
|
||||
<meta name="application-name" content="Train Times">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<header id="header">
|
||||
<div class="mui-appbar mui--appbar-line-height mui--z2">
|
||||
<div class='mui-col-xs-8 mui-col-md-8 mui--appbar-height'>Train Times</div>
|
||||
<div class='mui-col-xs-4 mui-col-md-4 mui--appbar-height'></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="mui--appbar-height"></div>
|
||||
<div class="mui-container">
|
||||
<div id="trains"></div>
|
||||
|
||||
<div id='trainResults' class="mui--hide"></div>
|
||||
</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>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
47
src/js/app.js
Normal file
@ -0,0 +1,47 @@
|
||||
require('muicss');
|
||||
|
||||
const { TrainModel, TrainView } = require('./train');
|
||||
const { RouteModel, RouteView } = require('./route');
|
||||
|
||||
const Minibus = require('minibus');
|
||||
|
||||
(function () {
|
||||
const bus = Minibus.create();
|
||||
|
||||
const app = {
|
||||
'routes' : [
|
||||
{ 'from': 'dbe', 'to': 'glq' },
|
||||
{ 'from': 'glq', 'to': 'dbe' },
|
||||
{ 'from': 'glq', 'to': 'hym' },
|
||||
{ 'from': 'hym', 'to': 'glq' }
|
||||
],
|
||||
'views':{}
|
||||
|
||||
};
|
||||
|
||||
const routeView = new RouteView( { 'model': new RouteModel() });
|
||||
|
||||
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 });
|
||||
}
|
||||
};
|
||||
|
||||
/*if ('serviceWorker' in navigator)
|
||||
navigator.serviceWorker
|
||||
.register('./service-worker.js')
|
||||
.then(function() {
|
||||
console.log('Service Worker Registered');
|
||||
});*/
|
||||
|
||||
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' }) });*/
|
||||
})();
|
106
src/js/route.js
Normal file
@ -0,0 +1,106 @@
|
||||
const $ = require('jquery');
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
|
||||
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 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.update();
|
||||
},
|
||||
'update': function () {
|
||||
const now = new Date;
|
||||
const hours = now.getHours();
|
||||
const limit = (hours < 6) ? 3600000 : 60000;
|
||||
|
||||
const mod = limit - (now.getTime() % limit);
|
||||
|
||||
if (hours >= 6)
|
||||
this.getRoute();
|
||||
else
|
||||
this.set('trainData', { 'eta':'OFF', 'sta': 'OFF' });
|
||||
|
||||
const routeUpdateFn = function () {
|
||||
this.update();
|
||||
};
|
||||
|
||||
setTimeout(routeUpdateFn.bind(this), mod + 10);
|
||||
},
|
||||
'getRoute': function () {
|
||||
const url = this.get('routeUrl');
|
||||
const self = this;
|
||||
|
||||
if (this.get('visible') === true)
|
||||
this.set('visible', false);
|
||||
else
|
||||
$.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'
|
||||
|
||||
},
|
||||
'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);
|
||||
}
|
||||
});
|
||||
},
|
||||
'setRoute': function(from, to) {
|
||||
this.set('from', from);
|
||||
this.set('to', to);
|
||||
}
|
||||
});
|
||||
|
||||
const RouteView = Backbone.View.extend({
|
||||
'tagName': 'div',
|
||||
'initialize': function () {
|
||||
_.bindAll(this, 'render');
|
||||
this.model.bind('change', this.render);
|
||||
this.$trains = $('#trains');
|
||||
this.$traininfo = $('#traininfo');
|
||||
this.$traintext = $('#trainResults');
|
||||
this.$el = this.$traintext;
|
||||
|
||||
this.initView();
|
||||
},
|
||||
'events': {
|
||||
'click': 'showTrains'
|
||||
},
|
||||
'render': function () {
|
||||
|
||||
},
|
||||
'initView': function () {
|
||||
},
|
||||
'showTrains': function () {
|
||||
console.log('Show train');
|
||||
},
|
||||
'viewRoute': function(from, to) {
|
||||
console.log('View route', from, to);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = { RouteModel, RouteView };
|
217
src/js/train.js
Normal file
@ -0,0 +1,217 @@
|
||||
/**
|
||||
*
|
||||
* User: Martin Donnelly
|
||||
* Date: 2016-10-03
|
||||
* Time: 14:20
|
||||
*
|
||||
*/
|
||||
const $ = require('jquery');
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const TrainModel = Backbone.Model.extend({
|
||||
'initialize': function () {
|
||||
const fromStation = this.get('from');
|
||||
const toStation = this.get('to');
|
||||
const url = `/getnexttraintimes?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', url);
|
||||
this.set('routeUrl', routeUrl);
|
||||
this.set('target', target);
|
||||
this.set('visible', false);
|
||||
this.set('trainData', { 'eta':'OFF', 'sta': 'OFF' });
|
||||
this.update();
|
||||
},
|
||||
'update': function () {
|
||||
const now = new Date;
|
||||
const hours = now.getHours();
|
||||
const limit = (hours < 6) ? 3600000 : 60000;
|
||||
|
||||
const mod = limit - (now.getTime() % limit);
|
||||
|
||||
if (hours >= 6)
|
||||
this.getTrain();
|
||||
else
|
||||
this.set('trainData', { 'eta':'OFF', 'sta': 'OFF' });
|
||||
|
||||
const trainUpdateFn = function () {
|
||||
this.update();
|
||||
};
|
||||
|
||||
setTimeout(trainUpdateFn.bind(this), mod + 10);
|
||||
},
|
||||
'getTrain': function () {
|
||||
const url = this.get('url');
|
||||
const self = this;
|
||||
$.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'
|
||||
},
|
||||
'success': function (data) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
'getRoute': function () {
|
||||
const url = this.get('routeUrl');
|
||||
const self = this;
|
||||
|
||||
if (this.get('visible') === true)
|
||||
this.set('visible', false);
|
||||
else
|
||||
$.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'
|
||||
|
||||
},
|
||||
'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.error(xhr);
|
||||
console.error(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const TrainView = Backbone.View.extend({
|
||||
'tagName': 'div',
|
||||
'initialize': function () {
|
||||
_.bindAll(this, 'render');
|
||||
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'));
|
||||
},
|
||||
'events': {
|
||||
'click': 'showTrains'
|
||||
},
|
||||
'render': function () {
|
||||
const obj = this.model.get('trainData');
|
||||
const visible = this.model.get('visible');
|
||||
const route = this.model.get('route');
|
||||
|
||||
const output = (obj.eta.toLowerCase() === 'on time') ? obj.sta : obj.eta;
|
||||
const status = (obj.eta.toLowerCase() === 'on time') ? 'ontime' : 'delayed';
|
||||
|
||||
this.$button.html(output);
|
||||
this.$button.removeClass('delayed').removeClass('ontime').addClass(status);
|
||||
|
||||
if (visible) {
|
||||
let ws = `<div>${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;
|
||||
|
||||
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>${status}</td>
|
||||
<td>${platform}</td>
|
||||
</tr>`;
|
||||
else
|
||||
ws = `${ws }<tr><td>${dest.locationName} ${via}</td><td>${time}</td>
|
||||
<td colspan="2">❌ ${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');
|
||||
}
|
||||
else
|
||||
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'>
|
||||
<button class="mui-btn mui-btn--flat" id="${target}"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
this.$html = $(html);
|
||||
this.$html.on('click', function () {
|
||||
// console.log(self)
|
||||
self.model.getRoute();
|
||||
});
|
||||
this.$trains.append(this.$html);
|
||||
|
||||
this.$button = $(`#${target}`);
|
||||
|
||||
const output = 'OFF';
|
||||
const status = (output === 'on time') ? 'ontime' : 'delayed';
|
||||
|
||||
this.$button.html(output);
|
||||
this.$button.removeClass('delayed').removeClass('ontime').addClass(status);
|
||||
|
||||
const cevent = `click #${target}`;
|
||||
this.events[cevent] = 'showTrains';
|
||||
},
|
||||
'showTrains': function () {
|
||||
console.log('Show train');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = { TrainModel, TrainView };
|
15
src/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "Train Times",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/img/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"start_url": "/index.html",
|
||||
"imgdisplay": "standalone",
|
||||
"display": "standalone"
|
||||
}
|
91
src/service-worker.js
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
var dataCacheName = 'traintimesData-v1';
|
||||
var cacheName = 'traintimePWA-final-1';
|
||||
var filesToCache = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/js/bundle.js',
|
||||
'/css/common.css'
|
||||
];
|
||||
|
||||
self.addEventListener('install', function(e) {
|
||||
console.log('[ServiceWorker] Install');
|
||||
e.waitUntil(
|
||||
caches.open(cacheName).then(function(cache) {
|
||||
console.log('[ServiceWorker] Caching app shell');
|
||||
return cache.addAll(filesToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', function(e) {
|
||||
console.log('[ServiceWorker] Activate');
|
||||
e.waitUntil(
|
||||
caches.keys().then(function(keyList) {
|
||||
return Promise.all(keyList.map(function(key) {
|
||||
if (key !== cacheName && key !== dataCacheName) {
|
||||
console.log('[ServiceWorker] Removing old cache', key);
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
/*
|
||||
* Fixes a corner case in which the app wasn't returning the latest data.
|
||||
* You can reproduce the corner case by commenting out the line below and
|
||||
* then doing the following steps: 1) load app for first time so that the
|
||||
* initial New York City data is shown 2) press the refresh button on the
|
||||
* app 3) go offline 4) reload the app. You expect to see the newer NYC
|
||||
* data, but you actually see the initial data. This happens because the
|
||||
* service worker is not yet activated. The code below essentially lets
|
||||
* you activate the service worker faster.
|
||||
*/
|
||||
return self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(e) {
|
||||
console.log('[Service Worker] Fetch', e.request.url);
|
||||
var dataUrl = '/getnexttraintimes?';
|
||||
if (e.request.url.indexOf(dataUrl) > -1) {
|
||||
console.log('!');
|
||||
/*
|
||||
* When the request URL contains dataUrl, the app is asking for fresh
|
||||
* weather data. In this case, the service worker always goes to the
|
||||
* network and then caches the response. This is called the "Cache then
|
||||
* network" strategy:
|
||||
* https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
|
||||
*/
|
||||
e.respondWith(
|
||||
caches.open(dataCacheName).then(function(cache) {
|
||||
return fetch(e.request).then(function(response){
|
||||
cache.put(e.request.url, response.clone());
|
||||
return response;
|
||||
});
|
||||
})
|
||||
);
|
||||
} else {
|
||||
/*
|
||||
* The app is asking for app shell files. In this scenario the app uses the
|
||||
* "Cache, falling back to the network" offline strategy:
|
||||
* https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
|
||||
*/
|
||||
e.respondWith(
|
||||
caches.match(e.request).then(function(response) {
|
||||
return response || fetch(e.request);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|